on xenial help sign in
Let the model deal with some window management decisions
f1dcc66Daniel d'Andrada
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
debian/control+3−3
@@ -38,7 +38,7 @@ Build-Depends: cmake,
38 libubuntugestures5-private-dev (>= 1.3.2030),
38 libubuntugestures5-private-dev (>= 1.3.2030),
39 libudev-dev,
39 libudev-dev,
40 libudm-common-dev,
40 libudm-common-dev,
41− libunity-api-dev (>= 7.120),
41+ libunity-api-dev (>= 8.0),
42 libusermetricsoutput1-dev,
42 libusermetricsoutput1-dev,
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44 libx11-dev[!arm64 !armhf],
44 libx11-dev[!arm64 !armhf],
@@ -160,7 +160,7 @@ Depends: qml-module-qtquick-layouts,
160 qttranslations5-l10n,
160 qttranslations5-l10n,
161 ubuntu-thumbnailer-impl-0,
161 ubuntu-thumbnailer-impl-0,
162 ubuntu-wallpapers,
162 ubuntu-wallpapers,
163− unity-application-impl-22,
163+ unity-application-impl-23,
164 unity-notifications-impl-3,
164 unity-notifications-impl-3,
165 unity-plugin-scopes | unity-scopes-impl,
165 unity-plugin-scopes | unity-scopes-impl,
166 unity-scopes-impl-12,
166 unity-scopes-impl-12,
@@ -206,7 +206,7 @@ Pre-Depends: ${misc:Pre-Depends},
206Depends: ${misc:Depends},
206Depends: ${misc:Depends},
207 ${shlibs:Depends},
207 ${shlibs:Depends},
208Provides: unity-application-impl,
208Provides: unity-application-impl,
209− unity-application-impl-22,
209+ unity-application-impl-23,
210Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
210Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
211Description: Fake environment for running Unity 8 shell
211Description: Fake environment for running Unity 8 shell
212 Provides fake implementations of some QML modules used by Unity 8 shell
212 Provides fake implementations of some QML modules used by Unity 8 shell
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+644
@@ -0,0 +1,644 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "TopLevelWindowModel.h"
18+
19+// unity-api
20+#include <unity/shell/application/ApplicationInfoInterface.h>
21+#include <unity/shell/application/ApplicationManagerInterface.h>
22+#include <unity/shell/application/MirSurfaceInterface.h>
23+#include <unity/shell/application/MirSurfaceListInterface.h>
24+#include <unity/shell/application/SurfaceManagerInterface.h>
25+
26+// Qt
27+#include <QGuiApplication>
28+#include <QDebug>
29+
30+// local
31+#include "Window.h"
32+
33+Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34+
35+#define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
36+#define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
37+
38+namespace unityapi = unity::shell::application;
39+
40+TopLevelWindowModel::TopLevelWindowModel()
41+{
42+}
43+
44+void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
45+{
46+ if (m_applicationManager == value) {
47+ return;
48+ }
49+
50+ DEBUG_MSG << "(" << value << ")";
51+
52+ Q_ASSERT(m_modelState == IdleState);
53+ m_modelState = ResettingState;
54+
55+ beginResetModel();
56+
57+ if (m_applicationManager) {
58+ m_windowModel.clear();
59+ disconnect(m_applicationManager, 0, this, 0);
60+ }
61+
62+ m_applicationManager = value;
63+
64+ if (m_applicationManager) {
65+ connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
66+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
67+ for (int i = first; i <= last; ++i) {
68+ auto application = m_applicationManager->get(i);
69+ addApplication(application);
70+ }
71+ });
72+
73+ connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
74+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
75+ for (int i = first; i <= last; ++i) {
76+ auto application = m_applicationManager->get(i);
77+ removeApplication(application);
78+ }
79+ });
80+
81+ for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82+ auto application = m_applicationManager->get(i);
83+ addApplication(application);
84+ }
85+ }
86+
87+ endResetModel();
88+ m_modelState = IdleState;
89+}
90+
91+void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *surfaceManager)
92+{
93+ if (surfaceManager == m_surfaceManager) {
94+ return;
95+ }
96+
97+ DEBUG_MSG << "(" << surfaceManager << ")";
98+
99+ if (m_surfaceManager) {
100+ disconnect(m_surfaceManager, 0, this, 0);
101+ }
102+
103+ m_surfaceManager = surfaceManager;
104+
105+ if (m_surfaceManager) {
106+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
107+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
110+ }
111+
112+ Q_EMIT surfaceManagerChanged(m_surfaceManager);
113+}
114+
115+void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
116+{
117+ DEBUG_MSG << "(" << application->appId() << ")";
118+
119+ if (application->state() != unityapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
120+ prependPlaceholder(application);
121+ }
122+}
123+
124+void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *application)
125+{
126+ DEBUG_MSG << "(" << application->appId() << ")";
127+
128+ Q_ASSERT(m_modelState == IdleState);
129+
130+ int i = 0;
131+ while (i < m_windowModel.count()) {
132+ if (m_windowModel.at(i).application == application) {
133+ removeAt(i);
134+ } else {
135+ ++i;
136+ }
137+ }
138+}
139+
140+void TopLevelWindowModel::prependPlaceholder(unityapi::ApplicationInfoInterface *application)
141+{
142+ INFO_MSG << "(" << application->appId() << ")";
143+
144+ prependSurfaceHelper(nullptr, application);
145+}
146+
147+void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
148+{
149+ Q_ASSERT(surface != nullptr);
150+
151+ bool filledPlaceholder = false;
152+ for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
153+ ModelEntry &entry = m_windowModel[i];
154+ if (entry.application == application && entry.window->surface() == nullptr) {
155+ entry.window->setSurface(surface);
156+ connectSurface(surface);
157+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface
158+ << ", filling out placeholder. after: " << toString();
159+ filledPlaceholder = true;
160+ }
161+ }
162+
163+ if (!filledPlaceholder) {
164+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
165+ prependSurfaceHelper(surface, application);
166+ }
167+}
168+
169+void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
170+{
171+ if (m_modelState == IdleState) {
172+ m_modelState = InsertingState;
173+ beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
174+ } else {
175+ Q_ASSERT(m_modelState == ResettingState);
176+ // No point in signaling anything if we're resetting the whole model
177+ }
178+
179+ int id = generateId();
180+ Window *window = new Window(id, this);
181+ if (surface) {
182+ window->setSurface(surface);
183+ }
184+ m_windowModel.prepend(ModelEntry(window, application));
185+ if (surface) {
186+ connectSurface(surface);
187+ }
188+
189+ connectWindow(window);
190+
191+ if (m_modelState == InsertingState) {
192+ endInsertRows();
193+ Q_EMIT countChanged();
194+ Q_EMIT listChanged();
195+ m_modelState = IdleState;
196+ }
197+
198+ if (!surface) {
199+ // focus the newly added window. miral can't help with that as it doesn't know about it.
200+ window->setFocused(true);
201+ if (m_focusedWindow && m_focusedWindow->surface()) {
202+ m_surfaceManager->activate(nullptr);
203+ }
204+ }
205+
206+ INFO_MSG << " after " << toString();
207+}
208+
209+void TopLevelWindowModel::connectWindow(Window *window)
210+{
211+ connect(window, &Window::focusRequested, this, [this, window]() {
212+ if (!window->surface()) {
213+ activateEmptyWindow(window);
214+ }
215+ });
216+
217+ connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
218+ if (window->surface()) {
219+ // Condense changes to the focused window
220+ // eg: Do focusedWindow=A to focusedWindow=B instead of
221+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
222+ m_focusedWindowChanged = true;
223+ if (focused) {
224+ Q_ASSERT(m_newlyFocusedWindow == nullptr);
225+ m_newlyFocusedWindow = window;
226+ }
227+ }
228+ });
229+
230+ connect(window, &Window::closeRequested, this, [this, window]() {
231+ if (!window->surface()) {
232+ int index = indexForId(window->id());
233+ Q_ASSERT(index >= 0);
234+ m_windowModel[index].application->close();
235+ }
236+ });
237+
238+ connect(window, &Window::emptyWindowActivated, this, [this, window]() {
239+ activateEmptyWindow(window);
240+ });
241+}
242+
243+void TopLevelWindowModel::activateEmptyWindow(Window *window)
244+{
245+ Q_ASSERT(!window->surface());
246+
247+ // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
248+ // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
249+
250+ window->setFocused(true);
251+ raiseId(window->id());
252+ Window *previousWindow = m_focusedWindow;
253+ setFocusedWindow(window);
254+ if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
255+ m_surfaceManager->activate(nullptr);
256+ }
257+}
258+
259+void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
260+{
261+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
262+ if (!live) {
263+ onSurfaceDied(surface);
264+ }
265+ });
266+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
267+}
268+
269+void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
270+{
271+ if (surface->type() == Mir::InputMethodType) {
272+ removeInputMethodWindow();
273+ return;
274+ }
275+
276+ int i = indexOf(surface);
277+ if (i == -1) {
278+ return;
279+ }
280+
281+ auto application = m_windowModel[i].application;
282+
283+ // can't be starting if it already has a surface
284+ Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
285+
286+ if (application->state() == unityapi::ApplicationInfoInterface::Running) {
287+ m_windowModel[i].removeOnceSurfaceDestroyed = true;
288+ } else {
289+ // assume it got killed by the out-of-memory daemon.
290+ //
291+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
292+ // in its place.
293+ m_windowModel[i].removeOnceSurfaceDestroyed = false;
294+ }
295+}
296+
297+void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
298+{
299+ int i = indexOf(surface);
300+ if (i == -1) {
301+ return;
302+ }
303+
304+ if (m_windowModel[i].removeOnceSurfaceDestroyed) {
305+ removeAt(i);
306+ } else {
307+ auto window = m_windowModel[i].window;
308+ window->setSurface(nullptr);
309+ window->setFocused(false);
310+ INFO_MSG << " Removed surface from entry. After: " << toString();
311+ }
312+}
313+
314+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
315+{
316+ DEBUG_MSG << "(" << surface << ")";
317+ if (surface->type() == Mir::InputMethodType) {
318+ int id = generateId();
319+ Window *qmlWindow = new Window(id, this);
320+ connectWindow(qmlWindow);
321+ qmlWindow->setSurface(surface);
322+ setInputMethodWindow(qmlWindow);
323+ } else {
324+ auto application = m_applicationManager->findApplicationWithSurface(surface);
325+ if (application) {
326+ prependSurface(surface, application);
327+ } else {
328+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
329+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
330+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
331+ int id = generateId();
332+ Window *promptWindow = new Window(id, this);
333+ connectWindow(promptWindow);
334+ promptWindow->setSurface(surface);
335+ connect(surface, &QObject::destroyed, promptWindow, [=](){
336+ promptWindow->setSurface(nullptr);
337+ promptWindow->deleteLater();
338+ });
339+ }
340+ }
341+ // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
342+}
343+
344+void TopLevelWindowModel::removeAt(int index)
345+{
346+ if (m_modelState == IdleState) {
347+ beginRemoveRows(QModelIndex(), index, index);
348+ m_modelState = RemovingState;
349+ } else {
350+ Q_ASSERT(m_modelState == ResettingState);
351+ // No point in signaling anything if we're resetting the whole model
352+ }
353+
354+ auto window = m_windowModel[index].window;
355+
356+ window->setSurface(nullptr);
357+ window->setFocused(false);
358+
359+ m_windowModel.removeAt(index);
360+
361+ if (m_modelState == RemovingState) {
362+ endRemoveRows();
363+ Q_EMIT countChanged();
364+ Q_EMIT listChanged();
365+ m_modelState = IdleState;
366+ }
367+
368+ disconnect(window, 0, this, 0);
369+ if (m_focusedWindow == window) {
370+ setFocusedWindow(nullptr);
371+ }
372+ delete window;
373+
374+ INFO_MSG << " after " << toString();
375+}
376+
377+void TopLevelWindowModel::setInputMethodWindow(Window *window)
378+{
379+ if (m_inputMethodWindow) {
380+ qWarning("Multiple Input Method Surfaces created, removing the old one!");
381+ delete m_inputMethodWindow;
382+ }
383+ m_inputMethodWindow = window;
384+ Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
385+}
386+
387+void TopLevelWindowModel::removeInputMethodWindow()
388+{
389+ if (m_inputMethodWindow) {
390+ delete m_inputMethodWindow;
391+ m_inputMethodWindow = nullptr;
392+ Q_EMIT inputMethodSurfaceChanged(nullptr);
393+ }
394+}
395+
396+void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
397+{
398+ DEBUG_MSG << "(" << surfaces << ")";
399+ const int raiseCount = surfaces.size();
400+ for (int i = 0; i < raiseCount; i++) {
401+ int fromIndex = findIndexOf(surfaces[i]);
402+ if (fromIndex != -1) {
403+ move(fromIndex, 0);
404+ }
405+ }
406+}
407+
408+int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
409+{
410+ return m_windowModel.count();
411+}
412+
413+QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
414+{
415+ if (index.row() < 0 || index.row() >= m_windowModel.size())
416+ return QVariant();
417+
418+ if (role == WindowRole) {
419+ Window *window = m_windowModel.at(index.row()).window;
420+ return QVariant::fromValue(window);
421+ } else if (role == ApplicationRole) {
422+ return QVariant::fromValue(m_windowModel.at(index.row()).application);
423+ } else {
424+ return QVariant();
425+ }
426+}
427+
428+int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
429+{
430+ for (int i=0; i<m_windowModel.count(); i++) {
431+ if (m_windowModel[i].window->surface() == surface) {
432+ return i;
433+ }
434+ }
435+ return -1;
436+}
437+
438+int TopLevelWindowModel::generateId()
439+{
440+ int id = m_nextId;
441+ m_nextId = nextFreeId(nextId(id), id);
442+ return id;
443+}
444+
445+int TopLevelWindowModel::nextId(int id) const
446+{
447+ if (id == m_maxId) {
448+ return id = 1;
449+ } else {
450+ return id + 1;
451+ }
452+}
453+
454+int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
455+{
456+ int firstCandidateId = candidateId;
457+
458+ while (indexForId(candidateId) != -1 || candidateId == latestId) {
459+ candidateId = nextId(candidateId);
460+
461+ if (candidateId == firstCandidateId) {
462+ qFatal("TopLevelWindowModel: run out of window ids.");
463+ }
464+ }
465+
466+ return candidateId;
467+}
468+
469+QString TopLevelWindowModel::toString()
470+{
471+ QString str;
472+ for (int i = 0; i < m_windowModel.count(); ++i) {
473+ auto item = m_windowModel.at(i);
474+
475+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
476+ .arg(QString::number(i),
477+ item.application->appId(),
478+ QString::number((qintptr)item.window->surface(), 16),
479+ QString::number(item.window->id()));
480+
481+ if (i > 0) {
482+ str.append(",");
483+ }
484+ str.append(itemStr);
485+ }
486+ return str;
487+}
488+
489+int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
490+{
491+ for (int i = 0; i < m_windowModel.count(); ++i) {
492+ if (m_windowModel.at(i).window->surface() == surface) {
493+ return i;
494+ }
495+ }
496+ return -1;
497+}
498+
499+int TopLevelWindowModel::indexForId(int id) const
500+{
501+ for (int i = 0; i < m_windowModel.count(); ++i) {
502+ if (m_windowModel[i].window->id() == id) {
503+ return i;
504+ }
505+ }
506+ return -1;
507+}
508+
509+Window *TopLevelWindowModel::windowAt(int index) const
510+{
511+ if (index >=0 && index < m_windowModel.count()) {
512+ return m_windowModel[index].window;
513+ } else {
514+ return nullptr;
515+ }
516+}
517+
518+unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
519+{
520+ if (index >=0 && index < m_windowModel.count()) {
521+ return m_windowModel[index].window->surface();
522+ } else {
523+ return nullptr;
524+ }
525+}
526+
527+unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
528+{
529+ if (index >=0 && index < m_windowModel.count()) {
530+ return m_windowModel[index].application;
531+ } else {
532+ return nullptr;
533+ }
534+}
535+
536+int TopLevelWindowModel::idAt(int index) const
537+{
538+ if (index >=0 && index < m_windowModel.count()) {
539+ return m_windowModel[index].window->id();
540+ } else {
541+ return 0;
542+ }
543+}
544+
545+void TopLevelWindowModel::raiseId(int id)
546+{
547+ if (m_modelState == IdleState) {
548+ DEBUG_MSG << "(id=" << id << ") - do it now.";
549+ doRaiseId(id);
550+ } else {
551+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
552+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
553+ // if we perform yet another model change straight away.
554+ //
555+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
556+ // the index is definitely within bounds.
557+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
558+ }
559+}
560+
561+void TopLevelWindowModel::doRaiseId(int id)
562+{
563+ int fromIndex = indexForId(id);
564+ if (fromIndex != -1) {
565+ auto surface = m_windowModel[fromIndex].window->surface();
566+ if (surface) {
567+ m_surfaceManager->raise(surface);
568+ } else {
569+ // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
570+ // miral can do about it.
571+ move(fromIndex, 0);
572+ }
573+ }
574+}
575+
576+void TopLevelWindowModel::setFocusedWindow(Window *window)
577+{
578+ if (window != m_focusedWindow) {
579+ INFO_MSG << "(" << window << ")";
580+
581+ Window* previousWindow = m_focusedWindow;
582+
583+ m_focusedWindow = window;
584+ Q_EMIT focusedWindowChanged(m_focusedWindow);
585+
586+ if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
587+ // do it ourselves. miral doesn't know about this window
588+ previousWindow->setFocused(false);
589+ }
590+ }
591+}
592+
593+unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
594+{
595+ return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
596+}
597+
598+Window* TopLevelWindowModel::focusedWindow() const
599+{
600+ return m_focusedWindow;
601+}
602+
603+void TopLevelWindowModel::move(int from, int to)
604+{
605+ if (from == to) return;
606+ DEBUG_MSG << " from=" << from << " to=" << to;
607+
608+ if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
609+ QModelIndex parent;
610+ /* When moving an item down, the destination index needs to be incremented
611+ by one, as explained in the documentation:
612+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
613+
614+ Q_ASSERT(m_modelState == IdleState);
615+ m_modelState = MovingState;
616+
617+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
618+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
619+ const auto &window = m_windowModel.takeAt(from);
620+ m_windowModel.insert(to, window);
621+#else
622+ m_windowModel.move(from, to);
623+#endif
624+ endMoveRows();
625+
626+ Q_EMIT listChanged();
627+ m_modelState = IdleState;
628+
629+ INFO_MSG << " after " << toString();
630+ }
631+}
632+void TopLevelWindowModel::onModificationsStarted()
633+{
634+}
635+
636+void TopLevelWindowModel::onModificationsEnded()
637+{
638+ if (m_focusedWindowChanged) {
639+ setFocusedWindow(m_newlyFocusedWindow);
640+ }
641+ // reset
642+ m_focusedWindowChanged = false;
643+ m_newlyFocusedWindow = nullptr;
644+}
plugins/WindowManager/TopLevelWindowModel.hnew file+258
@@ -0,0 +1,258 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TOPLEVELWINDOWMODEL_H
18+#define TOPLEVELWINDOWMODEL_H
19+
20+#include <QAbstractListModel>
21+#include <QLoggingCategory>
22+
23+Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
24+
25+class Window;
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class ApplicationInfoInterface;
31+ class ApplicationManagerInterface;
32+ class MirSurfaceInterface;
33+ class SurfaceManagerInterface;
34+ }
35+ }
36+}
37+
38+/**
39+ * @brief A model of top-level surfaces
40+ *
41+ * It's an abstraction of top-level application windows.
42+ *
43+ * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
44+ * still starting up. A shell should then display a splash screen or saved screenshot of the application
45+ * until its surface comes up.
46+ *
47+ * As applications can have multiple surfaces and you can also have entries without surfaces at all,
48+ * the only way to unambiguously refer to an entry in this model is through its id.
49+ */
50+class TopLevelWindowModel : public QAbstractListModel
51+{
52+ Q_OBJECT
53+
54+ /**
55+ * @brief Number of top-level surfaces in this model
56+ *
57+ * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
58+ */
59+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
60+
61+ /**
62+ * @brief The input method surface, if any
63+ *
64+ * The surface of a onscreen keyboard (akak "virtual keyboard") would be kept here and not in the model itself.
65+ */
66+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
67+
68+ /**
69+ * @brief The currently focused window, if any
70+ */
71+ Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
72+
73+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
74+ READ surfaceManager
75+ WRITE setSurfaceManager
76+ NOTIFY surfaceManagerChanged)
77+
78+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
79+ READ applicationManager
80+ WRITE setApplicationManager
81+ NOTIFY applicationManagerChanged)
82+
83+ /**
84+ The id to be used on the next entry created
85+ Useful for tests
86+ */
87+ Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
88+
89+public:
90+ /**
91+ * @brief The Roles supported by the model
92+ *
93+ * WindowRole - A Window.
94+ * ApplicationRole - An ApplicationInfoInterface
95+ */
96+ enum Roles {
97+ WindowRole = Qt::UserRole,
98+ ApplicationRole = Qt::UserRole + 1,
99+ };
100+
101+ TopLevelWindowModel();
102+
103+ // From QAbstractItemModel
104+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
105+ QVariant data(const QModelIndex& index, int role) const override;
106+ QHash<int, QByteArray> roleNames() const override {
107+ QHash<int, QByteArray> roleNames { {WindowRole, "window"},
108+ {ApplicationRole, "application"} };
109+ return roleNames;
110+ }
111+
112+ // Own API
113+
114+ unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
115+ Window* focusedWindow() const;
116+
117+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
118+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
119+
120+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
121+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
122+
123+ int nextId() const { return m_nextId; }
124+
125+public:
126+ /**
127+ * @brief Returns the surface at the given index
128+ *
129+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
130+ * and drawn into a surface.
131+ *
132+ * Same as windowAt(index).surface()
133+ */
134+ Q_INVOKABLE unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
135+
136+ /**
137+ * @brief Returns the window at the given index
138+ *
139+ * Will always be valid
140+ */
141+ Q_INVOKABLE Window *windowAt(int index) const;
142+
143+ /**
144+ * @brief Returns the application at the given index
145+ */
146+ Q_INVOKABLE unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
147+
148+ /**
149+ * @brief Returns the unique id of the element at the given index
150+ */
151+ Q_INVOKABLE int idAt(int index) const;
152+
153+ /**
154+ * @brief Returns the index where the row with the given id is located
155+ *
156+ * Returns -1 if there's no row with the given id.
157+ */
158+ Q_INVOKABLE int indexForId(int id) const;
159+
160+ /**
161+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
162+ */
163+ Q_INVOKABLE void raiseId(int id);
164+
165+Q_SIGNALS:
166+ void countChanged();
167+ void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
168+ void focusedWindowChanged(Window *focusedWindow);
169+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
170+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
171+
172+ /**
173+ * @brief Emitted when the list changes
174+ *
175+ * Emitted when model gains an element, loses an element or when elements exchange positions.
176+ */
177+ void listChanged();
178+
179+ void nextIdChanged();
180+
181+private Q_SLOTS:
182+ void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
183+ void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
184+
185+ void onModificationsStarted();
186+ void onModificationsEnded();
187+
188+private:
189+ void doRaiseId(int id);
190+ int generateId();
191+ int nextFreeId(int candidateId, const int latestId);
192+ int nextId(int id) const;
193+ QString toString();
194+ int indexOf(unity::shell::application::MirSurfaceInterface *surface);
195+
196+ void setInputMethodWindow(Window *window);
197+ void setFocusedWindow(Window *window);
198+ void removeInputMethodWindow();
199+ int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
200+ void removeAt(int index);
201+
202+ void addApplication(unity::shell::application::ApplicationInfoInterface *application);
203+ void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
204+
205+ void prependPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
206+ void prependSurface(unity::shell::application::MirSurfaceInterface *surface,
207+ unity::shell::application::ApplicationInfoInterface *application);
208+ void prependSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
209+ unity::shell::application::ApplicationInfoInterface *application);
210+
211+ void connectWindow(Window *window);
212+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
213+
214+ void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
215+ void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
216+
217+ void move(int from, int to);
218+
219+ void activateEmptyWindow(Window *window);
220+
221+ struct ModelEntry {
222+ ModelEntry() {}
223+ ModelEntry(Window *window,
224+ unity::shell::application::ApplicationInfoInterface *application)
225+ : window(window), application(application) {}
226+ Window *window{nullptr};
227+ unity::shell::application::ApplicationInfoInterface *application{nullptr};
228+ bool removeOnceSurfaceDestroyed{false};
229+ };
230+
231+ QVector<ModelEntry> m_windowModel;
232+ Window* m_inputMethodWindow{nullptr};
233+ Window* m_focusedWindow{nullptr};
234+
235+ int m_nextId{1};
236+ // Just something big enough that we don't risk running out of unused id numbers.
237+ // Not sure if QML int type supports something close to std::numeric_limits<int>::max() and
238+ // there's no reason to try out its limits.
239+ static const int m_maxId{1000000};
240+
241+ unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
242+ unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
243+
244+ enum ModelState {
245+ IdleState,
246+ InsertingState,
247+ RemovingState,
248+ MovingState,
249+ ResettingState
250+ };
251+ ModelState m_modelState{IdleState};
252+
253+ // Valid between modificationsStarted and modificationsEnded
254+ bool m_focusedWindowChanged{false};
255+ Window *m_newlyFocusedWindow{nullptr};
256+};
257+
258+#endif // TOPLEVELWINDOWMODEL_H
plugins/WindowManager/Window.cppnew file+234
@@ -0,0 +1,234 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Window.h"
18+
19+// unity-api
20+#include <unity/shell/application/MirSurfaceInterface.h>
21+
22+#include <QQmlEngine>
23+
24+namespace unityapi = unity::shell::application;
25+
26+Q_LOGGING_CATEGORY(UNITY_WINDOW, "unity.window", QtWarningMsg)
27+
28+#define DEBUG_MSG qCDebug(UNITY_WINDOW).nospace() << qPrintable(toString()) << "::" << __func__
29+
30+Window::Window(int id, QObject *parent)
31+ : QObject(parent)
32+ , m_id(id)
33+{
34+ DEBUG_MSG << "()";
35+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
36+}
37+
38+Window::~Window()
39+{
40+ DEBUG_MSG << "()";
41+}
42+
43+QPoint Window::position() const
44+{
45+ return m_position;
46+}
47+
48+QPoint Window::requestedPosition() const
49+{
50+ return m_requestedPosition;
51+}
52+
53+void Window::setRequestedPosition(const QPoint &value)
54+{
55+ m_positionRequested = true;
56+ if (value != m_requestedPosition) {
57+ m_requestedPosition = value;
58+ Q_EMIT requestedPositionChanged(m_requestedPosition);
59+ if (m_surface) {
60+ m_surface->setRequestedPosition(value);
61+ } else {
62+ // fake-miral: always comply
63+ m_position = m_requestedPosition;
64+ Q_EMIT positionChanged(m_position);
65+ }
66+ }
67+}
68+
69+Mir::State Window::state() const
70+{
71+ return m_state;
72+}
73+
74+bool Window::focused() const
75+{
76+ return m_focused;
77+}
78+
79+bool Window::confinesMousePointer() const
80+{
81+ if (m_surface) {
82+ return m_surface->confinesMousePointer();
83+ } else {
84+ return false;
85+ }
86+}
87+
88+int Window::id() const
89+{
90+ return m_id;
91+}
92+
93+unityapi::MirSurfaceInterface* Window::surface() const
94+{
95+ return m_surface;
96+}
97+
98+void Window::requestState(Mir::State state)
99+{
100+ m_stateRequested = true;
101+ if (m_surface) {
102+ m_surface->requestState(state);
103+ } else if (m_state != state) {
104+ m_state = state;
105+ Q_EMIT stateChanged(m_state);
106+ }
107+}
108+
109+void Window::close()
110+{
111+ if (m_surface) {
112+ m_surface->close();
113+ } else {
114+ Q_EMIT closeRequested();
115+ }
116+}
117+
118+void Window::activate()
119+{
120+ if (m_surface) {
121+ m_surface->activate();
122+ } else {
123+ Q_EMIT emptyWindowActivated();
124+ }
125+}
126+
127+void Window::setSurface(unityapi::MirSurfaceInterface *surface)
128+{
129+ DEBUG_MSG << "(" << surface << ")";
130+ if (m_surface) {
131+ disconnect(m_surface, 0, this, 0);
132+ }
133+
134+ m_surface = surface;
135+
136+ if (m_surface) {
137+ connect(surface, &unityapi::MirSurfaceInterface::focusRequested, this, [this]() {
138+ Q_EMIT focusRequested();
139+ });
140+
141+ connect(surface, &unityapi::MirSurfaceInterface::closeRequested, this, &Window::closeRequested);
142+
143+ connect(surface, &unityapi::MirSurfaceInterface::positionChanged, this, [this]() {
144+ updatePosition();
145+ });
146+
147+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [this]() {
148+ updateState();
149+ });
150+
151+ connect(surface, &unityapi::MirSurfaceInterface::focusedChanged, this, [this]() {
152+ updateFocused();
153+ });
154+
155+ // bring it up to speed
156+ if (m_positionRequested) {
157+ m_surface->setRequestedPosition(m_requestedPosition);
158+ }
159+ if (m_stateRequested) {
160+ m_surface->requestState(m_state);
161+ }
162+
163+ // and sync with surface
164+ updatePosition();
165+ updateState();
166+ updateFocused();
167+ }
168+
169+ Q_EMIT surfaceChanged(surface);
170+}
171+
172+void Window::updatePosition()
173+{
174+ if (m_surface->position() != m_position) {
175+ m_position = m_surface->position();
176+ Q_EMIT positionChanged(m_position);
177+ }
178+}
179+
180+void Window::updateState()
181+{
182+ if (m_surface->state() != m_state) {
183+ m_state = m_surface->state();
184+ Q_EMIT stateChanged(m_state);
185+ }
186+}
187+
188+void Window::updateFocused()
189+{
190+ if (m_surface->focused() != m_focused) {
191+ m_focused = m_surface->focused();
192+ Q_EMIT focusedChanged(m_focused);
193+ }
194+}
195+
196+void Window::setFocused(bool value)
197+{
198+ if (value != m_focused) {
199+ DEBUG_MSG << "(" << value << ")";
200+ m_focused = value;
201+ Q_EMIT focusedChanged(m_focused);
202+ // when we have a surface we get focus changes from updateFocused() instead
203+ Q_ASSERT(!m_surface);
204+ }
205+}
206+
207+QString Window::toString() const
208+{
209+ if (surface()) {
210+ return QString("Window[0x%1, id=%2, MirSurface[0x%3,\"%4\"]]").arg(
211+ QString::number((quintptr)this, 16),
212+ QString::number(id()),
213+ QString::number((quintptr)surface(), 16),
214+ surface()->name());
215+ } else {
216+ return QString("Window[0x%1, id=%2, null]").arg(
217+ QString::number((quintptr)this, 0, 16),
218+ QString::number(id()));
219+ }
220+}
221+
222+QDebug operator<<(QDebug dbg, const Window *window)
223+{
224+ QDebugStateSaver saver(dbg);
225+ dbg.nospace();
226+
227+ if (window) {
228+ dbg << qPrintable(window->toString());
229+ } else {
230+ dbg << (void*)(window);
231+ }
232+
233+ return dbg;
234+}
plugins/WindowManager/Window.hnew file+161
@@ -0,0 +1,161 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_WINDOW_H
18+#define UNITY_WINDOW_H
19+
20+#include <QLoggingCategory>
21+#include <QObject>
22+#include <QPoint>
23+
24+// Unity API
25+#include <unity/shell/application/Mir.h>
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class MirSurfaceInterface;
31+ }
32+ }
33+}
34+
35+
36+Q_DECLARE_LOGGING_CATEGORY(UNITY_WINDOW)
37+
38+/**
39+ @brief A slightly higher concept than MirSurface
40+
41+ A Window exists before its MirSurface gets created (for splashscreen purposes)
42+ and might also hang around after the backing surface is gone (In case the application
43+ was killed to free up memory, as it should still remain in the window list since the user
44+ did not explicitly close it).
45+ */
46+class Window : public QObject
47+{
48+ Q_OBJECT
49+
50+ /**
51+ * @brief Position of the current surface buffer, in pixels.
52+ */
53+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
54+
55+ /**
56+ * @brief Requested position of the current surface buffer, in pixels.
57+ */
58+ Q_PROPERTY(QPoint requestedPosition READ requestedPosition WRITE setRequestedPosition NOTIFY requestedPositionChanged)
59+
60+ /**
61+ * @brief State of the surface
62+ */
63+ Q_PROPERTY(Mir::State state READ state NOTIFY stateChanged)
64+
65+ /**
66+ * @brief Whether the surface is focused
67+ *
68+ * It will be true if this surface is MirFocusControllerInterface::focusedSurface
69+ */
70+ Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged)
71+
72+ /**
73+ * @brief Whether the surface wants to confine the mouse pointer within its boundaries
74+ *
75+ * If true, the surface doesn't want the mouse pointer to leave its boundaries while it's focused.
76+ */
77+ Q_PROPERTY(bool confinesMousePointer READ confinesMousePointer NOTIFY confinesMousePointerChanged)
78+
79+ /**
80+ * @brief A unique identifier for this window.
81+ * Useful for telling windows apart in a list model as they get moved around
82+ */
83+ Q_PROPERTY(int id READ id CONSTANT)
84+
85+ /**
86+ * @brief Surface backing up this window
87+ * It might be null if a surface hasn't been created yet (application is starting up) or if
88+ * the corresponding application has been killed (but can still get restarted to continue from
89+ * where it left)
90+ */
91+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
92+
93+public:
94+ Window(int id, QObject *parent = nullptr);
95+ virtual ~Window();
96+ QPoint position() const;
97+ QPoint requestedPosition() const;
98+ void setRequestedPosition(const QPoint &);
99+ Mir::State state() const;
100+ bool focused() const;
101+ bool confinesMousePointer() const;
102+ int id() const;
103+ unity::shell::application::MirSurfaceInterface* surface() const;
104+
105+ void setSurface(unity::shell::application::MirSurfaceInterface *surface);
106+ void setFocused(bool value);
107+
108+ QString toString() const;
109+
110+public Q_SLOTS:
111+ /**
112+ * @brief Requests a change to the specified state
113+ */
114+ void requestState(Mir::State state);
115+
116+ /**
117+ * @brief Sends a close request
118+ *
119+ */
120+ void close();
121+
122+ /**
123+ * @brief Focuses and raises the window
124+ */
125+ void activate();
126+
127+Q_SIGNALS:
128+ void closeRequested();
129+ void emptyWindowActivated();
130+
131+ void positionChanged(QPoint position);
132+ void requestedPositionChanged(QPoint position);
133+ void stateChanged(Mir::State value);
134+ void focusedChanged(bool value);
135+ void confinesMousePointerChanged(bool value);
136+ void surfaceChanged(unity::shell::application::MirSurfaceInterface *surface);
137+
138+ /**
139+ * @brief Emitted when focus for this window is requested by an external party
140+ */
141+ void focusRequested();
142+
143+private:
144+ void updatePosition();
145+ void updateState();
146+ void updateFocused();
147+
148+ QPoint m_position;
149+ QPoint m_requestedPosition;
150+ bool m_positionRequested{false};
151+ bool m_focused{false};
152+ int m_id;
153+ Mir::State m_state{Mir::RestoredState};
154+ bool m_stateRequested{false};
155+ unity::shell::application::MirSurfaceInterface *m_surface{nullptr};
156+};
157+
158+QDebug operator<<(QDebug dbg, const Window *window);
159+
160+Q_DECLARE_METATYPE(Window*)
161+#endif // UNITY_WINDOW_H
plugins/WindowManager/WindowManagerPlugin.cpp+5−2
@@ -16,13 +16,16 @@
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}
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 {
@@ -318,6 +323,7 @@ StyledItem {
318 InputMethod {
323 InputMethod {
319 id: inputMethod
324 id: inputMethod
320 objectName: "inputMethod"
325 objectName: "inputMethod"
326+ surface: topLevelSurfaceList.inputMethodSurface
321 anchors {
327 anchors {
322 fill: parent
328 fill: parent
323 topMargin: panel.panelHeight
329 topMargin: panel.panelHeight
@@ -485,8 +491,8 @@ StyledItem {
485 greeterShown: greeter.shown
491 greeterShown: greeter.shown
486 }
492 }
487
493
488− readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
494+ readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
489− ? MirFocusController.focusedSurface.state === Mir.FullscreenState
495+ ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
490 : false
496 : false
491 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
497 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
492 || greeter.hasLockedApp
498 || greeter.hasLockedApp
@@ -786,7 +792,9 @@ StyledItem {
786 }
792 }
787
793
788 // non-visual objects
794 // non-visual objects
789− KeymapSwitcher {}
795+ KeymapSwitcher {
796+ focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
797+ }
790 BrightnessControl {}
798 BrightnessControl {}
791
799
792 Rectangle {
800 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+117−110
@@ -119,8 +119,7 @@ FocusScope {
119 }
119 }
120 }
120 }
121
121
122− property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
122+ property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
123− priv.focusedAppDelegate.surface.confinesMousePointer ?
124 priv.focusedAppDelegate.clientAreaItem : null;
123 priv.focusedAppDelegate.clientAreaItem : null;
125
124
126 signal itemSnapshotRequested(Item item)
125 signal itemSnapshotRequested(Item item)
@@ -186,29 +185,34 @@ FocusScope {
186 GlobalShortcut {
185 GlobalShortcut {
187 id: maximizeWindowShortcut
186 id: maximizeWindowShortcut
188 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
187 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
189− onTriggered: priv.focusedAppDelegate.maximize()
188+ onTriggered: priv.focusedAppDelegate.requestMaximize()
190 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
189 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
191 }
190 }
192
191
193 GlobalShortcut {
192 GlobalShortcut {
194 id: maximizeWindowLeftShortcut
193 id: maximizeWindowLeftShortcut
195 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
194 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
196− onTriggered: priv.focusedAppDelegate.maximizeLeft()
195+ onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
197 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
196 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
198 }
197 }
199
198
200 GlobalShortcut {
199 GlobalShortcut {
201 id: maximizeWindowRightShortcut
200 id: maximizeWindowRightShortcut
202 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
201 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
203− onTriggered: priv.focusedAppDelegate.maximizeRight()
202+ onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
204 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
203 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
205 }
204 }
206
205
207 GlobalShortcut {
206 GlobalShortcut {
208 id: minimizeRestoreShortcut
207 id: minimizeRestoreShortcut
209 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
208 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
209+ onTriggered: {
210− onTriggered: priv.focusedAppDelegate.anyMaximized
210+ if (priv.focusedAppDelegate.anyMaximized) {
211− ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
211+ priv.focusedAppDelegate.requestRestore();
212+ } else {
213+ priv.focusedAppDelegate.requestMinimize();
214+ }
215+ }
212 active: root.state == "windowed" && priv.focusedAppDelegate
216 active: root.state == "windowed" && priv.focusedAppDelegate
213 }
217 }
214
218
@@ -244,20 +248,10 @@ FocusScope {
244 }
248 }
245
249
246 function minimizeAllWindows() {
250 function minimizeAllWindows() {
247− for (var i = 0; i < appRepeater.count; i++) {
251+ for (var i = appRepeater.count - 1; i >= 0; i--) {
248− var appDelegate = appRepeater.itemAt(i);
249− if (appDelegate && !appDelegate.minimized) {
250− appDelegate.minimize();
251− }
252− }
253− }
254−
255− function focusNext() {
256− for (var i = 0; i < appRepeater.count; i++) {
257 var appDelegate = appRepeater.itemAt(i);
252 var appDelegate = appRepeater.itemAt(i);
258 if (appDelegate && !appDelegate.minimized) {
253 if (appDelegate && !appDelegate.minimized) {
259− appDelegate.focus = true;
254+ appDelegate.requestMinimize();
260− return;
261 }
255 }
262 }
256 }
263 }
257 }
@@ -358,8 +352,8 @@ FocusScope {
358 Connections {
352 Connections {
359 target: PanelState
353 target: PanelState
360 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
354 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
361− onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
355+ onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
362− onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
356+ onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
363 }
357 }
364
358
365 Binding {
359 Binding {
@@ -434,13 +428,6 @@ FocusScope {
434 }
428 }
435 }
429 }
436
430
437− Binding {
438− target: MirFocusController
439− property: "focusedSurface"
440− value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
441− when: !appRepeater.startingUp && root.parent
442− }
443−
444 states: [
431 states: [
445 State {
432 State {
446 name: "spread"; when: priv.goneToSpread
433 name: "spread"; when: priv.goneToSpread
@@ -633,7 +620,7 @@ FocusScope {
633
620
634 onShownChanged: {
621 onShownChanged: {
635 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
622 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
636− priv.mainStageDelegate.claimFocus();
623+ priv.mainStageDelegate.activate();
637 }
624 }
638 }
625 }
639
626
@@ -666,14 +653,23 @@ FocusScope {
666 }
653 }
667 }
654 }
668
655
669− TopLevelSurfaceRepeater {
656+ Repeater {
670 id: appRepeater
657 id: appRepeater
671 model: topLevelSurfaceList
658 model: topLevelSurfaceList
672 objectName: "appRepeater"
659 objectName: "appRepeater"
673
660
661+ function indexOf(delegateItem) {
662+ for (var i = 0; i < count; i++) {
663+ if (itemAt(i) === delegateItem) {
664+ return i;
665+ }
666+ }
667+ return -1;
668+ }
669+
674 delegate: FocusScope {
670 delegate: FocusScope {
675 id: appDelegate
671 id: appDelegate
676− objectName: "appDelegate_" + model.id
672+ objectName: "appDelegate_" + model.window.id
677 property int itemIndex: index // We need this from outside the repeater
673 property int itemIndex: index // We need this from outside the repeater
678 // z might be overriden in some cases by effects, but we need z ordering
674 // z might be overriden in some cases by effects, but we need z ordering
679 // to calculate occlusion detection
675 // to calculate occlusion detection
@@ -685,11 +681,11 @@ FocusScope {
685 }
681 }
686 z: normalZ
682 z: normalZ
687
683
688− // Normally we want x/y where we request it to be. Width/height of our delegate will
684+ // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
689 // match what the actual surface size is.
685 // match what the actual surface size is.
690 // Don't write to those, they will be set by states
686 // Don't write to those, they will be set by states
691− x: requestedX
687+ x: model.window.position.x - clientAreaItem.x
692− y: requestedY
688+ y: model.window.position.y - clientAreaItem.y
693 width: decoratedWindow.implicitWidth
689 width: decoratedWindow.implicitWidth
694 height: decoratedWindow.implicitHeight
690 height: decoratedWindow.implicitHeight
695
691
@@ -699,6 +695,12 @@ FocusScope {
699 property real requestedY: windowedY
695 property real requestedY: windowedY
700 property real requestedWidth: windowedWidth
696 property real requestedWidth: windowedWidth
701 property real requestedHeight: windowedHeight
697 property real requestedHeight: windowedHeight
698+ Binding {
699+ target: model.window; property: "requestedPosition"
700+ // miral doesn't know about our window decorations. So we have to deduct them
701+ value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
702+ appDelegate.requestedY + appDelegate.clientAreaItem.y)
703+ }
702
704
703 // In those are for windowed mode. Those values basically store the window's properties
705 // In those are for windowed mode. Those values basically store the window's properties
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.
706 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
@@ -767,7 +769,7 @@ FocusScope {
767 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
769 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
768
770
769 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
771 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
770− readonly property bool fullscreen: surface ? surface.state === Mir.FullscreenState : application.fullscreen
772+ readonly property bool fullscreen: window.state === Mir.FullscreenState
771
773
772 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
774 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
773 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
775 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
@@ -793,7 +795,9 @@ FocusScope {
793 priv.updateMainAndSideStageIndexes()
795 priv.updateMainAndSideStageIndexes()
794 }
796 }
795
797
796− readonly property var surface: model.surface
798+ readonly property var surface: model.window.surface
799+ readonly property var window: model.window
800+
797 readonly property alias resizeArea: resizeArea
801 readonly property alias resizeArea: resizeArea
798 readonly property alias focusedSurface: decoratedWindow.focusedSurface
802 readonly property alias focusedSurface: decoratedWindow.focusedSurface
799 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
803 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
@@ -802,6 +806,25 @@ FocusScope {
802 readonly property bool isDash: appId == "unity8-dash"
806 readonly property bool isDash: appId == "unity8-dash"
803 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
807 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
804
808
809+ function activate() {
810+ if (model.window.focused) {
811+ updateQmlFocusFromMirSurfaceFocus();
812+ } else {
813+ model.window.activate();
814+ }
815+ }
816+ function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
817+ function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
818+ function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
819+ function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
820+ function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
821+ function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
822+ function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
823+ function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
824+ function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
825+ function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
826+ function requestRestore() { model.window.requestState(Mir.RestoredState); }
827+
805 function claimFocus() {
828 function claimFocus() {
806 if (root.state == "spread") {
829 if (root.state == "spread") {
807 spreadItem.highlightedIndex = index
830 spreadItem.highlightedIndex = index
@@ -813,45 +836,60 @@ FocusScope {
813 }
836 }
814 priv.updateMainAndSideStageIndexes();
837 priv.updateMainAndSideStageIndexes();
815 }
838 }
816−
817 if (root.mode == "windowed") {
839 if (root.mode == "windowed") {
818 appDelegate.restore(true /* animated */, appDelegate.windowState);
840 appDelegate.restore(true /* animated */, appDelegate.windowState);
819− } else {
820− appDelegate.focus = true;
821 }
841 }
842+ appDelegate.focus = true;
822 }
843 }
823− Connections {
844+
824− target: model.surface
845+ function updateQmlFocusFromMirSurfaceFocus() {
825− onFocusRequested: {
846+ if (model.window.focused) {
826 claimFocus();
847 claimFocus();
848+ priv.focusedAppDelegate = appDelegate;
827 }
849 }
828 }
850 }
851+
829 Connections {
852 Connections {
830− target: model.application
853+ target: model.window
854+ onFocusedChanged: {
855+ updateQmlFocusFromMirSurfaceFocus();
856+ }
831 onFocusRequested: {
857 onFocusRequested: {
832− if (!model.surface) {
858+ appDelegate.activate();
833− // when an app has no surfaces, we assume there's only one entry representing it:
859+ }
834− // this delegate.
860+ onStateChanged: {
835− claimFocus();
861+ if (model.window.state === Mir.MinimizedState) {
836− } else {
862+ appDelegate.minimize();
837− // if the application has surfaces, focus request should be at surface-level.
863+ } else if (model.window.state === Mir.MaximizedState) {
864+ appDelegate.maximize();
865+ } else if (model.window.state === Mir.VertMaximizedState) {
866+ appDelegate.maximizeVertically();
867+ } else if (model.window.state === Mir.HorizMaximizedState) {
868+ appDelegate.maximizeHorizontally();
869+ } else if (model.window.state === Mir.MaximizedLeftState) {
870+ appDelegate.maximizeLeft();
871+ } else if (model.window.state === Mir.MaximizedRightState) {
872+ appDelegate.maximizeRight();
873+ } else if (model.window.state === Mir.MaximizedTopLeftState) {
874+ appDelegate.maximizeTopLeft();
875+ } else if (model.window.state === Mir.MaximizedTopRightState) {
876+ appDelegate.maximizeTopRight();
877+ } else if (model.window.state === Mir.MaximizedBottomLeftState) {
878+ appDelegate.maximizeBottomLeft();
879+ } else if (model.window.state === Mir.MaximizedBottomRightState) {
880+ appDelegate.maximizeBottomRight();
881+ } else if (model.window.state === Mir.RestoredState) {
882+ if (decoratedWindow.dragging) {
883+ appDelegate.restore(false, WindowStateStorage.WindowStateNormal);
884+ } else if (appDelegate.anyMaximized) {
885+ appDelegate.restoreFromMaximized()
886+ } else {
887+ appDelegate.restore();
888+ }
838 }
889 }
839 }
890 }
840 }
891 }
841
892
842− onFocusChanged: {
843− if (appRepeater.startingUp)
844− return;
845−
846− if (focus) {
847− topLevelSurfaceList.raiseId(model.id);
848− priv.focusedAppDelegate = appDelegate;
849− } else if (!focus && priv.focusedAppDelegate === appDelegate && root.state != "spread") {
850− priv.focusedAppDelegate = null;
851− // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
852− MirFocusController.focusedSurface = null;
853− }
854− }
855 Component.onCompleted: {
893 Component.onCompleted: {
856 if (application && application.rotatesWindowContents) {
894 if (application && application.rotatesWindowContents) {
857 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
895 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -865,16 +903,7 @@ FocusScope {
865 // Now load any saved state. This needs to happen *after* the cascading!
903 // Now load any saved state. This needs to happen *after* the cascading!
866 resizeArea.loadWindowState();
904 resizeArea.loadWindowState();
867
905
868− // NB: We're differentiating if this delegate was created in response to a new entry in the model
906+ updateQmlFocusFromMirSurfaceFocus();
869− // or if the Repeater is just populating itself with delegates to match the model it received.
870− if (!appRepeater.startingUp) {
871− // a top level window is always the focused one when it first appears, unfocusing
872− // any preexisting one
873− if (root.state == "spread") {
874− spreadItem.highlightedIndex = index;
875− }
876− claimFocus();
877− }
878
907
879 refreshStage();
908 refreshStage();
880 _constructing = false;
909 _constructing = false;
@@ -889,16 +918,6 @@ FocusScope {
889 priv.updateForegroundMaximizedApp();
918 priv.updateForegroundMaximizedApp();
890 }
919 }
891
920
892− if (focus) {
893− // focus some other window
894− for (var i = 0; i < appRepeater.count; i++) {
895− var appDelegate = appRepeater.itemAt(i);
896− if (appDelegate && !appDelegate.minimized && i != index) {
897− appDelegate.focus = true;
898− return;
899− }
900− }
901− }
902 }
921 }
903
922
904 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
923 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
@@ -919,7 +938,7 @@ FocusScope {
919 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
938 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
920
939
921 function close() {
940 function close() {
922− model.surface.close();
941+ model.window.close();
923 }
942 }
924
943
925 function maximize(animated) {
944 function maximize(animated) {
@@ -970,7 +989,6 @@ FocusScope {
970 animationsEnabled = (animated === undefined) || animated;
989 animationsEnabled = (animated === undefined) || animated;
971 windowState = state || WindowStateStorage.WindowStateRestored;
990 windowState = state || WindowStateStorage.WindowStateRestored;
972 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
991 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
973− focus = true;
974 }
992 }
975
993
976 function playFocusAnimation() {
994 function playFocusAnimation() {
@@ -984,7 +1002,7 @@ FocusScope {
984 rightEdgeFocusAnimation.start()
1002 rightEdgeFocusAnimation.start()
985 }
1003 }
986 } else if (state == "windowedRightEdge" || state == "windowed") {
1004 } else if (state == "windowedRightEdge" || state == "windowed") {
987− claimFocus();
1005+ activate();
988 } else {
1006 } else {
989 focusAnimation.start()
1007 focusAnimation.start()
990 }
1008 }
@@ -1022,10 +1040,10 @@ FocusScope {
1022 to: 1
1040 to: 1
1023 duration: UbuntuAnimation.SnapDuration
1041 duration: UbuntuAnimation.SnapDuration
1024 onStarted: {
1042 onStarted: {
1025− topLevelSurfaceList.raiseId(model.id);
1043+ topLevelSurfaceList.raiseId(model.window.id);
1026 }
1044 }
1027 onStopped: {
1045 onStopped: {
1028− appDelegate.claimFocus();
1046+ appDelegate.activate();
1029 }
1047 }
1030 }
1048 }
1031 ParallelAnimation {
1049 ParallelAnimation {
@@ -1035,7 +1053,7 @@ FocusScope {
1035 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1053 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1036 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1054 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1037 onStopped: {
1055 onStopped: {
1038− appDelegate.focus = true
1056+ appDelegate.activate();
1039 }
1057 }
1040 }
1058 }
1041 ParallelAnimation {
1059 ParallelAnimation {
@@ -1399,14 +1417,6 @@ FocusScope {
1399 SequentialAnimation {
1417 SequentialAnimation {
1400 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1418 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1401 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1419 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1402− ScriptAction {
1403− script: {
1404− if (appDelegate.minimized) {
1405− appDelegate.focus = false;
1406− priv.focusNext();
1407− }
1408− }
1409− }
1410 }
1420 }
1411 },
1421 },
1412 Transition {
1422 Transition {
@@ -1497,7 +1507,7 @@ FocusScope {
1497 visible: enabled
1507 visible: enabled
1498
1508
1499 onPressed: {
1509 onPressed: {
1500− appDelegate.focus = true;
1510+ appDelegate.activate();
1501 }
1511 }
1502
1512
1503 Component.onDestruction: {
1513 Component.onDestruction: {
@@ -1511,7 +1521,7 @@ FocusScope {
1511 anchors.left: appDelegate.left
1521 anchors.left: appDelegate.left
1512 anchors.top: appDelegate.top
1522 anchors.top: appDelegate.top
1513 application: model.application
1523 application: model.application
1514− surface: model.surface
1524+ surface: model.window.surface
1515 active: appDelegate.focus
1525 active: appDelegate.focus
1516 focus: true
1526 focus: true
1517 interactive: root.interactive
1527 interactive: root.interactive
@@ -1536,21 +1546,21 @@ FocusScope {
1536 onCloseClicked: { appDelegate.close(); }
1546 onCloseClicked: { appDelegate.close(); }
1537 onMaximizeClicked: {
1547 onMaximizeClicked: {
1538 if (appDelegate.canBeMaximized) {
1548 if (appDelegate.canBeMaximized) {
1539− appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
1549+ appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
1540 }
1550 }
1541 }
1551 }
1542 onMaximizeHorizontallyClicked: {
1552 onMaximizeHorizontallyClicked: {
1543 if (appDelegate.canBeMaximizedHorizontally) {
1553 if (appDelegate.canBeMaximizedHorizontally) {
1544− appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
1554+ appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
1545 }
1555 }
1546 }
1556 }
1547 onMaximizeVerticallyClicked: {
1557 onMaximizeVerticallyClicked: {
1548 if (appDelegate.canBeMaximizedVertically) {
1558 if (appDelegate.canBeMaximizedVertically) {
1549− appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
1559+ appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
1550 }
1560 }
1551 }
1561 }
1552− onMinimizeClicked: appDelegate.minimize()
1562+ onMinimizeClicked: { appDelegate.requestMinimize(); }
1553− onDecorationPressed: { appDelegate.focus = true; }
1563+ onDecorationPressed: { appDelegate.activate(); }
1554 onDecorationReleased: fakeRectangle.commit();
1564 onDecorationReleased: fakeRectangle.commit();
1555
1565
1556 property real angle: 0
1566 property real angle: 0
@@ -1597,12 +1607,12 @@ FocusScope {
1597 WindowedFullscreenPolicy {
1607 WindowedFullscreenPolicy {
1598 id: windowedFullscreenPolicy
1608 id: windowedFullscreenPolicy
1599 active: root.mode == "windowed"
1609 active: root.mode == "windowed"
1600− surface: model.surface
1610+ surface: model.window.surface
1601 }
1611 }
1602 StagedFullscreenPolicy {
1612 StagedFullscreenPolicy {
1603 id: stagedFullscreenPolicy
1613 id: stagedFullscreenPolicy
1604 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1614 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1605− surface: model.surface
1615+ surface: model.window.surface
1606 }
1616 }
1607
1617
1608 SpreadDelegateInputArea {
1618 SpreadDelegateInputArea {
@@ -1615,16 +1625,13 @@ FocusScope {
1615 onClicked: {
1625 onClicked: {
1616 spreadItem.highlightedIndex = index;
1626 spreadItem.highlightedIndex = index;
1617 if (distance == 0) {
1627 if (distance == 0) {
1628+ model.window.activate();
1618 priv.goneToSpread = false;
1629 priv.goneToSpread = false;
1619 }
1630 }
1620 }
1631 }
1621 onClose: {
1632 onClose: {
1622 priv.closingIndex = index
1633 priv.closingIndex = index
1623− if (model.surface) { // could be stopped by OOM
1634+ model.window.close();
1624− model.surface.close()
1625− } else if (model.application) {
1626− root.applicationManager.stopApplication(model.application.appId);
1627− }
1628 }
1635 }
1629 }
1636 }
1630
1637
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)
@@ -218,7 +261,7 @@ void ApplicationInfo::setFullscreen(bool value)
218{
261{
219 m_fullscreen = value;
262 m_fullscreen = value;
220 if (m_surfaceList->rowCount() > 0) {
263 if (m_surfaceList->rowCount() > 0) {
221− m_surfaceList->get(0)->setState(Mir::FullscreenState);
264+ m_surfaceList->get(0)->requestState(Mir::FullscreenState);
222 }
265 }
223}
266}
224
267
@@ -349,26 +392,6 @@ bool ApplicationInfo::focused() const
349 return someSurfaceHasFocus;
392 return someSurfaceHasFocus;
350}
393}
351
394
352−void ApplicationInfo::setFocused(bool value)
353−{
354− if (focused() == value) {
355− return;
356− }
357−
358− if (value) {
359− if (m_surfaceList->count() > 0) {
360− m_surfaceList->get(0)->requestFocus();
361− }
362− } else {
363− for (int i = 0; i < m_surfaceList->count(); ++i) {
364− MirSurface *surface = static_cast<MirSurface*>(m_surfaceList->get(i));
365− if (surface->focused()) {
366− surface->setFocused(false);
367− }
368− }
369− }
370−}
371−
372void ApplicationInfo::onSurfaceCountChanged()
395void ApplicationInfo::onSurfaceCountChanged()
373{
396{
374 if (m_surfaceList->count() == 0 && m_state == Running) {
397 if (m_surfaceList->count() == 0 && m_state == Running) {
@@ -381,6 +404,7 @@ void ApplicationInfo::requestFocus()
381 if (m_surfaceList->count() == 0) {
404 if (m_surfaceList->count() == 0) {
382 Q_EMIT focusRequested();
405 Q_EMIT focusRequested();
383 } else {
406 } else {
407+ auto surface = static_cast<MirSurface*>(m_surfaceList->get(0));
384− m_surfaceList->get(0)->requestFocus();
408+ surface->requestFocus();
385 }
409 }
386}
410}
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)
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) 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+80−93
@@ -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,7 +84,7 @@ 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)
@@ -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+25−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,32 @@ 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+public Q_SLOTS:
145+ ////
146+ // unity.shell.application.MirSurface
147+ void requestState(Mir::State) override;
148+
156Q_SIGNALS:
149Q_SIGNALS:
157 ////
150 ////
158 // API for tests
151 // API for tests
159 void widthChanged();
152 void widthChanged();
160 void heightChanged();
153 void heightChanged();
161 void slowToResizeChanged();
154 void slowToResizeChanged();
155+ void exposedChanged(bool exposed);
162
156
163 ////
157 ////
164 // internal mock stuff
158 // internal mock stuff
165 void screenshotUrlChanged(QUrl);
159 void screenshotUrlChanged(QUrl);
166 void activeFocusChanged(bool);
160 void activeFocusChanged(bool);
167− void raiseRequested();
168 void closeRequested();
161 void closeRequested();
162+ void stateRequested(Mir::State);
169
163
170protected:
164protected:
171 virtual void updateInputBoundsAfterResize();
165 virtual void updateInputBoundsAfterResize();
@@ -175,7 +169,7 @@ private Q_SLOTS:
175
169
176private:
170private:
177 void doResize(int width, int height);
171 void doResize(int width, int height);
178− void updateVisibility();
172+ void updateExposure();
179
173
180 const QString m_name;
174 const QString m_name;
181 const Mir::Type m_type;
175 const Mir::Type m_type;
@@ -185,7 +179,7 @@ private:
185 QUrl m_screenshotUrl;
179 QUrl m_screenshotUrl;
186 QUrl m_qmlFilePath;
180 QUrl m_qmlFilePath;
187 bool m_live;
181 bool m_live;
188− bool m_visible;
182+ bool m_focused;
189 bool m_activeFocus;
183 bool m_activeFocus;
190 int m_width;
184 int m_width;
191 int m_height;
185 int m_height;
@@ -210,10 +204,14 @@ private:
210 bool visible;
204 bool visible;
211 };
205 };
212 QHash<qintptr, View> m_views;
206 QHash<qintptr, View> m_views;
207+ bool m_exposed{false};
213
208
214 QTimer m_zombieTimer;
209 QTimer m_zombieTimer;
215
210
216 QRect m_inputBounds;
211 QRect m_inputBounds;
212+
213+ QPoint m_position;
214+ QPoint m_requestedPosition;
217};
215};
218
216
219#endif // MOCK_MIR_SURFACE_H
217#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+167−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,130 @@ 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+ QVector<MirSurfaceInterface*> surfaces;
168+ surfaces.append(surface);
169+ Q_EMIT surfacesRaised(surfaces);
170+}
171+
172+void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
173+{
174+ auto surface = static_cast<MirSurface*>(apiSurface);
175+
176+ if (surface == m_focusedSurface) {
177+ return;
178+ }
179+
180+ Q_ASSERT(!m_underModification);
181+
182+ DEBUG_MSG("("<<surface<<") started");
183+ Q_EMIT modificationsStarted();
184+ m_underModification = true;
185+ if (m_focusedSurface) {
186+ m_focusedSurface->setFocused(false);
187+ }
188+ if (surface) {
189+ if (surface->state() == Mir::HiddenState || surface->state() == Mir::MinimizedState) {
190+ surface->setState(Mir::RestoredState);
191+ }
192+ surface->setFocused(true);
193+ doRaise(surface);
194+ }
195+ m_focusedSurface = surface;
196+ m_underModification = false;
197+ Q_EMIT modificationsEnded();
198+ DEBUG_MSG("("<<surface<<") ended");
199+}
200+
201+void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
202+{
203+ Q_ASSERT(!m_underModification);
204+
205+ DEBUG_MSG("("<<surface<<","<<state<<") started");
206+ Q_EMIT modificationsStarted();
207+ m_underModification = true;
208+
209+ surface->setState(state);
210+
211+ if ((state == Mir::MinimizedState || state == Mir::HiddenState) && surface->focused()) {
212+ Q_ASSERT(m_focusedSurface == surface);
213+ surface->setFocused(false);
214+ m_focusedSurface = nullptr;
215+ focusFirstAvailableSurface();
216+ }
217+
218+ m_underModification = false;
219+ Q_EMIT modificationsEnded();
220+ DEBUG_MSG("("<<surface<<","<<state<<") ended");
221+}
222+
223+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
224+{
225+ m_surfaces.removeAll(surface);
226+ if (m_focusedSurface == surface) {
227+ m_focusedSurface = nullptr;
228+
229+ Q_EMIT modificationsStarted();
230+ m_underModification = true;
231+
232+ focusFirstAvailableSurface();
233+
234+ m_underModification = false;
235+ Q_EMIT modificationsEnded();
236+ }
237+}
238+
239+void SurfaceManager::focusFirstAvailableSurface()
240+{
241+ MirSurface *chosenSurface = nullptr;
242+ for (int i = 0; i < m_surfaces.count() && !chosenSurface; ++i) {
243+ auto *surface = m_surfaces[i];
244+ if (surface->state() != Mir::HiddenState && surface->state() != Mir::MinimizedState) {
245+ chosenSurface = surface;
246+ }
247+ }
248+
249+ if (!chosenSurface) {
250+ return;
251+ }
252+
253+ if (m_focusedSurface) {
254+ m_focusedSurface->setFocused(false);
255+ }
256+ if (chosenSurface) {
257+ chosenSurface->setFocused(true);
258+ }
259+ m_focusedSurface = chosenSurface;
260+}
261+
262+void SurfaceManager::createInputMethodSurface()
263+{
264+ if (!m_virtualKeyboard) {
265+ m_virtualKeyboard = new VirtualKeyboard;
266+ registerSurface(m_virtualKeyboard);
267+ Q_EMIT surfaceCreated(m_virtualKeyboard);
268+ }
269+}
tests/mocks/Unity/Application/SurfaceManager.h+28−10
@@ -19,15 +19,16 @@
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+2−31
@@ -29,24 +29,8 @@
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
@@ -55,19 +39,6 @@ QObject* mirSingleton(QQmlEngine*, QJSEngine*)
55 createUnityApplicationSharedSingletons();
39 createUnityApplicationSharedSingletons();
56 return MirMock::instance();
40 return MirMock::instance();
57}
41}
58−
59−QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
60−{
61− createUnityApplicationSharedSingletons();
62− return SurfaceManager::instance();
63−}
64−
65−QObject* mirFocusControllerSingleton(QQmlEngine*, QJSEngine*)
66−{
67− createUnityApplicationSharedSingletons();
68− return MirFocusController::instance();
69−}
70−
71} // anonymous namespace
42} // anonymous namespace
72
43
73void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
44void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -75,6 +46,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
75 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
46 qRegisterMetaType<ApplicationInfo*>("ApplicationInfo*");
76 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
47 qRegisterMetaType<unity::shell::application::MirSurfaceInterface*>("unity::shell::application::MirSurfaceInterface*");
77 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
48 qRegisterMetaType<unity::shell::application::MirSurfaceListInterface*>("unity::shell::application::MirSurfaceListInterface*");
49+ qRegisterMetaType<unity::shell::application::SurfaceManagerInterface*>("unity::shell::application::SurfaceManagerInterface*");
78 qRegisterMetaType<Mir::Type>("Mir::Type");
50 qRegisterMetaType<Mir::Type>("Mir::Type");
79 qRegisterMetaType<Mir::State>("Mir::State");
51 qRegisterMetaType<Mir::State>("Mir::State");
80
52
@@ -83,13 +55,12 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
83 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
55 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
84 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
56 qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
85 uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
57 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");
58 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
88 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
59 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
89
60
90 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
61 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
91 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
62 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
92− qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
63+ qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
93}
64}
94
65
95void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
66void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/plugins/Unity/Launcher/launchermodeltest.cpp+4
@@ -65,6 +65,7 @@ public:
65 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
65 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
66 int surfaceCount() const override { return m_surfaceCount; }
66 int surfaceCount() const override { return m_surfaceCount; }
67 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
67 void setSurfaceCount(int count) { m_surfaceCount = count; Q_EMIT surfaceCountChanged(count); }
68+ void close() override {}
68
69
69 // Methods used for mocking (not in the interface)
70 // Methods used for mocking (not in the interface)
70 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
71 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
@@ -98,6 +99,9 @@ public:
98 }
99 }
99 return nullptr;
100 return nullptr;
100 }
101 }
102+ unity::shell::application::ApplicationInfoInterface *findApplicationWithSurface(unity::shell::application::MirSurfaceInterface* surface) override {
103+ return nullptr;
104+ }
101 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
105 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
102 bool stopApplication(const QString &appId) override {
106 bool stopApplication(const QString &appId) override {
103 Q_FOREACH(MockApp* app, m_list) {
107 Q_FOREACH(MockApp* app, m_list) {
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+50−40
@@ -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;
@@ -170,8 +173,9 @@ Item {
170 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
173 tryCompare(dashApp, "state", ApplicationInfoInterface.Running);
171
174
172 tryCompare(topSurfaceList, "count", 1);
175 tryCompare(topSurfaceList, "count", 1);
173− tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) != null;}, true);
176+ tryCompareFunction(function(){return topSurfaceList.windowAt(0) != null;}, true);
174− compare(MirFocusController.focusedSurface, topSurfaceList.surfaceAt(0));
177+ topSurfaceList.windowAt(0).activate();
178+ tryCompare(topSurfaceList, "focusedWindow", topSurfaceList.windowAt(0));
175 }
179 }
176
180
177 function cleanup() {
181 function cleanup() {
@@ -284,13 +288,13 @@ Item {
284 verify(fromAppWindow);
288 verify(fromAppWindow);
285 tap(fromAppWindow);
289 tap(fromAppWindow);
286 compare(fromDelegate.surface.activeFocus, true);
290 compare(fromDelegate.surface.activeFocus, true);
287− compare(MirFocusController.focusedSurface, fromDelegate.surface);
291+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
288
292
289 var toAppWindow = findChild(toDelegate, "appWindow");
293 var toAppWindow = findChild(toDelegate, "appWindow");
290 verify(toAppWindow);
294 verify(toAppWindow);
291 tap(toAppWindow);
295 tap(toAppWindow);
292 compare(toDelegate.surface.activeFocus, true);
296 compare(toDelegate.surface.activeFocus, true);
293− compare(MirFocusController.focusedSurface, toDelegate.surface);
297+ compare(topSurfaceList.focusedWindow, toDelegate.window);
294 }
298 }
295
299
296 function test_clickingOnWindowChangesFocusedApp_data() {
300 function test_clickingOnWindowChangesFocusedApp_data() {
@@ -309,13 +313,13 @@ Item {
309 verify(fromAppWindow);
313 verify(fromAppWindow);
310 mouseClick(fromAppWindow);
314 mouseClick(fromAppWindow);
311 compare(fromDelegate.surface.activeFocus, true);
315 compare(fromDelegate.surface.activeFocus, true);
312− compare(MirFocusController.focusedSurface, fromDelegate.surface);
316+ compare(topSurfaceList.focusedWindow, fromDelegate.window);
313
317
314 var toAppWindow = findChild(toDelegate, "appWindow");
318 var toAppWindow = findChild(toDelegate, "appWindow");
315 verify(toAppWindow);
319 verify(toAppWindow);
316 mouseClick(toAppWindow);
320 mouseClick(toAppWindow);
317 compare(toDelegate.surface.activeFocus, true);
321 compare(toDelegate.surface.activeFocus, true);
318− compare(MirFocusController.focusedSurface, toDelegate.surface);
322+ compare(topSurfaceList.focusedWindow, toDelegate.window);
319 }
323 }
320
324
321 function test_tappingOnDecorationFocusesApplication_data() {
325 function test_tappingOnDecorationFocusesApplication_data() {
@@ -343,6 +347,13 @@ Item {
343 return findChild(appDelegate, "appWindowDecoration");
347 return findChild(appDelegate, "appWindowDecoration");
344 }
348 }
345
349
350+ function maximizeDelegate(appDelegate) {
351+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
352+ verify(maximizeButton);
353+ mouseClick(maximizeButton);
354+ tryCompare(appDelegate, "visuallyMaximized", true);
355+ }
356+
346 function test_tappingOnDecorationFocusesApplication(data) {
357 function test_tappingOnDecorationFocusesApplication(data) {
347 var appDelegates = [];
358 var appDelegates = [];
348 for (var i = 0; i < data.apps.length; i++) {
359 for (var i = 0; i < data.apps.length; i++) {
@@ -398,7 +409,7 @@ Item {
398 startApplication("camera-app");
409 startApplication("camera-app");
399
410
400 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
411 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
401− dialerDelegate.surface.requestFocus();
412+ dialerDelegate.surface.activate();
402 tryCompare(dialerDelegate, "focus", true);
413 tryCompare(dialerDelegate, "focus", true);
403
414
404 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
415 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
@@ -411,7 +422,7 @@ Item {
411 startApplication("camera-app");
422 startApplication("camera-app");
412
423
413 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
424 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
414− dialerDelegate.surface.requestFocus();
425+ dialerDelegate.surface.activate();
415 tryCompare(dialerDelegate, "focus", true);
426 tryCompare(dialerDelegate, "focus", true);
416
427
417 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
428 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
@@ -426,7 +437,7 @@ Item {
426 startApplication("camera-app");
437 startApplication("camera-app");
427
438
428 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
439 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
429− dialerDelegate.surface.requestFocus();
440+ dialerDelegate.surface.activate();
430 tryCompare(dialerDelegate, "focus", true);
441 tryCompare(dialerDelegate, "focus", true);
431
442
432 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
443 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
@@ -441,7 +452,7 @@ Item {
441 startApplication("camera-app");
452 startApplication("camera-app");
442
453
443 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
454 tryCompareFunction(function(){ return dialerDelegate.surface !== null; }, true);
444− dialerDelegate.surface.requestFocus();
455+ dialerDelegate.surface.activate();
445 tryCompare(dialerDelegate, "focus", true);
456 tryCompare(dialerDelegate, "focus", true);
446
457
447 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
458 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
@@ -512,11 +523,11 @@ Item {
512 apps.forEach(startApplication);
523 apps.forEach(startApplication);
513 verify(topSurfaceList.count == 3);
524 verify(topSurfaceList.count == 3);
514 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
525 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
515− tryCompare(MirFocusController, "focusedSurface", null); // verify no surface is focused
526+ tryCompare(topSurfaceList, "focusedWindow", null); // verify no window is focused
516
527
517 // now try pressing all 4 arrow keys + ctrl + meta
528 // 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!!!
529 keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
519− tryCompare(MirFocusController, "focusedSurface", null); // verify still no surface is focused
530+ tryCompare(topSurfaceList, "focusedWindow", null); // verify still no window is focused
520 }
531 }
521
532
522 function test_minimizeApplicationHidesSurface() {
533 function test_minimizeApplicationHidesSurface() {
@@ -527,9 +538,12 @@ Item {
527 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
538 var decoratedWindow = findDecoratedWindow(dashSurfaceId);
528 verify(decoratedWindow);
539 verify(decoratedWindow);
529
540
541+ var minimizeButton = findChild(decoratedWindow, "minimizeWindowButton");
542+ verify(minimizeButton);
543+
530− tryCompare(dashSurface, "visible", true);
544+ tryCompare(dashSurface, "exposed", true);
531− decoratedWindow.minimizeClicked();
545+ mouseClick(minimizeButton);
532− tryCompare(dashSurface, "visible", false);
546+ tryCompare(dashSurface, "exposed", false);
533 }
547 }
534
548
535 function test_maximizeApplicationHidesSurfacesBehindIt() {
549 function test_maximizeApplicationHidesSurfacesBehindIt() {
@@ -538,16 +552,16 @@ Item {
538 var gmailDelegate = startApplication("gmail-webapp");
552 var gmailDelegate = startApplication("gmail-webapp");
539
553
540 // maximize without raising
554 // maximize without raising
541− dialerDelegate.maximize();
555+ dialerDelegate.requestMaximize();
542 tryCompare(dialerDelegate, "visuallyMaximized", true);
556 tryCompare(dialerDelegate, "visuallyMaximized", true);
543
557
544− tryCompare(dashDelegate.surface, "visible", false);
558+ tryCompare(dashDelegate.surface, "exposed", false);
545− compare(gmailDelegate.surface.visible, true);
559+ compare(gmailDelegate.surface.exposed, true);
546
560
547 // restore without raising
561 // restore without raising
548− dialerDelegate.restoreFromMaximized();
562+ dialerDelegate.requestRestore();
549− compare(dashDelegate.surface.visible, true);
563+ compare(dashDelegate.surface.exposed, true);
550− compare(gmailDelegate.surface.visible, true);
564+ compare(gmailDelegate.surface.exposed, true);
551 }
565 }
552
566
553 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
567 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
@@ -580,26 +594,22 @@ Item {
580 tryCompare(dialerDelegate, "visuallyMaximized", true);
594 tryCompare(dialerDelegate, "visuallyMaximized", true);
581 tryCompare(gmailDelegate, "visuallyMaximized", true);
595 tryCompare(gmailDelegate, "visuallyMaximized", true);
582
596
583− tryCompare(dashApp.surfaceList.get(0), "visible", false);
597+ tryCompare(dashApp.surfaceList.get(0), "exposed", false);
584− tryCompare(dialerApp.surfaceList.get(0), "visible", false);
598+ tryCompare(dialerApp.surfaceList.get(0), "exposed", false);
585− tryCompare(mapApp.surfaceList.get(0), "visible", false);
599+ tryCompare(mapApp.surfaceList.get(0), "exposed", false);
586
600
587 ApplicationManager.stopApplication("gmail-webapp");
601 ApplicationManager.stopApplication("gmail-webapp");
588 wait(2000)
602 wait(2000)
589
603
590− tryCompare(mapApp.surfaceList.get(0), "visible", true);
604+ tryCompare(mapApp.surfaceList.get(0), "exposed", true);
591− tryCompare(dialerApp.surfaceList.get(0), "visible", true);
605+ tryCompare(dialerApp.surfaceList.get(0), "exposed", true);
592− tryCompare(dashApp.surfaceList.get(0), "visible", false); // still occluded by maximised dialer
606+ tryCompare(dashApp.surfaceList.get(0), "exposed", false); // still occluded by maximised dialer
593 }
607 }
594
608
595 function test_maximisedAppStaysVisibleWhenAppStarts() {
609 function test_maximisedAppStaysVisibleWhenAppStarts() {
596 var dashDelegate = startApplication("unity8-dash");
610 var dashDelegate = startApplication("unity8-dash");
597
611
598− // maximize
612+ maximizeDelegate(dashDelegate);
599− var dashMaximizeButton = findChild(dashDelegate, "maximizeWindowButton");
600− verify(dashMaximizeButton);
601− mouseClick(dashMaximizeButton);
602− tryCompare(dashDelegate, "visuallyMaximized", true);
603
613
604 var dialerDelegate = startApplication("dialer-app");
614 var dialerDelegate = startApplication("dialer-app");
605 verify(dialerDelegate);
615 verify(dialerDelegate);
@@ -621,25 +631,25 @@ Item {
621 tryCompare(facebookAppDelegate, "visible", true);
631 tryCompare(facebookAppDelegate, "visible", true);
622
632
623 // Maximize the topmost and make sure the other two are hidden
633 // Maximize the topmost and make sure the other two are hidden
624− facebookAppDelegate.maximize();
634+ maximizeDelegate(facebookAppDelegate);
625 tryCompare(dashAppDelegate, "visible", false);
635 tryCompare(dashAppDelegate, "visible", false);
626 tryCompare(dialerAppDelegate, "visible", false);
636 tryCompare(dialerAppDelegate, "visible", false);
627 tryCompare(facebookAppDelegate, "visible", true);
637 tryCompare(facebookAppDelegate, "visible", true);
628
638
629 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
639 // Bring dash to front. make sure dash and the maximized facebook are visible, the restored one behind is hidden
630− dashAppDelegate.focus = true;
640+ dashAppDelegate.activate();
631 tryCompare(dashAppDelegate, "visible", true);
641 tryCompare(dashAppDelegate, "visible", true);
632 tryCompare(dialerAppDelegate, "visible", false);
642 tryCompare(dialerAppDelegate, "visible", false);
633 tryCompare(facebookAppDelegate, "visible", true);
643 tryCompare(facebookAppDelegate, "visible", true);
634
644
635 // Now focus the dialer app. all 3 should be visible again
645 // Now focus the dialer app. all 3 should be visible again
636− dialerAppDelegate.focus = true;
646+ dialerAppDelegate.activate();
637 tryCompare(dashAppDelegate, "visible", true);
647 tryCompare(dashAppDelegate, "visible", true);
638 tryCompare(dialerAppDelegate, "visible", true);
648 tryCompare(dialerAppDelegate, "visible", true);
639 tryCompare(facebookAppDelegate, "visible", true);
649 tryCompare(facebookAppDelegate, "visible", true);
640
650
641 // Maximize the dialer app. The other 2 should hide
651 // Maximize the dialer app. The other 2 should hide
642− dialerAppDelegate.maximize();
652+ maximizeDelegate(dialerAppDelegate);
643 tryCompare(dashAppDelegate, "visible", false);
653 tryCompare(dashAppDelegate, "visible", false);
644 tryCompare(dialerAppDelegate, "visible", true);
654 tryCompare(dialerAppDelegate, "visible", true);
645 tryCompare(facebookAppDelegate, "visible", false);
655 tryCompare(facebookAppDelegate, "visible", false);
@@ -648,7 +658,7 @@ Item {
648 function test_dropShadow() {
658 function test_dropShadow() {
649 // start an app, maximize it
659 // start an app, maximize it
650 var facebookAppDelegate = startApplication("facebook-webapp");
660 var facebookAppDelegate = startApplication("facebook-webapp");
651− facebookAppDelegate.maximize();
661+ maximizeDelegate(facebookAppDelegate);
652
662
653 // verify the drop shadow is still not visible
663 // verify the drop shadow is still not visible
654 verify(PanelState.dropShadow == false);
664 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
@@ -505,7 +511,7 @@ Item {
505 function test_selectSuspendedAppWithoutSurface() {
511 function test_selectSuspendedAppWithoutSurface() {
506 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
512 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
507 var dashSurfaceId = topLevelSurfaceList.idAt(0);
513 var dashSurfaceId = topLevelSurfaceList.idAt(0);
508− var dashSurface = topLevelSurfaceList.surfaceAt(0);
514+ var dashWindow = topLevelSurfaceList.windowAt(0);
509
515
510 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
516 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
511 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
517 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -513,7 +519,7 @@ Item {
513
519
514 switchToSurface(dashSurfaceId);
520 switchToSurface(dashSurfaceId);
515
521
516− tryCompare(MirFocusController, "focusedSurface", dashSurface);
522+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
517 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
523 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
518
524
519 compare(webbrowserApp.surfaceList.count, 1);
525 compare(webbrowserApp.surfaceList.count, 1);
@@ -549,7 +555,7 @@ Item {
549 {
555 {
550 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
556 compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
551 var dashSurfaceId = topLevelSurfaceList.idAt(0);
557 var dashSurfaceId = topLevelSurfaceList.idAt(0);
552− var dashSurface = topLevelSurfaceList.surfaceAt(0);
558+ var dashWindow = topLevelSurfaceList.windowAt(0);
553
559
554 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
560 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
555 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
561 var webbrowserApp = ApplicationManager.startApplication("webbrowser-app");
@@ -557,7 +563,7 @@ Item {
557
563
558 switchToSurface(dashSurfaceId);
564 switchToSurface(dashSurfaceId);
559
565
560− tryCompare(MirFocusController, "focusedSurface", dashSurface);
566+ tryCompare(topLevelSurfaceList, "focusedWindow", dashWindow);
561 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
567 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
562
568
563 compare(webbrowserApp.surfaceList.count, 1);
569 compare(webbrowserApp.surfaceList.count, 1);
tests/qmltests/Stage/tst_TabletStage.qml+16−10
@@ -21,7 +21,7 @@ import Ubuntu.Components.ListItems 1.3 as ListItem
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/tst_Shell.qml+114−40
@@ -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 }
@@ -530,6 +594,10 @@ Rectangle {
530
594
531 topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
595 topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
532 verify(topLevelSurfaceList);
596 verify(topLevelSurfaceList);
597+
598+ // wait until unity8-dash is fully loaded
599+ tryCompare(topLevelSurfaceList, "count", 1);
600+ waitUntilAppWindowIsFullyLoaded(topLevelSurfaceList.idAt(0));
533 }
601 }
534
602
535 function loadDesktopShellWithApps() {
603 function loadDesktopShellWithApps() {
@@ -599,10 +667,14 @@ Rectangle {
599
667
600 GSettingsController.setLifecycleExemptAppids([]);
668 GSettingsController.setLifecycleExemptAppids([]);
601 GSettingsController.setPictureUri("");
669 GSettingsController.setPictureUri("");
670+ }
671+
672+ function ensureInputMethodSurface() {
673+ var surfaceManager = findInvisibleChild(shell, "surfaceManager");
674+ verify(surfaceManager);
675+ surfaceManager.createInputMethodSurface();
602
676
603− // there should be only unity8-dash window over there
677+ tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
604− tryCompare(ApplicationManager, "count", 1);
605− compare(ApplicationManager.get(0).appId, "unity8-dash");
606 }
678 }
607
679
608 function test_phoneLeftEdgeDrag_data() {
680 function test_phoneLeftEdgeDrag_data() {
@@ -949,22 +1021,24 @@ Rectangle {
949 function test_showInputMethod() {
1021 function test_showInputMethod() {
950 loadShell("phone");
1022 loadShell("phone");
951 swipeAwayGreeter();
1023 swipeAwayGreeter();
1024+ ensureInputMethodSurface();
1025+
952 var item = findChild(shell, "inputMethod");
1026 var item = findChild(shell, "inputMethod");
953− var surface = SurfaceManager.inputMethodSurface;
1027+ var surface = topLevelSurfaceList.inputMethodSurface;
954
1028
955− surface.setState(Mir.MinimizedState);
1029+ surface.requestState(Mir.MinimizedState);
956 tryCompare(item, "visible", false);
1030 tryCompare(item, "visible", false);
957
1031
958− surface.setState(Mir.RestoredState);
1032+ surface.requestState(Mir.RestoredState);
959 tryCompare(item, "visible", true);
1033 tryCompare(item, "visible", true);
960
1034
961− surface.setState(Mir.MinimizedState);
1035+ surface.requestState(Mir.MinimizedState);
962 tryCompare(item, "visible", false);
1036 tryCompare(item, "visible", false);
963
1037
964− surface.setState(Mir.MaximizedState);
1038+ surface.requestState(Mir.MaximizedState);
965 tryCompare(item, "visible", true);
1039 tryCompare(item, "visible", true);
966
1040
967− surface.setState(Mir.MinimizedState);
1041+ surface.requestState(Mir.MinimizedState);
968 tryCompare(item, "visible", false);
1042 tryCompare(item, "visible", false);
969 }
1043 }
970
1044
@@ -2267,7 +2341,6 @@ Rectangle {
2267 var firstAppInLauncher = LauncherModel.get(0).appId;
2341 var firstAppInLauncher = LauncherModel.get(0).appId;
2268 compare(launcher.state, data.launcherLocked ? "visible": "");
2342 compare(launcher.state, data.launcherLocked ? "visible": "");
2269 compare(launcherPanel.highlightIndex, -2);
2343 compare(launcherPanel.highlightIndex, -2);
2270− compare(ApplicationManager.focusedApplicationId, "unity8-dash");
2271
2344
2272 // Use Super + Tab Tab to cycle to the first entry in the launcher
2345 // Use Super + Tab Tab to cycle to the first entry in the launcher
2273 keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2346 keyPress(Qt.Key_Super_L, Qt.MetaModifier);
@@ -2397,7 +2470,7 @@ Rectangle {
2397
2470
2398 compare(launcher.lockedVisible, true);
2471 compare(launcher.lockedVisible, true);
2399
2472
2400− app.surfaceList.get(0).state = Mir.FullscreenState;
2473+ app.surfaceList.get(0).requestState(Mir.FullscreenState);
2401
2474
2402 tryCompare(launcher, "lockedVisible", false);
2475 tryCompare(launcher, "lockedVisible", false);
2403 }
2476 }
@@ -2528,13 +2601,13 @@ Rectangle {
2528 var gallerySurfaceId = topLevelSurfaceList.nextId;
2601 var gallerySurfaceId = topLevelSurfaceList.nextId;
2529 var galleryApp = ApplicationManager.startApplication("gallery-app");
2602 var galleryApp = ApplicationManager.startApplication("gallery-app");
2530 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2603 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2531− compare(ApplicationManager.focusedApplicationId, "gallery-app");
2604+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app");
2532
2605
2533 dragLauncherIntoView();
2606 dragLauncherIntoView();
2534 tap(findChild(shell, "buttonShowDashHome"));
2607 tap(findChild(shell, "buttonShowDashHome"));
2535
2608
2536 tryCompare(broadcastHomeSpy, "count", 1);
2609 tryCompare(broadcastHomeSpy, "count", 1);
2537− compare(ApplicationManager.focusedApplicationId, "gallery-app"); // confirm we didn't focus dash
2610+ compare(topLevelSurfaceList.applicationAt(0).appId, "gallery-app"); // confirm we didn't raise dash
2538
2611
2539 var coverPage = findChild(shell, "coverPage");
2612 var coverPage = findChild(shell, "coverPage");
2540 tryCompare(coverPage, "showProgress", 0);
2613 tryCompare(coverPage, "showProgress", 0);
@@ -2579,12 +2652,12 @@ Rectangle {
2579 tryCompare(appSurface, "keymap", "sk");
2652 tryCompare(appSurface, "keymap", "sk");
2580
2653
2581 // try to create a prompt surface, verify it also has the same keymap
2654 // try to create a prompt surface, verify it also has the same keymap
2582− app.promptSurfaceList.createSurface();
2655+ app.createPromptSurface();
2583 var promptSurface = app.promptSurfaceList.get(0);
2656 var promptSurface = app.promptSurfaceList.get(0);
2584 verify(promptSurface);
2657 verify(promptSurface);
2585 tryCompare(appSurface, "keymap", promptSurface.keymap);
2658 tryCompare(appSurface, "keymap", promptSurface.keymap);
2586 // ... and that the controller's surface keymap is also the same
2659 // ... and that the controller's surface keymap is also the same
2587− tryCompare(MirFocusController.focusedSurface, "keymap", "sk");
2660+ tryCompare(topLevelSurfaceList.focusedWindow.surface, "keymap", "sk");
2588 app.promptSurfaceList.get(0).close();
2661 app.promptSurfaceList.get(0).close();
2589
2662
2590 // switch to next keymap, should go to "cz+qwerty"
2663 // switch to next keymap, should go to "cz+qwerty"
@@ -2596,7 +2669,7 @@ Rectangle {
2596 tryCompare(appSurface, "keymap", "fr");
2669 tryCompare(appSurface, "keymap", "fr");
2597
2670
2598 // go to e.g. desktop stage
2671 // go to e.g. desktop stage
2599− loadShell("desktop");
2672+ shellLoader.state = "desktop";
2600 shell.usageScenario = "desktop";
2673 shell.usageScenario = "desktop";
2601 waitForRendering(shell);
2674 waitForRendering(shell);
2602
2675
@@ -2702,7 +2775,6 @@ Rectangle {
2702 return [
2775 return [
2703 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2776 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2704 {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2777 {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2705− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2706 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2778 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2707 ]
2779 ]
2708 }
2780 }
@@ -2712,26 +2784,28 @@ Rectangle {
2712 shell.usageScenario = "desktop";
2784 shell.usageScenario = "desktop";
2713 waitForRendering(shell);
2785 waitForRendering(shell);
2714 swipeAwayGreeter();
2786 swipeAwayGreeter();
2787+ ensureInputMethodSurface();
2715 shell.oskEnabled = data.oskEnabled;
2788 shell.oskEnabled = data.oskEnabled;
2716
2789
2717− var oldOSKState = SurfaceManager.inputMethodSurface.state;
2790+ var oldOSKState = topLevelSurfaceList.inputMethodSurface.state;
2718− SurfaceManager.inputMethodSurface.state = Mir.RestoredState;
2791+ topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
2719 var appRepeater = findChild(shell, "appRepeater");
2792 var appRepeater = findChild(shell, "appRepeater");
2720 var dashAppDelegate = appRepeater.itemAt(0);
2793 var dashAppDelegate = appRepeater.itemAt(0);
2721 verify(dashAppDelegate);
2794 verify(dashAppDelegate);
2795+
2722− dashAppDelegate.requestedHeight = data.windowHeight;
2796+ dashAppDelegate.windowedHeight = data.windowHeight;
2723− dashAppDelegate.requestedY = data.windowY;
2797+ dashAppDelegate.windowedY = data.windowY;
2724− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2798+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2725 var initialY = dashAppDelegate.y;
2799 var initialY = dashAppDelegate.y;
2726 print("intial", initialY, "panel", PanelState.panelHeight);
2800 print("intial", initialY, "panel", PanelState.panelHeight);
2727 verify(initialY > PanelState.panelHeight);
2801 verify(initialY > PanelState.panelHeight);
2728
2802
2729− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2803+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2730 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2804 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2731
2805
2732− SurfaceManager.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2806+ topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2733 tryCompare(dashAppDelegate, "y", initialY);
2807 tryCompare(dashAppDelegate, "y", initialY);
2734− SurfaceManager.inputMethodSurface.state = oldOSKState;
2808+ topLevelSurfaceList.inputMethodSurface.requestState(oldOSKState);
2735 }
2809 }
2736
2810
2737 function test_cursorHidingWithFullscreenApp() {
2811 function test_cursorHidingWithFullscreenApp() {
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}