Merge lp:~dandrader/qtmir/promptBeforeSurfaceDraws into lp:qtmir

Proposed by Daniel d'Andrada
Status: Merged
Approved by: Nick Dedekind
Approved revision: 486
Merged at revision: 485
Proposed branch: lp:~dandrader/qtmir/promptBeforeSurfaceDraws
Merge into: lp:qtmir
Diff against target: 138 lines (+78/-2)
5 files modified
src/modules/Unity/Application/mirsurfacelistmodel.cpp (+5/-0)
src/modules/Unity/Application/mirsurfacelistmodel.h (+1/-0)
src/modules/Unity/Application/session.cpp (+23/-2)
src/modules/Unity/Application/session.h (+6/-0)
tests/modules/SessionManager/session_test.cpp (+43/-0)
To merge this branch: bzr merge lp:~dandrader/qtmir/promptBeforeSurfaceDraws
Reviewer Review Type Date Requested Status
Nick Dedekind (community) Approve
Unity8 CI Bot (community) continuous-integration Approve
Review via email: mp+294297@code.launchpad.net

Commit message

Session: Add a blank surface to the public list if it already has child prompt surfaces

A prompt session may come in too early, before its parent session got a chance
to draw its surface's first frame.

Normally that wouldn't be a problem and we could withhold that parent surface until
it's finally drawn to. But unfortunately the application process might be blocked,
unable to draw anything, until its child prompt session gets dismissed.

Because of that we have no option but to expose this blank surface to shell so that
it can display it along with the prompt surface on top of it, so that the user can
interact with it right away and finally unblock the application.

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.

* Did you perform an exploratory manual test run of your code change and any related functionality?
Yes.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

To post a comment you must log in.
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
486. By Daniel d'Andrada

Fix MirSurfaceListModel clean up

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:486
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/212/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/1575
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/1532
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/1532
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1532/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1532/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1532/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1532/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1532/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1532
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1532/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/212/rebuild

review: Approve (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

This seems to be a cheeky hack around the fact that the surface hasn't had it's first frame drawn and is therefore not "visible". After hacking in the fact that at the moment the prompts are a child of a session, not a surface.

What happens if the surface hadn't been drawn, we create the prompt and append to the blank surface and then suddenly the surface is drawn. The prompt will still be childed to the blank surface which I think is wrong.

I think the fact that the surface "isn't ready" to be shown should be dealt with in unity8. Maybe use the MirSurface::visible property?

bool MirSurface::visible() {
    return m_firstFrameDrawn &&
        m_surface->query(mir_surface_attrib_visibility) == mir_surface_visibility_exposed;
}

SurfaceContainer {
    MirSurfaceItem {
        visible: surface.visible
    }
}

+ add in checks for visibility in animations.

IMO it's all just a bit wrong at the moment.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 11/05/2016 06:17, Nick Dedekind wrote:
> The prompt will still be childed to the blank surface which I think is wrong.

So you think a prompt surface should be treated as a top-level
application surface? Or is it the problem just that the parent surface
is still blank?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 11/05/2016 06:17, Nick Dedekind wrote:
> bool MirSurface::visible() {
> return m_firstFrameDrawn &&
> m_surface->query(mir_surface_attrib_visibility) == mir_surface_visibility_exposed;
> }
>
> SurfaceContainer {
> MirSurfaceItem {
> visible: surface.visible
> }
> }
MirSurfaceItem already sets surface visibility based on its own visible
property. And here you make the latter also set the former. Like A sets
B and B sets A, a cyclic data-flow/control.

Besides MirSurface::visible is already used to convey surface
exposure/occlusion information. Here you want to overload that property
to include another meaning. I don't think the two combine well.

Revision history for this message
Gerry Boland (gerboland) wrote :

I suspect we should abandon the "only notify qml of new surface after its first frame" thing - and instead have a flag on the MirSurface{,Item} saying "notDrawnToYet" - instead of this blank surface idea.

Like Nick, I'm not a big fan of the blank surface idea either, but I see why you did it. It is the least complex change to fix the issue.

But the "only notify qml of new surface after its first frame" policy is not a Mir policy, just a QtMir policy, and it seems to hurt us here.

Dropping that policy causes us new problems: how does QML draw a MirSurfaceItem which is backed by a mirSurface which was never drawn to. I suppose we do as you do here: make it blank. unity8 will need work to reflect QtMir's behaviour change too.

This what I envision as the proper fix is a much more involved change, not so easily done if it needs to land quickly. We might need to land this (with a FIXME), but we should investigate a proper fix afterwards.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> On 11/05/2016 06:17, Nick Dedekind wrote:
> > The prompt will still be childed to the blank surface which I think is
> wrong.
>
> So you think a prompt surface should be treated as a top-level
> application surface? Or is it the problem just that the parent surface
> is still blank?

The Mir implementation of prompts has not got surfaces in mind, they're properties of a session. We've just forced it to be a child the first surface.
I'm not saying the current mir impl is correct; and we'll have trouble as soon as 2 top level surfaces require prompts at the same time...

Sorry, I misunderstood the code, but it seems that you might be prepending the surface twice. Once if you add a prompt when it's blank, and again when it's drawn it's first frame.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> On 11/05/2016 06:17, Nick Dedekind wrote:
> > bool MirSurface::visible() {
> > return m_firstFrameDrawn &&
> > m_surface->query(mir_surface_attrib_visibility) ==
> mir_surface_visibility_exposed;
> > }
> >
> > SurfaceContainer {
> > MirSurfaceItem {
> > visible: surface.visible
> > }
> > }
> MirSurfaceItem already sets surface visibility based on its own visible
> property. And here you make the latter also set the former. Like A sets
> B and B sets A, a cyclic data-flow/control.

Sure. opacity, occluded by spinner, or whatever to not show the actual surface.

>
> Besides MirSurface::visible is already used to convey surface
> exposure/occlusion information. Here you want to overload that property
> to include another meaning. I don't think the two combine well.

Well, it's up to us what a exposed property means. Whether it's "occluded" or "should be visible". A separate property would be better but require API.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Will start working on a overhauling of surface.drawnFirstFrame usage and policy.
If people get anxious they can ship this fix/work-around before the afore-mentioned overhauling is ready.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 11/05/2016 09:54, Nick Dedekind wrote:
> Sorry, I misunderstood the code, but it seems that you might be prepending the surface twice. Once if you add a prompt when it's blank, and again when it's drawn it's first frame.
prompt session surfaces are added to its parent session's
promptSurfaceList only in Session::addChildSession. And that's called
only once per prompt session.

Once the once-blank has drawn its first frame it's moved to the
Session's surface list. But that move doesn't affect the surfaces own
promptSurfaceList.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

ok, looks good. (for now ;)

review: Approve
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/mirsurfacelistmodel.cpp'
2--- src/modules/Unity/Application/mirsurfacelistmodel.cpp 2016-04-05 18:58:38 +0000
3+++ src/modules/Unity/Application/mirsurfacelistmodel.cpp 2016-05-10 21:06:43 +0000
4@@ -28,6 +28,11 @@
5 {
6 }
7
8+MirSurfaceListModel::~MirSurfaceListModel()
9+{
10+ Q_EMIT destroyed(this); // Early warning, while MirSurfaceListModel methods can still be accessed.
11+}
12+
13 int MirSurfaceListModel::rowCount(const QModelIndex &parent) const
14 {
15 return !parent.isValid() ? m_surfaceList.size() : 0;
16
17=== modified file 'src/modules/Unity/Application/mirsurfacelistmodel.h'
18--- src/modules/Unity/Application/mirsurfacelistmodel.h 2016-04-05 18:58:38 +0000
19+++ src/modules/Unity/Application/mirsurfacelistmodel.h 2016-05-10 21:06:43 +0000
20@@ -41,6 +41,7 @@
21 Q_OBJECT
22 public:
23 explicit MirSurfaceListModel(QObject *parent = 0);
24+ virtual ~MirSurfaceListModel();
25
26 Q_INVOKABLE unity::shell::application::MirSurfaceInterface *get(int index) override;
27 const unity::shell::application::MirSurfaceInterface *get(int index) const;
28
29=== modified file 'src/modules/Unity/Application/session.cpp'
30--- src/modules/Unity/Application/session.cpp 2016-04-06 16:52:49 +0000
31+++ src/modules/Unity/Application/session.cpp 2016-05-10 21:06:43 +0000
32@@ -199,8 +199,17 @@
33 if (newSurface->isFirstFrameDrawn()) {
34 prependSurface(newSurface);
35 } else {
36- connect(newSurface, &MirSurfaceInterface::firstFrameDrawn,
37- this, [this, newSurface]() { this->prependSurface(newSurface); });
38+ m_blankSurfaces.append(newSurface);
39+ connect(newSurface, &QObject::destroyed, this, [this, newSurface]()
40+ {
41+ this->m_blankSurfaces.removeAll(newSurface);
42+ });
43+ connect(newSurface, &MirSurfaceInterface::firstFrameDrawn, this, [this, newSurface]()
44+ {
45+ this->m_blankSurfaces.removeAll(newSurface);
46+ newSurface->disconnect(this);
47+ this->prependSurface(newSurface);
48+ });
49 }
50 }
51
52@@ -389,6 +398,18 @@
53 // we assume that the top-most surface is the one that caused the prompt session to show up.
54 auto promptSurfaceList = static_cast<MirSurfaceListModel*>(m_surfaceList.get(0)->promptSurfaceList());
55 promptSurfaceList->addSurfaceList(session->surfaceList());
56+ } else if (m_blankSurfaces.count() > 0) {
57+ // Prompt session came in too early.
58+ // Parent session might be blocked until the prompt session is dismissed.
59+ // So we cannot wait anymore and must put that blank surface on the surface list so that the
60+ // user can see the prompt surface on top of that it and interact.
61+ auto blankSurface = m_blankSurfaces.takeFirst();
62+
63+ auto promptSurfaceList = static_cast<MirSurfaceListModel*>(blankSurface->promptSurfaceList());
64+ promptSurfaceList->addSurfaceList(session->surfaceList());
65+
66+ blankSurface->disconnect(this);
67+ prependSurface(blankSurface);
68 }
69 }
70
71
72=== modified file 'src/modules/Unity/Application/session.h'
73--- src/modules/Unity/Application/session.h 2016-03-28 18:02:26 +0000
74+++ src/modules/Unity/Application/session.h 2016-05-10 21:06:43 +0000
75@@ -107,6 +107,12 @@
76 std::shared_ptr<mir::scene::Session> m_session;
77 Application* m_application;
78 MirSurfaceListModel m_surfaceList;
79+
80+ // Registered surfaces that haven't yet drawn their first frames.
81+ // Once they do, they get moved to m_surfaceList.
82+ // So this list is just a temporary holder and most of the time it's gonna be empty.
83+ QList<MirSurfaceInterface*> m_blankSurfaces;
84+
85 SessionModel* m_children;
86 bool m_fullscreen;
87 State m_state;
88
89=== modified file 'tests/modules/SessionManager/session_test.cpp'
90--- tests/modules/SessionManager/session_test.cpp 2016-03-28 18:02:26 +0000
91+++ tests/modules/SessionManager/session_test.cpp 2016-05-10 21:06:43 +0000
92@@ -311,3 +311,46 @@
93 EXPECT_EQ(Session::Stopped, session->state());
94 EXPECT_FALSE(session->m_suspendTimer->isRunning());
95 }
96+
97+/*
98+ Regression test for https://bugs.launchpad.net/ubuntu/+source/qtmir/+bug/1578665
99+
100+ A child prompt session was arriving before the application surface got a chance to draw
101+ its first frame. And on top of that, the application was blocked from drawing anything
102+ until that prompt session gone away.
103+ */
104+TEST_F(SessionTests, ChildSessionArrivesBeforeSurfaceDrawnFirstFrame)
105+{
106+ using namespace testing;
107+
108+ const QString appId("test-app");
109+ const pid_t procId = 5551;
110+
111+ auto mirSession = std::make_shared<MockSession>(appId.toStdString(), procId);
112+
113+ auto session = std::make_shared<qtmir::Session>(mirSession, promptSessionManager);
114+
115+ FakeMirSurface *blankSurface = new FakeMirSurface;
116+ session->registerSurface(blankSurface);
117+
118+ // Surface doesn't appear in this list as it has not drawn its first frame yet
119+ EXPECT_EQ(0, session->surfaceList()->count());
120+
121+ Session* childSession = new Session(mirSession, promptSessionManager);
122+ session->addChildSession(childSession);
123+
124+ FakeMirSurface *promptSurface = new FakeMirSurface;
125+ childSession->registerSurface(promptSurface);
126+ promptSurface->drawFirstFrame();
127+
128+ // Even though blankSurface still hasn't drawn its first frame (ie, it's still blank),
129+ // it should now appear in this list as it now has a child prompt surface, which the
130+ // user must interact with.
131+ auto sessionSurfaceList = static_cast<MirSurfaceListModel*>(session->surfaceList());
132+ EXPECT_EQ(1, sessionSurfaceList->count());
133+ EXPECT_TRUE(sessionSurfaceList->contains(blankSurface));
134+
135+ auto promptSurfaceList = static_cast<MirSurfaceListModel*>(blankSurface->promptSurfaceList());
136+ EXPECT_EQ(1, promptSurfaceList->count());
137+ EXPECT_TRUE(promptSurfaceList->contains(promptSurface));
138+}

Subscribers

People subscribed via source and target branches