on xenial help sign in
Merge 2160
98af9fbMichael Terry
CMakeLists.txt+1−1
@@ -70,7 +70,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
70find_package(Qt5Concurrent 5.4 REQUIRED)
70find_package(Qt5Concurrent 5.4 REQUIRED)
71find_package(Qt5Sql 5.4 REQUIRED)
71find_package(Qt5Sql 5.4 REQUIRED)
72
72
73−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
73+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
debian/changelog+7
@@ -1,3 +1,10 @@
1+unity8 (8.15+17.04.20161215-0ubuntu1) zesty; urgency=medium
2+
3+ * Let the model deal with some window management decisions (LP:
4+ #1346633)
5+
6+ -- Daniel d'Andrada <daniel.dandrada@canonical.com> Thu, 15 Dec 2016 16:17:29 +0000
7+
1unity8 (8.15+17.04.20161207.1-0ubuntu1) zesty; urgency=medium
8unity8 (8.15+17.04.20161207.1-0ubuntu1) zesty; urgency=medium
2
9
3 [ Albert Astals Cid ]
10 [ Albert Astals Cid ]
debian/control+3−3
@@ -39,7 +39,7 @@ Build-Depends: cmake,
39 libubuntugestures5-private-dev (>= 1.3.2030),
39 libubuntugestures5-private-dev (>= 1.3.2030),
40 libudev-dev,
40 libudev-dev,
41 libudm-common-dev,
41 libudm-common-dev,
42− libunity-api-dev (>= 7.121),
42+ libunity-api-dev (>= 8.0),
43 libusermetricsoutput1-dev,
43 libusermetricsoutput1-dev,
44# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
45 libx11-dev[!arm64 !armhf],
45 libx11-dev[!arm64 !armhf],
@@ -164,7 +164,7 @@ Depends: qml-module-qtquick-layouts,
164 qttranslations5-l10n,
164 qttranslations5-l10n,
165 ubuntu-thumbnailer-impl-0,
165 ubuntu-thumbnailer-impl-0,
166 ubuntu-wallpapers,
166 ubuntu-wallpapers,
167− unity-application-impl-22,
167+ unity-application-impl-23,
168 unity-notifications-impl-3,
168 unity-notifications-impl-3,
169 unity-plugin-scopes | unity-scopes-impl,
169 unity-plugin-scopes | unity-scopes-impl,
170 unity-scopes-impl-12,
170 unity-scopes-impl-12,
@@ -215,7 +215,7 @@ Depends: dbus-test-runner,
215 ${misc:Depends},
215 ${misc:Depends},
216 ${shlibs:Depends},
216 ${shlibs:Depends},
217Provides: unity-application-impl,
217Provides: unity-application-impl,
218− unity-application-impl-22,
218+ unity-application-impl-23,
219 unity8-fake-env,
219 unity8-fake-env,
220Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
220Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
221 unity8-fake-env,
221 unity8-fake-env,
plugins/Utils/windowstatestorage.cpp+23
@@ -189,3 +189,26 @@ QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
189 }
189 }
190 return query;
190 return query;
191}
191}
192+
193+Mir::State WindowStateStorage::toMirState(WindowState state) const
194+{
195+ // assumes a single state (not an OR of several)
196+ switch (state) {
197+ case WindowStateMaximized: return Mir::MaximizedState;
198+ case WindowStateMinimized: return Mir::MinimizedState;
199+ case WindowStateFullscreen: return Mir::FullscreenState;
200+ case WindowStateMaximizedLeft: return Mir::MaximizedLeftState;
201+ case WindowStateMaximizedRight: return Mir::MaximizedRightState;
202+ case WindowStateMaximizedHorizontally: return Mir::HorizMaximizedState;
203+ case WindowStateMaximizedVertically: return Mir::VertMaximizedState;
204+ case WindowStateMaximizedTopLeft: return Mir::MaximizedTopLeftState;
205+ case WindowStateMaximizedTopRight: return Mir::MaximizedTopRightState;
206+ case WindowStateMaximizedBottomLeft: return Mir::MaximizedBottomLeftState;
207+ case WindowStateMaximizedBottomRight: return Mir::MaximizedBottomRightState;
208+
209+ case WindowStateNormal:
210+ case WindowStateRestored:
211+ default:
212+ return Mir::RestoredState;
213+ }
214+}
plugins/Utils/windowstatestorage.h+5
@@ -19,6 +19,9 @@
19#include <QMutex>
19#include <QMutex>
20#include <QFuture>
20#include <QFuture>
21
21
22+// unity-api
23+#include <unity/shell/application/Mir.h>
24+
22class WindowStateStorage: public QObject
25class WindowStateStorage: public QObject
23{
26{
24 Q_OBJECT
27 Q_OBJECT
@@ -56,6 +59,8 @@ public:
56 Q_INVOKABLE void saveStage(const QString &appId, int stage);
59 Q_INVOKABLE void saveStage(const QString &appId, int stage);
57 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
60 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
58
61
62+ Q_INVOKABLE Mir::State toMirState(WindowState state) const;
63+
59private:
64private:
60 void initdb();
65 void initdb();
61
66
plugins/WindowManager/CMakeLists.txt+3−1
@@ -1,10 +1,12 @@
1set(WINDOWMANAGER_SRC
1set(WINDOWMANAGER_SRC
2− TopLevelSurfaceList.cpp
2+ TopLevelWindowModel.cpp
3+ Window.cpp
3 WindowManagerPlugin.cpp
4 WindowManagerPlugin.cpp
4 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
5 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
5 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
6 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
6 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
7 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
7 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
9+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
8 )
10 )
9
11
10add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
12add_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+668
@@ -0,0 +1,668 @@
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+ activateEmptyWindow(window);
200+ }
201+
202+ INFO_MSG << " after " << toString();
203+}
204+
205+void TopLevelWindowModel::connectWindow(Window *window)
206+{
207+ connect(window, &Window::focusRequested, this, [this, window]() {
208+ if (!window->surface()) {
209+ activateEmptyWindow(window);
210+ }
211+ });
212+
213+ connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
214+ if (window->surface()) {
215+ // Condense changes to the focused window
216+ // eg: Do focusedWindow=A to focusedWindow=B instead of
217+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
218+ if (focused) {
219+ Q_ASSERT(m_newlyFocusedWindow == nullptr);
220+ m_focusedWindowChanged = true;
221+ m_newlyFocusedWindow = window;
222+ } else if (m_focusedWindow == window) {
223+ m_focusedWindowChanged = true;
224+ } else {
225+ // don't clear the focused window if you were not there in the first place
226+ // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
227+ }
228+ }
229+ });
230+
231+ connect(window, &Window::closeRequested, this, [this, window]() {
232+ if (!window->surface()) {
233+ // do things ourselves as miral doesn't know about this window
234+ int id = window->id();
235+ int index = indexForId(id);
236+ bool focusOther = false;
237+ Q_ASSERT(index >= 0);
238+ if (window->focused()) {
239+ focusOther = true;
240+ }
241+ m_windowModel[index].application->close();
242+ if (focusOther) {
243+ activateTopMostWindowWithoutId(id);
244+ }
245+ }
246+ });
247+
248+ connect(window, &Window::emptyWindowActivated, this, [this, window]() {
249+ activateEmptyWindow(window);
250+ });
251+}
252+
253+void TopLevelWindowModel::activateEmptyWindow(Window *window)
254+{
255+ Q_ASSERT(!window->surface());
256+ DEBUG_MSG << "(" << window << ")";
257+
258+ // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
259+ // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
260+
261+ window->setFocused(true);
262+ raiseId(window->id());
263+ Window *previousWindow = m_focusedWindow;
264+ setFocusedWindow(window);
265+ if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
266+ m_surfaceManager->activate(nullptr);
267+ }
268+}
269+
270+void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
271+{
272+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
273+ if (!live) {
274+ onSurfaceDied(surface);
275+ }
276+ });
277+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
278+}
279+
280+void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
281+{
282+ if (surface->type() == Mir::InputMethodType) {
283+ removeInputMethodWindow();
284+ return;
285+ }
286+
287+ int i = indexOf(surface);
288+ if (i == -1) {
289+ return;
290+ }
291+
292+ auto application = m_windowModel[i].application;
293+
294+ // can't be starting if it already has a surface
295+ Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
296+
297+ if (application->state() == unityapi::ApplicationInfoInterface::Running) {
298+ m_windowModel[i].removeOnceSurfaceDestroyed = true;
299+ } else {
300+ // assume it got killed by the out-of-memory daemon.
301+ //
302+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
303+ // in its place.
304+ m_windowModel[i].removeOnceSurfaceDestroyed = false;
305+ }
306+}
307+
308+void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
309+{
310+ int i = indexOf(surface);
311+ if (i == -1) {
312+ return;
313+ }
314+
315+ if (m_windowModel[i].removeOnceSurfaceDestroyed) {
316+ removeAt(i);
317+ } else {
318+ auto window = m_windowModel[i].window;
319+ window->setSurface(nullptr);
320+ window->setFocused(false);
321+ INFO_MSG << " Removed surface from entry. After: " << toString();
322+ }
323+}
324+
325+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
326+{
327+ DEBUG_MSG << "(" << surface << ")";
328+ if (surface->type() == Mir::InputMethodType) {
329+ int id = generateId();
330+ Window *qmlWindow = new Window(id, this);
331+ connectWindow(qmlWindow);
332+ qmlWindow->setSurface(surface);
333+ setInputMethodWindow(qmlWindow);
334+ } else {
335+ auto application = m_applicationManager->findApplicationWithSurface(surface);
336+ if (application) {
337+ prependSurface(surface, application);
338+ } else {
339+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
340+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
341+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
342+ int id = generateId();
343+ Window *promptWindow = new Window(id, this);
344+ connectWindow(promptWindow);
345+ promptWindow->setSurface(surface);
346+ connect(surface, &QObject::destroyed, promptWindow, [=](){
347+ promptWindow->setSurface(nullptr);
348+ promptWindow->deleteLater();
349+ });
350+ }
351+ }
352+ // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
353+}
354+
355+void TopLevelWindowModel::removeAt(int index)
356+{
357+ if (m_modelState == IdleState) {
358+ beginRemoveRows(QModelIndex(), index, index);
359+ m_modelState = RemovingState;
360+ } else {
361+ Q_ASSERT(m_modelState == ResettingState);
362+ // No point in signaling anything if we're resetting the whole model
363+ }
364+
365+ auto window = m_windowModel[index].window;
366+
367+ window->setSurface(nullptr);
368+ window->setFocused(false);
369+
370+ m_windowModel.removeAt(index);
371+
372+ if (m_modelState == RemovingState) {
373+ endRemoveRows();
374+ Q_EMIT countChanged();
375+ Q_EMIT listChanged();
376+ m_modelState = IdleState;
377+ }
378+
379+ disconnect(window, 0, this, 0);
380+ if (m_focusedWindow == window) {
381+ setFocusedWindow(nullptr);
382+ }
383+ delete window;
384+
385+ INFO_MSG << " after " << toString();
386+}
387+
388+void TopLevelWindowModel::setInputMethodWindow(Window *window)
389+{
390+ if (m_inputMethodWindow) {
391+ qWarning("Multiple Input Method Surfaces created, removing the old one!");
392+ delete m_inputMethodWindow;
393+ }
394+ m_inputMethodWindow = window;
395+ Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
396+}
397+
398+void TopLevelWindowModel::removeInputMethodWindow()
399+{
400+ if (m_inputMethodWindow) {
401+ delete m_inputMethodWindow;
402+ m_inputMethodWindow = nullptr;
403+ Q_EMIT inputMethodSurfaceChanged(nullptr);
404+ }
405+}
406+
407+void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
408+{
409+ DEBUG_MSG << "(" << surfaces << ")";
410+ const int raiseCount = surfaces.size();
411+ for (int i = 0; i < raiseCount; i++) {
412+ int fromIndex = findIndexOf(surfaces[i]);
413+ if (fromIndex != -1) {
414+ move(fromIndex, 0);
415+ }
416+ }
417+}
418+
419+int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
420+{
421+ return m_windowModel.count();
422+}
423+
424+QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
425+{
426+ if (index.row() < 0 || index.row() >= m_windowModel.size())
427+ return QVariant();
428+
429+ if (role == WindowRole) {
430+ Window *window = m_windowModel.at(index.row()).window;
431+ return QVariant::fromValue(window);
432+ } else if (role == ApplicationRole) {
433+ return QVariant::fromValue(m_windowModel.at(index.row()).application);
434+ } else {
435+ return QVariant();
436+ }
437+}
438+
439+int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
440+{
441+ for (int i=0; i<m_windowModel.count(); i++) {
442+ if (m_windowModel[i].window->surface() == surface) {
443+ return i;
444+ }
445+ }
446+ return -1;
447+}
448+
449+int TopLevelWindowModel::generateId()
450+{
451+ int id = m_nextId;
452+ m_nextId = nextFreeId(nextId(id), id);
453+ return id;
454+}
455+
456+int TopLevelWindowModel::nextId(int id) const
457+{
458+ if (id == m_maxId) {
459+ return id = 1;
460+ } else {
461+ return id + 1;
462+ }
463+}
464+
465+int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
466+{
467+ int firstCandidateId = candidateId;
468+
469+ while (indexForId(candidateId) != -1 || candidateId == latestId) {
470+ candidateId = nextId(candidateId);
471+
472+ if (candidateId == firstCandidateId) {
473+ qFatal("TopLevelWindowModel: run out of window ids.");
474+ }
475+ }
476+
477+ return candidateId;
478+}
479+
480+QString TopLevelWindowModel::toString()
481+{
482+ QString str;
483+ for (int i = 0; i < m_windowModel.count(); ++i) {
484+ auto item = m_windowModel.at(i);
485+
486+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
487+ .arg(QString::number(i),
488+ item.application->appId(),
489+ QString::number((qintptr)item.window->surface(), 16),
490+ QString::number(item.window->id()));
491+
492+ if (i > 0) {
493+ str.append(",");
494+ }
495+ str.append(itemStr);
496+ }
497+ return str;
498+}
499+
500+int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
501+{
502+ for (int i = 0; i < m_windowModel.count(); ++i) {
503+ if (m_windowModel.at(i).window->surface() == surface) {
504+ return i;
505+ }
506+ }
507+ return -1;
508+}
509+
510+int TopLevelWindowModel::indexForId(int id) const
511+{
512+ for (int i = 0; i < m_windowModel.count(); ++i) {
513+ if (m_windowModel[i].window->id() == id) {
514+ return i;
515+ }
516+ }
517+ return -1;
518+}
519+
520+Window *TopLevelWindowModel::windowAt(int index) const
521+{
522+ if (index >=0 && index < m_windowModel.count()) {
523+ return m_windowModel[index].window;
524+ } else {
525+ return nullptr;
526+ }
527+}
528+
529+unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
530+{
531+ if (index >=0 && index < m_windowModel.count()) {
532+ return m_windowModel[index].window->surface();
533+ } else {
534+ return nullptr;
535+ }
536+}
537+
538+unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
539+{
540+ if (index >=0 && index < m_windowModel.count()) {
541+ return m_windowModel[index].application;
542+ } else {
543+ return nullptr;
544+ }
545+}
546+
547+int TopLevelWindowModel::idAt(int index) const
548+{
549+ if (index >=0 && index < m_windowModel.count()) {
550+ return m_windowModel[index].window->id();
551+ } else {
552+ return 0;
553+ }
554+}
555+
556+void TopLevelWindowModel::raiseId(int id)
557+{
558+ if (m_modelState == IdleState) {
559+ DEBUG_MSG << "(id=" << id << ") - do it now.";
560+ doRaiseId(id);
561+ } else {
562+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
563+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
564+ // if we perform yet another model change straight away.
565+ //
566+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
567+ // the index is definitely within bounds.
568+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
569+ }
570+}
571+
572+void TopLevelWindowModel::doRaiseId(int id)
573+{
574+ int fromIndex = indexForId(id);
575+ // can't raise something that doesn't exist or that it's already on top
576+ if (fromIndex != -1 && fromIndex != 0) {
577+ auto surface = m_windowModel[fromIndex].window->surface();
578+ if (surface) {
579+ m_surfaceManager->raise(surface);
580+ } else {
581+ // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
582+ // miral can do about it.
583+ move(fromIndex, 0);
584+ }
585+ }
586+}
587+
588+void TopLevelWindowModel::setFocusedWindow(Window *window)
589+{
590+ if (window != m_focusedWindow) {
591+ INFO_MSG << "(" << window << ")";
592+
593+ Window* previousWindow = m_focusedWindow;
594+
595+ m_focusedWindow = window;
596+ Q_EMIT focusedWindowChanged(m_focusedWindow);
597+
598+ if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
599+ // do it ourselves. miral doesn't know about this window
600+ previousWindow->setFocused(false);
601+ }
602+ }
603+}
604+
605+unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
606+{
607+ return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
608+}
609+
610+Window* TopLevelWindowModel::focusedWindow() const
611+{
612+ return m_focusedWindow;
613+}
614+
615+void TopLevelWindowModel::move(int from, int to)
616+{
617+ if (from == to) return;
618+ DEBUG_MSG << " from=" << from << " to=" << to;
619+
620+ if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
621+ QModelIndex parent;
622+ /* When moving an item down, the destination index needs to be incremented
623+ by one, as explained in the documentation:
624+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
625+
626+ Q_ASSERT(m_modelState == IdleState);
627+ m_modelState = MovingState;
628+
629+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
630+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
631+ const auto &window = m_windowModel.takeAt(from);
632+ m_windowModel.insert(to, window);
633+#else
634+ m_windowModel.move(from, to);
635+#endif
636+ endMoveRows();
637+
638+ Q_EMIT listChanged();
639+ m_modelState = IdleState;
640+
641+ INFO_MSG << " after " << toString();
642+ }
643+}
644+void TopLevelWindowModel::onModificationsStarted()
645+{
646+}
647+
648+void TopLevelWindowModel::onModificationsEnded()
649+{
650+ if (m_focusedWindowChanged) {
651+ setFocusedWindow(m_newlyFocusedWindow);
652+ }
653+ // reset
654+ m_focusedWindowChanged = false;
655+ m_newlyFocusedWindow = nullptr;
656+}
657+
658+void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
659+{
660+ DEBUG_MSG << "(" << forbiddenId << ")";
661+
662+ for (int i = 0; i < m_windowModel.count(); ++i) {
663+ Window *window = m_windowModel[i].window;
664+ if (window->id() != forbiddenId) {
665+ window->activate();
666+ }
667+ }
668+}
plugins/WindowManager/TopLevelWindowModel.hnew file+260
@@ -0,0 +1,260 @@
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+ void activateTopMostWindowWithoutId(int forbiddenId);
222+
223+ struct ModelEntry {
224+ ModelEntry() {}
225+ ModelEntry(Window *window,
226+ unity::shell::application::ApplicationInfoInterface *application)
227+ : window(window), application(application) {}
228+ Window *window{nullptr};
229+ unity::shell::application::ApplicationInfoInterface *application{nullptr};
230+ bool removeOnceSurfaceDestroyed{false};
231+ };
232+
233+ QVector<ModelEntry> m_windowModel;
234+ Window* m_inputMethodWindow{nullptr};
235+ Window* m_focusedWindow{nullptr};
236+
237+ int m_nextId{1};
238+ // Just something big enough that we don't risk running out of unused id numbers.
239+ // Not sure if QML int type supports something close to std::numeric_limits<int>::max() and
240+ // there's no reason to try out its limits.
241+ static const int m_maxId{1000000};
242+
243+ unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
244+ unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
245+
246+ enum ModelState {
247+ IdleState,
248+ InsertingState,
249+ RemovingState,
250+ MovingState,
251+ ResettingState
252+ };
253+ ModelState m_modelState{IdleState};
254+
255+ // Valid between modificationsStarted and modificationsEnded
256+ bool m_focusedWindowChanged{false};
257+ Window *m_newlyFocusedWindow{nullptr};
258+};
259+
260+#endif // TOPLEVELWINDOWMODEL_H
plugins/WindowManager/Window.cppnew file+237
@@ -0,0 +1,237 @@
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+#include <QTextStream>
24+
25+namespace unityapi = unity::shell::application;
26+
27+Q_LOGGING_CATEGORY(UNITY_WINDOW, "unity.window", QtWarningMsg)
28+
29+#define DEBUG_MSG qCDebug(UNITY_WINDOW).nospace() << qPrintable(toString()) << "::" << __func__
30+
31+Window::Window(int id, QObject *parent)
32+ : QObject(parent)
33+ , m_id(id)
34+{
35+ DEBUG_MSG << "()";
36+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
37+}
38+
39+Window::~Window()
40+{
41+ DEBUG_MSG << "()";
42+}
43+
44+QPoint Window::position() const
45+{
46+ return m_position;
47+}
48+
49+QPoint Window::requestedPosition() const
50+{
51+ return m_requestedPosition;
52+}
53+
54+void Window::setRequestedPosition(const QPoint &value)
55+{
56+ m_positionRequested = true;
57+ if (value != m_requestedPosition) {
58+ m_requestedPosition = value;
59+ Q_EMIT requestedPositionChanged(m_requestedPosition);
60+ if (m_surface) {
61+ m_surface->setRequestedPosition(value);
62+ } else {
63+ // fake-miral: always comply
64+ m_position = m_requestedPosition;
65+ Q_EMIT positionChanged(m_position);
66+ }
67+ }
68+}
69+
70+Mir::State Window::state() const
71+{
72+ return m_state;
73+}
74+
75+bool Window::focused() const
76+{
77+ return m_focused;
78+}
79+
80+bool Window::confinesMousePointer() const
81+{
82+ if (m_surface) {
83+ return m_surface->confinesMousePointer();
84+ } else {
85+ return false;
86+ }
87+}
88+
89+int Window::id() const
90+{
91+ return m_id;
92+}
93+
94+unityapi::MirSurfaceInterface* Window::surface() const
95+{
96+ return m_surface;
97+}
98+
99+void Window::requestState(Mir::State state)
100+{
101+ m_stateRequested = true;
102+ if (m_surface) {
103+ m_surface->requestState(state);
104+ } else if (m_state != state) {
105+ m_state = state;
106+ Q_EMIT stateChanged(m_state);
107+ }
108+}
109+
110+void Window::close()
111+{
112+ if (m_surface) {
113+ m_surface->close();
114+ } else {
115+ Q_EMIT closeRequested();
116+ }
117+}
118+
119+void Window::activate()
120+{
121+ DEBUG_MSG << "()";
122+ if (m_surface) {
123+ m_surface->activate();
124+ } else {
125+ Q_EMIT emptyWindowActivated();
126+ }
127+}
128+
129+void Window::setSurface(unityapi::MirSurfaceInterface *surface)
130+{
131+ DEBUG_MSG << "(" << surface << ")";
132+ if (m_surface) {
133+ disconnect(m_surface, 0, this, 0);
134+ }
135+
136+ m_surface = surface;
137+
138+ if (m_surface) {
139+ connect(surface, &unityapi::MirSurfaceInterface::focusRequested, this, [this]() {
140+ Q_EMIT focusRequested();
141+ });
142+
143+ connect(surface, &unityapi::MirSurfaceInterface::closeRequested, this, &Window::closeRequested);
144+
145+ connect(surface, &unityapi::MirSurfaceInterface::positionChanged, this, [this]() {
146+ updatePosition();
147+ });
148+
149+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [this]() {
150+ updateState();
151+ });
152+
153+ connect(surface, &unityapi::MirSurfaceInterface::focusedChanged, this, [this]() {
154+ updateFocused();
155+ });
156+
157+ // bring it up to speed
158+ if (m_positionRequested) {
159+ m_surface->setRequestedPosition(m_requestedPosition);
160+ }
161+ if (m_stateRequested && m_surface->state() == Mir::RestoredState) {
162+ m_surface->requestState(m_state);
163+ }
164+
165+ // and sync with surface
166+ updatePosition();
167+ updateState();
168+ updateFocused();
169+ }
170+
171+ Q_EMIT surfaceChanged(surface);
172+}
173+
174+void Window::updatePosition()
175+{
176+ if (m_surface->position() != m_position) {
177+ m_position = m_surface->position();
178+ Q_EMIT positionChanged(m_position);
179+ }
180+}
181+
182+void Window::updateState()
183+{
184+ if (m_surface->state() != m_state) {
185+ m_state = m_surface->state();
186+ Q_EMIT stateChanged(m_state);
187+ }
188+}
189+
190+void Window::updateFocused()
191+{
192+ if (m_surface->focused() != m_focused) {
193+ m_focused = m_surface->focused();
194+ Q_EMIT focusedChanged(m_focused);
195+ }
196+}
197+
198+void Window::setFocused(bool value)
199+{
200+ if (value != m_focused) {
201+ DEBUG_MSG << "(" << value << ")";
202+ m_focused = value;
203+ Q_EMIT focusedChanged(m_focused);
204+ // when we have a surface we get focus changes from updateFocused() instead
205+ Q_ASSERT(!m_surface);
206+ }
207+}
208+
209+QString Window::toString() const
210+{
211+ QString result;
212+ {
213+ QTextStream stream(&result);
214+ stream << "Window["<<(void*)this<<", id="<<id()<<", ";
215+ if (surface()) {
216+ stream << "MirSurface["<<(void*)surface()<<",\""<<surface()->name()<<"\"]";
217+ } else {
218+ stream << "null";
219+ }
220+ stream << "]";
221+ }
222+ return result;
223+}
224+
225+QDebug operator<<(QDebug dbg, const Window *window)
226+{
227+ QDebugStateSaver saver(dbg);
228+ dbg.nospace();
229+
230+ if (window) {
231+ dbg << qPrintable(window->toString());
232+ } else {
233+ dbg << (void*)(window);
234+ }
235+
236+ return dbg;
237+}
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 @@
16
16
17#include "WindowManagerPlugin.h"
17#include "WindowManagerPlugin.h"
18
18
19−#include "TopLevelSurfaceList.h"
19+#include "TopLevelWindowModel.h"
20+#include "Window.h"
20
21
21#include <QtQml>
22#include <QtQml>
22
23
23void WindowManagerPlugin::registerTypes(const char *uri)
24void WindowManagerPlugin::registerTypes(const char *uri)
24{
25{
25− qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
26+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
27+
28+ qRegisterMetaType<Window*>("Window*");
26
29
27 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
30 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
28}
31}
po/unity8.pot+1−1
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-12-07 13:49+0000\n"
11+"POT-Creation-Date: 2016-12-15 16:17+0000\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
qml/Components/InputMethod.qml+5−4
@@ -23,6 +23,8 @@ Item {
23
23
24 readonly property rect visibleRect: surfaceItem.surface && visible ? surfaceItem.surface.inputBounds : Qt.rect(0, 0, 0, 0)
24 readonly property rect visibleRect: surfaceItem.surface && visible ? surfaceItem.surface.inputBounds : Qt.rect(0, 0, 0, 0)
25
25
26+ property var surface
27+
26 MirSurfaceItem {
28 MirSurfaceItem {
27 id: surfaceItem
29 id: surfaceItem
28 anchors.fill: parent
30 anchors.fill: parent
@@ -31,7 +33,7 @@ Item {
31
33
32 surfaceWidth: root.enabled ? width : -1
34 surfaceWidth: root.enabled ? width : -1
33 surfaceHeight: root.enabled ? height : -1
35 surfaceHeight: root.enabled ? height : -1
34− surface: SurfaceManager.inputMethodSurface
36+ surface: root.surface
35
37
36 onLiveChanged: {
38 onLiveChanged: {
37 if (surface !== null && !live) {
39 if (surface !== null && !live) {
@@ -50,7 +52,6 @@ Item {
50 }
52 }
51
53
52 visible: surfaceItem.surface &&
54 visible: surfaceItem.surface &&
53− surfaceItem.surfaceState != Mir.HiddenState &&
55+ surfaceItem.surface.visible &&
54− surfaceItem.surfaceState != Mir.MinimizedState &&
55− root.enabled
56+ root.enabled
56}
57}
qml/Components/KeymapSwitcher.qml+4−1
@@ -23,6 +23,9 @@ import Unity.Application 0.1
23QtObject {
23QtObject {
24 id: root
24 id: root
25
25
26+ // to be set from outside
27+ property var focusedSurface: null
28+
26 property GlobalShortcut shortcutNext: GlobalShortcut {
29 property GlobalShortcut shortcutNext: GlobalShortcut {
27 shortcut: Qt.MetaModifier|Qt.Key_Space
30 shortcut: Qt.MetaModifier|Qt.Key_Space
28 onTriggered: root.nextKeymap()
31 onTriggered: root.nextKeymap()
@@ -60,7 +63,7 @@ QtObject {
60 }
63 }
61
64
62 property Binding surfaceKeymapBinding: Binding {
65 property Binding surfaceKeymapBinding: Binding {
63− target: MirFocusController.focusedSurface
66+ target: root.focusedSurface
64 property: "keymap"
67 property: "keymap"
65 value: root.currentKeymap
68 value: root.currentKeymap
66 }
69 }
qml/Shell.qml+14−6
@@ -43,7 +43,7 @@ import Unity.Session 0.1
43import Unity.DashCommunicator 0.1
43import Unity.DashCommunicator 0.1
44import Unity.Indicators 0.1 as Indicators
44import Unity.Indicators 0.1 as Indicators
45import Cursor 1.1
45import Cursor 1.1
46−import WindowManager 0.1
46+import WindowManager 1.0
47
47
48
48
49StyledItem {
49StyledItem {
@@ -264,10 +264,15 @@ StyledItem {
264 width: parent.width
264 width: parent.width
265 height: parent.height
265 height: parent.height
266
266
267− TopLevelSurfaceList {
267+ SurfaceManager {
268+ id: surfaceMan
269+ objectName: "surfaceManager"
270+ }
271+ TopLevelWindowModel {
268 id: topLevelSurfaceList
272 id: topLevelSurfaceList
269 objectName: "topLevelSurfaceList"
273 objectName: "topLevelSurfaceList"
270− applicationsModel: ApplicationManager
274+ applicationManager: ApplicationManager // it's a singleton
275+ surfaceManager: surfaceMan
271 }
276 }
272
277
273 Stage {
278 Stage {
@@ -314,6 +319,7 @@ StyledItem {
314 InputMethod {
319 InputMethod {
315 id: inputMethod
320 id: inputMethod
316 objectName: "inputMethod"
321 objectName: "inputMethod"
322+ surface: topLevelSurfaceList.inputMethodSurface
317 anchors {
323 anchors {
318 fill: parent
324 fill: parent
319 topMargin: panel.panelHeight
325 topMargin: panel.panelHeight
@@ -467,8 +473,8 @@ StyledItem {
467 greeterShown: greeter.shown
473 greeterShown: greeter.shown
468 }
474 }
469
475
470− readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
476+ readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
471− ? MirFocusController.focusedSurface.state === Mir.FullscreenState
477+ ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
472 : false
478 : false
473 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
479 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
474 || greeter.hasLockedApp
480 || greeter.hasLockedApp
@@ -762,7 +768,9 @@ StyledItem {
762 }
768 }
763
769
764 // non-visual objects
770 // non-visual objects
765− KeymapSwitcher {}
771+ KeymapSwitcher {
772+ focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
773+ }
766 BrightnessControl {}
774 BrightnessControl {}
767
775
768 Rectangle {
776 Rectangle {
qml/Stage/FakeMaximizeDelegate.qml+7−7
@@ -98,19 +98,19 @@ Rectangle {
98 function commit() {
98 function commit() {
99 if (progress > hintThreshold && edge != -1) {
99 if (progress > hintThreshold && edge != -1) {
100 if (edge == Item.Top) {
100 if (edge == Item.Top) {
101− target.maximize();
101+ target.requestMaximize();
102 } else if (edge == Item.Left) {
102 } else if (edge == Item.Left) {
103− target.maximizeLeft();
103+ target.requestMaximizeLeft();
104 } else if (edge == Item.Right) {
104 } else if (edge == Item.Right) {
105− target.maximizeRight();
105+ target.requestMaximizeRight();
106 } else if (edge == Item.TopLeft) {
106 } else if (edge == Item.TopLeft) {
107− target.maximizeTopLeft();
107+ target.requestMaximizeTopLeft();
108 } else if (edge == Item.TopRight) {
108 } else if (edge == Item.TopRight) {
109− target.maximizeTopRight();
109+ target.requestMaximizeTopRight();
110 } else if (edge == Item.BottomLeft) {
110 } else if (edge == Item.BottomLeft) {
111− target.maximizeBottomLeft();
111+ target.requestMaximizeBottomLeft();
112 } else if (edge == Item.BottomRight) {
112 } else if (edge == Item.BottomRight) {
113− target.maximizeBottomRight();
113+ target.requestMaximizeBottomRight();
114 }
114 }
115 } else {
115 } else {
116 stop();
116 stop();
qml/Stage/MoveHandler.qml+1−1
@@ -114,7 +114,7 @@ QtObject {
114 // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
114 // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
115 if (target.anyMaximized && !target.windowedTransitionRunning) {
115 if (target.anyMaximized && !target.windowedTransitionRunning) {
116 priv.progress = 0;
116 priv.progress = 0;
117− target.restore(false, WindowStateStorage.WindowStateNormal);
117+ target.requestRestore();
118 }
118 }
119
119
120 var pos = mapToItem(target.parent, mouse.x, mouse.y);
120 var pos = mapToItem(target.parent, mouse.x, mouse.y);
qml/Stage/Stage.qml+127−145
@@ -117,8 +117,7 @@ FocusScope {
117 }
117 }
118 }
118 }
119
119
120− property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
120+ property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
121− priv.focusedAppDelegate.surface.confinesMousePointer ?
122 priv.focusedAppDelegate.clientAreaItem : null;
121 priv.focusedAppDelegate.clientAreaItem : null;
123
122
124 signal itemSnapshotRequested(Item item)
123 signal itemSnapshotRequested(Item item)
@@ -184,29 +183,34 @@ FocusScope {
184 GlobalShortcut {
183 GlobalShortcut {
185 id: maximizeWindowShortcut
184 id: maximizeWindowShortcut
186 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
185 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
187− onTriggered: priv.focusedAppDelegate.maximize()
186+ onTriggered: priv.focusedAppDelegate.requestMaximize()
188 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
187 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
189 }
188 }
190
189
191 GlobalShortcut {
190 GlobalShortcut {
192 id: maximizeWindowLeftShortcut
191 id: maximizeWindowLeftShortcut
193 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
192 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
194− onTriggered: priv.focusedAppDelegate.maximizeLeft()
193+ onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
195 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
194 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
196 }
195 }
197
196
198 GlobalShortcut {
197 GlobalShortcut {
199 id: maximizeWindowRightShortcut
198 id: maximizeWindowRightShortcut
200 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
199 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
201− onTriggered: priv.focusedAppDelegate.maximizeRight()
200+ onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
202 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
201 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
203 }
202 }
204
203
205 GlobalShortcut {
204 GlobalShortcut {
206 id: minimizeRestoreShortcut
205 id: minimizeRestoreShortcut
207 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
206 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
207+ onTriggered: {
208− onTriggered: priv.focusedAppDelegate.anyMaximized
208+ if (priv.focusedAppDelegate.anyMaximized) {
209− ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
209+ priv.focusedAppDelegate.requestRestore();
210+ } else {
211+ priv.focusedAppDelegate.requestMinimize();
212+ }
213+ }
210 active: root.state == "windowed" && priv.focusedAppDelegate
214 active: root.state == "windowed" && priv.focusedAppDelegate
211 }
215 }
212
216
@@ -242,20 +246,10 @@ FocusScope {
242 }
246 }
243
247
244 function minimizeAllWindows() {
248 function minimizeAllWindows() {
245− for (var i = 0; i < appRepeater.count; i++) {
249+ for (var i = appRepeater.count - 1; i >= 0; i--) {
246− var appDelegate = appRepeater.itemAt(i);
247− if (appDelegate && !appDelegate.minimized) {
248− appDelegate.minimize();
249− }
250− }
251− }
252−
253− function focusNext() {
254− for (var i = 0; i < appRepeater.count; i++) {
255 var appDelegate = appRepeater.itemAt(i);
250 var appDelegate = appRepeater.itemAt(i);
256 if (appDelegate && !appDelegate.minimized) {
251 if (appDelegate && !appDelegate.minimized) {
257− appDelegate.focus = true;
252+ appDelegate.requestMinimize();
258− return;
259 }
253 }
260 }
254 }
261 }
255 }
@@ -356,8 +350,8 @@ FocusScope {
356 Connections {
350 Connections {
357 target: PanelState
351 target: PanelState
358 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
352 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
359− onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
353+ onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
360− onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
354+ onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
361 }
355 }
362
356
363 Binding {
357 Binding {
@@ -432,13 +426,6 @@ FocusScope {
432 }
426 }
433 }
427 }
434
428
435− Binding {
436− target: MirFocusController
437− property: "focusedSurface"
438− value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
439− when: !appRepeater.startingUp && root.parent
440− }
441−
442 states: [
429 states: [
443 State {
430 State {
444 name: "spread"; when: priv.goneToSpread
431 name: "spread"; when: priv.goneToSpread
@@ -631,7 +618,7 @@ FocusScope {
631
618
632 onShownChanged: {
619 onShownChanged: {
633 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
620 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
634− priv.mainStageDelegate.claimFocus();
621+ priv.mainStageDelegate.activate();
635 }
622 }
636 }
623 }
637
624
@@ -664,14 +651,23 @@ FocusScope {
664 }
651 }
665 }
652 }
666
653
667− TopLevelSurfaceRepeater {
654+ Repeater {
668 id: appRepeater
655 id: appRepeater
669 model: topLevelSurfaceList
656 model: topLevelSurfaceList
670 objectName: "appRepeater"
657 objectName: "appRepeater"
671
658
659+ function indexOf(delegateItem) {
660+ for (var i = 0; i < count; i++) {
661+ if (itemAt(i) === delegateItem) {
662+ return i;
663+ }
664+ }
665+ return -1;
666+ }
667+
672 delegate: FocusScope {
668 delegate: FocusScope {
673 id: appDelegate
669 id: appDelegate
674− objectName: "appDelegate_" + model.id
670+ objectName: "appDelegate_" + model.window.id
675 property int itemIndex: index // We need this from outside the repeater
671 property int itemIndex: index // We need this from outside the repeater
676 // z might be overriden in some cases by effects, but we need z ordering
672 // z might be overriden in some cases by effects, but we need z ordering
677 // to calculate occlusion detection
673 // to calculate occlusion detection
@@ -683,11 +679,11 @@ FocusScope {
683 }
679 }
684 z: normalZ
680 z: normalZ
685
681
686− // Normally we want x/y where we request it to be. Width/height of our delegate will
682+ // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
687 // match what the actual surface size is.
683 // match what the actual surface size is.
688 // Don't write to those, they will be set by states
684 // Don't write to those, they will be set by states
689− x: requestedX
685+ x: model.window.position.x - clientAreaItem.x
690− y: requestedY
686+ y: model.window.position.y - clientAreaItem.y
691 width: decoratedWindow.implicitWidth
687 width: decoratedWindow.implicitWidth
692 height: decoratedWindow.implicitHeight
688 height: decoratedWindow.implicitHeight
693
689
@@ -697,6 +693,12 @@ FocusScope {
697 property real requestedY: windowedY
693 property real requestedY: windowedY
698 property real requestedWidth: windowedWidth
694 property real requestedWidth: windowedWidth
699 property real requestedHeight: windowedHeight
695 property real requestedHeight: windowedHeight
696+ Binding {
697+ target: model.window; property: "requestedPosition"
698+ // miral doesn't know about our window decorations. So we have to deduct them
699+ value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
700+ appDelegate.requestedY + appDelegate.clientAreaItem.y)
701+ }
700
702
701 // In those are for windowed mode. Those values basically store the window's properties
703 // In those are for windowed mode. Those values basically store the window's properties
702 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
704 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
@@ -788,7 +790,7 @@ FocusScope {
788 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
790 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
789
791
790 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
792 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
791− readonly property bool fullscreen: surface ? surface.state === Mir.FullscreenState : application.fullscreen
793+ readonly property bool fullscreen: window.state === Mir.FullscreenState
792
794
793 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
795 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
794 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
796 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
@@ -814,7 +816,9 @@ FocusScope {
814 priv.updateMainAndSideStageIndexes()
816 priv.updateMainAndSideStageIndexes()
815 }
817 }
816
818
817− readonly property var surface: model.surface
819+ readonly property var surface: model.window.surface
820+ readonly property var window: model.window
821+
818 readonly property alias resizeArea: resizeArea
822 readonly property alias resizeArea: resizeArea
819 readonly property alias focusedSurface: decoratedWindow.focusedSurface
823 readonly property alias focusedSurface: decoratedWindow.focusedSurface
820 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
824 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
@@ -823,6 +827,25 @@ FocusScope {
823 readonly property bool isDash: appId == "unity8-dash"
827 readonly property bool isDash: appId == "unity8-dash"
824 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
828 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
825
829
830+ function activate() {
831+ if (model.window.focused) {
832+ updateQmlFocusFromMirSurfaceFocus();
833+ } else {
834+ model.window.activate();
835+ }
836+ }
837+ function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
838+ function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
839+ function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
840+ function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
841+ function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
842+ function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
843+ function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
844+ function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
845+ function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
846+ function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
847+ function requestRestore() { model.window.requestState(Mir.RestoredState); }
848+
826 function claimFocus() {
849 function claimFocus() {
827 if (root.state == "spread") {
850 if (root.state == "spread") {
828 spreadItem.highlightedIndex = index
851 spreadItem.highlightedIndex = index
@@ -834,43 +857,16 @@ FocusScope {
834 }
857 }
835 priv.updateMainAndSideStageIndexes();
858 priv.updateMainAndSideStageIndexes();
836 }
859 }
837−
838 if (root.mode == "windowed") {
860 if (root.mode == "windowed") {
839 appDelegate.restore(true /* animated */, appDelegate.windowState);
861 appDelegate.restore(true /* animated */, appDelegate.windowState);
840− } else {
841− appDelegate.focus = true;
842− }
843− }
844− Connections {
845− target: model.surface
846− onFocusRequested: {
847− claimFocus();
848− }
849− }
850− Connections {
851− target: model.application
852− onFocusRequested: {
853− if (!model.surface) {
854− // when an app has no surfaces, we assume there's only one entry representing it:
855− // this delegate.
856− claimFocus();
857− } else {
858− // if the application has surfaces, focus request should be at surface-level.
859− }
860 }
862 }
863+ appDelegate.focus = true;
861 }
864 }
862
865
863− onFocusChanged: {
866+ function updateQmlFocusFromMirSurfaceFocus() {
864− if (appRepeater.startingUp)
867+ if (model.window.focused) {
865− return;
868+ claimFocus();
866−
867− if (focus) {
868− topLevelSurfaceList.raiseId(model.id);
869 priv.focusedAppDelegate = appDelegate;
869 priv.focusedAppDelegate = appDelegate;
870− } else if (!focus && priv.focusedAppDelegate === appDelegate && root.state != "spread") {
871− priv.focusedAppDelegate = null;
872− // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
873− MirFocusController.focusedSurface = null;
874 }
870 }
875 }
871 }
876
872
@@ -883,6 +879,41 @@ FocusScope {
883 minimumY: PanelState.panelHeight
879 minimumY: PanelState.panelHeight
884 }
880 }
885
881
882+ Connections {
883+ target: model.window
884+ onFocusedChanged: {
885+ updateQmlFocusFromMirSurfaceFocus();
886+ }
887+ onFocusRequested: {
888+ appDelegate.activate();
889+ }
890+ onStateChanged: {
891+ if (model.window.state === Mir.MinimizedState) {
892+ appDelegate.minimize();
893+ } else if (model.window.state === Mir.MaximizedState) {
894+ appDelegate.maximize();
895+ } else if (model.window.state === Mir.VertMaximizedState) {
896+ appDelegate.maximizeVertically();
897+ } else if (model.window.state === Mir.HorizMaximizedState) {
898+ appDelegate.maximizeHorizontally();
899+ } else if (model.window.state === Mir.MaximizedLeftState) {
900+ appDelegate.maximizeLeft();
901+ } else if (model.window.state === Mir.MaximizedRightState) {
902+ appDelegate.maximizeRight();
903+ } else if (model.window.state === Mir.MaximizedTopLeftState) {
904+ appDelegate.maximizeTopLeft();
905+ } else if (model.window.state === Mir.MaximizedTopRightState) {
906+ appDelegate.maximizeTopRight();
907+ } else if (model.window.state === Mir.MaximizedBottomLeftState) {
908+ appDelegate.maximizeBottomLeft();
909+ } else if (model.window.state === Mir.MaximizedBottomRightState) {
910+ appDelegate.maximizeBottomRight();
911+ } else if (model.window.state === Mir.RestoredState) {
912+ appDelegate.restore();
913+ }
914+ }
915+ }
916+
886 Component.onCompleted: {
917 Component.onCompleted: {
887 if (application && application.rotatesWindowContents) {
918 if (application && application.rotatesWindowContents) {
888 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
919 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -895,17 +926,9 @@ FocusScope {
895 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
926 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
896 // Now load any saved state. This needs to happen *after* the cascading!
927 // Now load any saved state. This needs to happen *after* the cascading!
897 windowStateSaver.load();
928 windowStateSaver.load();
929+ model.window.requestState(WindowStateStorage.toMirState(windowState));
898
930
899− // NB: We're differentiating if this delegate was created in response to a new entry in the model
931+ updateQmlFocusFromMirSurfaceFocus();
900− // or if the Repeater is just populating itself with delegates to match the model it received.
901− if (!appRepeater.startingUp) {
902− // a top level window is always the focused one when it first appears, unfocusing
903− // any preexisting one
904− if (root.state == "spread") {
905− spreadItem.highlightedIndex = index;
906− }
907− claimFocus();
908− }
909
932
910 refreshStage();
933 refreshStage();
911 _constructing = false;
934 _constructing = false;
@@ -921,17 +944,6 @@ FocusScope {
921 if (visuallyMaximized) {
944 if (visuallyMaximized) {
922 priv.updateForegroundMaximizedApp();
945 priv.updateForegroundMaximizedApp();
923 }
946 }
924−
925− if (focus) {
926− // focus some other window
927− for (var i = 0; i < appRepeater.count; i++) {
928− var appDelegate = appRepeater.itemAt(i);
929− if (appDelegate && !appDelegate.minimized && i != index) {
930− appDelegate.focus = true;
931− return;
932− }
933− }
934− }
935 }
947 }
936
948
937 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
949 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
@@ -952,7 +964,7 @@ FocusScope {
952 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
964 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
953
965
954 function close() {
966 function close() {
955− model.surface.close();
967+ model.window.close();
956 }
968 }
957
969
958 function maximize(animated) {
970 function maximize(animated) {
@@ -995,15 +1007,10 @@ FocusScope {
995 animationsEnabled = (animated === undefined) || animated;
1007 animationsEnabled = (animated === undefined) || animated;
996 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
1008 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
997 }
1009 }
998− function restoreFromMaximized(animated) {
999− animationsEnabled = (animated === undefined) || animated;
1000− windowState = WindowStateStorage.WindowStateRestored;
1001− }
1002 function restore(animated,state) {
1010 function restore(animated,state) {
1003 animationsEnabled = (animated === undefined) || animated;
1011 animationsEnabled = (animated === undefined) || animated;
1004 windowState = state || WindowStateStorage.WindowStateRestored;
1012 windowState = state || WindowStateStorage.WindowStateRestored;
1005 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1013 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1006− focus = true;
1007 }
1014 }
1008
1015
1009 function playFocusAnimation() {
1016 function playFocusAnimation() {
@@ -1017,7 +1024,7 @@ FocusScope {
1017 rightEdgeFocusAnimation.start()
1024 rightEdgeFocusAnimation.start()
1018 }
1025 }
1019 } else if (state == "windowedRightEdge" || state == "windowed") {
1026 } else if (state == "windowedRightEdge" || state == "windowed") {
1020− claimFocus();
1027+ activate();
1021 } else {
1028 } else {
1022 focusAnimation.start()
1029 focusAnimation.start()
1023 }
1030 }
@@ -1055,10 +1062,10 @@ FocusScope {
1055 to: 1
1062 to: 1
1056 duration: UbuntuAnimation.SnapDuration
1063 duration: UbuntuAnimation.SnapDuration
1057 onStarted: {
1064 onStarted: {
1058− topLevelSurfaceList.raiseId(model.id);
1065+ topLevelSurfaceList.raiseId(model.window.id);
1059 }
1066 }
1060 onStopped: {
1067 onStopped: {
1061− appDelegate.claimFocus();
1068+ appDelegate.activate();
1062 }
1069 }
1063 }
1070 }
1064 ParallelAnimation {
1071 ParallelAnimation {
@@ -1068,7 +1075,7 @@ FocusScope {
1068 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1075 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1069 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1076 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1070 onStopped: {
1077 onStopped: {
1071− appDelegate.focus = true
1078+ appDelegate.activate();
1072 }
1079 }
1073 }
1080 }
1074 ParallelAnimation {
1081 ParallelAnimation {
@@ -1318,15 +1325,9 @@ FocusScope {
1318 windowedY: restoredY;
1325 windowedY: restoredY;
1319 }
1326 }
1320 },
1327 },
1321− State {
1322− name: "semiMaximized"
1323− PropertyChanges { target: touchControls; enabled: true }
1324− PropertyChanges { target: resizeArea; enabled: true }
1325− PropertyChanges { target: decoratedWindow; shadowOpacity: .3 }
1326− },
1327 State {
1328 State {
1328 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1329 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1329− extend: "semiMaximized"
1330+ extend: "normal"
1330 PropertyChanges {
1331 PropertyChanges {
1331 target: appDelegate
1332 target: appDelegate
1332 windowedX: root.leftMargin
1333 windowedX: root.leftMargin
@@ -1345,7 +1346,7 @@ FocusScope {
1345 },
1346 },
1346 State {
1347 State {
1347 name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1348 name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1348− extend: "semiMaximized"
1349+ extend: "normal"
1349 PropertyChanges {
1350 PropertyChanges {
1350 target: appDelegate
1351 target: appDelegate
1351 windowedX: root.leftMargin
1352 windowedX: root.leftMargin
@@ -1364,7 +1365,7 @@ FocusScope {
1364 },
1365 },
1365 State {
1366 State {
1366 name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1367 name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1367− extend: "semiMaximized"
1368+ extend: "normal"
1368 PropertyChanges {
1369 PropertyChanges {
1369 target: appDelegate
1370 target: appDelegate
1370 windowedX: root.leftMargin
1371 windowedX: root.leftMargin
@@ -1383,15 +1384,15 @@ FocusScope {
1383 },
1384 },
1384 State {
1385 State {
1385 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1386 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1386− extend: "semiMaximized"
1387+ extend: "normal"
1387− PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: windowedY;
1388+ PropertyChanges { target: appDelegate; windowedX: root.leftMargin; windowedY: windowedY;
1388− requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appDelegate.windowedHeight }
1389+ windowedWidth: appContainer.width - root.leftMargin; windowedHeight: windowedHeight }
1389 },
1390 },
1390 State {
1391 State {
1391 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1392 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1392− extend: "semiMaximized"
1393+ extend: "normal"
1393− PropertyChanges { target: appDelegate; requestedX: windowedX; requestedY: PanelState.panelHeight;
1394+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1394− requestedWidth: appDelegate.windowedWidth; requestedHeight: appContainer.height - PanelState.panelHeight }
1395+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1395 },
1396 },
1396 State {
1397 State {
1397 name: "minimized"; when: appDelegate.minimized
1398 name: "minimized"; when: appDelegate.minimized
@@ -1417,14 +1418,6 @@ FocusScope {
1417 to: "staged,stagedWithSideStage"
1418 to: "staged,stagedWithSideStage"
1418 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1419 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1419 },
1420 },
1420− Transition {
1421− from: "maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,minimized";
1422− to: "normal,restored"
1423− enabled: appDelegate.animationsEnabled
1424− PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1425− UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,requestedWidth,requestedHeight,windowedWidth,windowedHeight,scale";
1426− duration: priv.animationDuration }
1427− },
1428 Transition {
1421 Transition {
1429 to: "minimized"
1422 to: "minimized"
1430 enabled: appDelegate.animationsEnabled
1423 enabled: appDelegate.animationsEnabled
@@ -1432,14 +1425,6 @@ FocusScope {
1432 SequentialAnimation {
1425 SequentialAnimation {
1433 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1426 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1434 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1427 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1435− ScriptAction {
1436− script: {
1437− if (appDelegate.minimized) {
1438− appDelegate.focus = false;
1439− priv.focusNext();
1440− }
1441− }
1442− }
1443 }
1428 }
1444 },
1429 },
1445 Transition {
1430 Transition {
@@ -1492,8 +1477,8 @@ FocusScope {
1492 },
1477 },
1493 Transition {
1478 Transition {
1494 id: windowedTransition
1479 id: windowedTransition
1495− from: "normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTop,maximizedBottom,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1480+ from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
1496− to: "normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTop,maximizedBottom,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1481+ to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1497 enabled: appDelegate.animationsEnabled
1482 enabled: appDelegate.animationsEnabled
1498 SequentialAnimation {
1483 SequentialAnimation {
1499 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1484 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
@@ -1529,7 +1514,7 @@ FocusScope {
1529 visible: enabled
1514 visible: enabled
1530
1515
1531 onPressed: {
1516 onPressed: {
1532− appDelegate.focus = true;
1517+ appDelegate.activate();
1533 }
1518 }
1534 }
1519 }
1535
1520
@@ -1539,7 +1524,7 @@ FocusScope {
1539 anchors.left: appDelegate.left
1524 anchors.left: appDelegate.left
1540 anchors.top: appDelegate.top
1525 anchors.top: appDelegate.top
1541 application: model.application
1526 application: model.application
1542− surface: model.surface
1527+ surface: model.window.surface
1543 active: appDelegate.focus
1528 active: appDelegate.focus
1544 focus: true
1529 focus: true
1545 interactive: root.interactive
1530 interactive: root.interactive
@@ -1564,21 +1549,21 @@ FocusScope {
1564 onCloseClicked: { appDelegate.close(); }
1549 onCloseClicked: { appDelegate.close(); }
1565 onMaximizeClicked: {
1550 onMaximizeClicked: {
1566 if (appDelegate.canBeMaximized) {
1551 if (appDelegate.canBeMaximized) {
1567− appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
1552+ appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
1568 }
1553 }
1569 }
1554 }
1570 onMaximizeHorizontallyClicked: {
1555 onMaximizeHorizontallyClicked: {
1571 if (appDelegate.canBeMaximizedHorizontally) {
1556 if (appDelegate.canBeMaximizedHorizontally) {
1572− appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
1557+ appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
1573 }
1558 }
1574 }
1559 }
1575 onMaximizeVerticallyClicked: {
1560 onMaximizeVerticallyClicked: {
1576 if (appDelegate.canBeMaximizedVertically) {
1561 if (appDelegate.canBeMaximizedVertically) {
1577− appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
1562+ appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
1578 }
1563 }
1579 }
1564 }
1580− onMinimizeClicked: appDelegate.minimize()
1565+ onMinimizeClicked: { appDelegate.requestMinimize(); }
1581− onDecorationPressed: { appDelegate.focus = true; }
1566+ onDecorationPressed: { appDelegate.activate(); }
1582 onDecorationReleased: fakeRectangle.commit();
1567 onDecorationReleased: fakeRectangle.commit();
1583
1568
1584 property real angle: 0
1569 property real angle: 0
@@ -1625,12 +1610,12 @@ FocusScope {
1625 WindowedFullscreenPolicy {
1610 WindowedFullscreenPolicy {
1626 id: windowedFullscreenPolicy
1611 id: windowedFullscreenPolicy
1627 active: root.mode == "windowed"
1612 active: root.mode == "windowed"
1628− surface: model.surface
1613+ surface: model.window.surface
1629 }
1614 }
1630 StagedFullscreenPolicy {
1615 StagedFullscreenPolicy {
1631 id: stagedFullscreenPolicy
1616 id: stagedFullscreenPolicy
1632 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1617 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1633− surface: model.surface
1618+ surface: model.window.surface
1634 }
1619 }
1635
1620
1636 SpreadDelegateInputArea {
1621 SpreadDelegateInputArea {
@@ -1643,16 +1628,13 @@ FocusScope {
1643 onClicked: {
1628 onClicked: {
1644 spreadItem.highlightedIndex = index;
1629 spreadItem.highlightedIndex = index;
1645 if (distance == 0) {
1630 if (distance == 0) {
1631+ model.window.activate();
1646 priv.goneToSpread = false;
1632 priv.goneToSpread = false;
1647 }
1633 }
1648 }
1634 }
1649 onClose: {
1635 onClose: {
1650 priv.closingIndex = index
1636 priv.closingIndex = index
1651− if (model.surface) { // could be stopped by OOM
1637+ model.window.close();
1652− model.surface.close()
1653− } else if (model.application) {
1654− root.applicationManager.stopApplication(model.application.appId);
1655− }
1656 }
1638 }
1657 }
1639 }
1658
1640
qml/Stage/StagedFullscreenPolicy.qml+4−4
@@ -32,7 +32,7 @@ QtObject {
32 onSurfaceChanged: {
32 onSurfaceChanged: {
33 if (!active || !surface) return;
33 if (!active || !surface) return;
34 if (surface.shellChrome === Mir.LowChrome) {
34 if (surface.shellChrome === Mir.LowChrome) {
35− surface.state = Mir.FullscreenState;
35+ surface.requestState(Mir.FullscreenState);
36 }
36 }
37 }
37 }
38
38
@@ -41,15 +41,15 @@ QtObject {
41 onShellChromeChanged: {
41 onShellChromeChanged: {
42 if (!active || !surface) return;
42 if (!active || !surface) return;
43 if (surface.shellChrome === Mir.LowChrome) {
43 if (surface.shellChrome === Mir.LowChrome) {
44− surface.state = Mir.FullscreenState;
44+ surface.requestState(Mir.FullscreenState);
45 } else {
45 } else {
46− surface.state = Mir.RestoredState;
46+ surface.requestState(Mir.RestoredState);
47 }
47 }
48 }
48 }
49 onStateChanged: {
49 onStateChanged: {
50 if (!active) return;
50 if (!active) return;
51 if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
51 if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
52− surface.state = Mir.FullscreenState;
52+ surface.requestState(Mir.FullscreenState);
53 }
53 }
54 }
54 }
55 }
55 }
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 {
57 touchPoints.length <= maximumTouchPoints
57 touchPoints.length <= maximumTouchPoints
58 onRecognizedPressChanged: {
58 onRecognizedPressChanged: {
59 if (recognizedPress) {
59 if (recognizedPress) {
60− target.focus = true;
60+ target.activate();
61 overlayTimer.start();
61 overlayTimer.start();
62 }
62 }
63 }
63 }
qml/Stage/WindowedFullscreenPolicy.qml+1−1
@@ -33,7 +33,7 @@ QtObject {
33 _firstTimeSurface = false;
33 _firstTimeSurface = false;
34
34
35 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
35 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
36− surface.state = Mir.RestoredState;
36+ surface.requestState(Mir.RestoredState);
37 }
37 }
38 }
38 }
39}
39}
tests/mocks/Unity/Application/ApplicationInfo.cpp+48−24
@@ -75,6 +75,33 @@ ApplicationInfo::~ApplicationInfo()
75{
75{
76}
76}
77
77
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+
78void ApplicationInfo::createSurface()
105void ApplicationInfo::createSurface()
79{
106{
80 if (state() == ApplicationInfo::Stopped) { return; }
107 if (state() == ApplicationInfo::Stopped) { return; }
@@ -90,14 +117,16 @@ void ApplicationInfo::createSurface()
90 return;
117 return;
91 }
118 }
92
119
120+ bool wasFocused = focused();
121+
93 auto surface = surfaceManager->createSurface(surfaceName,
122 auto surface = surfaceManager->createSurface(surfaceName,
94 Mir::NormalType,
123 Mir::NormalType,
95− fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
124+ fullscreen() ? Mir::FullscreenState : Mir::RestoredState,
96 m_screenshotFileName);
125 m_screenshotFileName);
97
126
98 surface->setShellChrome(m_shellChrome);
127 surface->setShellChrome(m_shellChrome);
99
128
100− m_surfaceList->appendSurface(surface);
129+ m_surfaceList->addSurface(surface);
101
130
102 ++m_liveSurfaceCount;
131 ++m_liveSurfaceCount;
103 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
132 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
@@ -125,8 +154,19 @@ void ApplicationInfo::createSurface()
125 setState(Running);
154 setState(Running);
126 }
155 }
127 });
156 });
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+
128 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
164 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
129
165
166+ if (wasFocused != focused()) {
167+ Q_EMIT focusedChanged(focused());
168+ }
169+
130 if (m_state == Starting) {
170 if (m_state == Starting) {
131 if (m_requestedState == RequestedRunning) {
171 if (m_requestedState == RequestedRunning) {
132 setState(Running);
172 setState(Running);
@@ -134,6 +174,9 @@ void ApplicationInfo::createSurface()
134 setState(Suspended);
174 setState(Suspended);
135 }
175 }
136 }
176 }
177+
178+ surfaceManager->notifySurfaceCreated(surface);
179+ surfaceManager->activate(surface);
137}
180}
138
181
139void ApplicationInfo::setIconId(const QString &iconId)
182void ApplicationInfo::setIconId(const QString &iconId)
@@ -217,7 +260,7 @@ void ApplicationInfo::setFullscreen(bool value)
217{
260{
218 m_fullscreen = value;
261 m_fullscreen = value;
219 if (m_surfaceList->rowCount() > 0) {
262 if (m_surfaceList->rowCount() > 0) {
220− m_surfaceList->get(0)->setState(Mir::FullscreenState);
263+ m_surfaceList->get(0)->requestState(Mir::FullscreenState);
221 }
264 }
222}
265}
223
266
@@ -348,26 +391,6 @@ bool ApplicationInfo::focused() const
348 return someSurfaceHasFocus;
391 return someSurfaceHasFocus;
349}
392}
350
393
351−void ApplicationInfo::setFocused(bool value)
352−{
353− if (focused() == value) {
354− return;
355− }
356−
357− if (value) {
358− if (m_surfaceList->count() > 0) {
359− m_surfaceList->get(0)->requestFocus();
360− }
361− } else {
362− for (int i = 0; i < m_surfaceList->count(); ++i) {
363− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
364− if (surface->focused()) {
365− surface->setFocused(false);
366− }
367− }
368− }
369−}
370−
371void ApplicationInfo::onSurfaceCountChanged()
394void ApplicationInfo::onSurfaceCountChanged()
372{
395{
373 if (m_surfaceList->count() == 0 && m_state == Running) {
396 if (m_surfaceList->count() == 0 && m_state == Running) {
@@ -380,6 +403,7 @@ void ApplicationInfo::requestFocus()
380 if (m_surfaceList->count() == 0) {
403 if (m_surfaceList->count() == 0) {
381 Q_EMIT focusRequested();
404 Q_EMIT focusRequested();
382 } else {
405 } else {
406+ auto surface = static_cast<MirSurface*>(m_surfaceList->get(0));
383− m_surfaceList->get(0)->requestFocus();
407+ surface->requestFocus();
384 }
408 }
385}
409}
tests/mocks/Unity/Application/ApplicationInfo.h+2−2
@@ -51,6 +51,8 @@ public:
51 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
51 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
52 ~ApplicationInfo();
52 ~ApplicationInfo();
53
53
54+ Q_INVOKABLE void createPromptSurface();
55+
54 RequestedState requestedState() const override;
56 RequestedState requestedState() const override;
55 void setRequestedState(RequestedState) override;
57 void setRequestedState(RequestedState) override;
56
58
@@ -108,8 +110,6 @@ public:
108 MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
110 MirSurfaceListInterface* promptSurfaceList() const override { return m_promptSurfaceList; }
109 int surfaceCount() const override { return m_surfaceList->count(); }
111 int surfaceCount() const override { return m_surfaceList->count(); }
110
112
111− void setFocused(bool value);
112−
113 //////
113 //////
114 // internal mock stuff
114 // internal mock stuff
115 void close();
115 void close();
tests/mocks/Unity/Application/ApplicationManager.cpp+88−59
@@ -34,29 +34,23 @@
34
34
35#if APPLICATIONMANAGER_DEBUG
35#if APPLICATIONMANAGER_DEBUG
36#define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
36#define DEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << __func__ << " " << params
37+#define XDEBUG_MSG(params) qDebug().nospace() << "ApplicationManager::" << params
37#else
38#else
38#define DEBUG_MSG(params) ((void)0)
39#define DEBUG_MSG(params) ((void)0)
40+#define XDEBUG_MSG(params) ((void)0)
39#endif
41#endif
40
42
41namespace unityapi = unity::shell::application;
43namespace unityapi = unity::shell::application;
42
44
45+
43ApplicationManager::ApplicationManager(QObject *parent)
46ApplicationManager::ApplicationManager(QObject *parent)
44 : ApplicationManagerInterface(parent)
47 : ApplicationManagerInterface(parent)
45{
48{
46 DEBUG_MSG("");
49 DEBUG_MSG("");
47− buildListOfAvailableApplications();
48
50
49− // polling to find out when the toplevel window has been created as there's
51+ ApplicationManagerNotifier::instance()->setApplicationManager(this);
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);
59
52
53+ buildListOfAvailableApplications();
60
54
61 // Emit signal to notify Upstart that Mir is ready to receive client connections
55 // Emit signal to notify Upstart that Mir is ready to receive client connections
62 // see http://upstart.ubuntu.com/cookbook/#expect-stop
56 // see http://upstart.ubuntu.com/cookbook/#expect-stop
@@ -69,19 +63,7 @@ ApplicationManager::ApplicationManager(QObject *parent)
69
63
70ApplicationManager::~ApplicationManager()
64ApplicationManager::~ApplicationManager()
71{
65{
72−}
66+ ApplicationManagerNotifier::instance()->setApplicationManager(nullptr);
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");
85}
67}
86
68
87int ApplicationManager::rowCount(const QModelIndex& parent) const {
69int ApplicationManager::rowCount(const QModelIndex& parent) const {
@@ -132,6 +114,17 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
132 return nullptr;
114 return nullptr;
133}
115}
134
116
117+unityapi::ApplicationInfoInterface *ApplicationManager::findApplicationWithSurface(unityapi::MirSurfaceInterface* surface) const
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+
135QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
128QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
136{
129{
137 for (int i = 0; i < m_runningApplications.size(); ++i) {
130 for (int i = 0; i < m_runningApplications.size(); ++i) {
@@ -158,6 +151,11 @@ bool ApplicationManager::add(ApplicationInfo *application) {
158 QModelIndex appIndex = findIndex(application);
151 QModelIndex appIndex = findIndex(application);
159 if (!appIndex.isValid()) return;
152 if (!appIndex.isValid()) return;
160 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
153 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+ }
161 });
159 });
162 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
160 connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
163 QModelIndex appIndex = findIndex(application);
161 QModelIndex appIndex = findIndex(application);
@@ -181,15 +179,40 @@ bool ApplicationManager::add(ApplicationInfo *application) {
181
179
182void ApplicationManager::remove(ApplicationInfo *application) {
180void ApplicationManager::remove(ApplicationInfo *application) {
183 int i = m_runningApplications.indexOf(application);
181 int i = m_runningApplications.indexOf(application);
182+ application->disconnect(this);
184 if (i != -1) {
183 if (i != -1) {
185 DEBUG_MSG(application->appId());
184 DEBUG_MSG(application->appId());
185+ Q_ASSERT(!m_modelBusy);
186+ m_modelBusy = true;
186 beginRemoveRows(QModelIndex(), i, i);
187 beginRemoveRows(QModelIndex(), i, i);
187 m_runningApplications.removeAt(i);
188 m_runningApplications.removeAt(i);
188 endRemoveRows();
189 endRemoveRows();
190+ m_modelBusy = false;
189 Q_EMIT countChanged();
191 Q_EMIT countChanged();
190 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
192 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+ }
191 }
215 }
192− application->disconnect(this);
193}
216}
194
217
195void ApplicationManager::move(int from, int to) {
218void ApplicationManager::move(int from, int to) {
@@ -197,12 +220,15 @@ void ApplicationManager::move(int from, int to) {
197
220
198 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
221 if (from >= 0 && from < m_runningApplications.size() && to >= 0 && to < m_runningApplications.size()) {
199 QModelIndex parent;
222 QModelIndex parent;
223+ Q_ASSERT(!m_modelBusy);
224+ m_modelBusy = true;
200 /* When moving an item down, the destination index needs to be incremented
225 /* When moving an item down, the destination index needs to be incremented
201 * by one, as explained in the documentation:
226 * by one, as explained in the documentation:
202 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
227 * http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
203 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
228 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
204 m_runningApplications.move(from, to);
229 m_runningApplications.move(from, to);
205 endMoveRows();
230 endMoveRows();
231+ m_modelBusy = false;
206 }
232 }
207}
233}
208
234
@@ -224,8 +250,6 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
224 if (!application)
250 if (!application)
225 return nullptr;
251 return nullptr;
226
252
227− Q_EMIT application->focusRequested(); // we assume that an application that's starting up wants focus
228−
229 return application;
253 return application;
230}
254}
231
255
@@ -473,47 +497,52 @@ bool ApplicationManager::isEmpty() const
473 return m_runningApplications.isEmpty();
497 return m_runningApplications.isEmpty();
474}
498}
475
499
476−void ApplicationManager::updateFocusedApplication()
500+ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
477{
501{
478− ApplicationInfo *focusedApplication = nullptr;
502+ for (ApplicationInfo *app : m_runningApplications) {
479− ApplicationInfo *previouslyFocusedApplication = nullptr;
503+ auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
480−
504+ if (surfaceList->contains(surface)) {
481− auto controller = MirFocusController::instance();
482− if (!controller) {
483− return;
505+ return app;
506+ }
484 }
507 }
508+ return nullptr;
509+}
485
510
486− MirSurface *surface = static_cast<MirSurface*>(controller->focusedSurface());
511+QString ApplicationManager::toString()
487− if (surface) {
512+{
488− focusedApplication = findApplication(surface);
513+ QString str;
489− }
514+ for (int i = 0; i < m_runningApplications.count(); ++i) {
515+ auto *application = m_runningApplications.at(i);
490
516
491− surface = static_cast<MirSurface*>(controller->previouslyFocusedSurface());
517+ QString itemStr = QString("(index=%1,appId=%2)")
492− if (surface) {
518+ .arg(i)
493− previouslyFocusedApplication = findApplication(surface);
519+ .arg(application->appId());
494− }
495
520
496− if (focusedApplication != previouslyFocusedApplication) {
521+ if (i > 0) {
497− if (focusedApplication) {
522+ str.append(",");
498− DEBUG_MSG("focused " << focusedApplication->appId());
499− Q_EMIT focusedApplication->focusedChanged(true);
500− this->move(this->m_runningApplications.indexOf(focusedApplication), 0);
501 }
523 }
502− if (previouslyFocusedApplication) {
524+ str.append(itemStr);
503− DEBUG_MSG("unfocused " << previouslyFocusedApplication->appId());
504− Q_EMIT previouslyFocusedApplication->focusedChanged(false);
505− }
506− Q_EMIT focusedApplicationIdChanged();
507 }
525 }
526+ return str;
508}
527}
509
528
510−ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
529+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
530+// ApplicationManagerNotifier
531+
532+ApplicationManagerNotifier *ApplicationManagerNotifier::m_instance = nullptr;
533+
534+ApplicationManagerNotifier *ApplicationManagerNotifier::instance()
511{
535{
512− for (ApplicationInfo *app : m_runningApplications) {
536+ if (!m_instance) {
513− auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
537+ m_instance = new ApplicationManagerNotifier;
514− if (surfaceList->contains(surface)) {
538+ }
515− return app;
539+ return m_instance;
516− }
540+}
541+
542+void ApplicationManagerNotifier::setApplicationManager(ApplicationManager *appMan)
543+{
544+ if (appMan != m_applicationManager) {
545+ m_applicationManager = appMan;
546+ Q_EMIT applicationManagerChanged(m_applicationManager);
517 }
547 }
518− return nullptr;
519}
548}
tests/mocks/Unity/Application/ApplicationManager.h+31−4
@@ -50,8 +50,11 @@ class ApplicationManager : public ApplicationManagerInterface {
50 // QAbstractItemModel methods.
50 // QAbstractItemModel methods.
51 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
51 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
52 QVariant data(const QModelIndex& index, int role) const override;
52 QVariant data(const QModelIndex& index, int role) const override;
53+
54+ // ApplicationManagerInterface methods
53 Q_INVOKABLE ApplicationInfo *get(int index) const override;
55 Q_INVOKABLE ApplicationInfo *get(int index) const override;
54 Q_INVOKABLE ApplicationInfo *findApplication(const QString &appId) const override;
56 Q_INVOKABLE ApplicationInfo *findApplication(const QString &appId) const override;
57+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) const override;
55
58
56 Q_INVOKABLE void move(int from, int to);
59 Q_INVOKABLE void move(int from, int to);
57
60
@@ -76,18 +79,42 @@ class ApplicationManager : public ApplicationManagerInterface {
76 void availableApplicationsChanged(QStringList list);
79 void availableApplicationsChanged(QStringList list);
77
80
78 private Q_SLOTS:
81 private Q_SLOTS:
79− void onWindowCreatedTimerTimeout();
82+ void raiseApp(const QString &appId);
80− void updateFocusedApplication();
81
83
82 private:
84 private:
83 bool add(ApplicationInfo *application);
85 bool add(ApplicationInfo *application);
84 void remove(ApplicationInfo* application);
86 void remove(ApplicationInfo* application);
85 void buildListOfAvailableApplications();
87 void buildListOfAvailableApplications();
86− void onWindowCreated();
88+ QString toString();
87 ApplicationInfo *findApplication(MirSurface* surface);
89 ApplicationInfo *findApplication(MirSurface* surface);
88 QList<ApplicationInfo*> m_runningApplications;
90 QList<ApplicationInfo*> m_runningApplications;
89 QList<ApplicationInfo*> m_availableApplications;
91 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;
91};
118};
92
119
93Q_DECLARE_METATYPE(ApplicationManager*)
120Q_DECLARE_METATYPE(ApplicationManager*)
tests/mocks/Unity/Application/CMakeLists.txt+1−1
@@ -15,7 +15,7 @@ set(FakeUnityApplicationQml_SOURCES
15 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
15 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
16 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h
16 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h
17 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
17 ${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
19 resources/surfaces.qrc
19 resources/surfaces.qrc
20)
20)
21
21
tests/mocks/Unity/Application/MirSurface.cpp+81−94
@@ -19,14 +19,18 @@
19#include <QDebug>
19#include <QDebug>
20#include <QQmlEngine>
20#include <QQmlEngine>
21
21
22+// local
23+#include "SurfaceManager.h"
24+
22#define MIRSURFACE_DEBUG 0
25#define MIRSURFACE_DEBUG 0
23
26
24#if MIRSURFACE_DEBUG
27#if MIRSURFACE_DEBUG
25#define DEBUG_MSG(params) qDebug().nospace() << "MirSurface[" << (void*)this << "," << m_name << "]::" << __func__ << " " << params
28#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
26
30
27const char *stateToStr(Mir::State state)
31const char *stateToStr(Mir::State state)
28{
32{
29− switch(state) {
33+ switch (state) {
30 case Mir::UnknownState:
34 case Mir::UnknownState:
31 return "unknown";
35 return "unknown";
32 case Mir::RestoredState:
36 case Mir::RestoredState:
@@ -36,26 +40,37 @@ const char *stateToStr(Mir::State state)
36 case Mir::MaximizedState:
40 case Mir::MaximizedState:
37 return "maximized";
41 return "maximized";
38 case Mir::VertMaximizedState:
42 case Mir::VertMaximizedState:
39− return "vert-maximized";
43+ return "vertMaximized";
40 case Mir::FullscreenState:
44 case Mir::FullscreenState:
41 return "fullscreen";
45 return "fullscreen";
42 case Mir::HorizMaximizedState:
46 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";
44 case Mir::HiddenState:
60 case Mir::HiddenState:
45 return "hidden";
61 return "hidden";
46 default:
62 default:
47 return "???";
63 return "???";
48− };
64+ }
49}
65}
50
66
51#else
67#else
52#define DEBUG_MSG(params) ((void)0)
68#define DEBUG_MSG(params) ((void)0)
69+#define XDEBUG_MSG(params) ((void)0)
53#endif
70#endif
54
71
55using namespace unity::shell::application;
72using namespace unity::shell::application;
56
73
57−MirFocusController *MirFocusController::m_instance = nullptr;
58−
59MirSurface::MirSurface(const QString& name,
74MirSurface::MirSurface(const QString& name,
60 Mir::Type type,
75 Mir::Type type,
61 Mir::State state,
76 Mir::State state,
@@ -69,14 +84,14 @@ MirSurface::MirSurface(const QString& name,
69 , m_screenshotUrl(screenshot)
84 , m_screenshotUrl(screenshot)
70 , m_qmlFilePath(qmlFilePath)
85 , m_qmlFilePath(qmlFilePath)
71 , m_live(true)
86 , m_live(true)
72− , m_visible(true)
87+ , m_focused(false)
73 , m_activeFocus(false)
88 , m_activeFocus(false)
74 , m_width(-1)
89 , m_width(-1)
75 , m_height(-1)
90 , m_height(-1)
76 , m_slowToResize(false)
91 , m_slowToResize(false)
77 , m_shellChrome(Mir::NormalChrome)
92 , m_shellChrome(Mir::NormalChrome)
78{
93{
79− DEBUG_MSG("");
94+ DEBUG_MSG("state=" << stateToStr(state));
80
95
81 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
96 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
82
97
@@ -95,11 +110,8 @@ MirSurface::~MirSurface()
95{
110{
96 DEBUG_MSG("");
111 DEBUG_MSG("");
97
112
98− // controller instance might have been already destroyed by QQmlEngine destructor
113+ // Early warning, while MirSurface methods can still be accessed.
99− auto controller = MirFocusController::instance();
114+ Q_EMIT destroyed(this);
100− if (controller && controller->focusedSurface() == this) {
101− controller->clear();
102− }
103}
115}
104
116
105QString MirSurface::name() const
117QString MirSurface::name() const
@@ -122,14 +134,31 @@ Mir::State MirSurface::state() const
122 return m_state;
134 return m_state;
123}
135}
124
136
125−void MirSurface::setState(Mir::State state)
137+void MirSurface::requestState(Mir::State state)
126{
138{
127− if (state == m_state)
139+ if (state == m_state) {
128 return;
140 return;
141+ }
142+ DEBUG_MSG(stateToStr(state));
143+ Q_EMIT stateRequested(state);
144+}
129
145
146+void MirSurface::setState(Mir::State state)
147+{
148+ if (state == m_state) {
149+ return;
150+ }
130 DEBUG_MSG(stateToStr(state));
151 DEBUG_MSG(stateToStr(state));
152+
153+ bool oldVisible = visible();
154+
131 m_state = state;
155 m_state = state;
132 Q_EMIT stateChanged(state);
156 Q_EMIT stateChanged(state);
157+
158+ if (visible() != oldVisible) {
159+ XDEBUG_MSG("visibleChanged("<<visible()<<")");
160+ Q_EMIT visibleChanged(visible());
161+ }
133}
162}
134
163
135bool MirSurface::live() const
164bool MirSurface::live() const
@@ -139,7 +168,7 @@ bool MirSurface::live() const
139
168
140bool MirSurface::visible() const
169bool MirSurface::visible() const
141{
170{
142− return m_visible;
171+ return m_state != Mir::MinimizedState && m_state != Mir::HiddenState;
143}
172}
144
173
145void MirSurface::setLive(bool live)
174void MirSurface::setLive(bool live)
@@ -231,31 +260,30 @@ void MirSurface::unregisterView(qintptr viewId)
231 if (!m_live && m_views.count() == 0) {
260 if (!m_live && m_views.count() == 0) {
232 deleteLater();
261 deleteLater();
233 }
262 }
234− updateVisibility();
263+ updateExposure();
235}
264}
236
265
237−void MirSurface::setViewVisibility(qintptr viewId, bool visible)
266+void MirSurface::setViewExposure(qintptr viewId, bool visible)
238{
267{
239 if (!m_views.contains(viewId)) return;
268 if (!m_views.contains(viewId)) return;
240
269
241 m_views[viewId].visible = visible;
270 m_views[viewId].visible = visible;
242− updateVisibility();
271+ updateExposure();
243}
272}
244
273
245−void MirSurface::updateVisibility()
274+void MirSurface::updateExposure()
246{
275{
247− bool newVisible = false;
276+ bool newExposure = false;
248 QHashIterator<qintptr, View> i(m_views);
277 QHashIterator<qintptr, View> i(m_views);
249 while (i.hasNext()) {
278 while (i.hasNext()) {
250 i.next();
279 i.next();
251− newVisible |= i.value().visible;
280+ newExposure |= i.value().visible;
252 }
281 }
253
282
254− if (newVisible != visible()) {
283+ if (newExposure != m_exposed) {
255−// qDebug().nospace() << "MirSurface[" << name() << "]::updateVisibility(" << newVisible << ")";
284+ m_exposed = newExposure;
256−
285+ DEBUG_MSG(m_exposed);
257− m_visible = newVisible;
286+ Q_EMIT exposedChanged(m_exposed);
258− Q_EMIT visibleChanged(m_visible);
259 updateInputBoundsAfterResize();
287 updateInputBoundsAfterResize();
260 }
288 }
261}
289}
@@ -273,6 +301,10 @@ void MirSurface::setActiveFocus(bool value)
273 m_activeFocus = value;
301 m_activeFocus = value;
274
302
275 Q_EMIT activeFocusChanged(value);
303 Q_EMIT activeFocusChanged(value);
304+
305+ if (m_activeFocus && !m_focused) {
306+ requestFocus();
307+ }
276}
308}
277
309
278int MirSurface::width() const
310int MirSurface::width() const
@@ -332,6 +364,7 @@ void MirSurface::doResize(int width, int height)
332 }
364 }
333
365
334 if (changed) {
366 if (changed) {
367+ XDEBUG_MSG("sizeChanged(width="<<width<<", height="<<height<<")");
335 Q_EMIT sizeChanged(QSize(width, height));
368 Q_EMIT sizeChanged(QSize(width, height));
336 }
369 }
337
370
@@ -409,11 +442,6 @@ void MirSurface::setHeightIncrement(int value)
409 }
442 }
410}
443}
411
444
412−void MirSurface::raise()
413−{
414− Q_EMIT raiseRequested();
415−}
416−
417void MirSurface::close()
445void MirSurface::close()
418{
446{
419 DEBUG_MSG("");
447 DEBUG_MSG("");
@@ -423,6 +451,12 @@ void MirSurface::close()
423 }
451 }
424}
452}
425
453
454+void MirSurface::activate()
455+{
456+ DEBUG_MSG("");
457+ SurfaceManager::instance()->activate(this);
458+}
459+
426void MirSurface::requestFocus()
460void MirSurface::requestFocus()
427{
461{
428 DEBUG_MSG("");
462 DEBUG_MSG("");
@@ -431,27 +465,18 @@ void MirSurface::requestFocus()
431
465
432void MirSurface::setFocused(bool value)
466void MirSurface::setFocused(bool value)
433{
467{
434− DEBUG_MSG(value);
468+ if (m_focused == value)
435−
436− auto controller = MirFocusController::instance();
437− // controller instance might have been already destroyed by QQmlEngine destructor
438− if (!controller) {
439 return;
469 return;
440− }
441
470
442− if (value) {
471+ DEBUG_MSG("(" << value << ")");
443− controller->setFocusedSurface(this);
472+
444− } else if (controller->focusedSurface() == this) {
473+ m_focused = value;
445− controller->setFocusedSurface(nullptr);
474+ Q_EMIT focusedChanged(value);
446− }
447}
475}
448
476
449bool MirSurface::focused() const
477bool MirSurface::focused() const
450{
478{
451− auto controller = MirFocusController::instance();
479+ return m_focused;
452−
453− // controller instance might have been already destroyed by QQmlEngine destructor
454− return controller ? controller->focusedSurface() == this : false;
455}
480}
456
481
457QRect MirSurface::inputBounds() const
482QRect MirSurface::inputBounds() const
@@ -463,58 +488,20 @@ void MirSurface::setInputBounds(const QRect &boundsRect)
463{
488{
464 if (boundsRect != m_inputBounds) {
489 if (boundsRect != m_inputBounds) {
465 m_inputBounds = boundsRect;
490 m_inputBounds = boundsRect;
491+ DEBUG_MSG("(" << m_inputBounds << ")");
466 Q_EMIT inputBoundsChanged(m_inputBounds);
492 Q_EMIT inputBoundsChanged(m_inputBounds);
467 }
493 }
468}
494}
469−#if MIRSURFACE_DEBUG
470−#undef DEBUG_MSG
471−#define DEBUG_MSG(params) qDebug().nospace() << "MirFocusController::" << __func__ << " " << params
472−#endif
473
495
474−void MirFocusController::setFocusedSurface(MirSurfaceInterface *surface)
496+void MirSurface::setRequestedPosition(const QPoint &value)
475{
497{
476− if (m_focusedSurface == surface) {
498+ if (value != m_requestedPosition) {
477− return;
499+ m_requestedPosition = value;
478− }
500+ Q_EMIT requestedPositionChanged(value);
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− }
491
501
492− if (m_focusedSurface) {
502+ // fake-miral: always comply
493− Q_EMIT m_focusedSurface->focusedChanged(true);
503+ m_position = m_requestedPosition;
494− m_focusedSurface->raise();
504+ XDEBUG_MSG("positionChanged("<<m_position<<")");
505+ Q_EMIT positionChanged(m_position);
495 }
506 }
496}
507}
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+29−27
@@ -23,31 +23,12 @@
23#include <QHash>
23#include <QHash>
24
24
25// unity-api
25// unity-api
26−#include <unity/shell/application/MirFocusControllerInterface.h>
27#include <unity/shell/application/MirSurfaceInterface.h>
26#include <unity/shell/application/MirSurfaceInterface.h>
28
27
29#include "MirSurfaceListModel.h"
28#include "MirSurfaceListModel.h"
30
29
31class MirSurface;
30class MirSurface;
32
31
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−
51class MirSurface : public unity::shell::application::MirSurfaceInterface
32class MirSurface : public unity::shell::application::MirSurfaceInterface
52{
33{
53 Q_OBJECT
34 Q_OBJECT
@@ -58,6 +39,7 @@ class MirSurface : public unity::shell::application::MirSurfaceInterface
58 Q_PROPERTY(int height READ height NOTIFY heightChanged)
39 Q_PROPERTY(int height READ height NOTIFY heightChanged)
59 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
40 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
60 Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
41 Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
42+ Q_PROPERTY(bool exposed READ exposed NOTIFY exposedChanged)
61
43
62public:
44public:
63 MirSurface(const QString& name,
45 MirSurface(const QString& name,
@@ -76,13 +58,14 @@ public:
76
58
77 QString persistentId() const override;
59 QString persistentId() const override;
78
60
61+ QPoint position() const override { return m_position; }
62+
79 QSize size() const override { return QSize(width(),height()); }
63 QSize size() const override { return QSize(width(),height()); }
80 void resize(int width, int height) override;
64 void resize(int width, int height) override;
81 void resize(const QSize &size) override { resize(size.width(), size.height()); }
65 void resize(const QSize &size) override { resize(size.width(), size.height()); }
82
66
83
67
84 Mir::State state() const override;
68 Mir::State state() const override;
85− Q_INVOKABLE void setState(Mir::State) override;
86
69
87 bool live() const override;
70 bool live() const override;
88
71
@@ -108,15 +91,16 @@ public:
108
91
109 bool confinesMousePointer() const override { return false; }
92 bool confinesMousePointer() const override { return false; }
110
93
111− Q_INVOKABLE void requestFocus() override;
94+ QPoint requestedPosition() const override { return m_requestedPosition; }
95+ void setRequestedPosition(const QPoint &) override;
112
96
113 Q_INVOKABLE void close() override;
97 Q_INVOKABLE void close() override;
114−
115− Q_INVOKABLE void raise() override;
98+ Q_INVOKABLE void activate() override;
116
99
117 ////
100 ////
118 // API for tests
101 // API for tests
119
102
103+ Q_INVOKABLE void requestFocus();
120 Q_INVOKABLE void setLive(bool live);
104 Q_INVOKABLE void setLive(bool live);
121 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
105 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
122
106
@@ -126,6 +110,8 @@ public:
126 bool isSlowToResize() const;
110 bool isSlowToResize() const;
127 void setSlowToResize(bool value);
111 void setSlowToResize(bool value);
128
112
113+ bool exposed() const { return m_exposed; }
114+
129 Q_INVOKABLE void setMinimumWidth(int);
115 Q_INVOKABLE void setMinimumWidth(int);
130 Q_INVOKABLE void setMaximumWidth(int);
116 Q_INVOKABLE void setMaximumWidth(int);
131 Q_INVOKABLE void setMinimumHeight(int);
117 Q_INVOKABLE void setMinimumHeight(int);
@@ -148,24 +134,35 @@ public:
148
134
149 void registerView(qintptr viewId);
135 void registerView(qintptr viewId);
150 void unregisterView(qintptr viewId);
136 void unregisterView(qintptr viewId);
151− void setViewVisibility(qintptr viewId, bool visible);
137+ void setViewExposure(qintptr viewId, bool visible);
152 int viewCount() const { return m_views.count(); }
138 int viewCount() const { return m_views.count(); }
153
139
154 void setFocused(bool value);
140 void setFocused(bool value);
155
141
142+ void setState(Mir::State state);
143+
144+ Mir::State previousState() const { return m_previousState; }
145+ void setPreviousState(Mir::State state) { m_previousState = state; }
146+
147+public Q_SLOTS:
148+ ////
149+ // unity.shell.application.MirSurface
150+ void requestState(Mir::State) override;
151+
156Q_SIGNALS:
152Q_SIGNALS:
157 ////
153 ////
158 // API for tests
154 // API for tests
159 void widthChanged();
155 void widthChanged();
160 void heightChanged();
156 void heightChanged();
161 void slowToResizeChanged();
157 void slowToResizeChanged();
158+ void exposedChanged(bool exposed);
162
159
163 ////
160 ////
164 // internal mock stuff
161 // internal mock stuff
165 void screenshotUrlChanged(QUrl);
162 void screenshotUrlChanged(QUrl);
166 void activeFocusChanged(bool);
163 void activeFocusChanged(bool);
167− void raiseRequested();
168 void closeRequested();
164 void closeRequested();
165+ void stateRequested(Mir::State);
169
166
170protected:
167protected:
171 virtual void updateInputBoundsAfterResize();
168 virtual void updateInputBoundsAfterResize();
@@ -175,17 +172,18 @@ private Q_SLOTS:
175
172
176private:
173private:
177 void doResize(int width, int height);
174 void doResize(int width, int height);
178− void updateVisibility();
175+ void updateExposure();
179
176
180 const QString m_name;
177 const QString m_name;
181 const Mir::Type m_type;
178 const Mir::Type m_type;
182 Mir::State m_state;
179 Mir::State m_state;
180+ Mir::State m_previousState{Mir::UnknownState};
183 Mir::OrientationAngle m_orientationAngle;
181 Mir::OrientationAngle m_orientationAngle;
184
182
185 QUrl m_screenshotUrl;
183 QUrl m_screenshotUrl;
186 QUrl m_qmlFilePath;
184 QUrl m_qmlFilePath;
187 bool m_live;
185 bool m_live;
188− bool m_visible;
186+ bool m_focused;
189 bool m_activeFocus;
187 bool m_activeFocus;
190 int m_width;
188 int m_width;
191 int m_height;
189 int m_height;
@@ -210,10 +208,14 @@ private:
210 bool visible;
208 bool visible;
211 };
209 };
212 QHash<qintptr, View> m_views;
210 QHash<qintptr, View> m_views;
211+ bool m_exposed{false};
213
212
214 QTimer m_zombieTimer;
213 QTimer m_zombieTimer;
215
214
216 QRect m_inputBounds;
215 QRect m_inputBounds;
216+
217+ QPoint m_position;
218+ QPoint m_requestedPosition;
217};
219};
218
220
219#endif // MOCK_MIR_SURFACE_H
221#endif // MOCK_MIR_SURFACE_H
tests/mocks/Unity/Application/MirSurfaceItem.cpp+4−4
@@ -59,7 +59,7 @@ MirSurfaceItem::MirSurfaceItem(QQuickItem *parent)
59 Qt::ExtraButton12 | Qt::ExtraButton13);
59 Qt::ExtraButton12 | Qt::ExtraButton13);
60
60
61 connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
61 connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
62− connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility);
62+ connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceExposure);
63
63
64 connect(this, &MirSurfaceItem::consumesInputChanged, this, [this]() {
64 connect(this, &MirSurfaceItem::consumesInputChanged, this, [this]() {
65 updateMirSurfaceActiveFocus(hasActiveFocus());
65 updateMirSurfaceActiveFocus(hasActiveFocus());
@@ -278,7 +278,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
278 m_qmlSurface->registerView((qintptr)this);
278 m_qmlSurface->registerView((qintptr)this);
279
279
280 updateSurfaceSize();
280 updateSurfaceSize();
281− updateMirSurfaceVisibility();
281+ updateMirSurfaceExposure();
282
282
283 if (m_orientationAngle) {
283 if (m_orientationAngle) {
284 m_qmlSurface->setOrientationAngle(*m_orientationAngle);
284 m_qmlSurface->setOrientationAngle(*m_orientationAngle);
@@ -335,11 +335,11 @@ void MirSurfaceItem::updateMirSurfaceActiveFocus(bool focused)
335 }
335 }
336}
336}
337
337
338−void MirSurfaceItem::updateMirSurfaceVisibility()
338+void MirSurfaceItem::updateMirSurfaceExposure()
339{
339{
340 if (!m_qmlSurface) return;
340 if (!m_qmlSurface) return;
341
341
342− m_qmlSurface->setViewVisibility((qintptr)this, isVisible());
342+ m_qmlSurface->setViewExposure((qintptr)this, isVisible());
343}
343}
344
344
345void MirSurfaceItem::setConsumesInput(bool value)
345void MirSurfaceItem::setConsumesInput(bool value)
tests/mocks/Unity/Application/MirSurfaceItem.h+1−2
@@ -50,7 +50,6 @@ public:
50 Mir::ShellChrome shellChrome() const override;
50 Mir::ShellChrome shellChrome() const override;
51
51
52 Mir::State surfaceState() const override;
52 Mir::State surfaceState() const override;
53− void setSurfaceState(Mir::State) override {}
54
53
55 Mir::OrientationAngle orientationAngle() const override;
54 Mir::OrientationAngle orientationAngle() const override;
56 void setOrientationAngle(Mir::OrientationAngle angle) override;
55 void setOrientationAngle(Mir::OrientationAngle angle) override;
@@ -102,7 +101,7 @@ protected:
102private Q_SLOTS:
101private Q_SLOTS:
103 void onComponentStatusChanged(QQmlComponent::Status status);
102 void onComponentStatusChanged(QQmlComponent::Status status);
104 void updateScreenshot(QUrl screenshot);
103 void updateScreenshot(QUrl screenshot);
105− void updateMirSurfaceVisibility();
104+ void updateMirSurfaceExposure();
106 void updateMirSurfaceActiveFocus(bool focused);
105 void updateMirSurfaceActiveFocus(bool focused);
107
106
108private:
107private:
tests/mocks/Unity/Application/MirSurfaceListModel.cpp+11−33
@@ -21,8 +21,9 @@
21
21
22#define MIRSURFACELISTMODEL_DEBUG 0
22#define MIRSURFACELISTMODEL_DEBUG 0
23
23
24−#ifdef MIRSURFACELISTMODEL_DEBUG
24+#if MIRSURFACELISTMODEL_DEBUG
25+#include <QDebug>
25−#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << " " << params
26+#define DEBUG_MSG(params) qDebug().nospace() << "MirSurfaceListModel::" << __func__ << params
26#else
27#else
27#define DEBUG_MSG(params) ((void)0)
28#define DEBUG_MSG(params) ((void)0)
28#endif
29#endif
@@ -54,26 +55,16 @@ QVariant MirSurfaceListModel::data(const QModelIndex& index, int role) const
54
55
55void MirSurfaceListModel::raise(MirSurface *surface)
56void MirSurfaceListModel::raise(MirSurface *surface)
56{
57{
58+ DEBUG_MSG("(" << surface << ")");
57 int i = m_surfaceList.indexOf(surface);
59 int i = m_surfaceList.indexOf(surface);
58 if (i != -1) {
60 if (i != -1) {
59 moveSurface(i, 0);
61 moveSurface(i, 0);
60 }
62 }
61}
63}
62
64
63−void MirSurfaceListModel::appendSurface(MirSurface *surface)
65+void MirSurfaceListModel::addSurface(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)
76{
66{
67+ DEBUG_MSG("(" << surface << ")");
77 beginInsertRows(QModelIndex(), 0, 0);
68 beginInsertRows(QModelIndex(), 0, 0);
78 m_surfaceList.prepend(surface);
69 m_surfaceList.prepend(surface);
79 connectSurface(surface);
70 connectSurface(surface);
@@ -85,6 +76,11 @@ void MirSurfaceListModel::prependSurface(MirSurface *surface)
85void MirSurfaceListModel::connectSurface(MirSurface *surface)
76void MirSurfaceListModel::connectSurface(MirSurface *surface)
86{
77{
87 connect(surface, &QObject::destroyed, this, [this, surface](){ this->removeSurface(surface); });
78 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+ });
88}
84}
89
85
90void MirSurfaceListModel::removeSurface(MirSurface *surface)
86void MirSurfaceListModel::removeSurface(MirSurface *surface)
@@ -138,21 +134,3 @@ const MirSurfaceInterface *MirSurfaceListModel::get(int index) const
138 return nullptr;
134 return nullptr;
139 }
135 }
140}
136}
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:
38 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
38 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
39 QVariant data(const QModelIndex& index, int role) const override;
39 QVariant data(const QModelIndex& index, int role) const override;
40
40
41− void appendSurface(MirSurface *surface);
41+ void addSurface(MirSurface *surface);
42 void removeSurface(MirSurface *surface);
42 void removeSurface(MirSurface *surface);
43
43
44 bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
44 bool contains(MirSurface *surface) const { return m_surfaceList.contains(surface); }
45
45
46− ////
47− // API for tests
48−
49− Q_INVOKABLE unity::shell::application::MirSurfaceInterface *createSurface();
50−
51private:
46private:
52− void prependSurface(MirSurface *surface);
47+ void appendSurface(MirSurface *surface);
53 void raise(MirSurface *surface);
48 void raise(MirSurface *surface);
54 void moveSurface(int from, int to);
49 void moveSurface(int from, int to);
55 void connectSurface(MirSurface *surface);
50 void connectSurface(MirSurface *surface);
tests/mocks/Unity/Application/SurfaceManager.cpp+170−24
@@ -21,33 +21,41 @@
21
21
22#include <paths.h>
22#include <paths.h>
23
23
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+
24−SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
34+SurfaceManager *SurfaceManager::m_instance = nullptr;
25
35
26SurfaceManager *SurfaceManager::instance()
36SurfaceManager *SurfaceManager::instance()
27{
37{
28− return the_surface_manager;
38+ return m_instance;
29}
39}
30
40
31−SurfaceManager::SurfaceManager(QObject *parent) :
41+SurfaceManager::SurfaceManager(QObject *)
32− QObject(parent)
33− , m_virtualKeyboard(nullptr)
34{
42{
35− Q_ASSERT(the_surface_manager == nullptr);
43+ DEBUG_MSG("");
36− the_surface_manager = this;
37
44
38− m_virtualKeyboard = new VirtualKeyboard;
45+ Q_ASSERT(m_instance == nullptr);
39− connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
46+ m_instance = this;
40− MirSurface* surface = qobject_cast<MirSurface*>(obj);
41− m_virtualKeyboard = nullptr;
42− Q_EMIT inputMethodSurfaceChanged();
43− Q_EMIT surfaceDestroyed(surface);
44− });
45}
47}
46
48
47SurfaceManager::~SurfaceManager()
49SurfaceManager::~SurfaceManager()
48{
50{
51+ DEBUG_MSG("");
52+
53+ if (m_virtualKeyboard) {
54+ m_virtualKeyboard->setLive(false);
55+ }
56+
49− Q_ASSERT(the_surface_manager == this);
57+ Q_ASSERT(m_instance == this);
50− the_surface_manager = nullptr;
58+ m_instance = nullptr;
51}
59}
52
60
53MirSurface *SurfaceManager::createSurface(const QString& name,
61MirSurface *SurfaceManager::createSurface(const QString& name,
@@ -56,10 +64,13 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
56 const QUrl& screenshot)
64 const QUrl& screenshot)
57{
65{
58 MirSurface* surface = new MirSurface(name, type, state, screenshot);
66 MirSurface* surface = new MirSurface(name, type, state, screenshot);
59− connect(surface, &QObject::destroyed, this, [this](QObject *obj) {
67+ registerSurface(surface);
60− MirSurface* surface = qobject_cast<MirSurface*>(obj);
68+ return surface;
61− Q_EMIT surfaceDestroyed(surface);
69+}
62− });
70+
71+void SurfaceManager::registerSurface(MirSurface *surface)
72+{
73+ m_surfaces.prepend(surface);
63
74
64 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
75 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
65 surface->setMaximumWidth(m_newSurfaceMaximumWidth);
76 surface->setMaximumWidth(m_newSurfaceMaximumWidth);
@@ -68,13 +79,18 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
68 surface->setWidthIncrement(m_newSurfaceWidthIncrement);
79 surface->setWidthIncrement(m_newSurfaceWidthIncrement);
69 surface->setHeightIncrement(m_newSurfaceHeightIncrement);
80 surface->setHeightIncrement(m_newSurfaceHeightIncrement);
70
81
71− Q_EMIT surfaceCreated(surface);
82+ connect(surface, &MirSurface::stateRequested, this, [=](Mir::State state) {
72− return surface;
83+ this->onStateRequested(surface, state);
84+ });
85+
86+ connect(surface, &QObject::destroyed, this, [=]() {
87+ this->onSurfaceDestroyed(surface);
88+ });
73}
89}
74
90
75−MirSurface *SurfaceManager::inputMethodSurface() const
91+void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
76{
92{
77− return m_virtualKeyboard;
93+ Q_EMIT surfaceCreated(surface);
78}
94}
79
95
80void SurfaceManager::setNewSurfaceMinimumWidth(int value)
96void SurfaceManager::setNewSurfaceMinimumWidth(int value)
@@ -124,3 +140,133 @@ void SurfaceManager::setNewSurfaceHeightIncrement(int value)
124 Q_EMIT newSurfaceHeightIncrementChanged(m_newSurfaceHeightIncrement);
140 Q_EMIT newSurfaceHeightIncrementChanged(m_newSurfaceHeightIncrement);
125 }
141 }
126}
142}
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+ Q_EMIT surfacesRaised({surface});
168+}
169+
170+void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
171+{
172+ auto surface = static_cast<MirSurface*>(apiSurface);
173+
174+ if (surface == m_focusedSurface) {
175+ return;
176+ }
177+
178+ Q_ASSERT(!m_underModification);
179+
180+ DEBUG_MSG("("<<surface<<") started");
181+ Q_EMIT modificationsStarted();
182+ m_underModification = true;
183+ if (m_focusedSurface) {
184+ m_focusedSurface->setFocused(false);
185+ }
186+ if (surface) {
187+ if (surface->state() == Mir::HiddenState || surface->state() == Mir::MinimizedState) {
188+ if (surface->previousState() != Mir::UnknownState) {
189+ surface->setState(surface->previousState());
190+ } else {
191+ surface->setState(Mir::RestoredState);
192+ }
193+ }
194+ surface->setFocused(true);
195+ doRaise(surface);
196+ }
197+ m_focusedSurface = surface;
198+ m_underModification = false;
199+ Q_EMIT modificationsEnded();
200+ DEBUG_MSG("("<<surface<<") ended");
201+}
202+
203+void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
204+{
205+ Q_ASSERT(!m_underModification);
206+
207+ DEBUG_MSG("("<<surface<<","<<state<<") started");
208+ Q_EMIT modificationsStarted();
209+ m_underModification = true;
210+
211+ surface->setPreviousState(surface->state());
212+ surface->setState(state);
213+
214+ if ((state == Mir::MinimizedState || state == Mir::HiddenState) && surface->focused()) {
215+ Q_ASSERT(m_focusedSurface == surface);
216+ surface->setFocused(false);
217+ m_focusedSurface = nullptr;
218+ focusFirstAvailableSurface();
219+ }
220+
221+ m_underModification = false;
222+ Q_EMIT modificationsEnded();
223+ DEBUG_MSG("("<<surface<<","<<state<<") ended");
224+}
225+
226+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
227+{
228+ m_surfaces.removeAll(surface);
229+ if (m_focusedSurface == surface) {
230+ m_focusedSurface = nullptr;
231+
232+ Q_EMIT modificationsStarted();
233+ m_underModification = true;
234+
235+ focusFirstAvailableSurface();
236+
237+ m_underModification = false;
238+ Q_EMIT modificationsEnded();
239+ }
240+}
241+
242+void SurfaceManager::focusFirstAvailableSurface()
243+{
244+ MirSurface *chosenSurface = nullptr;
245+ for (int i = 0; i < m_surfaces.count() && !chosenSurface; ++i) {
246+ auto *surface = m_surfaces[i];
247+ if (surface->state() != Mir::HiddenState && surface->state() != Mir::MinimizedState) {
248+ chosenSurface = surface;
249+ }
250+ }
251+
252+ if (!chosenSurface) {
253+ return;
254+ }
255+
256+ if (m_focusedSurface) {
257+ m_focusedSurface->setFocused(false);
258+ }
259+ if (chosenSurface) {
260+ chosenSurface->setFocused(true);
261+ }
262+ m_focusedSurface = chosenSurface;
263+}
264+
265+void SurfaceManager::createInputMethodSurface()
266+{
267+ if (!m_virtualKeyboard) {
268+ m_virtualKeyboard = new VirtualKeyboard;
269+ registerSurface(m_virtualKeyboard);
270+ Q_EMIT surfaceCreated(m_virtualKeyboard);
271+ }
272+}
tests/mocks/Unity/Application/SurfaceManager.h+28−10
@@ -19,15 +19,16 @@
19
19
20#include <QObject>
20#include <QObject>
21
21
22+#include <unity/shell/application/SurfaceManagerInterface.h>
23+
22#include "MirSurface.h"
24#include "MirSurface.h"
23#include "VirtualKeyboard.h"
25#include "VirtualKeyboard.h"
24
26
25class ApplicationInfo;
27class ApplicationInfo;
26
28
27−class SurfaceManager : public QObject
29+class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
28{
30{
29 Q_OBJECT
31 Q_OBJECT
30− Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
31 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
32 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
32 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
33 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
33 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
34 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
@@ -41,12 +42,17 @@ public:
41
42
42 static SurfaceManager *instance();
43 static SurfaceManager *instance();
43
44
45+ // SurfaceManagerInterface
46+ void raise(unity::shell::application::MirSurfaceInterface *surface) override;
47+ void activate(unity::shell::application::MirSurfaceInterface *surface) override;
48+
44 Q_INVOKABLE MirSurface* createSurface(const QString& name,
49 Q_INVOKABLE MirSurface* createSurface(const QString& name,
45 Mir::Type type,
50 Mir::Type type,
46 Mir::State state,
51 Mir::State state,
47 const QUrl& screenshot);
52 const QUrl& screenshot);
48
53
49− MirSurface* inputMethodSurface() const;
54+
55+ void notifySurfaceCreated(unity::shell::application::MirSurfaceInterface *);
50
56
51 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
57 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
52 void setNewSurfaceMinimumWidth(int value);
58 void setNewSurfaceMinimumWidth(int value);
@@ -66,12 +72,10 @@ public:
66 int newSurfaceHeightIncrement() const { return m_newSurfaceHeightIncrement; }
72 int newSurfaceHeightIncrement() const { return m_newSurfaceHeightIncrement; }
67 void setNewSurfaceHeightIncrement(int);
73 void setNewSurfaceHeightIncrement(int);
68
74
69−Q_SIGNALS:
75+public Q_SLOTS:
70− void inputMethodSurfaceChanged();
76+ void createInputMethodSurface();
71− void countChanged();
72− void surfaceCreated(MirSurface *surface);
73− void surfaceDestroyed(MirSurface*surface);
74
77
78+Q_SIGNALS:
75 void newSurfaceMinimumWidthChanged(int value);
79 void newSurfaceMinimumWidthChanged(int value);
76 void newSurfaceMaximumWidthChanged(int value);
80 void newSurfaceMaximumWidthChanged(int value);
77 void newSurfaceMinimumHeightChanged(int value);
81 void newSurfaceMinimumHeightChanged(int value);
@@ -79,9 +83,16 @@ Q_SIGNALS:
79 void newSurfaceWidthIncrementChanged(int value);
83 void newSurfaceWidthIncrementChanged(int value);
80 void newSurfaceHeightIncrementChanged(int value);
84 void newSurfaceHeightIncrementChanged(int value);
81
85
86+private Q_SLOTS:
87+ void onStateRequested(MirSurface *surface, Mir::State state);
88+ void onSurfaceDestroyed(MirSurface *surface);
89+
82private:
90private:
91+ void doRaise(unity::shell::application::MirSurfaceInterface *surface);
92+ void focusFirstAvailableSurface();
93+ void registerSurface(MirSurface *surface);
94+
83− static SurfaceManager *the_surface_manager;
95+ static SurfaceManager *m_instance;
84− VirtualKeyboard *m_virtualKeyboard;
85
96
86 int m_newSurfaceMinimumWidth{0};
97 int m_newSurfaceMinimumWidth{0};
87 int m_newSurfaceMaximumWidth{0};
98 int m_newSurfaceMaximumWidth{0};
@@ -89,6 +100,13 @@ private:
89 int m_newSurfaceMaximumHeight{0};
100 int m_newSurfaceMaximumHeight{0};
90 int m_newSurfaceWidthIncrement{1};
101 int m_newSurfaceWidthIncrement{1};
91 int m_newSurfaceHeightIncrement{1};
102 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};
92};
110};
93
111
94#endif // SURFACEMANAGER_H
112#endif // SURFACEMANAGER_H
tests/mocks/Unity/Application/plugin.cpp+3−33
@@ -29,45 +29,15 @@
29
29
30namespace {
30namespace {
31
31
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−
47QObject* applicationManagerSingleton(QQmlEngine*, QJSEngine*)
32QObject* applicationManagerSingleton(QQmlEngine*, QJSEngine*)
48{
33{
49− createUnityApplicationSharedSingletons();
50 return new ApplicationManager;
34 return new ApplicationManager;
51}
35}
52
36
53QObject* mirSingleton(QQmlEngine*, QJSEngine*)
37QObject* mirSingleton(QQmlEngine*, QJSEngine*)
54{
38{
55− createUnityApplicationSharedSingletons();
56− return MirMock::instance();
39+ return new MirMock;
57−}
58−
59−QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
60−{
61− createUnityApplicationSharedSingletons();
62− return SurfaceManager::instance();
63}
40}
64−
65−QObject* mirFocusControllerSingleton(QQmlEngine*, QJSEngine*)
66−{
67− createUnityApplicationSharedSingletons();
68− return MirFocusController::instance();
69−}
70−
71} // anonymous namespace
41} // anonymous namespace
72
42
73void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
43void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -75,6 +45,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
75 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
45 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
76 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
46 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
77 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
47 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
48+ qRegisterMetaType<unity::shell::application::SurfaceManagerInterface*>("unity::shell::application::SurfaceManagerInterface*");
78 qRegisterMetaType<Mir::Type>("Mir::Type");
49 qRegisterMetaType<Mir::Type>("Mir::Type");
79 qRegisterMetaType<Mir::State>("Mir::State");
50 qRegisterMetaType<Mir::State>("Mir::State");
80
51
@@ -83,13 +54,12 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
83 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
54 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
84 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
55 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
85 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
56 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
86− qmlRegisterSingletonType<MirFocusController>(uri, 0, 1, "MirFocusController", mirFocusControllerSingleton);
87 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
57 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
88 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
58 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
89
59
90 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
60 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
91 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
61 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
92− qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
62+ qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
93}
63}
94
64
95void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
65void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Utils/windowstatestorage.cpp+23
@@ -75,3 +75,26 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
75 if (!m_state.contains(windowId)) return defaultValue;
75 if (!m_state.contains(windowId)) return defaultValue;
76 return m_state.value(windowId);
76 return m_state.value(windowId);
77}
77}
78+
79+Mir::State WindowStateStorage::toMirState(WindowState state) const
80+{
81+ // assumes a single state (not an OR of several)
82+ switch (state) {
83+ case WindowStateMaximized: return Mir::MaximizedState;
84+ case WindowStateMinimized: return Mir::MinimizedState;
85+ case WindowStateFullscreen: return Mir::FullscreenState;
86+ case WindowStateMaximizedLeft: return Mir::MaximizedLeftState;
87+ case WindowStateMaximizedRight: return Mir::MaximizedRightState;
88+ case WindowStateMaximizedHorizontally: return Mir::HorizMaximizedState;
89+ case WindowStateMaximizedVertically: return Mir::VertMaximizedState;
90+ case WindowStateMaximizedTopLeft: return Mir::MaximizedTopLeftState;
91+ case WindowStateMaximizedTopRight: return Mir::MaximizedTopRightState;
92+ case WindowStateMaximizedBottomLeft: return Mir::MaximizedBottomLeftState;
93+ case WindowStateMaximizedBottomRight: return Mir::MaximizedBottomRightState;
94+
95+ case WindowStateNormal:
96+ case WindowStateRestored:
97+ default:
98+ return Mir::RestoredState;
99+ }
100+}
tests/mocks/Utils/windowstatestorage.h+5
@@ -18,6 +18,9 @@
18#include <QVariantMap>
18#include <QVariantMap>
19#include <QRect>
19#include <QRect>
20
20
21+// unity-api
22+#include <unity/shell/application/Mir.h>
23+
21class WindowStateStorage: public QObject
24class WindowStateStorage: public QObject
22{
25{
23 Q_OBJECT
26 Q_OBJECT
@@ -58,6 +61,8 @@ public:
58 // Only in the mock, to easily restore a fresh state
61 // Only in the mock, to easily restore a fresh state
59 Q_INVOKABLE void clear();
62 Q_INVOKABLE void clear();
60
63
64+ Q_INVOKABLE Mir::State toMirState(WindowState state) const;
65+
61Q_SIGNALS:
66Q_SIGNALS:
62 void geometryChanged(const QVariantMap& geometry);
67 void geometryChanged(const QVariantMap& geometry);
63
68
tests/plugins/Unity/Launcher/launchermodeltest.cpp+6
@@ -34,6 +34,8 @@
34#include <glib.h>
34#include <glib.h>
35#include <paths.h>
35#include <paths.h>
36
36
37+namespace unityapi = unity::shell::application;
38+
37// This is a mock, specifically to test the LauncherModel
39// This is a mock, specifically to test the LauncherModel
38class MockApp: public unity::shell::application::ApplicationInfoInterface
40class MockApp: public unity::shell::application::ApplicationInfoInterface
39{
41{
@@ -66,6 +68,7 @@ public:
66 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
68 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
67 int surfaceCount() const override { return m_surfaceCount; }
69 int surfaceCount() const override { return m_surfaceCount; }
68 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
70 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
71+ void close() override {}
69
72
70 // Methods used for mocking (not in the interface)
73 // Methods used for mocking (not in the interface)
71 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
74 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
@@ -99,6 +102,9 @@ public:
99 }
102 }
100 return nullptr;
103 return nullptr;
101 }
104 }
105+ unityapi::ApplicationInfoInterface *findApplicationWithSurface(unityapi::MirSurfaceInterface* surface) const override {
106+ return nullptr;
107+ }
102 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
108 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
103 bool stopApplication(const QString &appId) override {
109 bool stopApplication(const QString &appId) override {
104 Q_FOREACH(MockApp* app, m_list) {
110 Q_FOREACH(MockApp* app, m_list) {
tests/qmltests/Panel/tst_ActiveCallHint.qml+16−2
@@ -41,6 +41,8 @@ Item {
41 elapsedTimerRunning: true
41 elapsedTimerRunning: true
42 }
42 }
43
43
44+ SurfaceManager {}
45+
44 ActiveCallHint {
46 ActiveCallHint {
45 id: callHint
47 id: callHint
46 anchors {
48 anchors {
@@ -95,18 +97,30 @@ Item {
95
97
96 function test_activeHint(data) {
98 function test_activeHint(data) {
97 if (data.dialer) {
99 if (data.dialer) {
100+ var dashApp = ApplicationManager.startApplication("unity8-dash");
101+ tryCompare(dashApp.surfaceList, "count", 1);
102+
98 var application = ApplicationManager.startApplication("dialer-app");
103 var application = ApplicationManager.startApplication("dialer-app");
99 tryCompare(application.surfaceList, "count", 1);
104 tryCompare(application.surfaceList, "count", 1);
100− MirFocusController.focusedSurface = application.surfaceList.get(0);
101 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
105 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
102 tryCompare(application, "state", ApplicationInfoInterface.Running);
106 tryCompare(application, "state", ApplicationInfoInterface.Running);
103
107
104− if (!data.focused) { MirFocusController.focusedSurface = null; }
108+ if (!data.focused) {
109+ dashApp.surfaceList.get(0).activate();
110+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
111+ }
105 }
112 }
106 callManager.foregroundCall = data.call;
113 callManager.foregroundCall = data.call;
107 callManager.callIndicatorVisible = data.visible;
114 callManager.callIndicatorVisible = data.visible;
108
115
109 compare(callHint.active, data.expected, "Call hint should be active when callIndicatorVisible=true");
116 compare(callHint.active, data.expected, "Call hint should be active when callIndicatorVisible=true");
117+
118+ if (data.dialer) {
119+ // clean up
120+ ApplicationManager.stopApplication("dialer-app");
121+ ApplicationManager.stopApplication("unity8-dash");
122+ tryCompare(ApplicationManager, "count", 0);
123+ }
110 }
124 }
111
125
112 function test_currentCall_data() {
126 function test_currentCall_data() {
tests/qmltests/Panel/tst_Panel.qml+8−1
@@ -33,6 +33,8 @@ IndicatorTest {
33
33
34 Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark"
34 Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark"
35
35
36+ SurfaceManager {}
37+
36 Binding {
38 Binding {
37 target: mouseEmulation
39 target: mouseEmulation
38 property: "checked"
40 property: "checked"
@@ -470,7 +472,9 @@ IndicatorTest {
470
472
471 callManager.foregroundCall = phoneCall;
473 callManager.foregroundCall = phoneCall;
472
474
473− MirFocusController.focusedSurface = ApplicationManager.findApplication("unity8-dash").surfaceList.get(0);
475+ var dashApp = ApplicationManager.startApplication("unity8-dash");
476+ tryCompare(dashApp.surfaceList, "count", 1);
477+ dashApp.surfaceList.get(0).activate();
474 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
478 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
475
479
476 mouseClick(panel.indicators,
480 mouseClick(panel.indicators,
@@ -479,6 +483,9 @@ IndicatorTest {
479
483
480 compare(panel.indicators.shown, false);
484 compare(panel.indicators.shown, false);
481 verify(panel.indicators.fullyClosed);
485 verify(panel.indicators.fullyClosed);
486+
487+ // clean up
488+ ApplicationManager.stopApplication("unity8-dash");
482 }
489 }
483
490
484 function test_openAndClosePanelWithMouseClicks() {
491 function test_openAndClosePanelWithMouseClicks() {
tests/qmltests/Stage/ApplicationCheckBox.qml+1−1
@@ -149,7 +149,7 @@ Column {
149 width: height
149 width: height
150 height: promptsLabel.height * 0.7
150 height: promptsLabel.height * 0.7
151 anchors.verticalCenter: parent.verticalCenter
151 anchors.verticalCenter: parent.verticalCenter
152− onClicked: d.application.promptSurfaceList.createSurface()
152+ onClicked: d.application.createPromptSurface()
153 Label { text: "➕"; anchors.centerIn: parent }
153 Label { text: "➕"; anchors.centerIn: parent }
154 }
154 }
155 MouseArea {
155 MouseArea {
tests/qmltests/Stage/tst_ApplicationWindow.qml+10−10
@@ -37,6 +37,8 @@ Rectangle {
37 }
37 }
38 property QtObject fakeApplication: null
38 property QtObject fakeApplication: null
39
39
40+ SurfaceManager{}
41+
40 Loader {
42 Loader {
41 id: applicationWindowLoader
43 id: applicationWindowLoader
42 focus: true
44 focus: true
@@ -105,17 +107,17 @@ Rectangle {
105 RowLayout {
107 RowLayout {
106 property var promptSurfaceList: root.fakeApplication ? root.fakeApplication.promptSurfaceList : null
108 property var promptSurfaceList: root.fakeApplication ? root.fakeApplication.promptSurfaceList : null
107 Button {
109 Button {
108− enabled: parent.promptSurfaceList !== null && parent.promptSurfaceList.count > 0
110+ enabled: root.fakeApplication && root.fakeApplication.promptSurfaceList.count > 0
109 activeFocusOnPress: false
111 activeFocusOnPress: false
110 text: "Remove"
112 text: "Remove"
111− onClicked: { parent.promptSurfaceList.get(0).close(); }
113+ onClicked: { root.fakeApplication.promptSurfaceList.get(0).close(); }
112 }
114 }
113
115
114 Button {
116 Button {
115− enabled: parent.promptSurfaceList !== null
117+ enabled: root.fakeApplication
116 activeFocusOnPress: false
118 activeFocusOnPress: false
117 text: "Add Prompt Surface"
119 text: "Add Prompt Surface"
118− onClicked: { parent.promptSurfaceList.createSurface(); }
120+ onClicked: { root.fakeApplication.createPromptSurface(); }
119 }
121 }
120 }
122 }
121
123
@@ -427,7 +429,7 @@ Rectangle {
427 var i;
429 var i;
428 // 3 surfaces should cover all edge cases
430 // 3 surfaces should cover all edge cases
429 for (i = 0; i < 3; i++) {
431 for (i = 0; i < 3; i++) {
430− promptSurfaceList.createSurface();
432+ root.fakeApplication.createPromptSurface();
431 compare(promptSurfaces.count, i+1);
433 compare(promptSurfaces.count, i+1);
432 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
434 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
433 }
435 }
@@ -452,9 +454,7 @@ Rectangle {
452 }
454 }
453
455
454 function test_promptSurfaceAdjustsForParentSize() {
456 function test_promptSurfaceAdjustsForParentSize() {
455− var promptSurfaceList = root.fakeApplication.promptSurfaceList;
457+ root.fakeApplication.createPromptSurface();
456−
457− promptSurfaceList.createSurface();
458
458
459 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
459 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
460
460
@@ -487,10 +487,10 @@ Rectangle {
487 var promptSurfaceList = root.fakeApplication.promptSurfaceList;
487 var promptSurfaceList = root.fakeApplication.promptSurfaceList;
488 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
488 var promptSurfaces = testCase.findChild(applicationWindow, "promptSurfacesRepeater");
489
489
490− promptSurfaceList.createSurface();
490+ root.fakeApplication.createPromptSurface();
491
491
492 for (var i = 2; i <= 3; i++) {
492 for (var i = 2; i <= 3; i++) {
493− promptSurfaceList.createSurface();
493+ root.fakeApplication.createPromptSurface();
494 tryCompare(promptSurfaces, "count", i);
494 tryCompare(promptSurfaces, "count", i);
495 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
495 waitUntilSurfaceContainerStopsAnimating(promptSurfaces.itemAt(0));
496
496
tests/qmltests/Stage/tst_DesktopStage.qml+53−80
@@ -20,8 +20,8 @@ import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.3
20import Ubuntu.Components.ListItems 1.3
21import Unity.Application 0.1
21import Unity.Application 0.1
22import Unity.Test 0.1
22import Unity.Test 0.1
23−import WindowManager 0.1
24import Utils 0.1
23import Utils 0.1
24+import WindowManager 1.0
25
25
26import ".." // For EdgeBarrierControls
26import ".." // For EdgeBarrierControls
27import "../../../qml/Stage"
27import "../../../qml/Stage"
@@ -57,9 +57,11 @@ Item {
57 }
57 }
58 }
58 }
59
59
60− TopLevelSurfaceList {
60+ SurfaceManager { id: sMgr }
61+ TopLevelWindowModel {
61 id: topSurfaceList
62 id: topSurfaceList
62− applicationsModel: ApplicationManager
63+ applicationManager: ApplicationManager
64+ surfaceManager: sMgr
63 }
65 }
64
66
65 Loader {
67 Loader {
@@ -80,6 +82,7 @@ Item {
80
82
81 Component.onCompleted: {
83 Component.onCompleted: {
82 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
84 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
85+ ApplicationManager.startApplication("unity8-dash");
83 }
86 }
84 Component.onDestruction: {
87 Component.onDestruction: {
85 stageLoader.itemDestroyed = true;
88 stageLoader.itemDestroyed = true;
@@ -154,12 +157,13 @@ Item {
154 target: clickThroughTester
157 target: clickThroughTester
155 }
158 }
156
159
157− UnityTestCase {
160+ StageTestCase {
158 id: testCase
161 id: testCase
159 name: "DesktopStage"
162 name: "DesktopStage"
160 when: windowShown
163 when: windowShown
161
164
162− property Item stage: stageLoader.status === Loader.Ready ? stageLoader.item : null
165+ stage: stageLoader.status === Loader.Ready ? stageLoader.item : null
166+ topLevelSurfaceList: topSurfaceList
163
167
164 function init() {
168 function init() {
165 // wait until unity8-dash is up and running.
169 // wait until unity8-dash is up and running.
@@ -170,8 +174,9 @@ Item {
170 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
174 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
171
175
172 tryCompare(topSurfaceList, "count", 1);
176 tryCompare(topSurfaceList, "count", 1);
173− tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) != null;}, true);
177+ tryCompareFunction(function(){return topSurfaceList.windowAt(0) != null;}, true);
174− compare(MirFocusController.focusedSurface, topSurfaceList.surfaceAt(0));
178+ topSurfaceList.windowAt(0).activate();
179+ tryCompare(topSurfaceList, "focusedWindow", topSurfaceList.windowAt(0));
175 }
180 }
176
181
177 function cleanup() {
182 function cleanup() {
@@ -196,44 +201,6 @@ Item {
196 mouseEaterSpy.clear();
201 mouseEaterSpy.clear();
197 }
202 }
198
203
199− function waitUntilAppSurfaceShowsUp(surfaceId) {
200− var appDelegate = findChild(stage, "appDelegate_" + surfaceId);
201− verify(appDelegate);
202− var appWindow = findChild(appDelegate, "appWindow");
203− verify(appWindow);
204− var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
205− verify(appWindowStates);
206− tryCompare(appWindowStates, "state", "surface");
207− }
208−
209− /*
210− Returns the appDelegate of the first surface created by the app with the specified appId
211− */
212− function startApplication(appId) {
213− try {
214− var app = ApplicationManager.findApplication(appId);
215− if (app) {
216− for (var i = 0; i < topSurfaceList.count; i++) {
217− if (topSurfaceList.applicationAt(i).appId === appId) {
218− var appRepeater = findChild(stage, "appRepeater");
219− verify(appRepeater);
220− return appRepeater.itemAt(i);
221− }
222− }
223− }
224−
225− var surfaceId = topSurfaceList.nextId;
226− app = ApplicationManager.startApplication(appId);
227− verify(app);
228− waitUntilAppSurfaceShowsUp(surfaceId);
229− compare(app.surfaceList.count, 1);
230−
231− return findChild(stage, "appDelegate_" + surfaceId);
232− } catch(err) {
233− throw new Error("startApplication("+appId+") called from line " + util.callerLine(1) + " failed!");
234− }
235− }
236−
237 function maximizeAppDelegate(appDelegate) {
204 function maximizeAppDelegate(appDelegate) {
238 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
205 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
239 if (!maximizeButton) {
206 if (!maximizeButton) {
@@ -284,13 +251,13 @@ Item {
284 verify(fromAppWindow);
251 verify(fromAppWindow);
285 tap(fromAppWindow);
252 tap(fromAppWindow);
286 compare(fromDelegate.surface.activeFocus, true);
253 compare(fromDelegate.surface.activeFocus, true);
287− compare(MirFocusController.focusedSurface, fromDelegate.surface);
254+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
288
255
289 var toAppWindow = findChild(toDelegate, "appWindow");
256 var toAppWindow = findChild(toDelegate, "appWindow");
290 verify(toAppWindow);
257 verify(toAppWindow);
291 tap(toAppWindow);
258 tap(toAppWindow);
292 compare(toDelegate.surface.activeFocus, true);
259 compare(toDelegate.surface.activeFocus, true);
293− compare(MirFocusController.focusedSurface, toDelegate.surface);
260+ compare(topSurfaceList.focusedWindow, toDelegate.window);
294 }
261 }
295
262
296 function test_clickingOnWindowChangesFocusedApp_data() {
263 function test_clickingOnWindowChangesFocusedApp_data() {
@@ -309,13 +276,13 @@ Item {
309 verify(fromAppWindow);
276 verify(fromAppWindow);
310 mouseClick(fromAppWindow);
277 mouseClick(fromAppWindow);
311 compare(fromDelegate.surface.activeFocus, true);
278 compare(fromDelegate.surface.activeFocus, true);
312− compare(MirFocusController.focusedSurface, fromDelegate.surface);
279+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
313
280
314 var toAppWindow = findChild(toDelegate, "appWindow");
281 var toAppWindow = findChild(toDelegate, "appWindow");
315 verify(toAppWindow);
282 verify(toAppWindow);
316 mouseClick(toAppWindow);
283 mouseClick(toAppWindow);
317 compare(toDelegate.surface.activeFocus, true);
284 compare(toDelegate.surface.activeFocus, true);
318− compare(MirFocusController.focusedSurface, toDelegate.surface);
285+ compare(topSurfaceList.focusedWindow, toDelegate.window);
319 }
286 }
320
287
321 function test_tappingOnDecorationFocusesApplication_data() {
288 function test_tappingOnDecorationFocusesApplication_data() {
@@ -343,6 +310,13 @@ Item {
343 return findChild(appDelegate, "appWindowDecoration");
310 return findChild(appDelegate, "appWindowDecoration");
344 }
311 }
345
312
313+ function maximizeDelegate(appDelegate) {
314+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
315+ verify(maximizeButton);
316+ mouseClick(maximizeButton);
317+ tryCompare(appDelegate, "visuallyMaximized", true);
318+ }
319+
346 function test_tappingOnDecorationFocusesApplication(data) {
320 function test_tappingOnDecorationFocusesApplication(data) {
347 var appDelegates = [];
321 var appDelegates = [];
348 for (var i = 0; i < data.apps.length; i++) {
322 for (var i = 0; i < data.apps.length; i++) {
@@ -398,7 +372,7 @@ Item {
398 startApplication("camera-app");
372 startApplication("camera-app");
399
373
400 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
374 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
401− dialerDelegate.surface.requestFocus();
375+ dialerDelegate.surface.activate();
402 tryCompare(dialerDelegate, "focus", true);
376 tryCompare(dialerDelegate, "focus", true);
403
377
404 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
378 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
@@ -411,7 +385,7 @@ Item {
411 startApplication("camera-app");
385 startApplication("camera-app");
412
386
413 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
387 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
414− dialerDelegate.surface.requestFocus();
388+ dialerDelegate.surface.activate();
415 tryCompare(dialerDelegate, "focus", true);
389 tryCompare(dialerDelegate, "focus", true);
416
390
417 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
391 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
@@ -426,7 +400,7 @@ Item {
426 startApplication("camera-app");
400 startApplication("camera-app");
427
401
428 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
402 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
429− dialerDelegate.surface.requestFocus();
403+ dialerDelegate.surface.activate();
430 tryCompare(dialerDelegate, "focus", true);
404 tryCompare(dialerDelegate, "focus", true);
431
405
432 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
406 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
@@ -441,7 +415,7 @@ Item {
441 startApplication("camera-app");
415 startApplication("camera-app");
442
416
443 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
417 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
444− dialerDelegate.surface.requestFocus();
418+ dialerDelegate.surface.activate();
445 tryCompare(dialerDelegate, "focus", true);
419 tryCompare(dialerDelegate, "focus", true);
446
420
447 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
421 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
@@ -512,11 +486,11 @@ Item {
512 apps.forEach(startApplication);
486 apps.forEach(startApplication);
513 verify(topSurfaceList.count == 3);
487 verify(topSurfaceList.count == 3);
514 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
488 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
489+ tryCompare(topSurfaceList, "focusedWindow", null); // verify no window is focused
516
490
517 // now try pressing all 4 arrow keys + ctrl + meta
491 // now try pressing all 4 arrow keys + ctrl + meta
518 keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
492 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
493+ tryCompare(topSurfaceList, "focusedWindow", null); // verify still no window is focused
520 }
494 }
521
495
522 function test_minimizeApplicationHidesSurface() {
496 function test_minimizeApplicationHidesSurface() {
@@ -527,9 +501,12 @@ Item {
527 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
501 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
528 verify(decoratedWindow);
502 verify(decoratedWindow);
529
503
504+ var minimizeButton = findChild(decoratedWindow, "minimizeWindowButton");
505+ verify(minimizeButton);
506+
530− tryCompare(dashSurface, "visible", true);
507+ tryCompare(dashSurface, "exposed", true);
531− decoratedWindow.minimizeClicked();
508+ mouseClick(minimizeButton);
532− tryCompare(dashSurface, "visible", false);
509+ tryCompare(dashSurface, "exposed", false);
533 }
510 }
534
511
535 function test_maximizeApplicationHidesSurfacesBehindIt() {
512 function test_maximizeApplicationHidesSurfacesBehindIt() {
@@ -538,16 +515,16 @@ Item {
538 var gmailDelegate = startApplication("gmail-webapp");
515 var gmailDelegate = startApplication("gmail-webapp");
539
516
540 // maximize without raising
517 // maximize without raising
541− dialerDelegate.maximize();
518+ dialerDelegate.requestMaximize();
542 tryCompare(dialerDelegate, "visuallyMaximized", true);
519 tryCompare(dialerDelegate, "visuallyMaximized", true);
543
520
544− tryCompare(dashDelegate.surface, "visible", false);
521+ tryCompare(dashDelegate.surface, "exposed", false);
545− compare(gmailDelegate.surface.visible, true);
522+ compare(gmailDelegate.surface.exposed, true);
546
523
547 // restore without raising
524 // restore without raising
548− dialerDelegate.restoreFromMaximized();
525+ dialerDelegate.requestRestore();
549− compare(dashDelegate.surface.visible, true);
526+ tryCompare(dashDelegate.surface, "exposed", true);
550− compare(gmailDelegate.surface.visible, true);
527+ compare(gmailDelegate.surface.exposed, true);
551 }
528 }
552
529
553 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
530 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
@@ -580,26 +557,22 @@ Item {
580 tryCompare(dialerDelegate, "visuallyMaximized", true);
557 tryCompare(dialerDelegate, "visuallyMaximized", true);
581 tryCompare(gmailDelegate, "visuallyMaximized", true);
558 tryCompare(gmailDelegate, "visuallyMaximized", true);
582
559
583− tryCompare(dashApp.surfaceList.get(0), "visible", false);
560+ tryCompare(dashApp.surfaceList.get(0), "exposed", false);
584− tryCompare(dialerApp.surfaceList.get(0), "visible", false);
561+ tryCompare(dialerApp.surfaceList.get(0), "exposed", false);
585− tryCompare(mapApp.surfaceList.get(0), "visible", false);
562+ tryCompare(mapApp.surfaceList.get(0), "exposed", false);
586
563
587 ApplicationManager.stopApplication("gmail-webapp");
564 ApplicationManager.stopApplication("gmail-webapp");
588 wait(2000)
565 wait(2000)
589
566
590− tryCompare(mapApp.surfaceList.get(0), "visible", true);
567+ tryCompare(mapApp.surfaceList.get(0), "exposed", true);
591− tryCompare(dialerApp.surfaceList.get(0), "visible", true);
568+ tryCompare(dialerApp.surfaceList.get(0), "exposed", true);
592− tryCompare(dashApp.surfaceList.get(0), "visible", false); // still occluded by maximised dialer
569+ tryCompare(dashApp.surfaceList.get(0), "exposed", false); // still occluded by maximised dialer
593 }
570 }
594
571
595 function test_maximisedAppStaysVisibleWhenAppStarts() {
572 function test_maximisedAppStaysVisibleWhenAppStarts() {
596 var dashDelegate = startApplication("unity8-dash");
573 var dashDelegate = startApplication("unity8-dash");
597
574
598− // maximize
575+ maximizeDelegate(dashDelegate);
599− var dashMaximizeButton = findChild(dashDelegate, "maximizeWindowButton");
600− verify(dashMaximizeButton);
601− mouseClick(dashMaximizeButton);
602− tryCompare(dashDelegate, "visuallyMaximized", true);
603
576
604 var dialerDelegate = startApplication("dialer-app");
577 var dialerDelegate = startApplication("dialer-app");
605 verify(dialerDelegate);
578 verify(dialerDelegate);
@@ -621,25 +594,25 @@ Item {
621 tryCompare(facebookAppDelegate, "visible", true);
594 tryCompare(facebookAppDelegate, "visible", true);
622
595
623 // Maximize the topmost and make sure the other two are hidden
596 // Maximize the topmost and make sure the other two are hidden
624− facebookAppDelegate.maximize();
597+ maximizeDelegate(facebookAppDelegate);
625 tryCompare(dashAppDelegate, "visible", false);
598 tryCompare(dashAppDelegate, "visible", false);
626 tryCompare(dialerAppDelegate, "visible", false);
599 tryCompare(dialerAppDelegate, "visible", false);
627 tryCompare(facebookAppDelegate, "visible", true);
600 tryCompare(facebookAppDelegate, "visible", true);
628
601
629 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
602 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
630− dashAppDelegate.focus = true;
603+ dashAppDelegate.activate();
631 tryCompare(dashAppDelegate, "visible", true);
604 tryCompare(dashAppDelegate, "visible", true);
632 tryCompare(dialerAppDelegate, "visible", false);
605 tryCompare(dialerAppDelegate, "visible", false);
633 tryCompare(facebookAppDelegate, "visible", true);
606 tryCompare(facebookAppDelegate, "visible", true);
634
607
635 // Now focus the dialer app. all 3 should be visible again
608 // Now focus the dialer app. all 3 should be visible again
636− dialerAppDelegate.focus = true;
609+ dialerAppDelegate.activate();
637 tryCompare(dashAppDelegate, "visible", true);
610 tryCompare(dashAppDelegate, "visible", true);
638 tryCompare(dialerAppDelegate, "visible", true);
611 tryCompare(dialerAppDelegate, "visible", true);
639 tryCompare(facebookAppDelegate, "visible", true);
612 tryCompare(facebookAppDelegate, "visible", true);
640
613
641 // Maximize the dialer app. The other 2 should hide
614 // Maximize the dialer app. The other 2 should hide
642− dialerAppDelegate.maximize();
615+ maximizeDelegate(dialerAppDelegate);
643 tryCompare(dashAppDelegate, "visible", false);
616 tryCompare(dashAppDelegate, "visible", false);
644 tryCompare(dialerAppDelegate, "visible", true);
617 tryCompare(dialerAppDelegate, "visible", true);
645 tryCompare(facebookAppDelegate, "visible", false);
618 tryCompare(facebookAppDelegate, "visible", false);
@@ -648,7 +621,7 @@ Item {
648 function test_dropShadow() {
621 function test_dropShadow() {
649 // start an app, maximize it
622 // start an app, maximize it
650 var facebookAppDelegate = startApplication("facebook-webapp");
623 var facebookAppDelegate = startApplication("facebook-webapp");
651− facebookAppDelegate.maximize();
624+ maximizeDelegate(facebookAppDelegate);
652
625
653 // verify the drop shadow is still not visible
626 // verify the drop shadow is still not visible
654 verify(PanelState.dropShadow == false);
627 verify(PanelState.dropShadow == false);
tests/qmltests/Stage/tst_PhoneStage.qml+18−12
@@ -22,7 +22,7 @@ import "../../../qml/Components"
22import "../../../qml/Stage"
22import "../../../qml/Stage"
23import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
24import Unity.Application 0.1
24import Unity.Application 0.1
25−import WindowManager 0.1
25+import WindowManager 1.0
26
26
27Item {
27Item {
28 id: root
28 id: root
@@ -31,6 +31,7 @@ Item {
31
31
32 property var greeter: { fullyShown: true }
32 property var greeter: { fullyShown: true }
33
33
34+ SurfaceManager { id: sMgr }
34 Stage {
35 Stage {
35 id: stage
36 id: stage
36 anchors { fill: parent; rightMargin: units.gu(30) }
37 anchors { fill: parent; rightMargin: units.gu(30) }
@@ -41,9 +42,13 @@ Item {
41 orientations: Orientations {}
42 orientations: Orientations {}
42 applicationManager: ApplicationManager
43 applicationManager: ApplicationManager
43 mode: "staged"
44 mode: "staged"
44− topLevelSurfaceList: TopLevelSurfaceList {
45+ topLevelSurfaceList: TopLevelWindowModel {
45 id: topLevelSurfaceList
46 id: topLevelSurfaceList
47+ applicationManager: ApplicationManager
48+ surfaceManager: sMgr
49+ }
50+ Component.onCompleted: {
46− applicationsModel: ApplicationManager
51+ ApplicationManager.startApplication("unity8-dash");
47 }
52 }
48 }
53 }
49
54
@@ -86,11 +91,16 @@ Item {
86
91
87 function init() {
92 function init() {
88 // wait until unity8-dash is up and running.
93 // wait until unity8-dash is up and running.
89− // it's started automatically by ApplicationManager mock implementation
94+ ApplicationManager.startApplication("unity8-dash");
90 tryCompare(ApplicationManager, "count", 1);
95 tryCompare(ApplicationManager, "count", 1);
91 var dashApp = ApplicationManager.findApplication("unity8-dash");
96 var dashApp = ApplicationManager.findApplication("unity8-dash");
92 verify(dashApp);
97 verify(dashApp);
93 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
98 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);
94 }
104 }
95
105
96 function cleanup() {
106 function cleanup() {
@@ -100,10 +110,6 @@ Item {
100 waitForRendering(stage);
110 waitForRendering(stage);
101
111
102 killApps();
112 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);
107
113
108 stage.shellOrientationAngle = 0;
114 stage.shellOrientationAngle = 0;
109
115
@@ -479,7 +485,7 @@ Item {
479 function test_selectSuspendedAppWithoutSurface() {
485 function test_selectSuspendedAppWithoutSurface() {
480 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
486 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
481 var dashSurfaceId = topLevelSurfaceList.idAt(0);
487 var dashSurfaceId = topLevelSurfaceList.idAt(0);
482− var dashSurface = topLevelSurfaceList.surfaceAt(0);
488+ var dashWindow = topLevelSurfaceList.windowAt(0);
483
489
484 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
490 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
485 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
491 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -487,7 +493,7 @@ Item {
487
493
488 switchToSurface(dashSurfaceId);
494 switchToSurface(dashSurfaceId);
489
495
490− tryCompare(MirFocusController, "focusedSurface", dashSurface);
496+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
491 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
497 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
492
498
493 compare(webbrowserApp.surfaceList.count, 1);
499 compare(webbrowserApp.surfaceList.count, 1);
@@ -523,7 +529,7 @@ Item {
523 {
529 {
524 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
530 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
525 var dashSurfaceId = topLevelSurfaceList.idAt(0);
531 var dashSurfaceId = topLevelSurfaceList.idAt(0);
526− var dashSurface = topLevelSurfaceList.surfaceAt(0);
532+ var dashWindow = topLevelSurfaceList.windowAt(0);
527
533
528 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
534 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
529 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
535 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -531,7 +537,7 @@ Item {
531
537
532 switchToSurface(dashSurfaceId);
538 switchToSurface(dashSurfaceId);
533
539
534− tryCompare(MirFocusController, "focusedSurface", dashSurface);
540+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
535 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
541 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
536
542
537 compare(webbrowserApp.surfaceList.count, 1);
543 compare(webbrowserApp.surfaceList.count, 1);
tests/qmltests/Stage/tst_SurfaceContainer.qml+4
@@ -41,6 +41,8 @@ Rectangle {
41 SurfaceContainer {
41 SurfaceContainer {
42 anchors.fill: parent
42 anchors.fill: parent
43 focus: true
43 focus: true
44+ requestedWidth: width
45+ requestedHeight: height
44 interactive: interactiveCheckbox.checked
46 interactive: interactiveCheckbox.checked
45 isPromptSurface: promptCheckbox.checked
47 isPromptSurface: promptCheckbox.checked
46 Component.onDestruction: {
48 Component.onDestruction: {
@@ -49,6 +51,8 @@ Rectangle {
49 }
51 }
50 }
52 }
51
53
54+ SurfaceManager {}
55+
52 Loader {
56 Loader {
53 id: surfaceContainerLoader
57 id: surfaceContainerLoader
54 focus: true
58 focus: true
tests/qmltests/Stage/tst_TabletStage.qml+16−10
@@ -21,7 +21,7 @@ import Ubuntu.Components.ListItems 1.3 as ListItem
21import Unity.Application 0.1
21import Unity.Application 0.1
22import Unity.Test 0.1
22import Unity.Test 0.1
23import Utils 0.1
23import Utils 0.1
24−import WindowManager 0.1
24+import WindowManager 1.0
25
25
26import ".."
26import ".."
27import "../../../qml/Stage"
27import "../../../qml/Stage"
@@ -35,6 +35,7 @@ Rectangle {
35
35
36 property var greeter: { fullyShown: true }
36 property var greeter: { fullyShown: true }
37
37
38+ SurfaceManager { id: sMgr }
38 Stage {
39 Stage {
39 id: stage
40 id: stage
40 anchors { fill: parent; rightMargin: units.gu(30) }
41 anchors { fill: parent; rightMargin: units.gu(30) }
@@ -50,9 +51,13 @@ Rectangle {
50 focus: true
51 focus: true
51 mode: "stagedWithSideStage"
52 mode: "stagedWithSideStage"
52 applicationManager: ApplicationManager
53 applicationManager: ApplicationManager
53− topLevelSurfaceList: TopLevelSurfaceList {
54+ topLevelSurfaceList: TopLevelWindowModel {
54 id: topLevelSurfaceList
55 id: topLevelSurfaceList
56+ applicationManager: ApplicationManager
57+ surfaceManager: sMgr
58+ }
59+ Component.onCompleted: {
55− applicationsModel: ApplicationManager
60+ ApplicationManager.startApplication("unity8-dash");
56 }
61 }
57 }
62 }
58
63
@@ -129,6 +134,7 @@ Rectangle {
129 function init() {
134 function init() {
130 stageSaver.clear();
135 stageSaver.clear();
131
136
137+ ApplicationManager.startApplication("unity8-dash");
132 tryCompare(topSurfaceList, "count", 1);
138 tryCompare(topSurfaceList, "count", 1);
133 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
139 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
134
140
@@ -143,6 +149,11 @@ Rectangle {
143 }
149 }
144 }
150 }
145
151
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+
146 waitUntilAppSurfaceShowsUp(topSurfaceList.idAt(0));
157 waitUntilAppSurfaceShowsUp(topSurfaceList.idAt(0));
147 sideStage.hideNow()
158 sideStage.hideNow()
148 tryCompare(sideStage, "x", stage.width)
159 tryCompare(sideStage, "x", stage.width)
@@ -156,11 +167,6 @@ Rectangle {
156
167
157 killApps();
168 killApps();
158
169
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−
164 sideStage.hideNow();
170 sideStage.hideNow();
165 tryCompare(sideStage, "x", stage.width)
171 tryCompare(sideStage, "x", stage.width)
166 waitForRendering(stage)
172 waitForRendering(stage)
@@ -628,7 +634,7 @@ Rectangle {
628 function test_selectSuspendedAppWithoutSurface() {
634 function test_selectSuspendedAppWithoutSurface() {
629 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
635 compare(topSurfaceList.applicationAt(0).appId, "unity8-dash");
630 var dashSurfaceId = topSurfaceList.idAt(0);
636 var dashSurfaceId = topSurfaceList.idAt(0);
631− var dashSurface = topSurfaceList.surfaceAt(0);
637+ var dashWindow = topSurfaceList.windowAt(0);
632
638
633 var webbrowserSurfaceId = topSurfaceList.nextId;
639 var webbrowserSurfaceId = topSurfaceList.nextId;
634 webbrowserCheckBox.checked = true;
640 webbrowserCheckBox.checked = true;
@@ -637,7 +643,7 @@ Rectangle {
637
643
638 switchToSurface(dashSurfaceId);
644 switchToSurface(dashSurfaceId);
639
645
640− tryCompare(MirFocusController, "focusedSurface", dashSurface);
646+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
641 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
647 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
642
648
643 compare(webbrowserApp.surfaceList.count, 1);
649 compare(webbrowserApp.surfaceList.count, 1);
tests/qmltests/Tutorial/tst_Tutorial.qml+30−8
@@ -222,11 +222,11 @@ Rectangle {
222 CheckBox {
222 CheckBox {
223 activeFocusOnPress: false
223 activeFocusOnPress: false
224 onCheckedChanged: {
224 onCheckedChanged: {
225− var surface = SurfaceManager.inputMethodSurface;
225+ var surface = topLevelSurfaceList.inputMethodSurface;
226 if (checked) {
226 if (checked) {
227− surface.setState(Mir.RestoredState);
227+ surface.requestState(Mir.RestoredState);
228 } else {
228 } else {
229− surface.setState(Mir.MinimizedState);
229+ surface.requestState(Mir.MinimizedState);
230 }
230 }
231 }
231 }
232 }
232 }
@@ -246,7 +246,7 @@ Rectangle {
246 }
246 }
247 }
247 }
248
248
249− UT.UnityTestCase {
249+ UT.StageTestCase {
250 id: testCase
250 id: testCase
251 name: "Tutorial"
251 name: "Tutorial"
252 when: windowShown
252 when: windowShown
@@ -255,6 +255,14 @@ Rectangle {
255 property real halfWidth: shell ? shell.width / 2 : 0
255 property real halfWidth: shell ? shell.width / 2 : 0
256 property real halfHeight: shell ? shell.height / 2 : 0
256 property real halfHeight: shell ? shell.height / 2 : 0
257
257
258+ onShellChanged: {
259+ if (shell) {
260+ topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
261+ } else {
262+ topLevelSurfaceList = null;
263+ }
264+ }
265+
258 function init() {
266 function init() {
259 prepareShell();
267 prepareShell();
260
268
@@ -299,13 +307,17 @@ Rectangle {
299 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
307 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
300
308
301 WindowStateStorage.clear();
309 WindowStateStorage.clear();
302− SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
310+ //topLevelSurfaceList.inputMethodSurface.setState(Mir.MinimizedState);
303 callManager.foregroundCall = null;
311 callManager.foregroundCall = null;
304 AccountsService.demoEdges = false;
312 AccountsService.demoEdges = false;
305 AccountsService.demoEdgesCompleted = [];
313 AccountsService.demoEdgesCompleted = [];
306 AccountsService.demoEdges = true;
314 AccountsService.demoEdges = true;
307
315
308 LightDM.Greeter.hideGreeter();
316 LightDM.Greeter.hideGreeter();
317+
318+ stage = findChild(shell, "stage"); // from StageTestCase
319+
320+ startApplication("unity8-dash");
309 }
321 }
310
322
311 function loadShell(state) {
323 function loadShell(state) {
@@ -313,6 +325,14 @@ Rectangle {
313 prepareShell();
325 prepareShell();
314 }
326 }
315
327
328+ function ensureInputMethodSurface() {
329+ var surfaceManager = findInvisibleChild(shell, "surfaceManager");
330+ verify(surfaceManager);
331+ surfaceManager.createInputMethodSurface();
332+
333+ tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
334+ }
335+
316 function swipeAwayGreeter() {
336 function swipeAwayGreeter() {
317 var coverPage = findChild(shell, "coverPage");
337 var coverPage = findChild(shell, "coverPage");
318 tryCompare(coverPage, "showProgress", 1);
338 tryCompare(coverPage, "showProgress", 1);
@@ -711,8 +731,9 @@ Rectangle {
711 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
731 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
712 verify(tutorialLeftLoader.shown);
732 verify(tutorialLeftLoader.shown);
713
733
734+ ensureInputMethodSurface();
714− var surface = SurfaceManager.inputMethodSurface;
735+ var surface = topLevelSurfaceList.inputMethodSurface;
715− surface.setState(Mir.RestoredState);
736+ surface.requestState(Mir.RestoredState);
716
737
717 var inputMethod = findInvisibleChild(shell, "inputMethod");
738 var inputMethod = findInvisibleChild(shell, "inputMethod");
718 tryCompare(inputMethod, "visible", true);
739 tryCompare(inputMethod, "visible", true);
@@ -724,7 +745,8 @@ Rectangle {
724 var tutorial = findChild(shell, "tutorial");
745 var tutorial = findChild(shell, "tutorial");
725 verify(!tutorial.delayed);
746 verify(!tutorial.delayed);
726
747
748+ ensureInputMethodSurface();
727− SurfaceManager.inputMethodSurface.setState(Mir.RestoredState);
749+ topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
728
750
729 tryCompare(tutorial, "delayed", true);
751 tryCompare(tutorial, "delayed", true);
730 }
752 }
tests/qmltests/tst_OrientedShell.qml+5
@@ -980,6 +980,11 @@ Rectangle {
980 function test_launchLandscapeOnlyAppOverPortraitOnlyDashThenSwitchToDash() {
980 function test_launchLandscapeOnlyAppOverPortraitOnlyDashThenSwitchToDash() {
981 loadShell("mako");
981 loadShell("mako");
982
982
983+ var dashSurfaceId = topLevelSurfaceList.nextId;
984+ var dashApp = ApplicationManager.startApplication("unity8-dash");
985+ verify(dashApp);
986+ waitUntilAppWindowIsFullyLoaded(dashSurfaceId);
987+
983 // starts as portrait, as unity8-dash is portrait only
988 // starts as portrait, as unity8-dash is portrait only
984 tryCompare(shell, "transformRotationAngle", 0);
989 tryCompare(shell, "transformRotationAngle", 0);
985
990
tests/qmltests/tst_Shell.qml+127−82
@@ -52,6 +52,15 @@ Rectangle {
52 }
52 }
53
53
54 property var shell: shellLoader.item ? shellLoader.item : null
54 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
55
64
56 Item {
65 Item {
57 id: shellContainer
66 id: shellContainer
@@ -120,6 +129,9 @@ Rectangle {
120 primary: shellLoader.primaryOrientation
129 primary: shellLoader.primaryOrientation
121 }
130 }
122 mode: shellLoader.mode
131 mode: shellLoader.mode
132+ Component.onCompleted: {
133+ ApplicationManager.startApplication("unity8-dash");
134+ }
123 Component.onDestruction: {
135 Component.onDestruction: {
124 shellLoader.itemDestroyed = true;
136 shellLoader.itemDestroyed = true;
125 }
137 }
@@ -227,7 +239,7 @@ Rectangle {
227 activeFocusOnPress: false
239 activeFocusOnPress: false
228 model: ["single", "single-passphrase", "single-pin", "full"]
240 model: ["single", "single-passphrase", "single-pin", "full"]
229 onSelectedIndexChanged: {
241 onSelectedIndexChanged: {
230− shellLoader.active = false;
242+ testCase.tearDown();
231 LightDM.Greeter.mockMode = model[selectedIndex];
243 LightDM.Greeter.mockMode = model[selectedIndex];
232 LightDM.Users.mockMode = model[selectedIndex];
244 LightDM.Users.mockMode = model[selectedIndex];
233 shellLoader.active = true;
245 shellLoader.active = true;
@@ -318,21 +330,21 @@ Rectangle {
318 id: fullscreeAppCheck
330 id: fullscreeAppCheck
319
331
320 onTriggered: {
332 onTriggered: {
321− if (!MirFocusController.focusedSurface) return;
333+ if (!topLevelSurfaceList.focusedWindow) return;
322− if (MirFocusController.focusedSurface.state == Mir.FullscreenState) {
334+ if (topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState) {
323− MirFocusController.focusedSurface.state = Mir.RestoredState;
335+ topLevelSurfaceList.focusedWindow.requestState(Mir.RestoredState);
324 } else {
336 } else {
325− MirFocusController.focusedSurface.state = Mir.FullscreenState;
337+ topLevelSurfaceList.focusedWindow.requestState(Mir.FullscreenState);
326 }
338 }
327 }
339 }
328
340
329 Binding {
341 Binding {
330 target: fullscreeAppCheck
342 target: fullscreeAppCheck
331− when: MirFocusController.focusedSurface
343+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow
332 property: "checked"
344 property: "checked"
333 value: {
345 value: {
334− if (!MirFocusController.focusedSurface) return false;
346+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow) return false;
335− return MirFocusController.focusedSurface.state === Mir.FullscreenState
347+ return topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
336 }
348 }
337 }
349 }
338 }
350 }
@@ -346,21 +358,22 @@ Rectangle {
346 id: chromeAppCheck
358 id: chromeAppCheck
347
359
348 onTriggered: {
360 onTriggered: {
349− if (!MirFocusController.focusedSurface) return;
361+ if (!topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return;
362+ var surface = topLevelSurfaceList.focusedWindow.surface;
350− if (MirFocusController.focusedSurface.shellChrome == Mir.LowChrome) {
363+ if (surface.shellChrome == Mir.LowChrome) {
351− MirFocusController.focusedSurface.setShellChrome(Mir.NormalChrome);
364+ surface.setShellChrome(Mir.NormalChrome);
352 } else {
365 } else {
353− MirFocusController.focusedSurface.setShellChrome(Mir.LowChrome);
366+ surface.setShellChrome(Mir.LowChrome);
354 }
367 }
355 }
368 }
356
369
357 Binding {
370 Binding {
358 target: chromeAppCheck
371 target: chromeAppCheck
359− when: MirFocusController.focusedSurface !== null
372+ when: topLevelSurfaceList && topLevelSurfaceList.focusedWindow !== null && topLevelSurfaceList.focusedWindow.surface !== null
360 property: "checked"
373 property: "checked"
361 value: {
374 value: {
362− if (!MirFocusController.focusedSurface) return false;
375+ if (!topLevelSurfaceList || !topLevelSurfaceList.focusedWindow || !topLevelSurfaceList.focusedWindow.surface) return false;
363− MirFocusController.focusedSurface.shellChrome === Mir.LowChrome
376+ topLevelSurfaceList.focusedWindow.surface.shellChrome === Mir.LowChrome
364 }
377 }
365 }
378 }
366 }
379 }
@@ -369,6 +382,57 @@ Rectangle {
369 }
382 }
370 }
383 }
371
384
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+
372 Label {
436 Label {
373 text: "Fingerprint"
437 text: "Fingerprint"
374 }
438 }
@@ -483,13 +547,12 @@ Rectangle {
483 }
547 }
484 }
548 }
485
549
486− UnityTestCase {
550+ StageTestCase {
487 id: testCase
551 id: testCase
488 name: "Shell"
552 name: "Shell"
489 when: windowShown
553 when: windowShown
490
554
491 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
555 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
492− property QtObject topLevelSurfaceList: null
493
556
494 function init() {
557 function init() {
495 if (shellLoader.active) {
558 if (shellLoader.active) {
@@ -528,8 +591,14 @@ Rectangle {
528
591
529 waitForGreeterToStabilize();
592 waitForGreeterToStabilize();
530
593
594+ // from StageTestCase
531 topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
595 topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
532 verify(topLevelSurfaceList);
596 verify(topLevelSurfaceList);
597+ stage = findChild(shell, "stage");
598+
599+ // wait until unity8-dash is fully loaded
600+ tryCompare(topLevelSurfaceList, "count", 1);
601+ waitUntilAppWindowIsFullyLoaded(topLevelSurfaceList.idAt(0));
533 }
602 }
534
603
535 function loadDesktopShellWithApps() {
604 function loadDesktopShellWithApps() {
@@ -599,22 +668,23 @@ Rectangle {
599
668
600 GSettingsController.setLifecycleExemptAppids([]);
669 GSettingsController.setLifecycleExemptAppids([]);
601 GSettingsController.setPictureUri("");
670 GSettingsController.setPictureUri("");
671+ }
672+
673+ function ensureInputMethodSurface() {
674+ var surfaceManager = findInvisibleChild(shell, "surfaceManager");
675+ verify(surfaceManager);
676+ surfaceManager.createInputMethodSurface();
602
677
603− // there should be only unity8-dash window over there
678+ tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
604− tryCompare(ApplicationManager, "count", 1);
605− compare(ApplicationManager.get(0).appId, "unity8-dash");
606 }
679 }
607
680
608 function test_snapDecisionDismissalReturnsFocus() {
681 function test_snapDecisionDismissalReturnsFocus() {
609 loadShell("phone");
682 loadShell("phone");
610 swipeAwayGreeter();
683 swipeAwayGreeter();
611 var notifications = findChild(shell, "notificationList");
684 var notifications = findChild(shell, "notificationList");
612− var appSurfaceId = topLevelSurfaceList.nextId;
613− var app = ApplicationManager.startApplication("camera-app");
685+ var appDelegate = startApplication("camera-app");
614− waitUntilAppWindowIsFullyLoaded(appSurfaceId);
615− var stage = findChild(shell, "stage")
616
686
617− var appSurface = app.surfaceList.get(0);
687+ var appSurface = appDelegate.surface;
618 verify(appSurface);
688 verify(appSurface);
619
689
620 tryCompare(appSurface, "activeFocus", true);
690 tryCompare(appSurface, "activeFocus", true);
@@ -810,53 +880,32 @@ Rectangle {
810 function test_showInputMethod() {
880 function test_showInputMethod() {
811 loadShell("phone");
881 loadShell("phone");
812 swipeAwayGreeter();
882 swipeAwayGreeter();
883+ ensureInputMethodSurface();
884+
813 var item = findChild(shell, "inputMethod");
885 var item = findChild(shell, "inputMethod");
814− var surface = SurfaceManager.inputMethodSurface;
886+ var surface = topLevelSurfaceList.inputMethodSurface;
815
887
816− surface.setState(Mir.MinimizedState);
888+ surface.requestState(Mir.MinimizedState);
817 tryCompare(item, "visible", false);
889 tryCompare(item, "visible", false);
818
890
819− surface.setState(Mir.RestoredState);
891+ surface.requestState(Mir.RestoredState);
820 tryCompare(item, "visible", true);
892 tryCompare(item, "visible", true);
821
893
822− surface.setState(Mir.MinimizedState);
894+ surface.requestState(Mir.MinimizedState);
823 tryCompare(item, "visible", false);
895 tryCompare(item, "visible", false);
824
896
825− surface.setState(Mir.MaximizedState);
897+ surface.requestState(Mir.MaximizedState);
826 tryCompare(item, "visible", true);
898 tryCompare(item, "visible", true);
827
899
828− surface.setState(Mir.MinimizedState);
900+ surface.requestState(Mir.MinimizedState);
829 tryCompare(item, "visible", false);
901 tryCompare(item, "visible", false);
830 }
902 }
831
903
832− function findAppWindowForSurfaceId(surfaceId) {
833− // for PhoneStage and TabletStage
834− var delegate = findChild(shell, "spreadDelegate_" + surfaceId, 0 /* timeout */);
835− if (!delegate) {
836− // for DesktopStage
837− delegate = findChild(shell, "appDelegate_" + surfaceId);
838− }
839− verify(delegate);
840− var appWindow = findChild(delegate, "appWindow");
841− return appWindow;
842− }
843−
844− // Wait until the ApplicationWindow for the given Application object is fully loaded
845− // (ie, the real surface has replaced the splash screen)
846− function waitUntilAppWindowIsFullyLoaded(surfaceId) {
847− var appWindow = findAppWindowForSurfaceId(surfaceId);
848− var appWindowStateGroup = findInvisibleChild(appWindow, "applicationWindowStateGroup");
849− tryCompareFunction(function() { return appWindowStateGroup.state === "surface" }, true);
850− waitUntilTransitionsEnd(appWindowStateGroup);
851− }
852−
853 function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
904 function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
854 loadShell("phone");
905 loadShell("phone");
855 swipeAwayGreeter();
906 swipeAwayGreeter();
856− var appSurfaceId = topLevelSurfaceList.nextId;
857− var app = ApplicationManager.startApplication("dialer-app");
907+ var appDelegate = startApplication("dialer-app");
858− waitUntilAppWindowIsFullyLoaded(appSurfaceId);
859− var appSurface = app.surfaceList.get(0);
908+ var appSurface = appDelegate.surface;
860 verify(appSurface);
909 verify(appSurface);
861
910
862 tryCompare(appSurface, "activeFocus", true);
911 tryCompare(appSurface, "activeFocus", true);
@@ -899,15 +948,9 @@ Rectangle {
899 shell.usageScenario = data.usageScenario;
948 shell.usageScenario = data.usageScenario;
900 swipeAwayGreeter();
949 swipeAwayGreeter();
901
950
902− var webAppSurfaceId = topLevelSurfaceList.nextId;
903− var webApp = ApplicationManager.startApplication("webbrowser-app");
951+ var appDelegate = startApplication("webbrowser-app");
904− verify(webApp);
905− waitUntilAppWindowIsFullyLoaded(webAppSurfaceId);
906
952
907− var webAppSurface = webApp.surfaceList.get(topLevelSurfaceList.indexForId(webAppSurfaceId));
908− verify(webAppSurface);
909−
910− tryCompare(webAppSurface, "activeFocus", true);
953+ tryCompare(appDelegate.surface, "activeFocus", true);
911 }
954 }
912
955
913 function test_launchedAppKeepsActiveFocusOnUsageModeChange() {
956 function test_launchedAppKeepsActiveFocusOnUsageModeChange() {
@@ -2123,7 +2166,6 @@ Rectangle {
2123 var firstAppInLauncher = LauncherModel.get(0).appId;
2166 var firstAppInLauncher = LauncherModel.get(0).appId;
2124 compare(launcher.state, data.launcherLocked ? "visible": "");
2167 compare(launcher.state, data.launcherLocked ? "visible": "");
2125 compare(launcherPanel.highlightIndex, -2);
2168 compare(launcherPanel.highlightIndex, -2);
2126− compare(ApplicationManager.focusedApplicationId, "unity8-dash");
2127
2169
2128 // Use Super + Tab Tab to cycle to the first entry in the launcher
2170 // Use Super + Tab Tab to cycle to the first entry in the launcher
2129 keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2171 keyPress(Qt.Key_Super_L, Qt.MetaModifier);
@@ -2253,7 +2295,7 @@ Rectangle {
2253
2295
2254 compare(launcher.lockedVisible, true);
2296 compare(launcher.lockedVisible, true);
2255
2297
2256− app.surfaceList.get(0).state = Mir.FullscreenState;
2298+ app.surfaceList.get(0).requestState(Mir.FullscreenState);
2257
2299
2258 tryCompare(launcher, "lockedVisible", false);
2300 tryCompare(launcher, "lockedVisible", false);
2259 }
2301 }
@@ -2384,13 +2426,13 @@ Rectangle {
2384 var gallerySurfaceId = topLevelSurfaceList.nextId;
2426 var gallerySurfaceId = topLevelSurfaceList.nextId;
2385 var galleryApp = ApplicationManager.startApplication("gallery-app");
2427 var galleryApp = ApplicationManager.startApplication("gallery-app");
2386 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2428 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2387− compare(ApplicationManager.focusedApplicationId, "gallery-app");
2429+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app");
2388
2430
2389 dragLauncherIntoView();
2431 dragLauncherIntoView();
2390 tap(findChild(shell, "buttonShowDashHome"));
2432 tap(findChild(shell, "buttonShowDashHome"));
2391
2433
2392 tryCompare(broadcastHomeSpy, "count", 1);
2434 tryCompare(broadcastHomeSpy, "count", 1);
2393− compare(ApplicationManager.focusedApplicationId, "gallery-app"); // confirm we didn't focus dash
2435+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app"); // confirm we didn't raise dash
2394
2436
2395 var coverPage = findChild(shell, "coverPage");
2437 var coverPage = findChild(shell, "coverPage");
2396 tryCompare(coverPage, "showProgress", 0);
2438 tryCompare(coverPage, "showProgress", 0);
@@ -2435,12 +2477,12 @@ Rectangle {
2435 tryCompare(appSurface, "keymap", "sk");
2477 tryCompare(appSurface, "keymap", "sk");
2436
2478
2437 // try to create a prompt surface, verify it also has the same keymap
2479 // try to create a prompt surface, verify it also has the same keymap
2438− app.promptSurfaceList.createSurface();
2480+ app.createPromptSurface();
2439 var promptSurface = app.promptSurfaceList.get(0);
2481 var promptSurface = app.promptSurfaceList.get(0);
2440 verify(promptSurface);
2482 verify(promptSurface);
2441 tryCompare(appSurface, "keymap", promptSurface.keymap);
2483 tryCompare(appSurface, "keymap", promptSurface.keymap);
2442 // ... and that the controller's surface keymap is also the same
2484 // ... and that the controller's surface keymap is also the same
2443− tryCompare(MirFocusController.focusedSurface, "keymap", "sk");
2485+ tryCompare(topLevelSurfaceList.focusedWindow.surface, "keymap", "sk");
2444 app.promptSurfaceList.get(0).close();
2486 app.promptSurfaceList.get(0).close();
2445
2487
2446 // switch to next keymap, should go to "cz+qwerty"
2488 // switch to next keymap, should go to "cz+qwerty"
@@ -2452,7 +2494,7 @@ Rectangle {
2452 tryCompare(appSurface, "keymap", "fr");
2494 tryCompare(appSurface, "keymap", "fr");
2453
2495
2454 // go to e.g. desktop stage
2496 // go to e.g. desktop stage
2455− loadShell("desktop");
2497+ shellLoader.state = "desktop";
2456 shell.usageScenario = "desktop";
2498 shell.usageScenario = "desktop";
2457 waitForRendering(shell);
2499 waitForRendering(shell);
2458
2500
@@ -2500,11 +2542,13 @@ Rectangle {
2500 var appContainer = findChild(shell, "appContainer");
2542 var appContainer = findChild(shell, "appContainer");
2501 var appDelegate = findChild(appContainer, "appDelegate_" + appSurfaceId);
2543 var appDelegate = findChild(appContainer, "appDelegate_" + appSurfaceId);
2502 verify(appDelegate);
2544 verify(appDelegate);
2503− appDelegate.maximize();
2545+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
2546+ mouseClick(maximizeButton);
2547+
2504 tryCompare(appDelegate, "state", "maximized");
2548 tryCompare(appDelegate, "state", "maximized");
2505
2549
2506 mouseDrag(panel, panel.width/3, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
2550 mouseDrag(panel, panel.width/3, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
2507− tryCompare(appDelegate, "state", "normal");
2551+ tryCompare(appDelegate, "state", "restored");
2508 }
2552 }
2509
2553
2510 function test_fullShellModeHasNoInitialGreeter() {
2554 function test_fullShellModeHasNoInitialGreeter() {
@@ -2558,7 +2602,6 @@ Rectangle {
2558 return [
2602 return [
2559 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2603 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2560 {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2604 {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2561− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2562 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2605 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2563 ]
2606 ]
2564 }
2607 }
@@ -2568,26 +2611,28 @@ Rectangle {
2568 shell.usageScenario = "desktop";
2611 shell.usageScenario = "desktop";
2569 waitForRendering(shell);
2612 waitForRendering(shell);
2570 swipeAwayGreeter();
2613 swipeAwayGreeter();
2614+ ensureInputMethodSurface();
2571 shell.oskEnabled = data.oskEnabled;
2615 shell.oskEnabled = data.oskEnabled;
2572
2616
2573− var oldOSKState = SurfaceManager.inputMethodSurface.state;
2617+ var oldOSKState = topLevelSurfaceList.inputMethodSurface.state;
2574− SurfaceManager.inputMethodSurface.state = Mir.RestoredState;
2618+ topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
2575 var appRepeater = findChild(shell, "appRepeater");
2619 var appRepeater = findChild(shell, "appRepeater");
2576 var dashAppDelegate = appRepeater.itemAt(0);
2620 var dashAppDelegate = appRepeater.itemAt(0);
2577 verify(dashAppDelegate);
2621 verify(dashAppDelegate);
2622+
2578− dashAppDelegate.requestedHeight = data.windowHeight;
2623+ dashAppDelegate.windowedHeight = data.windowHeight;
2579− dashAppDelegate.requestedY = data.windowY;
2624+ dashAppDelegate.windowedY = data.windowY;
2580− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2625+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2581 var initialY = dashAppDelegate.y;
2626 var initialY = dashAppDelegate.y;
2582 print("intial", initialY, "panel", PanelState.panelHeight);
2627 print("intial", initialY, "panel", PanelState.panelHeight);
2583 verify(initialY > PanelState.panelHeight);
2628 verify(initialY > PanelState.panelHeight);
2584
2629
2585− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2630+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2586 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2631 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2587
2632
2588− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2633+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2589 tryCompare(dashAppDelegate, "y", initialY);
2634 tryCompare(dashAppDelegate, "y", initialY);
2590− SurfaceManager.inputMethodSurface.state = oldOSKState;
2635+ topLevelSurfaceList.inputMethodSurface.requestState(oldOSKState);
2591 }
2636 }
2592
2637
2593 function test_cursorHidingWithFullscreenApp() {
2638 function test_cursorHidingWithFullscreenApp() {
tests/qmltests/tst_ShellWithPin.qml+18−9
@@ -142,7 +142,7 @@ Item {
142 }
142 }
143 }
143 }
144
144
145− UT.UnityTestCase {
145+ UT.StageTestCase {
146 id: testCase
146 id: testCase
147 name: "ShellWithPin"
147 name: "ShellWithPin"
148 when: windowShown
148 when: windowShown
@@ -161,11 +161,20 @@ Item {
161 var panel = findChild(launcher, "launcherPanel");
161 var panel = findChild(launcher, "launcherPanel");
162 verify(!!panel);
162 verify(!!panel);
163 panel.dismissTimer = fakeDismissTimer;
163 panel.dismissTimer = fakeDismissTimer;
164+
165+ // from StageTestCase
166+ stage = findChild(shell, "stage");
167+ topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
168+ verify(topLevelSurfaceList);
169+
170+ startApplication("unity8-dash");
164 }
171 }
165
172
166 function cleanup() {
173 function cleanup() {
167 tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
174 tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
168
175
176+ topLevelSurfaceList = null;
177+
169 shellLoader.itemDestroyed = false
178 shellLoader.itemDestroyed = false
170
179
171 shellLoader.active = false
180 shellLoader.active = false
@@ -301,13 +310,15 @@ Item {
301 }
310 }
302
311
303 function test_emergencyCallCrash() {
312 function test_emergencyCallCrash() {
313+ var dialerSurfaceId = topLevelSurfaceList.nextId;
304 var greeter = findChild(shell, "greeter");
314 var greeter = findChild(shell, "greeter");
305 var emergencyButton = findChild(greeter, "emergencyCallLabel");
315 var emergencyButton = findChild(greeter, "emergencyCallLabel");
306 tap(emergencyButton)
316 tap(emergencyButton)
307−
317+ tryCompare(topLevelSurfaceList, "count", 2);
318+ waitUntilAppWindowIsFullyLoaded(dialerSurfaceId);
308
319
309 tryCompare(greeter, "shown", false);
320 tryCompare(greeter, "shown", false);
310− killApps() // kill dialer-app, as if it crashed
321+ ApplicationManager.stopApplication("dialer-app"); // kill dialer-app, as if it crashed
311 tryCompare(greeter, "shown", true);
322 tryCompare(greeter, "shown", true);
312 tryCompare(findChild(greeter, "lockscreen"), "shown", true);
323 tryCompare(findChild(greeter, "lockscreen"), "shown", true);
313 tryCompare(findChild(greeter, "coverPage"), "shown", false);
324 tryCompare(findChild(greeter, "coverPage"), "shown", false);
@@ -319,7 +330,7 @@ Item {
319 tap(emergencyButton)
330 tap(emergencyButton)
320
331
321 tryCompare(greeter, "shown", false);
332 tryCompare(greeter, "shown", false);
322− ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
333+ startApplication("gallery-app");
323 tryCompare(greeter, "shown", true);
334 tryCompare(greeter, "shown", true);
324 }
335 }
325
336
@@ -406,7 +417,7 @@ Item {
406 compare(stage.usageScenario, "phone");
417 compare(stage.usageScenario, "phone");
407
418
408 // And when we kill the app, we go back to locked tablet mode
419 // And when we kill the app, we go back to locked tablet mode
409− killApps()
420+ ApplicationManager.stopApplication("dialer-app");
410 var greeter = findChild(shell, "greeter")
421 var greeter = findChild(shell, "greeter")
411 tryCompare(greeter, "fullyShown", true)
422 tryCompare(greeter, "fullyShown", true)
412 compare(stage.usageScenario, "tablet");
423 compare(stage.usageScenario, "tablet");
@@ -457,7 +468,7 @@ Item {
457 tryCompare(greeter, "shown", false);
468 tryCompare(greeter, "shown", false);
458 tryCompare(LightDM.Greeter, "active", false);
469 tryCompare(LightDM.Greeter, "active", false);
459
470
460− ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
471+ startApplication("dialer-app");
461 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
472 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
462 callManager.foregroundCall = phoneCall;
473 callManager.foregroundCall = phoneCall;
463
474
@@ -476,9 +487,7 @@ Item {
476 function test_focusRequestedHidesCoverPage() {
487 function test_focusRequestedHidesCoverPage() {
477 showGreeter();
488 showGreeter();
478
489
479− var app = ApplicationManager.startApplication("gallery-app");
490+ startApplication("gallery-app");
480− // wait until the app is fully loaded (ie, real surface replaces splash screen)
481− tryCompareFunction(function() { return app.session !== null && app.surfaceList.count > 0 }, true);
482
491
483 // New app hides coverPage?
492 // New app hides coverPage?
484 var greeter = findChild(shell, "greeter");
493 var greeter = findChild(shell, "greeter");
tests/utils/modules/Unity/Test/StageTestCase.qmlnew file+68
@@ -0,0 +1,68 @@
1+/*
2+ * Copyright 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+import Unity.Application 0.1
19+import Ubuntu.Components 1.3
20+
21+UnityTestCase {
22+ // set from outside
23+ property Item stage
24+ property QtObject topLevelSurfaceList: null
25+
26+ /*
27+ Wait until the ApplicationWindow for the given surface id (from TopLevelWindowModel) is fully loaded
28+ (ie, the real surface has replaced the splash screen)
29+ */
30+ function waitUntilAppWindowIsFullyLoaded(surfaceId) {
31+ var appDelegate = findChild(stage, "appDelegate_" + surfaceId);
32+ verify(appDelegate);
33+ var appWindow = findChild(appDelegate, "appWindow");
34+ verify(appWindow);
35+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
36+ verify(appWindowStates);
37+ tryCompare(appWindowStates, "state", "surface");
38+ waitUntilTransitionsEnd(appWindowStates);
39+ }
40+
41+ /*
42+ Returns the appDelegate of the first surface created by the app with the specified appId
43+ */
44+ function startApplication(appId) {
45+ try {
46+ var app = ApplicationManager.findApplication(appId);
47+ if (app) {
48+ for (var i = 0; i < topLevelSurfaceList.count; i++) {
49+ if (topLevelSurfaceList.applicationAt(i).appId === appId) {
50+ var appRepeater = findChild(stage, "appRepeater");
51+ verify(appRepeater);
52+ return appRepeater.itemAt(i);
53+ }
54+ }
55+ }
56+
57+ var surfaceId = topLevelSurfaceList.nextId;
58+ app = ApplicationManager.startApplication(appId);
59+ verify(app);
60+ waitUntilAppWindowIsFullyLoaded(surfaceId);
61+ compare(app.surfaceList.count, 1);
62+
63+ return findChild(stage, "appDelegate_" + surfaceId);
64+ } catch(err) {
65+ throw new Error("startApplication("+appId+") called from line " + util.callerLine(1) + " failed!");
66+ }
67+ }
68+}
tests/utils/modules/Unity/Test/UnityTestCase.qml+4−5
@@ -638,18 +638,17 @@ TestCase {
638 }
638 }
639
639
640 /*
640 /*
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
642 */
642 */
643 function killApps() {
643 function killApps() {
644− while (ApplicationManager.count > 1) {
644+ while (ApplicationManager.count > 0) {
645− var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
646− var application = ApplicationManager.get(appIndex);
645+ var application = ApplicationManager.get(0);
647 ApplicationManager.stopApplication(application.appId);
646 ApplicationManager.stopApplication(application.appId);
648 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
647 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
649 // They won't be gone until those surface items are destroyed.
648 // They won't be gone until those surface items are destroyed.
650 tryCompareFunction(function() { return application.surfaceList.count }, 0);
649 tryCompareFunction(function() { return application.surfaceList.count }, 0);
651 tryCompare(application, "state", ApplicationInfo.Stopped);
650 tryCompare(application, "state", ApplicationInfo.Stopped);
652 }
651 }
653− compare(ApplicationManager.count, 1);
652+ compare(ApplicationManager.count, 0);
654 }
653 }
655}
654}
tests/utils/modules/Unity/Test/qmldir+2−1
@@ -3,4 +3,5 @@ plugin UnityTestQml
3typeinfo Test.qmltypes
3typeinfo Test.qmltypes
4
4
5UnityTestCase 0.1 UnityTestCase.qml
5UnityTestCase 0.1 UnityTestCase.qml
6+StageTestCase 0.1 StageTestCase.qml
6−MouseTouchEmulationCheckbox 0.1 MouseTouchEmulationCheckbox.qml
7+MouseTouchEmulationCheckbox 0.1 MouseTouchEmulationCheckbox.qml