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
Prerequisite: lp:~dandrader/unity8/refactorWindowDecoration
Diff against target: 5347 lines (+2203/-1403)
42 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/FakeMaximizeDelegate.qml (+7/-7)
qml/Stage/MoveHandler.qml (+1/-1)
qml/Stage/Stage.qml (+126/-119)
qml/Stage/StagedFullscreenPolicy.qml (+4/-4)
qml/Stage/TopLevelSurfaceRepeater.qml (+0/-67)
qml/Stage/WindowControlsOverlay.qml (+1/-1)
qml/Stage/WindowedFullscreenPolicy.qml (+1/-1)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+48/-24)
tests/mocks/Unity/Application/ApplicationInfo.h (+2/-2)
tests/mocks/Unity/Application/ApplicationManager.cpp (+92/-63)
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/-31)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+4/-0)
tests/qmltests/Stage/ApplicationCheckBox.qml (+1/-1)
tests/qmltests/Stage/tst_ApplicationWindow.qml (+12/-30)
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
Lukáš Tinkl (community) Needs Fixing
Unity8 CI Bot continuous-integration Needs Fixing
Michael Zanetti Pending
Review via email: mp+311626@code.launchpad.net

This proposal supersedes a proposal from 2016-11-01.

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

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 : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

* 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

- 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

>>>> - 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

> 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

> 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 : Posted in a previous version of this proposal

> 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

> 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 : Posted in a previous version of this proposal
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...

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
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:2715
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2558/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3369/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3397
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3248/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3248
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3248/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3248
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3248/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3248/console

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

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

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

New instances of the same app do not cascade:

0. make tryShell
1. Launch dialer
2. Click the "+" button to launch a second instance
3. The new window doesn't cascade

review: Needs Fixing
Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

If you add a trust prompt to a window and launch a second instance of the same app, the newly created window is not focused

review: Needs Fixing
Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

Windows are not restored properly:

1. Maximize dash
2. Minimize it
3. Click the bfb icon
4. The dash window is restored to "normal" size, not to the maximized from step 1.

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

On 29/11/2016 15:35, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> New instances of the same app do not cascade:
>
> 0. make tryShell
> 1. Launch dialer
> 2. Click the "+" button to launch a second instance
> 3. The new window doesn't cascade

Same happens in trunk

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

Double click to maximize breaks:

1. Double click the dash decoration to maximize the window
2. Drag it down from the panel to restore it
3. Double click again to maximize -> no longer works

review: Needs Fixing
Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

In tablet mode, if you put a fullscreen app into side stage (like camera), the main app (dash usually) has its decoration cut at the top.

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

> In tablet mode, if you put a fullscreen app into side stage (like camera), the
> main app (dash usually) has its decoration cut at the top.

This happens in trunk too

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

On 29/11/2016 15:37, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> If you add a trust prompt to a window and launch a second instance of the same app, the newly created window is not focused

Fixed. Bug in the Unity.Application mock.

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

On 29/11/2016 15:39, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> Windows are not restored properly:
>
> 1. Maximize dash
> 2. Minimize it
> 3. Click the bfb icon
> 4. The dash window is restored to "normal" size, not to the maximized from step 1.

That's due to the simplicity of the Unity.Application mock. It doesn't
have a full blown implementation of the window management bits/policy
that have been now moved to a lower level, behind
Unity.Application/qtmir API. This use case works with the real
Unity.Application/qtmir.

Unfortunately this means that right now we can no longer test everything
window management related in our qmltests. For some things untiy8 just
sits back and expects to get the correct changes to the model for free,
which it will then respond to by updating the qml items and animating
the qml scene (the view).

To be more specific, in response to the click on step 3 unity8 just
calls MirSurface->activate(). qtmir/miral will take care of noticing
that this surface is minimized and will restore it to its previous state
before finally focusing and raising it.

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

On 29/11/2016 15:50, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> Double click to maximize breaks:
>
> 1. Double click the dash decoration to maximize the window
> 2. Drag it down from the panel to restore it
> 3. Double click again to maximize -> no longer works

Fixed.

lp:~dandrader/unity8/miral updated
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-30 19:24:53 +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-29 09:40:16 +0000
16+++ debian/control 2016-11-30 19:24:53 +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.120),
22+ libunity-api-dev (>= 8.0),
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@@ -160,7 +160,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@@ -206,7 +206,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-30 19:24:53 +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-30 19:24:53 +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-30 19:24:53 +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-30 19:24:53 +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-30 19:24:53 +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 UNITY_WINDOW_H
1949+#define UNITY_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 // UNITY_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-30 19:24:53 +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-30 19:24:53 +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-30 19:24:53 +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-29 09:39:56 +0000
2174+++ qml/Shell.qml 2016-11-30 19:24:53 +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@@ -318,6 +323,7 @@
2203 InputMethod {
2204 id: inputMethod
2205 objectName: "inputMethod"
2206+ surface: topLevelSurfaceList.inputMethodSurface
2207 anchors {
2208 fill: parent
2209 topMargin: panel.panelHeight
2210@@ -485,8 +491,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@@ -786,7 +792,9 @@
2222 }
2223
2224 // non-visual objects
2225- KeymapSwitcher {}
2226+ KeymapSwitcher {
2227+ focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
2228+ }
2229 BrightnessControl {}
2230
2231 Rectangle {
2232
2233=== modified file 'qml/Stage/FakeMaximizeDelegate.qml'
2234--- qml/Stage/FakeMaximizeDelegate.qml 2016-09-13 11:53:40 +0000
2235+++ qml/Stage/FakeMaximizeDelegate.qml 2016-11-30 19:24:53 +0000
2236@@ -98,19 +98,19 @@
2237 function commit() {
2238 if (progress > hintThreshold && edge != -1) {
2239 if (edge == Item.Top) {
2240- target.maximize();
2241+ target.requestMaximize();
2242 } else if (edge == Item.Left) {
2243- target.maximizeLeft();
2244+ target.requestMaximizeLeft();
2245 } else if (edge == Item.Right) {
2246- target.maximizeRight();
2247+ target.requestMaximizeRight();
2248 } else if (edge == Item.TopLeft) {
2249- target.maximizeTopLeft();
2250+ target.requestMaximizeTopLeft();
2251 } else if (edge == Item.TopRight) {
2252- target.maximizeTopRight();
2253+ target.requestMaximizeTopRight();
2254 } else if (edge == Item.BottomLeft) {
2255- target.maximizeBottomLeft();
2256+ target.requestMaximizeBottomLeft();
2257 } else if (edge == Item.BottomRight) {
2258- target.maximizeBottomRight();
2259+ target.requestMaximizeBottomRight();
2260 }
2261 } else {
2262 stop();
2263
2264=== modified file 'qml/Stage/MoveHandler.qml'
2265--- qml/Stage/MoveHandler.qml 2016-10-28 12:28:49 +0000
2266+++ qml/Stage/MoveHandler.qml 2016-11-30 19:24:53 +0000
2267@@ -114,7 +114,7 @@
2268 // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
2269 if (target.anyMaximized && !target.windowedTransitionRunning) {
2270 priv.progress = 0;
2271- target.restore(false, WindowStateStorage.WindowStateNormal);
2272+ target.requestRestore();
2273 }
2274
2275 var pos = mapToItem(target.parent, mouse.x, mouse.y);
2276
2277=== modified file 'qml/Stage/Stage.qml'
2278--- qml/Stage/Stage.qml 2016-11-29 09:39:25 +0000
2279+++ qml/Stage/Stage.qml 2016-11-30 19:24:53 +0000
2280@@ -119,8 +119,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@@ -186,29 +185,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@@ -244,20 +248,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@@ -358,8 +352,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@@ -434,13 +428,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@@ -633,7 +620,7 @@
2381
2382 onShownChanged: {
2383 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
2384- priv.mainStageDelegate.claimFocus();
2385+ priv.mainStageDelegate.activate();
2386 }
2387 }
2388
2389@@ -666,14 +653,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@@ -685,11 +681,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@@ -699,6 +695,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@@ -767,7 +769,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@@ -793,7 +795,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@@ -802,6 +806,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@@ -813,45 +836,60 @@
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 (decoratedWindow.dragging) {
2570+ appDelegate.restore(false, WindowStateStorage.WindowStateNormal);
2571+ } else if (appDelegate.anyMaximized) {
2572+ appDelegate.restoreFromMaximized()
2573+ } else {
2574+ appDelegate.restore();
2575+ }
2576+ }
2577+ }
2578+ }
2579+
2580 Component.onCompleted: {
2581 if (application && application.rotatesWindowContents) {
2582 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
2583@@ -865,16 +903,7 @@
2584 // Now load any saved state. This needs to happen *after* the cascading!
2585 resizeArea.loadWindowState();
2586
2587- // NB: We're differentiating if this delegate was created in response to a new entry in the model
2588- // or if the Repeater is just populating itself with delegates to match the model it received.
2589- if (!appRepeater.startingUp) {
2590- // a top level window is always the focused one when it first appears, unfocusing
2591- // any preexisting one
2592- if (root.state == "spread") {
2593- spreadItem.highlightedIndex = index;
2594- }
2595- claimFocus();
2596- }
2597+ updateQmlFocusFromMirSurfaceFocus();
2598
2599 refreshStage();
2600 _constructing = false;
2601@@ -889,16 +918,6 @@
2602 priv.updateForegroundMaximizedApp();
2603 }
2604
2605- if (focus) {
2606- // focus some other window
2607- for (var i = 0; i < appRepeater.count; i++) {
2608- var appDelegate = appRepeater.itemAt(i);
2609- if (appDelegate && !appDelegate.minimized && i != index) {
2610- appDelegate.focus = true;
2611- return;
2612- }
2613- }
2614- }
2615 }
2616
2617 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
2618@@ -919,7 +938,7 @@
2619 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
2620
2621 function close() {
2622- model.surface.close();
2623+ model.window.close();
2624 }
2625
2626 function maximize(animated) {
2627@@ -970,7 +989,6 @@
2628 animationsEnabled = (animated === undefined) || animated;
2629 windowState = state || WindowStateStorage.WindowStateRestored;
2630 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
2631- focus = true;
2632 }
2633
2634 function playFocusAnimation() {
2635@@ -984,7 +1002,7 @@
2636 rightEdgeFocusAnimation.start()
2637 }
2638 } else if (state == "windowedRightEdge" || state == "windowed") {
2639- claimFocus();
2640+ activate();
2641 } else {
2642 focusAnimation.start()
2643 }
2644@@ -1022,10 +1040,10 @@
2645 to: 1
2646 duration: UbuntuAnimation.SnapDuration
2647 onStarted: {
2648- topLevelSurfaceList.raiseId(model.id);
2649+ topLevelSurfaceList.raiseId(model.window.id);
2650 }
2651 onStopped: {
2652- appDelegate.claimFocus();
2653+ appDelegate.activate();
2654 }
2655 }
2656 ParallelAnimation {
2657@@ -1035,7 +1053,7 @@
2658 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
2659 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
2660 onStopped: {
2661- appDelegate.focus = true
2662+ appDelegate.activate();
2663 }
2664 }
2665 ParallelAnimation {
2666@@ -1399,14 +1417,6 @@
2667 SequentialAnimation {
2668 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
2669 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
2670- ScriptAction {
2671- script: {
2672- if (appDelegate.minimized) {
2673- appDelegate.focus = false;
2674- priv.focusNext();
2675- }
2676- }
2677- }
2678 }
2679 },
2680 Transition {
2681@@ -1497,7 +1507,7 @@
2682 visible: enabled
2683
2684 onPressed: {
2685- appDelegate.focus = true;
2686+ appDelegate.activate();
2687 }
2688
2689 Component.onDestruction: {
2690@@ -1511,7 +1521,7 @@
2691 anchors.left: appDelegate.left
2692 anchors.top: appDelegate.top
2693 application: model.application
2694- surface: model.surface
2695+ surface: model.window.surface
2696 active: appDelegate.focus
2697 focus: true
2698 interactive: root.interactive
2699@@ -1536,21 +1546,21 @@
2700 onCloseClicked: { appDelegate.close(); }
2701 onMaximizeClicked: {
2702 if (appDelegate.canBeMaximized) {
2703- appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
2704+ appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
2705 }
2706 }
2707 onMaximizeHorizontallyClicked: {
2708 if (appDelegate.canBeMaximizedHorizontally) {
2709- appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
2710+ appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
2711 }
2712 }
2713 onMaximizeVerticallyClicked: {
2714 if (appDelegate.canBeMaximizedVertically) {
2715- appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
2716+ appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
2717 }
2718 }
2719- onMinimizeClicked: appDelegate.minimize()
2720- onDecorationPressed: { appDelegate.focus = true; }
2721+ onMinimizeClicked: { appDelegate.requestMinimize(); }
2722+ onDecorationPressed: { appDelegate.activate(); }
2723 onDecorationReleased: fakeRectangle.commit();
2724
2725 property real angle: 0
2726@@ -1597,12 +1607,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@@ -1615,16 +1625,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-30 19:24:53 +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/WindowControlsOverlay.qml'
2866--- qml/Stage/WindowControlsOverlay.qml 2016-09-13 11:53:40 +0000
2867+++ qml/Stage/WindowControlsOverlay.qml 2016-11-30 19:24:53 +0000
2868@@ -57,7 +57,7 @@
2869 touchPoints.length <= maximumTouchPoints
2870 onRecognizedPressChanged: {
2871 if (recognizedPress) {
2872- target.focus = true;
2873+ target.activate();
2874 overlayTimer.start();
2875 }
2876 }
2877
2878=== modified file 'qml/Stage/WindowedFullscreenPolicy.qml'
2879--- qml/Stage/WindowedFullscreenPolicy.qml 2016-04-04 13:37:49 +0000
2880+++ qml/Stage/WindowedFullscreenPolicy.qml 2016-11-30 19:24:53 +0000
2881@@ -33,7 +33,7 @@
2882 _firstTimeSurface = false;
2883
2884 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
2885- surface.state = Mir.RestoredState;
2886+ surface.requestState(Mir.RestoredState);
2887 }
2888 }
2889 }
2890
2891=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
2892--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-09-12 17:05:43 +0000
2893+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-11-30 19:24:53 +0000
2894@@ -75,6 +75,33 @@
2895 {
2896 }
2897
2898+void ApplicationInfo::createPromptSurface()
2899+{
2900+ if (state() == ApplicationInfo::Stopped) { return; }
2901+
2902+ auto surfaceManager = SurfaceManager::instance();
2903+ if (!surfaceManager) {
2904+ WARNING_MSG("No SurfaceManager");
2905+ return;
2906+ }
2907+
2908+ QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
2909+ int i = rand() % screenshotIds.count();
2910+
2911+ QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
2912+ .arg(screenshotIds[i]);
2913+
2914+ auto surface = surfaceManager->createSurface(QString("prompt foo"),
2915+ Mir::NormalType,
2916+ Mir::RestoredState,
2917+ screenshotUrl);
2918+ surfaceManager->notifySurfaceCreated(surface);
2919+
2920+ m_promptSurfaceList->addSurface(surface);
2921+
2922+ surfaceManager->activate(surface);
2923+}
2924+
2925 void ApplicationInfo::createSurface()
2926 {
2927 if (state() == ApplicationInfo::Stopped) { return; }
2928@@ -90,14 +117,16 @@
2929 return;
2930 }
2931
2932+ bool wasFocused = focused();
2933+
2934 auto surface = surfaceManager->createSurface(surfaceName,
2935 Mir::NormalType,
2936- fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
2937+ fullscreen() ? Mir::FullscreenState : Mir::RestoredState,
2938 m_screenshotFileName);
2939
2940 surface->setShellChrome(m_shellChrome);
2941
2942- m_surfaceList->appendSurface(surface);
2943+ m_surfaceList->addSurface(surface);
2944
2945 ++m_liveSurfaceCount;
2946 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
2947@@ -125,8 +154,19 @@
2948 setState(Running);
2949 }
2950 });
2951+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [&](bool /*value*/) {
2952+ #if APPLICATION_DEBUG
2953+ qDebug().nospace() << "Application[" << appId() << "].focusedChanged(" << focused() << ")";
2954+ #endif
2955+ Q_EMIT focusedChanged(focused());
2956+ });
2957+
2958 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
2959
2960+ if (wasFocused != focused()) {
2961+ Q_EMIT focusedChanged(focused());
2962+ }
2963+
2964 if (m_state == Starting) {
2965 if (m_requestedState == RequestedRunning) {
2966 setState(Running);
2967@@ -134,6 +174,9 @@
2968 setState(Suspended);
2969 }
2970 }
2971+
2972+ surfaceManager->notifySurfaceCreated(surface);
2973+ surfaceManager->activate(surface);
2974 }
2975
2976 void ApplicationInfo::setIconId(const QString &iconId)
2977@@ -218,7 +261,7 @@
2978 {
2979 m_fullscreen = value;
2980 if (m_surfaceList->rowCount() > 0) {
2981- m_surfaceList->get(0)->setState(Mir::FullscreenState);
2982+ m_surfaceList->get(0)->requestState(Mir::FullscreenState);
2983 }
2984 }
2985
2986@@ -349,26 +392,6 @@
2987 return someSurfaceHasFocus;
2988 }
2989
2990-void ApplicationInfo::setFocused(bool value)
2991-{
2992- if (focused() == value) {
2993- return;
2994- }
2995-
2996- if (value) {
2997- if (m_surfaceList->count() > 0) {
2998- m_surfaceList->get(0)->requestFocus();
2999- }
3000- } else {
3001- for (int i = 0; i < m_surfaceList->count(); ++i) {
3002- MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
3003- if (surface->focused()) {
3004- surface->setFocused(false);
3005- }
3006- }
3007- }
3008-}
3009-
3010 void ApplicationInfo::onSurfaceCountChanged()
3011 {
3012 if (m_surfaceList->count() == 0 && m_state == Running) {
3013@@ -381,6 +404,7 @@
3014 if (m_surfaceList->count() == 0) {
3015 Q_EMIT focusRequested();
3016 } else {
3017- m_surfaceList->get(0)->requestFocus();
3018+ auto surface = static_cast<MirSurface*>(m_surfaceList->get(0));
3019+ surface->requestFocus();
3020 }
3021 }
3022
3023=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
3024--- tests/mocks/Unity/Application/ApplicationInfo.h 2016-07-25 14:57:11 +0000
3025+++ tests/mocks/Unity/Application/ApplicationInfo.h 2016-11-30 19:24:53 +0000
3026@@ -51,6 +51,8 @@
3027 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
3028 ~ApplicationInfo();
3029
3030+ Q_INVOKABLE void createPromptSurface();
3031+
3032 RequestedState requestedState() const override;
3033 void setRequestedState(RequestedState) override;
3034
3035@@ -108,8 +110,6 @@
3036 MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
3037 int surfaceCount() const override { return m_surfaceList->count(); }
3038
3039- void setFocused(bool value);
3040-
3041 //////
3042 // internal mock stuff
3043 void close();
3044
3045=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
3046--- tests/mocks/Unity/Application/ApplicationManager.cpp 2016-08-23 12:04:11 +0000
3047+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2016-11-30 19:24:53 +0000
3048@@ -34,30 +34,24 @@
3049
3050 #if APPLICATIONMANAGER_DEBUG
3051 #define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
3052+#define XDEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << params
3053 #else
3054 #define DEBUG_MSG(params) ((void)0)
3055+#define XDEBUG_MSG(params) ((void)0)
3056 #endif
3057
3058 namespace unityapi = unity::shell::application;
3059
3060+
3061 ApplicationManager::ApplicationManager(QObject *parent)
3062 : ApplicationManagerInterface(parent)
3063 {
3064 DEBUG_MSG("");
3065+
3066+ ApplicationManagerNotifier::instance()->setApplicationManager(this);
3067+
3068 buildListOfAvailableApplications();
3069
3070- // polling to find out when the toplevel window has been created as there's
3071- // no signal telling us that
3072- connect(&m_windowCreatedTimer, &QTimer::timeout,
3073- this, &ApplicationManager::onWindowCreatedTimerTimeout);
3074- m_windowCreatedTimer.setSingleShot(false);
3075- m_windowCreatedTimer.start(200);
3076-
3077- Q_ASSERT(MirFocusController::instance());
3078- connect(MirFocusController::instance(), &MirFocusController::focusedSurfaceChanged,
3079- this, &ApplicationManager::updateFocusedApplication, Qt::QueuedConnection);
3080-
3081-
3082 // Emit signal to notify Upstart that Mir is ready to receive client connections
3083 // see http://upstart.ubuntu.com/cookbook/#expect-stop
3084 // We do this because some autopilot tests actually use this mock Unity.Application module,
3085@@ -69,19 +63,7 @@
3086
3087 ApplicationManager::~ApplicationManager()
3088 {
3089-}
3090-
3091-void ApplicationManager::onWindowCreatedTimerTimeout()
3092-{
3093- if (QGuiApplication::topLevelWindows().count() > 0) {
3094- m_windowCreatedTimer.stop();
3095- onWindowCreated();
3096- }
3097-}
3098-
3099-void ApplicationManager::onWindowCreated()
3100-{
3101- startApplication("unity8-dash");
3102+ ApplicationManagerNotifier::instance()->setApplicationManager(nullptr);
3103 }
3104
3105 int ApplicationManager::rowCount(const QModelIndex& parent) const {
3106@@ -132,6 +114,17 @@
3107 return nullptr;
3108 }
3109
3110+unityapi::ApplicationInfoInterface *ApplicationManager::findApplicationWithSurface(unityapi::MirSurfaceInterface* surface)
3111+{
3112+ for (ApplicationInfo *app : m_runningApplications) {
3113+ auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
3114+ if (surfaceList->contains(static_cast<MirSurface*>(surface))) {
3115+ return app;
3116+ }
3117+ }
3118+ return nullptr;
3119+}
3120+
3121 QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
3122 {
3123 for (int i = 0; i < m_runningApplications.size(); ++i) {
3124@@ -158,6 +151,11 @@
3125 QModelIndex appIndex = findIndex(application);
3126 if (!appIndex.isValid()) return;
3127 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
3128+ XDEBUG_MSG("focusedApplicationId = " << focusedApplicationId());
3129+ Q_EMIT focusedApplicationIdChanged();
3130+ if (application->focused()) {
3131+ raiseApp(application->appId());
3132+ }
3133 });
3134 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
3135 QModelIndex appIndex = findIndex(application);
3136@@ -181,15 +179,40 @@
3137
3138 void ApplicationManager::remove(ApplicationInfo *application) {
3139 int i = m_runningApplications.indexOf(application);
3140+ application->disconnect(this);
3141 if (i != -1) {
3142 DEBUG_MSG(application->appId());
3143+ Q_ASSERT(!m_modelBusy);
3144+ m_modelBusy = true;
3145 beginRemoveRows(QModelIndex(), i, i);
3146 m_runningApplications.removeAt(i);
3147 endRemoveRows();
3148+ m_modelBusy = false;
3149 Q_EMIT countChanged();
3150 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
3151- }
3152- application->disconnect(this);
3153+ DEBUG_MSG(application->appId() << " after: " << qPrintable(toString()));
3154+ }
3155+}
3156+
3157+void ApplicationManager::raiseApp(const QString &appId)
3158+{
3159+
3160+ int index = -1;
3161+ for (int i = 0; i < m_runningApplications.count() && index == -1; ++i) {
3162+ if (m_runningApplications[i]->appId() == appId) {
3163+ index = i;
3164+ }
3165+ }
3166+
3167+ if (index >= 0) {
3168+ if (m_modelBusy) {
3169+ DEBUG_MSG(appId << " - model busy. Try again later.");
3170+ QMetaObject::invokeMethod(this, "raiseApp", Qt::QueuedConnection, Q_ARG(QString, appId));
3171+ } else {
3172+ DEBUG_MSG(appId);
3173+ move(index, 0);
3174+ }
3175+ }
3176 }
3177
3178 void ApplicationManager::move(int from, int to) {
3179@@ -197,12 +220,15 @@
3180
3181 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
3182 QModelIndex parent;
3183+ Q_ASSERT(!m_modelBusy);
3184+ m_modelBusy = true;
3185 /* When moving an item down, the destination index needs to be incremented
3186 * by one, as explained in the documentation:
3187 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
3188 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
3189 m_runningApplications.move(from, to);
3190 endMoveRows();
3191+ m_modelBusy = false;
3192 }
3193 }
3194
3195@@ -224,8 +250,6 @@
3196 if (!application)
3197 return nullptr;
3198
3199- Q_EMIT application->focusRequested(); // we assume that an application that's starting up wants focus
3200-
3201 return application;
3202 }
3203
3204@@ -473,40 +497,6 @@
3205 return m_runningApplications.isEmpty();
3206 }
3207
3208-void ApplicationManager::updateFocusedApplication()
3209-{
3210- ApplicationInfo *focusedApplication = nullptr;
3211- ApplicationInfo *previouslyFocusedApplication = nullptr;
3212-
3213- auto controller = MirFocusController::instance();
3214- if (!controller) {
3215- return;
3216- }
3217-
3218- MirSurface *surface = static_cast<MirSurface*>(controller->focusedSurface());
3219- if (surface) {
3220- focusedApplication = findApplication(surface);
3221- }
3222-
3223- surface = static_cast<MirSurface*>(controller->previouslyFocusedSurface());
3224- if (surface) {
3225- previouslyFocusedApplication = findApplication(surface);
3226- }
3227-
3228- if (focusedApplication != previouslyFocusedApplication) {
3229- if (focusedApplication) {
3230- DEBUG_MSG("focused " << focusedApplication->appId());
3231- Q_EMIT focusedApplication->focusedChanged(true);
3232- this->move(this->m_runningApplications.indexOf(focusedApplication), 0);
3233- }
3234- if (previouslyFocusedApplication) {
3235- DEBUG_MSG("unfocused " << previouslyFocusedApplication->appId());
3236- Q_EMIT previouslyFocusedApplication->focusedChanged(false);
3237- }
3238- Q_EMIT focusedApplicationIdChanged();
3239- }
3240-}
3241-
3242 ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
3243 {
3244 for (ApplicationInfo *app : m_runningApplications) {
3245@@ -517,3 +507,42 @@
3246 }
3247 return nullptr;
3248 }
3249+
3250+QString ApplicationManager::toString()
3251+{
3252+ QString str;
3253+ for (int i = 0; i < m_runningApplications.count(); ++i) {
3254+ auto *application = m_runningApplications.at(i);
3255+
3256+ QString itemStr = QString("(index=%1,appId=%2)")
3257+ .arg(i)
3258+ .arg(application->appId());
3259+
3260+ if (i > 0) {
3261+ str.append(",");
3262+ }
3263+ str.append(itemStr);
3264+ }
3265+ return str;
3266+}
3267+
3268+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3269+// ApplicationManagerNotifier
3270+
3271+ApplicationManagerNotifier *ApplicationManagerNotifier::m_instance = nullptr;
3272+
3273+ApplicationManagerNotifier *ApplicationManagerNotifier::instance()
3274+{
3275+ if (!m_instance) {
3276+ m_instance = new ApplicationManagerNotifier;
3277+ }
3278+ return m_instance;
3279+}
3280+
3281+void ApplicationManagerNotifier::setApplicationManager(ApplicationManager *appMan)
3282+{
3283+ if (appMan != m_applicationManager) {
3284+ m_applicationManager = appMan;
3285+ Q_EMIT applicationManagerChanged(m_applicationManager);
3286+ }
3287+}
3288
3289=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
3290--- tests/mocks/Unity/Application/ApplicationManager.h 2016-04-27 15:01:10 +0000
3291+++ tests/mocks/Unity/Application/ApplicationManager.h 2016-11-30 19:24:53 +0000
3292@@ -50,8 +50,11 @@
3293 // QAbstractItemModel methods.
3294 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
3295 QVariant data(const QModelIndex& index, int role) const override;
3296+
3297+ // ApplicationManagerInterface methods
3298 Q_INVOKABLE ApplicationInfo *get(int index) const override;
3299 Q_INVOKABLE ApplicationInfo *findApplication(const QString &appId) const override;
3300+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override;
3301
3302 Q_INVOKABLE void move(int from, int to);
3303
3304@@ -76,18 +79,42 @@
3305 void availableApplicationsChanged(QStringList list);
3306
3307 private Q_SLOTS:
3308- void onWindowCreatedTimerTimeout();
3309- void updateFocusedApplication();
3310+ void raiseApp(const QString &appId);
3311
3312 private:
3313 bool add(ApplicationInfo *application);
3314 void remove(ApplicationInfo* application);
3315 void buildListOfAvailableApplications();
3316- void onWindowCreated();
3317+ QString toString();
3318 ApplicationInfo *findApplication(MirSurface* surface);
3319 QList<ApplicationInfo*> m_runningApplications;
3320 QList<ApplicationInfo*> m_availableApplications;
3321- QTimer m_windowCreatedTimer;
3322+ bool m_modelBusy{false};
3323+};
3324+
3325+/*
3326+ Lifecycle of the ApplicationManager instance belongs to the QML plugin.
3327+ So this guy here is used to notify other parts of the system when the plugin creates and destroys
3328+ the ApplicationManager.
3329+
3330+ Unlike ApplicationManager, we create ApplicationManagerNotifier whenever we want.
3331+ */
3332+class ApplicationManagerNotifier : public QObject {
3333+ Q_OBJECT
3334+public:
3335+ static ApplicationManagerNotifier *instance();
3336+
3337+ ApplicationManager *applicationManager() { return m_applicationManager; }
3338+
3339+Q_SIGNALS:
3340+ void applicationManagerChanged(ApplicationManager *applicationManager);
3341+
3342+private:
3343+ void setApplicationManager(ApplicationManager *);
3344+ static ApplicationManagerNotifier *m_instance;
3345+ ApplicationManager *m_applicationManager{nullptr};
3346+
3347+friend class ApplicationManager;
3348 };
3349
3350 Q_DECLARE_METATYPE(ApplicationManager*)
3351
3352=== modified file 'tests/mocks/Unity/Application/CMakeLists.txt'
3353--- tests/mocks/Unity/Application/CMakeLists.txt 2016-10-20 14:37:34 +0000
3354+++ tests/mocks/Unity/Application/CMakeLists.txt 2016-11-30 19:24:53 +0000
3355@@ -15,7 +15,7 @@
3356 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
3357 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h
3358 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
3359- ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirFocusControllerInterface.h
3360+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
3361 resources/surfaces.qrc
3362 )
3363
3364
3365=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
3366--- tests/mocks/Unity/Application/MirSurface.cpp 2016-09-26 12:25:19 +0000
3367+++ tests/mocks/Unity/Application/MirSurface.cpp 2016-11-30 19:24:53 +0000
3368@@ -19,14 +19,18 @@
3369 #include <QDebug>
3370 #include <QQmlEngine>
3371
3372+// local
3373+#include "SurfaceManager.h"
3374+
3375 #define MIRSURFACE_DEBUG 0
3376
3377 #if MIRSURFACE_DEBUG
3378 #define DEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << __func__ << " " << params
3379+#define XDEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << params
3380
3381 const char *stateToStr(Mir::State state)
3382 {
3383- switch(state) {
3384+ switch (state) {
3385 case Mir::UnknownState:
3386 return "unknown";
3387 case Mir::RestoredState:
3388@@ -36,26 +40,37 @@
3389 case Mir::MaximizedState:
3390 return "maximized";
3391 case Mir::VertMaximizedState:
3392- return "vert-maximized";
3393+ return "vertMaximized";
3394 case Mir::FullscreenState:
3395 return "fullscreen";
3396 case Mir::HorizMaximizedState:
3397- return "horiz-maximized";
3398+ return "horizMaximized";
3399+ case Mir::MaximizedLeftState:
3400+ return "maximizedLeft";
3401+ case Mir::MaximizedRightState:
3402+ return "maximizedRight";
3403+ case Mir::MaximizedTopLeftState:
3404+ return "maximizedTopLeft";
3405+ case Mir::MaximizedTopRightState:
3406+ return "maximizedTopRight";
3407+ case Mir::MaximizedBottomLeftState:
3408+ return "maximizedBottomLeft";
3409+ case Mir::MaximizedBottomRightState:
3410+ return "maximizedBottomRight";
3411 case Mir::HiddenState:
3412 return "hidden";
3413 default:
3414 return "???";
3415- };
3416+ }
3417 }
3418
3419 #else
3420 #define DEBUG_MSG(params) ((void)0)
3421+#define XDEBUG_MSG(params) ((void)0)
3422 #endif
3423
3424 using namespace unity::shell::application;
3425
3426-MirFocusController *MirFocusController::m_instance = nullptr;
3427-
3428 MirSurface::MirSurface(const QString& name,
3429 Mir::Type type,
3430 Mir::State state,
3431@@ -69,7 +84,7 @@
3432 , m_screenshotUrl(screenshot)
3433 , m_qmlFilePath(qmlFilePath)
3434 , m_live(true)
3435- , m_visible(true)
3436+ , m_focused(false)
3437 , m_activeFocus(false)
3438 , m_width(-1)
3439 , m_height(-1)
3440@@ -95,11 +110,8 @@
3441 {
3442 DEBUG_MSG("");
3443
3444- // controller instance might have been already destroyed by QQmlEngine destructor
3445- auto controller = MirFocusController::instance();
3446- if (controller && controller->focusedSurface() == this) {
3447- controller->clear();
3448- }
3449+ // Early warning, while MirSurface methods can still be accessed.
3450+ Q_EMIT destroyed(this);
3451 }
3452
3453 QString MirSurface::name() const
3454@@ -122,14 +134,31 @@
3455 return m_state;
3456 }
3457
3458+void MirSurface::requestState(Mir::State state)
3459+{
3460+ if (state == m_state) {
3461+ return;
3462+ }
3463+ DEBUG_MSG(stateToStr(state));
3464+ Q_EMIT stateRequested(state);
3465+}
3466+
3467 void MirSurface::setState(Mir::State state)
3468 {
3469- if (state == m_state)
3470+ if (state == m_state) {
3471 return;
3472-
3473+ }
3474 DEBUG_MSG(stateToStr(state));
3475+
3476+ bool oldVisible = visible();
3477+
3478 m_state = state;
3479 Q_EMIT stateChanged(state);
3480+
3481+ if (visible() != oldVisible) {
3482+ XDEBUG_MSG("visibleChanged("<<visible()<<")");
3483+ Q_EMIT visibleChanged(visible());
3484+ }
3485 }
3486
3487 bool MirSurface::live() const
3488@@ -139,7 +168,7 @@
3489
3490 bool MirSurface::visible() const
3491 {
3492- return m_visible;
3493+ return m_state != Mir::MinimizedState && m_state != Mir::HiddenState;
3494 }
3495
3496 void MirSurface::setLive(bool live)
3497@@ -231,31 +260,30 @@
3498 if (!m_live && m_views.count() == 0) {
3499 deleteLater();
3500 }
3501- updateVisibility();
3502+ updateExposure();
3503 }
3504
3505-void MirSurface::setViewVisibility(qintptr viewId, bool visible)
3506+void MirSurface::setViewExposure(qintptr viewId, bool visible)
3507 {
3508 if (!m_views.contains(viewId)) return;
3509
3510 m_views[viewId].visible = visible;
3511- updateVisibility();
3512+ updateExposure();
3513 }
3514
3515-void MirSurface::updateVisibility()
3516+void MirSurface::updateExposure()
3517 {
3518- bool newVisible = false;
3519+ bool newExposure = false;
3520 QHashIterator<qintptr, View> i(m_views);
3521 while (i.hasNext()) {
3522 i.next();
3523- newVisible |= i.value().visible;
3524+ newExposure |= i.value().visible;
3525 }
3526
3527- if (newVisible != visible()) {
3528-// qDebug().nospace() << "MirSurface[" << name() << "]::updateVisibility(" << newVisible << ")";
3529-
3530- m_visible = newVisible;
3531- Q_EMIT visibleChanged(m_visible);
3532+ if (newExposure != m_exposed) {
3533+ m_exposed = newExposure;
3534+ DEBUG_MSG(m_exposed);
3535+ Q_EMIT exposedChanged(m_exposed);
3536 updateInputBoundsAfterResize();
3537 }
3538 }
3539@@ -273,6 +301,10 @@
3540 m_activeFocus = value;
3541
3542 Q_EMIT activeFocusChanged(value);
3543+
3544+ if (m_activeFocus && !m_focused) {
3545+ requestFocus();
3546+ }
3547 }
3548
3549 int MirSurface::width() const
3550@@ -332,6 +364,7 @@
3551 }
3552
3553 if (changed) {
3554+ XDEBUG_MSG("sizeChanged(width="<<width<<", height="<<height<<")");
3555 Q_EMIT sizeChanged(QSize(width, height));
3556 }
3557
3558@@ -409,11 +442,6 @@
3559 }
3560 }
3561
3562-void MirSurface::raise()
3563-{
3564- Q_EMIT raiseRequested();
3565-}
3566-
3567 void MirSurface::close()
3568 {
3569 DEBUG_MSG("");
3570@@ -423,6 +451,12 @@
3571 }
3572 }
3573
3574+void MirSurface::activate()
3575+{
3576+ DEBUG_MSG("");
3577+ SurfaceManager::instance()->activate(this);
3578+}
3579+
3580 void MirSurface::requestFocus()
3581 {
3582 DEBUG_MSG("");
3583@@ -431,27 +465,18 @@
3584
3585 void MirSurface::setFocused(bool value)
3586 {
3587- DEBUG_MSG(value);
3588-
3589- auto controller = MirFocusController::instance();
3590- // controller instance might have been already destroyed by QQmlEngine destructor
3591- if (!controller) {
3592+ if (m_focused == value)
3593 return;
3594- }
3595-
3596- if (value) {
3597- controller->setFocusedSurface(this);
3598- } else if (controller->focusedSurface() == this) {
3599- controller->setFocusedSurface(nullptr);
3600- }
3601+
3602+ DEBUG_MSG("(" << value << ")");
3603+
3604+ m_focused = value;
3605+ Q_EMIT focusedChanged(value);
3606 }
3607
3608 bool MirSurface::focused() const
3609 {
3610- auto controller = MirFocusController::instance();
3611-
3612- // controller instance might have been already destroyed by QQmlEngine destructor
3613- return controller ? controller->focusedSurface() == this : false;
3614+ return m_focused;
3615 }
3616
3617 QRect MirSurface::inputBounds() const
3618@@ -463,58 +488,20 @@
3619 {
3620 if (boundsRect != m_inputBounds) {
3621 m_inputBounds = boundsRect;
3622+ DEBUG_MSG("(" << m_inputBounds << ")");
3623 Q_EMIT inputBoundsChanged(m_inputBounds);
3624 }
3625 }
3626-#if MIRSURFACE_DEBUG
3627-#undef DEBUG_MSG
3628-#define DEBUG_MSG(params) qDebug().nospace() << "MirFocusController::" << __func__ << " " << params
3629-#endif
3630-
3631-void MirFocusController::setFocusedSurface(MirSurfaceInterface *surface)
3632-{
3633- if (m_focusedSurface == surface) {
3634- return;
3635- }
3636- DEBUG_MSG("MirSurface[" << (void*)surface << "," << (surface?surface->name():"") << "]");
3637-
3638- m_previouslyFocusedSurface = m_focusedSurface;
3639- m_focusedSurface = surface;
3640-
3641- if (m_previouslyFocusedSurface != m_focusedSurface) {
3642- Q_EMIT focusedSurfaceChanged();
3643- }
3644-
3645- if (m_previouslyFocusedSurface) {
3646- Q_EMIT m_previouslyFocusedSurface->focusedChanged(false);
3647- }
3648-
3649- if (m_focusedSurface) {
3650- Q_EMIT m_focusedSurface->focusedChanged(true);
3651- m_focusedSurface->raise();
3652- }
3653-}
3654-
3655-MirFocusController* MirFocusController::instance()
3656-{
3657- return m_instance;
3658-}
3659-
3660-MirFocusController::MirFocusController()
3661-{
3662- DEBUG_MSG("");
3663- Q_ASSERT(m_instance == nullptr);
3664- m_instance = this;
3665-}
3666-
3667-MirFocusController::~MirFocusController()
3668-{
3669- Q_ASSERT(m_instance == this);
3670- m_instance = nullptr;
3671-}
3672-
3673-void MirFocusController::clear()
3674-{
3675- m_focusedSurface = m_previouslyFocusedSurface = nullptr;
3676- Q_EMIT focusedSurfaceChanged();
3677+
3678+void MirSurface::setRequestedPosition(const QPoint &value)
3679+{
3680+ if (value != m_requestedPosition) {
3681+ m_requestedPosition = value;
3682+ Q_EMIT requestedPositionChanged(value);
3683+
3684+ // fake-miral: always comply
3685+ m_position = m_requestedPosition;
3686+ XDEBUG_MSG("positionChanged("<<m_position<<")");
3687+ Q_EMIT positionChanged(m_position);
3688+ }
3689 }
3690
3691=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
3692--- tests/mocks/Unity/Application/MirSurface.h 2016-09-07 08:50:19 +0000
3693+++ tests/mocks/Unity/Application/MirSurface.h 2016-11-30 19:24:53 +0000
3694@@ -23,31 +23,12 @@
3695 #include <QHash>
3696
3697 // unity-api
3698-#include <unity/shell/application/MirFocusControllerInterface.h>
3699 #include <unity/shell/application/MirSurfaceInterface.h>
3700
3701 #include "MirSurfaceListModel.h"
3702
3703 class MirSurface;
3704
3705-class MirFocusController : public unity::shell::application::MirFocusControllerInterface
3706-{
3707- Q_OBJECT
3708-public:
3709- MirFocusController();
3710- virtual ~MirFocusController();
3711- static MirFocusController* instance();
3712-
3713- void setFocusedSurface(unity::shell::application::MirSurfaceInterface *surface) override;
3714- unity::shell::application::MirSurfaceInterface* focusedSurface() const override { return m_focusedSurface; }
3715- unity::shell::application::MirSurfaceInterface* previouslyFocusedSurface() { return m_previouslyFocusedSurface; }
3716- void clear();
3717-private:
3718- static MirFocusController *m_instance;
3719- unity::shell::application::MirSurfaceInterface* m_previouslyFocusedSurface{nullptr};
3720- unity::shell::application::MirSurfaceInterface* m_focusedSurface{nullptr};
3721-};
3722-
3723 class MirSurface : public unity::shell::application::MirSurfaceInterface
3724 {
3725 Q_OBJECT
3726@@ -58,6 +39,7 @@
3727 Q_PROPERTY(int height READ height NOTIFY heightChanged)
3728 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
3729 Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
3730+ Q_PROPERTY(bool exposed READ exposed NOTIFY exposedChanged)
3731
3732 public:
3733 MirSurface(const QString& name,
3734@@ -76,13 +58,14 @@
3735
3736 QString persistentId() const override;
3737
3738+ QPoint position() const override { return m_position; }
3739+
3740 QSize size() const override { return QSize(width(),height()); }
3741 void resize(int width, int height) override;
3742 void resize(const QSize &size) override { resize(size.width(), size.height()); }
3743
3744
3745 Mir::State state() const override;
3746- Q_INVOKABLE void setState(Mir::State) override;
3747
3748 bool live() const override;
3749
3750@@ -108,15 +91,16 @@
3751
3752 bool confinesMousePointer() const override { return false; }
3753
3754- Q_INVOKABLE void requestFocus() override;
3755+ QPoint requestedPosition() const override { return m_requestedPosition; }
3756+ void setRequestedPosition(const QPoint &) override;
3757
3758 Q_INVOKABLE void close() override;
3759-
3760- Q_INVOKABLE void raise() override;
3761+ Q_INVOKABLE void activate() override;
3762
3763 ////
3764 // API for tests
3765
3766+ Q_INVOKABLE void requestFocus();
3767 Q_INVOKABLE void setLive(bool live);
3768 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
3769
3770@@ -126,6 +110,8 @@
3771 bool isSlowToResize() const;
3772 void setSlowToResize(bool value);
3773
3774+ bool exposed() const { return m_exposed; }
3775+
3776 Q_INVOKABLE void setMinimumWidth(int);
3777 Q_INVOKABLE void setMaximumWidth(int);
3778 Q_INVOKABLE void setMinimumHeight(int);
3779@@ -148,24 +134,32 @@
3780
3781 void registerView(qintptr viewId);
3782 void unregisterView(qintptr viewId);
3783- void setViewVisibility(qintptr viewId, bool visible);
3784+ void setViewExposure(qintptr viewId, bool visible);
3785 int viewCount() const { return m_views.count(); }
3786
3787 void setFocused(bool value);
3788
3789+ void setState(Mir::State state);
3790+
3791+public Q_SLOTS:
3792+ ////
3793+ // unity.shell.application.MirSurface
3794+ void requestState(Mir::State) override;
3795+
3796 Q_SIGNALS:
3797 ////
3798 // API for tests
3799 void widthChanged();
3800 void heightChanged();
3801 void slowToResizeChanged();
3802+ void exposedChanged(bool exposed);
3803
3804 ////
3805 // internal mock stuff
3806 void screenshotUrlChanged(QUrl);
3807 void activeFocusChanged(bool);
3808- void raiseRequested();
3809 void closeRequested();
3810+ void stateRequested(Mir::State);
3811
3812 protected:
3813 virtual void updateInputBoundsAfterResize();
3814@@ -175,7 +169,7 @@
3815
3816 private:
3817 void doResize(int width, int height);
3818- void updateVisibility();
3819+ void updateExposure();
3820
3821 const QString m_name;
3822 const Mir::Type m_type;
3823@@ -185,7 +179,7 @@
3824 QUrl m_screenshotUrl;
3825 QUrl m_qmlFilePath;
3826 bool m_live;
3827- bool m_visible;
3828+ bool m_focused;
3829 bool m_activeFocus;
3830 int m_width;
3831 int m_height;
3832@@ -210,10 +204,14 @@
3833 bool visible;
3834 };
3835 QHash<qintptr, View> m_views;
3836+ bool m_exposed{false};
3837
3838 QTimer m_zombieTimer;
3839
3840 QRect m_inputBounds;
3841+
3842+ QPoint m_position;
3843+ QPoint m_requestedPosition;
3844 };
3845
3846 #endif // MOCK_MIR_SURFACE_H
3847
3848=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
3849--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-08-23 11:11:32 +0000
3850+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-11-30 19:24:53 +0000
3851@@ -59,7 +59,7 @@
3852 Qt::ExtraButton12 | Qt::ExtraButton13);
3853
3854 connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
3855- connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility);
3856+ connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceExposure);
3857
3858 connect(this, &MirSurfaceItem::consumesInputChanged, this, [this]() {
3859 updateMirSurfaceActiveFocus(hasActiveFocus());
3860@@ -278,7 +278,7 @@
3861 m_qmlSurface->registerView((qintptr)this);
3862
3863 updateSurfaceSize();
3864- updateMirSurfaceVisibility();
3865+ updateMirSurfaceExposure();
3866
3867 if (m_orientationAngle) {
3868 m_qmlSurface->setOrientationAngle(*m_orientationAngle);
3869@@ -335,11 +335,11 @@
3870 }
3871 }
3872
3873-void MirSurfaceItem::updateMirSurfaceVisibility()
3874+void MirSurfaceItem::updateMirSurfaceExposure()
3875 {
3876 if (!m_qmlSurface) return;
3877
3878- m_qmlSurface->setViewVisibility((qintptr)this, isVisible());
3879+ m_qmlSurface->setViewExposure((qintptr)this, isVisible());
3880 }
3881
3882 void MirSurfaceItem::setConsumesInput(bool value)
3883
3884=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
3885--- tests/mocks/Unity/Application/MirSurfaceItem.h 2016-06-22 13:53:00 +0000
3886+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2016-11-30 19:24:53 +0000
3887@@ -50,7 +50,6 @@
3888 Mir::ShellChrome shellChrome() const override;
3889
3890 Mir::State surfaceState() const override;
3891- void setSurfaceState(Mir::State) override {}
3892
3893 Mir::OrientationAngle orientationAngle() const override;
3894 void setOrientationAngle(Mir::OrientationAngle angle) override;
3895@@ -102,7 +101,7 @@
3896 private Q_SLOTS:
3897 void onComponentStatusChanged(QQmlComponent::Status status);
3898 void updateScreenshot(QUrl screenshot);
3899- void updateMirSurfaceVisibility();
3900+ void updateMirSurfaceExposure();
3901 void updateMirSurfaceActiveFocus(bool focused);
3902
3903 private:
3904
3905=== modified file 'tests/mocks/Unity/Application/MirSurfaceListModel.cpp'
3906--- tests/mocks/Unity/Application/MirSurfaceListModel.cpp 2016-09-23 13:38:46 +0000
3907+++ tests/mocks/Unity/Application/MirSurfaceListModel.cpp 2016-11-30 19:24:53 +0000
3908@@ -21,8 +21,9 @@
3909
3910 #define MIRSURFACELISTMODEL_DEBUG 0
3911
3912-#ifdef MIRSURFACELISTMODEL_DEBUG
3913-#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << " " << params
3914+#if MIRSURFACELISTMODEL_DEBUG
3915+#include <QDebug>
3916+#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << params
3917 #else
3918 #define DEBUG_MSG(params) ((void)0)
3919 #endif
3920@@ -54,26 +55,16 @@
3921
3922 void MirSurfaceListModel::raise(MirSurface *surface)
3923 {
3924+ DEBUG_MSG("(" << surface << ")");
3925 int i = m_surfaceList.indexOf(surface);
3926 if (i != -1) {
3927 moveSurface(i, 0);
3928 }
3929 }
3930
3931-void MirSurfaceListModel::appendSurface(MirSurface *surface)
3932-{
3933- beginInsertRows(QModelIndex(), m_surfaceList.size(), m_surfaceList.size());
3934- m_surfaceList.append(surface);
3935- connectSurface(surface);
3936- endInsertRows();
3937- Q_EMIT countChanged(m_surfaceList.count());
3938- if (m_surfaceList.count() == 1) {
3939- Q_EMIT firstChanged();
3940- }
3941-}
3942-
3943-void MirSurfaceListModel::prependSurface(MirSurface *surface)
3944-{
3945+void MirSurfaceListModel::addSurface(MirSurface *surface)
3946+{
3947+ DEBUG_MSG("(" << surface << ")");
3948 beginInsertRows(QModelIndex(), 0, 0);
3949 m_surfaceList.prepend(surface);
3950 connectSurface(surface);
3951@@ -85,6 +76,11 @@
3952 void MirSurfaceListModel::connectSurface(MirSurface *surface)
3953 {
3954 connect(surface, &QObject::destroyed, this, [this, surface](){ this->removeSurface(surface); });
3955+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool surfaceFocused){
3956+ if (surfaceFocused) {
3957+ raise(surface);
3958+ }
3959+ });
3960 }
3961
3962 void MirSurfaceListModel::removeSurface(MirSurface *surface)
3963@@ -138,21 +134,3 @@
3964 return nullptr;
3965 }
3966 }
3967-
3968-MirSurfaceInterface *MirSurfaceListModel::createSurface()
3969-{
3970- QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
3971- int i = rand() % screenshotIds.count();
3972-
3973- QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
3974- .arg(screenshotIds[i]);
3975-
3976- auto surface = new MirSurface(QString("prompt foo"),
3977- Mir::NormalType,
3978- Mir::RestoredState,
3979- screenshotUrl);
3980-
3981- prependSurface(surface);
3982-
3983- return surface;
3984-}
3985
3986=== modified file 'tests/mocks/Unity/Application/MirSurfaceListModel.h'
3987--- tests/mocks/Unity/Application/MirSurfaceListModel.h 2016-06-02 12:02:35 +0000
3988+++ tests/mocks/Unity/Application/MirSurfaceListModel.h 2016-11-30 19:24:53 +0000
3989@@ -38,18 +38,13 @@
3990 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
3991 QVariant data(const QModelIndex& index, int role) const override;
3992
3993- void appendSurface(MirSurface *surface);
3994+ void addSurface(MirSurface *surface);
3995 void removeSurface(MirSurface *surface);
3996
3997 bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
3998
3999- ////
4000- // API for tests
4001-
4002- Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
4003-
4004 private:
4005- void prependSurface(MirSurface *surface);
4006+ void appendSurface(MirSurface *surface);
4007 void raise(MirSurface *surface);
4008 void moveSurface(int from, int to);
4009 void connectSurface(MirSurface *surface);
4010
4011=== modified file 'tests/mocks/Unity/Application/SurfaceManager.cpp'
4012--- tests/mocks/Unity/Application/SurfaceManager.cpp 2016-04-27 15:01:10 +0000
4013+++ tests/mocks/Unity/Application/SurfaceManager.cpp 2016-11-30 19:24:53 +0000
4014@@ -21,33 +21,41 @@
4015
4016 #include <paths.h>
4017
4018-SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
4019+#define SURFACEMANAGER_DEBUG 0
4020+
4021+#if SURFACEMANAGER_DEBUG
4022+#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
4023+#else
4024+#define DEBUG_MSG(params) ((void)0)
4025+#endif
4026+
4027+namespace unityapi = unity::shell::application;
4028+
4029+SurfaceManager *SurfaceManager::m_instance = nullptr;
4030
4031 SurfaceManager *SurfaceManager::instance()
4032 {
4033- return the_surface_manager;
4034+ return m_instance;
4035 }
4036
4037-SurfaceManager::SurfaceManager(QObject *parent) :
4038- QObject(parent)
4039- , m_virtualKeyboard(nullptr)
4040+SurfaceManager::SurfaceManager(QObject *)
4041 {
4042- Q_ASSERT(the_surface_manager == nullptr);
4043- the_surface_manager = this;
4044+ DEBUG_MSG("");
4045
4046- m_virtualKeyboard = new VirtualKeyboard;
4047- connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
4048- MirSurface* surface = qobject_cast<MirSurface*>(obj);
4049- m_virtualKeyboard = nullptr;
4050- Q_EMIT inputMethodSurfaceChanged();
4051- Q_EMIT surfaceDestroyed(surface);
4052- });
4053+ Q_ASSERT(m_instance == nullptr);
4054+ m_instance = this;
4055 }
4056
4057 SurfaceManager::~SurfaceManager()
4058 {
4059- Q_ASSERT(the_surface_manager == this);
4060- the_surface_manager = nullptr;
4061+ DEBUG_MSG("");
4062+
4063+ if (m_virtualKeyboard) {
4064+ m_virtualKeyboard->setLive(false);
4065+ }
4066+
4067+ Q_ASSERT(m_instance == this);
4068+ m_instance = nullptr;
4069 }
4070
4071 MirSurface *SurfaceManager::createSurface(const QString& name,
4072@@ -56,10 +64,13 @@
4073 const QUrl& screenshot)
4074 {
4075 MirSurface* surface = new MirSurface(name, type, state, screenshot);
4076- connect(surface, &QObject::destroyed, this, [this](QObject *obj) {
4077- MirSurface* surface = qobject_cast<MirSurface*>(obj);
4078- Q_EMIT surfaceDestroyed(surface);
4079- });
4080+ registerSurface(surface);
4081+ return surface;
4082+}
4083+
4084+void SurfaceManager::registerSurface(MirSurface *surface)
4085+{
4086+ m_surfaces.prepend(surface);
4087
4088 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
4089 surface->setMaximumWidth(m_newSurfaceMaximumWidth);
4090@@ -68,13 +79,18 @@
4091 surface->setWidthIncrement(m_newSurfaceWidthIncrement);
4092 surface->setHeightIncrement(m_newSurfaceHeightIncrement);
4093
4094+ connect(surface, &MirSurface::stateRequested, this, [=](Mir::State state) {
4095+ this->onStateRequested(surface, state);
4096+ });
4097+
4098+ connect(surface, &QObject::destroyed, this, [=]() {
4099+ this->onSurfaceDestroyed(surface);
4100+ });
4101+}
4102+
4103+void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
4104+{
4105 Q_EMIT surfaceCreated(surface);
4106- return surface;
4107-}
4108-
4109-MirSurface *SurfaceManager::inputMethodSurface() const
4110-{
4111- return m_virtualKeyboard;
4112 }
4113
4114 void SurfaceManager::setNewSurfaceMinimumWidth(int value)
4115@@ -124,3 +140,130 @@
4116 Q_EMIT newSurfaceHeightIncrementChanged(m_newSurfaceHeightIncrement);
4117 }
4118 }
4119+
4120+void SurfaceManager::raise(unityapi::MirSurfaceInterface *surface)
4121+{
4122+ if (m_underModification)
4123+ return;
4124+
4125+ DEBUG_MSG("("<<surface<<") started");
4126+ Q_EMIT modificationsStarted();
4127+ m_underModification = true;
4128+
4129+ doRaise(surface);
4130+
4131+ m_underModification = false;
4132+ Q_EMIT modificationsEnded();
4133+ DEBUG_MSG("("<<surface<<") ended");
4134+}
4135+
4136+void SurfaceManager::doRaise(unityapi::MirSurfaceInterface *apiSurface)
4137+{
4138+ auto surface = static_cast<MirSurface*>(apiSurface);
4139+ int index = m_surfaces.indexOf(surface);
4140+ Q_ASSERT(index != -1);
4141+ m_surfaces.move(index, 0);
4142+
4143+ QVector<MirSurfaceInterface*> surfaces;
4144+ surfaces.append(surface);
4145+ Q_EMIT surfacesRaised(surfaces);
4146+}
4147+
4148+void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
4149+{
4150+ auto surface = static_cast<MirSurface*>(apiSurface);
4151+
4152+ if (surface == m_focusedSurface) {
4153+ return;
4154+ }
4155+
4156+ Q_ASSERT(!m_underModification);
4157+
4158+ DEBUG_MSG("("<<surface<<") started");
4159+ Q_EMIT modificationsStarted();
4160+ m_underModification = true;
4161+ if (m_focusedSurface) {
4162+ m_focusedSurface->setFocused(false);
4163+ }
4164+ if (surface) {
4165+ if (surface->state() == Mir::HiddenState || surface->state() == Mir::MinimizedState) {
4166+ surface->setState(Mir::RestoredState);
4167+ }
4168+ surface->setFocused(true);
4169+ doRaise(surface);
4170+ }
4171+ m_focusedSurface = surface;
4172+ m_underModification = false;
4173+ Q_EMIT modificationsEnded();
4174+ DEBUG_MSG("("<<surface<<") ended");
4175+}
4176+
4177+void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
4178+{
4179+ Q_ASSERT(!m_underModification);
4180+
4181+ DEBUG_MSG("("<<surface<<","<<state<<") started");
4182+ Q_EMIT modificationsStarted();
4183+ m_underModification = true;
4184+
4185+ surface->setState(state);
4186+
4187+ if ((state == Mir::MinimizedState || state == Mir::HiddenState) && surface->focused()) {
4188+ Q_ASSERT(m_focusedSurface == surface);
4189+ surface->setFocused(false);
4190+ m_focusedSurface = nullptr;
4191+ focusFirstAvailableSurface();
4192+ }
4193+
4194+ m_underModification = false;
4195+ Q_EMIT modificationsEnded();
4196+ DEBUG_MSG("("<<surface<<","<<state<<") ended");
4197+}
4198+
4199+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
4200+{
4201+ m_surfaces.removeAll(surface);
4202+ if (m_focusedSurface == surface) {
4203+ m_focusedSurface = nullptr;
4204+
4205+ Q_EMIT modificationsStarted();
4206+ m_underModification = true;
4207+
4208+ focusFirstAvailableSurface();
4209+
4210+ m_underModification = false;
4211+ Q_EMIT modificationsEnded();
4212+ }
4213+}
4214+
4215+void SurfaceManager::focusFirstAvailableSurface()
4216+{
4217+ MirSurface *chosenSurface = nullptr;
4218+ for (int i = 0; i < m_surfaces.count() && !chosenSurface; ++i) {
4219+ auto *surface = m_surfaces[i];
4220+ if (surface->state() != Mir::HiddenState && surface->state() != Mir::MinimizedState) {
4221+ chosenSurface = surface;
4222+ }
4223+ }
4224+
4225+ if (!chosenSurface) {
4226+ return;
4227+ }
4228+
4229+ if (m_focusedSurface) {
4230+ m_focusedSurface->setFocused(false);
4231+ }
4232+ if (chosenSurface) {
4233+ chosenSurface->setFocused(true);
4234+ }
4235+ m_focusedSurface = chosenSurface;
4236+}
4237+
4238+void SurfaceManager::createInputMethodSurface()
4239+{
4240+ if (!m_virtualKeyboard) {
4241+ m_virtualKeyboard = new VirtualKeyboard;
4242+ registerSurface(m_virtualKeyboard);
4243+ Q_EMIT surfaceCreated(m_virtualKeyboard);
4244+ }
4245+}
4246
4247=== modified file 'tests/mocks/Unity/Application/SurfaceManager.h'
4248--- tests/mocks/Unity/Application/SurfaceManager.h 2016-04-13 18:33:15 +0000
4249+++ tests/mocks/Unity/Application/SurfaceManager.h 2016-11-30 19:24:53 +0000
4250@@ -19,15 +19,16 @@
4251
4252 #include <QObject>
4253
4254+#include <unity/shell/application/SurfaceManagerInterface.h>
4255+
4256 #include "MirSurface.h"
4257 #include "VirtualKeyboard.h"
4258
4259 class ApplicationInfo;
4260
4261-class SurfaceManager : public QObject
4262+class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
4263 {
4264 Q_OBJECT
4265- Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
4266 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
4267 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
4268 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
4269@@ -41,12 +42,17 @@
4270
4271 static SurfaceManager *instance();
4272
4273+ // SurfaceManagerInterface
4274+ void raise(unity::shell::application::MirSurfaceInterface *surface) override;
4275+ void activate(unity::shell::application::MirSurfaceInterface *surface) override;
4276+
4277 Q_INVOKABLE MirSurface* createSurface(const QString& name,
4278 Mir::Type type,
4279 Mir::State state,
4280 const QUrl& screenshot);
4281
4282- MirSurface* inputMethodSurface() const;
4283+
4284+ void notifySurfaceCreated(unity::shell::application::MirSurfaceInterface *);
4285
4286 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
4287 void setNewSurfaceMinimumWidth(int value);
4288@@ -66,12 +72,10 @@
4289 int newSurfaceHeightIncrement() const { return m_newSurfaceHeightIncrement; }
4290 void setNewSurfaceHeightIncrement(int);
4291
4292+public Q_SLOTS:
4293+ void createInputMethodSurface();
4294+
4295 Q_SIGNALS:
4296- void inputMethodSurfaceChanged();
4297- void countChanged();
4298- void surfaceCreated(MirSurface *surface);
4299- void surfaceDestroyed(MirSurface*surface);
4300-
4301 void newSurfaceMinimumWidthChanged(int value);
4302 void newSurfaceMaximumWidthChanged(int value);
4303 void newSurfaceMinimumHeightChanged(int value);
4304@@ -79,9 +83,16 @@
4305 void newSurfaceWidthIncrementChanged(int value);
4306 void newSurfaceHeightIncrementChanged(int value);
4307
4308+private Q_SLOTS:
4309+ void onStateRequested(MirSurface *surface, Mir::State state);
4310+ void onSurfaceDestroyed(MirSurface *surface);
4311+
4312 private:
4313- static SurfaceManager *the_surface_manager;
4314- VirtualKeyboard *m_virtualKeyboard;
4315+ void doRaise(unity::shell::application::MirSurfaceInterface *surface);
4316+ void focusFirstAvailableSurface();
4317+ void registerSurface(MirSurface *surface);
4318+
4319+ static SurfaceManager *m_instance;
4320
4321 int m_newSurfaceMinimumWidth{0};
4322 int m_newSurfaceMaximumWidth{0};
4323@@ -89,6 +100,13 @@
4324 int m_newSurfaceMaximumHeight{0};
4325 int m_newSurfaceWidthIncrement{1};
4326 int m_newSurfaceHeightIncrement{1};
4327+
4328+ MirSurface *m_focusedSurface{nullptr};
4329+ bool m_underModification{false};
4330+
4331+ QList<MirSurface*> m_surfaces;
4332+
4333+ VirtualKeyboard *m_virtualKeyboard{nullptr};
4334 };
4335
4336 #endif // SURFACEMANAGER_H
4337
4338=== modified file 'tests/mocks/Unity/Application/plugin.cpp'
4339--- tests/mocks/Unity/Application/plugin.cpp 2016-10-21 08:25:18 +0000
4340+++ tests/mocks/Unity/Application/plugin.cpp 2016-11-30 19:24:53 +0000
4341@@ -29,24 +29,8 @@
4342
4343 namespace {
4344
4345-// Creates the singletons that are called throughout C++ code
4346-void createUnityApplicationSharedSingletons()
4347-{
4348- // they have to be created in a specific order
4349- if (!MirFocusController::instance()) {
4350- new MirFocusController;
4351- }
4352- if (!SurfaceManager::instance()) {
4353- new SurfaceManager;
4354- }
4355- if (!MirMock::instance()) {
4356- new MirMock;
4357- }
4358-}
4359-
4360 QObject* applicationManagerSingleton(QQmlEngine*, QJSEngine*)
4361 {
4362- createUnityApplicationSharedSingletons();
4363 return new ApplicationManager;
4364 }
4365
4366@@ -55,19 +39,6 @@
4367 createUnityApplicationSharedSingletons();
4368 return MirMock::instance();
4369 }
4370-
4371-QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
4372-{
4373- createUnityApplicationSharedSingletons();
4374- return SurfaceManager::instance();
4375-}
4376-
4377-QObject* mirFocusControllerSingleton(QQmlEngine*, QJSEngine*)
4378-{
4379- createUnityApplicationSharedSingletons();
4380- return MirFocusController::instance();
4381-}
4382-
4383 } // anonymous namespace
4384
4385 void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
4386@@ -75,6 +46,7 @@
4387 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
4388 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
4389 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
4390+ qRegisterMetaType<unity::shell::application::SurfaceManagerInterface*>("unity::shell::application::SurfaceManagerInterface*");
4391 qRegisterMetaType<Mir::Type>("Mir::Type");
4392 qRegisterMetaType<Mir::State>("Mir::State");
4393
4394@@ -83,13 +55,12 @@
4395 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
4396 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
4397 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
4398- qmlRegisterSingletonType<MirFocusController>(uri, 0, 1, "MirFocusController", mirFocusControllerSingleton);
4399 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
4400 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
4401
4402 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
4403 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
4404- qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
4405+ qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
4406 }
4407
4408 void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
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-30 19:24:53 +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-30 19:24:53 +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_ApplicationWindow.qml'
4446--- tests/qmltests/Stage/tst_ApplicationWindow.qml 2016-09-14 09:14:46 +0000
4447+++ tests/qmltests/Stage/tst_ApplicationWindow.qml 2016-11-30 19:24:53 +0000
4448@@ -37,6 +37,8 @@
4449 }
4450 property QtObject fakeApplication: null
4451
4452+ SurfaceManager{}
4453+
4454 Loader {
4455 id: applicationWindowLoader
4456 focus: true
4457@@ -53,6 +55,8 @@
4458 surfaceOrientationAngle: 0
4459 interactive: true
4460 focus: true
4461+ requestedWidth: width
4462+ requestedHeight: height
4463 Component.onDestruction: {
4464 applicationWindowLoader.itemDestroyed = true;
4465 }
4466@@ -103,17 +107,17 @@
4467 RowLayout {
4468 property var promptSurfaceList: root.fakeApplication ? root.fakeApplication.promptSurfaceList : null
4469 Button {
4470- enabled: parent.promptSurfaceList !== null && parent.promptSurfaceList.count > 0
4471+ enabled: root.fakeApplication && root.fakeApplication.promptSurfaceList.count > 0
4472 activeFocusOnPress: false
4473 text: "Remove"
4474- onClicked: { parent.promptSurfaceList.get(0).close(); }
4475+ onClicked: { root.fakeApplication.promptSurfaceList.get(0).close(); }
4476 }
4477
4478 Button {
4479- enabled: parent.promptSurfaceList !== null
4480+ enabled: root.fakeApplication
4481 activeFocusOnPress: false
4482 text: "Add Prompt Surface"
4483- onClicked: { parent.promptSurfaceList.createSurface(); }
4484+ onClicked: { root.fakeApplication.createPromptSurface(); }
4485 }
4486 }
4487
4488@@ -150,26 +154,6 @@
4489 }
4490 }
4491 }
4492-
4493- Button {
4494- anchors { left: parent.left; right: parent.right }
4495- activeFocusOnPress: false
4496- text: "Rotate device \u27F3"
4497- onClicked: {
4498- var orientation = applicationWindowLoader.item.orientation
4499- if (orientation == Qt.PortraitOrientation) {
4500- orientation = Qt.LandscapeOrientation;
4501- } else if (orientation == Qt.LandscapeOrientation) {
4502- orientation = Qt.InvertedPortraitOrientation;
4503- } else if (orientation == Qt.InvertedPortraitOrientation) {
4504- orientation = Qt.InvertedLandscapeOrientation;
4505- } else {
4506- orientation = Qt.PortraitOrientation;
4507- }
4508- applicationWindowLoader.item.orientation = orientation;
4509- }
4510- }
4511-
4512 }
4513 }
4514
4515@@ -445,7 +429,7 @@
4516 var i;
4517 // 3 surfaces should cover all edge cases
4518 for (i = 0; i < 3; i++) {
4519- promptSurfaceList.createSurface();
4520+ root.fakeApplication.createPromptSurface();
4521 compare(promptSurfaces.count, i+1);
4522 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
4523 }
4524@@ -470,9 +454,7 @@
4525 }
4526
4527 function test_promptSurfaceAdjustsForParentSize() {
4528- var promptSurfaceList = root.fakeApplication.promptSurfaceList;
4529-
4530- promptSurfaceList.createSurface();
4531+ root.fakeApplication.createPromptSurface();
4532
4533 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
4534
4535@@ -505,10 +487,10 @@
4536 var promptSurfaceList = root.fakeApplication.promptSurfaceList;
4537 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
4538
4539- promptSurfaceList.createSurface();
4540+ root.fakeApplication.createPromptSurface();
4541
4542 for (var i = 2; i <= 3; i++) {
4543- promptSurfaceList.createSurface();
4544+ root.fakeApplication.createPromptSurface();
4545 tryCompare(promptSurfaces, "count", i);
4546 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
4547
4548
4549=== modified file 'tests/qmltests/Stage/tst_DesktopStage.qml'
4550--- tests/qmltests/Stage/tst_DesktopStage.qml 2016-11-29 09:38:13 +0000
4551+++ tests/qmltests/Stage/tst_DesktopStage.qml 2016-11-30 19:24:53 +0000
4552@@ -20,8 +20,8 @@
4553 import Ubuntu.Components.ListItems 1.3
4554 import Unity.Application 0.1
4555 import Unity.Test 0.1
4556-import WindowManager 0.1
4557 import Utils 0.1
4558+import WindowManager 1.0
4559
4560 import ".." // For EdgeBarrierControls
4561 import "../../../qml/Stage"
4562@@ -57,9 +57,11 @@
4563 }
4564 }
4565
4566- TopLevelSurfaceList {
4567+ SurfaceManager { id: sMgr }
4568+ TopLevelWindowModel {
4569 id: topSurfaceList
4570- applicationsModel: ApplicationManager
4571+ applicationManager: ApplicationManager
4572+ surfaceManager: sMgr
4573 }
4574
4575 Loader {
4576@@ -80,6 +82,7 @@
4577
4578 Component.onCompleted: {
4579 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
4580+ ApplicationManager.startApplication("unity8-dash");
4581 }
4582 Component.onDestruction: {
4583 stageLoader.itemDestroyed = true;
4584@@ -170,8 +173,9 @@
4585 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
4586
4587 tryCompare(topSurfaceList, "count", 1);
4588- tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) != null;}, true);
4589- compare(MirFocusController.focusedSurface, topSurfaceList.surfaceAt(0));
4590+ tryCompareFunction(function(){return topSurfaceList.windowAt(0) != null;}, true);
4591+ topSurfaceList.windowAt(0).activate();
4592+ tryCompare(topSurfaceList, "focusedWindow", topSurfaceList.windowAt(0));
4593 }
4594
4595 function cleanup() {
4596@@ -284,13 +288,13 @@
4597 verify(fromAppWindow);
4598 tap(fromAppWindow);
4599 compare(fromDelegate.surface.activeFocus, true);
4600- compare(MirFocusController.focusedSurface, fromDelegate.surface);
4601+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
4602
4603 var toAppWindow = findChild(toDelegate, "appWindow");
4604 verify(toAppWindow);
4605 tap(toAppWindow);
4606 compare(toDelegate.surface.activeFocus, true);
4607- compare(MirFocusController.focusedSurface, toDelegate.surface);
4608+ compare(topSurfaceList.focusedWindow, toDelegate.window);
4609 }
4610
4611 function test_clickingOnWindowChangesFocusedApp_data() {
4612@@ -309,13 +313,13 @@
4613 verify(fromAppWindow);
4614 mouseClick(fromAppWindow);
4615 compare(fromDelegate.surface.activeFocus, true);
4616- compare(MirFocusController.focusedSurface, fromDelegate.surface);
4617+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
4618
4619 var toAppWindow = findChild(toDelegate, "appWindow");
4620 verify(toAppWindow);
4621 mouseClick(toAppWindow);
4622 compare(toDelegate.surface.activeFocus, true);
4623- compare(MirFocusController.focusedSurface, toDelegate.surface);
4624+ compare(topSurfaceList.focusedWindow, toDelegate.window);
4625 }
4626
4627 function test_tappingOnDecorationFocusesApplication_data() {
4628@@ -343,6 +347,13 @@
4629 return findChild(appDelegate, "appWindowDecoration");
4630 }
4631
4632+ function maximizeDelegate(appDelegate) {
4633+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
4634+ verify(maximizeButton);
4635+ mouseClick(maximizeButton);
4636+ tryCompare(appDelegate, "visuallyMaximized", true);
4637+ }
4638+
4639 function test_tappingOnDecorationFocusesApplication(data) {
4640 var appDelegates = [];
4641 for (var i = 0; i < data.apps.length; i++) {
4642@@ -398,7 +409,7 @@
4643 startApplication("camera-app");
4644
4645 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4646- dialerDelegate.surface.requestFocus();
4647+ dialerDelegate.surface.activate();
4648 tryCompare(dialerDelegate, "focus", true);
4649
4650 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
4651@@ -411,7 +422,7 @@
4652 startApplication("camera-app");
4653
4654 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4655- dialerDelegate.surface.requestFocus();
4656+ dialerDelegate.surface.activate();
4657 tryCompare(dialerDelegate, "focus", true);
4658
4659 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
4660@@ -426,7 +437,7 @@
4661 startApplication("camera-app");
4662
4663 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4664- dialerDelegate.surface.requestFocus();
4665+ dialerDelegate.surface.activate();
4666 tryCompare(dialerDelegate, "focus", true);
4667
4668 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
4669@@ -441,7 +452,7 @@
4670 startApplication("camera-app");
4671
4672 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
4673- dialerDelegate.surface.requestFocus();
4674+ dialerDelegate.surface.activate();
4675 tryCompare(dialerDelegate, "focus", true);
4676
4677 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
4678@@ -512,11 +523,11 @@
4679 apps.forEach(startApplication);
4680 verify(topSurfaceList.count == 3);
4681 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
4682- tryCompare(MirFocusController, "focusedSurface", null); // verify no surface is focused
4683+ tryCompare(topSurfaceList, "focusedWindow", null); // verify no window is focused
4684
4685 // now try pressing all 4 arrow keys + ctrl + meta
4686 keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
4687- tryCompare(MirFocusController, "focusedSurface", null); // verify still no surface is focused
4688+ tryCompare(topSurfaceList, "focusedWindow", null); // verify still no window is focused
4689 }
4690
4691 function test_minimizeApplicationHidesSurface() {
4692@@ -527,9 +538,12 @@
4693 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
4694 verify(decoratedWindow);
4695
4696- tryCompare(dashSurface, "visible", true);
4697- decoratedWindow.minimizeClicked();
4698- tryCompare(dashSurface, "visible", false);
4699+ var minimizeButton = findChild(decoratedWindow, "minimizeWindowButton");
4700+ verify(minimizeButton);
4701+
4702+ tryCompare(dashSurface, "exposed", true);
4703+ mouseClick(minimizeButton);
4704+ tryCompare(dashSurface, "exposed", false);
4705 }
4706
4707 function test_maximizeApplicationHidesSurfacesBehindIt() {
4708@@ -538,16 +552,16 @@
4709 var gmailDelegate = startApplication("gmail-webapp");
4710
4711 // maximize without raising
4712- dialerDelegate.maximize();
4713+ dialerDelegate.requestMaximize();
4714 tryCompare(dialerDelegate, "visuallyMaximized", true);
4715
4716- tryCompare(dashDelegate.surface, "visible", false);
4717- compare(gmailDelegate.surface.visible, true);
4718+ tryCompare(dashDelegate.surface, "exposed", false);
4719+ compare(gmailDelegate.surface.exposed, true);
4720
4721 // restore without raising
4722- dialerDelegate.restoreFromMaximized();
4723- compare(dashDelegate.surface.visible, true);
4724- compare(gmailDelegate.surface.visible, true);
4725+ dialerDelegate.requestRestore();
4726+ compare(dashDelegate.surface.exposed, true);
4727+ compare(gmailDelegate.surface.exposed, true);
4728 }
4729
4730 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
4731@@ -580,26 +594,22 @@
4732 tryCompare(dialerDelegate, "visuallyMaximized", true);
4733 tryCompare(gmailDelegate, "visuallyMaximized", true);
4734
4735- tryCompare(dashApp.surfaceList.get(0), "visible", false);
4736- tryCompare(dialerApp.surfaceList.get(0), "visible", false);
4737- tryCompare(mapApp.surfaceList.get(0), "visible", false);
4738+ tryCompare(dashApp.surfaceList.get(0), "exposed", false);
4739+ tryCompare(dialerApp.surfaceList.get(0), "exposed", false);
4740+ tryCompare(mapApp.surfaceList.get(0), "exposed", false);
4741
4742 ApplicationManager.stopApplication("gmail-webapp");
4743 wait(2000)
4744
4745- tryCompare(mapApp.surfaceList.get(0), "visible", true);
4746- tryCompare(dialerApp.surfaceList.get(0), "visible", true);
4747- tryCompare(dashApp.surfaceList.get(0), "visible", false); // still occluded by maximised dialer
4748+ tryCompare(mapApp.surfaceList.get(0), "exposed", true);
4749+ tryCompare(dialerApp.surfaceList.get(0), "exposed", true);
4750+ tryCompare(dashApp.surfaceList.get(0), "exposed", false); // still occluded by maximised dialer
4751 }
4752
4753 function test_maximisedAppStaysVisibleWhenAppStarts() {
4754 var dashDelegate = startApplication("unity8-dash");
4755
4756- // maximize
4757- var dashMaximizeButton = findChild(dashDelegate, "maximizeWindowButton");
4758- verify(dashMaximizeButton);
4759- mouseClick(dashMaximizeButton);
4760- tryCompare(dashDelegate, "visuallyMaximized", true);
4761+ maximizeDelegate(dashDelegate);
4762
4763 var dialerDelegate = startApplication("dialer-app");
4764 verify(dialerDelegate);
4765@@ -621,25 +631,25 @@
4766 tryCompare(facebookAppDelegate, "visible", true);
4767
4768 // Maximize the topmost and make sure the other two are hidden
4769- facebookAppDelegate.maximize();
4770+ maximizeDelegate(facebookAppDelegate);
4771 tryCompare(dashAppDelegate, "visible", false);
4772 tryCompare(dialerAppDelegate, "visible", false);
4773 tryCompare(facebookAppDelegate, "visible", true);
4774
4775 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
4776- dashAppDelegate.focus = true;
4777+ dashAppDelegate.activate();
4778 tryCompare(dashAppDelegate, "visible", true);
4779 tryCompare(dialerAppDelegate, "visible", false);
4780 tryCompare(facebookAppDelegate, "visible", true);
4781
4782 // Now focus the dialer app. all 3 should be visible again
4783- dialerAppDelegate.focus = true;
4784+ dialerAppDelegate.activate();
4785 tryCompare(dashAppDelegate, "visible", true);
4786 tryCompare(dialerAppDelegate, "visible", true);
4787 tryCompare(facebookAppDelegate, "visible", true);
4788
4789 // Maximize the dialer app. The other 2 should hide
4790- dialerAppDelegate.maximize();
4791+ maximizeDelegate(dialerAppDelegate);
4792 tryCompare(dashAppDelegate, "visible", false);
4793 tryCompare(dialerAppDelegate, "visible", true);
4794 tryCompare(facebookAppDelegate, "visible", false);
4795@@ -648,7 +658,7 @@
4796 function test_dropShadow() {
4797 // start an app, maximize it
4798 var facebookAppDelegate = startApplication("facebook-webapp");
4799- facebookAppDelegate.maximize();
4800+ maximizeDelegate(facebookAppDelegate);
4801
4802 // verify the drop shadow is still not visible
4803 verify(PanelState.dropShadow == false);
4804
4805=== modified file 'tests/qmltests/Stage/tst_PhoneStage.qml'
4806--- tests/qmltests/Stage/tst_PhoneStage.qml 2016-10-08 16:45:37 +0000
4807+++ tests/qmltests/Stage/tst_PhoneStage.qml 2016-11-30 19:24:53 +0000
4808@@ -22,7 +22,7 @@
4809 import "../../../qml/Stage"
4810 import Ubuntu.Components 1.3
4811 import Unity.Application 0.1
4812-import WindowManager 0.1
4813+import WindowManager 1.0
4814
4815 Item {
4816 id: root
4817@@ -31,6 +31,7 @@
4818
4819 property var greeter: { fullyShown: true }
4820
4821+ SurfaceManager { id: sMgr }
4822 Stage {
4823 id: stage
4824 anchors { fill: parent; rightMargin: units.gu(30) }
4825@@ -41,9 +42,13 @@
4826 orientations: Orientations {}
4827 applicationManager: ApplicationManager
4828 mode: "staged"
4829- topLevelSurfaceList: TopLevelSurfaceList {
4830+ topLevelSurfaceList: TopLevelWindowModel {
4831 id: topLevelSurfaceList
4832- applicationsModel: ApplicationManager
4833+ applicationManager: ApplicationManager
4834+ surfaceManager: sMgr
4835+ }
4836+ Component.onCompleted: {
4837+ ApplicationManager.startApplication("unity8-dash");
4838 }
4839 }
4840
4841@@ -86,11 +91,16 @@
4842
4843 function init() {
4844 // wait until unity8-dash is up and running.
4845- // it's started automatically by ApplicationManager mock implementation
4846+ ApplicationManager.startApplication("unity8-dash");
4847 tryCompare(ApplicationManager, "count", 1);
4848 var dashApp = ApplicationManager.findApplication("unity8-dash");
4849 verify(dashApp);
4850 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
4851+
4852+ // wait for Stage to stabilize back into its initial state
4853+ var appRepeater = findChild(stage, "appRepeater");
4854+ tryCompare(appRepeater, "count", 1);
4855+ tryCompare(appRepeater.itemAt(0), "x", 0);
4856 }
4857
4858 function cleanup() {
4859@@ -100,10 +110,6 @@
4860 waitForRendering(stage);
4861
4862 killApps();
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 stage.shellOrientationAngle = 0;
4869
4870@@ -505,7 +511,7 @@
4871 function test_selectSuspendedAppWithoutSurface() {
4872 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
4873 var dashSurfaceId = topLevelSurfaceList.idAt(0);
4874- var dashSurface = topLevelSurfaceList.surfaceAt(0);
4875+ var dashWindow = topLevelSurfaceList.windowAt(0);
4876
4877 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
4878 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
4879@@ -513,7 +519,7 @@
4880
4881 switchToSurface(dashSurfaceId);
4882
4883- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4884+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4885 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4886
4887 compare(webbrowserApp.surfaceList.count, 1);
4888@@ -549,7 +555,7 @@
4889 {
4890 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
4891 var dashSurfaceId = topLevelSurfaceList.idAt(0);
4892- var dashSurface = topLevelSurfaceList.surfaceAt(0);
4893+ var dashWindow = topLevelSurfaceList.windowAt(0);
4894
4895 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
4896 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
4897@@ -557,7 +563,7 @@
4898
4899 switchToSurface(dashSurfaceId);
4900
4901- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4902+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4903 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4904
4905 compare(webbrowserApp.surfaceList.count, 1);
4906
4907=== modified file 'tests/qmltests/Stage/tst_TabletStage.qml'
4908--- tests/qmltests/Stage/tst_TabletStage.qml 2016-10-08 16:45:37 +0000
4909+++ tests/qmltests/Stage/tst_TabletStage.qml 2016-11-30 19:24:53 +0000
4910@@ -21,7 +21,7 @@
4911 import Unity.Application 0.1
4912 import Unity.Test 0.1
4913 import Utils 0.1
4914-import WindowManager 0.1
4915+import WindowManager 1.0
4916
4917 import ".."
4918 import "../../../qml/Stage"
4919@@ -35,6 +35,7 @@
4920
4921 property var greeter: { fullyShown: true }
4922
4923+ SurfaceManager { id: sMgr }
4924 Stage {
4925 id: stage
4926 anchors { fill: parent; rightMargin: units.gu(30) }
4927@@ -50,9 +51,13 @@
4928 focus: true
4929 mode: "stagedWithSideStage"
4930 applicationManager: ApplicationManager
4931- topLevelSurfaceList: TopLevelSurfaceList {
4932+ topLevelSurfaceList: TopLevelWindowModel {
4933 id: topLevelSurfaceList
4934- applicationsModel: ApplicationManager
4935+ applicationManager: ApplicationManager
4936+ surfaceManager: sMgr
4937+ }
4938+ Component.onCompleted: {
4939+ ApplicationManager.startApplication("unity8-dash");
4940 }
4941 }
4942
4943@@ -129,6 +134,7 @@
4944 function init() {
4945 stageSaver.clear();
4946
4947+ ApplicationManager.startApplication("unity8-dash");
4948 tryCompare(topSurfaceList, "count", 1);
4949 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
4950
4951@@ -143,6 +149,11 @@
4952 }
4953 }
4954
4955+ // wait for Stage to stabilize back into its initial state
4956+ var appRepeater = findChild(stage, "appRepeater");
4957+ tryCompare(appRepeater, "count", 1);
4958+ tryCompare(appRepeater.itemAt(0), "x", 0);
4959+
4960 waitUntilAppSurfaceShowsUp(topSurfaceList.idAt(0));
4961 sideStage.hideNow()
4962 tryCompare(sideStage, "x", stage.width)
4963@@ -156,11 +167,6 @@
4964
4965 killApps();
4966
4967- // wait for Stage to stabilize back into its initial state
4968- var appRepeater = findChild(stage, "appRepeater");
4969- tryCompare(appRepeater, "count", 1);
4970- tryCompare(appRepeater.itemAt(0), "x", 0);
4971-
4972 sideStage.hideNow();
4973 tryCompare(sideStage, "x", stage.width)
4974 waitForRendering(stage)
4975@@ -628,7 +634,7 @@
4976 function test_selectSuspendedAppWithoutSurface() {
4977 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
4978 var dashSurfaceId = topSurfaceList.idAt(0);
4979- var dashSurface = topSurfaceList.surfaceAt(0);
4980+ var dashWindow = topSurfaceList.windowAt(0);
4981
4982 var webbrowserSurfaceId = topSurfaceList.nextId;
4983 webbrowserCheckBox.checked = true;
4984@@ -637,7 +643,7 @@
4985
4986 switchToSurface(dashSurfaceId);
4987
4988- tryCompare(MirFocusController, "focusedSurface", dashSurface);
4989+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
4990 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
4991
4992 compare(webbrowserApp.surfaceList.count, 1);
4993
4994=== modified file 'tests/qmltests/tst_Shell.qml'
4995--- tests/qmltests/tst_Shell.qml 2016-11-22 17:00:33 +0000
4996+++ tests/qmltests/tst_Shell.qml 2016-11-30 19:24:53 +0000
4997@@ -52,6 +52,15 @@
4998 }
4999
5000 property var shell: shellLoader.item ? shellLoader.item : null
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches