on xenial help sign in
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

commitf1dcc66d18e1218ebfa46f205681048a4e71989f authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateWed 30 Nov 2016, 17:24 (10 years ago) parent7a58df3
42 files changed+2170−1352
CMakeLists.txt+1−1 debian/control+3−3 plugins/WindowManager/CMakeLists.txt+3−1 plugins/WindowManager/TopLevelSurfaceList.cpp−481 plugins/WindowManager/TopLevelSurfaceList.h−223 plugins/WindowManager/TopLevelWindowModel.cpp+644 plugins/WindowManager/TopLevelWindowModel.h+258 plugins/WindowManager/Window.cpp+234 plugins/WindowManager/Window.h+161 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+117−110 qml/Stage/StagedFullscreenPolicy.qml+4−4 qml/Stage/TopLevelSurfaceRepeater.qml−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+88−59 tests/mocks/Unity/Application/ApplicationManager.h+31−4 tests/mocks/Unity/Application/CMakeLists.txt+1−1 tests/mocks/Unity/Application/MirSurface.cpp+80−93 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+11−33 tests/mocks/Unity/Application/MirSurfaceListModel.h+2−7 tests/mocks/Unity/Application/SurfaceManager.cpp+167−24 tests/mocks/Unity/Application/SurfaceManager.h+28−10 tests/mocks/Unity/Application/plugin.cpp+2−31 tests/plugins/Unity/Launcher/launchermodeltest.cpp+4 tests/qmltests/Stage/ApplicationCheckBox.qml+1−1 tests/qmltests/Stage/tst_ApplicationWindow.qml+10−10 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+114−40 tests/utils/modules/Unity/Test/UnityTestCase.qml+4−5
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
5757find_package(Qt5Concurrent 5.4 REQUIRED)
5858find_package(Qt5Sql 5.4 REQUIRED)
5959
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
6161pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
6262pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6363pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
debian/control+3−3
@@ -38,7 +38,7 @@ Build-Depends: cmake,
3838 libubuntugestures5-private-dev (>= 1.3.2030),
3939 libudev-dev,
4040 libudm-common-dev,
41− libunity-api-dev (>= 7.120),
41+ libunity-api-dev (>= 8.0),
4242 libusermetricsoutput1-dev,
4343# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4444 libx11-dev[!arm64 !armhf],
@@ -160,7 +160,7 @@ Depends: qml-module-qtquick-layouts,
160160 qttranslations5-l10n,
161161 ubuntu-thumbnailer-impl-0,
162162 ubuntu-wallpapers,
163− unity-application-impl-22,
163+ unity-application-impl-23,
164164 unity-notifications-impl-3,
165165 unity-plugin-scopes | unity-scopes-impl,
166166 unity-scopes-impl-12,
@@ -206,7 +206,7 @@ Pre-Depends: ${misc:Pre-Depends},
206206Depends: ${misc:Depends},
207207 ${shlibs:Depends},
208208Provides: unity-application-impl,
209− unity-application-impl-22,
209+ unity-application-impl-23,
210210Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
211211Description: Fake environment for running Unity 8 shell
212212 Provides fake implementations of some QML modules used by Unity 8 shell
plugins/WindowManager/CMakeLists.txt+3−1
@@ -1,10 +1,12 @@
11set(WINDOWMANAGER_SRC
2− TopLevelSurfaceList.cpp
2+ TopLevelWindowModel.cpp
3+ Window.cpp
34 WindowManagerPlugin.cpp
45 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
56 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
67 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
78 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
9+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
810 )
911
1012add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
plugins/WindowManager/TopLevelSurfaceList.cppdeleted−481
@@ -1,481 +0,0 @@
1−/*
2− * Copyright (C) 2016 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−#include "TopLevelSurfaceList.h"
18−
19−// unity-api
20−#include <unity/shell/application/ApplicationInfoInterface.h>
21−#include <unity/shell/application/MirSurfaceInterface.h>
22−#include <unity/shell/application/MirSurfaceListInterface.h>
23−
24−#include <QMetaObject>
25−
26−Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
27−
28−#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
29−
30−using namespace unity::shell::application;
31−
32−TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
33− QAbstractListModel(parent)
34−{
35− DEBUG_MSG << "()";
36−}
37−
38−TopLevelSurfaceList::~TopLevelSurfaceList()
39−{
40− DEBUG_MSG << "()";
41−}
42−
43−int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
44−{
45− return !parent.isValid() ? m_surfaceList.size() : 0;
46−}
47−
48−QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
49−{
50− if (index.row() < 0 || index.row() >= m_surfaceList.size())
51− return QVariant();
52−
53− if (role == SurfaceRole) {
54− MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
55− return QVariant::fromValue(surface);
56− } else if (role == ApplicationRole) {
57− return QVariant::fromValue(m_surfaceList.at(index.row()).application);
58− } else if (role == IdRole) {
59− return QVariant::fromValue(m_surfaceList.at(index.row()).id);
60− } else {
61− return QVariant();
62− }
63−}
64−
65−void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
66−{
67− if (!surface)
68− return;
69−
70− DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
71−
72− int i = indexOf(surface);
73− if (i != -1) {
74− raiseId(m_surfaceList.at(i).id);
75− }
76−}
77−
78−void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
79−{
80− DEBUG_MSG << "(" << application->appId() << ")";
81−
82− appendSurfaceHelper(nullptr, application);
83−}
84−
85−void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
86−{
87− Q_ASSERT(surface != nullptr);
88−
89− bool filledPlaceholder = false;
90− for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
91− ModelEntry &entry = m_surfaceList[i];
92− if (entry.application == application && entry.surface == nullptr) {
93− entry.surface = surface;
94− connectSurface(surface);
95− DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
96− << ", filling out placeholder. after: " << toString();
97− Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
98− filledPlaceholder = true;
99− }
100− }
101−
102− if (!filledPlaceholder) {
103− DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
104− appendSurfaceHelper(surface, application);
105− }
106−}
107−
108−void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
109−{
110− if (m_modelState == IdleState) {
111− m_modelState = InsertingState;
112− beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
113− } else {
114− Q_ASSERT(m_modelState == ResettingState);
115− // No point in signaling anything if we're resetting the whole model
116− }
117−
118− int id = generateId();
119− m_surfaceList.append(ModelEntry(surface, application, id));
120− if (surface) {
121− connectSurface(surface);
122− }
123−
124− if (m_modelState == InsertingState) {
125− endInsertRows();
126− Q_EMIT countChanged();
127− Q_EMIT listChanged();
128− m_modelState = IdleState;
129− }
130−
131− DEBUG_MSG << " after " << toString();
132−}
133−
134−void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
135−{
136− connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
137− if (!live) {
138− onSurfaceDied(surface);
139− }
140− });
141− connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
142−}
143−
144−void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
145−{
146− int i = indexOf(surface);
147− if (i == -1) {
148− return;
149− }
150−
151− auto application = m_surfaceList[i].application;
152−
153− // can't be starting if it already has a surface
154− Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
155−
156− if (application->state() == ApplicationInfoInterface::Running) {
157− m_surfaceList[i].removeOnceSurfaceDestroyed = true;
158− } else {
159− // assume it got killed by the out-of-memory daemon.
160− //
161− // So leave entry in the model and only remove its surface, so shell can display a screenshot
162− // in its place.
163− m_surfaceList[i].removeOnceSurfaceDestroyed = false;
164− }
165−}
166−
167−void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
168−{
169− int i = indexOf(surface);
170− if (i == -1) {
171− return;
172− }
173−
174− if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
175− removeAt(i);
176− } else {
177− m_surfaceList[i].surface = nullptr;
178− Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
179− DEBUG_MSG << " Removed surface from entry. After: " << toString();
180− }
181−}
182−
183−void TopLevelSurfaceList::removeAt(int index)
184−{
185− if (m_modelState == IdleState) {
186− beginRemoveRows(QModelIndex(), index, index);
187− m_modelState = RemovingState;
188− } else {
189− Q_ASSERT(m_modelState == ResettingState);
190− // No point in signaling anything if we're resetting the whole model
191− }
192−
193− m_surfaceList.removeAt(index);
194−
195− if (m_modelState == RemovingState) {
196− endRemoveRows();
197− Q_EMIT countChanged();
198− Q_EMIT listChanged();
199− m_modelState = IdleState;
200− }
201−
202− DEBUG_MSG << " after " << toString();
203−}
204−
205−int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
206−{
207− for (int i = 0; i < m_surfaceList.count(); ++i) {
208− if (m_surfaceList.at(i).surface == surface) {
209− return i;
210− }
211− }
212− return -1;
213−}
214−
215−void TopLevelSurfaceList::move(int from, int to)
216−{
217− if (from == to) return;
218− DEBUG_MSG << " from=" << from << " to=" << to;
219−
220− if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
221− QModelIndex parent;
222− /* When moving an item down, the destination index needs to be incremented
223− by one, as explained in the documentation:
224− http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
225−
226− Q_ASSERT(m_modelState == IdleState);
227− m_modelState = MovingState;
228−
229− beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
230− m_surfaceList.move(from, to);
231− endMoveRows();
232− Q_EMIT listChanged();
233−
234− m_modelState = IdleState;
235−
236− DEBUG_MSG << " after " << toString();
237− }
238−}
239−
240−MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
241−{
242− if (index >=0 && index < m_surfaceList.count()) {
243− return m_surfaceList[index].surface;
244− } else {
245− return nullptr;
246− }
247−}
248−
249−ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
250−{
251− if (index >=0 && index < m_surfaceList.count()) {
252− return m_surfaceList[index].application;
253− } else {
254− return nullptr;
255− }
256−}
257−
258−int TopLevelSurfaceList::idAt(int index) const
259−{
260− if (index >=0 && index < m_surfaceList.count()) {
261− return m_surfaceList[index].id;
262− } else {
263− return 0;
264− }
265−}
266−
267−int TopLevelSurfaceList::indexForId(int id) const
268−{
269− for (int i = 0; i < m_surfaceList.count(); ++i) {
270− if (m_surfaceList[i].id == id) {
271− return i;
272− }
273− }
274− return -1;
275−}
276−
277−void TopLevelSurfaceList::doRaiseId(int id)
278−{
279− int fromIndex = indexForId(id);
280− if (fromIndex != -1) {
281− move(fromIndex, 0 /* toIndex */);
282− }
283−}
284−
285−void TopLevelSurfaceList::raiseId(int id)
286−{
287− if (m_modelState == IdleState) {
288− DEBUG_MSG << "(id=" << id << ") - do it now.";
289− doRaiseId(id);
290− } else {
291− DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
292− // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
293− // if we perform yet another model change straight away.
294− //
295− // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
296− // the index is definitely within bounds.
297− QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
298− }
299−}
300−
301−int TopLevelSurfaceList::generateId()
302−{
303− int id = m_nextId;
304− m_nextId = nextFreeId(m_nextId + 1);
305− Q_EMIT nextIdChanged();
306− return id;
307−}
308−
309−int TopLevelSurfaceList::nextFreeId(int candidateId)
310−{
311− if (candidateId > m_maxId) {
312− return nextFreeId(1);
313− } else {
314− if (indexForId(candidateId) == -1) {
315− // it's indeed free
316− return candidateId;
317− } else {
318− return nextFreeId(candidateId + 1);
319− }
320− }
321−}
322−
323−QString TopLevelSurfaceList::toString()
324−{
325− QString str;
326− for (int i = 0; i < m_surfaceList.count(); ++i) {
327− auto item = m_surfaceList.at(i);
328−
329− QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
330− .arg(i)
331− .arg(item.application->appId())
332− .arg((qintptr)item.surface, 0, 16)
333− .arg(item.id);
334−
335− if (i > 0) {
336− str.append(",");
337− }
338− str.append(itemStr);
339− }
340− return str;
341−}
342−
343−void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
344−{
345− DEBUG_MSG << "(" << application->appId() << ")";
346− Q_ASSERT(!m_applications.contains(application));
347− m_applications.append(application);
348−
349− MirSurfaceListInterface *surfaceList = application->surfaceList();
350−
351− if (application->state() != ApplicationInfoInterface::Stopped) {
352− if (surfaceList->count() == 0) {
353− appendPlaceholder(application);
354− } else {
355− for (int i = 0; i < surfaceList->count(); ++i) {
356− appendSurface(surfaceList->get(i), application);
357− }
358− }
359− }
360−
361− connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
362− [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
363− {
364− for (int i = last; i >= first; --i) {
365− this->appendSurface(surfaceList->get(i), application);
366− }
367− });
368−}
369−
370−void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
371−{
372− DEBUG_MSG << "(" << application->appId() << ")";
373− Q_ASSERT(m_applications.contains(application));
374−
375− MirSurfaceListInterface *surfaceList = application->surfaceList();
376−
377− disconnect(surfaceList, 0, this, 0);
378−
379− Q_ASSERT(m_modelState == IdleState);
380− m_modelState = RemovingState;
381−
382− int i = 0;
383− while (i < m_surfaceList.count()) {
384− if (m_surfaceList.at(i).application == application) {
385− beginRemoveRows(QModelIndex(), i, i);
386− m_surfaceList.removeAt(i);
387− endRemoveRows();
388− Q_EMIT countChanged();
389− Q_EMIT listChanged();
390− } else {
391− ++i;
392− }
393− }
394−
395− m_modelState = IdleState;
396−
397− DEBUG_MSG << " after " << toString();
398−
399− m_applications.removeAll(application);
400−}
401−
402−QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
403−{
404− return m_applicationsModel;
405−}
406−
407−void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
408−{
409− if (m_applicationsModel == value) {
410− return;
411− }
412−
413− DEBUG_MSG << "(" << value << ")";
414−
415− Q_ASSERT(m_modelState == IdleState);
416− m_modelState = ResettingState;
417−
418− beginResetModel();
419−
420− if (m_applicationsModel) {
421− m_surfaceList.clear();
422− m_applications.clear();
423− disconnect(m_applicationsModel, 0, this, 0);
424− }
425−
426− m_applicationsModel = value;
427−
428− if (m_applicationsModel) {
429− findApplicationRole();
430−
431− connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
432− this, [this](const QModelIndex &/*parent*/, int first, int last) {
433− for (int i = first; i <= last; ++i) {
434− auto application = getApplicationFromModelAt(i);
435− addApplication(application);
436− }
437− });
438−
439− connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
440− this, [this](const QModelIndex &/*parent*/, int first, int last) {
441− for (int i = first; i <= last; ++i) {
442− auto application = getApplicationFromModelAt(i);
443− removeApplication(application);
444− }
445− });
446−
447− for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
448− auto application = getApplicationFromModelAt(i);
449− addApplication(application);
450− }
451− }
452−
453− endResetModel();
454− m_modelState = IdleState;
455−}
456−
457−ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
458−{
459− QModelIndex modelIndex = m_applicationsModel->index(index);
460−
461− QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
462−
463− // variant.value<ApplicationInfoInterface*>() returns null for some reason.
464− return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
465−}
466−
467−void TopLevelSurfaceList::findApplicationRole()
468−{
469− QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
470−
471− m_applicationRole = -1;
472− for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
473− if (i.value() == "application") {
474− m_applicationRole = i.key();
475− }
476− }
477−
478− if (m_applicationRole == -1) {
479− qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
480− }
481−}
plugins/WindowManager/TopLevelSurfaceList.hdeleted−223
@@ -1,223 +0,0 @@
1−/*
2− * Copyright (C) 2016 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−#ifndef TOPLEVELSURFACELIST_H
18−#define TOPLEVELSURFACELIST_H
19−
20−#include <QAbstractListModel>
21−#include <QList>
22−#include <QLoggingCategory>
23−
24−Q_DECLARE_LOGGING_CATEGORY(UNITY_TOPSURFACELIST)
25−
26−namespace unity {
27− namespace shell {
28− namespace application {
29− class ApplicationInfoInterface;
30− class MirSurfaceInterface;
31− }
32− }
33−}
34−
35−/**
36− * @brief A model of top-level surfaces
37− *
38− * It's an abstraction of top-level application windows.
39− *
40− * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
41− * still starting up. A shell should then display a splash screen or saved screenshot of the application
42− * until its surface comes up.
43− *
44− * As applications can have multiple surfaces and you can also have entries without surfaces at all,
45− * the only way to unambiguously refer to an entry in this model is through its id.
46− */
47−class TopLevelSurfaceList : public QAbstractListModel
48−{
49−
50− Q_OBJECT
51−
52− /**
53− * @brief A list model of applications.
54− *
55− * It's expected to have a role called "application" which returns a ApplicationInfoInterface
56− */
57− Q_PROPERTY(QAbstractListModel* applicationsModel READ applicationsModel
58− WRITE setApplicationsModel
59− NOTIFY applicationsModelChanged)
60−
61− /**
62− * @brief Number of top-level surfaces in this model
63− *
64− * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
65− */
66− Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
67−
68− /**
69− The id to be used on the next entry created
70− Useful for tests
71− */
72− Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
73−public:
74−
75− /**
76− * @brief The Roles supported by the model
77− *
78− * SurfaceRole - A MirSurfaceInterface. It will be null if the application is still starting up
79− * ApplicationRole - An ApplicationInfoInterface
80− * IdRole - A unique identifier for this entry. Useful to unambiguosly track elements as they move around in the list
81− */
82− enum Roles {
83− SurfaceRole = Qt::UserRole,
84− ApplicationRole = Qt::UserRole + 1,
85− IdRole = Qt::UserRole + 2,
86− };
87−
88− explicit TopLevelSurfaceList(QObject *parent = nullptr);
89− virtual ~TopLevelSurfaceList();
90−
91− // QAbstractItemModel methods
92− int rowCount(const QModelIndex &parent = QModelIndex()) const override;
93− QVariant data(const QModelIndex& index, int role) const override;
94− QHash<int, QByteArray> roleNames() const override {
95− QHash<int, QByteArray> roleNames { {SurfaceRole, "surface"},
96− {ApplicationRole, "application"},
97− {IdRole, "id"} };
98− return roleNames;
99− }
100−
101− int nextId() const { return m_nextId; }
102−
103− QAbstractListModel *applicationsModel() const;
104− void setApplicationsModel(QAbstractListModel*);
105−
106−public Q_SLOTS:
107− /**
108− * @brief Returns the surface at the given index
109− *
110− * It will be a nullptr if the application is still starting up and thus hasn't yet created
111− * and drawn into a surface.
112− */
113− unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
114−
115− /**
116− * @brief Returns the application at the given index
117− */
118− unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
119−
120− /**
121− * @brief Returns the unique id of the element at the given index
122− */
123− int idAt(int index) const;
124−
125− /**
126− * @brief Returns the index where the row with the given id is located
127− *
128− * Returns -1 if there's no row with the given id.
129− */
130− int indexForId(int id) const;
131−
132− /**
133− * @brief Raises the row with the given id to index 0
134− */
135− void raiseId(int id);
136−
137− void doRaiseId(int id);
138−
139−Q_SIGNALS:
140− void countChanged();
141−
142− /**
143− * @brief Emitted when the list changes
144− *
145− * Emitted when model gains an element, loses an element or when elements exchange positions.
146− */
147− void listChanged();
148−
149− void nextIdChanged();
150−
151− void applicationsModelChanged();
152−
153−private:
154− void addApplication(unity::shell::application::ApplicationInfoInterface *application);
155− void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
156−
157− int indexOf(unity::shell::application::MirSurfaceInterface *surface);
158− void raise(unity::shell::application::MirSurfaceInterface *surface);
159− void move(int from, int to);
160− void appendSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
161− unity::shell::application::ApplicationInfoInterface *application);
162− void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
163− int generateId();
164− int nextFreeId(int candidateId);
165− QString toString();
166− void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
167− void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
168− void removeAt(int index);
169− void findApplicationRole();
170−
171− unity::shell::application::ApplicationInfoInterface *getApplicationFromModelAt(int index);
172−
173− /*
174− Placeholder for a future surface from a starting or running application.
175− Enables shell to give immediate feedback to the user by showing, eg,
176− a splash screen.
177−
178− It's a model row containing a null surface and the given application.
179− */
180− void appendPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
181−
182− /*
183− Adds a model row with the given surface and application
184−
185− Alternatively, if a placeholder exists for the given application it's
186− filled with the given surface instead.
187− */
188− void appendSurface(unity::shell::application::MirSurfaceInterface *surface,
189− unity::shell::application::ApplicationInfoInterface *application);
190−
191− struct ModelEntry {
192− ModelEntry(unity::shell::application::MirSurfaceInterface *surface, unity::shell::application::ApplicationInfoInterface *application, int id)
193− : surface(surface), application(application), id(id) {}
194− unity::shell::application::MirSurfaceInterface *surface;
195− unity::shell::application::ApplicationInfoInterface *application;
196− int id;
197− bool removeOnceSurfaceDestroyed{false};
198− };
199−
200− QList<ModelEntry> m_surfaceList;
201− int m_nextId{1};
202− static const int m_maxId{1000000};
203−
204− // applications that are being monitored
205− QList<unity::shell::application::ApplicationInfoInterface *> m_applications;
206−
207− QAbstractListModel* m_applicationsModel{nullptr};
208− int m_applicationRole{-1};
209−
210− enum ModelState {
211− IdleState,
212− InsertingState,
213− RemovingState,
214− MovingState,
215− ResettingState
216− };
217− ModelState m_modelState{IdleState};
218−};
219−
220−Q_DECLARE_METATYPE(TopLevelSurfaceList*)
221−Q_DECLARE_METATYPE(QAbstractListModel*)
222−
223−#endif // TOPLEVELSURFACELIST_H
plugins/WindowManager/TopLevelWindowModel.cppnew file+644
@@ -0,0 +1,644 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "TopLevelWindowModel.h"
18+
19+// unity-api
20+#include <unity/shell/application/ApplicationInfoInterface.h>
21+#include <unity/shell/application/ApplicationManagerInterface.h>
22+#include <unity/shell/application/MirSurfaceInterface.h>
23+#include <unity/shell/application/MirSurfaceListInterface.h>
24+#include <unity/shell/application/SurfaceManagerInterface.h>
25+
26+// Qt
27+#include <QGuiApplication>
28+#include <QDebug>
29+
30+// local
31+#include "Window.h"
32+
33+Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34+
35+#define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
36+#define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
37+
38+namespace unityapi = unity::shell::application;
39+
40+TopLevelWindowModel::TopLevelWindowModel()
41+{
42+}
43+
44+void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
45+{
46+ if (m_applicationManager == value) {
47+ return;
48+ }
49+
50+ DEBUG_MSG << "(" << value << ")";
51+
52+ Q_ASSERT(m_modelState == IdleState);
53+ m_modelState = ResettingState;
54+
55+ beginResetModel();
56+
57+ if (m_applicationManager) {
58+ m_windowModel.clear();
59+ disconnect(m_applicationManager, 0, this, 0);
60+ }
61+
62+ m_applicationManager = value;
63+
64+ if (m_applicationManager) {
65+ connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
66+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
67+ for (int i = first; i <= last; ++i) {
68+ auto application = m_applicationManager->get(i);
69+ addApplication(application);
70+ }
71+ });
72+
73+ connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
74+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
75+ for (int i = first; i <= last; ++i) {
76+ auto application = m_applicationManager->get(i);
77+ removeApplication(application);
78+ }
79+ });
80+
81+ for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82+ auto application = m_applicationManager->get(i);
83+ addApplication(application);
84+ }
85+ }
86+
87+ endResetModel();
88+ m_modelState = IdleState;
89+}
90+
91+void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *surfaceManager)
92+{
93+ if (surfaceManager == m_surfaceManager) {
94+ return;
95+ }
96+
97+ DEBUG_MSG << "(" << surfaceManager << ")";
98+
99+ if (m_surfaceManager) {
100+ disconnect(m_surfaceManager, 0, this, 0);
101+ }
102+
103+ m_surfaceManager = surfaceManager;
104+
105+ if (m_surfaceManager) {
106+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
107+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
110+ }
111+
112+ Q_EMIT surfaceManagerChanged(m_surfaceManager);
113+}
114+
115+void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
116+{
117+ DEBUG_MSG << "(" << application->appId() << ")";
118+
119+ if (application->state() != unityapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
120+ prependPlaceholder(application);
121+ }
122+}
123+
124+void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *application)
125+{
126+ DEBUG_MSG << "(" << application->appId() << ")";
127+
128+ Q_ASSERT(m_modelState == IdleState);
129+
130+ int i = 0;
131+ while (i < m_windowModel.count()) {
132+ if (m_windowModel.at(i).application == application) {
133+ removeAt(i);
134+ } else {
135+ ++i;
136+ }
137+ }
138+}
139+
140+void TopLevelWindowModel::prependPlaceholder(unityapi::ApplicationInfoInterface *application)
141+{
142+ INFO_MSG << "(" << application->appId() << ")";
143+
144+ prependSurfaceHelper(nullptr, application);
145+}
146+
147+void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
148+{
149+ Q_ASSERT(surface != nullptr);
150+
151+ bool filledPlaceholder = false;
152+ for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
153+ ModelEntry &entry = m_windowModel[i];
154+ if (entry.application == application && entry.window->surface() == nullptr) {
155+ entry.window->setSurface(surface);
156+ connectSurface(surface);
157+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface
158+ << ", filling out placeholder. after: " << toString();
159+ filledPlaceholder = true;
160+ }
161+ }
162+
163+ if (!filledPlaceholder) {
164+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
165+ prependSurfaceHelper(surface, application);
166+ }
167+}
168+
169+void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
170+{
171+ if (m_modelState == IdleState) {
172+ m_modelState = InsertingState;
173+ beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
174+ } else {
175+ Q_ASSERT(m_modelState == ResettingState);
176+ // No point in signaling anything if we're resetting the whole model
177+ }
178+
179+ int id = generateId();
180+ Window *window = new Window(id, this);
181+ if (surface) {
182+ window->setSurface(surface);
183+ }
184+ m_windowModel.prepend(ModelEntry(window, application));
185+ if (surface) {
186+ connectSurface(surface);
187+ }
188+
189+ connectWindow(window);
190+
191+ if (m_modelState == InsertingState) {
192+ endInsertRows();
193+ Q_EMIT countChanged();
194+ Q_EMIT listChanged();
195+ m_modelState = IdleState;
196+ }
197+
198+ if (!surface) {
199+ // focus the newly added window. miral can't help with that as it doesn't know about it.
200+ window->setFocused(true);
201+ if (m_focusedWindow && m_focusedWindow->surface()) {
202+ m_surfaceManager->activate(nullptr);
203+ }
204+ }
205+
206+ INFO_MSG << " after " << toString();
207+}
208+
209+void TopLevelWindowModel::connectWindow(Window *window)
210+{
211+ connect(window, &Window::focusRequested, this, [this, window]() {
212+ if (!window->surface()) {
213+ activateEmptyWindow(window);
214+ }
215+ });
216+
217+ connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
218+ if (window->surface()) {
219+ // Condense changes to the focused window
220+ // eg: Do focusedWindow=A to focusedWindow=B instead of
221+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
222+ m_focusedWindowChanged = true;
223+ if (focused) {
224+ Q_ASSERT(m_newlyFocusedWindow == nullptr);
225+ m_newlyFocusedWindow = window;
226+ }
227+ }
228+ });
229+
230+ connect(window, &Window::closeRequested, this, [this, window]() {
231+ if (!window->surface()) {
232+ int index = indexForId(window->id());
233+ Q_ASSERT(index >= 0);
234+ m_windowModel[index].application->close();
235+ }
236+ });
237+
238+ connect(window, &Window::emptyWindowActivated, this, [this, window]() {
239+ activateEmptyWindow(window);
240+ });
241+}
242+
243+void TopLevelWindowModel::activateEmptyWindow(Window *window)
244+{
245+ Q_ASSERT(!window->surface());
246+
247+ // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
248+ // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
249+
250+ window->setFocused(true);
251+ raiseId(window->id());
252+ Window *previousWindow = m_focusedWindow;
253+ setFocusedWindow(window);
254+ if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
255+ m_surfaceManager->activate(nullptr);
256+ }
257+}
258+
259+void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
260+{
261+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
262+ if (!live) {
263+ onSurfaceDied(surface);
264+ }
265+ });
266+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
267+}
268+
269+void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
270+{
271+ if (surface->type() == Mir::InputMethodType) {
272+ removeInputMethodWindow();
273+ return;
274+ }
275+
276+ int i = indexOf(surface);
277+ if (i == -1) {
278+ return;
279+ }
280+
281+ auto application = m_windowModel[i].application;
282+
283+ // can't be starting if it already has a surface
284+ Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
285+
286+ if (application->state() == unityapi::ApplicationInfoInterface::Running) {
287+ m_windowModel[i].removeOnceSurfaceDestroyed = true;
288+ } else {
289+ // assume it got killed by the out-of-memory daemon.
290+ //
291+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
292+ // in its place.
293+ m_windowModel[i].removeOnceSurfaceDestroyed = false;
294+ }
295+}
296+
297+void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
298+{
299+ int i = indexOf(surface);
300+ if (i == -1) {
301+ return;
302+ }
303+
304+ if (m_windowModel[i].removeOnceSurfaceDestroyed) {
305+ removeAt(i);
306+ } else {
307+ auto window = m_windowModel[i].window;
308+ window->setSurface(nullptr);
309+ window->setFocused(false);
310+ INFO_MSG << " Removed surface from entry. After: " << toString();
311+ }
312+}
313+
314+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
315+{
316+ DEBUG_MSG << "(" << surface << ")";
317+ if (surface->type() == Mir::InputMethodType) {
318+ int id = generateId();
319+ Window *qmlWindow = new Window(id, this);
320+ connectWindow(qmlWindow);
321+ qmlWindow->setSurface(surface);
322+ setInputMethodWindow(qmlWindow);
323+ } else {
324+ auto application = m_applicationManager->findApplicationWithSurface(surface);
325+ if (application) {
326+ prependSurface(surface, application);
327+ } else {
328+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
329+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
330+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
331+ int id = generateId();
332+ Window *promptWindow = new Window(id, this);
333+ connectWindow(promptWindow);
334+ promptWindow->setSurface(surface);
335+ connect(surface, &QObject::destroyed, promptWindow, [=](){
336+ promptWindow->setSurface(nullptr);
337+ promptWindow->deleteLater();
338+ });
339+ }
340+ }
341+ // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
342+}
343+
344+void TopLevelWindowModel::removeAt(int index)
345+{
346+ if (m_modelState == IdleState) {
347+ beginRemoveRows(QModelIndex(), index, index);
348+ m_modelState = RemovingState;
349+ } else {
350+ Q_ASSERT(m_modelState == ResettingState);
351+ // No point in signaling anything if we're resetting the whole model
352+ }
353+
354+ auto window = m_windowModel[index].window;
355+
356+ window->setSurface(nullptr);
357+ window->setFocused(false);
358+
359+ m_windowModel.removeAt(index);
360+
361+ if (m_modelState == RemovingState) {
362+ endRemoveRows();
363+ Q_EMIT countChanged();
364+ Q_EMIT listChanged();
365+ m_modelState = IdleState;
366+ }
367+
368+ disconnect(window, 0, this, 0);
369+ if (m_focusedWindow == window) {
370+ setFocusedWindow(nullptr);
371+ }
372+ delete window;
373+
374+ INFO_MSG << " after " << toString();
375+}
376+
377+void TopLevelWindowModel::setInputMethodWindow(Window *window)
378+{
379+ if (m_inputMethodWindow) {
380+ qWarning("Multiple Input Method Surfaces created, removing the old one!");
381+ delete m_inputMethodWindow;
382+ }
383+ m_inputMethodWindow = window;
384+ Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
385+}
386+
387+void TopLevelWindowModel::removeInputMethodWindow()
388+{
389+ if (m_inputMethodWindow) {
390+ delete m_inputMethodWindow;
391+ m_inputMethodWindow = nullptr;
392+ Q_EMIT inputMethodSurfaceChanged(nullptr);
393+ }
394+}
395+
396+void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
397+{
398+ DEBUG_MSG << "(" << surfaces << ")";
399+ const int raiseCount = surfaces.size();
400+ for (int i = 0; i < raiseCount; i++) {
401+ int fromIndex = findIndexOf(surfaces[i]);
402+ if (fromIndex != -1) {
403+ move(fromIndex, 0);
404+ }
405+ }
406+}
407+
408+int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
409+{
410+ return m_windowModel.count();
411+}
412+
413+QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
414+{
415+ if (index.row() < 0 || index.row() >= m_windowModel.size())
416+ return QVariant();
417+
418+ if (role == WindowRole) {
419+ Window *window = m_windowModel.at(index.row()).window;
420+ return QVariant::fromValue(window);
421+ } else if (role == ApplicationRole) {
422+ return QVariant::fromValue(m_windowModel.at(index.row()).application);
423+ } else {
424+ return QVariant();
425+ }
426+}
427+
428+int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
429+{
430+ for (int i=0; i<m_windowModel.count(); i++) {
431+ if (m_windowModel[i].window->surface() == surface) {
432+ return i;
433+ }
434+ }
435+ return -1;
436+}
437+
438+int TopLevelWindowModel::generateId()
439+{
440+ int id = m_nextId;
441+ m_nextId = nextFreeId(nextId(id), id);
442+ return id;
443+}
444+
445+int TopLevelWindowModel::nextId(int id) const
446+{
447+ if (id == m_maxId) {
448+ return id = 1;
449+ } else {
450+ return id + 1;
451+ }
452+}
453+
454+int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
455+{
456+ int firstCandidateId = candidateId;
457+
458+ while (indexForId(candidateId) != -1 || candidateId == latestId) {
459+ candidateId = nextId(candidateId);
460+
461+ if (candidateId == firstCandidateId) {
462+ qFatal("TopLevelWindowModel: run out of window ids.");
463+ }
464+ }
465+
466+ return candidateId;
467+}
468+
469+QString TopLevelWindowModel::toString()
470+{
471+ QString str;
472+ for (int i = 0; i < m_windowModel.count(); ++i) {
473+ auto item = m_windowModel.at(i);
474+
475+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
476+ .arg(QString::number(i),
477+ item.application->appId(),
478+ QString::number((qintptr)item.window->surface(), 16),
479+ QString::number(item.window->id()));
480+
481+ if (i > 0) {
482+ str.append(",");
483+ }
484+ str.append(itemStr);
485+ }
486+ return str;
487+}
488+
489+int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
490+{
491+ for (int i = 0; i < m_windowModel.count(); ++i) {
492+ if (m_windowModel.at(i).window->surface() == surface) {
493+ return i;
494+ }
495+ }
496+ return -1;
497+}
498+
499+int TopLevelWindowModel::indexForId(int id) const
500+{
501+ for (int i = 0; i < m_windowModel.count(); ++i) {
502+ if (m_windowModel[i].window->id() == id) {
503+ return i;
504+ }
505+ }
506+ return -1;
507+}
508+
509+Window *TopLevelWindowModel::windowAt(int index) const
510+{
511+ if (index >=0 && index < m_windowModel.count()) {
512+ return m_windowModel[index].window;
513+ } else {
514+ return nullptr;
515+ }
516+}
517+
518+unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
519+{
520+ if (index >=0 && index < m_windowModel.count()) {
521+ return m_windowModel[index].window->surface();
522+ } else {
523+ return nullptr;
524+ }
525+}
526+
527+unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
528+{
529+ if (index >=0 && index < m_windowModel.count()) {
530+ return m_windowModel[index].application;
531+ } else {
532+ return nullptr;
533+ }
534+}
535+
536+int TopLevelWindowModel::idAt(int index) const
537+{
538+ if (index >=0 && index < m_windowModel.count()) {
539+ return m_windowModel[index].window->id();
540+ } else {
541+ return 0;
542+ }
543+}
544+
545+void TopLevelWindowModel::raiseId(int id)
546+{
547+ if (m_modelState == IdleState) {
548+ DEBUG_MSG << "(id=" << id << ") - do it now.";
549+ doRaiseId(id);
550+ } else {
551+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
552+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
553+ // if we perform yet another model change straight away.
554+ //
555+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
556+ // the index is definitely within bounds.
557+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
558+ }
559+}
560+
561+void TopLevelWindowModel::doRaiseId(int id)
562+{
563+ int fromIndex = indexForId(id);
564+ if (fromIndex != -1) {
565+ auto surface = m_windowModel[fromIndex].window->surface();
566+ if (surface) {
567+ m_surfaceManager->raise(surface);
568+ } else {
569+ // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
570+ // miral can do about it.
571+ move(fromIndex, 0);
572+ }
573+ }
574+}
575+
576+void TopLevelWindowModel::setFocusedWindow(Window *window)
577+{
578+ if (window != m_focusedWindow) {
579+ INFO_MSG << "(" << window << ")";
580+
581+ Window* previousWindow = m_focusedWindow;
582+
583+ m_focusedWindow = window;
584+ Q_EMIT focusedWindowChanged(m_focusedWindow);
585+
586+ if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
587+ // do it ourselves. miral doesn't know about this window
588+ previousWindow->setFocused(false);
589+ }
590+ }
591+}
592+
593+unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
594+{
595+ return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
596+}
597+
598+Window* TopLevelWindowModel::focusedWindow() const
599+{
600+ return m_focusedWindow;
601+}
602+
603+void TopLevelWindowModel::move(int from, int to)
604+{
605+ if (from == to) return;
606+ DEBUG_MSG << " from=" << from << " to=" << to;
607+
608+ if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
609+ QModelIndex parent;
610+ /* When moving an item down, the destination index needs to be incremented
611+ by one, as explained in the documentation:
612+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
613+
614+ Q_ASSERT(m_modelState == IdleState);
615+ m_modelState = MovingState;
616+
617+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
618+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
619+ const auto &window = m_windowModel.takeAt(from);
620+ m_windowModel.insert(to, window);
621+#else
622+ m_windowModel.move(from, to);
623+#endif
624+ endMoveRows();
625+
626+ Q_EMIT listChanged();
627+ m_modelState = IdleState;
628+
629+ INFO_MSG << " after " << toString();
630+ }
631+}
632+void TopLevelWindowModel::onModificationsStarted()
633+{
634+}
635+
636+void TopLevelWindowModel::onModificationsEnded()
637+{
638+ if (m_focusedWindowChanged) {
639+ setFocusedWindow(m_newlyFocusedWindow);
640+ }
641+ // reset
642+ m_focusedWindowChanged = false;
643+ m_newlyFocusedWindow = nullptr;
644+}
plugins/WindowManager/TopLevelWindowModel.hnew file+258
@@ -0,0 +1,258 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TOPLEVELWINDOWMODEL_H
18+#define TOPLEVELWINDOWMODEL_H
19+
20+#include <QAbstractListModel>
21+#include <QLoggingCategory>
22+
23+Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
24+
25+class Window;
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class ApplicationInfoInterface;
31+ class ApplicationManagerInterface;
32+ class MirSurfaceInterface;
33+ class SurfaceManagerInterface;
34+ }
35+ }
36+}
37+
38+/**
39+ * @brief A model of top-level surfaces
40+ *
41+ * It's an abstraction of top-level application windows.
42+ *
43+ * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
44+ * still starting up. A shell should then display a splash screen or saved screenshot of the application
45+ * until its surface comes up.
46+ *
47+ * As applications can have multiple surfaces and you can also have entries without surfaces at all,
48+ * the only way to unambiguously refer to an entry in this model is through its id.
49+ */
50+class TopLevelWindowModel : public QAbstractListModel
51+{
52+ Q_OBJECT
53+
54+ /**
55+ * @brief Number of top-level surfaces in this model
56+ *
57+ * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
58+ */
59+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
60+
61+ /**
62+ * @brief The input method surface, if any
63+ *
64+ * The surface of a onscreen keyboard (akak "virtual keyboard") would be kept here and not in the model itself.
65+ */
66+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
67+
68+ /**
69+ * @brief The currently focused window, if any
70+ */
71+ Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
72+
73+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
74+ READ surfaceManager
75+ WRITE setSurfaceManager
76+ NOTIFY surfaceManagerChanged)
77+
78+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
79+ READ applicationManager
80+ WRITE setApplicationManager
81+ NOTIFY applicationManagerChanged)
82+
83+ /**
84+ The id to be used on the next entry created
85+ Useful for tests
86+ */
87+ Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
88+
89+public:
90+ /**
91+ * @brief The Roles supported by the model
92+ *
93+ * WindowRole - A Window.
94+ * ApplicationRole - An ApplicationInfoInterface
95+ */
96+ enum Roles {
97+ WindowRole = Qt::UserRole,
98+ ApplicationRole = Qt::UserRole + 1,
99+ };
100+
101+ TopLevelWindowModel();
102+
103+ // From QAbstractItemModel
104+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
105+ QVariant data(const QModelIndex& index, int role) const override;
106+ QHash<int, QByteArray> roleNames() const override {
107+ QHash<int, QByteArray> roleNames { {WindowRole, "window"},
108+ {ApplicationRole, "application"} };
109+ return roleNames;
110+ }
111+
112+ // Own API
113+
114+ unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
115+ Window* focusedWindow() const;
116+
117+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
118+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
119+
120+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
121+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
122+
123+ int nextId() const { return m_nextId; }
124+
125+public:
126+ /**
127+ * @brief Returns the surface at the given index
128+ *
129+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
130+ * and drawn into a surface.
131+ *
132+ * Same as windowAt(index).surface()
133+ */
134+ Q_INVOKABLE unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
135+
136+ /**
137+ * @brief Returns the window at the given index
138+ *
139+ * Will always be valid
140+ */
141+ Q_INVOKABLE Window *windowAt(int index) const;
142+
143+ /**
144+ * @brief Returns the application at the given index
145+ */
146+ Q_INVOKABLE unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
147+
148+ /**
149+ * @brief Returns the unique id of the element at the given index
150+ */
151+ Q_INVOKABLE int idAt(int index) const;
152+
153+ /**
154+ * @brief Returns the index where the row with the given id is located
155+ *
156+ * Returns -1 if there's no row with the given id.
157+ */
158+ Q_INVOKABLE int indexForId(int id) const;
159+
160+ /**
161+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
162+ */
163+ Q_INVOKABLE void raiseId(int id);
164+
165+Q_SIGNALS:
166+ void countChanged();
167+ void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
168+ void focusedWindowChanged(Window *focusedWindow);
169+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
170+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
171+
172+ /**
173+ * @brief Emitted when the list changes
174+ *
175+ * Emitted when model gains an element, loses an element or when elements exchange positions.
176+ */
177+ void listChanged();
178+
179+ void nextIdChanged();
180+
181+private Q_SLOTS:
182+ void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
183+ void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
184+
185+ void onModificationsStarted();
186+ void onModificationsEnded();
187+
188+private:
189+ void doRaiseId(int id);
190+ int generateId();
191+ int nextFreeId(int candidateId, const int latestId);
192+ int nextId(int id) const;
193+ QString toString();
194+ int indexOf(unity::shell::application::MirSurfaceInterface *surface);
195+
196+ void setInputMethodWindow(Window *window);
197+ void setFocusedWindow(Window *window);
198+ void removeInputMethodWindow();
199+ int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
200+ void removeAt(int index);
201+
202+ void addApplication(unity::shell::application::ApplicationInfoInterface *application);
203+ void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
204+
205+ void prependPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
206+ void prependSurface(unity::shell::application::MirSurfaceInterface *surface,
207+ unity::shell::application::ApplicationInfoInterface *application);
208+ void prependSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
209+ unity::shell::application::ApplicationInfoInterface *application);
210+
211+ void connectWindow(Window *window);
212+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
213+
214+ void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
215+ void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
216+
217+ void move(int from, int to);
218+
219+ void activateEmptyWindow(Window *window);
220+
221+ struct ModelEntry {
222+ ModelEntry() {}
223+ ModelEntry(Window *window,
224+ unity::shell::application::ApplicationInfoInterface *application)
225+ : window(window), application(application) {}
226+ Window *window{nullptr};
227+ unity::shell::application::ApplicationInfoInterface *application{nullptr};
228+ bool removeOnceSurfaceDestroyed{false};
229+ };
230+
231+ QVector<ModelEntry> m_windowModel;
232+ Window* m_inputMethodWindow{nullptr};
233+ Window* m_focusedWindow{nullptr};
234+
235+ int m_nextId{1};
236+ // Just something big enough that we don't risk running out of unused id numbers.
237+ // Not sure if QML int type supports something close to std::numeric_limits<int>::max() and
238+ // there's no reason to try out its limits.
239+ static const int m_maxId{1000000};
240+
241+ unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
242+ unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
243+
244+ enum ModelState {
245+ IdleState,
246+ InsertingState,
247+ RemovingState,
248+ MovingState,
249+ ResettingState
250+ };
251+ ModelState m_modelState{IdleState};
252+
253+ // Valid between modificationsStarted and modificationsEnded
254+ bool m_focusedWindowChanged{false};
255+ Window *m_newlyFocusedWindow{nullptr};
256+};
257+
258+#endif // TOPLEVELWINDOWMODEL_H
plugins/WindowManager/Window.cppnew file+234
@@ -0,0 +1,234 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Window.h"
18+
19+// unity-api
20+#include <unity/shell/application/MirSurfaceInterface.h>
21+
22+#include <QQmlEngine>
23+
24+namespace unityapi = unity::shell::application;
25+
26+Q_LOGGING_CATEGORY(UNITY_WINDOW, "unity.window", QtWarningMsg)
27+
28+#define DEBUG_MSG qCDebug(UNITY_WINDOW).nospace() << qPrintable(toString()) << "::" << __func__
29+
30+Window::Window(int id, QObject *parent)
31+ : QObject(parent)
32+ , m_id(id)
33+{
34+ DEBUG_MSG << "()";
35+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
36+}
37+
38+Window::~Window()
39+{
40+ DEBUG_MSG << "()";
41+}
42+
43+QPoint Window::position() const
44+{
45+ return m_position;
46+}
47+
48+QPoint Window::requestedPosition() const
49+{
50+ return m_requestedPosition;
51+}
52+
53+void Window::setRequestedPosition(const QPoint &value)
54+{
55+ m_positionRequested = true;
56+ if (value != m_requestedPosition) {
57+ m_requestedPosition = value;
58+ Q_EMIT requestedPositionChanged(m_requestedPosition);
59+ if (m_surface) {
60+ m_surface->setRequestedPosition(value);
61+ } else {
62+ // fake-miral: always comply
63+ m_position = m_requestedPosition;
64+ Q_EMIT positionChanged(m_position);
65+ }
66+ }
67+}
68+
69+Mir::State Window::state() const
70+{
71+ return m_state;
72+}
73+
74+bool Window::focused() const
75+{
76+ return m_focused;
77+}
78+
79+bool Window::confinesMousePointer() const
80+{
81+ if (m_surface) {
82+ return m_surface->confinesMousePointer();
83+ } else {
84+ return false;
85+ }
86+}
87+
88+int Window::id() const
89+{
90+ return m_id;
91+}
92+
93+unityapi::MirSurfaceInterface* Window::surface() const
94+{
95+ return m_surface;
96+}
97+
98+void Window::requestState(Mir::State state)
99+{
100+ m_stateRequested = true;
101+ if (m_surface) {
102+ m_surface->requestState(state);
103+ } else if (m_state != state) {
104+ m_state = state;
105+ Q_EMIT stateChanged(m_state);
106+ }
107+}
108+
109+void Window::close()
110+{
111+ if (m_surface) {
112+ m_surface->close();
113+ } else {
114+ Q_EMIT closeRequested();
115+ }
116+}
117+
118+void Window::activate()
119+{
120+ if (m_surface) {
121+ m_surface->activate();
122+ } else {
123+ Q_EMIT emptyWindowActivated();
124+ }
125+}
126+
127+void Window::setSurface(unityapi::MirSurfaceInterface *surface)
128+{
129+ DEBUG_MSG << "(" << surface << ")";
130+ if (m_surface) {
131+ disconnect(m_surface, 0, this, 0);
132+ }
133+
134+ m_surface = surface;
135+
136+ if (m_surface) {
137+ connect(surface, &unityapi::MirSurfaceInterface::focusRequested, this, [this]() {
138+ Q_EMIT focusRequested();
139+ });
140+
141+ connect(surface, &unityapi::MirSurfaceInterface::closeRequested, this, &Window::closeRequested);
142+
143+ connect(surface, &unityapi::MirSurfaceInterface::positionChanged, this, [this]() {
144+ updatePosition();
145+ });
146+
147+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [this]() {
148+ updateState();
149+ });
150+
151+ connect(surface, &unityapi::MirSurfaceInterface::focusedChanged, this, [this]() {
152+ updateFocused();
153+ });
154+
155+ // bring it up to speed
156+ if (m_positionRequested) {
157+ m_surface->setRequestedPosition(m_requestedPosition);
158+ }
159+ if (m_stateRequested) {
160+ m_surface->requestState(m_state);
161+ }
162+
163+ // and sync with surface
164+ updatePosition();
165+ updateState();
166+ updateFocused();
167+ }
168+
169+ Q_EMIT surfaceChanged(surface);
170+}
171+
172+void Window::updatePosition()
173+{
174+ if (m_surface->position() != m_position) {
175+ m_position = m_surface->position();
176+ Q_EMIT positionChanged(m_position);
177+ }
178+}
179+
180+void Window::updateState()
181+{
182+ if (m_surface->state() != m_state) {
183+ m_state = m_surface->state();
184+ Q_EMIT stateChanged(m_state);
185+ }
186+}
187+
188+void Window::updateFocused()
189+{
190+ if (m_surface->focused() != m_focused) {
191+ m_focused = m_surface->focused();
192+ Q_EMIT focusedChanged(m_focused);
193+ }
194+}
195+
196+void Window::setFocused(bool value)
197+{
198+ if (value != m_focused) {
199+ DEBUG_MSG << "(" << value << ")";
200+ m_focused = value;
201+ Q_EMIT focusedChanged(m_focused);
202+ // when we have a surface we get focus changes from updateFocused() instead
203+ Q_ASSERT(!m_surface);
204+ }
205+}
206+
207+QString Window::toString() const
208+{
209+ if (surface()) {
210+ return QString("Window[0x%1, id=%2, MirSurface[0x%3,\"%4\"]]").arg(
211+ QString::number((quintptr)this, 16),
212+ QString::number(id()),
213+ QString::number((quintptr)surface(), 16),
214+ surface()->name());
215+ } else {
216+ return QString("Window[0x%1, id=%2, null]").arg(
217+ QString::number((quintptr)this, 0, 16),
218+ QString::number(id()));
219+ }
220+}
221+
222+QDebug operator<<(QDebug dbg, const Window *window)
223+{
224+ QDebugStateSaver saver(dbg);
225+ dbg.nospace();
226+
227+ if (window) {
228+ dbg << qPrintable(window->toString());
229+ } else {
230+ dbg << (void*)(window);
231+ }
232+
233+ return dbg;
234+}
plugins/WindowManager/Window.hnew file+161
@@ -0,0 +1,161 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_WINDOW_H
18+#define UNITY_WINDOW_H
19+
20+#include <QLoggingCategory>
21+#include <QObject>
22+#include <QPoint>
23+
24+// Unity API
25+#include <unity/shell/application/Mir.h>
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class MirSurfaceInterface;
31+ }
32+ }
33+}
34+
35+
36+Q_DECLARE_LOGGING_CATEGORY(UNITY_WINDOW)
37+
38+/**
39+ @brief A slightly higher concept than MirSurface
40+
41+ A Window exists before its MirSurface gets created (for splashscreen purposes)
42+ and might also hang around after the backing surface is gone (In case the application
43+ was killed to free up memory, as it should still remain in the window list since the user
44+ did not explicitly close it).
45+ */
46+class Window : public QObject
47+{
48+ Q_OBJECT
49+
50+ /**
51+ * @brief Position of the current surface buffer, in pixels.
52+ */
53+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
54+
55+ /**
56+ * @brief Requested position of the current surface buffer, in pixels.
57+ */
58+ Q_PROPERTY(QPoint requestedPosition READ requestedPosition WRITE setRequestedPosition NOTIFY requestedPositionChanged)
59+
60+ /**
61+ * @brief State of the surface
62+ */
63+ Q_PROPERTY(Mir::State state READ state NOTIFY stateChanged)
64+
65+ /**
66+ * @brief Whether the surface is focused
67+ *
68+ * It will be true if this surface is MirFocusControllerInterface::focusedSurface
69+ */
70+ Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged)
71+
72+ /**
73+ * @brief Whether the surface wants to confine the mouse pointer within its boundaries
74+ *
75+ * If true, the surface doesn't want the mouse pointer to leave its boundaries while it's focused.
76+ */
77+ Q_PROPERTY(bool confinesMousePointer READ confinesMousePointer NOTIFY confinesMousePointerChanged)
78+
79+ /**
80+ * @brief A unique identifier for this window.
81+ * Useful for telling windows apart in a list model as they get moved around
82+ */
83+ Q_PROPERTY(int id READ id CONSTANT)
84+
85+ /**
86+ * @brief Surface backing up this window
87+ * It might be null if a surface hasn't been created yet (application is starting up) or if
88+ * the corresponding application has been killed (but can still get restarted to continue from
89+ * where it left)
90+ */
91+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
92+
93+public:
94+ Window(int id, QObject *parent = nullptr);
95+ virtual ~Window();
96+ QPoint position() const;
97+ QPoint requestedPosition() const;
98+ void setRequestedPosition(const QPoint &);
99+ Mir::State state() const;
100+ bool focused() const;
101+ bool confinesMousePointer() const;
102+ int id() const;
103+ unity::shell::application::MirSurfaceInterface* surface() const;
104+
105+ void setSurface(unity::shell::application::MirSurfaceInterface *surface);
106+ void setFocused(bool value);
107+
108+ QString toString() const;
109+
110+public Q_SLOTS:
111+ /**
112+ * @brief Requests a change to the specified state
113+ */
114+ void requestState(Mir::State state);
115+
116+ /**
117+ * @brief Sends a close request
118+ *
119+ */
120+ void close();
121+
122+ /**
123+ * @brief Focuses and raises the window
124+ */
125+ void activate();
126+
127+Q_SIGNALS:
128+ void closeRequested();
129+ void emptyWindowActivated();
130+
131+ void positionChanged(QPoint position);
132+ void requestedPositionChanged(QPoint position);
133+ void stateChanged(Mir::State value);
134+ void focusedChanged(bool value);
135+ void confinesMousePointerChanged(bool value);
136+ void surfaceChanged(unity::shell::application::MirSurfaceInterface *surface);
137+
138+ /**
139+ * @brief Emitted when focus for this window is requested by an external party
140+ */
141+ void focusRequested();
142+
143+private:
144+ void updatePosition();
145+ void updateState();
146+ void updateFocused();
147+
148+ QPoint m_position;
149+ QPoint m_requestedPosition;
150+ bool m_positionRequested{false};
151+ bool m_focused{false};
152+ int m_id;
153+ Mir::State m_state{Mir::RestoredState};
154+ bool m_stateRequested{false};
155+ unity::shell::application::MirSurfaceInterface *m_surface{nullptr};
156+};
157+
158+QDebug operator<<(QDebug dbg, const Window *window);
159+
160+Q_DECLARE_METATYPE(Window*)
161+#endif // UNITY_WINDOW_H
plugins/WindowManager/WindowManagerPlugin.cpp+5−2
@@ -16,13 +16,16 @@
1616
1717#include "WindowManagerPlugin.h"
1818
19−#include "TopLevelSurfaceList.h"
19+#include "TopLevelWindowModel.h"
20+#include "Window.h"
2021
2122#include <QtQml>
2223
2324void WindowManagerPlugin::registerTypes(const char *uri)
2425{
25− qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
26+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
27+
28+ qRegisterMetaType<Window*>("Window*");
2629
2730 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
2831}
qml/Components/InputMethod.qml+5−4
@@ -23,6 +23,8 @@ Item {
2323
2424 readonly property rect visibleRect: surfaceItem.surface && visible ? surfaceItem.surface.inputBounds : Qt.rect(0, 0, 0, 0)
2525
26+ property var surface
27+
2628 MirSurfaceItem {
2729 id: surfaceItem
2830 anchors.fill: parent
@@ -31,7 +33,7 @@ Item {
3133
3234 surfaceWidth: root.enabled ? width : -1
3335 surfaceHeight: root.enabled ? height : -1
34− surface: SurfaceManager.inputMethodSurface
36+ surface: root.surface
3537
3638 onLiveChanged: {
3739 if (surface !== null && !live) {
@@ -50,7 +52,6 @@ Item {
5052 }
5153
5254 visible: surfaceItem.surface &&
53− surfaceItem.surfaceState != Mir.HiddenState &&
54− surfaceItem.surfaceState != Mir.MinimizedState &&
55− root.enabled
55+ surfaceItem.surface.visible &&
56+ root.enabled
5657}
qml/Components/KeymapSwitcher.qml+4−1
@@ -23,6 +23,9 @@ import Unity.Application 0.1
2323QtObject {
2424 id: root
2525
26+ // to be set from outside
27+ property var focusedSurface: null
28+
2629 property GlobalShortcut shortcutNext: GlobalShortcut {
2730 shortcut: Qt.MetaModifier|Qt.Key_Space
2831 onTriggered: root.nextKeymap()
@@ -60,7 +63,7 @@ QtObject {
6063 }
6164
6265 property Binding surfaceKeymapBinding: Binding {
63− target: MirFocusController.focusedSurface
66+ target: root.focusedSurface
6467 property: "keymap"
6568 value: root.currentKeymap
6669 }
qml/Shell.qml+14−6
@@ -43,7 +43,7 @@ import Unity.Session 0.1
4343import Unity.DashCommunicator 0.1
4444import Unity.Indicators 0.1 as Indicators
4545import Cursor 1.1
46−import WindowManager 0.1
46+import WindowManager 1.0
4747
4848
4949StyledItem {
@@ -264,10 +264,15 @@ StyledItem {
264264 width: parent.width
265265 height: parent.height
266266
267− TopLevelSurfaceList {
267+ SurfaceManager {
268+ id: surfaceMan
269+ objectName: "surfaceManager"
270+ }
271+ TopLevelWindowModel {
268272 id: topLevelSurfaceList
269273 objectName: "topLevelSurfaceList"
270− applicationsModel: ApplicationManager
274+ applicationManager: ApplicationManager // it's a singleton
275+ surfaceManager: surfaceMan
271276 }
272277
273278 Stage {
@@ -318,6 +323,7 @@ StyledItem {
318323 InputMethod {
319324 id: inputMethod
320325 objectName: "inputMethod"
326+ surface: topLevelSurfaceList.inputMethodSurface
321327 anchors {
322328 fill: parent
323329 topMargin: panel.panelHeight
@@ -485,8 +491,8 @@ StyledItem {
485491 greeterShown: greeter.shown
486492 }
487493
488− readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
489− ? MirFocusController.focusedSurface.state === Mir.FullscreenState
494+ readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
495+ ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
490496 : false
491497 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
492498 || greeter.hasLockedApp
@@ -786,7 +792,9 @@ StyledItem {
786792 }
787793
788794 // non-visual objects
789− KeymapSwitcher {}
795+ KeymapSwitcher {
796+ focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
797+ }
790798 BrightnessControl {}
791799
792800 Rectangle {
qml/Stage/FakeMaximizeDelegate.qml+7−7
@@ -98,19 +98,19 @@ Rectangle {
9898 function commit() {
9999 if (progress > hintThreshold && edge != -1) {
100100 if (edge == Item.Top) {
101− target.maximize();
101+ target.requestMaximize();
102102 } else if (edge == Item.Left) {
103− target.maximizeLeft();
103+ target.requestMaximizeLeft();
104104 } else if (edge == Item.Right) {
105− target.maximizeRight();
105+ target.requestMaximizeRight();
106106 } else if (edge == Item.TopLeft) {
107− target.maximizeTopLeft();
107+ target.requestMaximizeTopLeft();
108108 } else if (edge == Item.TopRight) {
109− target.maximizeTopRight();
109+ target.requestMaximizeTopRight();
110110 } else if (edge == Item.BottomLeft) {
111− target.maximizeBottomLeft();
111+ target.requestMaximizeBottomLeft();
112112 } else if (edge == Item.BottomRight) {
113− target.maximizeBottomRight();
113+ target.requestMaximizeBottomRight();
114114 }
115115 } else {
116116 stop();
qml/Stage/MoveHandler.qml+1−1
@@ -114,7 +114,7 @@ QtObject {
114114 // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
115115 if (target.anyMaximized && !target.windowedTransitionRunning) {
116116 priv.progress = 0;
117− target.restore(false, WindowStateStorage.WindowStateNormal);
117+ target.requestRestore();
118118 }
119119
120120 var pos = mapToItem(target.parent, mouse.x, mouse.y);
qml/Stage/Stage.qml+117−110
@@ -119,8 +119,7 @@ FocusScope {
119119 }
120120 }
121121
122− property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
123− priv.focusedAppDelegate.surface.confinesMousePointer ?
122+ property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
124123 priv.focusedAppDelegate.clientAreaItem : null;
125124
126125 signal itemSnapshotRequested(Item item)
@@ -186,29 +185,34 @@ FocusScope {
186185 GlobalShortcut {
187186 id: maximizeWindowShortcut
188187 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
189− onTriggered: priv.focusedAppDelegate.maximize()
188+ onTriggered: priv.focusedAppDelegate.requestMaximize()
190189 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
191190 }
192191
193192 GlobalShortcut {
194193 id: maximizeWindowLeftShortcut
195194 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
196− onTriggered: priv.focusedAppDelegate.maximizeLeft()
195+ onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
197196 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
198197 }
199198
200199 GlobalShortcut {
201200 id: maximizeWindowRightShortcut
202201 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
203− onTriggered: priv.focusedAppDelegate.maximizeRight()
202+ onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
204203 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
205204 }
206205
207206 GlobalShortcut {
208207 id: minimizeRestoreShortcut
209208 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
210− onTriggered: priv.focusedAppDelegate.anyMaximized
211− ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
209+ onTriggered: {
210+ if (priv.focusedAppDelegate.anyMaximized) {
211+ priv.focusedAppDelegate.requestRestore();
212+ } else {
213+ priv.focusedAppDelegate.requestMinimize();
214+ }
215+ }
212216 active: root.state == "windowed" && priv.focusedAppDelegate
213217 }
214218
@@ -244,20 +248,10 @@ FocusScope {
244248 }
245249
246250 function minimizeAllWindows() {
247− for (var i = 0; i < appRepeater.count; i++) {
248− var appDelegate = appRepeater.itemAt(i);
249− if (appDelegate && !appDelegate.minimized) {
250− appDelegate.minimize();
251− }
252− }
253− }
254−
255− function focusNext() {
256− for (var i = 0; i < appRepeater.count; i++) {
251+ for (var i = appRepeater.count - 1; i >= 0; i--) {
257252 var appDelegate = appRepeater.itemAt(i);
258253 if (appDelegate && !appDelegate.minimized) {
259− appDelegate.focus = true;
260− return;
254+ appDelegate.requestMinimize();
261255 }
262256 }
263257 }
@@ -358,8 +352,8 @@ FocusScope {
358352 Connections {
359353 target: PanelState
360354 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
361− onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
362− onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
355+ onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
356+ onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
363357 }
364358
365359 Binding {
@@ -434,13 +428,6 @@ FocusScope {
434428 }
435429 }
436430
437− Binding {
438− target: MirFocusController
439− property: "focusedSurface"
440− value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
441− when: !appRepeater.startingUp && root.parent
442− }
443−
444431 states: [
445432 State {
446433 name: "spread"; when: priv.goneToSpread
@@ -633,7 +620,7 @@ FocusScope {
633620
634621 onShownChanged: {
635622 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
636− priv.mainStageDelegate.claimFocus();
623+ priv.mainStageDelegate.activate();
637624 }
638625 }
639626
@@ -666,14 +653,23 @@ FocusScope {
666653 }
667654 }
668655
669− TopLevelSurfaceRepeater {
656+ Repeater {
670657 id: appRepeater
671658 model: topLevelSurfaceList
672659 objectName: "appRepeater"
673660
661+ function indexOf(delegateItem) {
662+ for (var i = 0; i < count; i++) {
663+ if (itemAt(i) === delegateItem) {
664+ return i;
665+ }
666+ }
667+ return -1;
668+ }
669+
674670 delegate: FocusScope {
675671 id: appDelegate
676− objectName: "appDelegate_" + model.id
672+ objectName: "appDelegate_" + model.window.id
677673 property int itemIndex: index // We need this from outside the repeater
678674 // z might be overriden in some cases by effects, but we need z ordering
679675 // to calculate occlusion detection
@@ -685,11 +681,11 @@ FocusScope {
685681 }
686682 z: normalZ
687683
688− // Normally we want x/y where we request it to be. Width/height of our delegate will
684+ // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
689685 // match what the actual surface size is.
690686 // Don't write to those, they will be set by states
691− x: requestedX
692− y: requestedY
687+ x: model.window.position.x - clientAreaItem.x
688+ y: model.window.position.y - clientAreaItem.y
693689 width: decoratedWindow.implicitWidth
694690 height: decoratedWindow.implicitHeight
695691
@@ -699,6 +695,12 @@ FocusScope {
699695 property real requestedY: windowedY
700696 property real requestedWidth: windowedWidth
701697 property real requestedHeight: windowedHeight
698+ Binding {
699+ target: model.window; property: "requestedPosition"
700+ // miral doesn't know about our window decorations. So we have to deduct them
701+ value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
702+ appDelegate.requestedY + appDelegate.clientAreaItem.y)
703+ }
702704
703705 // In those are for windowed mode. Those values basically store the window's properties
704706 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
@@ -767,7 +769,7 @@ FocusScope {
767769 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
768770
769771 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
770− readonly property bool fullscreen: surface ? surface.state === Mir.FullscreenState : application.fullscreen
772+ readonly property bool fullscreen: window.state === Mir.FullscreenState
771773
772774 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
773775 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
@@ -793,7 +795,9 @@ FocusScope {
793795 priv.updateMainAndSideStageIndexes()
794796 }
795797
796− readonly property var surface: model.surface
798+ readonly property var surface: model.window.surface
799+ readonly property var window: model.window
800+
797801 readonly property alias resizeArea: resizeArea
798802 readonly property alias focusedSurface: decoratedWindow.focusedSurface
799803 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
@@ -802,6 +806,25 @@ FocusScope {
802806 readonly property bool isDash: appId == "unity8-dash"
803807 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
804808
809+ function activate() {
810+ if (model.window.focused) {
811+ updateQmlFocusFromMirSurfaceFocus();
812+ } else {
813+ model.window.activate();
814+ }
815+ }
816+ function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
817+ function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
818+ function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
819+ function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
820+ function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
821+ function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
822+ function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
823+ function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
824+ function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
825+ function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
826+ function requestRestore() { model.window.requestState(Mir.RestoredState); }
827+
805828 function claimFocus() {
806829 if (root.state == "spread") {
807830 spreadItem.highlightedIndex = index
@@ -813,45 +836,60 @@ FocusScope {
813836 }
814837 priv.updateMainAndSideStageIndexes();
815838 }
816−
817839 if (root.mode == "windowed") {
818840 appDelegate.restore(true /* animated */, appDelegate.windowState);
819− } else {
820− appDelegate.focus = true;
821841 }
842+ appDelegate.focus = true;
822843 }
823− Connections {
824− target: model.surface
825− onFocusRequested: {
844+
845+ function updateQmlFocusFromMirSurfaceFocus() {
846+ if (model.window.focused) {
826847 claimFocus();
848+ priv.focusedAppDelegate = appDelegate;
827849 }
828850 }
851+
829852 Connections {
830− target: model.application
853+ target: model.window
854+ onFocusedChanged: {
855+ updateQmlFocusFromMirSurfaceFocus();
856+ }
831857 onFocusRequested: {
832− if (!model.surface) {
833− // when an app has no surfaces, we assume there's only one entry representing it:
834− // this delegate.
835− claimFocus();
836− } else {
837− // if the application has surfaces, focus request should be at surface-level.
858+ appDelegate.activate();
859+ }
860+ onStateChanged: {
861+ if (model.window.state === Mir.MinimizedState) {
862+ appDelegate.minimize();
863+ } else if (model.window.state === Mir.MaximizedState) {
864+ appDelegate.maximize();
865+ } else if (model.window.state === Mir.VertMaximizedState) {
866+ appDelegate.maximizeVertically();
867+ } else if (model.window.state === Mir.HorizMaximizedState) {
868+ appDelegate.maximizeHorizontally();
869+ } else if (model.window.state === Mir.MaximizedLeftState) {
870+ appDelegate.maximizeLeft();
871+ } else if (model.window.state === Mir.MaximizedRightState) {
872+ appDelegate.maximizeRight();
873+ } else if (model.window.state === Mir.MaximizedTopLeftState) {
874+ appDelegate.maximizeTopLeft();
875+ } else if (model.window.state === Mir.MaximizedTopRightState) {
876+ appDelegate.maximizeTopRight();
877+ } else if (model.window.state === Mir.MaximizedBottomLeftState) {
878+ appDelegate.maximizeBottomLeft();
879+ } else if (model.window.state === Mir.MaximizedBottomRightState) {
880+ appDelegate.maximizeBottomRight();
881+ } else if (model.window.state === Mir.RestoredState) {
882+ if (decoratedWindow.dragging) {
883+ appDelegate.restore(false, WindowStateStorage.WindowStateNormal);
884+ } else if (appDelegate.anyMaximized) {
885+ appDelegate.restoreFromMaximized()
886+ } else {
887+ appDelegate.restore();
888+ }
838889 }
839890 }
840891 }
841892
842− onFocusChanged: {
843− if (appRepeater.startingUp)
844− return;
845−
846− if (focus) {
847− topLevelSurfaceList.raiseId(model.id);
848− priv.focusedAppDelegate = appDelegate;
849− } else if (!focus && priv.focusedAppDelegate === appDelegate && root.state != "spread") {
850− priv.focusedAppDelegate = null;
851− // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
852− MirFocusController.focusedSurface = null;
853− }
854− }
855893 Component.onCompleted: {
856894 if (application && application.rotatesWindowContents) {
857895 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -865,16 +903,7 @@ FocusScope {
865903 // Now load any saved state. This needs to happen *after* the cascading!
866904 resizeArea.loadWindowState();
867905
868− // NB: We're differentiating if this delegate was created in response to a new entry in the model
869− // or if the Repeater is just populating itself with delegates to match the model it received.
870− if (!appRepeater.startingUp) {
871− // a top level window is always the focused one when it first appears, unfocusing
872− // any preexisting one
873− if (root.state == "spread") {
874− spreadItem.highlightedIndex = index;
875− }
876− claimFocus();
877− }
906+ updateQmlFocusFromMirSurfaceFocus();
878907
879908 refreshStage();
880909 _constructing = false;
@@ -889,16 +918,6 @@ FocusScope {
889918 priv.updateForegroundMaximizedApp();
890919 }
891920
892− if (focus) {
893− // focus some other window
894− for (var i = 0; i < appRepeater.count; i++) {
895− var appDelegate = appRepeater.itemAt(i);
896− if (appDelegate && !appDelegate.minimized && i != index) {
897− appDelegate.focus = true;
898− return;
899− }
900− }
901− }
902921 }
903922
904923 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
@@ -919,7 +938,7 @@ FocusScope {
919938 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
920939
921940 function close() {
922− model.surface.close();
941+ model.window.close();
923942 }
924943
925944 function maximize(animated) {
@@ -970,7 +989,6 @@ FocusScope {
970989 animationsEnabled = (animated === undefined) || animated;
971990 windowState = state || WindowStateStorage.WindowStateRestored;
972991 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
973− focus = true;
974992 }
975993
976994 function playFocusAnimation() {
@@ -984,7 +1002,7 @@ FocusScope {
9841002 rightEdgeFocusAnimation.start()
9851003 }
9861004 } else if (state == "windowedRightEdge" || state == "windowed") {
987− claimFocus();
1005+ activate();
9881006 } else {
9891007 focusAnimation.start()
9901008 }
@@ -1022,10 +1040,10 @@ FocusScope {
10221040 to: 1
10231041 duration: UbuntuAnimation.SnapDuration
10241042 onStarted: {
1025− topLevelSurfaceList.raiseId(model.id);
1043+ topLevelSurfaceList.raiseId(model.window.id);
10261044 }
10271045 onStopped: {
1028− appDelegate.claimFocus();
1046+ appDelegate.activate();
10291047 }
10301048 }
10311049 ParallelAnimation {
@@ -1035,7 +1053,7 @@ FocusScope {
10351053 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
10361054 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
10371055 onStopped: {
1038− appDelegate.focus = true
1056+ appDelegate.activate();
10391057 }
10401058 }
10411059 ParallelAnimation {
@@ -1399,14 +1417,6 @@ FocusScope {
13991417 SequentialAnimation {
14001418 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
14011419 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1402− ScriptAction {
1403− script: {
1404− if (appDelegate.minimized) {
1405− appDelegate.focus = false;
1406− priv.focusNext();
1407− }
1408− }
1409− }
14101420 }
14111421 },
14121422 Transition {
@@ -1497,7 +1507,7 @@ FocusScope {
14971507 visible: enabled
14981508
14991509 onPressed: {
1500− appDelegate.focus = true;
1510+ appDelegate.activate();
15011511 }
15021512
15031513 Component.onDestruction: {
@@ -1511,7 +1521,7 @@ FocusScope {
15111521 anchors.left: appDelegate.left
15121522 anchors.top: appDelegate.top
15131523 application: model.application
1514− surface: model.surface
1524+ surface: model.window.surface
15151525 active: appDelegate.focus
15161526 focus: true
15171527 interactive: root.interactive
@@ -1536,21 +1546,21 @@ FocusScope {
15361546 onCloseClicked: { appDelegate.close(); }
15371547 onMaximizeClicked: {
15381548 if (appDelegate.canBeMaximized) {
1539− appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
1549+ appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
15401550 }
15411551 }
15421552 onMaximizeHorizontallyClicked: {
15431553 if (appDelegate.canBeMaximizedHorizontally) {
1544− appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
1554+ appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
15451555 }
15461556 }
15471557 onMaximizeVerticallyClicked: {
15481558 if (appDelegate.canBeMaximizedVertically) {
1549− appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
1559+ appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
15501560 }
15511561 }
1552− onMinimizeClicked: appDelegate.minimize()
1553− onDecorationPressed: { appDelegate.focus = true; }
1562+ onMinimizeClicked: { appDelegate.requestMinimize(); }
1563+ onDecorationPressed: { appDelegate.activate(); }
15541564 onDecorationReleased: fakeRectangle.commit();
15551565
15561566 property real angle: 0
@@ -1597,12 +1607,12 @@ FocusScope {
15971607 WindowedFullscreenPolicy {
15981608 id: windowedFullscreenPolicy
15991609 active: root.mode == "windowed"
1600− surface: model.surface
1610+ surface: model.window.surface
16011611 }
16021612 StagedFullscreenPolicy {
16031613 id: stagedFullscreenPolicy
16041614 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1605− surface: model.surface
1615+ surface: model.window.surface
16061616 }
16071617
16081618 SpreadDelegateInputArea {
@@ -1615,16 +1625,13 @@ FocusScope {
16151625 onClicked: {
16161626 spreadItem.highlightedIndex = index;
16171627 if (distance == 0) {
1628+ model.window.activate();
16181629 priv.goneToSpread = false;
16191630 }
16201631 }
16211632 onClose: {
16221633 priv.closingIndex = index
1623− if (model.surface) { // could be stopped by OOM
1624− model.surface.close()
1625− } else if (model.application) {
1626− root.applicationManager.stopApplication(model.application.appId);
1627− }
1634+ model.window.close();
16281635 }
16291636 }
16301637
qml/Stage/StagedFullscreenPolicy.qml+4−4
@@ -32,7 +32,7 @@ QtObject {
3232 onSurfaceChanged: {
3333 if (!active || !surface) return;
3434 if (surface.shellChrome === Mir.LowChrome) {
35− surface.state = Mir.FullscreenState;
35+ surface.requestState(Mir.FullscreenState);
3636 }
3737 }
3838
@@ -41,15 +41,15 @@ QtObject {
4141 onShellChromeChanged: {
4242 if (!active || !surface) return;
4343 if (surface.shellChrome === Mir.LowChrome) {
44− surface.state = Mir.FullscreenState;
44+ surface.requestState(Mir.FullscreenState);
4545 } else {
46− surface.state = Mir.RestoredState;
46+ surface.requestState(Mir.RestoredState);
4747 }
4848 }
4949 onStateChanged: {
5050 if (!active) return;
5151 if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
52− surface.state = Mir.FullscreenState;
52+ surface.requestState(Mir.FullscreenState);
5353 }
5454 }
5555 }
qml/Stage/TopLevelSurfaceRepeater.qmldeleted−67
@@ -1,67 +0,0 @@
1−/*
2− * Copyright (C) 2016 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−
19−Repeater {
20− id: root
21− // FIXME: This is a hack around us not knowing whether the Repeater has finished creating its
22− // delegates on start up.
23− // This is a problem when the stage gets a TopLevelSurfaceList already populated with several
24− // rows.
25− property bool startingUp: true
26− onStartingUpChanged: {
27− if (!startingUp) {
28− // the top-most surface must be the focused one.
29− var topmostDelegate = itemAt(0);
30− if (topmostDelegate.focus) {
31− // Delegate got focused while we were still starting up. Not good.
32− // Force signal handler to run again
33− topmostDelegate.onFocusChanged(true);
34− } else {
35− topmostDelegate.focus = true;
36− }
37− }
38− }
39−
40− onItemAdded: {
41− if (startingUp) {
42− checkIfStillStartingUp();
43− }
44− }
45−
46− function checkIfStillStartingUp() {
47− var i = 0;
48− var missingDelegate = false;
49− for (i = 0; i < model.count && !missingDelegate; ++i) {
50− if (!itemAt(i)) {
51− missingDelegate = true;
52− }
53− }
54− if (!missingDelegate) {
55− startingUp = false;
56− }
57− }
58−
59− function indexOf(delegateItem) {
60− for (var i = 0; i < count; i++) {
61− if (itemAt(i) === delegateItem) {
62− return i;
63− }
64− }
65− return -1;
66− }
67−}
qml/Stage/WindowControlsOverlay.qml+1−1
@@ -57,7 +57,7 @@ Item {
5757 touchPoints.length <= maximumTouchPoints
5858 onRecognizedPressChanged: {
5959 if (recognizedPress) {
60− target.focus = true;
60+ target.activate();
6161 overlayTimer.start();
6262 }
6363 }
qml/Stage/WindowedFullscreenPolicy.qml+1−1
@@ -33,7 +33,7 @@ QtObject {
3333 _firstTimeSurface = false;
3434
3535 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
36− surface.state = Mir.RestoredState;
36+ surface.requestState(Mir.RestoredState);
3737 }
3838 }
3939}
tests/mocks/Unity/Application/ApplicationInfo.cpp+48−24
@@ -75,6 +75,33 @@ ApplicationInfo::~ApplicationInfo()
7575{
7676}
7777
78+void ApplicationInfo::createPromptSurface()
79+{
80+ if (state() == ApplicationInfo::Stopped) { return; }
81+
82+ auto surfaceManager = SurfaceManager::instance();
83+ if (!surfaceManager) {
84+ WARNING_MSG("No SurfaceManager");
85+ return;
86+ }
87+
88+ QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
89+ int i = rand() % screenshotIds.count();
90+
91+ QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
92+ .arg(screenshotIds[i]);
93+
94+ auto surface = surfaceManager->createSurface(QString("prompt foo"),
95+ Mir::NormalType,
96+ Mir::RestoredState,
97+ screenshotUrl);
98+ surfaceManager->notifySurfaceCreated(surface);
99+
100+ m_promptSurfaceList->addSurface(surface);
101+
102+ surfaceManager->activate(surface);
103+}
104+
78105void ApplicationInfo::createSurface()
79106{
80107 if (state() == ApplicationInfo::Stopped) { return; }
@@ -90,14 +117,16 @@ void ApplicationInfo::createSurface()
90117 return;
91118 }
92119
120+ bool wasFocused = focused();
121+
93122 auto surface = surfaceManager->createSurface(surfaceName,
94123 Mir::NormalType,
95− fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
124+ fullscreen() ? Mir::FullscreenState : Mir::RestoredState,
96125 m_screenshotFileName);
97126
98127 surface->setShellChrome(m_shellChrome);
99128
100− m_surfaceList->appendSurface(surface);
129+ m_surfaceList->addSurface(surface);
101130
102131 ++m_liveSurfaceCount;
103132 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
@@ -125,8 +154,19 @@ void ApplicationInfo::createSurface()
125154 setState(Running);
126155 }
127156 });
157+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [&](bool /*value*/) {
158+ #if APPLICATION_DEBUG
159+ qDebug().nospace() << "Application[" << appId() << "].focusedChanged(" << focused() << ")";
160+ #endif
161+ Q_EMIT focusedChanged(focused());
162+ });
163+
128164 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
129165
166+ if (wasFocused != focused()) {
167+ Q_EMIT focusedChanged(focused());
168+ }
169+
130170 if (m_state == Starting) {
131171 if (m_requestedState == RequestedRunning) {
132172 setState(Running);
@@ -134,6 +174,9 @@ void ApplicationInfo::createSurface()
134174 setState(Suspended);
135175 }
136176 }
177+
178+ surfaceManager->notifySurfaceCreated(surface);
179+ surfaceManager->activate(surface);
137180}
138181
139182void ApplicationInfo::setIconId(const QString &iconId)
@@ -218,7 +261,7 @@ void ApplicationInfo::setFullscreen(bool value)
218261{
219262 m_fullscreen = value;
220263 if (m_surfaceList->rowCount() > 0) {
221− m_surfaceList->get(0)->setState(Mir::FullscreenState);
264+ m_surfaceList->get(0)->requestState(Mir::FullscreenState);
222265 }
223266}
224267
@@ -349,26 +392,6 @@ bool ApplicationInfo::focused() const
349392 return someSurfaceHasFocus;
350393}
351394
352−void ApplicationInfo::setFocused(bool value)
353−{
354− if (focused() == value) {
355− return;
356− }
357−
358− if (value) {
359− if (m_surfaceList->count() > 0) {
360− m_surfaceList->get(0)->requestFocus();
361− }
362− } else {
363− for (int i = 0; i < m_surfaceList->count(); ++i) {
364− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
365− if (surface->focused()) {
366− surface->setFocused(false);
367− }
368− }
369− }
370−}
371−
372395void ApplicationInfo::onSurfaceCountChanged()
373396{
374397 if (m_surfaceList->count() == 0 && m_state == Running) {
@@ -381,6 +404,7 @@ void ApplicationInfo::requestFocus()
381404 if (m_surfaceList->count() == 0) {
382405 Q_EMIT focusRequested();
383406 } else {
384− m_surfaceList->get(0)->requestFocus();
407+ auto surface = static_cast<MirSurface*>(m_surfaceList->get(0));
408+ surface->requestFocus();
385409 }
386410}
tests/mocks/Unity/Application/ApplicationInfo.h+2−2
@@ -51,6 +51,8 @@ public:
5151 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
5252 ~ApplicationInfo();
5353
54+ Q_INVOKABLE void createPromptSurface();
55+
5456 RequestedState requestedState() const override;
5557 void setRequestedState(RequestedState) override;
5658
@@ -108,8 +110,6 @@ public:
108110 MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
109111 int surfaceCount() const override { return m_surfaceList->count(); }
110112
111− void setFocused(bool value);
112−
113113 //////
114114 // internal mock stuff
115115 void close();
tests/mocks/Unity/Application/ApplicationManager.cpp+88−59
@@ -34,29 +34,23 @@
3434
3535#if APPLICATIONMANAGER_DEBUG
3636#define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
37+#define XDEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << params
3738#else
3839#define DEBUG_MSG(params) ((void)0)
40+#define XDEBUG_MSG(params) ((void)0)
3941#endif
4042
4143namespace unityapi = unity::shell::application;
4244
45+
4346ApplicationManager::ApplicationManager(QObject *parent)
4447 : ApplicationManagerInterface(parent)
4548{
4649 DEBUG_MSG("");
47− buildListOfAvailableApplications();
4850
49− // polling to find out when the toplevel window has been created as there's
50− // no signal telling us that
51− connect(&m_windowCreatedTimer, &QTimer::timeout,
52− this, &ApplicationManager::onWindowCreatedTimerTimeout);
53− m_windowCreatedTimer.setSingleShot(false);
54− m_windowCreatedTimer.start(200);
55−
56− Q_ASSERT(MirFocusController::instance());
57− connect(MirFocusController::instance(), &MirFocusController::focusedSurfaceChanged,
58− this, &ApplicationManager::updateFocusedApplication, Qt::QueuedConnection);
51+ ApplicationManagerNotifier::instance()->setApplicationManager(this);
5952
53+ buildListOfAvailableApplications();
6054
6155 // Emit signal to notify Upstart that Mir is ready to receive client connections
6256 // see http://upstart.ubuntu.com/cookbook/#expect-stop
@@ -69,19 +63,7 @@ ApplicationManager::ApplicationManager(QObject *parent)
6963
7064ApplicationManager::~ApplicationManager()
7165{
72−}
73−
74−void ApplicationManager::onWindowCreatedTimerTimeout()
75−{
76− if (QGuiApplication::topLevelWindows().count() > 0) {
77− m_windowCreatedTimer.stop();
78− onWindowCreated();
79− }
80−}
81−
82−void ApplicationManager::onWindowCreated()
83−{
84− startApplication("unity8-dash");
66+ ApplicationManagerNotifier::instance()->setApplicationManager(nullptr);
8567}
8668
8769int ApplicationManager::rowCount(const QModelIndex& parent) const {
@@ -132,6 +114,17 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
132114 return nullptr;
133115}
134116
117+unityapi::ApplicationInfoInterface *ApplicationManager::findApplicationWithSurface(unityapi::MirSurfaceInterface* surface)
118+{
119+ for (ApplicationInfo *app : m_runningApplications) {
120+ auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
121+ if (surfaceList->contains(static_cast<MirSurface*>(surface))) {
122+ return app;
123+ }
124+ }
125+ return nullptr;
126+}
127+
135128QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
136129{
137130 for (int i = 0; i < m_runningApplications.size(); ++i) {
@@ -158,6 +151,11 @@ bool ApplicationManager::add(ApplicationInfo *application) {
158151 QModelIndex appIndex = findIndex(application);
159152 if (!appIndex.isValid()) return;
160153 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
154+ XDEBUG_MSG("focusedApplicationId = " << focusedApplicationId());
155+ Q_EMIT focusedApplicationIdChanged();
156+ if (application->focused()) {
157+ raiseApp(application->appId());
158+ }
161159 });
162160 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
163161 QModelIndex appIndex = findIndex(application);
@@ -181,15 +179,40 @@ bool ApplicationManager::add(ApplicationInfo *application) {
181179
182180void ApplicationManager::remove(ApplicationInfo *application) {
183181 int i = m_runningApplications.indexOf(application);
182+ application->disconnect(this);
184183 if (i != -1) {
185184 DEBUG_MSG(application->appId());
185+ Q_ASSERT(!m_modelBusy);
186+ m_modelBusy = true;
186187 beginRemoveRows(QModelIndex(), i, i);
187188 m_runningApplications.removeAt(i);
188189 endRemoveRows();
190+ m_modelBusy = false;
189191 Q_EMIT countChanged();
190192 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
193+ DEBUG_MSG(application->appId() << " after: " << qPrintable(toString()));
194+ }
195+}
196+
197+void ApplicationManager::raiseApp(const QString &appId)
198+{
199+
200+ int index = -1;
201+ for (int i = 0; i < m_runningApplications.count() && index == -1; ++i) {
202+ if (m_runningApplications[i]->appId() == appId) {
203+ index = i;
204+ }
205+ }
206+
207+ if (index >= 0) {
208+ if (m_modelBusy) {
209+ DEBUG_MSG(appId << " - model busy. Try again later.");
210+ QMetaObject::invokeMethod(this, "raiseApp", Qt::QueuedConnection, Q_ARG(QString, appId));
211+ } else {
212+ DEBUG_MSG(appId);
213+ move(index, 0);
214+ }
191215 }
192− application->disconnect(this);
193216}
194217
195218void ApplicationManager::move(int from, int to) {
@@ -197,12 +220,15 @@ void ApplicationManager::move(int from, int to) {
197220
198221 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
199222 QModelIndex parent;
223+ Q_ASSERT(!m_modelBusy);
224+ m_modelBusy = true;
200225 /* When moving an item down, the destination index needs to be incremented
201226 * by one, as explained in the documentation:
202227 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
203228 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
204229 m_runningApplications.move(from, to);
205230 endMoveRows();
231+ m_modelBusy = false;
206232 }
207233}
208234
@@ -224,8 +250,6 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
224250 if (!application)
225251 return nullptr;
226252
227− Q_EMIT application->focusRequested(); // we assume that an application that's starting up wants focus
228−
229253 return application;
230254}
231255
@@ -473,47 +497,52 @@ bool ApplicationManager::isEmpty() const
473497 return m_runningApplications.isEmpty();
474498}
475499
476−void ApplicationManager::updateFocusedApplication()
500+ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
477501{
478− ApplicationInfo *focusedApplication = nullptr;
479− ApplicationInfo *previouslyFocusedApplication = nullptr;
480−
481− auto controller = MirFocusController::instance();
482− if (!controller) {
483− return;
502+ for (ApplicationInfo *app : m_runningApplications) {
503+ auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
504+ if (surfaceList->contains(surface)) {
505+ return app;
506+ }
484507 }
508+ return nullptr;
509+}
485510
486− MirSurface *surface = static_cast<MirSurface*>(controller->focusedSurface());
487− if (surface) {
488− focusedApplication = findApplication(surface);
489− }
511+QString ApplicationManager::toString()
512+{
513+ QString str;
514+ for (int i = 0; i < m_runningApplications.count(); ++i) {
515+ auto *application = m_runningApplications.at(i);
490516
491− surface = static_cast<MirSurface*>(controller->previouslyFocusedSurface());
492− if (surface) {
493− previouslyFocusedApplication = findApplication(surface);
494− }
517+ QString itemStr = QString("(index=%1,appId=%2)")
518+ .arg(i)
519+ .arg(application->appId());
495520
496− if (focusedApplication != previouslyFocusedApplication) {
497− if (focusedApplication) {
498− DEBUG_MSG("focused " << focusedApplication->appId());
499− Q_EMIT focusedApplication->focusedChanged(true);
500− this->move(this->m_runningApplications.indexOf(focusedApplication), 0);
521+ if (i > 0) {
522+ str.append(",");
501523 }
502− if (previouslyFocusedApplication) {
503− DEBUG_MSG("unfocused " << previouslyFocusedApplication->appId());
504− Q_EMIT previouslyFocusedApplication->focusedChanged(false);
505− }
506− Q_EMIT focusedApplicationIdChanged();
524+ str.append(itemStr);
507525 }
526+ return str;
508527}
509528
510−ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
529+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
530+// ApplicationManagerNotifier
531+
532+ApplicationManagerNotifier *ApplicationManagerNotifier::m_instance = nullptr;
533+
534+ApplicationManagerNotifier *ApplicationManagerNotifier::instance()
511535{
512− for (ApplicationInfo *app : m_runningApplications) {
513− auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
514− if (surfaceList->contains(surface)) {
515− return app;
516− }
536+ if (!m_instance) {
537+ m_instance = new ApplicationManagerNotifier;
538+ }
539+ return m_instance;
540+}
541+
542+void ApplicationManagerNotifier::setApplicationManager(ApplicationManager *appMan)
543+{
544+ if (appMan != m_applicationManager) {
545+ m_applicationManager = appMan;
546+ Q_EMIT applicationManagerChanged(m_applicationManager);
517547 }
518− return nullptr;
519548}
tests/mocks/Unity/Application/ApplicationManager.h+31−4
@@ -50,8 +50,11 @@ class ApplicationManager : public ApplicationManagerInterface {
5050 // QAbstractItemModel methods.
5151 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
5252 QVariant data(const QModelIndex& index, int role) const override;
53+
54+ // ApplicationManagerInterface methods
5355 Q_INVOKABLE ApplicationInfo *get(int index) const override;
5456 Q_INVOKABLE ApplicationInfo *findApplication(const QString &appId) const override;
57+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override;
5558
5659 Q_INVOKABLE void move(int from, int to);
5760
@@ -76,18 +79,42 @@ class ApplicationManager : public ApplicationManagerInterface {
7679 void availableApplicationsChanged(QStringList list);
7780
7881 private Q_SLOTS:
79− void onWindowCreatedTimerTimeout();
80− void updateFocusedApplication();
82+ void raiseApp(const QString &appId);
8183
8284 private:
8385 bool add(ApplicationInfo *application);
8486 void remove(ApplicationInfo* application);
8587 void buildListOfAvailableApplications();
86− void onWindowCreated();
88+ QString toString();
8789 ApplicationInfo *findApplication(MirSurface* surface);
8890 QList<ApplicationInfo*> m_runningApplications;
8991 QList<ApplicationInfo*> m_availableApplications;
90− QTimer m_windowCreatedTimer;
92+ bool m_modelBusy{false};
93+};
94+
95+/*
96+ Lifecycle of the ApplicationManager instance belongs to the QML plugin.
97+ So this guy here is used to notify other parts of the system when the plugin creates and destroys
98+ the ApplicationManager.
99+
100+ Unlike ApplicationManager, we create ApplicationManagerNotifier whenever we want.
101+ */
102+class ApplicationManagerNotifier : public QObject {
103+ Q_OBJECT
104+public:
105+ static ApplicationManagerNotifier *instance();
106+
107+ ApplicationManager *applicationManager() { return m_applicationManager; }
108+
109+Q_SIGNALS:
110+ void applicationManagerChanged(ApplicationManager *applicationManager);
111+
112+private:
113+ void setApplicationManager(ApplicationManager *);
114+ static ApplicationManagerNotifier *m_instance;
115+ ApplicationManager *m_applicationManager{nullptr};
116+
117+friend class ApplicationManager;
91118};
92119
93120Q_DECLARE_METATYPE(ApplicationManager*)
tests/mocks/Unity/Application/CMakeLists.txt+1−1
@@ -15,7 +15,7 @@ set(FakeUnityApplicationQml_SOURCES
1515 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
1616 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h
1717 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
18− ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirFocusControllerInterface.h
18+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
1919 resources/surfaces.qrc
2020)
2121
tests/mocks/Unity/Application/MirSurface.cpp+80−93
@@ -19,14 +19,18 @@
1919#include <QDebug>
2020#include <QQmlEngine>
2121
22+// local
23+#include "SurfaceManager.h"
24+
2225#define MIRSURFACE_DEBUG 0
2326
2427#if MIRSURFACE_DEBUG
2528#define DEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << __func__ << " " << params
29+#define XDEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << params
2630
2731const char *stateToStr(Mir::State state)
2832{
29− switch(state) {
33+ switch (state) {
3034 case Mir::UnknownState:
3135 return "unknown";
3236 case Mir::RestoredState:
@@ -36,26 +40,37 @@ const char *stateToStr(Mir::State state)
3640 case Mir::MaximizedState:
3741 return "maximized";
3842 case Mir::VertMaximizedState:
39− return "vert-maximized";
43+ return "vertMaximized";
4044 case Mir::FullscreenState:
4145 return "fullscreen";
4246 case Mir::HorizMaximizedState:
43− return "horiz-maximized";
47+ return "horizMaximized";
48+ case Mir::MaximizedLeftState:
49+ return "maximizedLeft";
50+ case Mir::MaximizedRightState:
51+ return "maximizedRight";
52+ case Mir::MaximizedTopLeftState:
53+ return "maximizedTopLeft";
54+ case Mir::MaximizedTopRightState:
55+ return "maximizedTopRight";
56+ case Mir::MaximizedBottomLeftState:
57+ return "maximizedBottomLeft";
58+ case Mir::MaximizedBottomRightState:
59+ return "maximizedBottomRight";
4460 case Mir::HiddenState:
4561 return "hidden";
4662 default:
4763 return "???";
48− };
64+ }
4965}
5066
5167#else
5268#define DEBUG_MSG(params) ((void)0)
69+#define XDEBUG_MSG(params) ((void)0)
5370#endif
5471
5572using namespace unity::shell::application;
5673
57−MirFocusController *MirFocusController::m_instance = nullptr;
58−
5974MirSurface::MirSurface(const QString& name,
6075 Mir::Type type,
6176 Mir::State state,
@@ -69,7 +84,7 @@ MirSurface::MirSurface(const QString& name,
6984 , m_screenshotUrl(screenshot)
7085 , m_qmlFilePath(qmlFilePath)
7186 , m_live(true)
72− , m_visible(true)
87+ , m_focused(false)
7388 , m_activeFocus(false)
7489 , m_width(-1)
7590 , m_height(-1)
@@ -95,11 +110,8 @@ MirSurface::~MirSurface()
95110{
96111 DEBUG_MSG("");
97112
98− // controller instance might have been already destroyed by QQmlEngine destructor
99− auto controller = MirFocusController::instance();
100− if (controller && controller->focusedSurface() == this) {
101− controller->clear();
102− }
113+ // Early warning, while MirSurface methods can still be accessed.
114+ Q_EMIT destroyed(this);
103115}
104116
105117QString MirSurface::name() const
@@ -122,14 +134,31 @@ Mir::State MirSurface::state() const
122134 return m_state;
123135}
124136
125−void MirSurface::setState(Mir::State state)
137+void MirSurface::requestState(Mir::State state)
126138{
127− if (state == m_state)
139+ if (state == m_state) {
128140 return;
141+ }
142+ DEBUG_MSG(stateToStr(state));
143+ Q_EMIT stateRequested(state);
144+}
129145
146+void MirSurface::setState(Mir::State state)
147+{
148+ if (state == m_state) {
149+ return;
150+ }
130151 DEBUG_MSG(stateToStr(state));
152+
153+ bool oldVisible = visible();
154+
131155 m_state = state;
132156 Q_EMIT stateChanged(state);
157+
158+ if (visible() != oldVisible) {
159+ XDEBUG_MSG("visibleChanged("<<visible()<<")");
160+ Q_EMIT visibleChanged(visible());
161+ }
133162}
134163
135164bool MirSurface::live() const
@@ -139,7 +168,7 @@ bool MirSurface::live() const
139168
140169bool MirSurface::visible() const
141170{
142− return m_visible;
171+ return m_state != Mir::MinimizedState && m_state != Mir::HiddenState;
143172}
144173
145174void MirSurface::setLive(bool live)
@@ -231,31 +260,30 @@ void MirSurface::unregisterView(qintptr viewId)
231260 if (!m_live && m_views.count() == 0) {
232261 deleteLater();
233262 }
234− updateVisibility();
263+ updateExposure();
235264}
236265
237−void MirSurface::setViewVisibility(qintptr viewId, bool visible)
266+void MirSurface::setViewExposure(qintptr viewId, bool visible)
238267{
239268 if (!m_views.contains(viewId)) return;
240269
241270 m_views[viewId].visible = visible;
242− updateVisibility();
271+ updateExposure();
243272}
244273
245−void MirSurface::updateVisibility()
274+void MirSurface::updateExposure()
246275{
247− bool newVisible = false;
276+ bool newExposure = false;
248277 QHashIterator<qintptr, View> i(m_views);
249278 while (i.hasNext()) {
250279 i.next();
251− newVisible |= i.value().visible;
280+ newExposure |= i.value().visible;
252281 }
253282
254− if (newVisible != visible()) {
255−// qDebug().nospace() << "MirSurface[" << name() << "]::updateVisibility(" << newVisible << ")";
256−
257− m_visible = newVisible;
258− Q_EMIT visibleChanged(m_visible);
283+ if (newExposure != m_exposed) {
284+ m_exposed = newExposure;
285+ DEBUG_MSG(m_exposed);
286+ Q_EMIT exposedChanged(m_exposed);
259287 updateInputBoundsAfterResize();
260288 }
261289}
@@ -273,6 +301,10 @@ void MirSurface::setActiveFocus(bool value)
273301 m_activeFocus = value;
274302
275303 Q_EMIT activeFocusChanged(value);
304+
305+ if (m_activeFocus && !m_focused) {
306+ requestFocus();
307+ }
276308}
277309
278310int MirSurface::width() const
@@ -332,6 +364,7 @@ void MirSurface::doResize(int width, int height)
332364 }
333365
334366 if (changed) {
367+ XDEBUG_MSG("sizeChanged(width="<<width<<", height="<<height<<")");
335368 Q_EMIT sizeChanged(QSize(width, height));
336369 }
337370
@@ -409,11 +442,6 @@ void MirSurface::setHeightIncrement(int value)
409442 }
410443}
411444
412−void MirSurface::raise()
413−{
414− Q_EMIT raiseRequested();
415−}
416−
417445void MirSurface::close()
418446{
419447 DEBUG_MSG("");
@@ -423,6 +451,12 @@ void MirSurface::close()
423451 }
424452}
425453
454+void MirSurface::activate()
455+{
456+ DEBUG_MSG("");
457+ SurfaceManager::instance()->activate(this);
458+}
459+
426460void MirSurface::requestFocus()
427461{
428462 DEBUG_MSG("");
@@ -431,27 +465,18 @@ void MirSurface::requestFocus()
431465
432466void MirSurface::setFocused(bool value)
433467{
434− DEBUG_MSG(value);
435−
436− auto controller = MirFocusController::instance();
437− // controller instance might have been already destroyed by QQmlEngine destructor
438− if (!controller) {
468+ if (m_focused == value)
439469 return;
440− }
441470
442− if (value) {
443− controller->setFocusedSurface(this);
444− } else if (controller->focusedSurface() == this) {
445− controller->setFocusedSurface(nullptr);
446− }
471+ DEBUG_MSG("(" << value << ")");
472+
473+ m_focused = value;
474+ Q_EMIT focusedChanged(value);
447475}
448476
449477bool MirSurface::focused() const
450478{
451− auto controller = MirFocusController::instance();
452−
453− // controller instance might have been already destroyed by QQmlEngine destructor
454− return controller ? controller->focusedSurface() == this : false;
479+ return m_focused;
455480}
456481
457482QRect MirSurface::inputBounds() const
@@ -463,58 +488,20 @@ void MirSurface::setInputBounds(const QRect &boundsRect)
463488{
464489 if (boundsRect != m_inputBounds) {
465490 m_inputBounds = boundsRect;
491+ DEBUG_MSG("(" << m_inputBounds << ")");
466492 Q_EMIT inputBoundsChanged(m_inputBounds);
467493 }
468494}
469−#if MIRSURFACE_DEBUG
470−#undef DEBUG_MSG
471−#define DEBUG_MSG(params) qDebug().nospace() << "MirFocusController::" << __func__ << " " << params
472−#endif
473495
474−void MirFocusController::setFocusedSurface(MirSurfaceInterface *surface)
496+void MirSurface::setRequestedPosition(const QPoint &value)
475497{
476− if (m_focusedSurface == surface) {
477− return;
478− }
479− DEBUG_MSG("MirSurface[" << (void*)surface << "," << (surface?surface->name():"") << "]");
480−
481− m_previouslyFocusedSurface = m_focusedSurface;
482− m_focusedSurface = surface;
483−
484− if (m_previouslyFocusedSurface != m_focusedSurface) {
485− Q_EMIT focusedSurfaceChanged();
486− }
487−
488− if (m_previouslyFocusedSurface) {
489− Q_EMIT m_previouslyFocusedSurface->focusedChanged(false);
490− }
498+ if (value != m_requestedPosition) {
499+ m_requestedPosition = value;
500+ Q_EMIT requestedPositionChanged(value);
491501
492− if (m_focusedSurface) {
493− Q_EMIT m_focusedSurface->focusedChanged(true);
494− m_focusedSurface->raise();
502+ // fake-miral: always comply
503+ m_position = m_requestedPosition;
504+ XDEBUG_MSG("positionChanged("<<m_position<<")");
505+ Q_EMIT positionChanged(m_position);
495506 }
496507}
497−
498−MirFocusController* MirFocusController::instance()
499−{
500− return m_instance;
501−}
502−
503−MirFocusController::MirFocusController()
504−{
505− DEBUG_MSG("");
506− Q_ASSERT(m_instance == nullptr);
507− m_instance = this;
508−}
509−
510−MirFocusController::~MirFocusController()
511−{
512− Q_ASSERT(m_instance == this);
513− m_instance = nullptr;
514−}
515−
516−void MirFocusController::clear()
517−{
518− m_focusedSurface = m_previouslyFocusedSurface = nullptr;
519− Q_EMIT focusedSurfaceChanged();
520−}
tests/mocks/Unity/Application/MirSurface.h+25−27
@@ -23,31 +23,12 @@
2323#include <QHash>
2424
2525// unity-api
26−#include <unity/shell/application/MirFocusControllerInterface.h>
2726#include <unity/shell/application/MirSurfaceInterface.h>
2827
2928#include "MirSurfaceListModel.h"
3029
3130class MirSurface;
3231
33−class MirFocusController : public unity::shell::application::MirFocusControllerInterface
34−{
35− Q_OBJECT
36−public:
37− MirFocusController();
38− virtual ~MirFocusController();
39− static MirFocusController* instance();
40−
41− void setFocusedSurface(unity::shell::application::MirSurfaceInterface *surface) override;
42− unity::shell::application::MirSurfaceInterface* focusedSurface() const override { return m_focusedSurface; }
43− unity::shell::application::MirSurfaceInterface* previouslyFocusedSurface() { return m_previouslyFocusedSurface; }
44− void clear();
45−private:
46− static MirFocusController *m_instance;
47− unity::shell::application::MirSurfaceInterface* m_previouslyFocusedSurface{nullptr};
48− unity::shell::application::MirSurfaceInterface* m_focusedSurface{nullptr};
49−};
50−
5132class MirSurface : public unity::shell::application::MirSurfaceInterface
5233{
5334 Q_OBJECT
@@ -58,6 +39,7 @@ class MirSurface : public unity::shell::application::MirSurfaceInterface
5839 Q_PROPERTY(int height READ height NOTIFY heightChanged)
5940 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
6041 Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
42+ Q_PROPERTY(bool exposed READ exposed NOTIFY exposedChanged)
6143
6244public:
6345 MirSurface(const QString& name,
@@ -76,13 +58,14 @@ public:
7658
7759 QString persistentId() const override;
7860
61+ QPoint position() const override { return m_position; }
62+
7963 QSize size() const override { return QSize(width(),height()); }
8064 void resize(int width, int height) override;
8165 void resize(const QSize &size) override { resize(size.width(), size.height()); }
8266
8367
8468 Mir::State state() const override;
85− Q_INVOKABLE void setState(Mir::State) override;
8669
8770 bool live() const override;
8871
@@ -108,15 +91,16 @@ public:
10891
10992 bool confinesMousePointer() const override { return false; }
11093
111− Q_INVOKABLE void requestFocus() override;
94+ QPoint requestedPosition() const override { return m_requestedPosition; }
95+ void setRequestedPosition(const QPoint &) override;
11296
11397 Q_INVOKABLE void close() override;
114−
115− Q_INVOKABLE void raise() override;
98+ Q_INVOKABLE void activate() override;
11699
117100 ////
118101 // API for tests
119102
103+ Q_INVOKABLE void requestFocus();
120104 Q_INVOKABLE void setLive(bool live);
121105 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
122106
@@ -126,6 +110,8 @@ public:
126110 bool isSlowToResize() const;
127111 void setSlowToResize(bool value);
128112
113+ bool exposed() const { return m_exposed; }
114+
129115 Q_INVOKABLE void setMinimumWidth(int);
130116 Q_INVOKABLE void setMaximumWidth(int);
131117 Q_INVOKABLE void setMinimumHeight(int);
@@ -148,24 +134,32 @@ public:
148134
149135 void registerView(qintptr viewId);
150136 void unregisterView(qintptr viewId);
151− void setViewVisibility(qintptr viewId, bool visible);
137+ void setViewExposure(qintptr viewId, bool visible);
152138 int viewCount() const { return m_views.count(); }
153139
154140 void setFocused(bool value);
155141
142+ void setState(Mir::State state);
143+
144+public Q_SLOTS:
145+ ////
146+ // unity.shell.application.MirSurface
147+ void requestState(Mir::State) override;
148+
156149Q_SIGNALS:
157150 ////
158151 // API for tests
159152 void widthChanged();
160153 void heightChanged();
161154 void slowToResizeChanged();
155+ void exposedChanged(bool exposed);
162156
163157 ////
164158 // internal mock stuff
165159 void screenshotUrlChanged(QUrl);
166160 void activeFocusChanged(bool);
167− void raiseRequested();
168161 void closeRequested();
162+ void stateRequested(Mir::State);
169163
170164protected:
171165 virtual void updateInputBoundsAfterResize();
@@ -175,7 +169,7 @@ private Q_SLOTS:
175169
176170private:
177171 void doResize(int width, int height);
178− void updateVisibility();
172+ void updateExposure();
179173
180174 const QString m_name;
181175 const Mir::Type m_type;
@@ -185,7 +179,7 @@ private:
185179 QUrl m_screenshotUrl;
186180 QUrl m_qmlFilePath;
187181 bool m_live;
188− bool m_visible;
182+ bool m_focused;
189183 bool m_activeFocus;
190184 int m_width;
191185 int m_height;
@@ -210,10 +204,14 @@ private:
210204 bool visible;
211205 };
212206 QHash<qintptr, View> m_views;
207+ bool m_exposed{false};
213208
214209 QTimer m_zombieTimer;
215210
216211 QRect m_inputBounds;
212+
213+ QPoint m_position;
214+ QPoint m_requestedPosition;
217215};
218216
219217#endif // MOCK_MIR_SURFACE_H
tests/mocks/Unity/Application/MirSurfaceItem.cpp+4−4
@@ -59,7 +59,7 @@ MirSurfaceItem::MirSurfaceItem(QQuickItem *parent)
5959 Qt::ExtraButton12 | Qt::ExtraButton13);
6060
6161 connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
62− connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility);
62+ connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceExposure);
6363
6464 connect(this, &MirSurfaceItem::consumesInputChanged, this, [this]() {
6565 updateMirSurfaceActiveFocus(hasActiveFocus());
@@ -278,7 +278,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
278278 m_qmlSurface->registerView((qintptr)this);
279279
280280 updateSurfaceSize();
281− updateMirSurfaceVisibility();
281+ updateMirSurfaceExposure();
282282
283283 if (m_orientationAngle) {
284284 m_qmlSurface->setOrientationAngle(*m_orientationAngle);
@@ -335,11 +335,11 @@ void MirSurfaceItem::updateMirSurfaceActiveFocus(bool focused)
335335 }
336336}
337337
338−void MirSurfaceItem::updateMirSurfaceVisibility()
338+void MirSurfaceItem::updateMirSurfaceExposure()
339339{
340340 if (!m_qmlSurface) return;
341341
342− m_qmlSurface->setViewVisibility((qintptr)this, isVisible());
342+ m_qmlSurface->setViewExposure((qintptr)this, isVisible());
343343}
344344
345345void MirSurfaceItem::setConsumesInput(bool value)
tests/mocks/Unity/Application/MirSurfaceItem.h+1−2
@@ -50,7 +50,6 @@ public:
5050 Mir::ShellChrome shellChrome() const override;
5151
5252 Mir::State surfaceState() const override;
53− void setSurfaceState(Mir::State) override {}
5453
5554 Mir::OrientationAngle orientationAngle() const override;
5655 void setOrientationAngle(Mir::OrientationAngle angle) override;
@@ -102,7 +101,7 @@ protected:
102101private Q_SLOTS:
103102 void onComponentStatusChanged(QQmlComponent::Status status);
104103 void updateScreenshot(QUrl screenshot);
105− void updateMirSurfaceVisibility();
104+ void updateMirSurfaceExposure();
106105 void updateMirSurfaceActiveFocus(bool focused);
107106
108107private:
tests/mocks/Unity/Application/MirSurfaceListModel.cpp+11−33
@@ -21,8 +21,9 @@
2121
2222#define MIRSURFACELISTMODEL_DEBUG 0
2323
24−#ifdef MIRSURFACELISTMODEL_DEBUG
25−#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << " " << params
24+#if MIRSURFACELISTMODEL_DEBUG
25+#include <QDebug>
26+#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << params
2627#else
2728#define DEBUG_MSG(params) ((void)0)
2829#endif
@@ -54,26 +55,16 @@ QVariant MirSurfaceListModel::data(const QModelIndex& index, int role) const
5455
5556void MirSurfaceListModel::raise(MirSurface *surface)
5657{
58+ DEBUG_MSG("(" << surface << ")");
5759 int i = m_surfaceList.indexOf(surface);
5860 if (i != -1) {
5961 moveSurface(i, 0);
6062 }
6163}
6264
63−void MirSurfaceListModel::appendSurface(MirSurface *surface)
64−{
65− beginInsertRows(QModelIndex(), m_surfaceList.size(), m_surfaceList.size());
66− m_surfaceList.append(surface);
67− connectSurface(surface);
68− endInsertRows();
69− Q_EMIT countChanged(m_surfaceList.count());
70− if (m_surfaceList.count() == 1) {
71− Q_EMIT firstChanged();
72− }
73−}
74−
75−void MirSurfaceListModel::prependSurface(MirSurface *surface)
65+void MirSurfaceListModel::addSurface(MirSurface *surface)
7666{
67+ DEBUG_MSG("(" << surface << ")");
7768 beginInsertRows(QModelIndex(), 0, 0);
7869 m_surfaceList.prepend(surface);
7970 connectSurface(surface);
@@ -85,6 +76,11 @@ void MirSurfaceListModel::prependSurface(MirSurface *surface)
8576void MirSurfaceListModel::connectSurface(MirSurface *surface)
8677{
8778 connect(surface, &QObject::destroyed, this, [this, surface](){ this->removeSurface(surface); });
79+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool surfaceFocused){
80+ if (surfaceFocused) {
81+ raise(surface);
82+ }
83+ });
8884}
8985
9086void MirSurfaceListModel::removeSurface(MirSurface *surface)
@@ -138,21 +134,3 @@ const MirSurfaceInterface *MirSurfaceListModel::get(int index) const
138134 return nullptr;
139135 }
140136}
141−
142−MirSurfaceInterface *MirSurfaceListModel::createSurface()
143−{
144− QStringList screenshotIds = {"gallery", "map", "facebook", "camera", "browser", "music", "twitter"};
145− int i = rand() % screenshotIds.count();
146−
147− QUrl screenshotUrl = QString("qrc:///Unity/Application/screenshots/%1@12.png")
148− .arg(screenshotIds[i]);
149−
150− auto surface = new MirSurface(QString("prompt foo"),
151− Mir::NormalType,
152− Mir::RestoredState,
153− screenshotUrl);
154−
155− prependSurface(surface);
156−
157− return surface;
158−}
tests/mocks/Unity/Application/MirSurfaceListModel.h+2−7
@@ -38,18 +38,13 @@ public:
3838 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
3939 QVariant data(const QModelIndex& index, int role) const override;
4040
41− void appendSurface(MirSurface *surface);
41+ void addSurface(MirSurface *surface);
4242 void removeSurface(MirSurface *surface);
4343
4444 bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
4545
46− ////
47− // API for tests
48−
49− Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
50−
5146private:
52− void prependSurface(MirSurface *surface);
47+ void appendSurface(MirSurface *surface);
5348 void raise(MirSurface *surface);
5449 void moveSurface(int from, int to);
5550 void connectSurface(MirSurface *surface);
tests/mocks/Unity/Application/SurfaceManager.cpp+167−24
@@ -21,33 +21,41 @@
2121
2222#include <paths.h>
2323
24−SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
24+#define SURFACEMANAGER_DEBUG 0
25+
26+#if SURFACEMANAGER_DEBUG
27+#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
28+#else
29+#define DEBUG_MSG(params) ((void)0)
30+#endif
31+
32+namespace unityapi = unity::shell::application;
33+
34+SurfaceManager *SurfaceManager::m_instance = nullptr;
2535
2636SurfaceManager *SurfaceManager::instance()
2737{
28− return the_surface_manager;
38+ return m_instance;
2939}
3040
31−SurfaceManager::SurfaceManager(QObject *parent) :
32− QObject(parent)
33− , m_virtualKeyboard(nullptr)
41+SurfaceManager::SurfaceManager(QObject *)
3442{
35− Q_ASSERT(the_surface_manager == nullptr);
36− the_surface_manager = this;
43+ DEBUG_MSG("");
3744
38− m_virtualKeyboard = new VirtualKeyboard;
39− connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
40− MirSurface* surface = qobject_cast<MirSurface*>(obj);
41− m_virtualKeyboard = nullptr;
42− Q_EMIT inputMethodSurfaceChanged();
43− Q_EMIT surfaceDestroyed(surface);
44− });
45+ Q_ASSERT(m_instance == nullptr);
46+ m_instance = this;
4547}
4648
4749SurfaceManager::~SurfaceManager()
4850{
49− Q_ASSERT(the_surface_manager == this);
50− the_surface_manager = nullptr;
51+ DEBUG_MSG("");
52+
53+ if (m_virtualKeyboard) {
54+ m_virtualKeyboard->setLive(false);
55+ }
56+
57+ Q_ASSERT(m_instance == this);
58+ m_instance = nullptr;
5159}
5260
5361MirSurface *SurfaceManager::createSurface(const QString& name,
@@ -56,10 +64,13 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
5664 const QUrl& screenshot)
5765{
5866 MirSurface* surface = new MirSurface(name, type, state, screenshot);
59− connect(surface, &QObject::destroyed, this, [this](QObject *obj) {
60− MirSurface* surface = qobject_cast<MirSurface*>(obj);
61− Q_EMIT surfaceDestroyed(surface);
62− });
67+ registerSurface(surface);
68+ return surface;
69+}
70+
71+void SurfaceManager::registerSurface(MirSurface *surface)
72+{
73+ m_surfaces.prepend(surface);
6374
6475 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
6576 surface->setMaximumWidth(m_newSurfaceMaximumWidth);
@@ -68,13 +79,18 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
6879 surface->setWidthIncrement(m_newSurfaceWidthIncrement);
6980 surface->setHeightIncrement(m_newSurfaceHeightIncrement);
7081
71− Q_EMIT surfaceCreated(surface);
72− return surface;
82+ connect(surface, &MirSurface::stateRequested, this, [=](Mir::State state) {
83+ this->onStateRequested(surface, state);
84+ });
85+
86+ connect(surface, &QObject::destroyed, this, [=]() {
87+ this->onSurfaceDestroyed(surface);
88+ });
7389}
7490
75−MirSurface *SurfaceManager::inputMethodSurface() const
91+void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
7692{
77− return m_virtualKeyboard;
93+ Q_EMIT surfaceCreated(surface);
7894}
7995
8096void SurfaceManager::setNewSurfaceMinimumWidth(int value)
@@ -124,3 +140,130 @@ void SurfaceManager::setNewSurfaceHeightIncrement(int value)
124140 Q_EMIT newSurfaceHeightIncrementChanged(m_newSurfaceHeightIncrement);
125141 }
126142}
143+
144+void SurfaceManager::raise(unityapi::MirSurfaceInterface *surface)
145+{
146+ if (m_underModification)
147+ return;
148+
149+ DEBUG_MSG("("<<surface<<") started");
150+ Q_EMIT modificationsStarted();
151+ m_underModification = true;
152+
153+ doRaise(surface);
154+
155+ m_underModification = false;
156+ Q_EMIT modificationsEnded();
157+ DEBUG_MSG("("<<surface<<") ended");
158+}
159+
160+void SurfaceManager::doRaise(unityapi::MirSurfaceInterface *apiSurface)
161+{
162+ auto surface = static_cast<MirSurface*>(apiSurface);
163+ int index = m_surfaces.indexOf(surface);
164+ Q_ASSERT(index != -1);
165+ m_surfaces.move(index, 0);
166+
167+ QVector<MirSurfaceInterface*> surfaces;
168+ surfaces.append(surface);
169+ Q_EMIT surfacesRaised(surfaces);
170+}
171+
172+void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
173+{
174+ auto surface = static_cast<MirSurface*>(apiSurface);
175+
176+ if (surface == m_focusedSurface) {
177+ return;
178+ }
179+
180+ Q_ASSERT(!m_underModification);
181+
182+ DEBUG_MSG("("<<surface<<") started");
183+ Q_EMIT modificationsStarted();
184+ m_underModification = true;
185+ if (m_focusedSurface) {
186+ m_focusedSurface->setFocused(false);
187+ }
188+ if (surface) {
189+ if (surface->state() == Mir::HiddenState || surface->state() == Mir::MinimizedState) {
190+ surface->setState(Mir::RestoredState);
191+ }
192+ surface->setFocused(true);
193+ doRaise(surface);
194+ }
195+ m_focusedSurface = surface;
196+ m_underModification = false;
197+ Q_EMIT modificationsEnded();
198+ DEBUG_MSG("("<<surface<<") ended");
199+}
200+
201+void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
202+{
203+ Q_ASSERT(!m_underModification);
204+
205+ DEBUG_MSG("("<<surface<<","<<state<<") started");
206+ Q_EMIT modificationsStarted();
207+ m_underModification = true;
208+
209+ surface->setState(state);
210+
211+ if ((state == Mir::MinimizedState || state == Mir::HiddenState) && surface->focused()) {
212+ Q_ASSERT(m_focusedSurface == surface);
213+ surface->setFocused(false);
214+ m_focusedSurface = nullptr;
215+ focusFirstAvailableSurface();
216+ }
217+
218+ m_underModification = false;
219+ Q_EMIT modificationsEnded();
220+ DEBUG_MSG("("<<surface<<","<<state<<") ended");
221+}
222+
223+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
224+{
225+ m_surfaces.removeAll(surface);
226+ if (m_focusedSurface == surface) {
227+ m_focusedSurface = nullptr;
228+
229+ Q_EMIT modificationsStarted();
230+ m_underModification = true;
231+
232+ focusFirstAvailableSurface();
233+
234+ m_underModification = false;
235+ Q_EMIT modificationsEnded();
236+ }
237+}
238+
239+void SurfaceManager::focusFirstAvailableSurface()
240+{
241+ MirSurface *chosenSurface = nullptr;
242+ for (int i = 0; i < m_surfaces.count() && !chosenSurface; ++i) {
243+ auto *surface = m_surfaces[i];
244+ if (surface->state() != Mir::HiddenState && surface->state() != Mir::MinimizedState) {
245+ chosenSurface = surface;
246+ }
247+ }
248+
249+ if (!chosenSurface) {
250+ return;
251+ }
252+
253+ if (m_focusedSurface) {
254+ m_focusedSurface->setFocused(false);
255+ }
256+ if (chosenSurface) {
257+ chosenSurface->setFocused(true);
258+ }
259+ m_focusedSurface = chosenSurface;
260+}
261+
262+void SurfaceManager::createInputMethodSurface()
263+{
264+ if (!m_virtualKeyboard) {
265+ m_virtualKeyboard = new VirtualKeyboard;
266+ registerSurface(m_virtualKeyboard);
267+ Q_EMIT surfaceCreated(m_virtualKeyboard);
268+ }
269+}
tests/mocks/Unity/Application/SurfaceManager.h+28−10
@@ -19,15 +19,16 @@
1919
2020#include <QObject>
2121
22+#include <unity/shell/application/SurfaceManagerInterface.h>
23+
2224#include "MirSurface.h"
2325#include "VirtualKeyboard.h"
2426
2527class ApplicationInfo;
2628
27−class SurfaceManager : public QObject
29+class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
2830{
2931 Q_OBJECT
30− Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
3132 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
3233 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
3334 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
@@ -41,12 +42,17 @@ public:
4142
4243 static SurfaceManager *instance();
4344
45+ // SurfaceManagerInterface
46+ void raise(unity::shell::application::MirSurfaceInterface *surface) override;
47+ void activate(unity::shell::application::MirSurfaceInterface *surface) override;
48+
4449 Q_INVOKABLE MirSurface* createSurface(const QString& name,
4550 Mir::Type type,
4651 Mir::State state,
4752 const QUrl& screenshot);
4853
49− MirSurface* inputMethodSurface() const;
54+
55+ void notifySurfaceCreated(unity::shell::application::MirSurfaceInterface *);
5056
5157 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
5258 void setNewSurfaceMinimumWidth(int value);
@@ -66,12 +72,10 @@ public:
6672 int newSurfaceHeightIncrement() const { return m_newSurfaceHeightIncrement; }
6773 void setNewSurfaceHeightIncrement(int);
6874
69−Q_SIGNALS:
70− void inputMethodSurfaceChanged();
71− void countChanged();
72− void surfaceCreated(MirSurface *surface);
73− void surfaceDestroyed(MirSurface*surface);
75+public Q_SLOTS:
76+ void createInputMethodSurface();
7477
78+Q_SIGNALS:
7579 void newSurfaceMinimumWidthChanged(int value);
7680 void newSurfaceMaximumWidthChanged(int value);
7781 void newSurfaceMinimumHeightChanged(int value);
@@ -79,9 +83,16 @@ Q_SIGNALS:
7983 void newSurfaceWidthIncrementChanged(int value);
8084 void newSurfaceHeightIncrementChanged(int value);
8185
86+private Q_SLOTS:
87+ void onStateRequested(MirSurface *surface, Mir::State state);
88+ void onSurfaceDestroyed(MirSurface *surface);
89+
8290private:
83− static SurfaceManager *the_surface_manager;
84− VirtualKeyboard *m_virtualKeyboard;
91+ void doRaise(unity::shell::application::MirSurfaceInterface *surface);
92+ void focusFirstAvailableSurface();
93+ void registerSurface(MirSurface *surface);
94+
95+ static SurfaceManager *m_instance;
8596
8697 int m_newSurfaceMinimumWidth{0};
8798 int m_newSurfaceMaximumWidth{0};
@@ -89,6 +100,13 @@ private:
89100 int m_newSurfaceMaximumHeight{0};
90101 int m_newSurfaceWidthIncrement{1};
91102 int m_newSurfaceHeightIncrement{1};
103+
104+ MirSurface *m_focusedSurface{nullptr};
105+ bool m_underModification{false};
106+
107+ QList<MirSurface*> m_surfaces;
108+
109+ VirtualKeyboard *m_virtualKeyboard{nullptr};
92110};
93111
94112#endif // SURFACEMANAGER_H
tests/mocks/Unity/Application/plugin.cpp+2−31
@@ -29,24 +29,8 @@
2929
3030namespace {
3131
32−// Creates the singletons that are called throughout C++ code
33−void createUnityApplicationSharedSingletons()
34−{
35− // they have to be created in a specific order
36− if (!MirFocusController::instance()) {
37− new MirFocusController;
38− }
39− if (!SurfaceManager::instance()) {
40− new SurfaceManager;
41− }
42− if (!MirMock::instance()) {
43− new MirMock;
44− }
45−}
46−
4732QObject* applicationManagerSingleton(QQmlEngine*, QJSEngine*)
4833{
49− createUnityApplicationSharedSingletons();
5034 return new ApplicationManager;
5135}
5236
@@ -55,19 +39,6 @@ QObject* mirSingleton(QQmlEngine*, QJSEngine*)
5539 createUnityApplicationSharedSingletons();
5640 return MirMock::instance();
5741}
58−
59−QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
60−{
61− createUnityApplicationSharedSingletons();
62− return SurfaceManager::instance();
63−}
64−
65−QObject* mirFocusControllerSingleton(QQmlEngine*, QJSEngine*)
66−{
67− createUnityApplicationSharedSingletons();
68− return MirFocusController::instance();
69−}
70−
7142} // anonymous namespace
7243
7344void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -75,6 +46,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
7546 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
7647 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
7748 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
49+ qRegisterMetaType<unity::shell::application::SurfaceManagerInterface*>("unity::shell::application::SurfaceManagerInterface*");
7850 qRegisterMetaType<Mir::Type>("Mir::Type");
7951 qRegisterMetaType<Mir::State>("Mir::State");
8052
@@ -83,13 +55,12 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
8355 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
8456 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
8557 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
86− qmlRegisterSingletonType<MirFocusController>(uri, 0, 1, "MirFocusController", mirFocusControllerSingleton);
8758 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
8859 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
8960
9061 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
9162 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
92− qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
63+ qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
9364}
9465
9566void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/plugins/Unity/Launcher/launchermodeltest.cpp+4
@@ -65,6 +65,7 @@ public:
6565 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
6666 int surfaceCount() const override { return m_surfaceCount; }
6767 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
68+ void close() override {}
6869
6970 // Methods used for mocking (not in the interface)
7071 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
@@ -98,6 +99,9 @@ public:
9899 }
99100 return nullptr;
100101 }
102+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override {
103+ return nullptr;
104+ }
101105 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
102106 bool stopApplication(const QString &appId) override {
103107 Q_FOREACH(MockApp* app, m_list) {
tests/qmltests/Stage/ApplicationCheckBox.qml+1−1
@@ -149,7 +149,7 @@ Column {
149149 width: height
150150 height: promptsLabel.height * 0.7
151151 anchors.verticalCenter: parent.verticalCenter
152− onClicked: d.application.promptSurfaceList.createSurface()
152+ onClicked: d.application.createPromptSurface()
153153 Label { text: "➕"; anchors.centerIn: parent }
154154 }
155155 MouseArea {
tests/qmltests/Stage/tst_ApplicationWindow.qml+10−10
@@ -37,6 +37,8 @@ Rectangle {
3737 }
3838 property QtObject fakeApplication: null
3939
40+ SurfaceManager{}
41+
4042 Loader {
4143 id: applicationWindowLoader
4244 focus: true
@@ -105,17 +107,17 @@ Rectangle {
105107 RowLayout {
106108 property var promptSurfaceList: root.fakeApplication ? root.fakeApplication.promptSurfaceList : null
107109 Button {
108− enabled: parent.promptSurfaceList !== null && parent.promptSurfaceList.count > 0
110+ enabled: root.fakeApplication && root.fakeApplication.promptSurfaceList.count > 0
109111 activeFocusOnPress: false
110112 text: "Remove"
111− onClicked: { parent.promptSurfaceList.get(0).close(); }
113+ onClicked: { root.fakeApplication.promptSurfaceList.get(0).close(); }
112114 }
113115
114116 Button {
115− enabled: parent.promptSurfaceList !== null
117+ enabled: root.fakeApplication
116118 activeFocusOnPress: false
117119 text: "Add Prompt Surface"
118− onClicked: { parent.promptSurfaceList.createSurface(); }
120+ onClicked: { root.fakeApplication.createPromptSurface(); }
119121 }
120122 }
121123
@@ -427,7 +429,7 @@ Rectangle {
427429 var i;
428430 // 3 surfaces should cover all edge cases
429431 for (i = 0; i < 3; i++) {
430− promptSurfaceList.createSurface();
432+ root.fakeApplication.createPromptSurface();
431433 compare(promptSurfaces.count, i+1);
432434 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
433435 }
@@ -452,9 +454,7 @@ Rectangle {
452454 }
453455
454456 function test_promptSurfaceAdjustsForParentSize() {
455− var promptSurfaceList = root.fakeApplication.promptSurfaceList;
456−
457− promptSurfaceList.createSurface();
457+ root.fakeApplication.createPromptSurface();
458458
459459 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
460460
@@ -487,10 +487,10 @@ Rectangle {
487487 var promptSurfaceList = root.fakeApplication.promptSurfaceList;
488488 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
489489
490− promptSurfaceList.createSurface();
490+ root.fakeApplication.createPromptSurface();
491491
492492 for (var i = 2; i <= 3; i++) {
493− promptSurfaceList.createSurface();
493+ root.fakeApplication.createPromptSurface();
494494 tryCompare(promptSurfaces, "count", i);
495495 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
496496
tests/qmltests/Stage/tst_DesktopStage.qml+50−40
@@ -20,8 +20,8 @@ import Ubuntu.Components 1.3
2020import Ubuntu.Components.ListItems 1.3
2121import Unity.Application 0.1
2222import Unity.Test 0.1
23−import WindowManager 0.1
2423import Utils 0.1
24+import WindowManager 1.0
2525
2626import ".." // For EdgeBarrierControls
2727import "../../../qml/Stage"
@@ -57,9 +57,11 @@ Item {
5757 }
5858 }
5959
60− TopLevelSurfaceList {
60+ SurfaceManager { id: sMgr }
61+ TopLevelWindowModel {
6162 id: topSurfaceList
62− applicationsModel: ApplicationManager
63+ applicationManager: ApplicationManager
64+ surfaceManager: sMgr
6365 }
6466
6567 Loader {
@@ -80,6 +82,7 @@ Item {
8082
8183 Component.onCompleted: {
8284 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
85+ ApplicationManager.startApplication("unity8-dash");
8386 }
8487 Component.onDestruction: {
8588 stageLoader.itemDestroyed = true;
@@ -170,8 +173,9 @@ Item {
170173 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
171174
172175 tryCompare(topSurfaceList, "count", 1);
173− tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) != null;}, true);
174− compare(MirFocusController.focusedSurface, topSurfaceList.surfaceAt(0));
176+ tryCompareFunction(function(){return topSurfaceList.windowAt(0) != null;}, true);
177+ topSurfaceList.windowAt(0).activate();
178+ tryCompare(topSurfaceList, "focusedWindow", topSurfaceList.windowAt(0));
175179 }
176180
177181 function cleanup() {
@@ -284,13 +288,13 @@ Item {
284288 verify(fromAppWindow);
285289 tap(fromAppWindow);
286290 compare(fromDelegate.surface.activeFocus, true);
287− compare(MirFocusController.focusedSurface, fromDelegate.surface);
291+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
288292
289293 var toAppWindow = findChild(toDelegate, "appWindow");
290294 verify(toAppWindow);
291295 tap(toAppWindow);
292296 compare(toDelegate.surface.activeFocus, true);
293− compare(MirFocusController.focusedSurface, toDelegate.surface);
297+ compare(topSurfaceList.focusedWindow, toDelegate.window);
294298 }
295299
296300 function test_clickingOnWindowChangesFocusedApp_data() {
@@ -309,13 +313,13 @@ Item {
309313 verify(fromAppWindow);
310314 mouseClick(fromAppWindow);
311315 compare(fromDelegate.surface.activeFocus, true);
312− compare(MirFocusController.focusedSurface, fromDelegate.surface);
316+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
313317
314318 var toAppWindow = findChild(toDelegate, "appWindow");
315319 verify(toAppWindow);
316320 mouseClick(toAppWindow);
317321 compare(toDelegate.surface.activeFocus, true);
318− compare(MirFocusController.focusedSurface, toDelegate.surface);
322+ compare(topSurfaceList.focusedWindow, toDelegate.window);
319323 }
320324
321325 function test_tappingOnDecorationFocusesApplication_data() {
@@ -343,6 +347,13 @@ Item {
343347 return findChild(appDelegate, "appWindowDecoration");
344348 }
345349
350+ function maximizeDelegate(appDelegate) {
351+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
352+ verify(maximizeButton);
353+ mouseClick(maximizeButton);
354+ tryCompare(appDelegate, "visuallyMaximized", true);
355+ }
356+
346357 function test_tappingOnDecorationFocusesApplication(data) {
347358 var appDelegates = [];
348359 for (var i = 0; i < data.apps.length; i++) {
@@ -398,7 +409,7 @@ Item {
398409 startApplication("camera-app");
399410
400411 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
401− dialerDelegate.surface.requestFocus();
412+ dialerDelegate.surface.activate();
402413 tryCompare(dialerDelegate, "focus", true);
403414
404415 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
@@ -411,7 +422,7 @@ Item {
411422 startApplication("camera-app");
412423
413424 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
414− dialerDelegate.surface.requestFocus();
425+ dialerDelegate.surface.activate();
415426 tryCompare(dialerDelegate, "focus", true);
416427
417428 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
@@ -426,7 +437,7 @@ Item {
426437 startApplication("camera-app");
427438
428439 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
429− dialerDelegate.surface.requestFocus();
440+ dialerDelegate.surface.activate();
430441 tryCompare(dialerDelegate, "focus", true);
431442
432443 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
@@ -441,7 +452,7 @@ Item {
441452 startApplication("camera-app");
442453
443454 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
444− dialerDelegate.surface.requestFocus();
455+ dialerDelegate.surface.activate();
445456 tryCompare(dialerDelegate, "focus", true);
446457
447458 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
@@ -512,11 +523,11 @@ Item {
512523 apps.forEach(startApplication);
513524 verify(topSurfaceList.count == 3);
514525 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
515− tryCompare(MirFocusController, "focusedSurface", null); // verify no surface is focused
526+ tryCompare(topSurfaceList, "focusedWindow", null); // verify no window is focused
516527
517528 // now try pressing all 4 arrow keys + ctrl + meta
518529 keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
519− tryCompare(MirFocusController, "focusedSurface", null); // verify still no surface is focused
530+ tryCompare(topSurfaceList, "focusedWindow", null); // verify still no window is focused
520531 }
521532
522533 function test_minimizeApplicationHidesSurface() {
@@ -527,9 +538,12 @@ Item {
527538 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
528539 verify(decoratedWindow);
529540
530− tryCompare(dashSurface, "visible", true);
531− decoratedWindow.minimizeClicked();
532− tryCompare(dashSurface, "visible", false);
541+ var minimizeButton = findChild(decoratedWindow, "minimizeWindowButton");
542+ verify(minimizeButton);
543+
544+ tryCompare(dashSurface, "exposed", true);
545+ mouseClick(minimizeButton);
546+ tryCompare(dashSurface, "exposed", false);
533547 }
534548
535549 function test_maximizeApplicationHidesSurfacesBehindIt() {
@@ -538,16 +552,16 @@ Item {
538552 var gmailDelegate = startApplication("gmail-webapp");
539553
540554 // maximize without raising
541− dialerDelegate.maximize();
555+ dialerDelegate.requestMaximize();
542556 tryCompare(dialerDelegate, "visuallyMaximized", true);
543557
544− tryCompare(dashDelegate.surface, "visible", false);
545− compare(gmailDelegate.surface.visible, true);
558+ tryCompare(dashDelegate.surface, "exposed", false);
559+ compare(gmailDelegate.surface.exposed, true);
546560
547561 // restore without raising
548− dialerDelegate.restoreFromMaximized();
549− compare(dashDelegate.surface.visible, true);
550− compare(gmailDelegate.surface.visible, true);
562+ dialerDelegate.requestRestore();
563+ compare(dashDelegate.surface.exposed, true);
564+ compare(gmailDelegate.surface.exposed, true);
551565 }
552566
553567 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
@@ -580,26 +594,22 @@ Item {
580594 tryCompare(dialerDelegate, "visuallyMaximized", true);
581595 tryCompare(gmailDelegate, "visuallyMaximized", true);
582596
583− tryCompare(dashApp.surfaceList.get(0), "visible", false);
584− tryCompare(dialerApp.surfaceList.get(0), "visible", false);
585− tryCompare(mapApp.surfaceList.get(0), "visible", false);
597+ tryCompare(dashApp.surfaceList.get(0), "exposed", false);
598+ tryCompare(dialerApp.surfaceList.get(0), "exposed", false);
599+ tryCompare(mapApp.surfaceList.get(0), "exposed", false);
586600
587601 ApplicationManager.stopApplication("gmail-webapp");
588602 wait(2000)
589603
590− tryCompare(mapApp.surfaceList.get(0), "visible", true);
591− tryCompare(dialerApp.surfaceList.get(0), "visible", true);
592− tryCompare(dashApp.surfaceList.get(0), "visible", false); // still occluded by maximised dialer
604+ tryCompare(mapApp.surfaceList.get(0), "exposed", true);
605+ tryCompare(dialerApp.surfaceList.get(0), "exposed", true);
606+ tryCompare(dashApp.surfaceList.get(0), "exposed", false); // still occluded by maximised dialer
593607 }
594608
595609 function test_maximisedAppStaysVisibleWhenAppStarts() {
596610 var dashDelegate = startApplication("unity8-dash");
597611
598− // maximize
599− var dashMaximizeButton = findChild(dashDelegate, "maximizeWindowButton");
600− verify(dashMaximizeButton);
601− mouseClick(dashMaximizeButton);
602− tryCompare(dashDelegate, "visuallyMaximized", true);
612+ maximizeDelegate(dashDelegate);
603613
604614 var dialerDelegate = startApplication("dialer-app");
605615 verify(dialerDelegate);
@@ -621,25 +631,25 @@ Item {
621631 tryCompare(facebookAppDelegate, "visible", true);
622632
623633 // Maximize the topmost and make sure the other two are hidden
624− facebookAppDelegate.maximize();
634+ maximizeDelegate(facebookAppDelegate);
625635 tryCompare(dashAppDelegate, "visible", false);
626636 tryCompare(dialerAppDelegate, "visible", false);
627637 tryCompare(facebookAppDelegate, "visible", true);
628638
629639 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
630− dashAppDelegate.focus = true;
640+ dashAppDelegate.activate();
631641 tryCompare(dashAppDelegate, "visible", true);
632642 tryCompare(dialerAppDelegate, "visible", false);
633643 tryCompare(facebookAppDelegate, "visible", true);
634644
635645 // Now focus the dialer app. all 3 should be visible again
636− dialerAppDelegate.focus = true;
646+ dialerAppDelegate.activate();
637647 tryCompare(dashAppDelegate, "visible", true);
638648 tryCompare(dialerAppDelegate, "visible", true);
639649 tryCompare(facebookAppDelegate, "visible", true);
640650
641651 // Maximize the dialer app. The other 2 should hide
642− dialerAppDelegate.maximize();
652+ maximizeDelegate(dialerAppDelegate);
643653 tryCompare(dashAppDelegate, "visible", false);
644654 tryCompare(dialerAppDelegate, "visible", true);
645655 tryCompare(facebookAppDelegate, "visible", false);
@@ -648,7 +658,7 @@ Item {
648658 function test_dropShadow() {
649659 // start an app, maximize it
650660 var facebookAppDelegate = startApplication("facebook-webapp");
651− facebookAppDelegate.maximize();
661+ maximizeDelegate(facebookAppDelegate);
652662
653663 // verify the drop shadow is still not visible
654664 verify(PanelState.dropShadow == false);
tests/qmltests/Stage/tst_PhoneStage.qml+18−12
@@ -22,7 +22,7 @@ import "../../../qml/Components"
2222import "../../../qml/Stage"
2323import Ubuntu.Components 1.3
2424import Unity.Application 0.1
25−import WindowManager 0.1
25+import WindowManager 1.0
2626
2727Item {
2828 id: root
@@ -31,6 +31,7 @@ Item {
3131
3232 property var greeter: { fullyShown: true }
3333
34+ SurfaceManager { id: sMgr }
3435 Stage {
3536 id: stage
3637 anchors { fill: parent; rightMargin: units.gu(30) }
@@ -41,9 +42,13 @@ Item {
4142 orientations: Orientations {}
4243 applicationManager: ApplicationManager
4344 mode: "staged"
44− topLevelSurfaceList: TopLevelSurfaceList {
45+ topLevelSurfaceList: TopLevelWindowModel {
4546 id: topLevelSurfaceList
46− applicationsModel: ApplicationManager
47+ applicationManager: ApplicationManager
48+ surfaceManager: sMgr
49+ }
50+ Component.onCompleted: {
51+ ApplicationManager.startApplication("unity8-dash");
4752 }
4853 }
4954
@@ -86,11 +91,16 @@ Item {
8691
8792 function init() {
8893 // wait until unity8-dash is up and running.
89− // it's started automatically by ApplicationManager mock implementation
94+ ApplicationManager.startApplication("unity8-dash");
9095 tryCompare(ApplicationManager, "count", 1);
9196 var dashApp = ApplicationManager.findApplication("unity8-dash");
9297 verify(dashApp);
9398 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
99+
100+ // wait for Stage to stabilize back into its initial state
101+ var appRepeater = findChild(stage, "appRepeater");
102+ tryCompare(appRepeater, "count", 1);
103+ tryCompare(appRepeater.itemAt(0), "x", 0);
94104 }
95105
96106 function cleanup() {
@@ -100,10 +110,6 @@ Item {
100110 waitForRendering(stage);
101111
102112 killApps();
103− // wait for Stage to stabilize back into its initial state
104− var appRepeater = findChild(stage, "appRepeater");
105− tryCompare(appRepeater, "count", 1);
106− tryCompare(appRepeater.itemAt(0), "x", 0);
107113
108114 stage.shellOrientationAngle = 0;
109115
@@ -505,7 +511,7 @@ Item {
505511 function test_selectSuspendedAppWithoutSurface() {
506512 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
507513 var dashSurfaceId = topLevelSurfaceList.idAt(0);
508− var dashSurface = topLevelSurfaceList.surfaceAt(0);
514+ var dashWindow = topLevelSurfaceList.windowAt(0);
509515
510516 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
511517 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -513,7 +519,7 @@ Item {
513519
514520 switchToSurface(dashSurfaceId);
515521
516− tryCompare(MirFocusController, "focusedSurface", dashSurface);
522+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
517523 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
518524
519525 compare(webbrowserApp.surfaceList.count, 1);
@@ -549,7 +555,7 @@ Item {
549555 {
550556 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
551557 var dashSurfaceId = topLevelSurfaceList.idAt(0);
552− var dashSurface = topLevelSurfaceList.surfaceAt(0);
558+ var dashWindow = topLevelSurfaceList.windowAt(0);
553559
554560 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
555561 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -557,7 +563,7 @@ Item {
557563
558564 switchToSurface(dashSurfaceId);
559565
560− tryCompare(MirFocusController, "focusedSurface", dashSurface);
566+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
561567 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
562568
563569 compare(webbrowserApp.surfaceList.count, 1);
tests/qmltests/Stage/tst_TabletStage.qml+16−10
@@ -21,7 +21,7 @@ import Ubuntu.Components.ListItems 1.3 as ListItem
2121import Unity.Application 0.1
2222import Unity.Test 0.1
2323import Utils 0.1
24−import WindowManager 0.1
24+import WindowManager 1.0
2525
2626import ".."
2727import "../../../qml/Stage"
@@ -35,6 +35,7 @@ Rectangle {
3535
3636 property var greeter: { fullyShown: true }
3737
38+ SurfaceManager { id: sMgr }
3839 Stage {
3940 id: stage
4041 anchors { fill: parent; rightMargin: units.gu(30) }
@@ -50,9 +51,13 @@ Rectangle {
5051 focus: true
5152 mode: "stagedWithSideStage"
5253 applicationManager: ApplicationManager
53− topLevelSurfaceList: TopLevelSurfaceList {
54+ topLevelSurfaceList: TopLevelWindowModel {
5455 id: topLevelSurfaceList
55− applicationsModel: ApplicationManager
56+ applicationManager: ApplicationManager
57+ surfaceManager: sMgr
58+ }
59+ Component.onCompleted: {
60+ ApplicationManager.startApplication("unity8-dash");
5661 }
5762 }
5863
@@ -129,6 +134,7 @@ Rectangle {
129134 function init() {
130135 stageSaver.clear();
131136
137+ ApplicationManager.startApplication("unity8-dash");
132138 tryCompare(topSurfaceList, "count", 1);
133139 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
134140
@@ -143,6 +149,11 @@ Rectangle {
143149 }
144150 }
145151
152+ // wait for Stage to stabilize back into its initial state
153+ var appRepeater = findChild(stage, "appRepeater");
154+ tryCompare(appRepeater, "count", 1);
155+ tryCompare(appRepeater.itemAt(0), "x", 0);
156+
146157 waitUntilAppSurfaceShowsUp(topSurfaceList.idAt(0));
147158 sideStage.hideNow()
148159 tryCompare(sideStage, "x", stage.width)
@@ -156,11 +167,6 @@ Rectangle {
156167
157168 killApps();
158169
159− // wait for Stage to stabilize back into its initial state
160− var appRepeater = findChild(stage, "appRepeater");
161− tryCompare(appRepeater, "count", 1);
162− tryCompare(appRepeater.itemAt(0), "x", 0);
163−
164170 sideStage.hideNow();
165171 tryCompare(sideStage, "x", stage.width)
166172 waitForRendering(stage)
@@ -628,7 +634,7 @@ Rectangle {
628634 function test_selectSuspendedAppWithoutSurface() {
629635 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
630636 var dashSurfaceId = topSurfaceList.idAt(0);
631− var dashSurface = topSurfaceList.surfaceAt(0);
637+ var dashWindow = topSurfaceList.windowAt(0);
632638
633639 var webbrowserSurfaceId = topSurfaceList.nextId;
634640 webbrowserCheckBox.checked = true;
@@ -637,7 +643,7 @@ Rectangle {
637643
638644 switchToSurface(dashSurfaceId);
639645
640− tryCompare(MirFocusController, "focusedSurface", dashSurface);
646+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
641647 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
642648
643649 compare(webbrowserApp.surfaceList.count, 1);
tests/qmltests/tst_Shell.qml+114−40
@@ -52,6 +52,15 @@ Rectangle {
5252 }
5353
5454 property var shell: shellLoader.item ? shellLoader.item : null
55+ onShellChanged: {
56+ if (shell) {
57+ topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
58+ } else {
59+ topLevelSurfaceList = null;
60+ }
61+ }
62+
63+ property var topLevelSurfaceList: null
5564
5665 Item {
5766 id: shellContainer
@@ -120,6 +129,9 @@ Rectangle {
120129 primary: shellLoader.primaryOrientation
121130 }
122131 mode: shellLoader.mode
132+ Component.onCompleted: {
133+ ApplicationManager.startApplication("unity8-dash");
134+ }
123135 Component.onDestruction: {
124136 shellLoader.itemDestroyed = true;
125137 }
@@ -227,7 +239,7 @@ Rectangle {
227239 activeFocusOnPress: false
228240 model: ["single", "single-passphrase", "single-pin", "full"]
229241 onSelectedIndexChanged: {
230− shellLoader.active = false;
242+ testCase.tearDown();
231243 LightDM.Greeter.mockMode = model[selectedIndex];
232244 LightDM.Users.mockMode = model[selectedIndex];
233245 shellLoader.active = true;
@@ -318,21 +330,21 @@ Rectangle {
318330 id: fullscreeAppCheck
319331
320332 onTriggered: {
321− if (!MirFocusController.focusedSurface) return;
322− if (MirFocusController.focusedSurface.state == Mir.FullscreenState) {
323− MirFocusController.focusedSurface.state = Mir.RestoredState;
333+ if (!topLevelSurfaceList.focusedWindow) return;
334+ if (topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState) {
335+ topLevelSurfaceList.focusedWindow.requestState(Mir.RestoredState);
324336 } else {
325− MirFocusController.focusedSurface.state = Mir.FullscreenState;
337+ topLevelSurfaceList.focusedWindow.requestState(Mir.FullscreenState);
326338 }
327339 }
328340
329341 Binding {
330342 target: fullscreeAppCheck
331− when: MirFocusController.focusedSurface
343+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow
332344 property: "checked"
333345 value: {
334− if (!MirFocusController.focusedSurface) return false;
335− return MirFocusController.focusedSurface.state === Mir.FullscreenState
346+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow) return false;
347+ return topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
336348 }
337349 }
338350 }
@@ -346,21 +358,22 @@ Rectangle {
346358 id: chromeAppCheck
347359
348360 onTriggered: {
349− if (!MirFocusController.focusedSurface) return;
350− if (MirFocusController.focusedSurface.shellChrome == Mir.LowChrome) {
351− MirFocusController.focusedSurface.setShellChrome(Mir.NormalChrome);
361+ if (!topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return;
362+ var surface = topLevelSurfaceList.focusedWindow.surface;
363+ if (surface.shellChrome == Mir.LowChrome) {
364+ surface.setShellChrome(Mir.NormalChrome);
352365 } else {
353− MirFocusController.focusedSurface.setShellChrome(Mir.LowChrome);
366+ surface.setShellChrome(Mir.LowChrome);
354367 }
355368 }
356369
357370 Binding {
358371 target: chromeAppCheck
359− when: MirFocusController.focusedSurface !== null
372+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow !== null && topLevelSurfaceList.focusedWindow.surface !== null
360373 property: "checked"
361374 value: {
362− if (!MirFocusController.focusedSurface) return false;
363− MirFocusController.focusedSurface.shellChrome === Mir.LowChrome
375+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return false;
376+ topLevelSurfaceList.focusedWindow.surface.shellChrome === Mir.LowChrome
364377 }
365378 }
366379 }
@@ -369,6 +382,57 @@ Rectangle {
369382 }
370383 }
371384
385+ Button {
386+ text: "Toggle input method"
387+ width: parent.width
388+ activeFocusOnPress: false
389+ onClicked: {
390+ testCase.ensureInputMethodSurface();
391+ var inputMethod = root.topLevelSurfaceList.inputMethodSurface;
392+ if (inputMethod.visible) {
393+ inputMethod.requestState(Mir.HiddenState);
394+ } else {
395+ inputMethod.requestState(Mir.RestoredState);
396+ }
397+ }
398+ }
399+
400+ Label {
401+ text: "Shell Mode"
402+ }
403+ ListItem.ItemSelector {
404+ id: shellModeSelector
405+ anchors { left: parent.left; right: parent.right }
406+ activeFocusOnPress: false
407+ model: ["full-greeter", "greeter", "shell"]
408+ property bool guard: false
409+ onSelectedIndexChanged: {
410+ if (guard) return;
411+ guard = true;
412+ testCase.tearDown();
413+ shellLoader.mode = model[selectedIndex];
414+ shellLoader.active = true;
415+ guard = false;
416+ }
417+ Connections {
418+ target: shellLoader
419+ onModeChanged: {
420+ if (shellModeSelector.guard) {
421+ return;
422+ }
423+ shellModeSelector.guard = true;
424+ for (var i = 0; i < 3; i++) {
425+ if (shellModeSelector.model[i] === shellLoader.mode) {
426+ shellModeSelector.selectedIndex = i;
427+ return;
428+ }
429+ }
430+ shellModeSelector.guard = false;
431+ }
432+ }
433+ }
434+
435+
372436 Label {
373437 text: "Fingerprint"
374438 }
@@ -530,6 +594,10 @@ Rectangle {
530594
531595 topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
532596 verify(topLevelSurfaceList);
597+
598+ // wait until unity8-dash is fully loaded
599+ tryCompare(topLevelSurfaceList, "count", 1);
600+ waitUntilAppWindowIsFullyLoaded(topLevelSurfaceList.idAt(0));
533601 }
534602
535603 function loadDesktopShellWithApps() {
@@ -599,10 +667,14 @@ Rectangle {
599667
600668 GSettingsController.setLifecycleExemptAppids([]);
601669 GSettingsController.setPictureUri("");
670+ }
671+
672+ function ensureInputMethodSurface() {
673+ var surfaceManager = findInvisibleChild(shell, "surfaceManager");
674+ verify(surfaceManager);
675+ surfaceManager.createInputMethodSurface();
602676
603− // there should be only unity8-dash window over there
604− tryCompare(ApplicationManager, "count", 1);
605− compare(ApplicationManager.get(0).appId, "unity8-dash");
677+ tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
606678 }
607679
608680 function test_phoneLeftEdgeDrag_data() {
@@ -949,22 +1021,24 @@ Rectangle {
9491021 function test_showInputMethod() {
9501022 loadShell("phone");
9511023 swipeAwayGreeter();
1024+ ensureInputMethodSurface();
1025+
9521026 var item = findChild(shell, "inputMethod");
953− var surface = SurfaceManager.inputMethodSurface;
1027+ var surface = topLevelSurfaceList.inputMethodSurface;
9541028
955− surface.setState(Mir.MinimizedState);
1029+ surface.requestState(Mir.MinimizedState);
9561030 tryCompare(item, "visible", false);
9571031
958− surface.setState(Mir.RestoredState);
1032+ surface.requestState(Mir.RestoredState);
9591033 tryCompare(item, "visible", true);
9601034
961− surface.setState(Mir.MinimizedState);
1035+ surface.requestState(Mir.MinimizedState);
9621036 tryCompare(item, "visible", false);
9631037
964− surface.setState(Mir.MaximizedState);
1038+ surface.requestState(Mir.MaximizedState);
9651039 tryCompare(item, "visible", true);
9661040
967− surface.setState(Mir.MinimizedState);
1041+ surface.requestState(Mir.MinimizedState);
9681042 tryCompare(item, "visible", false);
9691043 }
9701044
@@ -2267,7 +2341,6 @@ Rectangle {
22672341 var firstAppInLauncher = LauncherModel.get(0).appId;
22682342 compare(launcher.state, data.launcherLocked ? "visible": "");
22692343 compare(launcherPanel.highlightIndex, -2);
2270− compare(ApplicationManager.focusedApplicationId, "unity8-dash");
22712344
22722345 // Use Super + Tab Tab to cycle to the first entry in the launcher
22732346 keyPress(Qt.Key_Super_L, Qt.MetaModifier);
@@ -2397,7 +2470,7 @@ Rectangle {
23972470
23982471 compare(launcher.lockedVisible, true);
23992472
2400− app.surfaceList.get(0).state = Mir.FullscreenState;
2473+ app.surfaceList.get(0).requestState(Mir.FullscreenState);
24012474
24022475 tryCompare(launcher, "lockedVisible", false);
24032476 }
@@ -2528,13 +2601,13 @@ Rectangle {
25282601 var gallerySurfaceId = topLevelSurfaceList.nextId;
25292602 var galleryApp = ApplicationManager.startApplication("gallery-app");
25302603 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2531− compare(ApplicationManager.focusedApplicationId, "gallery-app");
2604+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app");
25322605
25332606 dragLauncherIntoView();
25342607 tap(findChild(shell, "buttonShowDashHome"));
25352608
25362609 tryCompare(broadcastHomeSpy, "count", 1);
2537− compare(ApplicationManager.focusedApplicationId, "gallery-app"); // confirm we didn't focus dash
2610+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app"); // confirm we didn't raise dash
25382611
25392612 var coverPage = findChild(shell, "coverPage");
25402613 tryCompare(coverPage, "showProgress", 0);
@@ -2579,12 +2652,12 @@ Rectangle {
25792652 tryCompare(appSurface, "keymap", "sk");
25802653
25812654 // try to create a prompt surface, verify it also has the same keymap
2582− app.promptSurfaceList.createSurface();
2655+ app.createPromptSurface();
25832656 var promptSurface = app.promptSurfaceList.get(0);
25842657 verify(promptSurface);
25852658 tryCompare(appSurface, "keymap", promptSurface.keymap);
25862659 // ... and that the controller's surface keymap is also the same
2587− tryCompare(MirFocusController.focusedSurface, "keymap", "sk");
2660+ tryCompare(topLevelSurfaceList.focusedWindow.surface, "keymap", "sk");
25882661 app.promptSurfaceList.get(0).close();
25892662
25902663 // switch to next keymap, should go to "cz+qwerty"
@@ -2596,7 +2669,7 @@ Rectangle {
25962669 tryCompare(appSurface, "keymap", "fr");
25972670
25982671 // go to e.g. desktop stage
2599− loadShell("desktop");
2672+ shellLoader.state = "desktop";
26002673 shell.usageScenario = "desktop";
26012674 waitForRendering(shell);
26022675
@@ -2702,7 +2775,6 @@ Rectangle {
27022775 return [
27032776 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
27042777 {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2705− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
27062778 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
27072779 ]
27082780 }
@@ -2712,26 +2784,28 @@ Rectangle {
27122784 shell.usageScenario = "desktop";
27132785 waitForRendering(shell);
27142786 swipeAwayGreeter();
2787+ ensureInputMethodSurface();
27152788 shell.oskEnabled = data.oskEnabled;
27162789
2717− var oldOSKState = SurfaceManager.inputMethodSurface.state;
2718− SurfaceManager.inputMethodSurface.state = Mir.RestoredState;
2790+ var oldOSKState = topLevelSurfaceList.inputMethodSurface.state;
2791+ topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
27192792 var appRepeater = findChild(shell, "appRepeater");
27202793 var dashAppDelegate = appRepeater.itemAt(0);
27212794 verify(dashAppDelegate);
2722− dashAppDelegate.requestedHeight = data.windowHeight;
2723− dashAppDelegate.requestedY = data.windowY;
2724− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2795+
2796+ dashAppDelegate.windowedHeight = data.windowHeight;
2797+ dashAppDelegate.windowedY = data.windowY;
2798+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
27252799 var initialY = dashAppDelegate.y;
27262800 print("intial", initialY, "panel", PanelState.panelHeight);
27272801 verify(initialY > PanelState.panelHeight);
27282802
2729− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2803+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
27302804 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
27312805
2732− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2806+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
27332807 tryCompare(dashAppDelegate, "y", initialY);
2734− SurfaceManager.inputMethodSurface.state = oldOSKState;
2808+ topLevelSurfaceList.inputMethodSurface.requestState(oldOSKState);
27352809 }
27362810
27372811 function test_cursorHidingWithFullscreenApp() {
tests/utils/modules/Unity/Test/UnityTestCase.qml+4−5
@@ -638,18 +638,17 @@ TestCase {
638638 }
639639
640640 /*
641− kill all (fake) running apps but unity8-dash, bringing Unity.Application back to its initial state
641+ kill all (fake) running apps, bringing Unity.Application back to its initial state
642642 */
643643 function killApps() {
644− while (ApplicationManager.count > 1) {
645− var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
646− var application = ApplicationManager.get(appIndex);
644+ while (ApplicationManager.count > 0) {
645+ var application = ApplicationManager.get(0);
647646 ApplicationManager.stopApplication(application.appId);
648647 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
649648 // They won't be gone until those surface items are destroyed.
650649 tryCompareFunction(function() { return application.surfaceList.count }, 0);
651650 tryCompare(application, "state", ApplicationInfo.Stopped);
652651 }
653− compare(ApplicationManager.count, 1);
652+ compare(ApplicationManager.count, 0);
654653 }
655654}