Merge lp:~dandrader/unity8/miral into lp:unity8

Proposed by Daniel d'Andrada
Status: Superseded
Proposed branch: lp:~dandrader/unity8/miral
Merge into: lp:unity8
Diff against target: 5243 lines (+2201/-1371)
41 files modified
CMakeLists.txt (+1/-1)
debian/control (+3/-3)
plugins/WindowManager/CMakeLists.txt (+3/-1)
plugins/WindowManager/TopLevelSurfaceList.cpp (+0/-481)
plugins/WindowManager/TopLevelSurfaceList.h (+0/-223)
plugins/WindowManager/TopLevelWindowModel.cpp (+644/-0)
plugins/WindowManager/TopLevelWindowModel.h (+258/-0)
plugins/WindowManager/Window.cpp (+234/-0)
plugins/WindowManager/Window.h (+161/-0)
plugins/WindowManager/WindowManagerPlugin.cpp (+5/-2)
qml/Components/InputMethod.qml (+5/-4)
qml/Components/KeymapSwitcher.qml (+4/-1)
qml/Shell.qml (+14/-6)
qml/Stage/DecoratedWindow.qml (+1/-1)
qml/Stage/FakeMaximizeDelegate.qml (+7/-7)
qml/Stage/Stage.qml (+136/-119)
qml/Stage/StagedFullscreenPolicy.qml (+4/-4)
qml/Stage/TopLevelSurfaceRepeater.qml (+0/-67)
qml/Stage/WindowDecoration.qml (+4/-5)
qml/Stage/WindowedFullscreenPolicy.qml (+1/-1)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+45/-24)
tests/mocks/Unity/Application/ApplicationInfo.h (+2/-2)
tests/mocks/Unity/Application/ApplicationManager.cpp (+92/-61)
tests/mocks/Unity/Application/ApplicationManager.h (+31/-4)
tests/mocks/Unity/Application/CMakeLists.txt (+1/-1)
tests/mocks/Unity/Application/MirSurface.cpp (+85/-98)
tests/mocks/Unity/Application/MirSurface.h (+25/-27)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+4/-4)
tests/mocks/Unity/Application/MirSurfaceItem.h (+1/-2)
tests/mocks/Unity/Application/MirSurfaceListModel.cpp (+12/-34)
tests/mocks/Unity/Application/MirSurfaceListModel.h (+2/-7)
tests/mocks/Unity/Application/SurfaceManager.cpp (+169/-26)
tests/mocks/Unity/Application/SurfaceManager.h (+28/-10)
tests/mocks/Unity/Application/plugin.cpp (+2/-27)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+4/-0)
tests/qmltests/Stage/ApplicationCheckBox.qml (+1/-1)
tests/qmltests/Stage/tst_DesktopStage.qml (+50/-40)
tests/qmltests/Stage/tst_PhoneStage.qml (+18/-12)
tests/qmltests/Stage/tst_TabletStage.qml (+16/-10)
tests/qmltests/tst_Shell.qml (+124/-50)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+4/-5)
To merge this branch: bzr merge lp:~dandrader/unity8/miral
Reviewer Review Type Date Requested Status
Unity8 CI Bot continuous-integration Needs Fixing
Michael Zanetti (community) Needs Fixing
Review via email: mp+309790@code.launchpad.net

This proposal has been superseded by a proposal from 2016-11-23.

Commit message

Let the model deal with some window management decisions

eg: which window to focus, whether to change surface state

unity8 requests and reacts to changes in the model instead of applying them

Description of the change

Prereq-archive: ppa:ci-train-ppa-service/ubuntu/2160

For the upcoming, miral-powered, qtmir.

One notable introduction is the Window object, which is a sligthly higher level abstraction of a MirSurface (and a drop-in replacement for it) to be used by Stage.qml. Window always exists in a TopLevelWindowModel entry so that QML code no longer has to if(){}else{} for the existence of a MirSurface (because of the splash screen case)

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

https://code.launchpad.net/~unity-team/unity-api/miral/+merge/309938
https://code.launchpad.net/~unity-team/qtmir/miral-qt-integration/+merge/310001

It's all in this silo: https://bileto.ubuntu.com/#/ticket/2160

* 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

* If you changed the UI, has there been a design review?
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)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2697
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2487/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3279/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3307
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3161/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3161/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3161/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

* See a bunch of inline comments

* I think we should not give up on the differntiation between claimFocus() and requestFocus(). One of them says the stage wants to actually focus something *now* while the other indicates that the app requested focus on its own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still do the raisId() call on the model. requestfocus() instead should end up in the shell, and if the shell is ok with doing so, it should call claimFocus() itself.

* IMO you're exaggerating with the debug messages. unity8.toplevelsurfacelist was already very annoying and this seems to have even more prints. Sure, you can argue that I could turn them off with the debug filter, which is what I do, but then I get none any more while the most important ones like raise() added() and removed() would still make sense.

This has been a code review for the code up until the mocks. Still needs review for tests/mocks and especially in depth testing.

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Not yet sure if just an issue in the mocks or not, but this breaks:

- open an app
- click the minimize button in the window decoration
- click the app again in the launcher to restore it
- now click the minimize button again
=> button doesn't work any more

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

On 18/11/2016 12:29, Michael Zanetti wrote:
> * IMO you're exaggerating with the debug messages. unity8.toplevelsurfacelist was already very annoying and this seems to have even more prints. Sure, you can argue that I could turn them off with the debug filter, which is what I do, but then I get none any more while the most important ones like raise() added() and removed() would still make sense.

Reduced the logging output. Should be the way you prefer now.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

- have 3 apps (a, b and c), make them overlap a bit for better visibility
- focus a by clicking it
- focus b by clicking it
- focus c by clicking it
- minimize c
=> a will be focused

expected: after minimizing c, b should be focused

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

On 18/11/2016 12:29, Michael Zanetti wrote:
> * I think we should not give up on the differntiation between claimFocus() and requestFocus(). One of them says the stage wants to actually focus something*now* while the other indicates that the app requested focus on its own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still do the raisId() call on the model. requestfocus() instead should end up in the shell, and if the shell is ok with doing so, it should call claimFocus() itself.

What you are explaining effectively is that: leave things as they are
currently with regards to focus. Which means focus lives in unity8 and
miral has no say over it.

The whole point of this miral effort is exactly to move such window
management decisions down to Mir, as dictated by our high-profile
stakeholder.

On the bright side, focus requests made by unity8 should naturally
always be sensible (since it knows the situation pretty well) and
therefore miral should always swiftly comply. So in terms of end
results, those requests will work just like imperative assignments.

Focus is a central piece of window management and if miral is to do
anything useful it has to have a say in it. And we cannot have two
entities making decisions on focus (unity8 and miral) as it will
inevitably be racy and conflictive.

As for the origin of the focus change request (shell vs. application),
it doesn't matter from miral's standpoint. It will comply to either as
long the change is valid and makes sense. Eg.: it should never allow a
window blocked by a child modal dialog to be focused. So it's mostly
sanity checking. I don't see miral ignoring requests on a whim. :)

Besides, having two separate code paths would make unity8 code more
complex. Eg: Having both code that sets surface state and code that
responds to surface state changes. "If surface state change was done in
response my your own set(), do nothing, else, ponder and respond."

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

FAILED: Continuous integration, rev:2698
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2512/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3317/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3345
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3197/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3197/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3197/artifact/output/*zip*/output.zip

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

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +int TopLevelWindowModel::nextFreeId(int candidateId)
>> >+{
>> >+ if (candidateId > m_maxId) {
>> >+ return nextFreeId(1);
>> >+ } else {
>> >+ if (indexForId(candidateId) == -1) {
>> >+ // it's indeed free
>> >+ return candidateId;
>> >+ } else {
>> >+ return nextFreeId(candidateId + 1);
>> >+ }
>> >+ }
>> >+}
> In the very unlikely event that we'd run out of free ids, this would crash with a stack overflow. Think it makes sense to write this in a non-recursive manner and perhaps add some way to actually handle failures?
>
If we run out of free ids it's because there's already a bug somewhere.
There's no way there's a valid situation where we would have literally a
million windows lying around.

But a non-recursive version is indeed lighter-weight on resources if it
ever has to traverse a long stretch of taken ids. Done.

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

FAILED: Continuous integration, rev:2699
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2513/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3318/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3346
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3198/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3198/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3198/artifact/output/*zip*/output.zip

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

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +QString TopLevelWindowModel::toString()
>> >+{
>> >+ QString str;
>> >+ for (int i = 0; i < m_windowModel.count(); ++i) {
>> >+ auto item = m_windowModel.at(i);
>> >+
>> >+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
>> >+ .arg(i)
>> >+ .arg(item.application->appId())
>> >+ .arg((qintptr)item.window->surface(), 0, 16)
>> >+ .arg(item.window->id());
> it would be slightly more performant if you'd use QString::arg(QString, QString, QSttring, QString) instead as it has to walk the string only once with that...
>
Done.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +public Q_SLOTS:
>> >+ /**
>> >+ * @brief Returns the surface at the given index
>> >+ *
>> >+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
>> >+ * and drawn into a surface.
>> >+ *
>> >+ * Same as windowAt(index).surface()
>> >+ */
>> >+ unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the window at the given index
>> >+ *
>> >+ * Will always be valid
>> >+ */
>> >+ Window *windowAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the application at the given index
>> >+ */
>> >+ unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the unique id of the element at the given index
>> >+ */
>> >+ int idAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the index where the row with the given id is located
>> >+ *
>> >+ * Returns -1 if there's no row with the given id.
>> >+ */
>> >+ int indexForId(int id) const;
> slots with return value are weird. All the above should be public Q_INVOKABLE instead IMO.

Which uglifies the header file. Not great either.

Done.

>
>> >+
>> >+ /**
>> >+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
>> >+ */
>> >+ void raiseId(int id);
> this makes sense as slot indeed
>
But it's only a slot so it can be called from QML. Made it a Q_INVOKABLE
as well to match the others.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +Window::Window(int id)
>> >+ : QObject(nullptr)
>> >+ , m_id(id)
>> >+{
>> >+ DEBUG_MSG << "()";
>> >+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
> is there a reason why you can't use QObject's parent instead?

Laziness. Done.

>
> Would make sense in any case to parent the Window objects properly IMO
>
Yes, that's a nice safety net.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +QString Window::toString() const
>> >+{
>> >+ if (surface()) {
>> >+ return QString("Window[0x%1, id=%2, MirSurface[0x%3,\"%4\"]]")
>> >+ .arg((quintptr)this, 0, 16)
>> >+ .arg(id())
>> >+ .arg((quintptr)surface(), 0, 16)
>> >+ .arg(surface()->name());
> same here... if this is only ever executed in certain debug modes, ok with me, but if this is executed in production code too, please use the single .arg(..., ..., ...) call for better performance.
>
Done.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
> gah I despise this overly excessive namespacing...

Me too! Nested namespaces are an eyesore.

> IMO you could just do "using namespace unity::shell::application;" and be done with this, really... It's not like we'd ever have a conflicting MirSurfaceInterface class somewhere else...
>
Not in a header file though. I think that's bad form. A .cpp file
#including this header would inadvertently get the
unity::shell::application namespace included in their main one.

I'm also scared of doing namespace manipulations in Qt macros. That
might go wrong.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + qRegisterMetaType<Window*>("Window*");
> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>

Not sure. qmlRegisterUncreatableType() documentation says: "This is
useful where the type is only intended for providing attached properties
or enum values."
Not the case here.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> - onMinimizeClicked: root.minimizeClicked();
>> >+ onMinimizeClicked: {
>> >+ if (applicationWindow.surface) {
>> >+ applicationWindow.surface.requestState(Mir.MinimizedState);
>> >+ }
>> >+ }
> this seems odd... why would you refactor DecoratedWindow to not directly operate on the data any more but instead pass everything outside with signals in a earlier branch, but now start introducing this somewhat messy behavior here again? IMO this should also be passed to outside to the Stage, and only the stage being in charge to actually execute things on the surfaces.
>
That's some old change that pre-dates that branch you mentioned.
Reverted.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> - function focusNext() {
> same as I mentioned earlier. I'm still a bit scared of dropping control over this from the qml part, but lets see how it works... At least now that the model is part of unity8 again, it seems easier to change that back or modify in yet another way if we need to.
>
This is a perfect case where it makes sense to delegate to the C++
model. Choosing the next window do be focused when the current one is
minimized is a bunch of imperative code (and WM policy) that fits better
in C++ and not mixed with animation code in QML.

Particularly now that the model knows everything (who's focused and the
position and state of all surfaces).

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

FAILED: Continuous integration, rev:2700
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2514/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3319/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3347
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3199/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3199/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3199/artifact/output/*zip*/output.zip

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

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> if (!shown && priv.mainStageDelegate && !root.spreadShown) {
>> >- priv.mainStageDelegate.claimFocus();
>> >+ priv.mainStageDelegate.requestFocus();
> ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>
> Can you think of a way to keep that separation perhaps?
>
Heh, I actually think it is a good thing to handle them the same way (as
I said in some other comment).

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + function updateQmlFocusFromMirSurfaceFocus() {
>> >+ if (model.window.focused) {
>> > claimFocus();
>> >+ priv.focusedAppDelegate = appDelegate;
>> >+ //} else if (priv.focusedAppDelegate === appDelegate && root.state != "spread") {
>> >+ // priv.focusedAppDelegate = null;
> leftover?
>
Yes, removed.

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

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + onMaximizeHorizontallyClicked: {
>> >+ if (appDelegate.maximizedHorizontally)
>> >+ appDelegate.requestRestore();
>> >+ else
>> >+ appDelegate.requestMaximizeHorizontally();
>> >+ }
> {} missing
>
>> >+ onMaximizeVerticallyClicked: {
>> >+ if (appDelegate.maximizedVertically)
>> >+ appDelegate.requestRestore();
>> >+ else
>> >+ appDelegate.requestMaximizeVertically();
>> >+ }
> {} missing
>

Not sure if that's in our coding style but ok, added.

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

On 18/11/2016 14:16, Michael Zanetti wrote:
> Review: Needs Fixing
>
> Not yet sure if just an issue in the mocks or not, but this breaks:
>
> - open an app
> - click the minimize button in the window decoration
> - click the app again in the launcher to restore it
> - now click the minimize button again
> => button doesn't work any more

Bug in the mock. Works with qtmir.

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

FAILED: Continuous integration, rev:2705
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2515/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3320/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3348
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3200/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3200/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3200/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2706
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2516/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3321/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3349
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3201/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3201/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3201/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

>>>> - priv.mainStageDelegate.claimFocus();
>>>> >> >+ priv.mainStageDelegate.requestFocus();
>> > ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>> >
>> > Can you think of a way to keep that separation perhaps?
>> >
> Heh, I actually think it is a good thing to handle them the same way (as
> I said in some other comment).

How does focus stealing prevention come into play here? I believe the
plan was to only grant focus to apps that send along a ~MirCookie based
on a timestamp. I believe it's described here:

https://docs.google.com/document/d/1L85DdfDd3lDbvchYbgQ45C_lJ1IeTMG4uc7Nuq_XdAE/edit#heading=h.nlenkblnkaqq

Revision history for this message
Michael Zanetti (mzanetti) wrote :

On 18.11.2016 21:09, Daniel d'Andrada wrote:
> On 18/11/2016 12:29, Michael Zanetti wrote:
>>> + qRegisterMetaType<Window*>("Window*");
>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>
>
> Not sure. qmlRegisterUncreatableType() documentation says: "This is
> useful where the type is only intended for providing attached properties
> or enum values."
> Not the case here.

I'm more thinking about being able to access the Window type in QML
altogether. Not really about providing attached properties etc, but it
could make sense to do pass the Window object to somewhere in QML and
access its property there without having access to the model.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> On 18/11/2016 12:29, Michael Zanetti wrote:
> > * I think we should not give up on the differntiation between claimFocus()
> and requestFocus(). One of them says the stage wants to actually focus
> something*now* while the other indicates that the app requested focus on its
> own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still
> do the raisId() call on the model. requestfocus() instead should end up in the
> shell, and if the shell is ok with doing so, it should call claimFocus()
> itself.
>
> What you are explaining effectively is that: leave things as they are
> currently with regards to focus. Which means focus lives in unity8 and
> miral has no say over it.
>
> The whole point of this miral effort is exactly to move such window
> management decisions down to Mir, as dictated by our high-profile
> stakeholder.

There's a bunch of other stakeholders though that want to have it pixel perfect when it comes to animations and transitions. I am not saying miral should not have any say, but miral should send a requestFocus() to the shell and the shell will do it.
What I'm saying is that miral can tell unity to change the focus, and unity will follow that (unless it's in the middle of a transition or something). What you're proposing is that miral has all the say on when to change focus, and unity8 cannot do any course correction if the moment is bad.

> Focus is a central piece of window management and if miral is to do
> anything useful it has to have a say in it. And we cannot have two
> entities making decisions on focus (unity8 and miral) as it will
> inevitably be racy and conflictive.

which is exactly what we have now... unity8 thinks it is in charge, but miral can just jump in and trash it. I'm really just trying to avoid that.

>
> As for the origin of the focus change request (shell vs. application),
> it doesn't matter from miral's standpoint. It will comply to either as
> long the change is valid and makes sense. Eg.: it should never allow a
> window blocked by a child modal dialog to be focused. So it's mostly
> sanity checking. I don't see miral ignoring requests on a whim. :)
>
> Besides, having two separate code paths would make unity8 code more
> complex. Eg: Having both code that sets surface state and code that
> responds to surface state changes. "If surface state change was done in
> response my your own set(), do nothing, else, ponder and respond."

And I'm also not talking about 2 separate code paths. I'm saying the one code path should go through unity8 and not just manipulate the model at random.

Therefore I think only unity8 should manipulate the model, taking miral as consultant on how to manipulate it. This current approach is really the one that creates multiple entities fighting over the state of the model.

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

On 18/11/2016 14:21, Michael Zanetti wrote:
> Review: Needs Fixing
>
> - have 3 apps (a, b and c), make them overlap a bit for better visibility
> - focus a by clicking it
> - focus b by clicking it
> - focus c by clicking it
> - minimize c
> => a will be focused
>
> expected: after minimizing c, b should be focused

Can't reproduce in "make tryDesktopStage". Need more specific steps
(like exact test and apps launched).

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

On 21/11/2016 07:27, Michael Zanetti wrote:
>
> On 18.11.2016 21:09, Daniel d'Andrada wrote:
>> On 18/11/2016 12:29, Michael Zanetti wrote:
>>>> + qRegisterMetaType<Window*>("Window*");
>>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>>
>> Not sure. qmlRegisterUncreatableType() documentation says: "This is
>> useful where the type is only intended for providing attached properties
>> or enum values."
>> Not the case here.
> I'm more thinking about being able to access the Window type in QML
> altogether. Not really about providing attached properties etc, but it
> could make sense to do pass the Window object to somewhere in QML and
> access its property there without having access to the model.
>
>
This already happens in Stage.qml:

"""
x: model.window.position.x - decoratedWindow.contentX
y: model.window.position.y - decoratedWindow.contentY
"""

Looks like qmlRegisterUncreatableType() is not needed for that. If think
you only need it for Window.foo type of constructs.

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

On 21/11/2016 06:48, Michał Sawicz wrote:
>>>>> - priv.mainStageDelegate.claimFocus();
>>>>>>>> + priv.mainStageDelegate.requestFocus();
>>>> ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>>>>
>>>> Can you think of a way to keep that separation perhaps?
>>>>
>> Heh, I actually think it is a good thing to handle them the same way (as
>> I said in some other comment).
> How does focus stealing prevention come into play here? I believe the
> plan was to only grant focus to apps that send along a ~MirCookie based
> on a timestamp. I believe it's described here:
>
> https://docs.google.com/document/d/1L85DdfDd3lDbvchYbgQ45C_lJ1IeTMG4uc7Nuq_XdAE/edit#heading=h.nlenkblnkaqq
>
>
This happens at a level lower than unity8. All unity8 knows and cares
about is that some surface in the model got focus.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

On 21.11.2016 12:09, Daniel d'Andrada wrote:
> On 21/11/2016 07:27, Michael Zanetti wrote:
>>
>> On 18.11.2016 21:09, Daniel d'Andrada wrote:
>>> On 18/11/2016 12:29, Michael Zanetti wrote:
>>>>> + qRegisterMetaType<Window*>("Window*");
>>>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>>>
>>> Not sure. qmlRegisterUncreatableType() documentation says: "This is
>>> useful where the type is only intended for providing attached properties
>>> or enum values."
>>> Not the case here.
>> I'm more thinking about being able to access the Window type in QML
>> altogether. Not really about providing attached properties etc, but it
>> could make sense to do pass the Window object to somewhere in QML and
>> access its property there without having access to the model.
>>
>>
> This already happens in Stage.qml:
>
> """
> x: model.window.position.x - decoratedWindow.contentX
> y: model.window.position.y - decoratedWindow.contentY
> """
>
> Looks like qmlRegisterUncreatableType() is not needed for that. If think
> you only need it for Window.foo type of constructs.

Hmm, ok... fine then.

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

FAILED: Continuous integration, rev:2707
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2517/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3322/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3350
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3202/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3202/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3202/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> On 18/11/2016 14:21, Michael Zanetti wrote:
> > Review: Needs Fixing
> >
> > - have 3 apps (a, b and c), make them overlap a bit for better visibility
> > - focus a by clicking it
> > - focus b by clicking it
> > - focus c by clicking it
> > - minimize c
> > => a will be focused
> >
> > expected: after minimizing c, b should be focused
>
>
> Can't reproduce in "make tryDesktopStage". Need more specific steps
> (like exact test and apps launched).

Hmm... I can't reproduce it any more either after your latest changes.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> On 18/11/2016 14:16, Michael Zanetti wrote:
> > Review: Needs Fixing
> >
> > Not yet sure if just an issue in the mocks or not, but this breaks:
> >
> > - open an app
> > - click the minimize button in the window decoration
> > - click the app again in the launcher to restore it
> > - now click the minimize button again
> > => button doesn't work any more
>
> Bug in the mock. Works with qtmir.

This is really why I think we need to move all those models to unity8, put unity8 in control of them and mock things at a lower level. All our tests regarding window management are just useless right now... they can perfectly pass while the real thing can be broken really badly.

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

On 21/11/2016 10:08, Michael Zanetti wrote:
>> On 18/11/2016 14:16, Michael Zanetti wrote:
>>> Review: Needs Fixing
>>>
>>> Not yet sure if just an issue in the mocks or not, but this breaks:
>>>
>>> - open an app
>>> - click the minimize button in the window decoration
>>> - click the app again in the launcher to restore it
>>> - now click the minimize button again
>>> => button doesn't work any more
>> Bug in the mock. Works with qtmir.
> This is really why I think we need to move all those models to unity8, put unity8 in control of them and mock things at a lower level. All our tests regarding window management are just useless right now... they can perfectly pass while the real thing can be broken really badly.

Window management policy things like "if currently focused window gets
minimized, focus the most recently focused window that is not hidden or
minimized" will now belong to a lower level (miral) and as such they get
mock implementations in our tests.

Thus qml tests that strictly check window management policy will indeed
get redundant. Test of this nature now belong to miral's own test suite.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> On 21/11/2016 10:08, Michael Zanetti wrote:
> >> On 18/11/2016 14:16, Michael Zanetti wrote:
> >>> Review: Needs Fixing
> >>>
> >>> Not yet sure if just an issue in the mocks or not, but this breaks:
> >>>
> >>> - open an app
> >>> - click the minimize button in the window decoration
> >>> - click the app again in the launcher to restore it
> >>> - now click the minimize button again
> >>> => button doesn't work any more
> >> Bug in the mock. Works with qtmir.
> > This is really why I think we need to move all those models to unity8, put
> unity8 in control of them and mock things at a lower level. All our tests
> regarding window management are just useless right now... they can perfectly
> pass while the real thing can be broken really badly.
>
>
> Window management policy things like "if currently focused window gets
> minimized, focus the most recently focused window that is not hidden or
> minimized" will now belong to a lower level (miral) and as such they get
> mock implementations in our tests.
>
> Thus qml tests that strictly check window management policy will indeed
> get redundant. Test of this nature now belong to miral's own test suite.

Right, but we would still need tests that make sure that the QML part works together with the model as we expect it. And right now we don't have such tests... Anyhow, this is a different discussion than this particular merge proposal. It just once again popped up here. Also this isn't a new problem, we already had this since forever, but as long as we only had the one ApplicationManager model which would not even do much except moving things when unity8 said so, it was still easy enough to keep the mock close enough to the real thing. Now this seems to grow out of a manageable size.

Revision history for this message
Gerry Boland (gerboland) wrote :
Download full text (3.3 KiB)

> > On 18/11/2016 12:29, Michael Zanetti wrote:
> > > * I think we should not give up on the differntiation between claimFocus()
> > and requestFocus(). One of them says the stage wants to actually focus
> > something*now* while the other indicates that the app requested focus on
> its
> > own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still
> > do the raisId() call on the model. requestfocus() instead should end up in
> the
> > shell, and if the shell is ok with doing so, it should call claimFocus()
> > itself.
> >
> > What you are explaining effectively is that: leave things as they are
> > currently with regards to focus. Which means focus lives in unity8 and
> > miral has no say over it.
> >
> > The whole point of this miral effort is exactly to move such window
> > management decisions down to Mir, as dictated by our high-profile
> > stakeholder.
>
> There's a bunch of other stakeholders though that want to have it pixel
> perfect when it comes to animations and transitions. I am not saying miral
> should not have any say, but miral should send a requestFocus() to the shell
> and the shell will do it.
> What I'm saying is that miral can tell unity to change the focus, and unity
> will follow that (unless it's in the middle of a transition or something).
> What you're proposing is that miral has all the say on when to change focus,
> and unity8 cannot do any course correction if the moment is bad.
>
> > Focus is a central piece of window management and if miral is to do
> > anything useful it has to have a say in it. And we cannot have two
> > entities making decisions on focus (unity8 and miral) as it will
> > inevitably be racy and conflictive.
>
> which is exactly what we have now... unity8 thinks it is in charge, but miral
> can just jump in and trash it. I'm really just trying to avoid that.
>
> >
> > As for the origin of the focus change request (shell vs. application),
> > it doesn't matter from miral's standpoint. It will comply to either as
> > long the change is valid and makes sense. Eg.: it should never allow a
> > window blocked by a child modal dialog to be focused. So it's mostly
> > sanity checking. I don't see miral ignoring requests on a whim. :)
> >
> > Besides, having two separate code paths would make unity8 code more
> > complex. Eg: Having both code that sets surface state and code that
> > responds to surface state changes. "If surface state change was done in
> > response my your own set(), do nothing, else, ponder and respond."
>
> And I'm also not talking about 2 separate code paths. I'm saying the one code
> path should go through unity8 and not just manipulate the model at random.
>
> Therefore I think only unity8 should manipulate the model, taking miral as
> consultant on how to manipulate it. This current approach is really the one
> that creates multiple entities fighting over the state of the model.

This is a valid concern. The model must only have one owner. I think it safest that Unity8 has total ownership of the model and uses MirAL's recommendations only when it can.

MirAL has no concept of intermediary-states (like transitions/animations cause), so cannot advise Unit...

Read more...

lp:~dandrader/unity8/miral updated
2703. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/miral updated
2704. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

Thanks a lot for the activate() thing. Makes me feel much more comfy with the whole thing.

One minor thing (see inline comments) which I missed last time.

I'll give it another test with the latest changes, but it looks like we're getting close to an approval now.

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

On 23/11/2016 09:24, Michael Zanetti wrote:
>> readonly property Item clientAreaItem: applicationWindow
>> >
>> >+ readonly property real contentX: applicationWindow.x
>> >+ readonly property real contentY: applicationWindow.y
> afaict, you're only using that for knowing how much to add/remove for the decoration height. There is already a property "decorationHeight" you can use so this would be not needed, esp as applicationWindow.y will always be 0 at this point too.
>
Actually I can just use the existing clientAreaItem property right above
them. Fixed.

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

On 23/11/2016 09:24, Michael Zanetti wrote:
> Qt.point(appDelegate.requestedX + decoratedWindow.decorationHeight, appDelegate.requestedY)
>
> should be enough

I would rather not assume that decorations only affect the Y coordinate.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/miral updated
2705. By Andrea Cimitan

Fix white text (LP: #1644468)

Approved by: Albert Astals Cid, Unity8 CI Bot

2706. By Andrea Cimitan

Make MascotLoader in CardCreator flat

Approved by: Albert Astals Cid, Unity8 CI Bot

2707. By Andrea Cimitan

Make the remaining UbuntuShape flat

Approved by: Albert Astals Cid, Unity8 CI Bot

2708. By Andrea Cimitan

added dropshadow from panel indicators to dash page header extra panel, so there is right shadow when the extra panel is narrow

Approved by: Albert Astals Cid, Unity8 CI Bot

2709. By Andrea Cimitan

Look up for expandable template dash category flag

Approved by: Albert Astals Cid, Unity8 CI Bot

2710. By Albert Astals Cid

Fix PreviewProgress implicitHeight

We actually want this to be the height of the progressbar that we fix so use that and not the implicitHeight (LP: #1641943)

Approved by: Andrea Cimitan, Unity8 CI Bot

2711. By Andrea Azzarone

Set Mir.cursorName to "grabbing" on first mouse press on a window decoration. Don't wait for press+motion. (LP: #1618078)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2712. By Andrea Azzarone

Do not show a divider between each quicklist entry but just between each section, similar to how unity7 does. (LP: #1637478)

Approved by: Albert Astals Cid, Unity8 CI Bot

2713. By Daniel d'Andrada

There's no need for WindowDecoration to access the appDelegate

It damages modularity.

Approved by: Michael Zanetti, Unity8 CI Bot

2714. By Brian Douglass

Added a setting to enable/disable the indicator dropdown menu.

Approved by: Michael Zanetti, Unity8 CI Bot

2715. By Michael Zanetti

disable spread interaction while locked (LP: #1641578)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2716. By Olivier Tilloy

Remove dependency on transitional package. (LP: #1583079)

Approved by: Albert Astals Cid, Unity8 CI Bot

2717. By Lukáš Tinkl

Enable brightness (laptop backlight) handling on desktop/laptop PCs (LP: #1595947)

Approved by: Michael Zanetti, Unity8 CI Bot

2718. By Andrea Azzarone

Implement launcher tooltips.

Approved by: Michał Sawicz

2719. By Albert Astals Cid

Adapt to dummy notification being gone

and warning fix as bonus

Approved by: Lukáš Tinkl, Unity8 CI Bot

2720. By Albert Astals Cid

Fix autopilot test_lock_screen tests

Approved by: Andrea Cimitan, Unity8 CI Bot

2721. By Albert Astals Cid

Autopilot: Add more applications to the list for wider screens

Otherwise the test was failing because available_applications was too short

Approved by: Andrea Cimitan, Unity8 CI Bot

2722. By Albert Astals Cid

Fix autopilot DashHelperTestCase.test_search

Approved by: Andrea Cimitan, Unity8 CI Bot

2723. By Albert Astals Cid

Also install the Screens mock

This way we can run some more autopilot tests in X11

Approved by: Lukáš Tinkl, Unity8 CI Bot

2724. By Albert Astals Cid

Give default value to gu-px size

Fixes some of the autopilot greeter tests when run under X11

Approved by: Andrea Cimitan, Unity8 CI Bot

2725. By CI Train Bot Account

Releasing 8.15+17.04.20161129-0ubuntu1

2726. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

2727. By Lukáš Tinkl

Fix the Super key not invoking the dash scope home (LP: #1607427)

Approved by: Daniel d'Andrada, Unity8 CI Bot

2728. By Albert Astals Cid

Add the Wsuggest-override flag to gcc

While at it mark system includes as such so we don't get warnings we can not fix

Approved by: Michael Zanetti, Unity8 CI Bot

2729. By Albert Astals Cid

Add support for compiler sanitizers via ECM

2730. By Albert Astals Cid

Use timeStep as delay time

Passing iterations / speed didn't make much sense since that parameter is a delay in milliseconds and the default parameters would give a value of 5 / units.gu(10) that is smaller than 1 millisecond.

Qt 5.7 calculation for velocity was very unhappy if we moved things so fast in less than 1ms and ignored the movements, so this also makes tests pass on Qt 5.7 (LP: #1642919)

Approved by: Josh Arenson, Unity8 CI Bot

2731. By Michael Zanetti

Add the ApplicationDrawer

Approved by: Lukáš Tinkl, Unity8 CI Bot

2732. By Michael Zanetti

tune right edge push

make it less intrusive when accidentally hitting the edge with the mouse
tweak visuals for the mouse case (LP: #1646094)

Approved by: Unity8 CI Bot

2733. By Michael Zanetti

improve close button visiblity when hovering with the mouse

Approved by: Albert Astals Cid, Unity8 CI Bot

2734. By Albert Astals Cid

Bring back fix for 1517830

Now with autotest \o/ (LP: #1517830)

Approved by: Andrea Cimitan, Unity8 CI Bot

2735. By Daniel d'Andrada

Fix "make tryApplicationWindow"

No surface was showing up on the screen
Also remove outdated button (feature is no longer there)

Approved by: Albert Astals Cid, Unity8 CI Bot

2736. By Albert Astals Cid

Fix compile warnings in mocks

Approved by: Daniel d'Andrada, Unity8 CI Bot

2737. By Josh Arenson

Enable the greeter to remember which session the user last logged into

This also fixes a small issue with how the default session was handled. (LP: #1631365)

Approved by: Albert Astals Cid, Unity8 CI Bot

2738. By Daniel d'Andrada

Take save/restore functions out of WindowResizeArea

They've no relationship with resizing whatsoever.

Approved by: Lukáš Tinkl, Unity8 CI Bot

2739. By Michael Zanetti

Update virtual touchpad visuals and add a tutorial. (LP: #1585220)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2740. By Albert Astals Cid

Do not hide panel when launching an application if the mouse is on the panel

Need Functions.itemUnderMouse because MouseArea.containsMouse returns true when tapping (i.e. no mouse used) on it.

Unfortunately the QML testlib do not set the proper value when issueing a mouseMove so i can't add a test that proofs it works, i'll try to propose something upstream and then add the test at a later MR (LP: #1591311)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2741. By Pete Woods

MenuItemFactory: Add subtitle support to SwitchItem widget

Approved by: Marco Trevisan (Treviño), Michał Sawicz, Unity8 CI Bot

2742. By CI Train Bot Account

Releasing 8.15+17.04.20161207.1-0ubuntu1

2743. By Daniel d'Andrada

Let the model deal with some window management decisions

eg: which window to focus, whether to change surface state

unit8 requests and reacts to changes in the model instead of applying them

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-10-24 11:34:08 +0000
3+++ CMakeLists.txt 2016-11-23 15:15:21 +0000
4@@ -57,7 +57,7 @@
5 find_package(Qt5Concurrent 5.4 REQUIRED)
6 find_package(Qt5Sql 5.4 REQUIRED)
7
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
10 pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
11 pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
12 pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
13
14=== modified file 'debian/control'
15--- debian/control 2016-11-16 10:44:00 +0000
16+++ debian/control 2016-11-23 15:15:21 +0000
17@@ -38,7 +38,7 @@
18 libubuntugestures5-private-dev (>= 1.3.2030),
19 libudev-dev,
20 libudm-common-dev,
21- libunity-api-dev (>= 7.119),
22+ libunity-api-dev (>= 7.120),
23 libusermetricsoutput1-dev,
24 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
25 libx11-dev[!arm64 !armhf],
26@@ -159,7 +159,7 @@
27 qttranslations5-l10n,
28 ubuntu-thumbnailer-impl-0,
29 ubuntu-wallpapers,
30- unity-application-impl-22,
31+ unity-application-impl-23,
32 unity-notifications-impl-3,
33 unity-plugin-scopes | unity-scopes-impl,
34 unity-scopes-impl-12,
35@@ -205,7 +205,7 @@
36 Depends: ${misc:Depends},
37 ${shlibs:Depends},
38 Provides: unity-application-impl,
39- unity-application-impl-22,
40+ unity-application-impl-23,
41 Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
42 Description: Fake environment for running Unity 8 shell
43 Provides fake implementations of some QML modules used by Unity 8 shell
44
45=== modified file 'plugins/WindowManager/CMakeLists.txt'
46--- plugins/WindowManager/CMakeLists.txt 2016-06-20 09:43:38 +0000
47+++ plugins/WindowManager/CMakeLists.txt 2016-11-23 15:15:21 +0000
48@@ -1,10 +1,12 @@
49 set(WINDOWMANAGER_SRC
50- TopLevelSurfaceList.cpp
51+ TopLevelWindowModel.cpp
52+ Window.cpp
53 WindowManagerPlugin.cpp
54 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
55 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
56 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
57 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
58+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
59 )
60
61 add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
62
63=== removed file 'plugins/WindowManager/TopLevelSurfaceList.cpp'
64--- plugins/WindowManager/TopLevelSurfaceList.cpp 2016-09-14 15:02:01 +0000
65+++ plugins/WindowManager/TopLevelSurfaceList.cpp 1970-01-01 00:00:00 +0000
66@@ -1,481 +0,0 @@
67-/*
68- * Copyright (C) 2016 Canonical, Ltd.
69- *
70- * This program is free software; you can redistribute it and/or modify
71- * it under the terms of the GNU General Public License as published by
72- * the Free Software Foundation; version 3.
73- *
74- * This program is distributed in the hope that it will be useful,
75- * but WITHOUT ANY WARRANTY; without even the implied warranty of
76- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
77- * GNU General Public License for more details.
78- *
79- * You should have received a copy of the GNU General Public License
80- * along with this program. If not, see <http://www.gnu.org/licenses/>.
81- */
82-
83-#include "TopLevelSurfaceList.h"
84-
85-// unity-api
86-#include <unity/shell/application/ApplicationInfoInterface.h>
87-#include <unity/shell/application/MirSurfaceInterface.h>
88-#include <unity/shell/application/MirSurfaceListInterface.h>
89-
90-#include <QMetaObject>
91-
92-Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
93-
94-#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
95-
96-using namespace unity::shell::application;
97-
98-TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
99- QAbstractListModel(parent)
100-{
101- DEBUG_MSG << "()";
102-}
103-
104-TopLevelSurfaceList::~TopLevelSurfaceList()
105-{
106- DEBUG_MSG << "()";
107-}
108-
109-int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
110-{
111- return !parent.isValid() ? m_surfaceList.size() : 0;
112-}
113-
114-QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
115-{
116- if (index.row() < 0 || index.row() >= m_surfaceList.size())
117- return QVariant();
118-
119- if (role == SurfaceRole) {
120- MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
121- return QVariant::fromValue(surface);
122- } else if (role == ApplicationRole) {
123- return QVariant::fromValue(m_surfaceList.at(index.row()).application);
124- } else if (role == IdRole) {
125- return QVariant::fromValue(m_surfaceList.at(index.row()).id);
126- } else {
127- return QVariant();
128- }
129-}
130-
131-void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
132-{
133- if (!surface)
134- return;
135-
136- DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
137-
138- int i = indexOf(surface);
139- if (i != -1) {
140- raiseId(m_surfaceList.at(i).id);
141- }
142-}
143-
144-void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
145-{
146- DEBUG_MSG << "(" << application->appId() << ")";
147-
148- appendSurfaceHelper(nullptr, application);
149-}
150-
151-void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
152-{
153- Q_ASSERT(surface != nullptr);
154-
155- bool filledPlaceholder = false;
156- for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
157- ModelEntry &entry = m_surfaceList[i];
158- if (entry.application == application && entry.surface == nullptr) {
159- entry.surface = surface;
160- connectSurface(surface);
161- DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
162- << ", filling out placeholder. after: " << toString();
163- Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
164- filledPlaceholder = true;
165- }
166- }
167-
168- if (!filledPlaceholder) {
169- DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
170- appendSurfaceHelper(surface, application);
171- }
172-}
173-
174-void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
175-{
176- if (m_modelState == IdleState) {
177- m_modelState = InsertingState;
178- beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
179- } else {
180- Q_ASSERT(m_modelState == ResettingState);
181- // No point in signaling anything if we're resetting the whole model
182- }
183-
184- int id = generateId();
185- m_surfaceList.append(ModelEntry(surface, application, id));
186- if (surface) {
187- connectSurface(surface);
188- }
189-
190- if (m_modelState == InsertingState) {
191- endInsertRows();
192- Q_EMIT countChanged();
193- Q_EMIT listChanged();
194- m_modelState = IdleState;
195- }
196-
197- DEBUG_MSG << " after " << toString();
198-}
199-
200-void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
201-{
202- connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
203- if (!live) {
204- onSurfaceDied(surface);
205- }
206- });
207- connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
208-}
209-
210-void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
211-{
212- int i = indexOf(surface);
213- if (i == -1) {
214- return;
215- }
216-
217- auto application = m_surfaceList[i].application;
218-
219- // can't be starting if it already has a surface
220- Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
221-
222- if (application->state() == ApplicationInfoInterface::Running) {
223- m_surfaceList[i].removeOnceSurfaceDestroyed = true;
224- } else {
225- // assume it got killed by the out-of-memory daemon.
226- //
227- // So leave entry in the model and only remove its surface, so shell can display a screenshot
228- // in its place.
229- m_surfaceList[i].removeOnceSurfaceDestroyed = false;
230- }
231-}
232-
233-void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
234-{
235- int i = indexOf(surface);
236- if (i == -1) {
237- return;
238- }
239-
240- if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
241- removeAt(i);
242- } else {
243- m_surfaceList[i].surface = nullptr;
244- Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
245- DEBUG_MSG << " Removed surface from entry. After: " << toString();
246- }
247-}
248-
249-void TopLevelSurfaceList::removeAt(int index)
250-{
251- if (m_modelState == IdleState) {
252- beginRemoveRows(QModelIndex(), index, index);
253- m_modelState = RemovingState;
254- } else {
255- Q_ASSERT(m_modelState == ResettingState);
256- // No point in signaling anything if we're resetting the whole model
257- }
258-
259- m_surfaceList.removeAt(index);
260-
261- if (m_modelState == RemovingState) {
262- endRemoveRows();
263- Q_EMIT countChanged();
264- Q_EMIT listChanged();
265- m_modelState = IdleState;
266- }
267-
268- DEBUG_MSG << " after " << toString();
269-}
270-
271-int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
272-{
273- for (int i = 0; i < m_surfaceList.count(); ++i) {
274- if (m_surfaceList.at(i).surface == surface) {
275- return i;
276- }
277- }
278- return -1;
279-}
280-
281-void TopLevelSurfaceList::move(int from, int to)
282-{
283- if (from == to) return;
284- DEBUG_MSG << " from=" << from << " to=" << to;
285-
286- if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
287- QModelIndex parent;
288- /* When moving an item down, the destination index needs to be incremented
289- by one, as explained in the documentation:
290- http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
291-
292- Q_ASSERT(m_modelState == IdleState);
293- m_modelState = MovingState;
294-
295- beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
296- m_surfaceList.move(from, to);
297- endMoveRows();
298- Q_EMIT listChanged();
299-
300- m_modelState = IdleState;
301-
302- DEBUG_MSG << " after " << toString();
303- }
304-}
305-
306-MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
307-{
308- if (index >=0 && index < m_surfaceList.count()) {
309- return m_surfaceList[index].surface;
310- } else {
311- return nullptr;
312- }
313-}
314-
315-ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
316-{
317- if (index >=0 && index < m_surfaceList.count()) {
318- return m_surfaceList[index].application;
319- } else {
320- return nullptr;
321- }
322-}
323-
324-int TopLevelSurfaceList::idAt(int index) const
325-{
326- if (index >=0 && index < m_surfaceList.count()) {
327- return m_surfaceList[index].id;
328- } else {
329- return 0;
330- }
331-}
332-
333-int TopLevelSurfaceList::indexForId(int id) const
334-{
335- for (int i = 0; i < m_surfaceList.count(); ++i) {
336- if (m_surfaceList[i].id == id) {
337- return i;
338- }
339- }
340- return -1;
341-}
342-
343-void TopLevelSurfaceList::doRaiseId(int id)
344-{
345- int fromIndex = indexForId(id);
346- if (fromIndex != -1) {
347- move(fromIndex, 0 /* toIndex */);
348- }
349-}
350-
351-void TopLevelSurfaceList::raiseId(int id)
352-{
353- if (m_modelState == IdleState) {
354- DEBUG_MSG << "(id=" << id << ") - do it now.";
355- doRaiseId(id);
356- } else {
357- DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
358- // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
359- // if we perform yet another model change straight away.
360- //
361- // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
362- // the index is definitely within bounds.
363- QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
364- }
365-}
366-
367-int TopLevelSurfaceList::generateId()
368-{
369- int id = m_nextId;
370- m_nextId = nextFreeId(m_nextId + 1);
371- Q_EMIT nextIdChanged();
372- return id;
373-}
374-
375-int TopLevelSurfaceList::nextFreeId(int candidateId)
376-{
377- if (candidateId > m_maxId) {
378- return nextFreeId(1);
379- } else {
380- if (indexForId(candidateId) == -1) {
381- // it's indeed free
382- return candidateId;
383- } else {
384- return nextFreeId(candidateId + 1);
385- }
386- }
387-}
388-
389-QString TopLevelSurfaceList::toString()
390-{
391- QString str;
392- for (int i = 0; i < m_surfaceList.count(); ++i) {
393- auto item = m_surfaceList.at(i);
394-
395- QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
396- .arg(i)
397- .arg(item.application->appId())
398- .arg((qintptr)item.surface, 0, 16)
399- .arg(item.id);
400-
401- if (i > 0) {
402- str.append(",");
403- }
404- str.append(itemStr);
405- }
406- return str;
407-}
408-
409-void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
410-{
411- DEBUG_MSG << "(" << application->appId() << ")";
412- Q_ASSERT(!m_applications.contains(application));
413- m_applications.append(application);
414-
415- MirSurfaceListInterface *surfaceList = application->surfaceList();
416-
417- if (application->state() != ApplicationInfoInterface::Stopped) {
418- if (surfaceList->count() == 0) {
419- appendPlaceholder(application);
420- } else {
421- for (int i = 0; i < surfaceList->count(); ++i) {
422- appendSurface(surfaceList->get(i), application);
423- }
424- }
425- }
426-
427- connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
428- [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
429- {
430- for (int i = last; i >= first; --i) {
431- this->appendSurface(surfaceList->get(i), application);
432- }
433- });
434-}
435-
436-void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
437-{
438- DEBUG_MSG << "(" << application->appId() << ")";
439- Q_ASSERT(m_applications.contains(application));
440-
441- MirSurfaceListInterface *surfaceList = application->surfaceList();
442-
443- disconnect(surfaceList, 0, this, 0);
444-
445- Q_ASSERT(m_modelState == IdleState);
446- m_modelState = RemovingState;
447-
448- int i = 0;
449- while (i < m_surfaceList.count()) {
450- if (m_surfaceList.at(i).application == application) {
451- beginRemoveRows(QModelIndex(), i, i);
452- m_surfaceList.removeAt(i);
453- endRemoveRows();
454- Q_EMIT countChanged();
455- Q_EMIT listChanged();
456- } else {
457- ++i;
458- }
459- }
460-
461- m_modelState = IdleState;
462-
463- DEBUG_MSG << " after " << toString();
464-
465- m_applications.removeAll(application);
466-}
467-
468-QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
469-{
470- return m_applicationsModel;
471-}
472-
473-void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
474-{
475- if (m_applicationsModel == value) {
476- return;
477- }
478-
479- DEBUG_MSG << "(" << value << ")";
480-
481- Q_ASSERT(m_modelState == IdleState);
482- m_modelState = ResettingState;
483-
484- beginResetModel();
485-
486- if (m_applicationsModel) {
487- m_surfaceList.clear();
488- m_applications.clear();
489- disconnect(m_applicationsModel, 0, this, 0);
490- }
491-
492- m_applicationsModel = value;
493-
494- if (m_applicationsModel) {
495- findApplicationRole();
496-
497- connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
498- this, [this](const QModelIndex &/*parent*/, int first, int last) {
499- for (int i = first; i <= last; ++i) {
500- auto application = getApplicationFromModelAt(i);
501- addApplication(application);
502- }
503- });
504-
505- connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
506- this, [this](const QModelIndex &/*parent*/, int first, int last) {
507- for (int i = first; i <= last; ++i) {
508- auto application = getApplicationFromModelAt(i);
509- removeApplication(application);
510- }
511- });
512-
513- for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
514- auto application = getApplicationFromModelAt(i);
515- addApplication(application);
516- }
517- }
518-
519- endResetModel();
520- m_modelState = IdleState;
521-}
522-
523-ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
524-{
525- QModelIndex modelIndex = m_applicationsModel->index(index);
526-
527- QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
528-
529- // variant.value<ApplicationInfoInterface*>() returns null for some reason.
530- return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
531-}
532-
533-void TopLevelSurfaceList::findApplicationRole()
534-{
535- QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
536-
537- m_applicationRole = -1;
538- for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
539- if (i.value() == "application") {
540- m_applicationRole = i.key();
541- }
542- }
543-
544- if (m_applicationRole == -1) {
545- qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
546- }
547-}
548
549=== removed file 'plugins/WindowManager/TopLevelSurfaceList.h'
550--- plugins/WindowManager/TopLevelSurfaceList.h 2016-04-04 13:39:44 +0000
551+++ plugins/WindowManager/TopLevelSurfaceList.h 1970-01-01 00:00:00 +0000
552@@ -1,223 +0,0 @@
553-/*
554- * Copyright (C) 2016 Canonical, Ltd.
555- *
556- * This program is free software; you can redistribute it and/or modify
557- * it under the terms of the GNU General Public License as published by
558- * the Free Software Foundation; version 3.
559- *
560- * This program is distributed in the hope that it will be useful,
561- * but WITHOUT ANY WARRANTY; without even the implied warranty of
562- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
563- * GNU General Public License for more details.
564- *
565- * You should have received a copy of the GNU General Public License
566- * along with this program. If not, see <http://www.gnu.org/licenses/>.
567- */
568-
569-#ifndef TOPLEVELSURFACELIST_H
570-#define TOPLEVELSURFACELIST_H
571-
572-#include <QAbstractListModel>
573-#include <QList>
574-#include <QLoggingCategory>
575-
576-Q_DECLARE_LOGGING_CATEGORY(UNITY_TOPSURFACELIST)
577-
578-namespace unity {
579- namespace shell {
580- namespace application {
581- class ApplicationInfoInterface;
582- class MirSurfaceInterface;
583- }
584- }
585-}
586-
587-/**
588- * @brief A model of top-level surfaces
589- *
590- * It's an abstraction of top-level application windows.
591- *
592- * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
593- * still starting up. A shell should then display a splash screen or saved screenshot of the application
594- * until its surface comes up.
595- *
596- * As applications can have multiple surfaces and you can also have entries without surfaces at all,
597- * the only way to unambiguously refer to an entry in this model is through its id.
598- */
599-class TopLevelSurfaceList : public QAbstractListModel
600-{
601-
602- Q_OBJECT
603-
604- /**
605- * @brief A list model of applications.
606- *
607- * It's expected to have a role called "application" which returns a ApplicationInfoInterface
608- */
609- Q_PROPERTY(QAbstractListModel* applicationsModel READ applicationsModel
610- WRITE setApplicationsModel
611- NOTIFY applicationsModelChanged)
612-
613- /**
614- * @brief Number of top-level surfaces in this model
615- *
616- * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
617- */
618- Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
619-
620- /**
621- The id to be used on the next entry created
622- Useful for tests
623- */
624- Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
625-public:
626-
627- /**
628- * @brief The Roles supported by the model
629- *
630- * SurfaceRole - A MirSurfaceInterface. It will be null if the application is still starting up
631- * ApplicationRole - An ApplicationInfoInterface
632- * IdRole - A unique identifier for this entry. Useful to unambiguosly track elements as they move around in the list
633- */
634- enum Roles {
635- SurfaceRole = Qt::UserRole,
636- ApplicationRole = Qt::UserRole + 1,
637- IdRole = Qt::UserRole + 2,
638- };
639-
640- explicit TopLevelSurfaceList(QObject *parent = nullptr);
641- virtual ~TopLevelSurfaceList();
642-
643- // QAbstractItemModel methods
644- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
645- QVariant data(const QModelIndex& index, int role) const override;
646- QHash<int, QByteArray> roleNames() const override {
647- QHash<int, QByteArray> roleNames { {SurfaceRole, "surface"},
648- {ApplicationRole, "application"},
649- {IdRole, "id"} };
650- return roleNames;
651- }
652-
653- int nextId() const { return m_nextId; }
654-
655- QAbstractListModel *applicationsModel() const;
656- void setApplicationsModel(QAbstractListModel*);
657-
658-public Q_SLOTS:
659- /**
660- * @brief Returns the surface at the given index
661- *
662- * It will be a nullptr if the application is still starting up and thus hasn't yet created
663- * and drawn into a surface.
664- */
665- unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
666-
667- /**
668- * @brief Returns the application at the given index
669- */
670- unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
671-
672- /**
673- * @brief Returns the unique id of the element at the given index
674- */
675- int idAt(int index) const;
676-
677- /**
678- * @brief Returns the index where the row with the given id is located
679- *
680- * Returns -1 if there's no row with the given id.
681- */
682- int indexForId(int id) const;
683-
684- /**
685- * @brief Raises the row with the given id to index 0
686- */
687- void raiseId(int id);
688-
689- void doRaiseId(int id);
690-
691-Q_SIGNALS:
692- void countChanged();
693-
694- /**
695- * @brief Emitted when the list changes
696- *
697- * Emitted when model gains an element, loses an element or when elements exchange positions.
698- */
699- void listChanged();
700-
701- void nextIdChanged();
702-
703- void applicationsModelChanged();
704-
705-private:
706- void addApplication(unity::shell::application::ApplicationInfoInterface *application);
707- void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
708-
709- int indexOf(unity::shell::application::MirSurfaceInterface *surface);
710- void raise(unity::shell::application::MirSurfaceInterface *surface);
711- void move(int from, int to);
712- void appendSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
713- unity::shell::application::ApplicationInfoInterface *application);
714- void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
715- int generateId();
716- int nextFreeId(int candidateId);
717- QString toString();
718- void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
719- void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
720- void removeAt(int index);
721- void findApplicationRole();
722-
723- unity::shell::application::ApplicationInfoInterface *getApplicationFromModelAt(int index);
724-
725- /*
726- Placeholder for a future surface from a starting or running application.
727- Enables shell to give immediate feedback to the user by showing, eg,
728- a splash screen.
729-
730- It's a model row containing a null surface and the given application.
731- */
732- void appendPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
733-
734- /*
735- Adds a model row with the given surface and application
736-
737- Alternatively, if a placeholder exists for the given application it's
738- filled with the given surface instead.
739- */
740- void appendSurface(unity::shell::application::MirSurfaceInterface *surface,
741- unity::shell::application::ApplicationInfoInterface *application);
742-
743- struct ModelEntry {
744- ModelEntry(unity::shell::application::MirSurfaceInterface *surface, unity::shell::application::ApplicationInfoInterface *application, int id)
745- : surface(surface), application(application), id(id) {}
746- unity::shell::application::MirSurfaceInterface *surface;
747- unity::shell::application::ApplicationInfoInterface *application;
748- int id;
749- bool removeOnceSurfaceDestroyed{false};
750- };
751-
752- QList<ModelEntry> m_surfaceList;
753- int m_nextId{1};
754- static const int m_maxId{1000000};
755-
756- // applications that are being monitored
757- QList<unity::shell::application::ApplicationInfoInterface *> m_applications;
758-
759- QAbstractListModel* m_applicationsModel{nullptr};
760- int m_applicationRole{-1};
761-
762- enum ModelState {
763- IdleState,
764- InsertingState,
765- RemovingState,
766- MovingState,
767- ResettingState
768- };
769- ModelState m_modelState{IdleState};
770-};
771-
772-Q_DECLARE_METATYPE(TopLevelSurfaceList*)
773-Q_DECLARE_METATYPE(QAbstractListModel*)
774-
775-#endif // TOPLEVELSURFACELIST_H
776
777=== added file 'plugins/WindowManager/TopLevelWindowModel.cpp'
778--- plugins/WindowManager/TopLevelWindowModel.cpp 1970-01-01 00:00:00 +0000
779+++ plugins/WindowManager/TopLevelWindowModel.cpp 2016-11-23 15:15:21 +0000
780@@ -0,0 +1,644 @@
781+/*
782+ * Copyright (C) 2016 Canonical, Ltd.
783+ *
784+ * This program is free software: you can redistribute it and/or modify it under
785+ * the terms of the GNU Lesser General Public License version 3, as published by
786+ * the Free Software Foundation.
787+ *
788+ * This program is distributed in the hope that it will be useful, but WITHOUT
789+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
790+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
791+ * Lesser General Public License for more details.
792+ *
793+ * You should have received a copy of the GNU Lesser General Public License
794+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
795+ */
796+
797+#include "TopLevelWindowModel.h"
798+
799+// unity-api
800+#include <unity/shell/application/ApplicationInfoInterface.h>
801+#include <unity/shell/application/ApplicationManagerInterface.h>
802+#include <unity/shell/application/MirSurfaceInterface.h>
803+#include <unity/shell/application/MirSurfaceListInterface.h>
804+#include <unity/shell/application/SurfaceManagerInterface.h>
805+
806+// Qt
807+#include <QGuiApplication>
808+#include <QDebug>
809+
810+// local
811+#include "Window.h"
812+
813+Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
814+
815+#define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
816+#define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
817+
818+namespace unityapi = unity::shell::application;
819+
820+TopLevelWindowModel::TopLevelWindowModel()
821+{
822+}
823+
824+void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
825+{
826+ if (m_applicationManager == value) {
827+ return;
828+ }
829+
830+ DEBUG_MSG << "(" << value << ")";
831+
832+ Q_ASSERT(m_modelState == IdleState);
833+ m_modelState = ResettingState;
834+
835+ beginResetModel();
836+
837+ if (m_applicationManager) {
838+ m_windowModel.clear();
839+ disconnect(m_applicationManager, 0, this, 0);
840+ }
841+
842+ m_applicationManager = value;
843+
844+ if (m_applicationManager) {
845+ connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
846+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
847+ for (int i = first; i <= last; ++i) {
848+ auto application = m_applicationManager->get(i);
849+ addApplication(application);
850+ }
851+ });
852+
853+ connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
854+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
855+ for (int i = first; i <= last; ++i) {
856+ auto application = m_applicationManager->get(i);
857+ removeApplication(application);
858+ }
859+ });
860+
861+ for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
862+ auto application = m_applicationManager->get(i);
863+ addApplication(application);
864+ }
865+ }
866+
867+ endResetModel();
868+ m_modelState = IdleState;
869+}
870+
871+void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *surfaceManager)
872+{
873+ if (surfaceManager == m_surfaceManager) {
874+ return;
875+ }
876+
877+ DEBUG_MSG << "(" << surfaceManager << ")";
878+
879+ if (m_surfaceManager) {
880+ disconnect(m_surfaceManager, 0, this, 0);
881+ }
882+
883+ m_surfaceManager = surfaceManager;
884+
885+ if (m_surfaceManager) {
886+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
887+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
888+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
889+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
890+ }
891+
892+ Q_EMIT surfaceManagerChanged(m_surfaceManager);
893+}
894+
895+void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
896+{
897+ DEBUG_MSG << "(" << application->appId() << ")";
898+
899+ if (application->state() != unityapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
900+ prependPlaceholder(application);
901+ }
902+}
903+
904+void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *application)
905+{
906+ DEBUG_MSG << "(" << application->appId() << ")";
907+
908+ Q_ASSERT(m_modelState == IdleState);
909+
910+ int i = 0;
911+ while (i < m_windowModel.count()) {
912+ if (m_windowModel.at(i).application == application) {
913+ removeAt(i);
914+ } else {
915+ ++i;
916+ }
917+ }
918+}
919+
920+void TopLevelWindowModel::prependPlaceholder(unityapi::ApplicationInfoInterface *application)
921+{
922+ INFO_MSG << "(" << application->appId() << ")";
923+
924+ prependSurfaceHelper(nullptr, application);
925+}
926+
927+void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
928+{
929+ Q_ASSERT(surface != nullptr);
930+
931+ bool filledPlaceholder = false;
932+ for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
933+ ModelEntry &entry = m_windowModel[i];
934+ if (entry.application == application && entry.window->surface() == nullptr) {
935+ entry.window->setSurface(surface);
936+ connectSurface(surface);
937+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface
938+ << ", filling out placeholder. after: " << toString();
939+ filledPlaceholder = true;
940+ }
941+ }
942+
943+ if (!filledPlaceholder) {
944+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
945+ prependSurfaceHelper(surface, application);
946+ }
947+}
948+
949+void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
950+{
951+ if (m_modelState == IdleState) {
952+ m_modelState = InsertingState;
953+ beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
954+ } else {
955+ Q_ASSERT(m_modelState == ResettingState);
956+ // No point in signaling anything if we're resetting the whole model
957+ }
958+
959+ int id = generateId();
960+ Window *window = new Window(id, this);
961+ if (surface) {
962+ window->setSurface(surface);
963+ }
964+ m_windowModel.prepend(ModelEntry(window, application));
965+ if (surface) {
966+ connectSurface(surface);
967+ }
968+
969+ connectWindow(window);
970+
971+ if (m_modelState == InsertingState) {
972+ endInsertRows();
973+ Q_EMIT countChanged();
974+ Q_EMIT listChanged();
975+ m_modelState = IdleState;
976+ }
977+
978+ if (!surface) {
979+ // focus the newly added window. miral can't help with that as it doesn't know about it.
980+ window->setFocused(true);
981+ if (m_focusedWindow && m_focusedWindow->surface()) {
982+ m_surfaceManager->activate(nullptr);
983+ }
984+ }
985+
986+ INFO_MSG << " after " << toString();
987+}
988+
989+void TopLevelWindowModel::connectWindow(Window *window)
990+{
991+ connect(window, &Window::focusRequested, this, [this, window]() {
992+ if (!window->surface()) {
993+ activateEmptyWindow(window);
994+ }
995+ });
996+
997+ connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
998+ if (window->surface()) {
999+ // Condense changes to the focused window
1000+ // eg: Do focusedWindow=A to focusedWindow=B instead of
1001+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
1002+ m_focusedWindowChanged = true;
1003+ if (focused) {
1004+ Q_ASSERT(m_newlyFocusedWindow == nullptr);
1005+ m_newlyFocusedWindow = window;
1006+ }
1007+ }
1008+ });
1009+
1010+ connect(window, &Window::closeRequested, this, [this, window]() {
1011+ if (!window->surface()) {
1012+ int index = indexForId(window->id());
1013+ Q_ASSERT(index >= 0);
1014+ m_windowModel[index].application->close();
1015+ }
1016+ });
1017+
1018+ connect(window, &Window::emptyWindowActivated, this, [this, window]() {
1019+ activateEmptyWindow(window);
1020+ });
1021+}
1022+
1023+void TopLevelWindowModel::activateEmptyWindow(Window *window)
1024+{
1025+ Q_ASSERT(!window->surface());
1026+
1027+ // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
1028+ // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
1029+
1030+ window->setFocused(true);
1031+ raiseId(window->id());
1032+ Window *previousWindow = m_focusedWindow;
1033+ setFocusedWindow(window);
1034+ if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
1035+ m_surfaceManager->activate(nullptr);
1036+ }
1037+}
1038+
1039+void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
1040+{
1041+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
1042+ if (!live) {
1043+ onSurfaceDied(surface);
1044+ }
1045+ });
1046+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
1047+}
1048+
1049+void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
1050+{
1051+ if (surface->type() == Mir::InputMethodType) {
1052+ removeInputMethodWindow();
1053+ return;
1054+ }
1055+
1056+ int i = indexOf(surface);
1057+ if (i == -1) {
1058+ return;
1059+ }
1060+
1061+ auto application = m_windowModel[i].application;
1062+
1063+ // can't be starting if it already has a surface
1064+ Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
1065+
1066+ if (application->state() == unityapi::ApplicationInfoInterface::Running) {
1067+ m_windowModel[i].removeOnceSurfaceDestroyed = true;
1068+ } else {
1069+ // assume it got killed by the out-of-memory daemon.
1070+ //
1071+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
1072+ // in its place.
1073+ m_windowModel[i].removeOnceSurfaceDestroyed = false;
1074+ }
1075+}
1076+
1077+void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
1078+{
1079+ int i = indexOf(surface);
1080+ if (i == -1) {
1081+ return;
1082+ }
1083+
1084+ if (m_windowModel[i].removeOnceSurfaceDestroyed) {
1085+ removeAt(i);
1086+ } else {
1087+ auto window = m_windowModel[i].window;
1088+ window->setSurface(nullptr);
1089+ window->setFocused(false);
1090+ INFO_MSG << " Removed surface from entry. After: " << toString();
1091+ }
1092+}
1093+
1094+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
1095+{
1096+ DEBUG_MSG << "(" << surface << ")";
1097+ if (surface->type() == Mir::InputMethodType) {
1098+ int id = generateId();
1099+ Window *qmlWindow = new Window(id, this);
1100+ connectWindow(qmlWindow);
1101+ qmlWindow->setSurface(surface);
1102+ setInputMethodWindow(qmlWindow);
1103+ } else {
1104+ auto application = m_applicationManager->findApplicationWithSurface(surface);
1105+ if (application) {
1106+ prependSurface(surface, application);
1107+ } else {
1108+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
1109+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
1110+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
1111+ int id = generateId();
1112+ Window *promptWindow = new Window(id, this);
1113+ connectWindow(promptWindow);
1114+ promptWindow->setSurface(surface);
1115+ connect(surface, &QObject::destroyed, promptWindow, [=](){
1116+ promptWindow->setSurface(nullptr);
1117+ promptWindow->deleteLater();
1118+ });
1119+ }
1120+ }
1121+ // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
1122+}
1123+
1124+void TopLevelWindowModel::removeAt(int index)
1125+{
1126+ if (m_modelState == IdleState) {
1127+ beginRemoveRows(QModelIndex(), index, index);
1128+ m_modelState = RemovingState;
1129+ } else {
1130+ Q_ASSERT(m_modelState == ResettingState);
1131+ // No point in signaling anything if we're resetting the whole model
1132+ }
1133+
1134+ auto window = m_windowModel[index].window;
1135+
1136+ window->setSurface(nullptr);
1137+ window->setFocused(false);
1138+
1139+ m_windowModel.removeAt(index);
1140+
1141+ if (m_modelState == RemovingState) {
1142+ endRemoveRows();
1143+ Q_EMIT countChanged();
1144+ Q_EMIT listChanged();
1145+ m_modelState = IdleState;
1146+ }
1147+
1148+ disconnect(window, 0, this, 0);
1149+ if (m_focusedWindow == window) {
1150+ setFocusedWindow(nullptr);
1151+ }
1152+ delete window;
1153+
1154+ INFO_MSG << " after " << toString();
1155+}
1156+
1157+void TopLevelWindowModel::setInputMethodWindow(Window *window)
1158+{
1159+ if (m_inputMethodWindow) {
1160+ qWarning("Multiple Input Method Surfaces created, removing the old one!");
1161+ delete m_inputMethodWindow;
1162+ }
1163+ m_inputMethodWindow = window;
1164+ Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
1165+}
1166+
1167+void TopLevelWindowModel::removeInputMethodWindow()
1168+{
1169+ if (m_inputMethodWindow) {
1170+ delete m_inputMethodWindow;
1171+ m_inputMethodWindow = nullptr;
1172+ Q_EMIT inputMethodSurfaceChanged(nullptr);
1173+ }
1174+}
1175+
1176+void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
1177+{
1178+ DEBUG_MSG << "(" << surfaces << ")";
1179+ const int raiseCount = surfaces.size();
1180+ for (int i = 0; i < raiseCount; i++) {
1181+ int fromIndex = findIndexOf(surfaces[i]);
1182+ if (fromIndex != -1) {
1183+ move(fromIndex, 0);
1184+ }
1185+ }
1186+}
1187+
1188+int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
1189+{
1190+ return m_windowModel.count();
1191+}
1192+
1193+QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
1194+{
1195+ if (index.row() < 0 || index.row() >= m_windowModel.size())
1196+ return QVariant();
1197+
1198+ if (role == WindowRole) {
1199+ Window *window = m_windowModel.at(index.row()).window;
1200+ return QVariant::fromValue(window);
1201+ } else if (role == ApplicationRole) {
1202+ return QVariant::fromValue(m_windowModel.at(index.row()).application);
1203+ } else {
1204+ return QVariant();
1205+ }
1206+}
1207+
1208+int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
1209+{
1210+ for (int i=0; i<m_windowModel.count(); i++) {
1211+ if (m_windowModel[i].window->surface() == surface) {
1212+ return i;
1213+ }
1214+ }
1215+ return -1;
1216+}
1217+
1218+int TopLevelWindowModel::generateId()
1219+{
1220+ int id = m_nextId;
1221+ m_nextId = nextFreeId(nextId(id), id);
1222+ return id;
1223+}
1224+
1225+int TopLevelWindowModel::nextId(int id) const
1226+{
1227+ if (id == m_maxId) {
1228+ return id = 1;
1229+ } else {
1230+ return id + 1;
1231+ }
1232+}
1233+
1234+int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
1235+{
1236+ int firstCandidateId = candidateId;
1237+
1238+ while (indexForId(candidateId) != -1 || candidateId == latestId) {
1239+ candidateId = nextId(candidateId);
1240+
1241+ if (candidateId == firstCandidateId) {
1242+ qFatal("TopLevelWindowModel: run out of window ids.");
1243+ }
1244+ }
1245+
1246+ return candidateId;
1247+}
1248+
1249+QString TopLevelWindowModel::toString()
1250+{
1251+ QString str;
1252+ for (int i = 0; i < m_windowModel.count(); ++i) {
1253+ auto item = m_windowModel.at(i);
1254+
1255+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
1256+ .arg(QString::number(i),
1257+ item.application->appId(),
1258+ QString::number((qintptr)item.window->surface(), 16),
1259+ QString::number(item.window->id()));
1260+
1261+ if (i > 0) {
1262+ str.append(",");
1263+ }
1264+ str.append(itemStr);
1265+ }
1266+ return str;
1267+}
1268+
1269+int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
1270+{
1271+ for (int i = 0; i < m_windowModel.count(); ++i) {
1272+ if (m_windowModel.at(i).window->surface() == surface) {
1273+ return i;
1274+ }
1275+ }
1276+ return -1;
1277+}
1278+
1279+int TopLevelWindowModel::indexForId(int id) const
1280+{
1281+ for (int i = 0; i < m_windowModel.count(); ++i) {
1282+ if (m_windowModel[i].window->id() == id) {
1283+ return i;
1284+ }
1285+ }
1286+ return -1;
1287+}
1288+
1289+Window *TopLevelWindowModel::windowAt(int index) const
1290+{
1291+ if (index >=0 && index < m_windowModel.count()) {
1292+ return m_windowModel[index].window;
1293+ } else {
1294+ return nullptr;
1295+ }
1296+}
1297+
1298+unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
1299+{
1300+ if (index >=0 && index < m_windowModel.count()) {
1301+ return m_windowModel[index].window->surface();
1302+ } else {
1303+ return nullptr;
1304+ }
1305+}
1306+
1307+unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
1308+{
1309+ if (index >=0 && index < m_windowModel.count()) {
1310+ return m_windowModel[index].application;
1311+ } else {
1312+ return nullptr;
1313+ }
1314+}
1315+
1316+int TopLevelWindowModel::idAt(int index) const
1317+{
1318+ if (index >=0 && index < m_windowModel.count()) {
1319+ return m_windowModel[index].window->id();
1320+ } else {
1321+ return 0;
1322+ }
1323+}
1324+
1325+void TopLevelWindowModel::raiseId(int id)
1326+{
1327+ if (m_modelState == IdleState) {
1328+ DEBUG_MSG << "(id=" << id << ") - do it now.";
1329+ doRaiseId(id);
1330+ } else {
1331+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
1332+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
1333+ // if we perform yet another model change straight away.
1334+ //
1335+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
1336+ // the index is definitely within bounds.
1337+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
1338+ }
1339+}
1340+
1341+void TopLevelWindowModel::doRaiseId(int id)
1342+{
1343+ int fromIndex = indexForId(id);
1344+ if (fromIndex != -1) {
1345+ auto surface = m_windowModel[fromIndex].window->surface();
1346+ if (surface) {
1347+ m_surfaceManager->raise(surface);
1348+ } else {
1349+ // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
1350+ // miral can do about it.
1351+ move(fromIndex, 0);
1352+ }
1353+ }
1354+}
1355+
1356+void TopLevelWindowModel::setFocusedWindow(Window *window)
1357+{
1358+ if (window != m_focusedWindow) {
1359+ INFO_MSG << "(" << window << ")";
1360+
1361+ Window* previousWindow = m_focusedWindow;
1362+
1363+ m_focusedWindow = window;
1364+ Q_EMIT focusedWindowChanged(m_focusedWindow);
1365+
1366+ if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
1367+ // do it ourselves. miral doesn't know about this window
1368+ previousWindow->setFocused(false);
1369+ }
1370+ }
1371+}
1372+
1373+unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
1374+{
1375+ return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
1376+}
1377+
1378+Window* TopLevelWindowModel::focusedWindow() const
1379+{
1380+ return m_focusedWindow;
1381+}
1382+
1383+void TopLevelWindowModel::move(int from, int to)
1384+{
1385+ if (from == to) return;
1386+ DEBUG_MSG << " from=" << from << " to=" << to;
1387+
1388+ if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
1389+ QModelIndex parent;
1390+ /* When moving an item down, the destination index needs to be incremented
1391+ by one, as explained in the documentation:
1392+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
1393+
1394+ Q_ASSERT(m_modelState == IdleState);
1395+ m_modelState = MovingState;
1396+
1397+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
1398+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
1399+ const auto &window = m_windowModel.takeAt(from);
1400+ m_windowModel.insert(to, window);
1401+#else
1402+ m_windowModel.move(from, to);
1403+#endif
1404+ endMoveRows();
1405+
1406+ Q_EMIT listChanged();
1407+ m_modelState = IdleState;
1408+
1409+ INFO_MSG << " after " << toString();
1410+ }
1411+}
1412+void TopLevelWindowModel::onModificationsStarted()
1413+{
1414+}
1415+
1416+void TopLevelWindowModel::onModificationsEnded()
1417+{
1418+ if (m_focusedWindowChanged) {
1419+ setFocusedWindow(m_newlyFocusedWindow);
1420+ }
1421+ // reset
1422+ m_focusedWindowChanged = false;
1423+ m_newlyFocusedWindow = nullptr;
1424+}
1425
1426=== added file 'plugins/WindowManager/TopLevelWindowModel.h'
1427--- plugins/WindowManager/TopLevelWindowModel.h 1970-01-01 00:00:00 +0000
1428+++ plugins/WindowManager/TopLevelWindowModel.h 2016-11-23 15:15:21 +0000
1429@@ -0,0 +1,258 @@
1430+/*
1431+ * Copyright (C) 2016 Canonical, Ltd.
1432+ *
1433+ * This program is free software: you can redistribute it and/or modify it under
1434+ * the terms of the GNU Lesser General Public License version 3, as published by
1435+ * the Free Software Foundation.
1436+ *
1437+ * This program is distributed in the hope that it will be useful, but WITHOUT
1438+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1439+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1440+ * Lesser General Public License for more details.
1441+ *
1442+ * You should have received a copy of the GNU Lesser General Public License
1443+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1444+ */
1445+
1446+#ifndef TOPLEVELWINDOWMODEL_H
1447+#define TOPLEVELWINDOWMODEL_H
1448+
1449+#include <QAbstractListModel>
1450+#include <QLoggingCategory>
1451+
1452+Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
1453+
1454+class Window;
1455+
1456+namespace unity {
1457+ namespace shell {
1458+ namespace application {
1459+ class ApplicationInfoInterface;
1460+ class ApplicationManagerInterface;
1461+ class MirSurfaceInterface;
1462+ class SurfaceManagerInterface;
1463+ }
1464+ }
1465+}
1466+
1467+/**
1468+ * @brief A model of top-level surfaces
1469+ *
1470+ * It's an abstraction of top-level application windows.
1471+ *
1472+ * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
1473+ * still starting up. A shell should then display a splash screen or saved screenshot of the application
1474+ * until its surface comes up.
1475+ *
1476+ * As applications can have multiple surfaces and you can also have entries without surfaces at all,
1477+ * the only way to unambiguously refer to an entry in this model is through its id.
1478+ */
1479+class TopLevelWindowModel : public QAbstractListModel
1480+{
1481+ Q_OBJECT
1482+
1483+ /**
1484+ * @brief Number of top-level surfaces in this model
1485+ *
1486+ * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
1487+ */
1488+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
1489+
1490+ /**
1491+ * @brief The input method surface, if any
1492+ *
1493+ * The surface of a onscreen keyboard (akak "virtual keyboard") would be kept here and not in the model itself.
1494+ */
1495+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
1496+
1497+ /**
1498+ * @brief The currently focused window, if any
1499+ */
1500+ Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
1501+
1502+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
1503+ READ surfaceManager
1504+ WRITE setSurfaceManager
1505+ NOTIFY surfaceManagerChanged)
1506+
1507+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
1508+ READ applicationManager
1509+ WRITE setApplicationManager
1510+ NOTIFY applicationManagerChanged)
1511+
1512+ /**
1513+ The id to be used on the next entry created
1514+ Useful for tests
1515+ */
1516+ Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
1517+
1518+public:
1519+ /**
1520+ * @brief The Roles supported by the model
1521+ *
1522+ * WindowRole - A Window.
1523+ * ApplicationRole - An ApplicationInfoInterface
1524+ */
1525+ enum Roles {
1526+ WindowRole = Qt::UserRole,
1527+ ApplicationRole = Qt::UserRole + 1,
1528+ };
1529+
1530+ TopLevelWindowModel();
1531+
1532+ // From QAbstractItemModel
1533+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
1534+ QVariant data(const QModelIndex& index, int role) const override;
1535+ QHash<int, QByteArray> roleNames() const override {
1536+ QHash<int, QByteArray> roleNames { {WindowRole, "window"},
1537+ {ApplicationRole, "application"} };
1538+ return roleNames;
1539+ }
1540+
1541+ // Own API
1542+
1543+ unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
1544+ Window* focusedWindow() const;
1545+
1546+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
1547+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
1548+
1549+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
1550+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
1551+
1552+ int nextId() const { return m_nextId; }
1553+
1554+public:
1555+ /**
1556+ * @brief Returns the surface at the given index
1557+ *
1558+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
1559+ * and drawn into a surface.
1560+ *
1561+ * Same as windowAt(index).surface()
1562+ */
1563+ Q_INVOKABLE unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
1564+
1565+ /**
1566+ * @brief Returns the window at the given index
1567+ *
1568+ * Will always be valid
1569+ */
1570+ Q_INVOKABLE Window *windowAt(int index) const;
1571+
1572+ /**
1573+ * @brief Returns the application at the given index
1574+ */
1575+ Q_INVOKABLE unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
1576+
1577+ /**
1578+ * @brief Returns the unique id of the element at the given index
1579+ */
1580+ Q_INVOKABLE int idAt(int index) const;
1581+
1582+ /**
1583+ * @brief Returns the index where the row with the given id is located
1584+ *
1585+ * Returns -1 if there's no row with the given id.
1586+ */
1587+ Q_INVOKABLE int indexForId(int id) const;
1588+
1589+ /**
1590+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
1591+ */
1592+ Q_INVOKABLE void raiseId(int id);
1593+
1594+Q_SIGNALS:
1595+ void countChanged();
1596+ void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
1597+ void focusedWindowChanged(Window *focusedWindow);
1598+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
1599+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
1600+
1601+ /**
1602+ * @brief Emitted when the list changes
1603+ *
1604+ * Emitted when model gains an element, loses an element or when elements exchange positions.
1605+ */
1606+ void listChanged();
1607+
1608+ void nextIdChanged();
1609+
1610+private Q_SLOTS:
1611+ void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
1612+ void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
1613+
1614+ void onModificationsStarted();
1615+ void onModificationsEnded();
1616+
1617+private:
1618+ void doRaiseId(int id);
1619+ int generateId();
1620+ int nextFreeId(int candidateId, const int latestId);
1621+ int nextId(int id) const;
1622+ QString toString();
1623+ int indexOf(unity::shell::application::MirSurfaceInterface *surface);
1624+
1625+ void setInputMethodWindow(Window *window);
1626+ void setFocusedWindow(Window *window);
1627+ void removeInputMethodWindow();
1628+ int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
1629+ void removeAt(int index);
1630+
1631+ void addApplication(unity::shell::application::ApplicationInfoInterface *application);
1632+ void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
1633+
1634+ void prependPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
1635+ void prependSurface(unity::shell::application::MirSurfaceInterface *surface,
1636+ unity::shell::application::ApplicationInfoInterface *application);
1637+ void prependSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
1638+ unity::shell::application::ApplicationInfoInterface *application);
1639+
1640+ void connectWindow(Window *window);
1641+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
1642+
1643+ void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
1644+ void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
1645+
1646+ void move(int from, int to);
1647+
1648+ void activateEmptyWindow(Window *window);
1649+
1650+ struct ModelEntry {
1651+ ModelEntry() {}
1652+ ModelEntry(Window *window,
1653+ unity::shell::application::ApplicationInfoInterface *application)
1654+ : window(window), application(application) {}
1655+ Window *window{nullptr};
1656+ unity::shell::application::ApplicationInfoInterface *application{nullptr};
1657+ bool removeOnceSurfaceDestroyed{false};
1658+ };
1659+
1660+ QVector<ModelEntry> m_windowModel;
1661+ Window* m_inputMethodWindow{nullptr};
1662+ Window* m_focusedWindow{nullptr};
1663+
1664+ int m_nextId{1};
1665+ // Just something big enough that we don't risk running out of unused id numbers.
1666+ // Not sure if QML int type supports something close to std::numeric_limits<int>::max() and
1667+ // there's no reason to try out its limits.
1668+ static const int m_maxId{1000000};
1669+
1670+ unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
1671+ unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
1672+
1673+ enum ModelState {
1674+ IdleState,
1675+ InsertingState,
1676+ RemovingState,
1677+ MovingState,
1678+ ResettingState
1679+ };
1680+ ModelState m_modelState{IdleState};
1681+
1682+ // Valid between modificationsStarted and modificationsEnded
1683+ bool m_focusedWindowChanged{false};
1684+ Window *m_newlyFocusedWindow{nullptr};
1685+};
1686+
1687+#endif // TOPLEVELWINDOWMODEL_H
1688
1689=== added file 'plugins/WindowManager/Window.cpp'
1690--- plugins/WindowManager/Window.cpp 1970-01-01 00:00:00 +0000
1691+++ plugins/WindowManager/Window.cpp 2016-11-23 15:15:21 +0000
1692@@ -0,0 +1,234 @@
1693+/*
1694+ * Copyright (C) 2016 Canonical, Ltd.
1695+ *
1696+ * This program is free software; you can redistribute it and/or modify
1697+ * it under the terms of the GNU General Public License as published by
1698+ * the Free Software Foundation; version 3.
1699+ *
1700+ * This program is distributed in the hope that it will be useful,
1701+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1702+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1703+ * GNU General Public License for more details.
1704+ *
1705+ * You should have received a copy of the GNU General Public License
1706+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1707+ */
1708+
1709+#include "Window.h"
1710+
1711+// unity-api
1712+#include <unity/shell/application/MirSurfaceInterface.h>
1713+
1714+#include <QQmlEngine>
1715+
1716+namespace unityapi = unity::shell::application;
1717+
1718+Q_LOGGING_CATEGORY(UNITY_WINDOW, "unity.window", QtWarningMsg)
1719+
1720+#define DEBUG_MSG qCDebug(UNITY_WINDOW).nospace() << qPrintable(toString()) << "::" << __func__
1721+
1722+Window::Window(int id, QObject *parent)
1723+ : QObject(parent)
1724+ , m_id(id)
1725+{
1726+ DEBUG_MSG << "()";
1727+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
1728+}
1729+
1730+Window::~Window()
1731+{
1732+ DEBUG_MSG << "()";
1733+}
1734+
1735+QPoint Window::position() const
1736+{
1737+ return m_position;
1738+}
1739+
1740+QPoint Window::requestedPosition() const
1741+{
1742+ return m_requestedPosition;
1743+}
1744+
1745+void Window::setRequestedPosition(const QPoint &value)
1746+{
1747+ m_positionRequested = true;
1748+ if (value != m_requestedPosition) {
1749+ m_requestedPosition = value;
1750+ Q_EMIT requestedPositionChanged(m_requestedPosition);
1751+ if (m_surface) {
1752+ m_surface->setRequestedPosition(value);
1753+ } else {
1754+ // fake-miral: always comply
1755+ m_position = m_requestedPosition;
1756+ Q_EMIT positionChanged(m_position);
1757+ }
1758+ }
1759+}
1760+
1761+Mir::State Window::state() const
1762+{
1763+ return m_state;
1764+}
1765+
1766+bool Window::focused() const
1767+{
1768+ return m_focused;
1769+}
1770+
1771+bool Window::confinesMousePointer() const
1772+{
1773+ if (m_surface) {
1774+ return m_surface->confinesMousePointer();
1775+ } else {
1776+ return false;
1777+ }
1778+}
1779+
1780+int Window::id() const
1781+{
1782+ return m_id;
1783+}
1784+
1785+unityapi::MirSurfaceInterface* Window::surface() const
1786+{
1787+ return m_surface;
1788+}
1789+
1790+void Window::requestState(Mir::State state)
1791+{
1792+ m_stateRequested = true;
1793+ if (m_surface) {
1794+ m_surface->requestState(state);
1795+ } else if (m_state != state) {
1796+ m_state = state;
1797+ Q_EMIT stateChanged(m_state);
1798+ }
1799+}
1800+
1801+void Window::close()
1802+{
1803+ if (m_surface) {
1804+ m_surface->close();
1805+ } else {
1806+ Q_EMIT closeRequested();
1807+ }
1808+}
1809+
1810+void Window::activate()
1811+{
1812+ if (m_surface) {
1813+ m_surface->activate();
1814+ } else {
1815+ Q_EMIT emptyWindowActivated();
1816+ }
1817+}
1818+
1819+void Window::setSurface(unityapi::MirSurfaceInterface *surface)
1820+{
1821+ DEBUG_MSG << "(" << surface << ")";
1822+ if (m_surface) {
1823+ disconnect(m_surface, 0, this, 0);
1824+ }
1825+
1826+ m_surface = surface;
1827+
1828+ if (m_surface) {
1829+ connect(surface, &unityapi::MirSurfaceInterface::focusRequested, this, [this]() {
1830+ Q_EMIT focusRequested();
1831+ });
1832+
1833+ connect(surface, &unityapi::MirSurfaceInterface::closeRequested, this, &Window::closeRequested);
1834+
1835+ connect(surface, &unityapi::MirSurfaceInterface::positionChanged, this, [this]() {
1836+ updatePosition();
1837+ });
1838+
1839+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [this]() {
1840+ updateState();
1841+ });
1842+
1843+ connect(surface, &unityapi::MirSurfaceInterface::focusedChanged, this, [this]() {
1844+ updateFocused();
1845+ });
1846+
1847+ // bring it up to speed
1848+ if (m_positionRequested) {
1849+ m_surface->setRequestedPosition(m_requestedPosition);
1850+ }
1851+ if (m_stateRequested) {
1852+ m_surface->requestState(m_state);
1853+ }
1854+
1855+ // and sync with surface
1856+ updatePosition();
1857+ updateState();
1858+ updateFocused();
1859+ }
1860+
1861+ Q_EMIT surfaceChanged(surface);
1862+}
1863+
1864+void Window::updatePosition()
1865+{
1866+ if (m_surface->position() != m_position) {
1867+ m_position = m_surface->position();
1868+ Q_EMIT positionChanged(m_position);
1869+ }
1870+}
1871+
1872+void Window::updateState()
1873+{
1874+ if (m_surface->state() != m_state) {
1875+ m_state = m_surface->state();
1876+ Q_EMIT stateChanged(m_state);
1877+ }
1878+}
1879+
1880+void Window::updateFocused()
1881+{
1882+ if (m_surface->focused() != m_focused) {
1883+ m_focused = m_surface->focused();
1884+ Q_EMIT focusedChanged(m_focused);
1885+ }
1886+}
1887+
1888+void Window::setFocused(bool value)
1889+{
1890+ if (value != m_focused) {
1891+ DEBUG_MSG << "(" << value << ")";
1892+ m_focused = value;
1893+ Q_EMIT focusedChanged(m_focused);
1894+ // when we have a surface we get focus changes from updateFocused() instead
1895+ Q_ASSERT(!m_surface);
1896+ }
1897+}
1898+
1899+QString Window::toString() const
1900+{
1901+ if (surface()) {
1902+ return QString("Window[0x%1, id=%2, MirSurface[0x%3,\"%4\"]]").arg(
1903+ QString::number((quintptr)this, 16),
1904+ QString::number(id()),
1905+ QString::number((quintptr)surface(), 16),
1906+ surface()->name());
1907+ } else {
1908+ return QString("Window[0x%1, id=%2, null]").arg(
1909+ QString::number((quintptr)this, 0, 16),
1910+ QString::number(id()));
1911+ }
1912+}
1913+
1914+QDebug operator<<(QDebug dbg, const Window *window)
1915+{
1916+ QDebugStateSaver saver(dbg);
1917+ dbg.nospace();
1918+
1919+ if (window) {
1920+ dbg << qPrintable(window->toString());
1921+ } else {
1922+ dbg << (void*)(window);
1923+ }
1924+
1925+ return dbg;
1926+}
1927
1928=== added file 'plugins/WindowManager/Window.h'
1929--- plugins/WindowManager/Window.h 1970-01-01 00:00:00 +0000
1930+++ plugins/WindowManager/Window.h 2016-11-23 15:15:21 +0000
1931@@ -0,0 +1,161 @@
1932+/*
1933+ * Copyright (C) 2016 Canonical, Ltd.
1934+ *
1935+ * This program is free software; you can redistribute it and/or modify
1936+ * it under the terms of the GNU General Public License as published by
1937+ * the Free Software Foundation; version 3.
1938+ *
1939+ * This program is distributed in the hope that it will be useful,
1940+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1941+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1942+ * GNU General Public License for more details.
1943+ *
1944+ * You should have received a copy of the GNU General Public License
1945+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1946+ */
1947+
1948+#ifndef QTMIR_WINDOW_H
1949+#define QTMIR_WINDOW_H
1950+
1951+#include <QLoggingCategory>
1952+#include <QObject>
1953+#include <QPoint>
1954+
1955+// Unity API
1956+#include <unity/shell/application/Mir.h>
1957+
1958+namespace unity {
1959+ namespace shell {
1960+ namespace application {
1961+ class MirSurfaceInterface;
1962+ }
1963+ }
1964+}
1965+
1966+
1967+Q_DECLARE_LOGGING_CATEGORY(UNITY_WINDOW)
1968+
1969+/**
1970+ @brief A slightly higher concept than MirSurface
1971+
1972+ A Window exists before its MirSurface gets created (for splashscreen purposes)
1973+ and might also hang around after the backing surface is gone (In case the application
1974+ was killed to free up memory, as it should still remain in the window list since the user
1975+ did not explicitly close it).
1976+ */
1977+class Window : public QObject
1978+{
1979+ Q_OBJECT
1980+
1981+ /**
1982+ * @brief Position of the current surface buffer, in pixels.
1983+ */
1984+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
1985+
1986+ /**
1987+ * @brief Requested position of the current surface buffer, in pixels.
1988+ */
1989+ Q_PROPERTY(QPoint requestedPosition READ requestedPosition WRITE setRequestedPosition NOTIFY requestedPositionChanged)
1990+
1991+ /**
1992+ * @brief State of the surface
1993+ */
1994+ Q_PROPERTY(Mir::State state READ state NOTIFY stateChanged)
1995+
1996+ /**
1997+ * @brief Whether the surface is focused
1998+ *
1999+ * It will be true if this surface is MirFocusControllerInterface::focusedSurface
2000+ */
2001+ Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged)
2002+
2003+ /**
2004+ * @brief Whether the surface wants to confine the mouse pointer within its boundaries
2005+ *
2006+ * If true, the surface doesn't want the mouse pointer to leave its boundaries while it's focused.
2007+ */
2008+ Q_PROPERTY(bool confinesMousePointer READ confinesMousePointer NOTIFY confinesMousePointerChanged)
2009+
2010+ /**
2011+ * @brief A unique identifier for this window.
2012+ * Useful for telling windows apart in a list model as they get moved around
2013+ */
2014+ Q_PROPERTY(int id READ id CONSTANT)
2015+
2016+ /**
2017+ * @brief Surface backing up this window
2018+ * It might be null if a surface hasn't been created yet (application is starting up) or if
2019+ * the corresponding application has been killed (but can still get restarted to continue from
2020+ * where it left)
2021+ */
2022+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
2023+
2024+public:
2025+ Window(int id, QObject *parent = nullptr);
2026+ virtual ~Window();
2027+ QPoint position() const;
2028+ QPoint requestedPosition() const;
2029+ void setRequestedPosition(const QPoint &);
2030+ Mir::State state() const;
2031+ bool focused() const;
2032+ bool confinesMousePointer() const;
2033+ int id() const;
2034+ unity::shell::application::MirSurfaceInterface* surface() const;
2035+
2036+ void setSurface(unity::shell::application::MirSurfaceInterface *surface);
2037+ void setFocused(bool value);
2038+
2039+ QString toString() const;
2040+
2041+public Q_SLOTS:
2042+ /**
2043+ * @brief Requests a change to the specified state
2044+ */
2045+ void requestState(Mir::State state);
2046+
2047+ /**
2048+ * @brief Sends a close request
2049+ *
2050+ */
2051+ void close();
2052+
2053+ /**
2054+ * @brief Focuses and raises the window
2055+ */
2056+ void activate();
2057+
2058+Q_SIGNALS:
2059+ void closeRequested();
2060+ void emptyWindowActivated();
2061+
2062+ void positionChanged(QPoint position);
2063+ void requestedPositionChanged(QPoint position);
2064+ void stateChanged(Mir::State value);
2065+ void focusedChanged(bool value);
2066+ void confinesMousePointerChanged(bool value);
2067+ void surfaceChanged(unity::shell::application::MirSurfaceInterface *surface);
2068+
2069+ /**
2070+ * @brief Emitted when focus for this window is requested by an external party
2071+ */
2072+ void focusRequested();
2073+
2074+private:
2075+ void updatePosition();
2076+ void updateState();
2077+ void updateFocused();
2078+
2079+ QPoint m_position;
2080+ QPoint m_requestedPosition;
2081+ bool m_positionRequested{false};
2082+ bool m_focused{false};
2083+ int m_id;
2084+ Mir::State m_state{Mir::RestoredState};
2085+ bool m_stateRequested{false};
2086+ unity::shell::application::MirSurfaceInterface *m_surface{nullptr};
2087+};
2088+
2089+QDebug operator<<(QDebug dbg, const Window *window);
2090+
2091+Q_DECLARE_METATYPE(Window*)
2092+#endif // QTMIR_WINDOW_H
2093
2094=== modified file 'plugins/WindowManager/WindowManagerPlugin.cpp'
2095--- plugins/WindowManager/WindowManagerPlugin.cpp 2016-04-04 13:37:49 +0000
2096+++ plugins/WindowManager/WindowManagerPlugin.cpp 2016-11-23 15:15:21 +0000
2097@@ -16,13 +16,16 @@
2098
2099 #include "WindowManagerPlugin.h"
2100
2101-#include "TopLevelSurfaceList.h"
2102+#include "TopLevelWindowModel.h"
2103+#include "Window.h"
2104
2105 #include <QtQml>
2106
2107 void WindowManagerPlugin::registerTypes(const char *uri)
2108 {
2109- qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
2110+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
2111+
2112+ qRegisterMetaType<Window*>("Window*");
2113
2114 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
2115 }
2116
2117=== modified file 'qml/Components/InputMethod.qml'
2118--- qml/Components/InputMethod.qml 2016-10-06 13:06:31 +0000
2119+++ qml/Components/InputMethod.qml 2016-11-23 15:15:21 +0000
2120@@ -23,6 +23,8 @@
2121
2122 readonly property rect visibleRect: surfaceItem.surface && visible ? surfaceItem.surface.inputBounds : Qt.rect(0, 0, 0, 0)
2123
2124+ property var surface
2125+
2126 MirSurfaceItem {
2127 id: surfaceItem
2128 anchors.fill: parent
2129@@ -31,7 +33,7 @@
2130
2131 surfaceWidth: root.enabled ? width : -1
2132 surfaceHeight: root.enabled ? height : -1
2133- surface: SurfaceManager.inputMethodSurface
2134+ surface: root.surface
2135
2136 onLiveChanged: {
2137 if (surface !== null && !live) {
2138@@ -50,7 +52,6 @@
2139 }
2140
2141 visible: surfaceItem.surface &&
2142- surfaceItem.surfaceState != Mir.HiddenState &&
2143- surfaceItem.surfaceState != Mir.MinimizedState &&
2144- root.enabled
2145+ surfaceItem.surface.visible &&
2146+ root.enabled
2147 }
2148
2149=== modified file 'qml/Components/KeymapSwitcher.qml'
2150--- qml/Components/KeymapSwitcher.qml 2016-07-11 14:40:56 +0000
2151+++ qml/Components/KeymapSwitcher.qml 2016-11-23 15:15:21 +0000
2152@@ -23,6 +23,9 @@
2153 QtObject {
2154 id: root
2155
2156+ // to be set from outside
2157+ property var focusedSurface: null
2158+
2159 property GlobalShortcut shortcutNext: GlobalShortcut {
2160 shortcut: Qt.MetaModifier|Qt.Key_Space
2161 onTriggered: root.nextKeymap()
2162@@ -60,7 +63,7 @@
2163 }
2164
2165 property Binding surfaceKeymapBinding: Binding {
2166- target: MirFocusController.focusedSurface
2167+ target: root.focusedSurface
2168 property: "keymap"
2169 value: root.currentKeymap
2170 }
2171
2172=== modified file 'qml/Shell.qml'
2173--- qml/Shell.qml 2016-11-09 15:07:26 +0000
2174+++ qml/Shell.qml 2016-11-23 15:15:21 +0000
2175@@ -43,7 +43,7 @@
2176 import Unity.DashCommunicator 0.1
2177 import Unity.Indicators 0.1 as Indicators
2178 import Cursor 1.1
2179-import WindowManager 0.1
2180+import WindowManager 1.0
2181
2182
2183 StyledItem {
2184@@ -264,10 +264,15 @@
2185 width: parent.width
2186 height: parent.height
2187
2188- TopLevelSurfaceList {
2189+ SurfaceManager {
2190+ id: surfaceMan
2191+ objectName: "surfaceManager"
2192+ }
2193+ TopLevelWindowModel {
2194 id: topLevelSurfaceList
2195 objectName: "topLevelSurfaceList"
2196- applicationsModel: ApplicationManager
2197+ applicationManager: ApplicationManager // it's a singleton
2198+ surfaceManager: surfaceMan
2199 }
2200
2201 Stage {
2202@@ -320,6 +325,7 @@
2203 InputMethod {
2204 id: inputMethod
2205 objectName: "inputMethod"
2206+ surface: topLevelSurfaceList.inputMethodSurface
2207 anchors {
2208 fill: parent
2209 topMargin: panel.panelHeight
2210@@ -486,8 +492,8 @@
2211 greeterShown: greeter.shown
2212 }
2213
2214- readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
2215- ? MirFocusController.focusedSurface.state === Mir.FullscreenState
2216+ readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
2217+ ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
2218 : false
2219 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
2220 || greeter.hasLockedApp
2221@@ -787,7 +793,9 @@
2222 }
2223
2224 // non-visual object
2225- KeymapSwitcher {}
2226+ KeymapSwitcher {
2227+ focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
2228+ }
2229
2230 Rectangle {
2231 id: shutdownFadeOutRectangle
2232
2233=== modified file 'qml/Stage/DecoratedWindow.qml'
2234--- qml/Stage/DecoratedWindow.qml 2016-10-07 11:20:09 +0000
2235+++ qml/Stage/DecoratedWindow.qml 2016-11-23 15:15:21 +0000
2236@@ -149,7 +149,7 @@
2237
2238 WindowDecoration {
2239 id: decoration
2240- target: root.parent || null
2241+ closeButtonVisible: root.application.appId !== "unity8-dash"
2242 objectName: "appWindowDecoration"
2243 anchors { left: parent.left; top: parent.top; right: parent.right }
2244 height: units.gu(3)
2245
2246=== modified file 'qml/Stage/FakeMaximizeDelegate.qml'
2247--- qml/Stage/FakeMaximizeDelegate.qml 2016-09-13 11:53:40 +0000
2248+++ qml/Stage/FakeMaximizeDelegate.qml 2016-11-23 15:15:21 +0000
2249@@ -98,19 +98,19 @@
2250 function commit() {
2251 if (progress > hintThreshold && edge != -1) {
2252 if (edge == Item.Top) {
2253- target.maximize();
2254+ target.requestMaximize();
2255 } else if (edge == Item.Left) {
2256- target.maximizeLeft();
2257+ target.requestMaximizeLeft();
2258 } else if (edge == Item.Right) {
2259- target.maximizeRight();
2260+ target.requestMaximizeRight();
2261 } else if (edge == Item.TopLeft) {
2262- target.maximizeTopLeft();
2263+ target.requestMaximizeTopLeft();
2264 } else if (edge == Item.TopRight) {
2265- target.maximizeTopRight();
2266+ target.requestMaximizeTopRight();
2267 } else if (edge == Item.BottomLeft) {
2268- target.maximizeBottomLeft();
2269+ target.requestMaximizeBottomLeft();
2270 } else if (edge == Item.BottomRight) {
2271- target.maximizeBottomRight();
2272+ target.requestMaximizeBottomRight();
2273 }
2274 } else {
2275 stop();
2276
2277=== modified file 'qml/Stage/Stage.qml'
2278--- qml/Stage/Stage.qml 2016-11-12 16:05:40 +0000
2279+++ qml/Stage/Stage.qml 2016-11-23 15:15:21 +0000
2280@@ -117,8 +117,7 @@
2281 }
2282 }
2283
2284- property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
2285- priv.focusedAppDelegate.surface.confinesMousePointer ?
2286+ property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
2287 priv.focusedAppDelegate.clientAreaItem : null;
2288
2289 signal itemSnapshotRequested(Item item)
2290@@ -181,29 +180,34 @@
2291 GlobalShortcut {
2292 id: maximizeWindowShortcut
2293 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
2294- onTriggered: priv.focusedAppDelegate.maximize()
2295+ onTriggered: priv.focusedAppDelegate.requestMaximize()
2296 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
2297 }
2298
2299 GlobalShortcut {
2300 id: maximizeWindowLeftShortcut
2301 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
2302- onTriggered: priv.focusedAppDelegate.maximizeLeft()
2303+ onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
2304 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
2305 }
2306
2307 GlobalShortcut {
2308 id: maximizeWindowRightShortcut
2309 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
2310- onTriggered: priv.focusedAppDelegate.maximizeRight()
2311+ onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
2312 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
2313 }
2314
2315 GlobalShortcut {
2316 id: minimizeRestoreShortcut
2317 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
2318- onTriggered: priv.focusedAppDelegate.anyMaximized
2319- ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
2320+ onTriggered: {
2321+ if (priv.focusedAppDelegate.anyMaximized) {
2322+ priv.focusedAppDelegate.requestRestore();
2323+ } else {
2324+ priv.focusedAppDelegate.requestMinimize();
2325+ }
2326+ }
2327 active: root.state == "windowed" && priv.focusedAppDelegate
2328 }
2329
2330@@ -239,20 +243,10 @@
2331 }
2332
2333 function minimizeAllWindows() {
2334- for (var i = 0; i < appRepeater.count; i++) {
2335- var appDelegate = appRepeater.itemAt(i);
2336- if (appDelegate && !appDelegate.minimized) {
2337- appDelegate.minimize();
2338- }
2339- }
2340- }
2341-
2342- function focusNext() {
2343- for (var i = 0; i < appRepeater.count; i++) {
2344- var appDelegate = appRepeater.itemAt(i);
2345- if (appDelegate && !appDelegate.minimized) {
2346- appDelegate.focus = true;
2347- return;
2348+ for (var i = appRepeater.count - 1; i >= 0; i--) {
2349+ var appDelegate = appRepeater.itemAt(i);
2350+ if (appDelegate && !appDelegate.minimized) {
2351+ appDelegate.requestMinimize();
2352 }
2353 }
2354 }
2355@@ -353,8 +347,8 @@
2356 Connections {
2357 target: PanelState
2358 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
2359- onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
2360- onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
2361+ onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
2362+ onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
2363 }
2364
2365 Binding {
2366@@ -429,13 +423,6 @@
2367 }
2368 }
2369
2370- Binding {
2371- target: MirFocusController
2372- property: "focusedSurface"
2373- value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
2374- when: !appRepeater.startingUp && root.parent
2375- }
2376-
2377 states: [
2378 State {
2379 name: "spread"; when: priv.goneToSpread
2380@@ -628,7 +615,7 @@
2381
2382 onShownChanged: {
2383 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
2384- priv.mainStageDelegate.claimFocus();
2385+ priv.mainStageDelegate.activate();
2386 }
2387 }
2388
2389@@ -661,14 +648,23 @@
2390 }
2391 }
2392
2393- TopLevelSurfaceRepeater {
2394+ Repeater {
2395 id: appRepeater
2396 model: topLevelSurfaceList
2397 objectName: "appRepeater"
2398
2399+ function indexOf(delegateItem) {
2400+ for (var i = 0; i < count; i++) {
2401+ if (itemAt(i) === delegateItem) {
2402+ return i;
2403+ }
2404+ }
2405+ return -1;
2406+ }
2407+
2408 delegate: FocusScope {
2409 id: appDelegate
2410- objectName: "appDelegate_" + model.id
2411+ objectName: "appDelegate_" + model.window.id
2412 property int itemIndex: index // We need this from outside the repeater
2413 // z might be overriden in some cases by effects, but we need z ordering
2414 // to calculate occlusion detection
2415@@ -680,11 +676,11 @@
2416 }
2417 z: normalZ
2418
2419- // Normally we want x/y where we request it to be. Width/height of our delegate will
2420+ // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
2421 // match what the actual surface size is.
2422 // Don't write to those, they will be set by states
2423- x: requestedX
2424- y: requestedY
2425+ x: model.window.position.x - clientAreaItem.x
2426+ y: model.window.position.y - clientAreaItem.y
2427 width: decoratedWindow.implicitWidth
2428 height: decoratedWindow.implicitHeight
2429
2430@@ -694,6 +690,12 @@
2431 property real requestedY: windowedY
2432 property real requestedWidth: windowedWidth
2433 property real requestedHeight: windowedHeight
2434+ Binding {
2435+ target: model.window; property: "requestedPosition"
2436+ // miral doesn't know about our window decorations. So we have to deduct them
2437+ value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
2438+ appDelegate.requestedY + appDelegate.clientAreaItem.y)
2439+ }
2440
2441 // In those are for windowed mode. Those values basically store the window's properties
2442 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
2443@@ -762,7 +764,7 @@
2444 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
2445
2446 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
2447- readonly property bool fullscreen: surface ? surface.state === Mir.FullscreenState : application.fullscreen
2448+ readonly property bool fullscreen: window.state === Mir.FullscreenState
2449
2450 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
2451 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
2452@@ -788,7 +790,9 @@
2453 priv.updateMainAndSideStageIndexes()
2454 }
2455
2456- readonly property var surface: model.surface
2457+ readonly property var surface: model.window.surface
2458+ readonly property var window: model.window
2459+
2460 readonly property alias resizeArea: resizeArea
2461 readonly property alias focusedSurface: decoratedWindow.focusedSurface
2462 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
2463@@ -797,6 +801,25 @@
2464 readonly property bool isDash: appId == "unity8-dash"
2465 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
2466
2467+ function activate() {
2468+ if (model.window.focused) {
2469+ updateQmlFocusFromMirSurfaceFocus();
2470+ } else {
2471+ model.window.activate();
2472+ }
2473+ }
2474+ function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
2475+ function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
2476+ function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
2477+ function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
2478+ function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
2479+ function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
2480+ function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
2481+ function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
2482+ function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
2483+ function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
2484+ function requestRestore() { model.window.requestState(Mir.RestoredState); }
2485+
2486 function claimFocus() {
2487 if (root.state == "spread") {
2488 spreadItem.highlightedIndex = index
2489@@ -808,45 +831,58 @@
2490 }
2491 priv.updateMainAndSideStageIndexes();
2492 }
2493-
2494 if (root.mode == "windowed") {
2495 appDelegate.restore(true /* animated */, appDelegate.windowState);
2496- } else {
2497- appDelegate.focus = true;
2498 }
2499+ appDelegate.focus = true;
2500 }
2501- Connections {
2502- target: model.surface
2503- onFocusRequested: {
2504+
2505+ function updateQmlFocusFromMirSurfaceFocus() {
2506+ if (model.window.focused) {
2507 claimFocus();
2508- }
2509- }
2510- Connections {
2511- target: model.application
2512- onFocusRequested: {
2513- if (!model.surface) {
2514- // when an app has no surfaces, we assume there's only one entry representing it:
2515- // this delegate.
2516- claimFocus();
2517- } else {
2518- // if the application has surfaces, focus request should be at surface-level.
2519- }
2520- }
2521- }
2522-
2523- onFocusChanged: {
2524- if (appRepeater.startingUp)
2525- return;
2526-
2527- if (focus) {
2528- topLevelSurfaceList.raiseId(model.id);
2529 priv.focusedAppDelegate = appDelegate;
2530- } else if (!focus && priv.focusedAppDelegate === appDelegate && root.state != "spread") {
2531- priv.focusedAppDelegate = null;
2532- // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
2533- MirFocusController.focusedSurface = null;
2534- }
2535- }
2536+ }
2537+ }
2538+
2539+ Connections {
2540+ target: model.window
2541+ onFocusedChanged: {
2542+ updateQmlFocusFromMirSurfaceFocus();
2543+ }
2544+ onFocusRequested: {
2545+ appDelegate.activate();
2546+ }
2547+ onStateChanged: {
2548+ if (model.window.state === Mir.MinimizedState) {
2549+ appDelegate.minimize();
2550+ } else if (model.window.state === Mir.MaximizedState) {
2551+ appDelegate.maximize();
2552+ } else if (model.window.state === Mir.VertMaximizedState) {
2553+ appDelegate.maximizeVertically();
2554+ } else if (model.window.state === Mir.HorizMaximizedState) {
2555+ appDelegate.maximizeHorizontally();
2556+ } else if (model.window.state === Mir.MaximizedLeftState) {
2557+ appDelegate.maximizeLeft();
2558+ } else if (model.window.state === Mir.MaximizedRightState) {
2559+ appDelegate.maximizeRight();
2560+ } else if (model.window.state === Mir.MaximizedTopLeftState) {
2561+ appDelegate.maximizeTopLeft();
2562+ } else if (model.window.state === Mir.MaximizedTopRightState) {
2563+ appDelegate.maximizeTopRight();
2564+ } else if (model.window.state === Mir.MaximizedBottomLeftState) {
2565+ appDelegate.maximizeBottomLeft();
2566+ } else if (model.window.state === Mir.MaximizedBottomRightState) {
2567+ appDelegate.maximizeBottomRight();
2568+ } else if (model.window.state === Mir.RestoredState) {
2569+ if (appDelegate.anyMaximized) {
2570+ appDelegate.restoreFromMaximized()
2571+ } else {
2572+ appDelegate.restore();
2573+ }
2574+ }
2575+ }
2576+ }
2577+
2578 Component.onCompleted: {
2579 if (application && application.rotatesWindowContents) {
2580 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
2581@@ -860,16 +896,7 @@
2582 // Now load any saved state. This needs to happen *after* the cascading!
2583 resizeArea.loadWindowState();
2584
2585- // NB: We're differentiating if this delegate was created in response to a new entry in the model
2586- // or if the Repeater is just populating itself with delegates to match the model it received.
2587- if (!appRepeater.startingUp) {
2588- // a top level window is always the focused one when it first appears, unfocusing
2589- // any preexisting one
2590- if (root.state == "spread") {
2591- spreadItem.highlightedIndex = index;
2592- }
2593- claimFocus();
2594- }
2595+ updateQmlFocusFromMirSurfaceFocus();
2596
2597 refreshStage();
2598 _constructing = false;
2599@@ -884,16 +911,6 @@
2600 priv.updateForegroundMaximizedApp();
2601 }
2602
2603- if (focus) {
2604- // focus some other window
2605- for (var i = 0; i < appRepeater.count; i++) {
2606- var appDelegate = appRepeater.itemAt(i);
2607- if (appDelegate && !appDelegate.minimized && i != index) {
2608- appDelegate.focus = true;
2609- return;
2610- }
2611- }
2612- }
2613 }
2614
2615 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
2616@@ -914,7 +931,7 @@
2617 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
2618
2619 function close() {
2620- model.surface.close();
2621+ model.window.close();
2622 }
2623
2624 function maximize(animated) {
2625@@ -965,7 +982,6 @@
2626 animationsEnabled = (animated === undefined) || animated;
2627 windowState = state || WindowStateStorage.WindowStateRestored;
2628 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
2629- focus = true;
2630 }
2631
2632 function playFocusAnimation() {
2633@@ -979,7 +995,7 @@
2634 rightEdgeFocusAnimation.start()
2635 }
2636 } else if (state == "windowedRightEdge" || state == "windowed") {
2637- claimFocus();
2638+ activate();
2639 } else {
2640 focusAnimation.start()
2641 }
2642@@ -1017,10 +1033,10 @@
2643 to: 1
2644 duration: UbuntuAnimation.SnapDuration
2645 onStarted: {
2646- topLevelSurfaceList.raiseId(model.id);
2647+ topLevelSurfaceList.raiseId(model.window.id);
2648 }
2649 onStopped: {
2650- appDelegate.claimFocus();
2651+ appDelegate.activate();
2652 }
2653 }
2654 ParallelAnimation {
2655@@ -1030,7 +1046,7 @@
2656 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
2657 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
2658 onStopped: {
2659- appDelegate.focus = true
2660+ appDelegate.activate();
2661 }
2662 }
2663 ParallelAnimation {
2664@@ -1394,14 +1410,6 @@
2665 SequentialAnimation {
2666 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
2667 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
2668- ScriptAction {
2669- script: {
2670- if (appDelegate.minimized) {
2671- appDelegate.focus = false;
2672- priv.focusNext();
2673- }
2674- }
2675- }
2676 }
2677 },
2678 Transition {
2679@@ -1492,7 +1500,7 @@
2680 visible: enabled
2681
2682 onPressed: {
2683- appDelegate.focus = true;
2684+ appDelegate.activate();
2685 }
2686
2687 Component.onDestruction: {
2688@@ -1506,7 +1514,7 @@
2689 anchors.left: appDelegate.left
2690 anchors.top: appDelegate.top
2691 application: model.application
2692- surface: model.surface
2693+ surface: model.window.surface
2694 active: appDelegate.focus
2695 focus: true
2696 interactive: root.interactive
2697@@ -1529,11 +1537,23 @@
2698 onRequestedHeightChanged: oldRequestedHeight = requestedHeight
2699
2700 onCloseClicked: { appDelegate.close(); }
2701- onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
2702- onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
2703- onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
2704- onMinimizeClicked: appDelegate.minimize()
2705- onDecorationPressed: { appDelegate.focus = true; }
2706+ onMaximizeClicked: {
2707+ if (appDelegate.canBeMaximized) {
2708+ appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
2709+ }
2710+ }
2711+ onMaximizeHorizontallyClicked: {
2712+ if (appDelegate.canBeMaximizedHorizontally) {
2713+ appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
2714+ }
2715+ }
2716+ onMaximizeVerticallyClicked: {
2717+ if (appDelegate.canBeMaximizedVertically) {
2718+ appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
2719+ }
2720+ }
2721+ onMinimizeClicked: { appDelegate.requestMinimize(); }
2722+ onDecorationPressed: { appDelegate.activate(); }
2723 onDecorationReleased: fakeRectangle.commit();
2724
2725 property real angle: 0
2726@@ -1580,12 +1600,12 @@
2727 WindowedFullscreenPolicy {
2728 id: windowedFullscreenPolicy
2729 active: root.mode == "windowed"
2730- surface: model.surface
2731+ surface: model.window.surface
2732 }
2733 StagedFullscreenPolicy {
2734 id: stagedFullscreenPolicy
2735 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
2736- surface: model.surface
2737+ surface: model.window.surface
2738 }
2739
2740 SpreadDelegateInputArea {
2741@@ -1598,16 +1618,13 @@
2742 onClicked: {
2743 spreadItem.highlightedIndex = index;
2744 if (distance == 0) {
2745+ model.window.activate();
2746 priv.goneToSpread = false;
2747 }
2748 }
2749 onClose: {
2750 priv.closingIndex = index
2751- if (model.surface) { // could be stopped by OOM
2752- model.surface.close()
2753- } else if (model.application) {
2754- root.applicationManager.stopApplication(model.application.appId);
2755- }
2756+ model.window.close();
2757 }
2758 }
2759
2760
2761=== modified file 'qml/Stage/StagedFullscreenPolicy.qml'
2762--- qml/Stage/StagedFullscreenPolicy.qml 2016-04-04 13:41:19 +0000
2763+++ qml/Stage/StagedFullscreenPolicy.qml 2016-11-23 15:15:21 +0000
2764@@ -32,7 +32,7 @@
2765 onSurfaceChanged: {
2766 if (!active || !surface) return;
2767 if (surface.shellChrome === Mir.LowChrome) {
2768- surface.state = Mir.FullscreenState;
2769+ surface.requestState(Mir.FullscreenState);
2770 }
2771 }
2772
2773@@ -41,15 +41,15 @@
2774 onShellChromeChanged: {
2775 if (!active || !surface) return;
2776 if (surface.shellChrome === Mir.LowChrome) {
2777- surface.state = Mir.FullscreenState;
2778+ surface.requestState(Mir.FullscreenState);
2779 } else {
2780- surface.state = Mir.RestoredState;
2781+ surface.requestState(Mir.RestoredState);
2782 }
2783 }
2784 onStateChanged: {
2785 if (!active) return;
2786 if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
2787- surface.state = Mir.FullscreenState;
2788+ surface.requestState(Mir.FullscreenState);
2789 }
2790 }
2791 }
2792
2793=== removed file 'qml/Stage/TopLevelSurfaceRepeater.qml'
2794--- qml/Stage/TopLevelSurfaceRepeater.qml 2016-09-12 17:05:43 +0000
2795+++ qml/Stage/TopLevelSurfaceRepeater.qml 1970-01-01 00:00:00 +0000
2796@@ -1,67 +0,0 @@
2797-/*
2798- * Copyright (C) 2016 Canonical, Ltd.
2799- *
2800- * This program is free software; you can redistribute it and/or modify
2801- * it under the terms of the GNU General Public License as published by
2802- * the Free Software Foundation; version 3.
2803- *
2804- * This program is distributed in the hope that it will be useful,
2805- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2806- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2807- * GNU General Public License for more details.
2808- *
2809- * You should have received a copy of the GNU General Public License
2810- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2811- */
2812-
2813-import QtQuick 2.4
2814-
2815-Repeater {
2816- id: root
2817- // FIXME: This is a hack around us not knowing whether the Repeater has finished creating its
2818- // delegates on start up.
2819- // This is a problem when the stage gets a TopLevelSurfaceList already populated with several
2820- // rows.
2821- property bool startingUp: true
2822- onStartingUpChanged: {
2823- if (!startingUp) {
2824- // the top-most surface must be the focused one.
2825- var topmostDelegate = itemAt(0);
2826- if (topmostDelegate.focus) {
2827- // Delegate got focused while we were still starting up. Not good.
2828- // Force signal handler to run again
2829- topmostDelegate.onFocusChanged(true);
2830- } else {
2831- topmostDelegate.focus = true;
2832- }
2833- }
2834- }
2835-
2836- onItemAdded: {
2837- if (startingUp) {
2838- checkIfStillStartingUp();
2839- }
2840- }
2841-
2842- function checkIfStillStartingUp() {
2843- var i = 0;
2844- var missingDelegate = false;
2845- for (i = 0; i < model.count && !missingDelegate; ++i) {
2846- if (!itemAt(i)) {
2847- missingDelegate = true;
2848- }
2849- }
2850- if (!missingDelegate) {
2851- startingUp = false;
2852- }
2853- }
2854-
2855- function indexOf(delegateItem) {
2856- for (var i = 0; i < count; i++) {
2857- if (itemAt(i) === delegateItem) {
2858- return i;
2859- }
2860- }
2861- return -1;
2862- }
2863-}
2864
2865=== modified file 'qml/Stage/WindowDecoration.qml'
2866--- qml/Stage/WindowDecoration.qml 2016-09-22 10:33:39 +0000
2867+++ qml/Stage/WindowDecoration.qml 2016-11-23 15:15:21 +0000
2868@@ -22,7 +22,7 @@
2869 id: root
2870 clip: true
2871
2872- property Item target // appDelegate
2873+ property alias closeButtonVisible: buttons.closeButtonShown
2874 property alias title: titleLabel.text
2875 property alias maximizeButtonShown: buttons.maximizeButtonShown
2876 property bool active: false
2877@@ -40,7 +40,7 @@
2878 signal maximizeVerticallyClicked()
2879
2880 onDoubleClicked: {
2881- if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
2882+ if (mouse.button == Qt.LeftButton) {
2883 root.maximizeClicked();
2884 }
2885 }
2886@@ -77,9 +77,8 @@
2887 onCloseClicked: root.closeClicked();
2888 onMinimizeClicked: root.minimizeClicked();
2889 onMaximizeClicked: root.maximizeClicked();
2890- onMaximizeHorizontallyClicked: if (root.target.canBeMaximizedHorizontally) root.maximizeHorizontallyClicked();
2891- onMaximizeVerticallyClicked: if (root.target.canBeMaximizedVertically) root.maximizeVerticallyClicked();
2892- closeButtonShown: root.target.appId !== "unity8-dash"
2893+ onMaximizeHorizontallyClicked: root.maximizeHorizontallyClicked();
2894+ onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
2895 }
2896
2897 Label {
2898
2899=== modified file 'qml/Stage/WindowedFullscreenPolicy.qml'
2900--- qml/Stage/WindowedFullscreenPolicy.qml 2016-04-04 13:37:49 +0000
2901+++ qml/Stage/WindowedFullscreenPolicy.qml 2016-11-23 15:15:21 +0000
2902@@ -33,7 +33,7 @@
2903 _firstTimeSurface = false;
2904
2905 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
2906- surface.state = Mir.RestoredState;
2907+ surface.requestState(Mir.RestoredState);
2908 }
2909 }
2910 }
2911
2912=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
2913--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-09-12 17:05:43 +0000
2914+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-11-23 15:15:21 +0000
2915@@ -75,6 +75,31 @@
2916 {
2917 }
2918
2919+void ApplicationInfo::createPromptSurface()
2920+{
2921+ if (state() == ApplicationInfo::Stopped) { return; }
2922+
2923+ auto surfaceManager = SurfaceManager::instance();
2924+ if (!surfaceManager) {
2925+ WARNING_MSG("No SurfaceManager");
2926+ return;
2927+ }
2928+
2929+ QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
2930+ int i = rand() % screenshotIds.count();
2931+
2932+ QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
2933+ .arg(screenshotIds[i]);
2934+
2935+ auto surface = surfaceManager->createSurface(QString("prompt foo"),
2936+ Mir::NormalType,
2937+ Mir::RestoredState,
2938+ screenshotUrl);
2939+ surfaceManager->notifySurfaceCreated(surface);
2940+
2941+ m_promptSurfaceList->addSurface(surface);
2942+}
2943+
2944 void ApplicationInfo::createSurface()
2945 {
2946 if (state() == ApplicationInfo::Stopped) { return; }
2947@@ -90,14 +115,16 @@
2948 return;
2949 }
2950
2951+ bool wasFocused = focused();
2952+
2953 auto surface = surfaceManager->createSurface(surfaceName,
2954 Mir::NormalType,
2955- fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
2956+ fullscreen() ? Mir::FullscreenState : Mir::RestoredState,
2957 m_screenshotFileName);
2958
2959 surface->setShellChrome(m_shellChrome);
2960
2961- m_surfaceList->appendSurface(surface);
2962+ m_surfaceList->addSurface(surface);
2963
2964 ++m_liveSurfaceCount;
2965 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
2966@@ -125,8 +152,19 @@
2967 setState(Running);
2968 }
2969 });
2970+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [&](bool /*value*/) {
2971+ #if APPLICATION_DEBUG
2972+ qDebug().nospace() << "Application[" << appId() << "].focusedChanged(" << focused() << ")";
2973+ #endif
2974+ Q_EMIT focusedChanged(focused());
2975+ });
2976+
2977 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
2978
2979+ if (wasFocused != focused()) {
2980+ Q_EMIT focusedChanged(focused());
2981+ }
2982+
2983 if (m_state == Starting) {
2984 if (m_requestedState == RequestedRunning) {
2985 setState(Running);
2986@@ -134,6 +172,8 @@
2987 setState(Suspended);
2988 }
2989 }
2990+
2991+ surfaceManager->notifySurfaceCreated(surface);
2992 }
2993
2994 void ApplicationInfo::setIconId(const QString &iconId)
2995@@ -218,7 +258,7 @@
2996 {
2997 m_fullscreen = value;
2998 if (m_surfaceList->rowCount() > 0) {
2999- m_surfaceList->get(0)->setState(Mir::FullscreenState);
3000+ m_surfaceList->get(0)->requestState(Mir::FullscreenState);
3001 }
3002 }
3003
3004@@ -349,26 +389,6 @@
3005 return someSurfaceHasFocus;
3006 }
3007
3008-void ApplicationInfo::setFocused(bool value)
3009-{
3010- if (focused() == value) {
3011- return;
3012- }
3013-
3014- if (value) {
3015- if (m_surfaceList->count() > 0) {
3016- m_surfaceList->get(0)->requestFocus();
3017- }
3018- } else {
3019- for (int i = 0; i < m_surfaceList->count(); ++i) {
3020- MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
3021- if (surface->focused()) {
3022- surface->setFocused(false);
3023- }
3024- }
3025- }
3026-}
3027-
3028 void ApplicationInfo::onSurfaceCountChanged()
3029 {
3030 if (m_surfaceList->count() == 0 && m_state == Running) {
3031@@ -381,6 +401,7 @@
3032 if (m_surfaceList->count() == 0) {
3033 Q_EMIT focusRequested();
3034 } else {
3035- m_surfaceList->get(0)->requestFocus();
3036+ auto surface = static_cast<MirSurface*>(m_surfaceList->get(0));
3037+ surface->requestFocus();
3038 }
3039 }
3040
3041=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
3042--- tests/mocks/Unity/Application/ApplicationInfo.h 2016-07-25 14:57:11 +0000
3043+++ tests/mocks/Unity/Application/ApplicationInfo.h 2016-11-23 15:15:21 +0000
3044@@ -51,6 +51,8 @@
3045 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
3046 ~ApplicationInfo();
3047
3048+ Q_INVOKABLE void createPromptSurface();
3049+
3050 RequestedState requestedState() const override;
3051 void setRequestedState(RequestedState) override;
3052
3053@@ -108,8 +110,6 @@
3054 MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
3055 int surfaceCount() const override { return m_surfaceList->count(); }
3056
3057- void setFocused(bool value);
3058-
3059 //////
3060 // internal mock stuff
3061 void close();
3062
3063=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
3064--- tests/mocks/Unity/Application/ApplicationManager.cpp 2016-08-23 12:04:11 +0000
3065+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2016-11-23 15:15:21 +0000
3066@@ -34,30 +34,24 @@
3067
3068 #if APPLICATIONMANAGER_DEBUG
3069 #define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
3070+#define XDEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << params
3071 #else
3072 #define DEBUG_MSG(params) ((void)0)
3073+#define XDEBUG_MSG(params) ((void)0)
3074 #endif
3075
3076 namespace unityapi = unity::shell::application;
3077
3078+
3079 ApplicationManager::ApplicationManager(QObject *parent)
3080 : ApplicationManagerInterface(parent)
3081 {
3082 DEBUG_MSG("");
3083+
3084+ ApplicationManagerNotifier::instance()->setApplicationManager(this);
3085+
3086 buildListOfAvailableApplications();
3087
3088- // polling to find out when the toplevel window has been created as there's
3089- // no signal telling us that
3090- connect(&m_windowCreatedTimer, &QTimer::timeout,
3091- this, &ApplicationManager::onWindowCreatedTimerTimeout);
3092- m_windowCreatedTimer.setSingleShot(false);
3093- m_windowCreatedTimer.start(200);
3094-
3095- Q_ASSERT(MirFocusController::instance());
3096- connect(MirFocusController::instance(), &MirFocusController::focusedSurfaceChanged,
3097- this, &ApplicationManager::updateFocusedApplication, Qt::QueuedConnection);
3098-
3099-
3100 // Emit signal to notify Upstart that Mir is ready to receive client connections
3101 // see http://upstart.ubuntu.com/cookbook/#expect-stop
3102 // We do this because some autopilot tests actually use this mock Unity.Application module,
3103@@ -69,19 +63,7 @@
3104
3105 ApplicationManager::~ApplicationManager()
3106 {
3107-}
3108-
3109-void ApplicationManager::onWindowCreatedTimerTimeout()
3110-{
3111- if (QGuiApplication::topLevelWindows().count() > 0) {
3112- m_windowCreatedTimer.stop();
3113- onWindowCreated();
3114- }
3115-}
3116-
3117-void ApplicationManager::onWindowCreated()
3118-{
3119- startApplication("unity8-dash");
3120+ ApplicationManagerNotifier::instance()->setApplicationManager(nullptr);
3121 }
3122
3123 int ApplicationManager::rowCount(const QModelIndex& parent) const {
3124@@ -132,6 +114,17 @@
3125 return nullptr;
3126 }
3127
3128+unityapi::ApplicationInfoInterface *ApplicationManager::findApplicationWithSurface(unityapi::MirSurfaceInterface* surface)
3129+{
3130+ for (ApplicationInfo *app : m_runningApplications) {
3131+ auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
3132+ if (surfaceList->contains(static_cast<MirSurface*>(surface))) {
3133+ return app;
3134+ }
3135+ }
3136+ return nullptr;
3137+}
3138+
3139 QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
3140 {
3141 for (int i = 0; i < m_runningApplications.size(); ++i) {
3142@@ -158,6 +151,11 @@
3143 QModelIndex appIndex = findIndex(application);
3144 if (!appIndex.isValid()) return;
3145 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
3146+ XDEBUG_MSG("focusedApplicationId = " << focusedApplicationId());
3147+ Q_EMIT focusedApplicationIdChanged();
3148+ if (application->focused()) {
3149+ raiseApp(application->appId());
3150+ }
3151 });
3152 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
3153 QModelIndex appIndex = findIndex(application);
3154@@ -181,15 +179,40 @@
3155
3156 void ApplicationManager::remove(ApplicationInfo *application) {
3157 int i = m_runningApplications.indexOf(application);
3158+ application->disconnect(this);
3159 if (i != -1) {
3160 DEBUG_MSG(application->appId());
3161+ Q_ASSERT(!m_modelBusy);
3162+ m_modelBusy = true;
3163 beginRemoveRows(QModelIndex(), i, i);
3164 m_runningApplications.removeAt(i);
3165 endRemoveRows();
3166+ m_modelBusy = false;
3167 Q_EMIT countChanged();
3168 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
3169- }
3170- application->disconnect(this);
3171+ DEBUG_MSG(application->appId() << " after: " << qPrintable(toString()));
3172+ }
3173+}
3174+
3175+void ApplicationManager::raiseApp(const QString &appId)
3176+{
3177+
3178+ int index = -1;
3179+ for (int i = 0; i < m_runningApplications.count() && index == -1; ++i) {
3180+ if (m_runningApplications[i]->appId() == appId) {
3181+ index = i;
3182+ }
3183+ }
3184+
3185+ if (index >= 0) {
3186+ if (m_modelBusy) {
3187+ DEBUG_MSG(appId << " - model busy. Try again later.");
3188+ QMetaObject::invokeMethod(this, "raiseApp", Qt::QueuedConnection, Q_ARG(QString, appId));
3189+ } else {
3190+ DEBUG_MSG(appId);
3191+ move(index, 0);
3192+ }
3193+ }
3194 }
3195
3196 void ApplicationManager::move(int from, int to) {
3197@@ -197,12 +220,15 @@
3198
3199 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
3200 QModelIndex parent;
3201+ Q_ASSERT(!m_modelBusy);
3202+ m_modelBusy = true;
3203 /* When moving an item down, the destination index needs to be incremented
3204 * by one, as explained in the documentation:
3205 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
3206 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
3207 m_runningApplications.move(from, to);
3208 endMoveRows();
3209+ m_modelBusy = false;
3210 }
3211 }
3212
3213@@ -473,40 +499,6 @@
3214 return m_runningApplications.isEmpty();
3215 }
3216
3217-void ApplicationManager::updateFocusedApplication()
3218-{
3219- ApplicationInfo *focusedApplication = nullptr;
3220- ApplicationInfo *previouslyFocusedApplication = nullptr;
3221-
3222- auto controller = MirFocusController::instance();
3223- if (!controller) {
3224- return;
3225- }
3226-
3227- MirSurface *surface = static_cast<MirSurface*>(controller->focusedSurface());
3228- if (surface) {
3229- focusedApplication = findApplication(surface);
3230- }
3231-
3232- surface = static_cast<MirSurface*>(controller->previouslyFocusedSurface());
3233- if (surface) {
3234- previouslyFocusedApplication = findApplication(surface);
3235- }
3236-
3237- if (focusedApplication != previouslyFocusedApplication) {
3238- if (focusedApplication) {
3239- DEBUG_MSG("focused " << focusedApplication->appId());
3240- Q_EMIT focusedApplication->focusedChanged(true);
3241- this->move(this->m_runningApplications.indexOf(focusedApplication), 0);
3242- }
3243- if (previouslyFocusedApplication) {
3244- DEBUG_MSG("unfocused " << previouslyFocusedApplication->appId());
3245- Q_EMIT previouslyFocusedApplication->focusedChanged(false);
3246- }
3247- Q_EMIT focusedApplicationIdChanged();
3248- }
3249-}
3250-
3251 ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
3252 {
3253 for (ApplicationInfo *app : m_runningApplications) {
3254@@ -517,3 +509,42 @@
3255 }
3256 return nullptr;
3257 }
3258+
3259+QString ApplicationManager::toString()
3260+{
3261+ QString str;
3262+ for (int i = 0; i < m_runningApplications.count(); ++i) {
3263+ auto *application = m_runningApplications.at(i);
3264+
3265+ QString itemStr = QString("(index=%1,appId=%2)")
3266+ .arg(i)
3267+ .arg(application->appId());
3268+
3269+ if (i > 0) {
3270+ str.append(",");
3271+ }
3272+ str.append(itemStr);
3273+ }
3274+ return str;
3275+}
3276+
3277+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3278+// ApplicationManagerNotifier
3279+
3280+ApplicationManagerNotifier *ApplicationManagerNotifier::m_instance = nullptr;
3281+
3282+ApplicationManagerNotifier *ApplicationManagerNotifier::instance()
3283+{
3284+ if (!m_instance) {
3285+ m_instance = new ApplicationManagerNotifier;
3286+ }
3287+ return m_instance;
3288+}
3289+
3290+void ApplicationManagerNotifier::setApplicationManager(ApplicationManager *appMan)
3291+{
3292+ if (appMan != m_applicationManager) {
3293+ m_applicationManager = appMan;
3294+ Q_EMIT applicationManagerChanged(m_applicationManager);
3295+ }
3296+}
3297
3298=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
3299--- tests/mocks/Unity/Application/ApplicationManager.h 2016-04-27 15:01:10 +0000
3300+++ tests/mocks/Unity/Application/ApplicationManager.h 2016-11-23 15:15:21 +0000
3301@@ -50,8 +50,11 @@
3302 // QAbstractItemModel methods.
3303 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
3304 QVariant data(const QModelIndex& index, int role) const override;
3305+
3306+ // ApplicationManagerInterface methods
3307 Q_INVOKABLE ApplicationInfo *get(int index) const override;
3308 Q_INVOKABLE ApplicationInfo *findApplication(const QString &appId) const override;
3309+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override;
3310
3311 Q_INVOKABLE void move(int from, int to);
3312
3313@@ -76,18 +79,42 @@
3314 void availableApplicationsChanged(QStringList list);
3315
3316 private Q_SLOTS:
3317- void onWindowCreatedTimerTimeout();
3318- void updateFocusedApplication();
3319+ void raiseApp(const QString &appId);
3320
3321 private:
3322 bool add(ApplicationInfo *application);
3323 void remove(ApplicationInfo* application);
3324 void buildListOfAvailableApplications();
3325- void onWindowCreated();
3326+ QString toString();
3327 ApplicationInfo *findApplication(MirSurface* surface);
3328 QList<ApplicationInfo*> m_runningApplications;
3329 QList<ApplicationInfo*> m_availableApplications;
3330- QTimer m_windowCreatedTimer;
3331+ bool m_modelBusy{false};
3332+};
3333+
3334+/*
3335+ Lifecycle of the ApplicationManager instance belongs to the QML plugin.
3336+ So this guy here is used to notify other parts of the system when the plugin creates and destroys
3337+ the ApplicationManager.
3338+
3339+ Unlike ApplicationManager, we create ApplicationManagerNotifier whenever we want.
3340+ */
3341+class ApplicationManagerNotifier : public QObject {
3342+ Q_OBJECT
3343+public:
3344+ static ApplicationManagerNotifier *instance();
3345+
3346+ ApplicationManager *applicationManager() { return m_applicationManager; }
3347+
3348+Q_SIGNALS:
3349+ void applicationManagerChanged(ApplicationManager *applicationManager);
3350+
3351+private:
3352+ void setApplicationManager(ApplicationManager *);
3353+ static ApplicationManagerNotifier *m_instance;
3354+ ApplicationManager *m_applicationManager{nullptr};
3355+
3356+friend class ApplicationManager;
3357 };
3358
3359 Q_DECLARE_METATYPE(ApplicationManager*)
3360
3361=== modified file 'tests/mocks/Unity/Application/CMakeLists.txt'
3362--- tests/mocks/Unity/Application/CMakeLists.txt 2016-06-30 13:51:32 +0000
3363+++ tests/mocks/Unity/Application/CMakeLists.txt 2016-11-23 15:15:21 +0000
3364@@ -14,7 +14,7 @@
3365 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
3366 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h
3367 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
3368- ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirFocusControllerInterface.h
3369+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
3370 resources/surfaces.qrc
3371 )
3372
3373
3374=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
3375--- tests/mocks/Unity/Application/MirSurface.cpp 2016-09-26 12:25:19 +0000
3376+++ tests/mocks/Unity/Application/MirSurface.cpp 2016-11-23 15:15:21 +0000
3377@@ -19,14 +19,18 @@
3378 #include <QDebug>
3379 #include <QQmlEngine>
3380
3381+// local
3382+#include "SurfaceManager.h"
3383+
3384 #define MIRSURFACE_DEBUG 0
3385
3386 #if MIRSURFACE_DEBUG
3387 #define DEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << __func__ << " " << params
3388+#define XDEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << params
3389
3390 const char *stateToStr(Mir::State state)
3391 {
3392- switch(state) {
3393+ switch (state) {
3394 case Mir::UnknownState:
3395 return "unknown";
3396 case Mir::RestoredState:
3397@@ -36,26 +40,37 @@
3398 case Mir::MaximizedState:
3399 return "maximized";
3400 case Mir::VertMaximizedState:
3401- return "vert-maximized";
3402+ return "vertMaximized";
3403 case Mir::FullscreenState:
3404 return "fullscreen";
3405 case Mir::HorizMaximizedState:
3406- return "horiz-maximized";
3407+ return "horizMaximized";
3408+ case Mir::MaximizedLeftState:
3409+ return "maximizedLeft";
3410+ case Mir::MaximizedRightState:
3411+ return "maximizedRight";
3412+ case Mir::MaximizedTopLeftState:
3413+ return "maximizedTopLeft";
3414+ case Mir::MaximizedTopRightState:
3415+ return "maximizedTopRight";
3416+ case Mir::MaximizedBottomLeftState:
3417+ return "maximizedBottomLeft";
3418+ case Mir::MaximizedBottomRightState:
3419+ return "maximizedBottomRight";
3420 case Mir::HiddenState:
3421 return "hidden";
3422 default:
3423 return "???";
3424- };
3425+ }
3426 }
3427
3428 #else
3429 #define DEBUG_MSG(params) ((void)0)
3430+#define XDEBUG_MSG(params) ((void)0)
3431 #endif
3432
3433 using namespace unity::shell::application;
3434
3435-MirFocusController *MirFocusController::m_instance = nullptr;
3436-
3437 MirSurface::MirSurface(const QString& name,
3438 Mir::Type type,
3439 Mir::State state,
3440@@ -69,7 +84,7 @@
3441 , m_screenshotUrl(screenshot)
3442 , m_qmlFilePath(qmlFilePath)
3443 , m_live(true)
3444- , m_visible(true)
3445+ , m_focused(false)
3446 , m_activeFocus(false)
3447 , m_width(-1)
3448 , m_height(-1)
3449@@ -95,11 +110,8 @@
3450 {
3451 DEBUG_MSG("");
3452
3453- // controller instance might have been already destroyed by QQmlEngine destructor
3454- auto controller = MirFocusController::instance();
3455- if (controller && controller->focusedSurface() == this) {
3456- controller->clear();
3457- }
3458+ // Early warning, while MirSurface methods can still be accessed.
3459+ Q_EMIT destroyed(this);
3460 }
3461
3462 QString MirSurface::name() const
3463@@ -122,14 +134,31 @@
3464 return m_state;
3465 }
3466
3467+void MirSurface::requestState(Mir::State state)
3468+{
3469+ if (state == m_state) {
3470+ return;
3471+ }
3472+ DEBUG_MSG(stateToStr(state));
3473+ Q_EMIT stateRequested(state);
3474+}
3475+
3476 void MirSurface::setState(Mir::State state)
3477 {
3478- if (state == m_state)
3479+ if (state == m_state) {
3480 return;
3481-
3482+ }
3483 DEBUG_MSG(stateToStr(state));
3484+
3485+ bool oldVisible = visible();
3486+
3487 m_state = state;
3488 Q_EMIT stateChanged(state);
3489+
3490+ if (visible() != oldVisible) {
3491+ XDEBUG_MSG("visibleChanged("<<visible()<<")");
3492+ Q_EMIT visibleChanged(visible());
3493+ }
3494 }
3495
3496 bool MirSurface::live() const
3497@@ -139,7 +168,7 @@
3498
3499 bool MirSurface::visible() const
3500 {
3501- return m_visible;
3502+ return m_state != Mir::MinimizedState && m_state != Mir::HiddenState;
3503 }
3504
3505 void MirSurface::setLive(bool live)
3506@@ -231,31 +260,30 @@
3507 if (!m_live && m_views.count() == 0) {
3508 deleteLater();
3509 }
3510- updateVisibility();
3511+ updateExposure();
3512 }
3513
3514-void MirSurface::setViewVisibility(qintptr viewId, bool visible)
3515+void MirSurface::setViewExposure(qintptr viewId, bool visible)
3516 {
3517 if (!m_views.contains(viewId)) return;
3518
3519 m_views[viewId].visible = visible;
3520- updateVisibility();
3521+ updateExposure();
3522 }
3523
3524-void MirSurface::updateVisibility()
3525+void MirSurface::updateExposure()
3526 {
3527- bool newVisible = false;
3528+ bool newExposure = false;
3529 QHashIterator<qintptr, View> i(m_views);
3530 while (i.hasNext()) {
3531 i.next();
3532- newVisible |= i.value().visible;
3533+ newExposure |= i.value().visible;
3534 }
3535
3536- if (newVisible != visible()) {
3537-// qDebug().nospace() << "MirSurface[" << name() << "]::updateVisibility(" << newVisible << ")";
3538-
3539- m_visible = newVisible;
3540- Q_EMIT visibleChanged(m_visible);
3541+ if (newExposure != m_exposed) {
3542+ m_exposed = newExposure;
3543+ DEBUG_MSG(m_exposed);
3544+ Q_EMIT exposedChanged(m_exposed);
3545 updateInputBoundsAfterResize();
3546 }
3547 }
3548@@ -273,6 +301,10 @@
3549 m_activeFocus = value;
3550
3551 Q_EMIT activeFocusChanged(value);
3552+
3553+ if (m_activeFocus && !m_focused) {
3554+ requestFocus();
3555+ }
3556 }
3557
3558 int MirSurface::width() const
3559@@ -332,6 +364,7 @@
3560 }
3561
3562 if (changed) {
3563+ XDEBUG_MSG("sizeChanged(width="<<width<<", height="<<height<<")");
3564 Q_EMIT sizeChanged(QSize(width, height));
3565 }
3566
3567@@ -409,11 +442,6 @@
3568 }
3569 }
3570
3571-void MirSurface::raise()
3572-{
3573- Q_EMIT raiseRequested();
3574-}
3575-
3576 void MirSurface::close()
3577 {
3578 DEBUG_MSG("");
3579@@ -423,6 +451,12 @@
3580 }
3581 }
3582
3583+void MirSurface::activate()
3584+{
3585+ DEBUG_MSG("");
3586+ SurfaceManager::instance()->activate(this);
3587+}
3588+
3589 void MirSurface::requestFocus()
3590 {
3591 DEBUG_MSG("");
3592@@ -431,27 +465,18 @@
3593
3594 void MirSurface::setFocused(bool value)
3595 {
3596- DEBUG_MSG(value);
3597-
3598- auto controller = MirFocusController::instance();
3599- // controller instance might have been already destroyed by QQmlEngine destructor
3600- if (!controller) {
3601+ if (m_focused == value)
3602 return;
3603- }
3604-
3605- if (value) {
3606- controller->setFocusedSurface(this);
3607- } else if (controller->focusedSurface() == this) {
3608- controller->setFocusedSurface(nullptr);
3609- }
3610+
3611+ DEBUG_MSG("(" << value << ")");
3612+
3613+ m_focused = value;
3614+ Q_EMIT focusedChanged(value);
3615 }
3616
3617 bool MirSurface::focused() const
3618 {
3619- auto controller = MirFocusController::instance();
3620-
3621- // controller instance might have been already destroyed by QQmlEngine destructor
3622- return controller ? controller->focusedSurface() == this : false;
3623+ return m_focused;
3624 }
3625
3626 QRect MirSurface::inputBounds() const
3627@@ -463,58 +488,20 @@
3628 {
3629 if (boundsRect != m_inputBounds) {
3630 m_inputBounds = boundsRect;
3631+ DEBUG_MSG("(" << m_inputBounds << ")");
3632 Q_EMIT inputBoundsChanged(m_inputBounds);
3633 }
3634 }
3635-#if MIRSURFACE_DEBUG
3636-#undef DEBUG_MSG
3637-#define DEBUG_MSG(params) qDebug().nospace() << "MirFocusController::" << __func__ << " " << params
3638-#endif
3639-
3640-void MirFocusController::setFocusedSurface(MirSurfaceInterface *surface)
3641-{
3642- if (m_focusedSurface == surface) {
3643- return;
3644- }
3645- DEBUG_MSG("MirSurface[" << (void*)surface << "," << (surface?surface->name():"") << "]");
3646-
3647- m_previouslyFocusedSurface = m_focusedSurface;
3648- m_focusedSurface = surface;
3649-
3650- if (m_previouslyFocusedSurface != m_focusedSurface) {
3651- Q_EMIT focusedSurfaceChanged();
3652- }
3653-
3654- if (m_previouslyFocusedSurface) {
3655- Q_EMIT m_previouslyFocusedSurface->focusedChanged(false);
3656- }
3657-
3658- if (m_focusedSurface) {
3659- Q_EMIT m_focusedSurface->focusedChanged(true);
3660- m_focusedSurface->raise();
3661- }
3662-}
3663-
3664-MirFocusController* MirFocusController::instance()
3665-{
3666- return m_instance;
3667-}
3668-
3669-MirFocusController::MirFocusController()
3670-{
3671- DEBUG_MSG("");
3672- Q_ASSERT(m_instance == nullptr);
3673- m_instance = this;
3674-}
3675-
3676-MirFocusController::~MirFocusController()
3677-{
3678- Q_ASSERT(m_instance == this);
3679- m_instance = nullptr;
3680-}
3681-
3682-void MirFocusController::clear()
3683-{
3684- m_focusedSurface = m_previouslyFocusedSurface = nullptr;
3685- Q_EMIT focusedSurfaceChanged();
3686+
3687+void MirSurface::setRequestedPosition(const QPoint &value)
3688+{
3689+ if (value != m_requestedPosition) {
3690+ m_requestedPosition = value;
3691+ Q_EMIT requestedPositionChanged(value);
3692+
3693+ // fake-miral: always comply
3694+ m_position = m_requestedPosition;
3695+ XDEBUG_MSG("positionChanged("<<m_position<<")");
3696+ Q_EMIT positionChanged(m_position);
3697+ }
3698 }
3699
3700=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
3701--- tests/mocks/Unity/Application/MirSurface.h 2016-09-07 08:50:19 +0000
3702+++ tests/mocks/Unity/Application/MirSurface.h 2016-11-23 15:15:21 +0000
3703@@ -23,31 +23,12 @@
3704 #include <QHash>
3705
3706 // unity-api
3707-#include <unity/shell/application/MirFocusControllerInterface.h>
3708 #include <unity/shell/application/MirSurfaceInterface.h>
3709
3710 #include "MirSurfaceListModel.h"
3711
3712 class MirSurface;
3713
3714-class MirFocusController : public unity::shell::application::MirFocusControllerInterface
3715-{
3716- Q_OBJECT
3717-public:
3718- MirFocusController();
3719- virtual ~MirFocusController();
3720- static MirFocusController* instance();
3721-
3722- void setFocusedSurface(unity::shell::application::MirSurfaceInterface *surface) override;
3723- unity::shell::application::MirSurfaceInterface* focusedSurface() const override { return m_focusedSurface; }
3724- unity::shell::application::MirSurfaceInterface* previouslyFocusedSurface() { return m_previouslyFocusedSurface; }
3725- void clear();
3726-private:
3727- static MirFocusController *m_instance;
3728- unity::shell::application::MirSurfaceInterface* m_previouslyFocusedSurface{nullptr};
3729- unity::shell::application::MirSurfaceInterface* m_focusedSurface{nullptr};
3730-};
3731-
3732 class MirSurface : public unity::shell::application::MirSurfaceInterface
3733 {
3734 Q_OBJECT
3735@@ -58,6 +39,7 @@
3736 Q_PROPERTY(int height READ height NOTIFY heightChanged)
3737 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
3738 Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
3739+ Q_PROPERTY(bool exposed READ exposed NOTIFY exposedChanged)
3740
3741 public:
3742 MirSurface(const QString& name,
3743@@ -76,13 +58,14 @@
3744
3745 QString persistentId() const override;
3746
3747+ QPoint position() const override { return m_position; }
3748+
3749 QSize size() const override { return QSize(width(),height()); }
3750 void resize(int width, int height) override;
3751 void resize(const QSize &size) override { resize(size.width(), size.height()); }
3752
3753
3754 Mir::State state() const override;
3755- Q_INVOKABLE void setState(Mir::State) override;
3756
3757 bool live() const override;
3758
3759@@ -108,15 +91,16 @@
3760
3761 bool confinesMousePointer() const override { return false; }
3762
3763- Q_INVOKABLE void requestFocus() override;
3764+ QPoint requestedPosition() const override { return m_requestedPosition; }
3765+ void setRequestedPosition(const QPoint &) override;
3766
3767 Q_INVOKABLE void close() override;
3768-
3769- Q_INVOKABLE void raise() override;
3770+ Q_INVOKABLE void activate() override;
3771
3772 ////
3773 // API for tests
3774
3775+ Q_INVOKABLE void requestFocus();
3776 Q_INVOKABLE void setLive(bool live);
3777 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
3778
3779@@ -126,6 +110,8 @@
3780 bool isSlowToResize() const;
3781 void setSlowToResize(bool value);
3782
3783+ bool exposed() const { return m_exposed; }
3784+
3785 Q_INVOKABLE void setMinimumWidth(int);
3786 Q_INVOKABLE void setMaximumWidth(int);
3787 Q_INVOKABLE void setMinimumHeight(int);
3788@@ -148,24 +134,32 @@
3789
3790 void registerView(qintptr viewId);
3791 void unregisterView(qintptr viewId);
3792- void setViewVisibility(qintptr viewId, bool visible);
3793+ void setViewExposure(qintptr viewId, bool visible);
3794 int viewCount() const { return m_views.count(); }
3795
3796 void setFocused(bool value);
3797
3798+ void setState(Mir::State state);
3799+
3800+public Q_SLOTS:
3801+ ////
3802+ // unity.shell.application.MirSurface
3803+ void requestState(Mir::State) override;
3804+
3805 Q_SIGNALS:
3806 ////
3807 // API for tests
3808 void widthChanged();
3809 void heightChanged();
3810 void slowToResizeChanged();
3811+ void exposedChanged(bool exposed);
3812
3813 ////
3814 // internal mock stuff
3815 void screenshotUrlChanged(QUrl);
3816 void activeFocusChanged(bool);
3817- void raiseRequested();
3818 void closeRequested();
3819+ void stateRequested(Mir::State);
3820
3821 protected:
3822 virtual void updateInputBoundsAfterResize();
3823@@ -175,7 +169,7 @@
3824
3825 private:
3826 void doResize(int width, int height);
3827- void updateVisibility();
3828+ void updateExposure();
3829
3830 const QString m_name;
3831 const Mir::Type m_type;
3832@@ -185,7 +179,7 @@
3833 QUrl m_screenshotUrl;
3834 QUrl m_qmlFilePath;
3835 bool m_live;
3836- bool m_visible;
3837+ bool m_focused;
3838 bool m_activeFocus;
3839 int m_width;
3840 int m_height;
3841@@ -210,10 +204,14 @@
3842 bool visible;
3843 };
3844 QHash<qintptr, View> m_views;
3845+ bool m_exposed{false};
3846
3847 QTimer m_zombieTimer;
3848
3849 QRect m_inputBounds;
3850+
3851+ QPoint m_position;
3852+ QPoint m_requestedPosition;
3853 };
3854
3855 #endif // MOCK_MIR_SURFACE_H
3856
3857=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
3858--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-08-23 11:11:32 +0000
3859+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-11-23 15:15:21 +0000
3860@@ -59,7 +59,7 @@
3861 Qt::ExtraButton12 | Qt::ExtraButton13);
3862
3863 connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
3864- connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility);
3865+ connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceExposure);
3866
3867 connect(this, &MirSurfaceItem::consumesInputChanged, this, [this]() {
3868 updateMirSurfaceActiveFocus(hasActiveFocus());
3869@@ -278,7 +278,7 @@
3870 m_qmlSurface->registerView((qintptr)this);
3871
3872 updateSurfaceSize();
3873- updateMirSurfaceVisibility();
3874+ updateMirSurfaceExposure();
3875
3876 if (m_orientationAngle) {
3877 m_qmlSurface->setOrientationAngle(*m_orientationAngle);
3878@@ -335,11 +335,11 @@
3879 }
3880 }
3881
3882-void MirSurfaceItem::updateMirSurfaceVisibility()
3883+void MirSurfaceItem::updateMirSurfaceExposure()
3884 {
3885 if (!m_qmlSurface) return;
3886
3887- m_qmlSurface->setViewVisibility((qintptr)this, isVisible());
3888+ m_qmlSurface->setViewExposure((qintptr)this, isVisible());
3889 }
3890
3891 void MirSurfaceItem::setConsumesInput(bool value)
3892
3893=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
3894--- tests/mocks/Unity/Application/MirSurfaceItem.h 2016-06-22 13:53:00 +0000
3895+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2016-11-23 15:15:21 +0000
3896@@ -50,7 +50,6 @@
3897 Mir::ShellChrome shellChrome() const override;
3898
3899 Mir::State surfaceState() const override;
3900- void setSurfaceState(Mir::State) override {}
3901
3902 Mir::OrientationAngle orientationAngle() const override;
3903 void setOrientationAngle(Mir::OrientationAngle angle) override;
3904@@ -102,7 +101,7 @@
3905 private Q_SLOTS:
3906 void onComponentStatusChanged(QQmlComponent::Status status);
3907 void updateScreenshot(QUrl screenshot);
3908- void updateMirSurfaceVisibility();
3909+ void updateMirSurfaceExposure();
3910 void updateMirSurfaceActiveFocus(bool focused);
3911
3912 private:
3913
3914=== modified file 'tests/mocks/Unity/Application/MirSurfaceListModel.cpp'
3915--- tests/mocks/Unity/Application/MirSurfaceListModel.cpp 2016-09-23 13:38:46 +0000
3916+++ tests/mocks/Unity/Application/MirSurfaceListModel.cpp 2016-11-23 15:15:21 +0000
3917@@ -21,8 +21,9 @@
3918
3919 #define MIRSURFACELISTMODEL_DEBUG 0
3920
3921-#ifdef MIRSURFACELISTMODEL_DEBUG
3922-#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << " " << params
3923+#if MIRSURFACELISTMODEL_DEBUG
3924+#include <QDebug>
3925+#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << params
3926 #else
3927 #define DEBUG_MSG(params) ((void)0)
3928 #endif
3929@@ -54,26 +55,16 @@
3930
3931 void MirSurfaceListModel::raise(MirSurface *surface)
3932 {
3933+ DEBUG_MSG("(" << surface << ")");
3934 int i = m_surfaceList.indexOf(surface);
3935 if (i != -1) {
3936 moveSurface(i, 0);
3937 }
3938 }
3939
3940-void MirSurfaceListModel::appendSurface(MirSurface *surface)
3941-{
3942- beginInsertRows(QModelIndex(), m_surfaceList.size(), m_surfaceList.size());
3943- m_surfaceList.append(surface);
3944- connectSurface(surface);
3945- endInsertRows();
3946- Q_EMIT countChanged(m_surfaceList.count());
3947- if (m_surfaceList.count() == 1) {
3948- Q_EMIT firstChanged();
3949- }
3950-}
3951-
3952-void MirSurfaceListModel::prependSurface(MirSurface *surface)
3953-{
3954+void MirSurfaceListModel::addSurface(MirSurface *surface)
3955+{
3956+ DEBUG_MSG("(" << surface << ")");
3957 beginInsertRows(QModelIndex(), 0, 0);
3958 m_surfaceList.prepend(surface);
3959 connectSurface(surface);
3960@@ -85,6 +76,11 @@
3961 void MirSurfaceListModel::connectSurface(MirSurface *surface)
3962 {
3963 connect(surface, &QObject::destroyed, this, [this, surface](){ this->removeSurface(surface); });
3964+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool surfaceFocused){
3965+ if (surfaceFocused) {
3966+ raise(surface);
3967+ }
3968+ });
3969 }
3970
3971 void MirSurfaceListModel::removeSurface(MirSurface *surface)
3972@@ -138,21 +134,3 @@
3973 return nullptr;
3974 }
3975 }
3976-
3977-MirSurfaceInterface *MirSurfaceListModel::createSurface()
3978-{
3979- QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
3980- int i = rand() % screenshotIds.count();
3981-
3982- QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
3983- .arg(screenshotIds[i]);
3984-
3985- auto surface = new MirSurface(QString("prompt foo"),
3986- Mir::NormalType,
3987- Mir::RestoredState,
3988- screenshotUrl);
3989-
3990- prependSurface(surface);
3991-
3992- return surface;
3993-}
3994
3995=== modified file 'tests/mocks/Unity/Application/MirSurfaceListModel.h'
3996--- tests/mocks/Unity/Application/MirSurfaceListModel.h 2016-06-02 12:02:35 +0000
3997+++ tests/mocks/Unity/Application/MirSurfaceListModel.h 2016-11-23 15:15:21 +0000
3998@@ -38,18 +38,13 @@
3999 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
4000 QVariant data(const QModelIndex& index, int role) const override;
4001
4002- void appendSurface(MirSurface *surface);
4003+ void addSurface(MirSurface *surface);
4004 void removeSurface(MirSurface *surface);
4005
4006 bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
4007
4008- ////
4009- // API for tests
4010-
4011- Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
4012-
4013 private:
4014- void prependSurface(MirSurface *surface);
4015+ void appendSurface(MirSurface *surface);
4016 void raise(MirSurface *surface);
4017 void moveSurface(int from, int to);
4018 void connectSurface(MirSurface *surface);
4019
4020=== modified file 'tests/mocks/Unity/Application/SurfaceManager.cpp'
4021--- tests/mocks/Unity/Application/SurfaceManager.cpp 2016-04-27 15:01:10 +0000
4022+++ tests/mocks/Unity/Application/SurfaceManager.cpp 2016-11-23 15:15:21 +0000
4023@@ -21,33 +21,41 @@
4024
4025 #include <paths.h>
4026
4027-SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
4028+#define SURFACEMANAGER_DEBUG 0
4029+
4030+#if SURFACEMANAGER_DEBUG
4031+#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
4032+#else
4033+#define DEBUG_MSG(params) ((void)0)
4034+#endif
4035+
4036+namespace unityapi = unity::shell::application;
4037+
4038+SurfaceManager *SurfaceManager::m_instance = nullptr;
4039
4040 SurfaceManager *SurfaceManager::instance()
4041 {
4042- return the_surface_manager;
4043+ return m_instance;
4044 }
4045
4046-SurfaceManager::SurfaceManager(QObject *parent) :
4047- QObject(parent)
4048- , m_virtualKeyboard(nullptr)
4049+SurfaceManager::SurfaceManager(QObject *)
4050 {
4051- Q_ASSERT(the_surface_manager == nullptr);
4052- the_surface_manager = this;
4053+ DEBUG_MSG("");
4054
4055- m_virtualKeyboard = new VirtualKeyboard;
4056- connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
4057- MirSurface* surface = qobject_cast<MirSurface*>(obj);
4058- m_virtualKeyboard = nullptr;
4059- Q_EMIT inputMethodSurfaceChanged();
4060- Q_EMIT surfaceDestroyed(surface);
4061- });
4062+ Q_ASSERT(m_instance == nullptr);
4063+ m_instance = this;
4064 }
4065
4066 SurfaceManager::~SurfaceManager()
4067 {
4068- Q_ASSERT(the_surface_manager == this);
4069- the_surface_manager = nullptr;
4070+ DEBUG_MSG("");
4071+
4072+ if (m_virtualKeyboard) {
4073+ m_virtualKeyboard->setLive(false);
4074+ }
4075+
4076+ Q_ASSERT(m_instance == this);
4077+ m_instance = nullptr;
4078 }
4079
4080 MirSurface *SurfaceManager::createSurface(const QString& name,
4081@@ -56,10 +64,13 @@
4082 const QUrl& screenshot)
4083 {
4084 MirSurface* surface = new MirSurface(name, type, state, screenshot);
4085- connect(surface, &QObject::destroyed, this, [this](QObject *obj) {
4086- MirSurface* surface = qobject_cast<MirSurface*>(obj);
4087- Q_EMIT surfaceDestroyed(surface);
4088- });
4089+ registerSurface(surface);
4090+ return surface;
4091+}
4092+
4093+void SurfaceManager::registerSurface(MirSurface *surface)
4094+{
4095+ m_surfaces.prepend(surface);
4096
4097 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
4098 surface->setMaximumWidth(m_newSurfaceMaximumWidth);
4099@@ -68,13 +79,18 @@
4100 surface->setWidthIncrement(m_newSurfaceWidthIncrement);
4101 surface->setHeightIncrement(m_newSurfaceHeightIncrement);
4102
4103+ connect(surface, &MirSurface::stateRequested, this, [=](Mir::State state) {
4104+ this->onStateRequested(surface, state);
4105+ });
4106+
4107+ connect(surface, &QObject::destroyed, this, [=]() {
4108+ this->onSurfaceDestroyed(surface);
4109+ });
4110+}
4111+
4112+void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
4113+{
4114 Q_EMIT surfaceCreated(surface);
4115- return surface;
4116-}
4117-
4118-MirSurface *SurfaceManager::inputMethodSurface() const
4119-{
4120- return m_virtualKeyboard;
4121 }
4122
4123 void SurfaceManager::setNewSurfaceMinimumWidth(int value)
4124@@ -124,3 +140,130 @@
4125 Q_EMIT newSurfaceHeightIncrementChanged(m_newSurfaceHeightIncrement);
4126 }
4127 }
4128+
4129+void SurfaceManager::raise(unityapi::MirSurfaceInterface *surface)
4130+{
4131+ if (m_underModification)
4132+ return;
4133+
4134+ DEBUG_MSG("("<<surface<<") started");
4135+ Q_EMIT modificationsStarted();
4136+ m_underModification = true;
4137+
4138+ doRaise(surface);
4139+
4140+ m_underModification = false;
4141+ Q_EMIT modificationsEnded();
4142+ DEBUG_MSG("("<<surface<<") ended");
4143+}
4144+
4145+void SurfaceManager::doRaise(unityapi::MirSurfaceInterface *apiSurface)
4146+{
4147+ auto surface = static_cast<MirSurface*>(apiSurface);
4148+ int index = m_surfaces.indexOf(surface);
4149+ Q_ASSERT(index != -1);
4150+ m_surfaces.move(index, 0);
4151+
4152+ QVector<MirSurfaceInterface*> surfaces;
4153+ surfaces.append(surface);
4154+ Q_EMIT surfacesRaised(surfaces);
4155+}
4156+
4157+void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
4158+{
4159+ auto surface = static_cast<MirSurface*>(apiSurface);
4160+
4161+ if (surface == m_focusedSurface) {
4162+ return;
4163+ }
4164+
4165+ Q_ASSERT(!m_underModification);
4166+
4167+ DEBUG_MSG("("<<surface<<") started");
4168+ Q_EMIT modificationsStarted();
4169+ m_underModification = true;
4170+ if (m_focusedSurface) {
4171+ m_focusedSurface->setFocused(false);
4172+ }
4173+ if (surface) {
4174+ if (surface->state() == Mir::HiddenState || surface->state() == Mir::MinimizedState) {
4175+ surface->setState(Mir::RestoredState);
4176+ }
4177+ surface->setFocused(true);
4178+ doRaise(surface);
4179+ }
4180+ m_focusedSurface = surface;
4181+ m_underModification = false;
4182+ Q_EMIT modificationsEnded();
4183+ DEBUG_MSG("("<<surface<<") ended");
4184+}
4185+
4186+void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
4187+{
4188+ Q_ASSERT(!m_underModification);
4189+
4190+ DEBUG_MSG("("<<surface<<","<<state<<") started");
4191+ Q_EMIT modificationsStarted();
4192+ m_underModification = true;
4193+
4194+ surface->setState(state);
4195+
4196+ if ((state == Mir::MinimizedState || state == Mir::HiddenState) && surface->focused()) {
4197+ Q_ASSERT(m_focusedSurface == surface);
4198+ surface->setFocused(false);
4199+ m_focusedSurface = nullptr;
4200+ focusFirstAvailableSurface();
4201+ }
4202+
4203+ m_underModification = false;
4204+ Q_EMIT modificationsEnded();
4205+ DEBUG_MSG("("<<surface<<","<<state<<") ended");
4206+}
4207+
4208+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
4209+{
4210+ m_surfaces.removeAll(surface);
4211+ if (m_focusedSurface == surface) {
4212+ m_focusedSurface = nullptr;
4213+
4214+ Q_EMIT modificationsStarted();
4215+ m_underModification = true;
4216+
4217+ focusFirstAvailableSurface();
4218+
4219+ m_underModification = false;
4220+ Q_EMIT modificationsEnded();
4221+ }
4222+}
4223+
4224+void SurfaceManager::focusFirstAvailableSurface()
4225+{
4226+ MirSurface *chosenSurface = nullptr;
4227+ for (int i = 0; i < m_surfaces.count() && !chosenSurface; ++i) {
4228+ auto *surface = m_surfaces[i];
4229+ if (surface->state() != Mir::HiddenState && surface->state() != Mir::MinimizedState) {
4230+ chosenSurface = surface;
4231+ }
4232+ }
4233+
4234+ if (!chosenSurface) {
4235+ return;
4236+ }
4237+
4238+ if (m_focusedSurface) {
4239+ m_focusedSurface->setFocused(false);
4240+ }
4241+ if (chosenSurface) {
4242+ chosenSurface->setFocused(true);
4243+ }
4244+ m_focusedSurface = chosenSurface;
4245+}
4246+
4247+void SurfaceManager::createInputMethodSurface()
4248+{
4249+ if (!m_virtualKeyboard) {
4250+ m_virtualKeyboard = new VirtualKeyboard;
4251+ registerSurface(m_virtualKeyboard);
4252+ Q_EMIT surfaceCreated(m_virtualKeyboard);
4253+ }
4254+}
4255
4256=== modified file 'tests/mocks/Unity/Application/SurfaceManager.h'
4257--- tests/mocks/Unity/Application/SurfaceManager.h 2016-04-13 18:33:15 +0000
4258+++ tests/mocks/Unity/Application/SurfaceManager.h 2016-11-23 15:15:21 +0000
4259@@ -19,15 +19,16 @@
4260
4261 #include <QObject>
4262
4263+#include <unity/shell/application/SurfaceManagerInterface.h>
4264+
4265 #include "MirSurface.h"
4266 #include "VirtualKeyboard.h"
4267
4268 class ApplicationInfo;
4269
4270-class SurfaceManager : public QObject
4271+class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
4272 {
4273 Q_OBJECT
4274- Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
4275 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
4276 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
4277 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
4278@@ -41,12 +42,17 @@
4279
4280 static SurfaceManager *instance();
4281
4282+ // SurfaceManagerInterface
4283+ void raise(unity::shell::application::MirSurfaceInterface *surface) override;
4284+ void activate(unity::shell::application::MirSurfaceInterface *surface) override;
4285+
4286 Q_INVOKABLE MirSurface* createSurface(const QString& name,
4287 Mir::Type type,
4288 Mir::State state,
4289 const QUrl& screenshot);
4290
4291- MirSurface* inputMethodSurface() const;
4292+
4293+ void notifySurfaceCreated(unity::shell::application::MirSurfaceInterface *);
4294
4295 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
4296 void setNewSurfaceMinimumWidth(int value);
4297@@ -66,12 +72,10 @@
4298 int newSurfaceHeightIncrement() const { return m_newSurfaceHeightIncrement; }
4299 void setNewSurfaceHeightIncrement(int);
4300
4301+public Q_SLOTS:
4302+ void createInputMethodSurface();
4303+
4304 Q_SIGNALS:
4305- void inputMethodSurfaceChanged();
4306- void countChanged();
4307- void surfaceCreated(MirSurface *surface);
4308- void surfaceDestroyed(MirSurface*surface);
4309-
4310 void newSurfaceMinimumWidthChanged(int value);
4311 void newSurfaceMaximumWidthChanged(int value);
4312 void newSurfaceMinimumHeightChanged(int value);
4313@@ -79,9 +83,16 @@
4314 void newSurfaceWidthIncrementChanged(int value);
4315 void newSurfaceHeightIncrementChanged(int value);
4316
4317+private Q_SLOTS:
4318+ void onStateRequested(MirSurface *surface, Mir::State state);
4319+ void onSurfaceDestroyed(MirSurface *surface);
4320+
4321 private:
4322- static SurfaceManager *the_surface_manager;
4323- VirtualKeyboard *m_virtualKeyboard;
4324+ void doRaise(unity::shell::application::MirSurfaceInterface *surface);
4325+ void focusFirstAvailableSurface();
4326+ void registerSurface(MirSurface *surface);
4327+
4328+ static SurfaceManager *m_instance;
4329
4330 int m_newSurfaceMinimumWidth{0};
4331 int m_newSurfaceMaximumWidth{0};
4332@@ -89,6 +100,13 @@
4333 int m_newSurfaceMaximumHeight{0};
4334 int m_newSurfaceWidthIncrement{1};
4335 int m_newSurfaceHeightIncrement{1};
4336+
4337+ MirSurface *m_focusedSurface{nullptr};
4338+ bool m_underModification{false};
4339+
4340+ QList<MirSurface*> m_surfaces;
4341+
4342+ VirtualKeyboard *m_virtualKeyboard{nullptr};
4343 };
4344
4345 #endif // SURFACEMANAGER_H
4346
4347=== modified file 'tests/mocks/Unity/Application/plugin.cpp'
4348--- tests/mocks/Unity/Application/plugin.cpp 2016-06-30 13:51:32 +0000
4349+++ tests/mocks/Unity/Application/plugin.cpp 2016-11-23 15:15:21 +0000
4350@@ -28,36 +28,11 @@
4351
4352 namespace {
4353
4354-// Creates the singletons that are called throughout C++ code
4355-void createUnityApplicationSharedSingletons()
4356-{
4357- // they have to be created in a specific order
4358- if (!MirFocusController::instance()) {
4359- new MirFocusController;
4360- }
4361- if (!SurfaceManager::instance()) {
4362- new SurfaceManager;
4363- }
4364-}
4365-
4366 QObject* applicationManagerSingleton(QQmlEngine*, QJSEngine*)
4367 {
4368- createUnityApplicationSharedSingletons();
4369 return new ApplicationManager;
4370 }
4371
4372-QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
4373-{
4374- createUnityApplicationSharedSingletons();
4375- return SurfaceManager::instance();
4376-}
4377-
4378-QObject* mirFocusControllerSingleton(QQmlEngine*, QJSEngine*)
4379-{
4380- createUnityApplicationSharedSingletons();
4381- return MirFocusController::instance();
4382-}
4383-
4384 } // anonymous namespace
4385
4386 void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
4387@@ -65,6 +40,7 @@
4388 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
4389 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
4390 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
4391+ qRegisterMetaType<unity::shell::application::SurfaceManagerInterface*>("unity::shell::application::SurfaceManagerInterface*");
4392 qRegisterMetaType<Mir::Type>("Mir::Type");
4393 qRegisterMetaType<Mir::State>("Mir::State");
4394
4395@@ -73,12 +49,11 @@
4396 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
4397 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
4398 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
4399- qmlRegisterSingletonType<MirFocusController>(uri, 0, 1, "MirFocusController", mirFocusControllerSingleton);
4400 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
4401 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
4402
4403 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
4404- qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
4405+ qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
4406
4407 qmlRegisterUncreatableType<Mir>(uri, 0, 1, "Mir", "Mir provides enum values, it can't be instantiated");
4408 }
4409
4410=== modified file 'tests/plugins/Unity/Launcher/launchermodeltest.cpp'
4411--- tests/plugins/Unity/Launcher/launchermodeltest.cpp 2016-09-22 20:43:05 +0000
4412+++ tests/plugins/Unity/Launcher/launchermodeltest.cpp 2016-11-23 15:15:21 +0000
4413@@ -65,6 +65,7 @@
4414 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
4415 int surfaceCount() const override { return m_surfaceCount; }
4416 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
4417+ void close() override {}
4418
4419 // Methods used for mocking (not in the interface)
4420 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
4421@@ -98,6 +99,9 @@
4422 }
4423 return nullptr;
4424 }
4425+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override {
4426+ return nullptr;
4427+ }
4428 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
4429 bool stopApplication(const QString &appId) override {
4430 Q_FOREACH(MockApp* app, m_list) {
4431
4432=== modified file 'tests/qmltests/Stage/ApplicationCheckBox.qml'
4433--- tests/qmltests/Stage/ApplicationCheckBox.qml 2016-06-02 12:02:35 +0000
4434+++ tests/qmltests/Stage/ApplicationCheckBox.qml 2016-11-23 15:15:21 +0000
4435@@ -149,7 +149,7 @@
4436 width: height
4437 height: promptsLabel.height * 0.7
4438 anchors.verticalCenter: parent.verticalCenter
4439- onClicked: d.application.promptSurfaceList.createSurface()
4440+ onClicked: d.application.createPromptSurface()
4441 Label { text: "âž•"; anchors.centerIn: parent }
4442 }
4443 MouseArea {
4444
4445=== modified file 'tests/qmltests/Stage/tst_DesktopStage.qml'
4446--- tests/qmltests/Stage/tst_DesktopStage.qml 2016-11-08 15:35:35 +0000
4447+++ tests/qmltests/Stage/tst_DesktopStage.qml 2016-11-23 15:15:21 +0000
4448@@ -20,8 +20,8 @@
4449 import Ubuntu.Components.ListItems 1.3
4450 import Unity.Application 0.1
4451 import Unity.Test 0.1
4452-import WindowManager 0.1
4453 import Utils 0.1
4454+import WindowManager 1.0
4455
4456 import ".." // For EdgeBarrierControls
4457 import "../../../qml/Stage"
4458@@ -57,9 +57,11 @@
4459 }
4460 }
4461
4462- TopLevelSurfaceList {
4463+ SurfaceManager { id: sMgr }
4464+ TopLevelWindowModel {
4465 id: topSurfaceList
4466- applicationsModel: ApplicationManager
4467+ applicationManager: ApplicationManager
4468+ surfaceManager: sMgr
4469 }
4470
4471 Loader {
4472@@ -80,6 +82,7 @@
4473
4474 Component.onCompleted: {
4475 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
4476+ ApplicationManager.startApplication("unity8-dash");
4477 }
4478 Component.onDestruction: {
4479 stageLoader.itemDestroyed = true;
4480@@ -170,8 +173,9 @@
4481 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
4482
4483 tryCompare(topSurfaceList, "count", 1);
4484- tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) != null;}, true);
4485- compare(MirFocusController.focusedSurface, topSurfaceList.surfaceAt(0));
4486+ tryCompareFunction(function(){return topSurfaceList.windowAt(0) != null;}, true);
4487+ topSurfaceList.windowAt(0).activate();
4488+ tryCompare(topSurfaceList, "focusedWindow", topSurfaceList.windowAt(0));
4489 }
4490
4491 function cleanup() {
4492@@ -284,13 +288,13 @@
4493 verify(fromAppWindow);
4494 tap(fromAppWindow);
4495 compare(fromDelegate.surface.activeFocus, true);
4496- compare(MirFocusController.focusedSurface, fromDelegate.surface);
4497+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
4498
4499 var toAppWindow = findChild(toDelegate, "appWindow");
4500 verify(toAppWindow);
4501 tap(toAppWindow);
4502 compare(toDelegate.surface.activeFocus, true);
4503- compare(MirFocusController.focusedSurface, toDelegate.surface);
4504+ compare(topSurfaceList.focusedWindow, toDelegate.window);
4505 }
4506
4507 function test_clickingOnWindowChangesFocusedApp_data() {
4508@@ -309,13 +313,13 @@
4509 verify(fromAppWindow);
4510 mouseClick(fromAppWindow);
4511 compare(fromDelegate.surface.activeFocus, true);
4512- compare(MirFocusController.focusedSurface, fromDelegate.surface);
4513+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
4514
4515 var toAppWindow = findChild(toDelegate, "appWindow");
4516 verify(toAppWindow);
4517 mouseClick(toAppWindow);
4518 compare(toDelegate.surface.activeFocus, true);
4519- compare(MirFocusController.focusedSurface, toDelegate.surface);
4520+ compare(topSurfaceList.focusedWindow, toDelegate.window);
4521 }
4522
4523 function test_tappingOnDecorationFocusesApplication_data() {
4524@@ -343,6 +347,13 @@
4525 return findChild(appDelegate, "appWindowDecoration");
4526 }
4527
4528+ function maximizeDelegate(appDelegate) {
4529+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
4530+ verify(maximizeButton);
4531+ mouseClick(maximizeButton);
4532+ tryCompare(appDelegate, "visuallyMaximized", true);
4533+ }
4534+
4535 function test_tappingOnDecorationFocusesApplication(data) {
4536 var appDelegates = [];
4537 for (var i = 0; i < data.apps.length; i++) {
4538@@ -398,7 +409,7 @@
4539 startApplication("camera-app");
4540
4541 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4542- dialerDelegate.surface.requestFocus();
4543+ dialerDelegate.surface.activate();
4544 tryCompare(dialerDelegate, "focus", true);
4545
4546 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
4547@@ -411,7 +422,7 @@
4548 startApplication("camera-app");
4549
4550 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4551- dialerDelegate.surface.requestFocus();
4552+ dialerDelegate.surface.activate();
4553 tryCompare(dialerDelegate, "focus", true);
4554
4555 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
4556@@ -426,7 +437,7 @@
4557 startApplication("camera-app");
4558
4559 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4560- dialerDelegate.surface.requestFocus();
4561+ dialerDelegate.surface.activate();
4562 tryCompare(dialerDelegate, "focus", true);
4563
4564 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
4565@@ -441,7 +452,7 @@
4566 startApplication("camera-app");
4567
4568 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4569- dialerDelegate.surface.requestFocus();
4570+ dialerDelegate.surface.activate();
4571 tryCompare(dialerDelegate, "focus", true);
4572
4573 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
4574@@ -512,11 +523,11 @@
4575 apps.forEach(startApplication);
4576 verify(topSurfaceList.count == 3);
4577 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
4578- tryCompare(MirFocusController, "focusedSurface", null); // verify no surface is focused
4579+ tryCompare(topSurfaceList, "focusedWindow", null); // verify no window is focused
4580
4581 // now try pressing all 4 arrow keys + ctrl + meta
4582 keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
4583- tryCompare(MirFocusController, "focusedSurface", null); // verify still no surface is focused
4584+ tryCompare(topSurfaceList, "focusedWindow", null); // verify still no window is focused
4585 }
4586
4587 function test_minimizeApplicationHidesSurface() {
4588@@ -527,9 +538,12 @@
4589 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
4590 verify(decoratedWindow);
4591
4592- tryCompare(dashSurface, "visible", true);
4593- decoratedWindow.minimizeClicked();
4594- tryCompare(dashSurface, "visible", false);
4595+ var minimizeButton = findChild(decoratedWindow, "minimizeWindowButton");
4596+ verify(minimizeButton);
4597+
4598+ tryCompare(dashSurface, "exposed", true);
4599+ mouseClick(minimizeButton);
4600+ tryCompare(dashSurface, "exposed", false);
4601 }
4602
4603 function test_maximizeApplicationHidesSurfacesBehindIt() {
4604@@ -538,16 +552,16 @@
4605 var gmailDelegate = startApplication("gmail-webapp");
4606
4607 // maximize without raising
4608- dialerDelegate.maximize();
4609+ dialerDelegate.requestMaximize();
4610 tryCompare(dialerDelegate, "visuallyMaximized", true);
4611
4612- tryCompare(dashDelegate.surface, "visible", false);
4613- compare(gmailDelegate.surface.visible, true);
4614+ tryCompare(dashDelegate.surface, "exposed", false);
4615+ compare(gmailDelegate.surface.exposed, true);
4616
4617 // restore without raising
4618- dialerDelegate.restoreFromMaximized();
4619- compare(dashDelegate.surface.visible, true);
4620- compare(gmailDelegate.surface.visible, true);
4621+ dialerDelegate.requestRestore();
4622+ compare(dashDelegate.surface.exposed, true);
4623+ compare(gmailDelegate.surface.exposed, true);
4624 }
4625
4626 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
4627@@ -580,26 +594,22 @@
4628 tryCompare(dialerDelegate, "visuallyMaximized", true);
4629 tryCompare(gmailDelegate, "visuallyMaximized", true);
4630
4631- tryCompare(dashApp.surfaceList.get(0), "visible", false);
4632- tryCompare(dialerApp.surfaceList.get(0), "visible", false);
4633- tryCompare(mapApp.surfaceList.get(0), "visible", false);
4634+ tryCompare(dashApp.surfaceList.get(0), "exposed", false);
4635+ tryCompare(dialerApp.surfaceList.get(0), "exposed", false);
4636+ tryCompare(mapApp.surfaceList.get(0), "exposed", false);
4637
4638 ApplicationManager.stopApplication("gmail-webapp");
4639 wait(2000)
4640
4641- tryCompare(mapApp.surfaceList.get(0), "visible", true);
4642- tryCompare(dialerApp.surfaceList.get(0), "visible", true);
4643- tryCompare(dashApp.surfaceList.get(0), "visible", false); // still occluded by maximised dialer
4644+ tryCompare(mapApp.surfaceList.get(0), "exposed", true);
4645+ tryCompare(dialerApp.surfaceList.get(0), "exposed", true);
4646+ tryCompare(dashApp.surfaceList.get(0), "exposed", false); // still occluded by maximised dialer
4647 }
4648
4649 function test_maximisedAppStaysVisibleWhenAppStarts() {
4650 var dashDelegate = startApplication("unity8-dash");
4651
4652- // maximize
4653- var dashMaximizeButton = findChild(dashDelegate, "maximizeWindowButton");
4654- verify(dashMaximizeButton);
4655- mouseClick(dashMaximizeButton);
4656- tryCompare(dashDelegate, "visuallyMaximized", true);
4657+ maximizeDelegate(dashDelegate);
4658
4659 var dialerDelegate = startApplication("dialer-app");
4660 verify(dialerDelegate);
4661@@ -621,25 +631,25 @@
4662 tryCompare(facebookAppDelegate, "visible", true);
4663
4664 // Maximize the topmost and make sure the other two are hidden
4665- facebookAppDelegate.maximize();
4666+ maximizeDelegate(facebookAppDelegate);
4667 tryCompare(dashAppDelegate, "visible", false);
4668 tryCompare(dialerAppDelegate, "visible", false);
4669 tryCompare(facebookAppDelegate, "visible", true);
4670
4671 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
4672- dashAppDelegate.focus = true;
4673+ dashAppDelegate.activate();
4674 tryCompare(dashAppDelegate, "visible", true);
4675 tryCompare(dialerAppDelegate, "visible", false);
4676 tryCompare(facebookAppDelegate, "visible", true);
4677
4678 // Now focus the dialer app. all 3 should be visible again
4679- dialerAppDelegate.focus = true;
4680+ dialerAppDelegate.activate();
4681 tryCompare(dashAppDelegate, "visible", true);
4682 tryCompare(dialerAppDelegate, "visible", true);
4683 tryCompare(facebookAppDelegate, "visible", true);
4684
4685 // Maximize the dialer app. The other 2 should hide
4686- dialerAppDelegate.maximize();
4687+ maximizeDelegate(dialerAppDelegate);
4688 tryCompare(dashAppDelegate, "visible", false);
4689 tryCompare(dialerAppDelegate, "visible", true);
4690 tryCompare(facebookAppDelegate, "visible", false);
4691@@ -648,7 +658,7 @@
4692 function test_dropShadow() {
4693 // start an app, maximize it
4694 var facebookAppDelegate = startApplication("facebook-webapp");
4695- facebookAppDelegate.maximize();
4696+ maximizeDelegate(facebookAppDelegate);
4697
4698 // verify the drop shadow is still not visible
4699 verify(PanelState.dropShadow == false);
4700
4701=== modified file 'tests/qmltests/Stage/tst_PhoneStage.qml'
4702--- tests/qmltests/Stage/tst_PhoneStage.qml 2016-10-08 16:45:37 +0000
4703+++ tests/qmltests/Stage/tst_PhoneStage.qml 2016-11-23 15:15:21 +0000
4704@@ -22,7 +22,7 @@
4705 import "../../../qml/Stage"
4706 import Ubuntu.Components 1.3
4707 import Unity.Application 0.1
4708-import WindowManager 0.1
4709+import WindowManager 1.0
4710
4711 Item {
4712 id: root
4713@@ -31,6 +31,7 @@
4714
4715 property var greeter: { fullyShown: true }
4716
4717+ SurfaceManager { id: sMgr }
4718 Stage {
4719 id: stage
4720 anchors { fill: parent; rightMargin: units.gu(30) }
4721@@ -41,9 +42,13 @@
4722 orientations: Orientations {}
4723 applicationManager: ApplicationManager
4724 mode: "staged"
4725- topLevelSurfaceList: TopLevelSurfaceList {
4726+ topLevelSurfaceList: TopLevelWindowModel {
4727 id: topLevelSurfaceList
4728- applicationsModel: ApplicationManager
4729+ applicationManager: ApplicationManager
4730+ surfaceManager: sMgr
4731+ }
4732+ Component.onCompleted: {
4733+ ApplicationManager.startApplication("unity8-dash");
4734 }
4735 }
4736
4737@@ -86,11 +91,16 @@
4738
4739 function init() {
4740 // wait until unity8-dash is up and running.
4741- // it's started automatically by ApplicationManager mock implementation
4742+ ApplicationManager.startApplication("unity8-dash");
4743 tryCompare(ApplicationManager, "count", 1);
4744 var dashApp = ApplicationManager.findApplication("unity8-dash");
4745 verify(dashApp);
4746 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
4747+
4748+ // wait for Stage to stabilize back into its initial state
4749+ var appRepeater = findChild(stage, "appRepeater");
4750+ tryCompare(appRepeater, "count", 1);
4751+ tryCompare(appRepeater.itemAt(0), "x", 0);
4752 }
4753
4754 function cleanup() {
4755@@ -100,10 +110,6 @@
4756 waitForRendering(stage);
4757
4758 killApps();
4759- // wait for Stage to stabilize back into its initial state
4760- var appRepeater = findChild(stage, "appRepeater");
4761- tryCompare(appRepeater, "count", 1);
4762- tryCompare(appRepeater.itemAt(0), "x", 0);
4763
4764 stage.shellOrientationAngle = 0;
4765
4766@@ -505,7 +511,7 @@
4767 function test_selectSuspendedAppWithoutSurface() {
4768 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
4769 var dashSurfaceId = topLevelSurfaceList.idAt(0);
4770- var dashSurface = topLevelSurfaceList.surfaceAt(0);
4771+ var dashWindow = topLevelSurfaceList.windowAt(0);
4772
4773 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
4774 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
4775@@ -513,7 +519,7 @@
4776
4777 switchToSurface(dashSurfaceId);
4778
4779- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4780+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4781 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4782
4783 compare(webbrowserApp.surfaceList.count, 1);
4784@@ -549,7 +555,7 @@
4785 {
4786 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
4787 var dashSurfaceId = topLevelSurfaceList.idAt(0);
4788- var dashSurface = topLevelSurfaceList.surfaceAt(0);
4789+ var dashWindow = topLevelSurfaceList.windowAt(0);
4790
4791 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
4792 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
4793@@ -557,7 +563,7 @@
4794
4795 switchToSurface(dashSurfaceId);
4796
4797- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4798+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4799 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4800
4801 compare(webbrowserApp.surfaceList.count, 1);
4802
4803=== modified file 'tests/qmltests/Stage/tst_TabletStage.qml'
4804--- tests/qmltests/Stage/tst_TabletStage.qml 2016-10-08 16:45:37 +0000
4805+++ tests/qmltests/Stage/tst_TabletStage.qml 2016-11-23 15:15:21 +0000
4806@@ -21,7 +21,7 @@
4807 import Unity.Application 0.1
4808 import Unity.Test 0.1
4809 import Utils 0.1
4810-import WindowManager 0.1
4811+import WindowManager 1.0
4812
4813 import ".."
4814 import "../../../qml/Stage"
4815@@ -35,6 +35,7 @@
4816
4817 property var greeter: { fullyShown: true }
4818
4819+ SurfaceManager { id: sMgr }
4820 Stage {
4821 id: stage
4822 anchors { fill: parent; rightMargin: units.gu(30) }
4823@@ -50,9 +51,13 @@
4824 focus: true
4825 mode: "stagedWithSideStage"
4826 applicationManager: ApplicationManager
4827- topLevelSurfaceList: TopLevelSurfaceList {
4828+ topLevelSurfaceList: TopLevelWindowModel {
4829 id: topLevelSurfaceList
4830- applicationsModel: ApplicationManager
4831+ applicationManager: ApplicationManager
4832+ surfaceManager: sMgr
4833+ }
4834+ Component.onCompleted: {
4835+ ApplicationManager.startApplication("unity8-dash");
4836 }
4837 }
4838
4839@@ -129,6 +134,7 @@
4840 function init() {
4841 stageSaver.clear();
4842
4843+ ApplicationManager.startApplication("unity8-dash");
4844 tryCompare(topSurfaceList, "count", 1);
4845 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
4846
4847@@ -143,6 +149,11 @@
4848 }
4849 }
4850
4851+ // wait for Stage to stabilize back into its initial state
4852+ var appRepeater = findChild(stage, "appRepeater");
4853+ tryCompare(appRepeater, "count", 1);
4854+ tryCompare(appRepeater.itemAt(0), "x", 0);
4855+
4856 waitUntilAppSurfaceShowsUp(topSurfaceList.idAt(0));
4857 sideStage.hideNow()
4858 tryCompare(sideStage, "x", stage.width)
4859@@ -156,11 +167,6 @@
4860
4861 killApps();
4862
4863- // wait for Stage to stabilize back into its initial state
4864- var appRepeater = findChild(stage, "appRepeater");
4865- tryCompare(appRepeater, "count", 1);
4866- tryCompare(appRepeater.itemAt(0), "x", 0);
4867-
4868 sideStage.hideNow();
4869 tryCompare(sideStage, "x", stage.width)
4870 waitForRendering(stage)
4871@@ -628,7 +634,7 @@
4872 function test_selectSuspendedAppWithoutSurface() {
4873 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
4874 var dashSurfaceId = topSurfaceList.idAt(0);
4875- var dashSurface = topSurfaceList.surfaceAt(0);
4876+ var dashWindow = topSurfaceList.windowAt(0);
4877
4878 var webbrowserSurfaceId = topSurfaceList.nextId;
4879 webbrowserCheckBox.checked = true;
4880@@ -637,7 +643,7 @@
4881
4882 switchToSurface(dashSurfaceId);
4883
4884- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4885+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4886 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4887
4888 compare(webbrowserApp.surfaceList.count, 1);
4889
4890=== modified file 'tests/qmltests/tst_Shell.qml'
4891--- tests/qmltests/tst_Shell.qml 2016-10-28 19:44:07 +0000
4892+++ tests/qmltests/tst_Shell.qml 2016-11-23 15:15:21 +0000
4893@@ -52,6 +52,15 @@
4894 }
4895
4896 property var shell: shellLoader.item ? shellLoader.item : null
4897+ onShellChanged: {
4898+ if (shell) {
4899+ topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
4900+ } else {
4901+ topLevelSurfaceList = null;
4902+ }
4903+ }
4904+
4905+ property var topLevelSurfaceList: null
4906
4907 Item {
4908 id: shellContainer
4909@@ -120,6 +129,9 @@
4910 primary: shellLoader.primaryOrientation
4911 }
4912 mode: shellLoader.mode
4913+ Component.onCompleted: {
4914+ ApplicationManager.startApplication("unity8-dash");
4915+ }
4916 Component.onDestruction: {
4917 shellLoader.itemDestroyed = true;
4918 }
4919@@ -227,7 +239,7 @@
4920 activeFocusOnPress: false
4921 model: ["single", "single-passphrase", "single-pin", "full"]
4922 onSelectedIndexChanged: {
4923- shellLoader.active = false;
4924+ testCase.tearDown();
4925 LightDM.Greeter.mockMode = model[selectedIndex];
4926 LightDM.Users.mockMode = model[selectedIndex];
4927 shellLoader.active = true;
4928@@ -318,21 +330,21 @@
4929 id: fullscreeAppCheck
4930
4931 onTriggered: {
4932- if (!MirFocusController.focusedSurface) return;
4933- if (MirFocusController.focusedSurface.state == Mir.FullscreenState) {
4934- MirFocusController.focusedSurface.state = Mir.RestoredState;
4935+ if (!topLevelSurfaceList.focusedWindow) return;
4936+ if (topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState) {
4937+ topLevelSurfaceList.focusedWindow.requestState(Mir.RestoredState);
4938 } else {
4939- MirFocusController.focusedSurface.state = Mir.FullscreenState;
4940+ topLevelSurfaceList.focusedWindow.requestState(Mir.FullscreenState);
4941 }
4942 }
4943
4944 Binding {
4945 target: fullscreeAppCheck
4946- when: MirFocusController.focusedSurface
4947+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow
4948 property: "checked"
4949 value: {
4950- if (!MirFocusController.focusedSurface) return false;
4951- return MirFocusController.focusedSurface.state === Mir.FullscreenState
4952+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow) return false;
4953+ return topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
4954 }
4955 }
4956 }
4957@@ -346,21 +358,22 @@
4958 id: chromeAppCheck
4959
4960 onTriggered: {
4961- if (!MirFocusController.focusedSurface) return;
4962- if (MirFocusController.focusedSurface.shellChrome == Mir.LowChrome) {
4963- MirFocusController.focusedSurface.setShellChrome(Mir.NormalChrome);
4964+ if (!topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return;
4965+ var surface = topLevelSurfaceList.focusedWindow.surface;
4966+ if (surface.shellChrome == Mir.LowChrome) {
4967+ surface.setShellChrome(Mir.NormalChrome);
4968 } else {
4969- MirFocusController.focusedSurface.setShellChrome(Mir.LowChrome);
4970+ surface.setShellChrome(Mir.LowChrome);
4971 }
4972 }
4973
4974 Binding {
4975 target: chromeAppCheck
4976- when: MirFocusController.focusedSurface !== null
4977+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow !== null && topLevelSurfaceList.focusedWindow.surface !== null
4978 property: "checked"
4979 value: {
4980- if (!MirFocusController.focusedSurface) return false;
4981- MirFocusController.focusedSurface.shellChrome === Mir.LowChrome
4982+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return false;
4983+ topLevelSurfaceList.focusedWindow.surface.shellChrome === Mir.LowChrome
4984 }
4985 }
4986 }
4987@@ -369,6 +382,57 @@
4988 }
4989 }
4990
4991+ Button {
4992+ text: "Toggle input method"
4993+ width: parent.width
4994+ activeFocusOnPress: false
4995+ onClicked: {
4996+ testCase.ensureInputMethodSurface();
4997+ var inputMethod = root.topLevelSurfaceList.inputMethodSurface;
4998+ if (inputMethod.visible) {
4999+ inputMethod.requestState(Mir.HiddenState);
5000+ } else {
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches