on 67df091 help sign in
CMakeLists.txt+7−3
since splitsnapshots
unifiedsplit
@@ -70,15 +70,17 @@ find_package(Qt5DBus 5.6 REQUIRED)
70find_package(Qt5Concurrent 5.6 REQUIRED)
70find_package(Qt5Concurrent 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
72
72
73−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=26)
73+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
74+pkg_check_modules(QTMIRSERVER REQUIRED qtmirserver>=0.6.0)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
77pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
77pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
78pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
78pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
79pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
79pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
80pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
80−pkg_check_modules(UAL REQUIRED ubuntu-app-launch-2)
81+pkg_check_modules(UAL REQUIRED ubuntu-app-launch-3)
81pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
82pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
83+pkg_check_modules(MIRAL REQUIRED miral)
82
84
83### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
85### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
84find_path(UBUNTUGESTUREPRIV
86find_path(UBUNTUGESTUREPRIV
@@ -113,7 +115,9 @@ if(SHELL_PLUGINDIR STREQUAL "")
113 message(FATAL_ERROR "Could not determine plugin import dir.")
115 message(FATAL_ERROR "Could not determine plugin import dir.")
114endif()
116endif()
115
117
116−set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
118+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-permissive -pedantic -Wall -Wextra")
119+
120+set (CMAKE_CXX_STANDARD 14)
117
121
118if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
122if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
119 option(Werror "Treat warnings as errors" ON)
123 option(Werror "Treat warnings as errors" ON)
debian/control+10−4
@@ -31,20 +31,22 @@ Build-Depends: cmake,
31 libqmenumodel-dev (>= 0.2.11),
31 libqmenumodel-dev (>= 0.2.11),
32 libqt5svg5-dev,
32 libqt5svg5-dev,
33 libqt5xmlpatterns5-dev,
33 libqt5xmlpatterns5-dev,
34+ libqtmirserver-dev (>= 0.6.0),
34 libsystemsettings-dev,
35 libsystemsettings-dev,
35− libubuntu-app-launch2-dev,
36+ libubuntu-app-launch3-dev,
36 libubuntu-download-manager-common-dev,
37 libubuntu-download-manager-common-dev,
37 libubuntugestures5-dev (>= 1.3.2030),
38 libubuntugestures5-dev (>= 1.3.2030),
38 libubuntugestures5-private-dev (>= 1.3.2030),
39 libubuntugestures5-private-dev (>= 1.3.2030),
39 libudev-dev,
40 libudev-dev,
40 libudm-common-dev,
41 libudm-common-dev,
41− libunity-api-dev (>= 8.2),
42+ libunity-api-dev (>= 8.3),
42 libusermetricsoutput1-dev,
43 libusermetricsoutput1-dev,
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44 libx11-dev[!arm64 !armhf],
45 libx11-dev[!arm64 !armhf],
45 libxcb1-dev[!arm64 !armhf],
46 libxcb1-dev[!arm64 !armhf],
46 libxi-dev[!arm64 !armhf],
47 libxi-dev[!arm64 !armhf],
47# End of X11 libs
48# End of X11 libs
49+ mirtest-dev,
48 pkg-config,
50 pkg-config,
49 python3-all:any,
51 python3-all:any,
50 python3-setuptools,
52 python3-setuptools,
@@ -70,6 +72,9 @@ Build-Depends: cmake,
70 qtdeclarative5-ubuntu-settings-components (>= 0.11),
72 qtdeclarative5-ubuntu-settings-components (>= 0.11),
71 ttf-ubuntu-font-family,
73 ttf-ubuntu-font-family,
72 xvfb,
74 xvfb,
75+# mirtest pkgconfig requires these, but doesn't have a deb dependency. Bug lp:1633537
76+ libboost-filesystem-dev,
77+ libboost-system-dev,
73Standards-Version: 3.9.4
78Standards-Version: 3.9.4
74Homepage: http://launchpad.net/unity
79Homepage: http://launchpad.net/unity
75# If you aren't a member of ~unity-team but need to upload
80# If you aren't a member of ~unity-team but need to upload
@@ -131,6 +136,7 @@ Depends: dmz-cursor-theme,
131 qml-module-ubuntu-web,
136 qml-module-ubuntu-web,
132 qtdeclarative5-qtmir-plugin (>= 0.4.8),
137 qtdeclarative5-qtmir-plugin (>= 0.4.8),
133 qtdeclarative5-ubuntu-telephony0.1,
138 qtdeclarative5-ubuntu-telephony0.1,
139+ qtmir-desktop (>= 0.6.0) | qtmir-android (>= 0.6.0),
134 ubuntu-system-settings (>= 0.4),
140 ubuntu-system-settings (>= 0.4),
135 unity-launcher-impl-10,
141 unity-launcher-impl-10,
136 unity8-common (= ${source:Version}),
142 unity8-common (= ${source:Version}),
@@ -162,7 +168,7 @@ Depends: qml-module-qtquick-layouts,
162 qttranslations5-l10n,
168 qttranslations5-l10n,
163 ubuntu-thumbnailer-impl-0,
169 ubuntu-thumbnailer-impl-0,
164 ubuntu-wallpapers,
170 ubuntu-wallpapers,
165− unity-application-impl-26,
171+ unity-application-impl-27,
166 unity-notifications-impl-3,
172 unity-notifications-impl-3,
167 unity-plugin-scopes | unity-scopes-impl,
173 unity-plugin-scopes | unity-scopes-impl,
168 unity-scopes-impl-12,
174 unity-scopes-impl-12,
@@ -212,7 +218,7 @@ Depends: dbus-test-runner,
212 ${misc:Depends},
218 ${misc:Depends},
213 ${shlibs:Depends},
219 ${shlibs:Depends},
214Provides: unity-application-impl,
220Provides: unity-application-impl,
215− unity-application-impl-26,
221+ unity-application-impl-27,
216 unity8-fake-env,
222 unity8-fake-env,
217Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
223Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
218 unity8-fake-env,
224 unity8-fake-env,
debian/unity8.install+4
@@ -16,7 +16,11 @@ usr/share/unity8/Rotation
16usr/share/unity8/DeviceConfiguration.qml
16usr/share/unity8/DeviceConfiguration.qml
17usr/share/unity8/OrientedShell.qml
17usr/share/unity8/OrientedShell.qml
18usr/share/unity8/DisabledScreenNotice.qml
18usr/share/unity8/DisabledScreenNotice.qml
19+usr/share/unity8/qmldir
19usr/share/unity8/Shell.qml
20usr/share/unity8/Shell.qml
21+usr/share/unity8/ShellApplication.qml
22+usr/share/unity8/ShellNotifier.qml
23+usr/share/unity8/ShellScreen.qml
20usr/share/unity8/Stage
24usr/share/unity8/Stage
21usr/share/unity8/Tutorial
25usr/share/unity8/Tutorial
22usr/share/unity8/Wizard
26usr/share/unity8/Wizard
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 CursorImageInfo.cpp
15 CursorImageInfo.cpp
16 CursorImageProvider.cpp
16 CursorImageProvider.cpp
17 MousePointer.cpp
17 MousePointer.cpp
18+ InputDispatcherFilter.cpp
18 # We need to run moc on this header
19 # We need to run moc on this header
19 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 )
21 )
plugins/Cursor/InputDispatcherFilter.cppnew file+175
@@ -0,0 +1,175 @@
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 "InputDispatcherFilter.h"
18+#include "MousePointer.h"
19+
20+#include <QEvent>
21+#include <QGuiApplication>
22+#include <QQuickWindow>
23+#include <QScreen>
24+#include <QtMath>
25+#include <qpa/qplatformnativeinterface.h>
26+#include <qpa/qplatformscreen.h>
27+
28+InputDispatcherFilter *InputDispatcherFilter::instance()
29+{
30+ static InputDispatcherFilter filter;
31+ return &filter;
32+}
33+
34+InputDispatcherFilter::InputDispatcherFilter(QObject *parent)
35+ : QObject(parent)
36+ , m_pushing(false)
37+{
38+ QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface();
39+ m_inputDispatcher = static_cast<QObject*>(ni->nativeResourceForIntegration("InputDispatcher"));
40+ if (m_inputDispatcher) {
41+ m_inputDispatcher->installEventFilter(this);
42+ }
43+}
44+
45+void InputDispatcherFilter::registerPointer(MousePointer *pointer)
46+{
47+ // allow first registered pointer to be visible.
48+ m_pointers.insert(pointer);
49+}
50+
51+void InputDispatcherFilter::unregisterPointer(MousePointer *pointer)
52+{
53+ m_pointers.remove(pointer);
54+}
55+
56+bool InputDispatcherFilter::eventFilter(QObject *o, QEvent *e)
57+{
58+ if (o != m_inputDispatcher) return false;
59+
60+ switch (e->type()) {
61+ case QEvent::MouseMove:
62+ case QEvent::MouseButtonPress:
63+ case QEvent::MouseButtonRelease:
64+ {
65+ // if we don't have any pointers, filter all mouse events.
66+ auto pointer = currentPointer();
67+ if (!pointer) return true;
68+
69+ QMouseEvent* me = static_cast<QMouseEvent*>(e);
70+
71+ // Local position gives relative change of mouse pointer.
72+ QPointF movement = me->localPos();
73+
74+ // Adjust the position
75+ QPointF oldPos = pointer->window()->geometry().topLeft() + pointer->position();
76+ QPointF newPos = adjustedPositionForMovement(oldPos, movement);
77+
78+ QScreen* currentScreen = screenAt(newPos);
79+ if (currentScreen) {
80+ QRect screenRect = currentScreen->geometry();
81+ qreal newX = (oldPos + movement).x();
82+ qreal newY = (oldPos + movement).y();
83+
84+ if (newX <= screenRect.left() && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top left corner
85+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY- screenRect.top() - pointer->topBoundaryOffset(), 2));
86+ Q_EMIT pushedTopLeftCorner(currentScreen, qAbs(distance), me->buttons());
87+ m_pushing = true;
88+ } else if (newX >= screenRect.right()-1 && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top right corner
89+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY - screenRect.top() - pointer->topBoundaryOffset(), 2));
90+ Q_EMIT pushedTopRightCorner(currentScreen, qAbs(distance), me->buttons());
91+ m_pushing = true;
92+ } else if (newX < 0 && newY >= screenRect.bottom()-1) { // bottom left corner
93+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-screenRect.bottom(), 2));
94+ Q_EMIT pushedBottomLeftCorner(currentScreen, qAbs(distance), me->buttons());
95+ m_pushing = true;
96+ } else if (newX >= screenRect.right()-1 && newY >= screenRect.bottom()-1) { // bottom right corner
97+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY-screenRect.bottom(), 2));
98+ Q_EMIT pushedBottomRightCorner(currentScreen, qAbs(distance), me->buttons());
99+ m_pushing = true;
100+ } else if (newX < screenRect.left()) { // left edge
101+ Q_EMIT pushedLeftBoundary(currentScreen, qAbs(newX), me->buttons());
102+ m_pushing = true;
103+ } else if (newX >= screenRect.right()) { // right edge
104+ Q_EMIT pushedRightBoundary(currentScreen, newX - (screenRect.right() - 1), me->buttons());
105+ m_pushing = true;
106+ } else if (newY < screenRect.top() + pointer->topBoundaryOffset()) { // top edge
107+ Q_EMIT pushedTopBoundary(currentScreen, qAbs(newY - screenRect.top() - pointer->topBoundaryOffset()), me->buttons());
108+ m_pushing = true;
109+ } else if (Q_LIKELY(newX > 0 && newX < screenRect.right()-1 && newY > 0 && newY < screenRect.bottom()-1)) { // normal pos, not pushing
110+ if (m_pushing) {
111+ Q_EMIT pushStopped(currentScreen);
112+ m_pushing = false;
113+ }
114+ }
115+ }
116+
117+ // Send the event
118+ QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
119+ eCopy.setTimestamp(me->timestamp());
120+ o->event(&eCopy);
121+ return true;
122+ }
123+ default:
124+ break;
125+ }
126+ return false;
127+}
128+
129+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
130+{
131+ QPointF adjusted = pt + movement;
132+
133+ auto screen = screenAt(adjusted); // first check if our move was to a screen with an enabled pointer.
134+ if (screen) {
135+ return adjusted;
136+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
137+ QRectF screenBounds = screen->geometry();
138+ // bound the new position to the old screen geometry
139+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
140+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
141+ } else {
142+ auto screens = QGuiApplication::screens();
143+
144+ // center of first screen with a pointer.
145+ Q_FOREACH(QScreen* screen, screens) {
146+ Q_FOREACH(MousePointer* pointer, m_pointers) {
147+ if (pointer->isEnabled() && pointer->screen() == screen) {
148+ return screen->geometry().center();
149+ }
150+ }
151+ }
152+ }
153+ return adjusted;
154+}
155+
156+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
157+{
158+ Q_FOREACH(MousePointer* pointer, m_pointers) {
159+ if (!pointer->isEnabled()) continue;
160+
161+ QScreen* screen = pointer->screen();
162+ if (screen && screen->geometry().contains(pt.toPoint()))
163+ return screen;
164+ }
165+ return nullptr;
166+}
167+
168+MousePointer *InputDispatcherFilter::currentPointer() const
169+{
170+ Q_FOREACH(MousePointer* pointer, m_pointers) {
171+ if (!pointer->isEnabled()) continue;
172+ return pointer;
173+ }
174+ return nullptr;
175+}
plugins/Cursor/InputDispatcherFilter.hnew file+62
@@ -0,0 +1,62 @@
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 INPUTDISPATCHERFILTER_H
18+#define INPUTDISPATCHERFILTER_H
19+
20+#include <QObject>
21+#include <QPointF>
22+#include <QSet>
23+
24+class MousePointer;
25+class QScreen;
26+
27+class InputDispatcherFilter : public QObject
28+{
29+ Q_OBJECT
30+public:
31+ static InputDispatcherFilter *instance();
32+
33+ void registerPointer(MousePointer* pointer);
34+ void unregisterPointer(MousePointer* pointer);
35+
36+Q_SIGNALS:
37+ void pushedLeftBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
38+ void pushedRightBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
39+ void pushedTopBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
40+ void pushedTopLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
41+ void pushedTopRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
42+ void pushedBottomLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
43+ void pushedBottomRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
44+ void pushStopped(QScreen* screen);
45+
46+protected:
47+ InputDispatcherFilter(QObject* parent = nullptr);
48+
49+ bool eventFilter(QObject *o, QEvent *e) override;
50+
51+ QPointF adjustedPositionForMovement(const QPointF& pt, const QPointF& movement) const;
52+ QScreen* screenAt(const QPointF& pt) const;
53+
54+ MousePointer* currentPointer() const;
55+
56+private:
57+ QObject* m_inputDispatcher;
58+ QSet<MousePointer*> m_pointers;
59+ bool m_pushing;
60+};
61+
62+#endif // INPUTDISPATCHERFILTER_H
plugins/Cursor/MousePointer.cpp+63−78
@@ -16,84 +16,74 @@
16
16
17#include "MousePointer.h"
17#include "MousePointer.h"
18#include "CursorImageProvider.h"
18#include "CursorImageProvider.h"
19−
19+#include "InputDispatcherFilter.h"
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
22
20
23#include <QQuickWindow>
21#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
26
22
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
28
25
29MousePointer::MousePointer(QQuickItem *parent)
26MousePointer::MousePointer(QQuickItem *parent)
30 : MirMousePointerInterface(parent)
27 : MirMousePointerInterface(parent)
31 , m_cursorName(QStringLiteral("left_ptr"))
28 , m_cursorName(QStringLiteral("left_ptr"))
32 , m_themeName(QStringLiteral("default"))
29 , m_themeName(QStringLiteral("default"))
33{
30{
34−}
31+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
35−
32+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
33+ if (window() && window()->screen() == screen) {
37− Qt::KeyboardModifiers modifiers)
34+ Q_EMIT pushedLeftBoundary(amount, buttons);
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
44− Q_EMIT mouseMoved();
45− }
35+ }
46−
36+ });
47− m_accumulatedMovement += movement;
37+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
48− // don't apply the fractional part
38+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
39+ if (window() && window()->screen() == screen) {
50− m_accumulatedMovement -= appliedMovement;
40+ Q_EMIT pushedRightBoundary(amount, buttons);
51−
41+ }
52− qreal newX = x() + appliedMovement.x();
42+ });
53− qreal newY = y() + appliedMovement.y();
43+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
54− const qreal sceneWidth = parentItem()->width();
44+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
55− const qreal sceneHeight = parentItem()->height();
45+ if (window() && window()->screen() == screen) {
56−
46+ Q_EMIT pushedTopBoundary(amount, buttons);
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
47+ }
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
48+ });
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
49+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
60− m_pushing = true;
50+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
51+ if (window() && window()->screen() == screen) {
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
52+ Q_EMIT pushedTopLeftCorner(amount, buttons);
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
53+ }
64− m_pushing = true;
54+ });
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
55+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
56+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
57+ if (window() && window()->screen() == screen) {
68− m_pushing = true;
58+ Q_EMIT pushedTopRightCorner(amount, buttons);
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
59+ }
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
60+ });
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
61+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
72− m_pushing = true;
62+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
73− } else if (newX < 0) { // left edge
63+ if (window() && window()->screen() == screen) {
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
64+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
75− m_pushing = true;
65+ }
76− } else if (newX >= sceneWidth) { // right edge
66+ });
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
67+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
78− m_pushing = true;
68+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
79− } else if (newY < m_topBoundaryOffset) { // top edge
69+ if (window() && window()->screen() == screen) {
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
70+ Q_EMIT pushedBottomRightCorner(amount, buttons);
81− m_pushing = true;
71+ }
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
72+ });
83− if (m_pushing) {
73+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
74+ this, [this](QScreen* screen) {
75+ if (window() && window()->screen() == screen) {
84 Q_EMIT pushStopped();
76 Q_EMIT pushStopped();
85− m_pushing = false;
86 }
77 }
87− }
78+ });
88
79
89− applyItemConfinement(newX, newY);
80+ InputDispatcherFilter::instance()->registerPointer(this);
90−
81+}
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
93
82
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
83+MousePointer::~MousePointer()
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
84+{
96− buttons, modifiers);
85+ registerScreen(nullptr);
86+ InputDispatcherFilter::instance()->unregisterPointer(this);
97}
87}
98
88
99void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
89void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121 }
111 }
122}
112}
123
113
124−void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
125−{
126− if (!parentItem()) {
127− return;
128− }
129−
130− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
131− QWindowSystemInterface::handleWheelEvent(window(), timestamp, scenePosition /* local */, scenePosition /* global */,
132− QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
133−}
134−
135int MousePointer::topBoundaryOffset() const
114int MousePointer::topBoundaryOffset() const
136{
115{
137 return m_topBoundaryOffset;
116 return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
182 if (m_registeredScreen) {
161 if (m_registeredScreen) {
183 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
162 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184 if (previousCursor) {
163 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
164+ previousCursor->unregisterMousePointer(this);
186 } else {
165 } else {
187 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
166 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188 }
167 }
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
193 if (m_registeredScreen) {
172 if (m_registeredScreen) {
194 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
173 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195 if (cursor) {
174 if (cursor) {
196− cursor->setMousePointer(this);
175+ cursor->registerMousePointer(this);
197 } else {
176 } else {
198 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
177 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199 }
178 }
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
216 }
195 }
217}
196}
218
197
198+void MousePointer::moveTo(const QPoint &position)
199+{
200+ setPosition(position);
201+ Q_EMIT mouseMoved();
202+}
203+
219void MousePointer::setCustomCursor(const QCursor &customCursor)
204void MousePointer::setCustomCursor(const QCursor &customCursor)
220{
205{
221 CursorImageProvider::instance()->setCustomCursor(customCursor);
206 CursorImageProvider::instance()->setCustomCursor(customCursor);
plugins/Cursor/MousePointer.h+5−4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
32public:
32public:
33 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
34
35
35 void setCursorName(const QString &qtCursorName) override;
36 void setCursorName(const QString &qtCursorName) override;
36 QString cursorName() const override { return m_cursorName; }
37 QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ public:
38 void setThemeName(const QString &themeName) override;
39 void setThemeName(const QString &themeName) override;
39 QString themeName() const override { return m_themeName; }
40 QString themeName() const override { return m_themeName; }
40
41
42+ void moveTo(const QPoint& position) override;
43+
41 void setCustomCursor(const QCursor &) override;
44 void setCustomCursor(const QCursor &) override;
42
45
43 QQuickItem* confiningItem() const;
46 QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ public:
46 int topBoundaryOffset() const;
49 int topBoundaryOffset() const;
47 void setTopBoundaryOffset(int topBoundaryOffset);
50 void setTopBoundaryOffset(int topBoundaryOffset);
48
51
49−public Q_SLOTS:
52+ QScreen* screen() const { return m_registeredScreen; }
50− void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
51− Qt::KeyboardModifiers modifiers) override;
52− void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers) override;
53
53
54Q_SIGNALS:
54Q_SIGNALS:
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private:
79 QPointer<QScreen> m_registeredScreen;
79 QPointer<QScreen> m_registeredScreen;
80 QString m_cursorName;
80 QString m_cursorName;
81 QString m_themeName;
81 QString m_themeName;
82+ bool m_active;
82
83
83 // Accumulated, unapplied, mouse movement.
84 // Accumulated, unapplied, mouse movement.
84 QPointF m_accumulatedMovement;
85 QPointF m_accumulatedMovement;
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
22void UInputPlugin::registerTypes(const char *uri)
28void UInputPlugin::registerTypes(const char *uri)
23{
29{
24 Q_ASSERT(uri == QLatin1String("UInput"));
30 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
32}
plugins/Unity/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1add_subdirectory(ApplicationMenu)
1add_subdirectory(ApplicationMenu)
2add_subdirectory(Connectivity)
2add_subdirectory(Connectivity)
3+add_subdirectory(Debug)
3add_subdirectory(Indicators)
4add_subdirectory(Indicators)
4add_subdirectory(Launcher)
5add_subdirectory(Launcher)
5add_subdirectory(Session)
6add_subdirectory(Session)
plugins/Unity/Debug/CMakeLists.txtnew file+30
@@ -0,0 +1,30 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+)
5+
6+include_directories(
7+ SYSTEM
8+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
9+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10+ ${GIO_INCLUDE_DIRS}
11+)
12+
13+set(QMLPLUGIN_SRC
14+ consolelog.cpp
15+ plugin.cpp
16+ )
17+
18+add_library(Debug-qml SHARED
19+ ${QMLPLUGIN_SRC}
20+ )
21+
22+# Because this is an internal support library, we want
23+# to expose all symbols in it. Consider changing this
24+# either to a static library or just using the
25+# files directly in targets.
26+set_target_properties(Debug-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
27+
28+qt5_use_modules(Debug-qml Qml Quick Concurrent)
29+
30+add_unity8_plugin(Unity.Debug 0.1 Unity/Debug TARGETS Debug-qml)
plugins/Unity/Debug/consolelog.cppnew file+221
@@ -0,0 +1,221 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 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 "consolelog.h"
18+
19+#include <unistd.h>
20+#include <stdio.h>
21+#include <thread>
22+#include <iostream>
23+#include <sys/ioctl.h>
24+
25+#include <functional>
26+
27+namespace
28+{
29+
30+int wait_fd(std::function<int(void)> fn)
31+{
32+ int ret = -1;
33+ bool fd_blocked = false;
34+ do
35+ {
36+ ret = fn();
37+ fd_blocked = (errno == EINTR || errno == EBUSY);
38+ if (fd_blocked)
39+ QThread::msleep(10);
40+ }
41+ while (ret < 0);
42+ return ret;
43+}
44+
45+#define PIPE_WAIT(val) wait_fd(std::bind(pipe, val))
46+#define DUP_WAIT(fd) wait_fd(std::bind(dup, fd))
47+#define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2))
48+#define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd))
49+
50+}
51+
52+LogRedirector::LogRedirector()
53+ : m_ref(0)
54+ , m_stop(false)
55+{
56+ setObjectName("ConsoleLog");
57+
58+ // make stdout & stderr streams unbuffered
59+ // so that we don't need to flush the streams
60+ // before capture and after capture
61+ // (fflush can cause a deadlock if the stream is currently being used)
62+ setvbuf(stdout,NULL,_IONBF,0);
63+ setvbuf(stderr,NULL,_IONBF,0);
64+}
65+
66+void LogRedirector::run()
67+{
68+ PIPE_WAIT(m_pipe);
69+
70+ int oldStdOut = DUP_WAIT(fileno(stdout));
71+ int oldStdErr = DUP_WAIT(fileno(stderr));
72+
73+ DUP2_WAIT(m_pipe[WRITE], fileno(stdout));
74+ DUP2_WAIT(m_pipe[WRITE], fileno(stderr));
75+ CLOSE_WAIT(m_pipe[WRITE]);
76+
77+ while(true) {
78+ {
79+ QMutexLocker lock(&m_mutex);
80+ if (m_stop) break;
81+ }
82+ checkLog();
83+ QThread::msleep(50);
84+ }
85+
86+ DUP2_WAIT(oldStdOut, fileno(stdout));
87+ DUP2_WAIT(oldStdErr, fileno(stderr));
88+
89+ CLOSE_WAIT(oldStdOut);
90+ CLOSE_WAIT(oldStdErr);
91+ CLOSE_WAIT(m_pipe[READ]);
92+}
93+
94+
95+void LogRedirector::checkLog()
96+{
97+ // Do not allow read to block with no data.
98+ // If we stop the thread while waiting a read,
99+ // it will block the main thread waiting for this thread to stop.
100+ int count = 0;
101+ ioctl(m_pipe[READ], FIONREAD, &count);
102+ if (count <= 0) return;
103+
104+ std::string captured;
105+ std::string buf;
106+ const int bufSize = 1024;
107+ buf.resize(bufSize);
108+ int bytesRead = 0;
109+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
110+ while(bytesRead == bufSize)
111+ {
112+ captured += buf;
113+ bytesRead = 0;
114+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
115+ }
116+ if (bytesRead > 0)
117+ {
118+ buf.resize(bytesRead);
119+ captured += buf;
120+ }
121+
122+ if (!captured.empty()) {
123+ Q_EMIT log(QString::fromStdString(captured));
124+ }
125+}
126+
127+LogRedirector *LogRedirector::instance()
128+{
129+ static LogRedirector* log = nullptr;
130+ if (!log) {
131+ log = new LogRedirector();
132+ }
133+ return log;
134+}
135+
136+void LogRedirector::add(ConsoleLog* logger)
137+{
138+ QMutexLocker lock(&m_mutex);
139+ connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection);
140+
141+ m_ref++;
142+ if (!LogRedirector::instance()->isRunning()) {
143+ m_stop = false;
144+ LogRedirector::instance()->start();
145+ }
146+}
147+
148+void LogRedirector::remove(ConsoleLog* logger)
149+{
150+ QMutexLocker lock(&m_mutex);
151+ disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged);
152+
153+ m_ref = qMax(m_ref-1, 0);
154+ if (m_ref == 0 && LogRedirector::instance()->isRunning()) {
155+ m_stop = true;
156+ lock.unlock();
157+ LogRedirector::instance()->wait();
158+ }
159+}
160+
161+ConsoleLog::ConsoleLog(QObject *parent)
162+ : QObject(parent)
163+ , m_enabled(false)
164+ , m_maxLines(60)
165+{
166+ auto updateEnabled = [this]() {
167+ if (m_enabled) {
168+ LogRedirector::instance()->add(this);
169+ } else {
170+ LogRedirector::instance()->remove(this);
171+ }
172+ };
173+ connect(this, &ConsoleLog::enabledChanged, this, updateEnabled);
174+}
175+
176+ConsoleLog::~ConsoleLog()
177+{
178+ if (m_enabled) {
179+ LogRedirector::instance()->remove(this);
180+ }
181+}
182+
183+void ConsoleLog::setEnabled(bool enabled)
184+{
185+ if (m_enabled == enabled) {
186+ return;
187+ }
188+
189+ m_enabled = enabled;
190+ Q_EMIT enabledChanged();
191+}
192+
193+QString ConsoleLog::out() const
194+{
195+ return m_out.join("\n");
196+}
197+
198+void ConsoleLog::setMaxLines(int maxLines)
199+{
200+ if (m_maxLines == maxLines) {
201+ return;
202+ }
203+
204+ m_maxLines = maxLines;
205+ while (m_out.count() > m_maxLines) {
206+ m_out.removeLast();
207+ }
208+ Q_EMIT outChanged();
209+ Q_EMIT maxLinesChanged();
210+}
211+
212+void ConsoleLog::logged(QString captured)
213+{
214+ QStringList li = captured.split("\n", QString::SkipEmptyParts);
215+ li << m_out;
216+ m_out = li;
217+ while (m_out.count() > m_maxLines) {
218+ m_out.removeLast();
219+ }
220+ Q_EMIT outChanged();
221+}
plugins/Unity/Debug/consolelog.hnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 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 CONSOLELOG_H
18+#define CONSOLELOG_H
19+
20+#include <QThread>
21+#include <QMutex>
22+#include <QSet>
23+
24+class ConsoleLog;
25+
26+class LogRedirector : public QThread
27+{
28+ Q_OBJECT
29+public:
30+ static LogRedirector *instance();
31+
32+ void add(ConsoleLog* logger);
33+ void remove(ConsoleLog* logger);
34+
35+private Q_SLOTS:
36+ void checkLog();
37+
38+Q_SIGNALS:
39+ void log(QString log);
40+
41+private:
42+ LogRedirector();
43+ void run() Q_DECL_OVERRIDE;
44+
45+ QMutex m_mutex;
46+ int m_pipe[2];
47+ int m_ref;
48+ bool m_stop;
49+
50+ enum PIPES { READ, WRITE };
51+};
52+
53+class ConsoleLog : public QObject
54+{
55+ Q_OBJECT
56+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
57+ Q_PROPERTY(QString out READ out NOTIFY outChanged)
58+ Q_PROPERTY(int maxLines READ maxLines WRITE setMaxLines NOTIFY maxLinesChanged)
59+public:
60+ explicit ConsoleLog(QObject *parent = 0);
61+ ~ConsoleLog();
62+
63+ bool isEnabled() const { return m_enabled; }
64+ void setEnabled(bool enabled);
65+
66+ QString out() const;
67+
68+ int maxLines() const { return m_maxLines; }
69+ void setMaxLines(int maxLines);
70+
71+public Q_SLOTS:
72+ void logged(QString captured);
73+
74+Q_SIGNALS:
75+ void enabledChanged();
76+ void outChanged();
77+ void maxLinesChanged();
78+
79+private:
80+ void updateEnabled();
81+
82+ QStringList m_out;
83+ bool m_enabled;
84+ int m_maxLines;
85+};
86+
87+#endif // CONSOLELOG_H
plugins/Unity/Debug/plugin.cppnew file+31
@@ -0,0 +1,31 @@
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+// Qt
18+#include <QtQml/qqml.h>
19+
20+// self
21+#include "plugin.h"
22+
23+// local
24+#include "consolelog.h"
25+
26+void UnityDebugPlugin::registerTypes(const char *uri)
27+{
28+ Q_ASSERT(uri == QLatin1String("Unity.Debug"));
29+
30+ qmlRegisterType<ConsoleLog>(uri, 0, 1, "ConsoleLog");
31+}
src/ShellView.h → plugins/Unity/Debug/plugin.hrenamed+9−11
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_SHELL_VIEW_H
17+#ifndef UNITYDEBUG_PLUGIN_H
18−#define UNITY_SHELL_VIEW_H
18+#define UNITYDEBUG_PLUGIN_H
19
19
20−#include <QQuickView>
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
21
22
22−class ShellView : public QQuickView
23+class UnityDebugPlugin : public QQmlExtensionPlugin
23{
24{
24 Q_OBJECT
25 Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25
27
26public:
28public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
29+ void registerTypes(const char *uri) override;
28−
29−private Q_SLOTS:
30− void onWidthChanged(int);
31− void onHeightChanged(int);
32};
30};
33
31
34−#endif // UNITY_SHELL_VIEW_H
32+#endif
plugins/Unity/Debug/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.Debug
2+plugin Debug-qml
3+typeinfo Debug.qmltypes
plugins/WindowManager/CMakeLists.txt+23−2
@@ -1,16 +1,37 @@
1+include_directories(
2+ SYSTEM
3+ ${QTMIRSERVER_INCLUDE_DIRS}
4+)
5+
6+include_directories(
7+ ${CMAKE_CURRENT_SOURCE_DIR}
8+ ${libunity8-private_SOURCE_DIR}
9+)
10+
1set(WINDOWMANAGER_SRC
11set(WINDOWMANAGER_SRC
2 TopLevelWindowModel.cpp
12 TopLevelWindowModel.cpp
3 Window.cpp
13 Window.cpp
4 WindowManagerPlugin.cpp
14 WindowManagerPlugin.cpp
15+ Screens.cpp
16+ Screen.cpp
17+ ScreenWindow.cpp
18+ Workspace.cpp
19+ WorkspaceManager.cpp
20+ WorkspaceModel.cpp
5 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
21 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
6 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
22 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
7 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
23 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
24 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
25 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
10− )
26+)
11
27
12add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
28add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
13
29
30+target_link_libraries(windowmanager-qml
31+ ${QTMIRSERVER_LDFLAGS}
32+ unity8-private
33+)
34+
14qt5_use_modules(windowmanager-qml Qml Quick Gui)
35qt5_use_modules(windowmanager-qml Qml Quick Gui)
15
36
16−add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
37+add_unity8_plugin(WindowManager 1.0 WindowManager TARGETS windowmanager-qml)
plugins/WindowManager/Screen.cppnew file+224
@@ -0,0 +1,224 @@
1+/*
2+ * Copyright (C) 2017 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 "Screen.h"
18+#include "WorkspaceManager.h"
19+#include "Workspace.h"
20+
21+#include <QQmlEngine>
22+
23+Screen::Screen(qtmir::Screen* screen)
24+ : m_wrapped(screen)
25+ , m_workspaces(new WorkspaceModel)
26+{
27+ QQmlEngine::setObjectOwnership(m_workspaces.data(), QQmlEngine::CppOwnership);
28+
29+ connect(m_wrapped, &qtmir::Screen::usedChanged, this, &Screen::usedChanged);
30+ connect(m_wrapped, &qtmir::Screen::nameChanged, this, &Screen::nameChanged);
31+ connect(m_wrapped, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeChanged);
32+ connect(m_wrapped, &qtmir::Screen::scaleChanged, this, &Screen::scaleChanged);
33+ connect(m_wrapped, &qtmir::Screen::formFactorChanged, this, &Screen::formFactorChanged);
34+ connect(m_wrapped, &qtmir::Screen::physicalSizeChanged, this, &Screen::physicalSizeChanged);
35+ connect(m_wrapped, &qtmir::Screen::positionChanged, this, &Screen::positionChanged);
36+ connect(m_wrapped, &qtmir::Screen::activeChanged, this, &Screen::activeChanged);
37+ connect(m_wrapped, &qtmir::Screen::currentModeIndexChanged, this, &Screen::currentModeIndexChanged);
38+ connect(m_wrapped, &qtmir::Screen::availableModesChanged, this, &Screen::availableModesChanged);
39+
40+ // Connect the active workspace to activate the screen.
41+ connect(m_workspaces.data(), &WorkspaceModel::workspaceAdded, this, [this](Workspace* workspace) {
42+ connect(workspace, &Workspace::activeChanged, this, [this, workspace](bool active) {
43+ if (active) {
44+ setCurrentWorkspace(workspace);
45+ activate();
46+ }
47+ });
48+ if (workspace->isActive()) {
49+ activate();
50+ setCurrentWorkspace(workspace);
51+ }
52+ if (!m_currentWorspace) {
53+ setCurrentWorkspace(workspace);
54+ }
55+ });
56+ connect(m_workspaces.data(), &WorkspaceModel::workspaceRemoved, this, [this](Workspace* workspace) {
57+ disconnect(workspace, &Workspace::activeChanged, this, 0);
58+ if (workspace == m_currentWorspace) {
59+ resetCurrentWorkspace();
60+ }
61+ });
62+ connect(this, &Screen::activeChanged, this, [this](bool active) {
63+ if (active && m_currentWorspace) {
64+ m_currentWorspace->activate();
65+ }
66+ });
67+
68+ WorkspaceManager::instance()->createWorkspace()->assign(m_workspaces.data());
69+ WorkspaceManager::instance()->createWorkspace()->assign(m_workspaces.data());
70+}
71+
72+Screen::Screen(const Screen &other)
73+ : qtmir::Screen(nullptr)
74+ , m_wrapped(other.m_wrapped)
75+ , m_workspaces(new WorkspaceModelProxy(other.m_workspaces.data()))
76+{
77+ QQmlEngine::setObjectOwnership(m_workspaces.data(), QQmlEngine::CppOwnership);
78+
79+ connect(m_wrapped, &qtmir::Screen::usedChanged, this, &Screen::usedChanged);
80+ connect(m_wrapped, &qtmir::Screen::nameChanged, this, &Screen::nameChanged);
81+ connect(m_wrapped, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeChanged);
82+ connect(m_wrapped, &qtmir::Screen::scaleChanged, this, &Screen::scaleChanged);
83+ connect(m_wrapped, &qtmir::Screen::formFactorChanged, this, &Screen::formFactorChanged);
84+ connect(m_wrapped, &qtmir::Screen::physicalSizeChanged, this, &Screen::physicalSizeChanged);
85+ connect(m_wrapped, &qtmir::Screen::positionChanged, this, &Screen::positionChanged);
86+ connect(m_wrapped, &qtmir::Screen::activeChanged, this, &Screen::activeChanged);
87+ connect(m_wrapped, &qtmir::Screen::currentModeIndexChanged, this, &Screen::currentModeIndexChanged);
88+ connect(m_wrapped, &qtmir::Screen::availableModesChanged, this, &Screen::availableModesChanged);
89+}
90+
91+void Screen::resetCurrentWorkspace()
92+{
93+ auto newCurrent = m_workspaces->rowCount() > 0 ? m_workspaces->get(0) : nullptr;
94+ if (m_currentWorspace != newCurrent) {
95+ m_currentWorspace = newCurrent;
96+ Q_EMIT currentWorkspaceChanged(newCurrent);
97+ }
98+}
99+
100+qtmir::OutputId Screen::outputId() const
101+{
102+ return m_wrapped->outputId();
103+}
104+
105+bool Screen::used() const
106+{
107+ return m_wrapped->used();
108+}
109+
110+QString Screen::name() const
111+{
112+ return m_wrapped->name();
113+}
114+
115+float Screen::scale() const
116+{
117+ return m_wrapped->scale();
118+}
119+
120+QSizeF Screen::physicalSize() const
121+{
122+ return m_wrapped->physicalSize();
123+}
124+
125+qtmir::FormFactor Screen::formFactor() const
126+{
127+ return m_wrapped->formFactor();
128+}
129+
130+qtmir::OutputTypes Screen::outputType() const
131+{
132+ return m_wrapped->outputType();
133+}
134+
135+MirPowerMode Screen::powerMode() const
136+{
137+ return m_wrapped->powerMode();
138+}
139+
140+Qt::ScreenOrientation Screen::orientation() const
141+{
142+ return m_wrapped->orientation();
143+}
144+
145+QPoint Screen::position() const
146+{
147+ return m_wrapped->position();
148+}
149+
150+QQmlListProperty<qtmir::ScreenMode> Screen::availableModes()
151+{
152+ return m_wrapped->availableModes();
153+}
154+
155+uint Screen::currentModeIndex() const
156+{
157+ return m_wrapped->currentModeIndex();
158+}
159+
160+bool Screen::isActive() const
161+{
162+ return m_wrapped->isActive();
163+}
164+
165+qtmir::ScreenConfiguration *Screen::beginConfiguration() const
166+{
167+ return m_wrapped->beginConfiguration();
168+}
169+
170+bool Screen::applyConfiguration(qtmir::ScreenConfiguration *configuration)
171+{
172+ return m_wrapped->applyConfiguration(configuration);
173+}
174+
175+WorkspaceModel *Screen::workspaces() const
176+{
177+ return m_workspaces.data();
178+}
179+
180+Workspace *Screen::currentWorkspace() const
181+{
182+ return m_currentWorspace.data();
183+}
184+
185+void Screen::setCurrentWorkspace(Workspace *workspace)
186+{
187+ if (m_currentWorspace != workspace) {
188+ m_currentWorspace = workspace;
189+ Q_EMIT currentWorkspaceChanged(workspace);
190+ }
191+}
192+
193+void Screen::sync(Screen *proxy)
194+{
195+ if (!proxy) return;
196+
197+ workspaces()->sync(proxy->workspaces());
198+}
199+
200+void Screen::activate()
201+{
202+ setActive(true);
203+}
204+
205+void Screen::setActive(bool active)
206+{
207+ m_wrapped->setActive(active);
208+}
209+
210+QScreen *Screen::qscreen() const
211+{
212+ return m_wrapped->qscreen();
213+}
214+
215+ScreenProxy::ScreenProxy(ScreenProxy::Screen *const screen)
216+ : Screen(*screen)
217+ , m_original(screen)
218+{
219+}
220+
221+void ScreenProxy::addWorkspace()
222+{
223+ (new WorkspaceProxy(WorkspaceManager::instance()->createWorkspace()))->assign(workspaces());
224+}
plugins/WindowManager/Screen.hnew file+81
@@ -0,0 +1,81 @@
1+#ifndef SCREEN_H
2+#define SCREEN_H
3+
4+#include <qtmir/screen.h>
5+#include <QScopedPointer>
6+#include <QPointer>
7+
8+#include "WorkspaceModel.h"
9+
10+class ScreenProxy;
11+
12+class Screen : public qtmir::Screen
13+{
14+ Q_OBJECT
15+ Q_PROPERTY(WorkspaceModel* workspaces READ workspaces CONSTANT)
16+ Q_PROPERTY(Workspace* currentWorkspace READ currentWorkspace WRITE setCurrentWorkspace NOTIFY currentWorkspaceChanged)
17+
18+public:
19+ explicit Screen(qtmir::Screen*const wrapped);
20+
21+ qtmir::OutputId outputId() const override;
22+ bool used() const override;
23+ QString name() const override;
24+ float scale() const override;
25+ QSizeF physicalSize() const override;
26+ qtmir::FormFactor formFactor() const override;
27+ qtmir::OutputTypes outputType() const override;
28+ MirPowerMode powerMode() const override;
29+ Qt::ScreenOrientation orientation() const override;
30+ QPoint position() const override;
31+ QQmlListProperty<qtmir::ScreenMode> availableModes() override;
32+ uint currentModeIndex() const override;
33+ bool isActive() const override;
34+ void setActive(bool active) override;
35+
36+ QScreen* qscreen() const override;
37+
38+ qtmir::ScreenConfiguration *beginConfiguration() const override;
39+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration) override;
40+
41+ qtmir::Screen* wrapped() const { return m_wrapped; }
42+
43+ WorkspaceModel* workspaces() const;
44+
45+ Workspace *currentWorkspace() const;
46+ void setCurrentWorkspace(Workspace* workspace);
47+
48+ void sync(Screen* proxy);
49+
50+public Q_SLOTS:
51+ void activate();
52+
53+Q_SIGNALS:
54+ void currentWorkspaceChanged(Workspace*);
55+
56+protected:
57+ Screen(Screen const& other);
58+
59+ void resetCurrentWorkspace();
60+
61+ qtmir::Screen*const m_wrapped;
62+ const QScopedPointer<WorkspaceModel> m_workspaces;
63+ QPointer<Workspace> m_currentWorspace;
64+};
65+
66+class ScreenProxy : public Screen
67+{
68+ Q_OBJECT
69+public:
70+ explicit ScreenProxy(Screen*const screen);
71+
72+ Screen* proxyObject() const { return m_original.data(); }
73+
74+public Q_SLOTS:
75+ void addWorkspace();
76+
77+private:
78+ const QPointer<Screen> m_original;
79+};
80+
81+#endif // SCREEN_H
plugins/WindowManager/ScreenWindow.cppnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2016-2017 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 "ScreenWindow.h"
18+#include "Screen.h"
19+
20+// Qt
21+#include <QGuiApplication>
22+#include <QDebug>
23+
24+ScreenWindow::ScreenWindow(QQuickWindow *parent)
25+ : QQuickWindow(parent)
26+{
27+ if (qGuiApp->platformName() != QLatin1String("mirserver")) {
28+ qCritical("Not using 'mirserver' QPA plugin. Using ScreenWindow may produce unknown results.");
29+ }
30+}
31+
32+ScreenWindow::~ScreenWindow()
33+{
34+}
35+
36+Screen *ScreenWindow::screenWrapper() const
37+{
38+ return m_screen.data();
39+}
40+
41+void ScreenWindow::setScreenWrapper(Screen *screen)
42+{
43+ if (m_screen != screen) {
44+ m_screen = screen;
45+ Q_EMIT screenWrapperChanged();
46+ }
47+ QQuickWindow::setScreen(screen->qscreen());
48+}
plugins/WindowManager/ScreenWindow.hnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2016-2017 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 UNITY_SCREENWINDOW_H
18+#define UNITY_SCREENWINDOW_H
19+
20+#include <QQuickWindow>
21+#include <QPointer>
22+
23+#include "Screen.h"
24+
25+class ScreenAdapter;
26+
27+/*
28+ * ScreenWindow - wrapper of QQuickWindow to enable QML to specify destination screen.
29+**/
30+class ScreenWindow : public QQuickWindow
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(Screen *screen READ screenWrapper WRITE setScreenWrapper NOTIFY screenWrapperChanged)
34+ Q_PROPERTY(int winId READ winId CONSTANT)
35+public:
36+ explicit ScreenWindow(QQuickWindow *parent = 0);
37+ ~ScreenWindow();
38+
39+ Screen *screenWrapper() const;
40+ void setScreenWrapper(Screen *screen);
41+
42+Q_SIGNALS:
43+ void screenWrapperChanged();
44+
45+private:
46+ QPointer<Screen> m_screen;
47+};
48+
49+#endif // UNITY_SCREENWINDOW_H
plugins/WindowManager/Screens.cppnew file+205
@@ -0,0 +1,205 @@
1+/*
2+ * Copyright (C) 2016-2017 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 "Screens.h"
18+#include "Screen.h"
19+#include "WorkspaceManager.h"
20+
21+// qtmirserver
22+#include <qtmir/screens.h>
23+#include <QGuiApplication>
24+#include <QQmlEngine>
25+
26+// Qt
27+#include <QScreen>
28+#include <QWindow>
29+
30+Screens::Screens(const QSharedPointer<qtmir::Screens>& model)
31+ : m_wrapped(model)
32+{
33+ connect(m_wrapped.data(), &qtmir::Screens::screenAdded, this, &Screens::onScreenAdded);
34+ connect(m_wrapped.data(), &qtmir::Screens::screenRemoved, this, &Screens::onScreenRemoved);
35+ connect(m_wrapped.data(), &qtmir::Screens::activeScreenChanged, this, &Screens::activeScreenChanged);
36+
37+ Q_FOREACH(qtmir::Screen* screen, m_wrapped->screens()) {
38+ auto screenWrapper(new Screen(screen));
39+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
40+ m_screens.push_back(screenWrapper);
41+ }
42+}
43+
44+Screens::~Screens()
45+{
46+ qDeleteAll(m_screens);
47+ m_screens.clear();
48+}
49+
50+Screens::Screens(const Screens &other)
51+ : QAbstractListModel(nullptr)
52+ , m_wrapped(other.m_wrapped)
53+{
54+}
55+
56+QHash<int, QByteArray> Screens::roleNames() const
57+{
58+ QHash<int, QByteArray> roles;
59+ roles[ScreenRole] = "screen";
60+ return roles;
61+}
62+
63+QVariant Screens::data(const QModelIndex &index, int role) const
64+{
65+ if (!index.isValid() || index.row() >= m_screens.size()) {
66+ return QVariant();
67+ }
68+
69+ switch(role) {
70+ case ScreenRole:
71+ return QVariant::fromValue(m_screens.at(index.row()));
72+ } // switch
73+
74+ return QVariant();
75+}
76+
77+int Screens::rowCount(const QModelIndex &) const
78+{
79+ return count();
80+}
81+
82+int Screens::count() const
83+{
84+ return m_screens.size();
85+}
86+
87+QVariant Screens::activeScreen() const
88+{
89+ for (int i = 0; i < m_screens.count(); i++) {
90+ if (m_screens[i]->isActive()) return i;
91+ }
92+ return QVariant();
93+}
94+
95+ScreensProxy *Screens::createProxy()
96+{
97+ return new ScreensProxy(this);
98+}
99+
100+void Screens::sync(Screens *proxy)
101+{
102+ if (!proxy) return;
103+
104+ const auto& proxyList = proxy->list();
105+ for (int i = 0; i < m_screens.count() && i < proxyList.count(); ++i) {
106+ m_screens[i]->sync(proxyList[i]);
107+ }
108+
109+ // need to clean up all the workspaces we unassigned.
110+ WorkspaceManager::instance()->destroyFloatingWorkspaces();
111+}
112+
113+void Screens::activateScreen(const QVariant& vindex)
114+{
115+ bool ok = false;
116+ int index = vindex.toInt(&ok);
117+ if (!ok || index < 0 || m_screens.count() <= index) return;
118+
119+ auto screen = m_screens.at(index);
120+ screen->setActive(true);
121+}
122+
123+void Screens::onScreenAdded(qtmir::Screen *added)
124+{
125+ Q_FOREACH(auto screenWrapper, m_screens) {
126+ if (screenWrapper->wrapped() == added) return;
127+ }
128+
129+ beginInsertRows(QModelIndex(), count(), count());
130+ auto screenWrapper(new Screen(added));
131+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
132+ m_screens.push_back(screenWrapper);
133+ endInsertRows();
134+ Q_EMIT screenAdded(screenWrapper);
135+ Q_EMIT countChanged();
136+}
137+
138+void Screens::onScreenRemoved(qtmir::Screen *removed)
139+{
140+ int index = 0;
141+ QMutableVectorIterator<Screen*> iter(m_screens);
142+ while(iter.hasNext()) {
143+ auto screenWrapper = iter.next();
144+ if (screenWrapper->wrapped() == removed) {
145+
146+ beginRemoveRows(QModelIndex(), index, index);
147+ iter.remove();
148+ endRemoveRows();
149+
150+ Q_EMIT screenRemoved(screenWrapper);
151+ Q_EMIT countChanged();
152+
153+ screenWrapper->deleteLater();
154+ break;
155+ }
156+ index++;
157+ }
158+}
159+
160+ScreensProxy::ScreensProxy(Screens * const screens)
161+ : Screens(*screens)
162+ , m_original(screens)
163+{
164+ connect(screens, &Screens::screenAdded, this, [this](Screen *added) {
165+ Q_FOREACH(auto screen, m_screens) {
166+ auto proxy = static_cast<ScreenProxy*>(screen);
167+ if (proxy->proxyObject() == added) return;
168+ }
169+
170+ beginInsertRows(QModelIndex(), count(), count());
171+ auto screenWrapper(new ScreenProxy(added));
172+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
173+ m_screens.push_back(screenWrapper);
174+ endInsertRows();
175+ Q_EMIT screenAdded(screenWrapper);
176+ Q_EMIT countChanged();
177+ });
178+
179+ connect(screens, &Screens::screenRemoved, this, [this](Screen *removed) {
180+ int index = 0;
181+ QMutableVectorIterator<Screen*> iter(m_screens);
182+ while(iter.hasNext()) {
183+ auto proxy = static_cast<ScreenProxy*>(iter.next());
184+ if (proxy->proxyObject() == removed) {
185+
186+ beginRemoveRows(QModelIndex(), index, index);
187+ iter.remove();
188+ endRemoveRows();
189+
190+ Q_EMIT screenRemoved(proxy);
191+ Q_EMIT countChanged();
192+
193+ delete proxy;
194+ break;
195+ }
196+ index++;
197+ }
198+ });
199+
200+ Q_FOREACH(Screen* screen, screens->list()) {
201+ auto screenWrapper(new ScreenProxy(screen));
202+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
203+ m_screens.push_back(new ScreenProxy(screen));
204+ }
205+}
tests/mocks/Unity/Screens/screens.h → plugins/WindowManager/Screens.hrenamed+42−34
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it under
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
5 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,47 +14,38 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef SCREENS_H
17+#ifndef UNITY_SCREENS_H
18−#define SCREENS_H
18+#define UNITY_SCREENS_H
19
19
20#include <QAbstractListModel>
20#include <QAbstractListModel>
21−#include <QScreen>
21+#include <QSharedPointer>
22+#include <QPointer>
23+
24+namespace qtmir
25+{
26+class Screen;
27+class Screens;
28+}
22
29
23class Screen;
30class Screen;
31+class ScreensProxy;
24
32
25class Screens : public QAbstractListModel
33class Screens : public QAbstractListModel
26{
34{
27 Q_OBJECT
35 Q_OBJECT
28−
29 Q_PROPERTY(int count READ count NOTIFY countChanged)
36 Q_PROPERTY(int count READ count NOTIFY countChanged)
37+ Q_PROPERTY(QVariant activeScreen READ activeScreen WRITE activateScreen NOTIFY activeScreenChanged)
30
38
31public:
39public:
32 enum ItemRoles {
40 enum ItemRoles {
33− ScreenRole = Qt::UserRole + 1,
41+ ScreenRole = Qt::UserRole + 1
34− OutputTypeRole
35 };
42 };
36
43
37− enum OutputTypes {
44+ explicit Screens(const QSharedPointer<qtmir::Screens>& model);
38− Unknown,
45+ ~Screens();
39− VGA,
40− DVII,
41− DVID,
42− DVIA,
43− Composite,
44− SVideo,
45− LVDS,
46− Component,
47− NinePinDIN,
48− DisplayPort,
49− HDMIA,
50− HDMIB,
51− TV,
52− EDP
53− };
54− Q_ENUM(OutputTypes)
55
46
56− explicit Screens(QObject *parent = 0);
47+ Q_INVOKABLE ScreensProxy *createProxy();
57− virtual ~Screens() noexcept;
48+ Q_INVOKABLE void sync(Screens *proxy);
58
49
59 /* QAbstractItemModel */
50 /* QAbstractItemModel */
60 QHash<int, QByteArray> roleNames() const override;
51 QHash<int, QByteArray> roleNames() const override;
@@ -62,21 +53,38 @@ public:
62 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
53 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
63
54
64 int count() const;
55 int count() const;
56+ QVariant activeScreen() const;
57+
58+ const QVector<Screen*>& list() const { return m_screens; }
59+
60+public Q_SLOTS:
61+ void activateScreen(const QVariant& index);
65
62
66Q_SIGNALS:
63Q_SIGNALS:
67 void countChanged();
64 void countChanged();
68− void screenAdded(QScreen *screen);
65+ void activeScreenChanged();
69− void screenRemoved(QScreen *screen);
70
66
67+ void screenAdded(Screen* screen);
68+ void screenRemoved(Screen* screen);
69+
71−private:
70+private Q_SLOTS:
72− QList<Screen *> m_screenList;
71+ void onScreenAdded(qtmir::Screen *screen);
72+ void onScreenRemoved(qtmir::Screen *screen);
73+
74+protected:
75+ Screens(const Screens& other);
76+
77+ QVector<Screen*> m_screens;
78+ QSharedPointer<qtmir::Screens> m_wrapped;
73};
79};
74
80
75−class Screen
81+class ScreensProxy : public Screens
76{
82{
77public:
83public:
78− Screens::OutputTypes outputTypes = Screens::Unknown;
84+ ScreensProxy(Screens*const screens);
79− QScreen *qScreen = nullptr;
85+
86+private:
87+ const QPointer<Screens> m_original;
80};
88};
81
89
82#endif // SCREENS_H
90#endif // SCREENS_H
plugins/WindowManager/TopLevelWindowModel.cpp+193−40
@@ -24,11 +24,16 @@
24#include <unity/shell/application/SurfaceManagerInterface.h>
24#include <unity/shell/application/SurfaceManagerInterface.h>
25
25
26// Qt
26// Qt
27−#include <QGuiApplication>
28#include <QDebug>
27#include <QDebug>
29
28
30// local
29// local
31#include "Window.h"
30#include "Window.h"
31+#include "Workspace.h"
32+#include "wmpolicyinterface.h"
33+
34+// qtmir
35+#include <qtmir/mirserverapplication.h>
36+#include <qtmir/windowmodelnotifier.h>
32
37
33Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
38Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34
39
@@ -55,7 +60,6 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
55 beginResetModel();
60 beginResetModel();
56
61
57 if (m_applicationManager) {
62 if (m_applicationManager) {
58− m_windowModel.clear();
59 disconnect(m_applicationManager, 0, this, 0);
63 disconnect(m_applicationManager, 0, this, 0);
60 }
64 }
61
65
@@ -64,26 +68,27 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
64 if (m_applicationManager) {
68 if (m_applicationManager) {
65 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
69 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
66 this, [this](const QModelIndex &/*parent*/, int first, int last) {
70 this, [this](const QModelIndex &/*parent*/, int first, int last) {
71+ if (!m_workspace || !m_workspace->isActive())
72+ return;
73+
67− for (int i = first; i <= last; ++i) {
74+ for (int i = first; i <= last; ++i) {
68− auto application = m_applicationManager->get(i);
75+ auto application = m_applicationManager->get(i);
69− addApplication(application);
76+ addApplication(application);
70− }
77+ }
71− });
78+ });
72
79
73 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
80 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
74 this, [this](const QModelIndex &/*parent*/, int first, int last) {
81 this, [this](const QModelIndex &/*parent*/, int first, int last) {
75− for (int i = first; i <= last; ++i) {
82+ for (int i = first; i <= last; ++i) {
76− auto application = m_applicationManager->get(i);
83+ auto application = m_applicationManager->get(i);
77− removeApplication(application);
84+ removeApplication(application);
78− }
79− });
85+ }
80−
86+ });
81− for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82− auto application = m_applicationManager->get(i);
83− addApplication(application);
84− }
85 }
87 }
86
88
89+ Q_EMIT applicationManagerChanged(m_applicationManager);
90+ refreshWindows();
91+
87 endResetModel();
92 endResetModel();
88 m_modelState = IdleState;
93 m_modelState = IdleState;
89}
94}
@@ -96,6 +101,11 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
96
101
97 DEBUG_MSG << "(" << surfaceManager << ")";
102 DEBUG_MSG << "(" << surfaceManager << ")";
98
103
104+ Q_ASSERT(m_modelState == IdleState);
105+ m_modelState = ResettingState;
106+
107+ beginResetModel();
108+
99 if (m_surfaceManager) {
109 if (m_surfaceManager) {
100 disconnect(m_surfaceManager, 0, this, 0);
110 disconnect(m_surfaceManager, 0, this, 0);
101 }
111 }
@@ -107,9 +117,44 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
107 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
117 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
118 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
119 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
120+
121+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAddedToWorkspace,
122+ this, &TopLevelWindowModel::onSurfacesAddedToWorkspace);
123+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAboutToBeRemovedFromWorkspace,
124+ this, &TopLevelWindowModel::onSurfacesAboutToBeRemovedFromWorkspace);
110 }
125 }
111
126
127+ refreshWindows();
112 Q_EMIT surfaceManagerChanged(m_surfaceManager);
128 Q_EMIT surfaceManagerChanged(m_surfaceManager);
129+
130+ endResetModel();
131+ m_modelState = IdleState;
132+}
133+
134+void TopLevelWindowModel::setWorkspace(Workspace *workspace)
135+{
136+ if (workspace == m_workspace) {
137+ return;
138+ }
139+
140+ DEBUG_MSG << "(" << workspace << ")";
141+
142+ Q_ASSERT(m_modelState == IdleState);
143+ m_modelState = ResettingState;
144+
145+ beginResetModel();
146+
147+ if (m_workspace) {
148+ disconnect(m_workspace, 0, this, 0);
149+ }
150+
151+ m_workspace = workspace;
152+
153+ refreshWindows();
154+ Q_EMIT workspaceChanged(workspace);
155+
156+ endResetModel();
157+ m_modelState = IdleState;
113}
158}
114
159
115void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
160void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
@@ -179,6 +224,7 @@ void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *su
179 Window *window = createWindow(surface);
224 Window *window = createWindow(surface);
180
225
181 m_windowModel.prepend(ModelEntry(window, application));
226 m_windowModel.prepend(ModelEntry(window, application));
227+ m_allSurfaces.insert(surface);
182 if (surface) {
228 if (surface) {
183 connectSurface(surface);
229 connectSurface(surface);
184 }
230 }
@@ -313,6 +359,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
313 auto window = m_windowModel[i].window;
359 auto window = m_windowModel[i].window;
314 window->setSurface(nullptr);
360 window->setSurface(nullptr);
315 window->setFocused(false);
361 window->setFocused(false);
362+ m_allSurfaces.remove(surface);
316 INFO_MSG << " Removed surface from entry. After: " << toString();
363 INFO_MSG << " Removed surface from entry. After: " << toString();
317 }
364 }
318}
365}
@@ -328,38 +375,102 @@ Window *TopLevelWindowModel::createWindow(unityapi::MirSurfaceInterface *surface
328 return qmlWindow;
375 return qmlWindow;
329}
376}
330
377
378+
331−void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
379+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface */*surface*/)
332{
380{
333− DEBUG_MSG << "(" << surface << ")";
381+}
334
382
383+void TopLevelWindowModel::onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
384+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces)
385+{
386+ if (!m_workspace || !m_applicationManager) return;
387+ if (workspace != m_workspace->workspace()) return;
388+
389+ Q_FOREACH(auto surface, surfaces) {
390+ if (m_allSurfaces.contains(surface)) continue;
391+
335− if (surface->parentSurface()) {
392+ if (surface->parentSurface()) {
336− // Wrap it in a Window so that we keep focusedWindow() up to date.
393+ // Wrap it in a Window so that we keep focusedWindow() up to date.
337− Window *window = createWindow(surface);
394+ Window *window = createWindow(surface);
338− connect(surface, &QObject::destroyed, window, [=](){
395+ connect(surface, &QObject::destroyed, window, [=](){
339− window->setSurface(nullptr);
396+ window->setSurface(nullptr);
340− window->deleteLater();
397+ window->deleteLater();
341− });
398+ });
342− } else {
343− if (surface->type() == Mir::InputMethodType) {
344− setInputMethodWindow(createWindow(surface));
345 } else {
399 } else {
346− auto *application = m_applicationManager->findApplicationWithSurface(surface);
400+ if (surface->type() == Mir::InputMethodType) {
347− if (application) {
401+ setInputMethodWindow(createWindow(surface));
348− prependSurface(surface, application);
349 } else {
402 } else {
403+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
404+ if (application) {
405+ prependSurface(surface, application);
406+ } else {
350− // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
407+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
351− // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
408+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
352− // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
409+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
353− Window *promptWindow = createWindow(surface);
410+ Window *promptWindow = createWindow(surface);
354− connect(surface, &QObject::destroyed, promptWindow, [=](){
411+ connect(surface, &QObject::destroyed, promptWindow, [=](){
355− promptWindow->setSurface(nullptr);
412+ promptWindow->setSurface(nullptr);
356− promptWindow->deleteLater();
413+ promptWindow->deleteLater();
357− });
414+ });
415+ }
358 }
416 }
359 }
417 }
360 }
418 }
361}
419}
362
420
421+void TopLevelWindowModel::onSurfacesAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace> &workspace,
422+ const QVector<unity::shell::application::MirSurfaceInterface *> surfaces)
423+{
424+ if (!m_workspace || workspace != m_workspace->workspace()) return;
425+
426+ int start = -1;
427+ int end = -1;
428+ for (auto iter = surfaces.constBegin(); iter != surfaces.constEnd();) {
429+ auto surface = *iter;
430+ iter++;
431+
432+ // Do removals in adjacent blocks.
433+ start = end = indexOf(surface);
434+ if (start == -1) {
435+ // probably a child surface
436+ m_allSurfaces.remove(surface);
437+ continue;
438+ }
439+ while(iter != surfaces.constEnd()) {
440+ int index = indexOf(*iter);
441+ if (index != end+1) {
442+ break;
443+ }
444+ end++;
445+ iter++;
446+ }
447+
448+ if (m_modelState == IdleState) {
449+ beginRemoveRows(QModelIndex(), start, end);
450+ m_modelState = RemovingState;
451+ } else {
452+ Q_ASSERT(m_modelState == ResettingState);
453+ // No point in signaling anything if we're resetting the whole model
454+ }
455+
456+ for (int index = start; index <= end; index++) {
457+ auto window = m_windowModel[start].window;
458+ window->setSurface(nullptr);
459+ window->setFocused(false);
460+
461+ m_windowModel.removeAt(start);
462+ m_allSurfaces.remove(window->surface());
463+ }
464+
465+ if (m_modelState == RemovingState) {
466+ endRemoveRows();
467+ Q_EMIT countChanged();
468+ Q_EMIT listChanged();
469+ m_modelState = IdleState;
470+ }
471+ }
472+}
473+
363void TopLevelWindowModel::removeAt(int index)
474void TopLevelWindowModel::removeAt(int index)
364{
475{
365 if (m_modelState == IdleState) {
476 if (m_modelState == IdleState) {
@@ -376,6 +487,7 @@ void TopLevelWindowModel::removeAt(int index)
376 window->setFocused(false);
487 window->setFocused(false);
377
488
378 m_windowModel.removeAt(index);
489 m_windowModel.removeAt(index);
490+ m_allSurfaces.remove(window->surface());
379
491
380 if (m_modelState == RemovingState) {
492 if (m_modelState == RemovingState) {
381 endRemoveRows();
493 endRemoveRows();
@@ -674,3 +786,44 @@ void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
674 }
786 }
675 }
787 }
676}
788}
789+
790+void TopLevelWindowModel::refreshWindows()
791+{
792+ DEBUG_MSG << "()";
793+
794+ m_windowModel.clear();
795+ m_allSurfaces.clear();
796+
797+ if (!m_workspace || !m_applicationManager) return;
798+ WMPolicyInterface::instance()->forEachWindowInWorkspace(m_workspace->workspace(), [this](const miral::Window &window) {
799+ auto surface = m_surfaceManager->surfaceFor(window);
800+ if (surface) {
801+ if (surface->parentSurface()) {
802+ // Wrap it in a Window so that we keep focusedWindow() up to date.
803+ Window *window = createWindow(surface);
804+ connect(surface, &QObject::destroyed, window, [=](){
805+ window->setSurface(nullptr);
806+ window->deleteLater();
807+ });
808+ } else {
809+ if (surface->type() == Mir::InputMethodType) {
810+ setInputMethodWindow(createWindow(surface));
811+ } else {
812+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
813+ if (application) {
814+ prependSurface(surface, application);
815+ } else {
816+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
817+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
818+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
819+ Window *promptWindow = createWindow(surface);
820+ connect(surface, &QObject::destroyed, promptWindow, [=](){
821+ promptWindow->setSurface(nullptr);
822+ promptWindow->deleteLater();
823+ });
824+ }
825+ }
826+ }
827+ }
828+ });
829+}
plugins/WindowManager/TopLevelWindowModel.h+23
@@ -20,9 +20,14 @@
20#include <QAbstractListModel>
20#include <QAbstractListModel>
21#include <QLoggingCategory>
21#include <QLoggingCategory>
22
22
23+#include <memory>
24+
23Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
25Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
24
26
25class Window;
27class Window;
28+class Workspace;
29+
30+namespace miral { class Workspace; }
26
31
27namespace unity {
32namespace unity {
28 namespace shell {
33 namespace shell {
@@ -70,6 +75,11 @@ class TopLevelWindowModel : public QAbstractListModel
70 */
75 */
71 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
76 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
72
77
78+ Q_PROPERTY(Workspace* workspace
79+ READ workspace
80+ WRITE setWorkspace
81+ NOTIFY workspaceChanged)
82+
73 Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
83 Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
74 READ surfaceManager
84 READ surfaceManager
75 WRITE setSurfaceManager
85 WRITE setSurfaceManager
@@ -111,6 +121,9 @@ public:
111
121
112 // Own API
122 // Own API
113
123
124+ Workspace* workspace() const { return m_workspace; }
125+ void setWorkspace(Workspace* workspace);
126+
114 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
127 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
115 Window* focusedWindow() const;
128 Window* focusedWindow() const;
116
129
@@ -178,8 +191,14 @@ Q_SIGNALS:
178
191
179 void nextIdChanged();
192 void nextIdChanged();
180
193
194+ void workspaceChanged(Workspace*);
195+
181private Q_SLOTS:
196private Q_SLOTS:
182 void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
197 void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
198+ void onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
199+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces);
200+ void onSurfacesAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
201+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces);
183 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
202 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
184
203
185 void onModificationsStarted();
204 void onModificationsStarted();
@@ -219,6 +238,7 @@ private:
219 void activateEmptyWindow(Window *window);
238 void activateEmptyWindow(Window *window);
220
239
221 void activateTopMostWindowWithoutId(int forbiddenId);
240 void activateTopMostWindowWithoutId(int forbiddenId);
241+ void refreshWindows();
222
242
223 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
243 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
224
244
@@ -235,6 +255,9 @@ private:
235 QVector<ModelEntry> m_windowModel;
255 QVector<ModelEntry> m_windowModel;
236 Window* m_inputMethodWindow{nullptr};
256 Window* m_inputMethodWindow{nullptr};
237 Window* m_focusedWindow{nullptr};
257 Window* m_focusedWindow{nullptr};
258+ Workspace* m_workspace{nullptr};
259+ // track all the surfaces we've been told about.
260+ QSet<unity::shell::application::MirSurfaceInterface*> m_allSurfaces;
238
261
239 int m_nextId{1};
262 int m_nextId{1};
240 // Just something big enough that we don't risk running out of unused id numbers.
263 // Just something big enough that we don't risk running out of unused id numbers.
plugins/WindowManager/WindowManagerPlugin.cpp+31−1
@@ -16,16 +16,46 @@
16
16
17#include "WindowManagerPlugin.h"
17#include "WindowManagerPlugin.h"
18
18
19+#include "Screens.h"
20+#include "Screen.h"
21+#include "ScreenWindow.h"
19#include "TopLevelWindowModel.h"
22#include "TopLevelWindowModel.h"
20#include "Window.h"
23#include "Window.h"
24+#include "WorkspaceManager.h"
25+#include "Workspace.h"
26+#include "WorkspaceModel.h"
21
27
22#include <QtQml>
28#include <QtQml>
29+#include <qtmir/qtmir.h>
30+
31+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
32+{
33+ Q_UNUSED(engine)
34+ Q_UNUSED(scriptEngine)
35+ return WorkspaceManager::instance();
36+}
37+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
38+ Q_UNUSED(engine);
39+ Q_UNUSED(scriptEngine);
40+ return new Screens(qtmir::get_screen_model());
41+}
23
42
24void WindowManagerPlugin::registerTypes(const char *uri)
43void WindowManagerPlugin::registerTypes(const char *uri)
25{
44{
26 qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
45 qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
46+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
47+ qmlRegisterUncreatableType<WorkspaceModel>(uri, 1, 0, "WorkspaceModel", "Not a creatable type");
48+ qmlRegisterSingletonType<Screens>(uri, 1, 0, "Screens", screensSingleton);
49+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", "ScreenMode is not creatable.");
50+ qmlRegisterUncreatableType<Workspace>(uri, 1, 0, "Workspace", "Workspace is not creatable.");
27
51
28− qRegisterMetaType<Window*>("Window*");
52+ qRegisterMetaType<Screen*>("Screen*");
53+ qRegisterMetaType<ScreensProxy*>("ScreensProxy*");
54+ qRegisterMetaType<Workspace*>("Workspace*");
29
55
56+ qRegisterMetaType<Window*>("Window*");
30 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
57 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
58+
59+ qmlRegisterType<ScreenWindow>(uri, 1, 0, "ScreenWindow");
60+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
31}
61}
plugins/WindowManager/Workspace.cppnew file+146
@@ -0,0 +1,146 @@
1+/*
2+ * Copyright (C) 2017 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 "Workspace.h"
18+#include "WorkspaceModel.h"
19+#include "WorkspaceManager.h"
20+#include "TopLevelWindowModel.h"
21+
22+#include "wmpolicyinterface.h"
23+
24+int nextWorkspace = 0;
25+
26+Workspace::Workspace(QObject *parent)
27+ : QObject(parent)
28+ , m_workspace(WMPolicyInterface::instance()->createWorkspace())
29+ , m_model(nullptr)
30+ , m_active(false)
31+{
32+ setObjectName((QString("Wks%1").arg(nextWorkspace++)));
33+
34+ connect(WorkspaceManager::instance(), &WorkspaceManager::activeWorkspaceChanged, this, [this]() {
35+ bool newActive = WorkspaceManager::instance()->activeWorkspace() == this;
36+ if (newActive != m_active) {
37+ m_active = newActive;
38+ Q_EMIT activeChanged(m_active);
39+
40+ if (m_active) {
41+ WMPolicyInterface::instance()->setActiveWorkspace(m_workspace);
42+ }
43+ }
44+ });
45+}
46+
47+Workspace::Workspace(const Workspace &other)
48+ : QObject(nullptr)
49+ , m_workspace(other.m_workspace)
50+ , m_model(nullptr)
51+ , m_active(other.m_active)
52+{
53+ setObjectName(other.objectName());
54+
55+ connect(&other, &Workspace::activeChanged, this, [this](bool active) {
56+ m_active = active;
57+ Q_EMIT activeChanged(m_active);
58+ });
59+}
60+
61+Workspace::~Workspace()
62+{
63+ if (m_model) {
64+ m_model->remove(this);
65+ }
66+}
67+
68+void Workspace::moveWindowsTo(Workspace *workspace)
69+{
70+ WMPolicyInterface::instance()->moveWorkspaceContentToWorkspace(workspace->m_workspace, m_workspace);
71+}
72+
73+void Workspace::activate()
74+{
75+ WorkspaceManager::instance()->setActiveWorkspace(this);
76+}
77+
78+void Workspace::assign(WorkspaceModel *model, const QVariant& vIndex)
79+{
80+ if (m_model == model) return;
81+
82+ if (m_model) {
83+ disconnect(m_model, 0, this, 0);
84+ m_model->remove(this);
85+ }
86+
87+ m_model = model;
88+
89+ if (model) {
90+ int index = m_model->rowCount();
91+ if (vIndex.isValid() && vIndex.canConvert(QVariant::Int)) {
92+ index = vIndex.toInt();
93+ }
94+ m_model->insert(index, this);
95+
96+ connect(m_model, &QObject::destroyed, this, [this]() {
97+ m_model = nullptr;
98+ Q_EMIT unassigned();
99+ });
100+ Q_EMIT assigned();
101+ } else {
102+ Q_EMIT unassigned();
103+ }
104+}
105+
106+void Workspace::unassign()
107+{
108+ assign(nullptr);
109+}
110+
111+void Workspace::release()
112+{
113+ WMPolicyInterface::instance()->releaseWorkspace(m_workspace);
114+ deleteLater();
115+}
116+
117+bool Workspace::isAssigned() const
118+{
119+ return m_model != nullptr;
120+}
121+
122+WorkspaceProxy::WorkspaceProxy(Workspace * const workspace)
123+ : Workspace(*workspace)
124+ , m_original(workspace)
125+{
126+}
127+
128+void WorkspaceProxy::assign(WorkspaceModel *model, const QVariant &index)
129+{
130+ Workspace::assign(model, index);
131+}
132+
133+void WorkspaceProxy::unassign()
134+{
135+ Workspace::unassign();
136+}
137+
138+bool WorkspaceProxy::isActive() const
139+{
140+ return m_original ? m_original->isActive() : false;
141+}
142+
143+void WorkspaceProxy::activate()
144+{
145+ if (m_original) m_original->activate();
146+}
plugins/WindowManager/Workspace.hnew file+100
@@ -0,0 +1,100 @@
1+/*
2+ * Copyright (C) 2017 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 WINDOWMANAGER_WORKSPACE_H
18+#define WINDOWMANAGER_WORKSPACE_H
19+
20+#include <QObject>
21+#include <QVariant>
22+#include <QPointer>
23+
24+#include <memory>
25+#include <functional>
26+
27+class WorkspaceModel;
28+class TopLevelWindowModel;
29+
30+namespace miral { class Workspace; }
31+
32+namespace unity {
33+ namespace shell {
34+ namespace application {
35+ class MirSurfaceInterface;
36+ }
37+ }
38+}
39+
40+class Workspace : public QObject
41+{
42+ Q_OBJECT
43+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
44+public:
45+ ~Workspace();
46+
47+ virtual void assign(WorkspaceModel* model, const QVariant& index = QVariant());
48+ virtual void unassign();
49+ void release();
50+
51+ virtual bool isActive() const { return m_active; }
52+ bool isAssigned() const;
53+
54+ void moveWindowsTo(Workspace* workspace);
55+
56+ std::shared_ptr<miral::Workspace> workspace() const { return m_workspace; }
57+
58+
59+public Q_SLOTS:
60+ virtual void activate();
61+
62+Q_SIGNALS:
63+ void assigned();
64+ void unassigned();
65+
66+ void activeChanged(bool);
67+
68+protected:
69+ Workspace(QObject *parent = 0);
70+ Workspace(Workspace const& other);
71+
72+ std::shared_ptr<miral::Workspace> m_workspace;
73+ WorkspaceModel* m_model;
74+ bool m_active;
75+
76+ friend class WorkspaceManager;
77+};
78+
79+class WorkspaceProxy : public Workspace
80+{
81+ Q_OBJECT
82+public:
83+ WorkspaceProxy(Workspace*const workspace);
84+
85+ Q_INVOKABLE void assign(WorkspaceModel* model, const QVariant& index = QVariant()) override;
86+
87+ bool isActive() const override;
88+
89+ void activate() override;
90+
91+ Workspace* proxyObject() const { return m_original.data(); }
92+
93+public Q_SLOTS:
94+ void unassign() override;
95+
96+private:
97+ const QPointer<Workspace> m_original;
98+};
99+
100+#endif // WINDOWMANAGER_WORKSPACE_H
plugins/WindowManager/WorkspaceManager.cppnew file+117
@@ -0,0 +1,117 @@
1+/*
2+ * Copyright (C) 2017 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 "WorkspaceManager.h"
18+#include "Workspace.h"
19+#include "TopLevelWindowModel.h"
20+
21+// Qt
22+#include <QGuiApplication>
23+#include <QScreen>
24+#include <QQmlEngine>
25+
26+WorkspaceManager *WorkspaceManager::instance()
27+{
28+ static WorkspaceManager* workspaceManager(new WorkspaceManager());
29+ return workspaceManager;
30+}
31+
32+WorkspaceManager::WorkspaceManager()
33+ : m_activeWorkspace(nullptr)
34+{
35+}
36+
37+Workspace *WorkspaceManager::createWorkspace()
38+{
39+ auto workspace = new Workspace(this);
40+ QQmlEngine::setObjectOwnership(workspace, QQmlEngine::CppOwnership);
41+ m_allWorkspaces.insert(workspace);
42+ m_floatingWorkspaces.append(workspace);
43+
44+ connect(workspace, &Workspace::assigned, this, [this, workspace]() {
45+ m_floatingWorkspaces.removeOne(workspace);
46+ Q_EMIT floatingWorkspacesChanged();
47+ });
48+ connect(workspace, &Workspace::unassigned, this, [this, workspace]() {
49+ m_floatingWorkspaces.append(workspace);
50+ Q_EMIT floatingWorkspacesChanged();
51+ });
52+
53+ if (m_allWorkspaces.count() == 0 && m_activeWorkspace) {
54+ setActiveWorkspace(nullptr);
55+ Q_EMIT activeWorkspaceChanged();
56+ } else if (m_allWorkspaces.count() == 1) {
57+ setActiveWorkspace(workspace);
58+ }
59+
60+ return workspace;
61+}
62+
63+void WorkspaceManager::destroyWorkspace(Workspace *workspace)
64+{
65+ if (!workspace) return;
66+
67+ if (workspace->isAssigned()) {
68+ workspace->unassign();
69+ }
70+ m_floatingWorkspaces.removeOne(workspace);
71+ m_allWorkspaces.remove(workspace);
72+ Q_EMIT floatingWorkspacesChanged();
73+
74+ if (m_activeWorkspace == workspace) {
75+ Q_ASSERT(false); // Shouldn't happen, should have chosen something by now.
76+ // just choose anything.
77+ setActiveWorkspace(m_allWorkspaces.count() ? *m_allWorkspaces.begin() : nullptr);
78+ }
79+ if (m_activeWorkspace) {
80+ workspace->moveWindowsTo(m_activeWorkspace);
81+ }
82+
83+ disconnect(workspace, 0, this, 0);
84+ workspace->release();
85+}
86+
87+QQmlListProperty<Workspace> WorkspaceManager::floatingWorkspaces()
88+{
89+ return QQmlListProperty<Workspace>(this, m_floatingWorkspaces);
90+}
91+
92+void WorkspaceManager::destroyFloatingWorkspaces()
93+{
94+ QList<Workspace*> dpCpy(m_floatingWorkspaces);
95+ Q_FOREACH(auto workspace, dpCpy) {
96+ destroyWorkspace(workspace);
97+ }
98+}
99+
100+Workspace *WorkspaceManager::activeWorkspace() const
101+{
102+ return m_activeWorkspace;
103+}
104+
105+void WorkspaceManager::setActiveWorkspace(Workspace *workspace)
106+{
107+ if (workspace != m_activeWorkspace) {
108+ m_activeWorkspace = workspace;
109+ Q_EMIT activeWorkspaceChanged();
110+ }
111+}
112+
113+void WorkspaceManager::setActiveWorkspace2(Workspace *workspace)
114+{
115+ if (!workspace) return;
116+ workspace->activate();
117+}
plugins/WindowManager/WorkspaceManager.hnew file+58
@@ -0,0 +1,58 @@
1+/*
2+ * Copyright (C) 2017 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 WORKSPACEMANAGER_H
18+#define WORKSPACEMANAGER_H
19+
20+#include "WorkspaceModel.h"
21+#include <QQmlListProperty>
22+
23+class Workspace;
24+class ScreensProxy;
25+
26+class WorkspaceManager : public QObject
27+{
28+ Q_OBJECT
29+ Q_PROPERTY(Workspace* activeWorkspace READ activeWorkspace WRITE setActiveWorkspace2 NOTIFY activeWorkspaceChanged)
30+ Q_PROPERTY(QQmlListProperty<Workspace> floatingWorkspaces READ floatingWorkspaces NOTIFY floatingWorkspacesChanged)
31+
32+public:
33+ static WorkspaceManager* instance();
34+
35+ Workspace* activeWorkspace() const;
36+ void setActiveWorkspace(Workspace* workspace);
37+
38+ Workspace* createWorkspace();
39+ void destroyWorkspace(Workspace* workspace);
40+
41+ QQmlListProperty<Workspace> floatingWorkspaces();
42+ void destroyFloatingWorkspaces();
43+
44+Q_SIGNALS:
45+ void activeWorkspaceChanged();
46+ void floatingWorkspacesChanged();
47+
48+private:
49+ WorkspaceManager();
50+
51+ void setActiveWorkspace2(Workspace* workspace);
52+
53+ QSet<Workspace*> m_allWorkspaces;
54+ QList<Workspace*> m_floatingWorkspaces;
55+ Workspace* m_activeWorkspace;
56+};
57+
58+#endif // WORKSPACEMANAGER_H
plugins/WindowManager/WorkspaceModel.cppnew file+191
@@ -0,0 +1,191 @@
1+/*
2+ * Copyright (C) 2017 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 "WorkspaceModel.h"
18+#include "WorkspaceManager.h"
19+#include "Workspace.h"
20+
21+#include <QQmlEngine>
22+
23+Q_LOGGING_CATEGORY(WORKSPACES, "Workspaces", QtInfoMsg)
24+
25+#define DEBUG_MSG qCDebug(WORKSPACES).nospace().noquote() << __func__
26+#define INFO_MSG qCInfo(WORKSPACES).nospace().noquote() << __func__
27+
28+WorkspaceModel::WorkspaceModel(QObject *parent)
29+ : QAbstractListModel(parent)
30+{
31+}
32+
33+void WorkspaceModel::append(Workspace *workspace)
34+{
35+ insert(m_workspaces.count(), workspace);
36+}
37+
38+void WorkspaceModel::insert(int index, Workspace *workspace)
39+{
40+ beginInsertRows(QModelIndex(), index, index);
41+
42+ m_workspaces.insert(index, workspace);
43+
44+ endInsertRows();
45+
46+ Q_EMIT workspaceAdded(workspace);
47+ Q_EMIT countChanged();
48+}
49+
50+void WorkspaceModel::remove(Workspace *workspace)
51+{
52+ int index = m_workspaces.indexOf(workspace);
53+ if (index < 0) return;
54+
55+ beginRemoveRows(QModelIndex(), index, index);
56+
57+ m_workspaces.removeAt(index);
58+
59+ endRemoveRows();
60+
61+ Q_EMIT workspaceRemoved(workspace);
62+ Q_EMIT countChanged();
63+}
64+
65+void WorkspaceModel::move(int from, int to)
66+{
67+ if (from == to) return;
68+ DEBUG_MSG << " from=" << from << " to=" << to;
69+
70+ if (from >= 0 && from < m_workspaces.size() && to >= 0 && to < m_workspaces.size()) {
71+ QModelIndex parent;
72+
73+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
74+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
75+ const auto &window = m_windowModel.takeAt(from);
76+ m_workspaces.insert(to, window);
77+#else
78+ m_workspaces.move(from, to);
79+#endif
80+ endMoveRows();
81+ Q_EMIT countChanged();
82+ }
83+}
84+
85+int WorkspaceModel::indexOf(Workspace *workspace) const
86+{
87+ return m_workspaces.indexOf(workspace);
88+}
89+
90+Workspace *WorkspaceModel::get(int index) const
91+{
92+ if (index < 0 || index >= rowCount()) return nullptr;
93+ return m_workspaces.at(index);
94+}
95+
96+int WorkspaceModel::rowCount(const QModelIndex &) const
97+{
98+ return m_workspaces.count();
99+}
100+
101+QVariant WorkspaceModel::data(const QModelIndex &index, int role) const
102+{
103+ if (index.row() < 0 || index.row() >= m_workspaces.size())
104+ return QVariant();
105+
106+ if (role == WorkspaceRole) {
107+ Workspace *workspace = m_workspaces.at(index.row());
108+ return QVariant::fromValue(workspace);
109+ } else {
110+ return QVariant();
111+ }
112+}
113+
114+void WorkspaceModel::sync(WorkspaceModel *proxy)
115+{
116+ if (!proxy) return;
117+ const auto& proxyList = proxy->list();
118+
119+ // check for removals
120+ int removedIndexWhichWasActive = -1;
121+ QVector<Workspace*> dpCpy(this->list());
122+ Q_FOREACH(auto workspace, dpCpy) {
123+
124+ bool found = false;
125+ Q_FOREACH(auto p, proxyList) {
126+ auto workspaceProxy = static_cast<WorkspaceProxy*>(p);
127+ if (workspaceProxy->proxyObject() == workspace) {
128+ found = true;
129+ break;
130+ }
131+ }
132+ if (!found) {
133+ if (workspace->isActive()) {
134+ removedIndexWhichWasActive = indexOf(workspace);
135+ }
136+ workspace->unassign();
137+ }
138+ }
139+
140+ // existing
141+ QSet<Workspace*> newWorkspaces;
142+ for (int i = 0; i < proxyList.count(); i++) {
143+ auto workspaceProxy = static_cast<WorkspaceProxy*>(proxyList[i]);
144+ auto workspace = workspaceProxy->proxyObject();
145+
146+ int oldIndex = this->indexOf(workspace);
147+
148+ if (oldIndex < 0) {
149+ workspace->assign(this, QVariant(i));
150+ } else if (oldIndex != i) {
151+ this->move(oldIndex, i);
152+ }
153+ newWorkspaces.insert(workspace);
154+ }
155+
156+ // Make sure we have at least one workspace in the model.
157+ if (rowCount() == 0) {
158+ Workspace* workspace = WorkspaceManager::instance()->createWorkspace();
159+ workspace->assign(this);
160+ (new WorkspaceProxy(workspace))->assign(proxy);
161+ }
162+
163+ if (removedIndexWhichWasActive != -1) {
164+ int newActiveIndex = qMin(removedIndexWhichWasActive, this->rowCount()-1);
165+ Workspace* newActiveWorkspace = newActiveIndex >= 0 ? this->get(newActiveIndex) : nullptr;
166+
167+ WorkspaceManager::instance()->setActiveWorkspace(newActiveWorkspace);
168+ }
169+}
170+
171+
172+WorkspaceModelProxy::WorkspaceModelProxy(WorkspaceModel * const model)
173+ : m_original(model)
174+{
175+ Q_FOREACH(auto workspace, model->list()) {
176+ auto proxy = new WorkspaceProxy(workspace);
177+ QQmlEngine::setObjectOwnership(proxy, QQmlEngine::CppOwnership);
178+ proxy->assign(this);
179+ }
180+}
181+
182+WorkspaceModelProxy::~WorkspaceModelProxy()
183+{
184+ qDeleteAll(m_workspaces.toList()); // make a copy so the list doesnt edit itself during delete.
185+ m_workspaces.clear();
186+}
187+
188+void WorkspaceModelProxy::move(int from, int to)
189+{
190+ WorkspaceModel::move(from, to);
191+}
plugins/WindowManager/WorkspaceModel.hnew file+88
@@ -0,0 +1,88 @@
1+/*
2+ * Copyright (C) 2017 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 WORKSPACEMODEL_H
18+#define WORKSPACEMODEL_H
19+
20+#include <QAbstractListModel>
21+#include <QLoggingCategory>
22+#include <QPointer>
23+
24+Q_DECLARE_LOGGING_CATEGORY(WORKSPACES)
25+
26+class Workspace;
27+class WorkspaceModelProxy;
28+
29+class WorkspaceModel : public QAbstractListModel
30+{
31+ Q_OBJECT
32+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
33+public:
34+ /**
35+ * @brief The Roles supported by the model
36+ *
37+ * WorkspaceRole - A workspace.
38+ */
39+ enum Roles {
40+ WorkspaceRole = Qt::UserRole
41+ };
42+
43+ explicit WorkspaceModel(QObject *parent = 0);
44+
45+ void append(Workspace *workspace);
46+ void insert(int index, Workspace *workspace);
47+ void remove(Workspace* workspace);
48+ virtual void move(int from, int to);
49+
50+ int indexOf(Workspace *workspace) const;
51+ Q_INVOKABLE Workspace* get(int index) const;
52+
53+ // From QAbstractItemModel
54+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
55+ QVariant data(const QModelIndex& index, int role) const override;
56+ QHash<int, QByteArray> roleNames() const override {
57+ QHash<int, QByteArray> roleNames { {WorkspaceRole, "workspace"} };
58+ return roleNames;
59+ }
60+
61+ const QVector<Workspace*>& list() const { return m_workspaces; }
62+
63+ void sync(WorkspaceModel* proxy);
64+
65+Q_SIGNALS:
66+ void countChanged();
67+
68+ void workspaceAdded(Workspace *workspace);
69+ void workspaceRemoved(Workspace *workspace);
70+
71+protected:
72+ QVector<Workspace*> m_workspaces;
73+};
74+
75+class WorkspaceModelProxy : public WorkspaceModel
76+{
77+ Q_OBJECT
78+public:
79+ WorkspaceModelProxy(WorkspaceModel*const model);
80+ ~WorkspaceModelProxy();
81+
82+ Q_INVOKABLE void move(int from, int to) override;
83+
84+private:
85+ const QPointer<WorkspaceModel> m_original;
86+};
87+
88+#endif // WORKSPACEMODEL_H
qml/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−file(GLOB QML_JS_FILES *.qml *.js)
1+file(GLOB QML_JS_FILES *.qml *.js qmldir)
2
2
3install(FILES ${QML_JS_FILES}
3install(FILES ${QML_JS_FILES}
4 DESTINATION ${SHELL_APP_DIR}
4 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−pragma Singleton
18import QtQuick 2.4
17import QtQuick 2.4
19
18
20QtObject {
19QtObject {
qml/Components/PanelState/qmldir+1−1
@@ -1 +1 @@
1−singleton PanelState 1.0 PanelState.qml
1+PanelState 1.0 PanelState.qml
qml/Components/VirtualTouchPad.qml+14−20
@@ -18,22 +18,20 @@ import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Qt.labs.settings 1.0
20import Qt.labs.settings 1.0
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import UInput 0.1
22import UInput 0.1
23import "../Components"
23import "../Components"
24
24
25Item {
25Item {
26 id: root
26 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
31
27
32 Component.onCompleted: {
28 Component.onCompleted: {
29+ UInput.createMouse();
33 if (!settings.touchpadTutorialHasRun) {
30 if (!settings.touchpadTutorialHasRun) {
34 root.runTutorial()
31 root.runTutorial()
35 }
32 }
36 }
33 }
34+ Component.onDestruction: UInput.removeMouse()
37
35
38 function runTutorial() {
36 function runTutorial() {
39 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
37 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
85 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
83 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
86 && clickTimer.running) {
84 && clickTimer.running) {
87 clickTimer.stop();
85 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
89 isDoubleClick = true;
87 isDoubleClick = true;
90 }
88 }
91 isClick = true;
89 isClick = true;
@@ -104,7 +102,7 @@ Item {
104
102
105 onReleased: {
103 onReleased: {
106 if (isDoubleClick || isDrag) {
104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108 isDoubleClick = false;
106 isDoubleClick = false;
109 }
107 }
110 if (isClick) {
108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120 interval: 200
118 interval: 200
121 property int button: UInput.ButtonLeft
119 property int button: UInput.ButtonLeft
122 onTriggered: {
120 onTriggered: {
123− uinput.pressMouse(button);
121+ UInput.pressMouse(button);
124− uinput.releaseMouse(button);
122+ UInput.releaseMouse(button);
125 }
123 }
126 function scheduleClick(button) {
124 function scheduleClick(button) {
127 clickTimer.button = button;
125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138 isDrag = true;
136 isDrag = true;
139 }
137 }
140
138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142 }
140 }
143
141
144 function scroll(touchPoints) {
142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166 dh /= 2;
164 dh /= 2;
167 dv /= 2;
165 dv /= 2;
168
166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170 }
168 }
171
169
172 touchPoints: [
170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189 objectName: "leftButton"
187 objectName: "leftButton"
190 Layout.fillWidth: true
188 Layout.fillWidth: true
191 Layout.fillHeight: true
189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194 property bool highlight: false
192 property bool highlight: false
195 UbuntuShape {
193 UbuntuShape {
196 anchors.fill: parent
194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204 objectName: "rightButton"
202 objectName: "rightButton"
205 Layout.fillWidth: true
203 Layout.fillWidth: true
206 Layout.fillHeight: true
204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209 property bool highlight: false
207 property bool highlight: false
210 UbuntuShape {
208 UbuntuShape {
211 anchors.fill: parent
209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239 }
237 }
240 }
238 }
241
239
242− Screens {
243− id: screens
244− }
245−
246 InputMethod {
240 InputMethod {
247 id: inputMethod
241 id: inputMethod
248 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
242 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
249− enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
243+ enabled: Screens.count > 1 && settings.oskEnabled && !tutorial.running
250 objectName: "inputMethod"
244 objectName: "inputMethod"
251 anchors.fill: parent
245 anchors.fill: parent
252 }
246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
28 property var screen: Screen
28 property var screen: Screen
29 property var orientationLock: OrientationLock
29 property var orientationLock: OrientationLock
30
30
31+ property alias deviceConfiguration: _deviceConfiguration
31 DeviceConfiguration {
32 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
33 name: applicationArguments.deviceName
34 name: applicationArguments.deviceName
34 }
35 }
35
36
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
25import Unity.Session 0.1
25import Unity.Session 0.1
26
26
27import "." 0.1
27import "." 0.1
28+import ".." 0.1
28import "../Components"
29import "../Components"
29
30
30Showable {
31Showable {
@@ -227,7 +228,7 @@ Showable {
227 if (forcedUnlock && shown) {
228 if (forcedUnlock && shown) {
228 hideView();
229 hideView();
229 if (hideNow) {
230 if (hideNow) {
230− root.hideNow(); // skip hide animation
231+ ShellNotifier.greeter.hide(true); // skip hide animation
231 }
232 }
232 }
233 }
233 }
234 }
@@ -375,7 +376,7 @@ Showable {
375 onEmergencyCall: root.emergencyCall()
376 onEmergencyCall: root.emergencyCall()
376 onRequiredChanged: {
377 onRequiredChanged: {
377 if (!loader.item.required) {
378 if (!loader.item.required) {
378− root.hide();
379+ ShellNotifier.greeter.hide(false);
379 }
380 }
380 }
381 }
381 }
382 }
@@ -498,12 +499,29 @@ Showable {
498 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499 }
500 }
500
501
502+ Connections {
503+ target: ShellNotifier.greeter
504+ onHide: {
505+ if (now) {
506+ root.hideNow(); // skip hide animation
507+ } else {
508+ root.hide();
509+ }
510+ }
511+ }
512+
513+ Binding {
514+ target: ShellNotifier.greeter
515+ property: "shown"
516+ value: root.shown
517+ }
518+
501 Connections {
519 Connections {
502 target: DBusUnitySessionService
520 target: DBusUnitySessionService
503 onLockRequested: root.forceShow()
521 onLockRequested: root.forceShow()
504 onUnlocked: {
522 onUnlocked: {
505 root.forcedUnlock = true;
523 root.forcedUnlock = true;
506− root.hideNow();
524+ ShellNotifier.greeter.hide(true);
507 }
525 }
508 }
526 }
509
527
qml/Greeter/SecondaryGreeter.qmlnew file+75
@@ -0,0 +1,75 @@
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+import Ubuntu.Components 1.3
19+
20+import "../Components"
21+import ".." 0.1
22+
23+Showable {
24+ id: root
25+
26+ readonly property bool active: required || hasLockedApp
27+
28+ readonly property bool hasLockedApp: lockedApp !== ""
29+ readonly property bool locked: false
30+ readonly property bool waiting: false
31+ readonly property bool fullyShown: shown
32+
33+ property string lockedApp: ""
34+
35+ function forceShow() { show(); }
36+ property var notifyAppFocusRequested: (function(appId) { return; })
37+ property var notifyUserRequestedApp: (function(appId) { return; })
38+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
39+
40+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
41+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
42+
43+ shown: ShellNotifier.greeter.shown
44+ Component.onCompleted: opacity = shown ? 1 : 0
45+ visible: opacity != 0
46+
47+ Rectangle {
48+ anchors.fill: parent
49+ color: UbuntuColors.purple
50+ }
51+
52+ MouseArea {
53+ anchors.fill: parent
54+ acceptedButtons: Qt.AllButtons
55+ onWheel: wheel.accepted = true
56+ }
57+
58+ Connections {
59+ target: ShellNotifier.greeter
60+ onHide: {
61+ if (now) {
62+ root.hideNow(); // skip hide animation
63+ } else {
64+ root.hide();
65+ }
66+ }
67+ onShownChanged: {
68+ if (ShellNotifier.greeter.shown) {
69+ root.show();
70+ } else {
71+ root.hide();
72+ }
73+ }
74+ }
75+}
qml/OrientedShell.qml+7−10
@@ -18,7 +18,7 @@ import QtQuick 2.4
18import QtQuick.Window 2.2
18import QtQuick.Window 2.2
19import Unity.InputInfo 0.1
19import Unity.InputInfo 0.1
20import Unity.Session 0.1
20import Unity.Session 0.1
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import Utils 0.1
22import Utils 0.1
23import GSettings 1.0
23import GSettings 1.0
24import "Components"
24import "Components"
@@ -32,15 +32,16 @@ Item {
32 implicitWidth: units.gu(40)
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
33 implicitHeight: units.gu(71)
34
34
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+
35 onWidthChanged: calculateUsageMode();
38 onWidthChanged: calculateUsageMode();
36
39
37 DeviceConfiguration {
40 DeviceConfiguration {
38− id: deviceConfiguration
41+ id: _deviceConfiguration
39 name: applicationArguments.deviceName
42 name: applicationArguments.deviceName
40 }
43 }
41
44
42− property alias orientations: d.orientations
43−
44 Item {
45 Item {
45 id: d
46 id: d
46
47
@@ -157,10 +158,6 @@ Item {
157 return false;
158 return false;
158 }
159 }
159
160
160− Screens {
161− id: screens
162− }
163−
164 property int orientation
161 property int orientation
165 onPhysicalOrientationChanged: {
162 onPhysicalOrientationChanged: {
166 if (!orientationLocked) {
163 if (!orientationLocked) {
@@ -258,7 +255,7 @@ Item {
258
255
259 Shell {
256 Shell {
260 id: shell
257 id: shell
261− objectName: "shell"
258+ objectName: "shell-"+screenWindow.objectName
262 width: root.width
259 width: root.width
263 height: root.height
260 height: root.height
264 orientation: root.angleToOrientation(orientationAngle)
261 orientation: root.angleToOrientation(orientationAngle)
@@ -272,7 +269,7 @@ Item {
272 // have multiple keyboards around. For now we only enable one keyboard at a time
269 // have multiple keyboards around. For now we only enable one keyboard at a time
273 // thus hiding it here if there is a physical one around or if we have a second
270 // thus hiding it here if there is a physical one around or if we have a second
274 // screen (the virtual touchpad & osk on the phone) attached.
271 // screen (the virtual touchpad & osk on the phone) attached.
275− oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
272+ oskEnabled: (keyboardsModel.count === 0 && Screens.count === 1) ||
276 forceOSKEnabled
273 forceOSKEnabled
277
274
278 usageScenario: {
275 usageScenario: {
qml/Panel/Panel.qml+14−14
@@ -47,8 +47,8 @@ Item {
47 property bool fullscreenMode: false
47 property bool fullscreenMode: false
48 property real panelAreaShowProgress: 1.0
48 property real panelAreaShowProgress: 1.0
49 property bool greeterShown: false
49 property bool greeterShown: false
50−
51 property string mode: "staged"
50 property string mode: "staged"
51+ property PanelState panelState
52
52
53 MouseArea {
53 MouseArea {
54 id: backMouseEater
54 id: backMouseEater
@@ -65,14 +65,14 @@ Item {
65 }
65 }
66
66
67 Binding {
67 Binding {
68− target: PanelState
68+ target: panelState
69 property: "panelHeight"
69 property: "panelHeight"
70 value: minimizedPanelHeight
70 value: minimizedPanelHeight
71 }
71 }
72
72
73 RegisteredApplicationMenuModel {
73 RegisteredApplicationMenuModel {
74 id: registeredMenuModel
74 id: registeredMenuModel
75− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
76 }
76 }
77
77
78 QtObject {
78 QtObject {
@@ -83,11 +83,11 @@ Item {
83 !indicators.shown &&
83 !indicators.shown &&
84 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
84 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
85
85
86− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
87− PanelState.decorationsAlwaysVisible
87+ panelState.decorationsAlwaysVisible
88
88
89 property bool showPointerMenu: revealControls &&
89 property bool showPointerMenu: revealControls &&
90− (PanelState.decorationsVisible || mode == "staged")
90+ (panelState.decorationsVisible || mode == "staged")
91
91
92 property bool enablePointerMenu: revealControls &&
92 property bool enablePointerMenu: revealControls &&
93 applicationMenus.available &&
93 applicationMenus.available &&
@@ -139,7 +139,7 @@ Item {
139 fill: panelAreaBackground
139 fill: panelAreaBackground
140 bottomMargin: -units.gu(1)
140 bottomMargin: -units.gu(1)
141 }
141 }
142− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
143 source: "graphics/rectangular_dropshadow.sci"
143 source: "graphics/rectangular_dropshadow.sci"
144 }
144 }
145
145
@@ -192,12 +192,12 @@ Item {
192 visible: opacity != 0
192 visible: opacity != 0
193 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
193 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
194
194
195− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
195+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
196 windowIsMaximized: true
196 windowIsMaximized: true
197− onCloseClicked: PanelState.closeClicked()
197+ onCloseClicked: panelState.closeClicked()
198− onMinimizeClicked: PanelState.minimizeClicked()
198+ onMinimizeClicked: panelState.minimizeClicked()
199− onMaximizeClicked: PanelState.restoreClicked()
199+ onMaximizeClicked: panelState.restoreClicked()
200− closeButtonShown: PanelState.closeButtonShown
200+ closeButtonShown: panelState.closeButtonShown
201 }
201 }
202
202
203 Loader {
203 Loader {
@@ -219,7 +219,7 @@ Item {
219 anchors.left: parent.left
219 anchors.left: parent.left
220 anchors.margins: units.gu(1)
220 anchors.margins: units.gu(1)
221 height: menuBarLoader.height
221 height: menuBarLoader.height
222− enableKeyFilter: valid && PanelState.decorationsVisible
222+ enableKeyFilter: valid && panelState.decorationsVisible
223 unityMenuModel: __applicationMenus.model
223 unityMenuModel: __applicationMenus.model
224
224
225 Connections {
225 Connections {
@@ -267,7 +267,7 @@ Item {
267 }
267 }
268
268
269 showRowTitle: !expanded
269 showRowTitle: !expanded
270− rowTitle: PanelState.title
270+ rowTitle: panelState.title
271 rowItemDelegate: ActionItem {
271 rowItemDelegate: ActionItem {
272 id: actionItem
272 id: actionItem
273 property int ownIndex: index
273 property int ownIndex: index
qml/Shell.qml+50−12
@@ -1,4 +1,4 @@
1−/*
1+/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
@@ -38,12 +38,14 @@ import "Notifications"
38import "Stage"
38import "Stage"
39import "Tutorial"
39import "Tutorial"
40import "Wizard"
40import "Wizard"
41+import "Components/PanelState"
41import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Session 0.1
43import Unity.Session 0.1
43import Unity.DashCommunicator 0.1
44import Unity.DashCommunicator 0.1
44import Unity.Indicators 0.1 as Indicators
45import Unity.Indicators 0.1 as Indicators
45import Cursor 1.1
46import Cursor 1.1
46import WindowManager 1.0
47import WindowManager 1.0
48+import Unity.Debug 0.1 as Debug
47
49
48
50
49StyledItem {
51StyledItem {
@@ -185,6 +187,7 @@ StyledItem {
185 if (ApplicationManager.findApplication(appId)) {
187 if (ApplicationManager.findApplication(appId)) {
186 ApplicationManager.requestFocusApplication(appId);
188 ApplicationManager.requestFocusApplication(appId);
187 } else {
189 } else {
190+ print("calling startApplication. active workspace is", WorkspaceManager.activeWorkspace)
188 ApplicationManager.startApplication(appId);
191 ApplicationManager.startApplication(appId);
189 }
192 }
190 }
193 }
@@ -259,21 +262,23 @@ StyledItem {
259 schema.id: "com.canonical.Unity8"
262 schema.id: "com.canonical.Unity8"
260 }
263 }
261
264
265+ PanelState {
266+ id: panelState
267+ objectName: "panelState"
268+ }
269+
262 Item {
270 Item {
263 id: stages
271 id: stages
264 objectName: "stages"
272 objectName: "stages"
265 width: parent.width
273 width: parent.width
266 height: parent.height
274 height: parent.height
267
275
268− SurfaceManager {
269− id: surfaceMan
270− objectName: "surfaceManager"
271− }
272 TopLevelWindowModel {
276 TopLevelWindowModel {
273 id: topLevelSurfaceList
277 id: topLevelSurfaceList
274 objectName: "topLevelSurfaceList"
278 objectName: "topLevelSurfaceList"
275 applicationManager: ApplicationManager // it's a singleton
279 applicationManager: ApplicationManager // it's a singleton
276− surfaceManager: surfaceMan
280+ surfaceManager: SurfaceManager
281+ workspace: screenWindow.screen.currentWorkspace
277 }
282 }
278
283
279 Stage {
284 Stage {
@@ -315,6 +320,7 @@ StyledItem {
315 altTabPressed: physicalKeysMapper.altTabPressed
320 altTabPressed: physicalKeysMapper.altTabPressed
316 oskEnabled: shell.oskEnabled
321 oskEnabled: shell.oskEnabled
317 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
322 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
323+ panelState: panelState
318
324
319 onSpreadShownChanged: {
325 onSpreadShownChanged: {
320 panel.indicators.hide();
326 panel.indicators.hide();
@@ -369,8 +375,13 @@ StyledItem {
369 objectName: "greeterLoader"
375 objectName: "greeterLoader"
370 anchors.fill: parent
376 anchors.fill: parent
371 anchors.topMargin: panel.panelHeight
377 anchors.topMargin: panel.panelHeight
372− sourceComponent: shell.mode != "shell" ? integratedGreeter :
378+ sourceComponent: {
379+ if (shell.mode != "shell") {
380+ if (screenWindow.primary) return integratedGreeter;
381+ return secondaryGreeter;
382+ }
373− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
383+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
384+ }
374 onLoaded: {
385 onLoaded: {
375 item.objectName = "greeter"
386 item.objectName = "greeter"
376 }
387 }
@@ -420,6 +431,13 @@ StyledItem {
420 }
431 }
421 }
432 }
422
433
434+ Component {
435+ id: secondaryGreeter
436+ SecondaryGreeter {
437+ hides: [launcher, panel.indicators]
438+ }
439+ }
440+
423 Timer {
441 Timer {
424 // See powerConnection for why this is useful
442 // See powerConnection for why this is useful
425 id: showGreeterDelayed
443 id: showGreeterDelayed
@@ -533,6 +551,7 @@ StyledItem {
533 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
551 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
534 || greeter.hasLockedApp
552 || greeter.hasLockedApp
535 greeterShown: greeter && greeter.shown
553 greeterShown: greeter && greeter.shown
554+ panelState: panelState
536 }
555 }
537
556
538 Launcher {
557 Launcher {
@@ -772,12 +791,13 @@ StyledItem {
772
791
773 Cursor {
792 Cursor {
774 id: cursor
793 id: cursor
775− objectName: "cursor"
794+ objectName: "Pointer-"+screenWindow.objectName
776− visible: shell.hasMouse
795+
777 z: itemGrabber.z + 1
796 z: itemGrabber.z + 1
797+ opacity: 0
778 topBoundaryOffset: panel.panelHeight
798 topBoundaryOffset: panel.panelHeight
779−
799+ enabled: shell.hasMouse && screenWindow.active
780− confiningItem: stage.itemConfiningMouseCursor
800+ visible: enabled
781
801
782 property bool mouseNeverMoved: true
802 property bool mouseNeverMoved: true
783 Binding {
803 Binding {
@@ -789,6 +809,8 @@ StyledItem {
789 when: cursor.mouseNeverMoved && cursor.visible
809 when: cursor.mouseNeverMoved && cursor.visible
790 }
810 }
791
811
812+ confiningItem: stage.itemConfiningMouseCursor
813+
792 height: units.gu(3)
814 height: units.gu(3)
793
815
794 readonly property var previewRectangle: stage.previewRectangle.target &&
816 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -875,4 +897,20 @@ StyledItem {
875 }
897 }
876 }
898 }
877 }
899 }
900+
901+ Loader {
902+ z: shutdownFadeOutRectangle.z + 1
903+ active: DebuggingController.logOverlay
904+
905+ sourceComponent: Text {
906+ width: shell.width
907+ height: shell.height
908+
909+ text: consoleLog.out
910+ Debug.ConsoleLog {
911+ id: consoleLog
912+ enabled: true
913+ }
914+ }
915+ }
878}
916}
qml/ShellApplication.qmlnew file+48
@@ -0,0 +1,48 @@
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+import QtQuick.Window 2.2
19+import WindowManager 1.0
20+
21+Instantiator {
22+ id: root
23+ model: Screens
24+
25+ ShellScreen {
26+ id: window
27+ objectName: "screen"+index
28+ screen: model.screen
29+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
30+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
31+
32+ Binding {
33+ when: applicationArguments.hasGeometry
34+ target: window
35+ property: "width"
36+ value: applicationArguments.windowGeometry.width
37+ }
38+ Binding {
39+ when: applicationArguments.hasGeometry
40+ target: window
41+ property: "height"
42+ value: applicationArguments.windowGeometry.height
43+ }
44+
45+ Component.onCompleted: screen.active = primary
46+ primary: index == 0
47+ }
48+}
qml/ShellNotifier.qmlnew file+30
@@ -0,0 +1,30 @@
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+/*
18+ Shared signals & properties on multi-window desktop
19+ */
20+
21+pragma Singleton
22+import QtQuick 2.4
23+
24+QtObject {
25+ property var greeter: QtObject {
26+ signal hide(bool now)
27+
28+ property bool shown: true
29+ }
30+}
qml/ShellScreen.qmlnew file+63
@@ -0,0 +1,63 @@
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+import Ubuntu.Components 1.3
19+import WindowManager 1.0
20+import Cursor 1.1
21+import "Components"
22+
23+ScreenWindow {
24+ id: screenWindow
25+
26+ color: "black"
27+ title: "Unity8 Shell"
28+ property bool primary: false
29+
30+ DeviceConfiguration {
31+ id: deviceConfiguration
32+ name: applicationArguments.deviceName
33+ }
34+
35+ Loader {
36+ width: screenWindow.width
37+ height: screenWindow.height
38+
39+ sourceComponent: {
40+ if (Screens.count > 1 && primary && deviceConfiguration.category !== "desktop") {
41+ return disabledScreenComponent;
42+ }
43+ return shellComponent;
44+ }
45+ }
46+
47+ Component {
48+ id: shellComponent
49+ OrientedShell {
50+ implicitWidth: screenWindow.width
51+ implicitHeight: screenWindow.height
52+
53+ deviceConfiguration {
54+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
55+ }
56+ }
57+ }
58+
59+ Component {
60+ id: disabledScreenComponent
61+ DisabledScreenNotice {}
62+ }
63+}
qml/Stage/DecoratedWindow.qml+3−1
@@ -72,6 +72,8 @@ FocusScope {
72
72
73 readonly property Item clientAreaItem: applicationWindow
73 readonly property Item clientAreaItem: applicationWindow
74
74
75+ property PanelState panelState
76+
75 signal closeClicked()
77 signal closeClicked()
76 signal maximizeClicked()
78 signal maximizeClicked()
77 signal maximizeHorizontallyClicked()
79 signal maximizeHorizontallyClicked()
@@ -225,7 +227,7 @@ FocusScope {
225 enableMenus: {
227 enableMenus: {
226 return active &&
228 return active &&
227 surface &&
229 surface &&
228− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
230+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
229 }
231 }
230 menu: sharedAppModel.model
232 menu: sharedAppModel.model
231
233
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
35 property int leftMargin
35 property int leftMargin
36 property real appContainerWidth
36 property real appContainerWidth
37 property real appContainerHeight
37 property real appContainerHeight
38+ property PanelState panelState
38
39
39 readonly property real hintThreshold: 0.1
40 readonly property real hintThreshold: 0.1
40
41
@@ -178,7 +179,7 @@ Rectangle {
178 ParallelAnimation {
179 ParallelAnimation {
179 id: fakeMaximizeAnimation
180 id: fakeMaximizeAnimation
180 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
182 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 }
185 }
@@ -186,39 +187,39 @@ Rectangle {
186 ParallelAnimation {
187 ParallelAnimation {
187 id: fakeMaximizeLeftAnimation
188 id: fakeMaximizeLeftAnimation
188 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
190 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
192 }
193 }
193
194
194 ParallelAnimation {
195 ParallelAnimation {
195 id: fakeMaximizeRightAnimation
196 id: fakeMaximizeRightAnimation
196 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
198 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
200 }
201 }
201
202
202 ParallelAnimation {
203 ParallelAnimation {
203 id: fakeMaximizeTopLeftAnimation
204 id: fakeMaximizeTopLeftAnimation
204 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
206 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
208 }
209 }
209
210
210 ParallelAnimation {
211 ParallelAnimation {
211 id: fakeMaximizeTopRightAnimation
212 id: fakeMaximizeTopRightAnimation
212 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
214 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
216 }
217 }
217
218
218 ParallelAnimation {
219 ParallelAnimation {
219 id: fakeMaximizeBottomLeftAnimation
220 id: fakeMaximizeBottomLeftAnimation
220 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
222 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 }
225 }
@@ -226,7 +227,7 @@ Rectangle {
226 ParallelAnimation {
227 ParallelAnimation {
227 id: fakeMaximizeBottomRightAnimation
228 id: fakeMaximizeBottomRightAnimation
228 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
230 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 }
233 }
qml/Stage/Spread/ScreensAndWorkspaces.qmlnew file+183
@@ -0,0 +1,183 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Ubuntu.Components.Popups 1.3
4+import WindowManager 1.0
5+import Unity.Application 0.1
6+import ".."
7+
8+Item {
9+ id: root
10+
11+ property string background
12+
13+ property var screensProxy: Screens.createProxy();
14+
15+ Row {
16+ id: row
17+ anchors.bottom: parent.bottom
18+ anchors.horizontalCenter: parent.horizontalCenter
19+ Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: UbuntuAnimation.SlowDuration } }
20+// anchors.left: parent.left
21+ spacing: units.gu(1)
22+
23+ Repeater {
24+ model: screensProxy
25+
26+ delegate: Item {
27+ height: root.height - units.gu(6)
28+ width: workspaces.width
29+ clip: true
30+
31+ UbuntuShape {
32+ id: header
33+ anchors { left: parent.left; top: parent.top; right: parent.right }
34+ height: units.gu(7)
35+ backgroundColor: "white"
36+ z: 1
37+
38+ DropArea {
39+ anchors.fill: parent
40+ keys: ["workspace"]
41+
42+ onEntered: {
43+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
44+ drag.source.inDropArea = true;
45+ }
46+
47+ onExited: {
48+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
49+ drag.source.inDropArea = false;
50+ }
51+
52+ onDropped: {
53+ drag.source.inDropArea = false;
54+ }
55+ }
56+
57+ Column {
58+ anchors.fill: parent
59+ anchors.margins: units.gu(1)
60+
61+ Label {
62+ text: model.screen.name
63+ color: "black"
64+ }
65+
66+ Label {
67+ text: model.screen.outputType === Screens.LVDS ? "Built-in" : "Clone"
68+ color: "black"
69+ fontSize: "x-small"
70+ }
71+
72+ Label {
73+ text: model.screen.physicalSize.width + "x" + model.screen.physicalSize.height
74+ color: "black"
75+ fontSize: "x-small"
76+ }
77+ }
78+
79+ MouseArea {
80+ anchors.fill: parent
81+// acceptedButtons: Qt.RightButton
82+
83+
84+ onClicked: {
85+ print("should open popup")
86+ PopupUtils.open(contextMenu)
87+ }
88+ }
89+
90+ Component {
91+ id: contextMenu
92+ ActionSelectionPopover {
93+ actions: ActionList {
94+ Action {
95+ text: "Add workspace"
96+ onTriggered: {
97+ screen.addWorkspace();
98+ }
99+ }
100+ }
101+ }
102+ }
103+ }
104+
105+
106+ Workspaces {
107+ id: workspaces
108+ height: parent.height - header.height - units.gu(2)
109+ width: {
110+ if (screensProxy.count == 0) {
111+ return Math.min(implicitWidth, root.width - units.gu(8));
112+ }
113+ // TODO: needs better logic: If this is the active/current screen, make it wide, otherwise narrow
114+ return Math.min(implicitWidth, index == 0 ? units.gu(80) : units.gu(40), root.width)
115+ }
116+
117+ Behavior on width { UbuntuNumberAnimation {} }
118+ anchors.bottom: parent.bottom
119+ anchors.bottomMargin: units.gu(1)
120+ anchors.horizontalCenter: parent.horizontalCenter
121+ screen: model.screen
122+ background: root.background
123+
124+ workspaceModel: model.screen.workspaces
125+
126+ onCommitScreenSetup: Screens.sync(root.screensProxy)
127+ }
128+ }
129+ }
130+ }
131+
132+ Rectangle {
133+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
134+ width: units.gu(5)
135+ color: "#33000000"
136+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
137+ MouseArea {
138+ id: leftScrollArea
139+ anchors.fill: parent
140+ hoverEnabled: true
141+ onPressed: mouse.accepted = false;
142+ }
143+ DropArea {
144+ id: leftFakeDropArea
145+ anchors.fill: parent
146+ keys: ["application", "workspace"]
147+ }
148+ }
149+ Rectangle {
150+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
151+ width: units.gu(5)
152+ color: "#33000000"
153+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
154+ MouseArea {
155+ id: rightScrollArea
156+ anchors.fill: parent
157+ hoverEnabled: true
158+ onPressed: mouse.accepted = false;
159+ }
160+ DropArea {
161+ id: rightFakeDropArea
162+ anchors.fill: parent
163+ keys: ["application", "workspace"]
164+ }
165+ }
166+ Timer {
167+ repeat: true
168+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
169+ interval: UbuntuAnimation.SlowDuration
170+ triggeredOnStart: true
171+ onTriggered: {
172+ var newOffset = row.anchors.horizontalCenterOffset;
173+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
174+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
175+ newOffset += units.gu(20)
176+ } else {
177+ newOffset -= units.gu(20)
178+ }
179+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
180+ row.anchors.horizontalCenterOffset = newOffset;
181+ }
182+ }
183+}
qml/Stage/Spread/Spread.qml+6−7
@@ -28,10 +28,10 @@ Item {
28 property var spreadFlickable
28 property var spreadFlickable
29
29
30 // some config options
30 // some config options
31− property real contentMargin: 0.16 * root.height
31+ property real contentMargin: 0.1 * root.height
32− property real contentTopMargin: contentMargin
32+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
33− property real contentBottomMargin: 0.35 * contentMargin
33+ property real contentBottomMargin: contentMargin
34− property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
34+ property real windowTitleTopMargin: contentMargin - windowTitle.height
35 property int stackItemCount: 3
35 property int stackItemCount: 3
36 property real leftRotationAngle: 22
36 property real leftRotationAngle: 22
37 property real rightRotationAngle: 32
37 property real rightRotationAngle: 32
@@ -51,7 +51,7 @@ Item {
51
51
52 readonly property real spreadWidth: rightStackXPos - leftStackXPos
52 readonly property real spreadWidth: rightStackXPos - leftStackXPos
53 readonly property real spreadHeight: root.height
53 readonly property real spreadHeight: root.height
54− readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
54+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
55 readonly property real spreadItemWidth: stackHeight
55 readonly property real spreadItemWidth: stackHeight
56
56
57 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
57 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
@@ -93,8 +93,7 @@ Item {
93
93
94 readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
94 readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
95
95
96− readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth ,0) / (2 * spreadWidth)
96+ readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + leftStackXPos * 2, 0) / (2 * spreadWidth)
97−
98
97
99 readonly property var curve: BezierCurve {
98 readonly property var curve: BezierCurve {
100 controlPoint2: {'x': 0.19, 'y': 0.00}
99 controlPoint2: {'x': 0.19, 'y': 0.00}
qml/Stage/Spread/SpreadDelegateInputArea.qml+26−11
@@ -85,15 +85,15 @@ Item {
85 }
85 }
86
86
87 // Event eater
87 // Event eater
88− MouseArea {
88+// MouseArea {
89− anchors.fill: parent
89+// anchors.fill: parent
90− onClicked: root.clicked()
90+// onClicked: root.clicked()
91− onWheel: wheel.accepted = true
91+// onWheel: wheel.accepted = true
92− }
92+// }
93
93
94 MultiPointTouchArea {
94 MultiPointTouchArea {
95 anchors.fill: parent
95 anchors.fill: parent
96− mouseEnabled: false
96+// mouseEnabled: false
97 maximumTouchPoints: 1
97 maximumTouchPoints: 1
98 property int offset: 0
98 property int offset: 0
99
99
@@ -109,7 +109,7 @@ Item {
109 }
109 }
110
110
111 onTouchUpdated: {
111 onTouchUpdated: {
112− if (!d.moving) {
112+ if (!d.moving || !tp.pressed) {
113 if (Math.abs(tp.startY - tp.y) > d.threshold) {
113 if (Math.abs(tp.startY - tp.y) > d.threshold) {
114 d.moving = true;
114 d.moving = true;
115 d.dragEvents = []
115 d.dragEvents = []
@@ -119,17 +119,32 @@ Item {
119 }
119 }
120 }
120 }
121
121
122− if (root.closeable) {
122+
123− d.distance = tp.y - tp.startY - offset
123+ var value = tp.y - tp.startY - offset;
124+ if (value < 0) {
125+ var coords = mapToItem(shell, tp.x, tp.y);
126+ fakeDragItem.x = coords.x - units.gu(5)
127+ fakeDragItem.y = coords.y - units.gu(5)
128+ fakeDragItem.Drag.hotSpot.x = units.gu(5)
129+ fakeDragItem.Drag.hotSpot.y = units.gu(5)
130+ fakeDragItem.Drag.active = true;
131+ fakeDragItem.surface = model.window.surface;
132+
124 } else {
133 } else {
125− var value = tp.y - tp.startY - offset;
134+ if (root.closeable) {
135+ d.distance = value
136+ } else {
126− d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
137+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
138+ }
127 }
139 }
128
140
129 d.pushDragEvent(tp);
141 d.pushDragEvent(tp);
130 }
142 }
131
143
132 onReleased: {
144 onReleased: {
145+ print("released!")
146+ fakeDragItem.surface = null;
147+
133 if (!d.moving) {
148 if (!d.moving) {
134 root.clicked()
149 root.clicked()
135 }
150 }
qml/Stage/Spread/WorkspacePreview.qmlnew file+227
@@ -0,0 +1,227 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Application 0.1
4+import WindowManager 1.0
5+import ".."
6+import "../../Components"
7+
8+Item {
9+ id: previewSpace
10+ clip: true
11+
12+ property string background
13+ property int screenHeight
14+
15+ property real previewScale: previewSpace.height / previewSpace.screenHeight
16+
17+ property bool containsDrag: false
18+
19+ Image {
20+ source: previewSpace.background
21+ anchors.fill: parent
22+ sourceSize.width: width
23+ sourceSize.height: height
24+
25+ TopLevelWindowModel {
26+ id: windowModel
27+ applicationManager: ApplicationManager
28+ surfaceManager: SurfaceManager
29+ workspace: model.workspace
30+ }
31+
32+ Repeater {
33+ id: topLevelSurfaceRepeater
34+ model: visible ? windowModel : null
35+ delegate: Rectangle {
36+ width: surfaceItem.width
37+ height: surfaceItem.height + decorationHeight * previewScale
38+ x: model.window.position.x * previewScale
39+ y: (model.window.position.y - decorationHeight) * previewScale
40+ color: "blue"
41+ z: topLevelSurfaceRepeater.count - index
42+
43+ property int decorationHeight: units.gu(3)
44+
45+ WindowDecoration {
46+ width: surfaceItem.implicitWidth
47+ height: parent.decorationHeight
48+ transform: Scale {
49+ origin.x: 0
50+ origin.y: 0
51+ xScale: previewScale
52+ yScale: previewScale
53+ }
54+ title: model.window && model.window.surface ? model.window.surface.name : ""
55+ z: 3
56+ }
57+
58+ MirSurfaceItem {
59+ id: surfaceItem
60+ y: parent.decorationHeight * previewScale
61+ width: implicitWidth * previewScale
62+ height: implicitHeight * previewScale
63+ surfaceWidth: -1
64+ surfaceHeight: -1
65+ onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
66+ surface: model.window.surface
67+ }
68+ }
69+ }
70+
71+ }
72+
73+ Rectangle {
74+ anchors.fill: parent
75+ border.color: UbuntuColors.blue
76+ border.width: units.gu(.5)
77+ color: "transparent"
78+ visible: previewSpace.containsDrag
79+ }
80+}
81+
82+ // Repeater {
83+ // id: topLevelSurfaceRepeater
84+ // model: visible ? topLevelSurfaceList : null
85+ // delegate: Rectangle {
86+ // width: surfaceItem.width
87+ // height: surfaceItem.height + decorationHeight * previewScale
88+ // x: model.window.position.x * previewScale
89+ // y: (model.window.position.y - decorationHeight) * previewScale
90+ // color: "blue"
91+ // z: topLevelSurfaceRepeater.count - index
92+
93+ // property int decorationHeight: units.gu(3)
94+
95+ // WindowDecoration {
96+ // width: surfaceItem.implicitWidth
97+ // height: parent.decorationHeight
98+ // transform: Scale {
99+ // origin.x: 0
100+ // origin.y: 0
101+ // xScale: previewScale
102+ // yScale: previewScale
103+ // }
104+ // title: model.window && model.window.surface ? model.window.surface.name : ""
105+ // z: 3
106+ // }
107+
108+ // MirSurfaceItem {
109+ // id: surfaceItem
110+ // y: parent.decorationHeight * previewScale
111+ // width: implicitWidth * previewScale
112+ // height: implicitHeight * previewScale
113+ // surfaceWidth: -1
114+ // surfaceHeight: -1
115+ // onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
116+ // surface: model.window.surface
117+ // }
118+ // }
119+ // }
120+// }
121+// }
122+
123+
124+// MouseArea {
125+// id: mouseArea
126+// anchors.centerIn: parent
127+// anchors.verticalCenterOffset: header.height / 2
128+// width: units.gu(8)
129+// height: width
130+// hoverEnabled: true
131+// opacity: containsMouse ? 1 : 0
132+
133+// Rectangle {
134+// anchors.fill: parent
135+// color: "#80000000"
136+// radius: height / 2
137+// }
138+
139+// NumberAnimation {
140+// target: canvas
141+// property: "progress"
142+// duration: 2000
143+// running: mouseArea.containsMouse
144+// from: 0
145+// to: 1
146+// }
147+
148+// Canvas {
149+// id: canvas
150+// height: parent.height
151+// width: height
152+// anchors.centerIn: parent
153+
154+// property real progress: 0.5
155+// property int lineWidth: units.dp(4)
156+// onProgressChanged: {
157+// requestPaint();
158+// if (progress == 1) {
159+// Screens.activateScreen(index);
160+// }
161+// }
162+
163+// rotation: -90
164+
165+// onPaint: {
166+// var ctx = canvas.getContext("2d");
167+// ctx.save();
168+// ctx.reset();
169+
170+// ctx.lineWidth = lineWidth;
171+
172+// ctx.strokeStyle = "#3bffffff"
173+// ctx.beginPath();
174+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2),false);
175+// ctx.stroke();
176+// ctx.closePath();
177+
178+// ctx.strokeStyle = "white"
179+// ctx.beginPath();
180+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2*(progress)),false);
181+// ctx.stroke();
182+// ctx.closePath();
183+
184+// ctx.restore();
185+// }
186+// }
187+
188+// Icon {
189+// source: "graphics/multi-monitor_leave.png"
190+// height: units.gu(4)
191+// width: height
192+// anchors.centerIn: parent
193+// }
194+// }
195+// }
196+
197+// FloatingFlickable {
198+// id: flickable
199+// anchors.fill: parent
200+// contentWidth: workspaceRepeater.count * previewSpace.screenWidth * previewScale
201+// property real progress: contentX / contentWidth
202+// onContentXChanged: print("contentX:", contentX, "progress:", progress)
203+// }
204+
205+// MouseArea {
206+// anchors.fill: parent
207+// hoverEnabled: true
208+// property int scrollAreaWidth: width / 3
209+
210+// onMouseXChanged: {
211+// var margins = flickable.width * 0.05;
212+
213+// // do we need to scroll?
214+// if (mouseX < scrollAreaWidth + margins) {
215+// var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
216+// var contentX = (1 - progress) * (flickable.contentWidth - flickable.width)
217+// flickable.contentX = Math.max(0, Math.min(flickable.contentX, contentX))
218+// }
219+// if (mouseX > flickable.width - scrollAreaWidth) {
220+// var progress = Math.min(1, (mouseX - (flickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
221+// var contentX = progress * (flickable.contentWidth - flickable.width)
222+// flickable.contentX = Math.min(flickable.contentWidth - flickable.width, Math.max(flickable.contentX, contentX))
223+// }
224+// }
225+// }
226+// }
227+//}
qml/Stage/Spread/Workspaces.qmlnew file+314
@@ -0,0 +1,314 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import WindowManager 1.0
4+import "MathUtils.js" as MathUtils
5+import "../../Components"
6+
7+Item {
8+ id: root
9+ implicitWidth: listView.contentWidth
10+
11+ property QtObject screen: null
12+ property alias workspaceModel: listView.model
13+ property var background // TODO: should be stored in the workspace data
14+
15+ signal commitScreenSetup();
16+
17+ DropArea {
18+ anchors.fill: root
19+
20+ keys: ['workspace']
21+
22+ onEntered: {
23+ var index = listView.getDropIndex(drag);
24+ drag.source.workspace.assign(workspaceModel, index)
25+ listView.dropItemIndex = index;
26+ drag.source.inDropArea = true;
27+ }
28+
29+ onPositionChanged: {
30+ var index = listView.getDropIndex(drag);
31+ if (listView.dropItemIndex == index) return;
32+ listView.model.move(listView.dropItemIndex, index, 1);
33+ listView.dropItemIndex = index;
34+ }
35+
36+ onExited: {
37+ drag.source.workspace.unassign()
38+ listView.dropItemIndex = -1;
39+ drag.source.inDropArea = false;
40+ }
41+
42+ onDropped: {
43+ drop.accept(Qt.MoveAction);
44+ listView.dropItemIndex = -1;
45+ drag.source.inDropArea = false;
46+ }
47+ }
48+ DropArea {
49+ anchors.fill: parent
50+ keys: ["application"]
51+
52+ onPositionChanged: {
53+ listView.progressiveScroll(drag.x)
54+ listView.hoveredWorkspaceIndex = listView.getDropIndex(drag)
55+ }
56+ onExited: {
57+ listView.hoveredWorkspaceIndex = -1
58+ }
59+ }
60+
61+ ListView {
62+ id: listView
63+ anchors.fill: parent
64+ anchors.leftMargin: -itemWidth
65+ anchors.rightMargin: -itemWidth
66+// interactive: false
67+
68+ orientation: ListView.Horizontal
69+ spacing: units.gu(1)
70+ leftMargin: itemWidth
71+ rightMargin: itemWidth
72+
73+ property int itemWidth: height * screen.physicalSize.width / screen.physicalSize.height
74+ property int foldingAreaWidth: units.gu(10)
75+ property real realContentX: contentX - originX + leftMargin
76+ property int dropItemIndex: -1
77+ property int hoveredWorkspaceIndex: -1
78+
79+ function getDropIndex(drag) {
80+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
81+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
82+ if (index < 0) index = 0;
83+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
84+ if (index > upperLimit) index = upperLimit;
85+ return index;
86+ }
87+
88+ function progressiveScroll(mouseX) {
89+ var progress = Math.max(0, Math.min(1, (mouseX - listView.foldingAreaWidth) / (width - listView.leftMargin * 2 - listView.foldingAreaWidth * 2)))
90+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
91+ }
92+
93+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
94+
95+ delegate: Item {
96+ objectName: "delegate" + index
97+ height: parent.height
98+ width: listView.itemWidth
99+ Behavior on width { UbuntuNumberAnimation {} }
100+ visible: listView.dropItemIndex !== index
101+
102+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
103+ property int distanceFromLeft: itemX //- listView.leftMargin
104+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
105+
106+ property int maxAngle: 40
107+
108+ property int itemAngle: {
109+ if (index == 0) {
110+ if (distanceFromLeft < 0) {
111+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
112+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
113+ }
114+ return 0
115+ }
116+ if (index == listView.count - 1) {
117+ if (distanceFromRight < 0) {
118+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
119+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
120+ }
121+ return 0
122+ }
123+
124+ if (distanceFromLeft < listView.foldingAreaWidth) {
125+ // itemX : 10gu = p : 100
126+ var progress = distanceFromLeft / listView.foldingAreaWidth
127+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
128+ }
129+ if (distanceFromRight < listView.foldingAreaWidth) {
130+ var progress = distanceFromRight / listView.foldingAreaWidth
131+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
132+ }
133+ return 0
134+ }
135+
136+ property int itemOffset: {
137+ var rotationOffset = (1 - Math.cos(itemAngle * Math.PI / 360)) * listView.itemWidth
138+ if (index == 0) {
139+ if (distanceFromLeft < 0) {
140+ return -distanceFromLeft - rotationOffset
141+ }
142+ return 0
143+ }
144+ if (index == listView.count - 1) {
145+ if (distanceFromRight < 0) {
146+ return distanceFromRight + rotationOffset
147+ }
148+ return 0
149+ }
150+
151+ if (itemX < -listView.foldingAreaWidth) {
152+ return -itemX - rotationOffset
153+ }
154+ if (distanceFromLeft < listView.foldingAreaWidth) {
155+ return (listView.foldingAreaWidth - distanceFromLeft) / 2 - rotationOffset
156+ }
157+
158+ if (distanceFromRight < -listView.foldingAreaWidth) {
159+ return distanceFromRight + rotationOffset
160+ }
161+
162+ if (distanceFromRight < listView.foldingAreaWidth) {
163+ return -(listView.foldingAreaWidth - distanceFromRight) / 2 + rotationOffset
164+ }
165+
166+ return 0
167+ }
168+
169+// onItemXChanged: if (index == 1) print("x", itemX, listView.contentX)
170+
171+ z: itemOffset < 0 ? itemOffset : -itemOffset
172+ transform: [
173+ Rotation {
174+ angle: itemAngle
175+ axis { x: 0; y: 1; z: 0 }
176+ origin { x: listView.itemWidth / 2; y: height / 2 }
177+ },
178+ Translate {
179+ x: itemOffset
180+ }
181+ ]
182+
183+ WorkspacePreview {
184+ height: listView.height
185+ width: listView.itemWidth
186+ background: root.background
187+ screenHeight: root.screen.physicalSize.height
188+ containsDrag: listView.hoveredWorkspaceIndex == index
189+
190+ Label {
191+ anchors.centerIn: parent
192+ text: model.text
193+ color: "red"
194+ fontSize: "large"
195+ }
196+ }
197+ }
198+
199+ MouseArea {
200+ id: mouseArea
201+ anchors.fill: parent
202+ hoverEnabled: true
203+ propagateComposedEvents: true
204+ anchors.leftMargin: listView.leftMargin
205+ anchors.rightMargin: listView.rightMargin
206+
207+ property int draggedIndex: -1
208+
209+ property int startX: 0
210+ property int startY: 0
211+
212+ onMouseXChanged: {
213+ if (!pressed || dragging) {
214+ listView.progressiveScroll(mouseX)
215+ }
216+ }
217+ onMouseYChanged: {
218+ if (Math.abs(mouseY - startY) > units.gu(3)) {
219+ drag.axis = Drag.XAndYAxis;
220+ }
221+ }
222+
223+ onReleased: {
224+ var result = fakeDragItem.Drag.drop();
225+// if (result == Qt.IgnoreAction) {
226+// WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
227+// }
228+ root.commitScreenSetup();
229+ drag.target = null;
230+ }
231+
232+ property bool dragging: drag.active
233+ onDraggingChanged: {
234+ if (drag.active) {
235+ print("drai", draggedIndex)
236+ var ws = listView.model.get(draggedIndex);
237+ print("ws:", ws)
238+ ws.unassign();
239+// listView.model.remove(draggedIndex, 1)
240+ }
241+ }
242+
243+ onPressed: {
244+ startX = mouseX;
245+ startY = mouseY;
246+ if (listView.model.count < 2) return;
247+
248+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
249+ draggedIndex = listView.indexAt(coords.x, coords.y)
250+ var clickedItem = listView.itemAt(coords.x, coords.y)
251+
252+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
253+ fakeDragItem.x = itemCoords.x
254+ fakeDragItem.y = itemCoords.y
255+ fakeDragItem.workspace = listView.model.get(draggedIndex)
256+
257+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
258+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
259+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
260+
261+ drag.axis = Drag.YAxis;
262+ drag.target = fakeDragItem;
263+ }
264+
265+ onClicked: {
266+ screenWindow.screen.activeWorkspace = fakeDragItem.workspace
267+ }
268+
269+ WorkspacePreview {
270+ id: fakeDragItem
271+ height: listView.height
272+ width: listView.itemWidth
273+ background: root.background
274+ screenHeight: root.screen.physicalSize.height
275+ visible: Drag.active
276+
277+ Drag.active: mouseArea.drag.active
278+ Drag.keys: ['workspace']
279+
280+ property var workspace
281+ property bool inDropArea: false
282+
283+ Rectangle {
284+ anchors.fill: parent
285+ color: "#33000000"
286+ opacity: parent.inDropArea ? 0 : 1
287+ Behavior on opacity { UbuntuNumberAnimation { } }
288+ Rectangle {
289+ anchors.centerIn: parent
290+ width: units.gu(6)
291+ height: units.gu(6)
292+ radius: width / 2
293+ color: "#aa000000"
294+ }
295+
296+ Icon {
297+ height: units.gu(3)
298+ width: height
299+ anchors.centerIn: parent
300+ name: "edit-delete"
301+ color: "white"
302+ }
303+ }
304+
305+ states: [
306+ State {
307+ when: fakeDragItem.Drag.active
308+ ParentChange { target: fakeDragItem; parent: shell }
309+ }
310+ ]
311+ }
312+ }
313+ }
314+}
qml/Stage/Stage.qml+138−103
@@ -47,6 +47,7 @@ FocusScope {
47 property bool oskEnabled: false
47 property bool oskEnabled: false
48 property rect inputMethodRect
48 property rect inputMethodRect
49 property real rightEdgePushProgress: 0
49 property real rightEdgePushProgress: 0
50+ property PanelState panelState
50
51
51 // Configuration
52 // Configuration
52 property string mode: "staged"
53 property string mode: "staged"
@@ -349,23 +350,23 @@ FocusScope {
349 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
350 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
350 }
351 }
351
352
352− Component.onCompleted: priv.updateMainAndSideStageIndexes();
353+ Component.onCompleted: {priv.updateMainAndSideStageIndexes(); print("size", root.width, root.height)}
353
354
354 Connections {
355 Connections {
355− target: PanelState
356+ target: panelState
356 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
357 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
357 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
358 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
358 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
359 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
359 }
360 }
360
361
361 Binding {
362 Binding {
362− target: PanelState
363+ target: panelState
363 property: "decorationsVisible"
364 property: "decorationsVisible"
364 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
365 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
365 }
366 }
366
367
367 Binding {
368 Binding {
368− target: PanelState
369+ target: panelState
369 property: "title"
370 property: "title"
370 value: {
371 value: {
371 if (priv.focusedAppDelegate !== null) {
372 if (priv.focusedAppDelegate !== null) {
@@ -380,7 +381,7 @@ FocusScope {
380 }
381 }
381
382
382 Binding {
383 Binding {
383− target: PanelState
384+ target: panelState
384 property: "focusedPersistentSurfaceId"
385 property: "focusedPersistentSurfaceId"
385 value: {
386 value: {
386 if (priv.focusedAppDelegate !== null) {
387 if (priv.focusedAppDelegate !== null) {
@@ -394,21 +395,21 @@ FocusScope {
394 }
395 }
395
396
396 Binding {
397 Binding {
397− target: PanelState
398+ target: panelState
398 property: "dropShadow"
399 property: "dropShadow"
399 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
400 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
400 }
401 }
401
402
402 Binding {
403 Binding {
403− target: PanelState
404+ target: panelState
404 property: "closeButtonShown"
405 property: "closeButtonShown"
405 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
406 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
406 }
407 }
407
408
408 Component.onDestruction: {
409 Component.onDestruction: {
409− PanelState.title = "";
410+ panelState.title = "";
410− PanelState.decorationsVisible = false;
411+ panelState.decorationsVisible = false;
411− PanelState.dropShadow = false;
412+ panelState.dropShadow = false;
412 }
413 }
413
414
414 Instantiator {
415 Instantiator {
@@ -454,6 +455,7 @@ FocusScope {
454 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
455 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
455 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
456 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
456 PropertyChanges { target: wallpaper; visible: false }
457 PropertyChanges { target: wallpaper; visible: false }
458+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
457 },
459 },
458 State {
460 State {
459 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
461 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
@@ -502,10 +504,13 @@ FocusScope {
502 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
504 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
503 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
505 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
504 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
506 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
507+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
505 },
508 },
506 Transition {
509 Transition {
507 to: "spread"
510 to: "spread"
508 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
511 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
512+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
513+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
509 },
514 },
510 Transition {
515 Transition {
511 from: "spread"
516 from: "spread"
@@ -563,10 +568,19 @@ FocusScope {
563 visible: false
568 visible: false
564 }
569 }
565
570
571+ ScreensAndWorkspaces {
572+ id: screensAndWorkspaces
573+ anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.leftMargin }
574+ height: parent.height / 3
575+ background: root.background
576+ opacity: 0
577+ visible: opacity > 0
578+ }
579+
566 Spread {
580 Spread {
567 id: spreadItem
581 id: spreadItem
568 objectName: "spreadItem"
582 objectName: "spreadItem"
569− anchors.fill: appContainer
583+ anchors { left: parent.left; bottom: parent.bottom; right: parent.right; top: screensAndWorkspaces.bottom }
570 leftMargin: root.leftMargin
584 leftMargin: root.leftMargin
571 model: root.topLevelSurfaceList
585 model: root.topLevelSurfaceList
572 spreadFlickable: floatingFlickable
586 spreadFlickable: floatingFlickable
@@ -575,6 +589,83 @@ FocusScope {
575 onLeaveSpread: {
589 onLeaveSpread: {
576 priv.goneToSpread = false;
590 priv.goneToSpread = false;
577 }
591 }
592+
593+ FloatingFlickable {
594+ id: floatingFlickable
595+ objectName: "spreadFlickable"
596+ anchors.fill: parent
597+ enabled: false
598+ contentWidth: spreadItem.spreadTotalWidth
599+
600+ function snap(toIndex) {
601+ var delegate = appRepeater.itemAt(toIndex)
602+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
603+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
604+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
605+ snapAnimation.to = floatingFlickable.contentX - offset;
606+ snapAnimation.start();
607+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
608+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
609+ snapAnimation.to = floatingFlickable.contentX - offset;
610+ snapAnimation.start();
611+ }
612+ }
613+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
614+ }
615+
616+ MouseArea {
617+ id: hoverMouseArea
618+ objectName: "hoverMouseArea"
619+ anchors.fill: parent
620+ propagateComposedEvents: true
621+ hoverEnabled: true
622+ enabled: false
623+ visible: enabled
624+
625+ property int scrollAreaWidth: width / 3
626+ property bool progressiveScrollingEnabled: false
627+
628+ onPressed: mouse.accepted = false
629+
630+ onMouseXChanged: {
631+ mouse.accepted = false
632+
633+ if (hoverMouseArea.pressed) {
634+ return;
635+ }
636+
637+ // Find the hovered item and mark it active
638+ for (var i = appRepeater.count - 1; i >= 0; i--) {
639+ var appDelegate = appRepeater.itemAt(i);
640+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
641+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
642+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
643+ spreadItem.highlightedIndex = i;
644+ break;
645+ }
646+ }
647+
648+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
649+ var margins = floatingFlickable.width * 0.05;
650+
651+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
652+ progressiveScrollingEnabled = true
653+ }
654+
655+ // do we need to scroll?
656+ if (mouseX < scrollAreaWidth + margins) {
657+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
658+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
659+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
660+ }
661+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
662+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
663+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
664+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
665+ }
666+ }
667+ }
668+ }
578 }
669 }
579
670
580 Connections {
671 Connections {
@@ -669,10 +760,27 @@ FocusScope {
669 }
760 }
670 }
761 }
671
762
763+ MirSurfaceItem {
764+ id: fakeDragItem
765+ property real previewScale: .5
766+ width: implicitWidth * previewScale
767+ height: implicitHeight * previewScale
768+ surfaceWidth: -1
769+ surfaceHeight: -1
770+ opacity: surface != null ? 1 : 0
771+ Behavior on opacity { UbuntuNumberAnimation {} }
772+ visible: opacity > 0
773+
774+ Drag.active: surface != null
775+ Drag.keys: ["application"]
776+
777+ z: 1000
778+ }
779+
672 Item {
780 Item {
673 id: boundariesForWindowPlacement
781 id: boundariesForWindowPlacement
674 anchors.fill: parent
782 anchors.fill: parent
675− anchors.topMargin: PanelState.panelHeight
783+ anchors.topMargin: panelState.panelHeight
676 visible: false
784 visible: false
677 }
785 }
678
786
@@ -704,6 +812,9 @@ FocusScope {
704 }
812 }
705 z: normalZ
813 z: normalZ
706
814
815+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
816+ Behavior on opacity { UbuntuNumberAnimation {} }
817+
707 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
818 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
708 // match what the actual surface size is.
819 // match what the actual surface size is.
709 // Don't write to those, they will be set by states
820 // Don't write to those, they will be set by states
@@ -764,7 +875,7 @@ FocusScope {
764 target: appDelegate
875 target: appDelegate
765 property: "y"
876 property: "y"
766 value: appDelegate.requestedY -
877 value: appDelegate.requestedY -
767− Math.min(appDelegate.requestedY - PanelState.panelHeight,
878+ Math.min(appDelegate.requestedY - panelState.panelHeight,
768 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
879 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
769 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
880 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
770 && root.inputMethodRect.height > 0
881 && root.inputMethodRect.height > 0
@@ -1148,7 +1259,7 @@ FocusScope {
1148 progress: 0
1259 progress: 0
1149 targetHeight: spreadItem.stackHeight
1260 targetHeight: spreadItem.stackHeight
1150 targetX: spreadMaths.targetX
1261 targetX: spreadMaths.targetX
1151− startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1262+ startY: appDelegate.fullscreen ? 0 : panelState.panelHeight
1152 targetY: spreadMaths.targetY
1263 targetY: spreadMaths.targetY
1153 targetAngle: spreadMaths.targetAngle
1264 targetAngle: spreadMaths.targetAngle
1154 targetScale: spreadMaths.targetScale
1265 targetScale: spreadMaths.targetScale
@@ -1267,9 +1378,9 @@ FocusScope {
1267 PropertyChanges {
1378 PropertyChanges {
1268 target: appDelegate
1379 target: appDelegate
1269 x: stageMaths.itemX
1380 x: stageMaths.itemX
1270− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1381+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
1271 requestedWidth: appContainer.width
1382 requestedWidth: appContainer.width
1272− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1383+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
1273 visuallyMaximized: true
1384 visuallyMaximized: true
1274 visible: appDelegate.x < root.width
1385 visible: appDelegate.x < root.width
1275 }
1386 }
@@ -1295,10 +1406,10 @@ FocusScope {
1295 PropertyChanges {
1406 PropertyChanges {
1296 target: appDelegate
1407 target: appDelegate
1297 x: stageMaths.itemX
1408 x: stageMaths.itemX
1298− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1409+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
1299 z: stageMaths.itemZ
1410 z: stageMaths.itemZ
1300 requestedWidth: stageMaths.itemWidth
1411 requestedWidth: stageMaths.itemWidth
1301− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1412+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
1302 visuallyMaximized: true
1413 visuallyMaximized: true
1303 visible: appDelegate.x < root.width
1414 visible: appDelegate.x < root.width
1304 }
1415 }
@@ -1363,9 +1474,9 @@ FocusScope {
1363 PropertyChanges {
1474 PropertyChanges {
1364 target: appDelegate
1475 target: appDelegate
1365 windowedX: root.leftMargin
1476 windowedX: root.leftMargin
1366− windowedY: PanelState.panelHeight
1477+ windowedY: panelState.panelHeight
1367 windowedWidth: (appContainer.width - root.leftMargin)/2
1478 windowedWidth: (appContainer.width - root.leftMargin)/2
1368− windowedHeight: appContainer.height - PanelState.panelHeight
1479+ windowedHeight: appContainer.height - panelState.panelHeight
1369 }
1480 }
1370 },
1481 },
1371 State {
1482 State {
@@ -1382,9 +1493,9 @@ FocusScope {
1382 PropertyChanges {
1493 PropertyChanges {
1383 target: appDelegate
1494 target: appDelegate
1384 windowedX: root.leftMargin
1495 windowedX: root.leftMargin
1385− windowedY: PanelState.panelHeight
1496+ windowedY: panelState.panelHeight
1386 windowedWidth: (appContainer.width - root.leftMargin)/2
1497 windowedWidth: (appContainer.width - root.leftMargin)/2
1387− windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1498+ windowedHeight: (appContainer.height - panelState.panelHeight)/2
1388 }
1499 }
1389 },
1500 },
1390 State {
1501 State {
@@ -1401,7 +1512,7 @@ FocusScope {
1401 PropertyChanges {
1512 PropertyChanges {
1402 target: appDelegate
1513 target: appDelegate
1403 windowedX: root.leftMargin
1514 windowedX: root.leftMargin
1404− windowedY: (appContainer.height + PanelState.panelHeight)/2
1515+ windowedY: (appContainer.height + panelState.panelHeight)/2
1405 windowedWidth: (appContainer.width - root.leftMargin)/2
1516 windowedWidth: (appContainer.width - root.leftMargin)/2
1406 windowedHeight: appContainer.height/2
1517 windowedHeight: appContainer.height/2
1407 }
1518 }
@@ -1423,8 +1534,8 @@ FocusScope {
1423 State {
1534 State {
1424 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1535 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1425 extend: "normal"
1536 extend: "normal"
1426− PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1537+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: panelState.panelHeight;
1427− windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1538+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - panelState.panelHeight }
1428 },
1539 },
1429 State {
1540 State {
1430 name: "minimized"; when: appDelegate.minimized
1541 name: "minimized"; when: appDelegate.minimized
@@ -1540,7 +1651,7 @@ FocusScope {
1540 ]
1651 ]
1541
1652
1542 Binding {
1653 Binding {
1543− target: PanelState
1654+ target: panelState
1544 property: "decorationsAlwaysVisible"
1655 property: "decorationsAlwaysVisible"
1545 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1656 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1546 }
1657 }
@@ -1785,83 +1896,7 @@ FocusScope {
1785 leftMargin: root.leftMargin
1896 leftMargin: root.leftMargin
1786 appContainerWidth: appContainer.width
1897 appContainerWidth: appContainer.width
1787 appContainerHeight: appContainer.height
1898 appContainerHeight: appContainer.height
1788− }
1899+ panelState: root.panelState
1789−
1790− MouseArea {
1791− id: hoverMouseArea
1792− objectName: "hoverMouseArea"
1793− anchors.fill: appContainer
1794− propagateComposedEvents: true
1795− hoverEnabled: true
1796− enabled: false
1797− visible: enabled
1798−
1799− property int scrollAreaWidth: width / 3
1800− property bool progressiveScrollingEnabled: false
1801−
1802− onMouseXChanged: {
1803− mouse.accepted = false
1804−
1805− if (hoverMouseArea.pressed) {
1806− return;
1807− }
1808−
1809− // Find the hovered item and mark it active
1810− for (var i = appRepeater.count - 1; i >= 0; i--) {
1811− var appDelegate = appRepeater.itemAt(i);
1812− var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1813− var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1814− if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1815− spreadItem.highlightedIndex = i;
1816− break;
1817− }
1818− }
1819−
1820− if (floatingFlickable.contentWidth > floatingFlickable.width) {
1821− var margins = floatingFlickable.width * 0.05;
1822−
1823− if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1824− progressiveScrollingEnabled = true
1825− }
1826−
1827− // do we need to scroll?
1828− if (mouseX < scrollAreaWidth + margins) {
1829− var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1830− var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1831− floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1832− }
1833− if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1834− var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1835− var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1836− floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1837− }
1838− }
1839− }
1840−
1841− onPressed: mouse.accepted = false
1842− }
1843−
1844− FloatingFlickable {
1845− id: floatingFlickable
1846− objectName: "spreadFlickable"
1847− anchors.fill: appContainer
1848− enabled: false
1849− contentWidth: spreadItem.spreadTotalWidth
1850−
1851− function snap(toIndex) {
1852− var delegate = appRepeater.itemAt(toIndex)
1853− var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1854− if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1855− var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1856− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1857− snapAnimation.start();
1858− } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1859− var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1860− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1861− snapAnimation.start();
1862− }
1863− }
1864− UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1865 }
1900 }
1866
1901
1867 PropertyAnimation {
1902 PropertyAnimation {
qml/Stage/SurfaceContainer.qml+2
@@ -66,6 +66,8 @@ FocusScope {
66 fillMode: MirSurfaceItem.PadOrCrop
66 fillMode: MirSurfaceItem.PadOrCrop
67 consumesInput: true
67 consumesInput: true
68
68
69+ onSurfaceChanged: print("surface changed", surface, implicitWidth, implicitHeight)
70+
69 surfaceWidth: root.requestedWidth
71 surfaceWidth: root.requestedWidth
70 surfaceHeight: root.requestedHeight
72 surfaceHeight: root.requestedHeight
71
73
qml/Stage/WorkspacePreview.qmlnew file+212
@@ -0,0 +1,212 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Screens 0.1
4+import Unity.Application 0.1
5+import "../Components"
6+
7+UbuntuShape {
8+ id: previewSpace
9+
10+ // Set the height. this preview will then automatically adjust the width, keeping aspect ratio of the screen
11+ width: model.geometry.width * previewScale * 4
12+ color: "white"
13+ property string background
14+
15+ property int screenWidth: model.geometry.width
16+ property int screenHeight: model.geometry.height
17+ property real previewScale: (previewSpace.height - header.height) / previewSpace.screenHeight
18+
19+ Item {
20+ id: header
21+ anchors { left: parent.left; top: parent.top; right: parent.right }
22+ height: units.gu(7)
23+
24+ Column {
25+ anchors.fill: parent
26+ anchors.margins: units.gu(1)
27+
28+ Label {
29+ text: model.name
30+ color: "black"
31+ }
32+
33+ Label {
34+ text: model.outputType === Screens.LVDS ? "Built-in" : "Clone"
35+ color: "black"
36+ fontSize: "x-small"
37+ }
38+
39+ Label {
40+ text: model.geometry.width + "x" + model.geometry.height
41+ color: "black"
42+ fontSize: "x-small"
43+ }
44+ }
45+ }
46+
47+
48+ Rectangle {
49+ anchors.fill: parent
50+ anchors.topMargin: header.height
51+ color: "blue"
52+
53+ Repeater {
54+ id: workspaceRepeater
55+ model: 15
56+
57+ delegate: Item {
58+ x: index * previewSpace.screenWidth * previewScale
59+ height: previewSpace.screenHeight * previewScale
60+ width: previewSpace.screenWidth * previewScale
61+ clip: true
62+
63+ Wallpaper {
64+ source: previewSpace.background
65+ anchors.fill: parent
66+
67+ // Repeater {
68+ // id: topLevelSurfaceRepeater
69+ // model: visible ? topLevelSurfaceList : null
70+ // delegate: Rectangle {
71+ // width: surfaceItem.width
72+ // height: surfaceItem.height + decorationHeight * previewScale
73+ // x: model.window.position.x * previewScale
74+ // y: (model.window.position.y - decorationHeight) * previewScale
75+ // color: "blue"
76+ // z: topLevelSurfaceRepeater.count - index
77+
78+ // property int decorationHeight: units.gu(3)
79+
80+ // WindowDecoration {
81+ // width: surfaceItem.implicitWidth
82+ // height: parent.decorationHeight
83+ // transform: Scale {
84+ // origin.x: 0
85+ // origin.y: 0
86+ // xScale: previewScale
87+ // yScale: previewScale
88+ // }
89+ // title: model.window && model.window.surface ? model.window.surface.name : ""
90+ // z: 3
91+ // }
92+
93+ // MirSurfaceItem {
94+ // id: surfaceItem
95+ // y: parent.decorationHeight * previewScale
96+ // width: implicitWidth * previewScale
97+ // height: implicitHeight * previewScale
98+ // surfaceWidth: -1
99+ // surfaceHeight: -1
100+ // onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
101+ // surface: model.window.surface
102+ // }
103+ // }
104+ // }
105+ }
106+ }
107+
108+
109+// MouseArea {
110+// id: mouseArea
111+// anchors.centerIn: parent
112+// anchors.verticalCenterOffset: header.height / 2
113+// width: units.gu(8)
114+// height: width
115+// hoverEnabled: true
116+// opacity: containsMouse ? 1 : 0
117+
118+// Rectangle {
119+// anchors.fill: parent
120+// color: "#80000000"
121+// radius: height / 2
122+// }
123+
124+// NumberAnimation {
125+// target: canvas
126+// property: "progress"
127+// duration: 2000
128+// running: mouseArea.containsMouse
129+// from: 0
130+// to: 1
131+// }
132+
133+// Canvas {
134+// id: canvas
135+// height: parent.height
136+// width: height
137+// anchors.centerIn: parent
138+
139+// property real progress: 0.5
140+// property int lineWidth: units.dp(4)
141+// onProgressChanged: {
142+// requestPaint();
143+// if (progress == 1) {
144+// Screens.activateScreen(index);
145+// }
146+// }
147+
148+// rotation: -90
149+
150+// onPaint: {
151+// var ctx = canvas.getContext("2d");
152+// ctx.save();
153+// ctx.reset();
154+
155+// ctx.lineWidth = lineWidth;
156+
157+// ctx.strokeStyle = "#3bffffff"
158+// ctx.beginPath();
159+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2),false);
160+// ctx.stroke();
161+// ctx.closePath();
162+
163+// ctx.strokeStyle = "white"
164+// ctx.beginPath();
165+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2*(progress)),false);
166+// ctx.stroke();
167+// ctx.closePath();
168+
169+// ctx.restore();
170+// }
171+// }
172+
173+// Icon {
174+// source: "graphics/multi-monitor_leave.png"
175+// height: units.gu(4)
176+// width: height
177+// anchors.centerIn: parent
178+// }
179+// }
180+ }
181+
182+ FloatingFlickable {
183+ id: flickable
184+ anchors.fill: parent
185+ contentWidth: workspaceRepeater.count * previewSpace.screenWidth * previewScale
186+ property real progress: contentX / contentWidth
187+ onContentXChanged: print("contentX:", contentX, "progress:", progress)
188+ }
189+
190+ MouseArea {
191+ anchors.fill: parent
192+ hoverEnabled: true
193+ property int scrollAreaWidth: width / 3
194+
195+ onMouseXChanged: {
196+ var margins = flickable.width * 0.05;
197+
198+ // do we need to scroll?
199+ if (mouseX < scrollAreaWidth + margins) {
200+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
201+ var contentX = (1 - progress) * (flickable.contentWidth - flickable.width)
202+ flickable.contentX = Math.max(0, Math.min(flickable.contentX, contentX))
203+ }
204+ if (mouseX > flickable.width - scrollAreaWidth) {
205+ var progress = Math.min(1, (mouseX - (flickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
206+ var contentX = progress * (flickable.contentWidth - flickable.width)
207+ flickable.contentX = Math.min(flickable.contentWidth - flickable.width, Math.max(flickable.contentX, contentX))
208+ }
209+ }
210+ }
211+ }
212+}
qml/Stage/graphics/multi-monitor_leave.pngnew file

Binary file not shown.

qml/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ShellNotifier 0.1 ShellNotifier.qml
src/ApplicationArguments.cpp+11−2
@@ -16,7 +16,16 @@
16
16
17#include "ApplicationArguments.h"
17#include "ApplicationArguments.h"
18
18
19−ApplicationArguments::ApplicationArguments(QObject *parent)
19+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
20− : QObject(parent)
20+ : QObject(app)
21+ , UnityCommandLineParser(*app)
21{
22{
22}
23}
24+
25+void ApplicationArguments::setDeviceName(const QString &deviceName)
26+{
27+ if (deviceName != m_deviceName) {
28+ m_deviceName = deviceName;
29+ Q_EMIT deviceNameChanged(m_deviceName);
30+ }
31+}
src/ApplicationArguments.h+17−15
@@ -23,31 +23,33 @@
23#include <QSize>
23#include <QSize>
24#include <QString>
24#include <QString>
25
25
26+#include "UnityCommandLineParser.h"
27+
26−class ApplicationArguments : public QObject
28+class ApplicationArguments : public QObject,
29+ public UnityCommandLineParser
27{
30{
28 Q_OBJECT
31 Q_OBJECT
29 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
32 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
30 Q_PROPERTY(QString mode READ mode CONSTANT)
33 Q_PROPERTY(QString mode READ mode CONSTANT)
34+
35+ Q_PROPERTY(bool hasGeometry READ hasGeometry CONSTANT)
36+ Q_PROPERTY(QSize windowGeometry READ windowGeometry CONSTANT)
37+ Q_PROPERTY(bool hasTestability READ hasTestability CONSTANT)
38+ Q_PROPERTY(bool hasFrameless READ hasFrameless CONSTANT)
39+ Q_PROPERTY(bool hasFullscreen READ hasFullscreen CONSTANT)
40+#ifdef UNITY8_ENABLE_TOUCH_EMULATION
41+ Q_PROPERTY(bool hasMouseToTouch READ hasMouseToTouch CONSTANT)
42+#endif
43+
31public:
44public:
32− ApplicationArguments(QObject *parent = nullptr);
45+ ApplicationArguments(QCoreApplication *app);
33
46
34− void setDeviceName(const QString &deviceName) {
47+ void setDeviceName(const QString &deviceName);
35− if (deviceName != m_deviceName) {
36− m_deviceName = deviceName;
37− Q_EMIT deviceNameChanged(m_deviceName);
38− }
39− }
40− QString deviceName() const { return m_deviceName; }
41
48
42− void setMode(const QString &mode) { m_mode = mode; }
49+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
43− QString mode() const { return m_mode; }
44
50
45Q_SIGNALS:
51Q_SIGNALS:
46 void deviceNameChanged(const QString&);
52 void deviceNameChanged(const QString&);
47−
48−private:
49− QString m_deviceName;
50− QString m_mode;
51};
53};
52
54
53#endif // APPLICATION_ARGUMENTS_H
55#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt+10−4
@@ -7,6 +7,7 @@ include_directories(
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
8 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
8 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
9 ${CONNECTIVITY_INCLUDE_DIRS}
9 ${CONNECTIVITY_INCLUDE_DIRS}
10+ ${QTMIRSERVER_INCLUDE_DIRS}
10)
11)
11
12
12include_directories(
13include_directories(
@@ -27,12 +28,11 @@ set(SOURCE_FILES
27 ApplicationArguments.cpp
28 ApplicationArguments.cpp
28 main.cpp
29 main.cpp
29 CachingNetworkManagerFactory.cpp
30 CachingNetworkManagerFactory.cpp
30− SecondaryWindow.cpp
31+ UnityApplication.cpp
31− ShellApplication.cpp
32− ShellView.cpp
33 UnityCommandLineParser.cpp
32 UnityCommandLineParser.cpp
34 UnixSignalHandler.cpp
33 UnixSignalHandler.cpp
35 DebuggingController.cpp
34 DebuggingController.cpp
35+ WindowManagementPolicy.cpp
36 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
36 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
37)
37)
38
38
@@ -47,7 +47,13 @@ qt5_use_modules(${SHELL_APP} DBus Gui Qml Quick Test)
47if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
47if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
48 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
48 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
49endif()
49endif()
50−target_link_libraries(${SHELL_APP} ${ANDROID_PROPERTIES_LDFLAGS} UbuntuGestures connectivity-qt1 unity8-private)
50+target_link_libraries(${SHELL_APP}
51+ ${ANDROID_PROPERTIES_LDFLAGS}
52+ ${QTMIRSERVER_LDFLAGS}
53+ UbuntuGestures
54+ connectivity-qt1
55+ unity8-private
56+)
51
57
52if (ENABLE_TOUCH_EMULATION)
58if (ENABLE_TOUCH_EMULATION)
53 target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
59 target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
src/DebuggingController.cpp+12−1
@@ -49,7 +49,8 @@ public:
49};
49};
50
50
51DebuggingController::DebuggingController(QObject *parent):
51DebuggingController::DebuggingController(QObject *parent):
52− UnityDBusObject(QStringLiteral("/com/canonical/Unity8/Debugging"), QStringLiteral("com.canonical.Unity8"), true, parent)
52+ UnityDBusObject(QStringLiteral("/com/canonical/Unity8/Debugging"), QStringLiteral("com.canonical.Unity8"), true, parent),
53+ m_logOverlay(false)
53{
54{
54}
55}
55
56
@@ -90,3 +91,13 @@ void DebuggingController::SetLoggingFilterRules(const QString &filterRules)
90{
91{
91 QLoggingCategory::setFilterRules(filterRules);
92 QLoggingCategory::setFilterRules(filterRules);
92}
93}
94+
95+void DebuggingController::SetLogOverlay(bool logOverlay)
96+{
97+ if (logOverlay == m_logOverlay) {
98+ return;
99+ }
100+
101+ m_logOverlay = logOverlay;
102+ Q_EMIT logOverlayChanged(m_logOverlay);
103+}
src/DebuggingController.h+14
@@ -32,10 +32,14 @@ class DebuggingController: public UnityDBusObject
32 Q_OBJECT
32 Q_OBJECT
33 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity8.Debugging")
33 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity8.Debugging")
34
34
35+ Q_PROPERTY(bool logOverlay READ logOverlay NOTIFY logOverlayChanged)
36+
35public:
37public:
36 DebuggingController(QObject *parent = nullptr);
38 DebuggingController(QObject *parent = nullptr);
37 ~DebuggingController() = default;
39 ~DebuggingController() = default;
38
40
41+ bool logOverlay() const { return m_logOverlay; }
42+
39public Q_SLOTS:
43public Q_SLOTS:
40 /**
44 /**
41 * Set the QSG_VISUALIZE mode. This follows the vlues supported by Qt in
45 * Set the QSG_VISUALIZE mode. This follows the vlues supported by Qt in
@@ -53,5 +57,15 @@ public Q_SLOTS:
53 */
57 */
54 Q_SCRIPTABLE void SetLoggingFilterRules(const QString &filterRules);
58 Q_SCRIPTABLE void SetLoggingFilterRules(const QString &filterRules);
55
59
60+ /**
61+ * Enable/Disable the shell console log overlay
62+ */
63+ Q_SCRIPTABLE void SetLogOverlay(bool logOverlay);
64+
65+Q_SIGNALS:
66+ void logOverlayChanged(bool logOverlay);
67+
68+private:
69+ bool m_logOverlay;
56};
70};
57#endif // DEBUGGINGCONTROLLER_H
71#endif // DEBUGGINGCONTROLLER_H
src/SecondaryWindow.cppdeleted−40
@@ -1,40 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "SecondaryWindow.h"
18−
19−// local
20−#include <paths.h>
21−
22−#include <QQmlContext>
23−
24−SecondaryWindow::SecondaryWindow(QQmlEngine *engine)
25− : QQuickView(engine, nullptr)
26−{
27− QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
28− bool ok;
29− int pxpgu = pxpguEnv.toInt(&ok);
30− if (!ok) {
31− pxpgu = 8;
32− }
33− engine->rootContext()->setContextProperty(QStringLiteral("internalGu"), pxpgu);
34− setResizeMode(QQuickView::SizeRootObjectToView);
35− setColor("black");
36− setTitle(QStringLiteral("Unity8 Shell - Secondary Screen"));
37−
38− QUrl source(::qmlDirectory() + "/DisabledScreenNotice.qml");
39− setSource(source);
40−}
src/ShellApplication.cppdeleted−216
@@ -1,216 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "ShellApplication.h"
18−
19−// Qt
20−#include <QLibrary>
21−#include <QProcess>
22−#include <QScreen>
23−
24−#include <libintl.h>
25−
26−// libandroid-properties
27−#include <hybris/properties/properties.h>
28−
29−// local
30−#include <paths.h>
31−#include "CachingNetworkManagerFactory.h"
32−#include "UnityCommandLineParser.h"
33−#include "DebuggingController.h"
34−
35−ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
36− : QGuiApplication(argc, argv)
37−{
38− setApplicationName(QStringLiteral("unity8"));
39− setOrganizationName(QStringLiteral("Canonical"));
40−
41− connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
42−
43− setupQmlEngine(isMirServer);
44−
45− UnityCommandLineParser parser(*this);
46−
47− if (!parser.deviceName().isEmpty()) {
48− m_deviceName = parser.deviceName();
49− } else {
50− char buffer[200];
51− property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
52− m_deviceName = QString(buffer);
53− }
54− m_qmlArgs.setDeviceName(m_deviceName);
55−
56− m_qmlArgs.setMode(parser.mode());
57−
58− // The testability driver is only loaded by QApplication but not by QGuiApplication.
59− // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
60− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
61− QLibrary testLib(QStringLiteral("qttestability"));
62− if (testLib.load()) {
63− typedef void (*TasInitialize)(void);
64− TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
65− if (initFunction) {
66− initFunction();
67− } else {
68− qCritical("Library qttestability resolve failed!");
69− }
70− } else {
71− qCritical("Library qttestability load failed!");
72− }
73− }
74−
75− bindtextdomain("unity8", translationDirectory().toUtf8().data());
76− textdomain("unity8");
77−
78− m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
79−
80− if (parser.windowGeometry().isValid()) {
81− m_shellView->setWidth(parser.windowGeometry().width());
82− m_shellView->setHeight(parser.windowGeometry().height());
83− }
84−
85− if (parser.hasFrameless()) {
86− m_shellView->setFlags(Qt::FramelessWindowHint);
87− }
88−
89−
90− #ifdef UNITY8_ENABLE_TOUCH_EMULATION
91− // You will need this if you want to interact with touch-only components using a mouse
92− // Needed only when manually testing on a desktop.
93− if (parser.hasMouseToTouch()) {
94− m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
95− }
96− #endif
97−
98− new DebuggingController(this);
99−
100− // Some hard-coded policy for now.
101− // NB: We don't support more than two screens at the moment
102− //
103− // TODO: Support an arbitrary number of screens and different policies
104− // (eg cloned desktop, several desktops, etc)
105− if (isMirServer && screens().count() == 2) {
106− m_shellView->setScreen(screens().at(1));
107− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
108−
109− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
110− m_secondaryWindow->setScreen(screens().at(0));
111− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
112− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
113− m_secondaryWindow->setVisible(true);
114− }
115−
116− if (parser.mode().compare("greeter") == 0) {
117− QSize primaryScreenSize = this->primaryScreen()->size();
118− m_shellView->setHeight(primaryScreenSize.height());
119− m_shellView->setWidth(primaryScreenSize.width());
120− m_shellView->show();
121− m_shellView->requestActivate();
122− if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
123− qDebug() << "Unable to send unity8-greeter-started event to Upstart";
124− }
125− } else if (isMirServer || parser.hasFullscreen()) {
126− m_shellView->showFullScreen();
127− } else {
128− m_shellView->show();
129− }
130−}
131−
132−ShellApplication::~ShellApplication()
133−{
134− destroyResources();
135−}
136−
137−void ShellApplication::destroyResources()
138−{
139− // Deletion order is important. Don't use QScopedPointers and the like
140− // Otherwise the process will hang on shutdown (bug somewhere I guess).
141− delete m_shellView;
142− m_shellView = nullptr;
143−
144− delete m_secondaryWindow;
145− m_secondaryWindow = nullptr;
146−
147− #ifdef UNITY8_ENABLE_TOUCH_EMULATION
148− delete m_mouseTouchAdaptor;
149− m_mouseTouchAdaptor = nullptr;
150− #endif
151−
152− delete m_qmlEngine;
153− m_qmlEngine = nullptr;
154−}
155−
156−void ShellApplication::setupQmlEngine(bool isMirServer)
157−{
158− m_qmlEngine = new QQmlEngine(this);
159−
160− m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
161−
162− prependImportPaths(m_qmlEngine, ::overrideImportPaths());
163− if (!isMirServer) {
164− prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
165− }
166− appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
167−
168− m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
169−
170− QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
171−}
172−
173−void ShellApplication::onScreenAdded(QScreen * /*screen*/)
174−{
175− // TODO: Support an arbitrary number of screens and different policies
176− // (eg cloned desktop, several desktops, etc)
177− if (screens().count() == 2) {
178− m_shellView->setScreen(screens().at(1));
179− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
180− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
181− // its backing QPlatformWindow recreated). So lets refocus it.
182− m_shellView->requestActivate();
183− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
184− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
185− m_shellView->setVisible(true);
186−
187− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
188− m_secondaryWindow->setScreen(screens().at(0));
189−
190− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
191− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
192− m_secondaryWindow->setVisible(true);
193− }
194−}
195−
196−void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
197−{
198− // TODO: Support an arbitrary number of screens and different policies
199− // (eg cloned desktop, several desktops, etc)
200− if (screen == m_shellView->screen()) {
201− const QList<QScreen *> allScreens = screens();
202− Q_ASSERT(allScreens.count() > 1);
203− Q_ASSERT(allScreens.at(0) != screen);
204− Q_ASSERT(m_secondaryWindow);
205− delete m_secondaryWindow;
206− m_secondaryWindow = nullptr;
207− m_shellView->setScreen(allScreens.first());
208− m_qmlArgs.setDeviceName(m_deviceName);
209− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
210− // its backing QPlatformWindow recreated). So lets refocus it.
211− m_shellView->requestActivate();
212− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
213− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
214− m_shellView->setVisible(true);
215− }
216−}
src/ShellView.cppdeleted−63
@@ -1,63 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "ShellView.h"
18−
19−// Qt
20−#include <QQmlContext>
21−#include <QQuickItem>
22−
23−// local
24−#include <paths.h>
25−
26−ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
27− : QQuickView(engine, nullptr)
28−{
29− setResizeMode(QQuickView::SizeRootObjectToView);
30− setColor("black");
31− setTitle(QStringLiteral("Unity8"));
32−
33− rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
34−
35− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
36− setSource(source);
37−
38− connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
39− connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
40−}
41−
42−void ShellView::onWidthChanged(int w)
43−{
44− // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
45− //
46− // There's at least one situation that's know to leave the root object with an outdated size.
47− // (really looks like Qt bug)
48− // Happens when starting unity8 with an external monitor already connected.
49− // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
50− // from the resize event handler, the root item doesn't get resized.
51− // TODO: Confirm the Qt bug and submit a patch upstream
52− if (rootObject()) {
53− rootObject()->setWidth(w);
54− }
55−}
56−
57−void ShellView::onHeightChanged(int h)
58−{
59− // See comment in ShellView::onWidthChanged()
60− if (rootObject()) {
61− rootObject()->setHeight(h);
62− }
63−}
src/UnityApplication.cppnew file+131
@@ -0,0 +1,131 @@
1+/*
2+ * Copyright (C) 2015 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 "UnityApplication.h"
18+
19+// Qt
20+#include <QLibrary>
21+#include <QProcess>
22+#include <QScreen>
23+#include <QQmlContext>
24+
25+#include <libintl.h>
26+
27+// libandroid-properties
28+#include <hybris/properties/properties.h>
29+
30+// local
31+#include <paths.h>
32+#include "CachingNetworkManagerFactory.h"
33+#include "UnityCommandLineParser.h"
34+#include "DebuggingController.h"
35+#include "WindowManagementPolicy.h"
36+
37+#include <QDebug>
38+
39+
40+
41+UnityApplication::UnityApplication(int & argc, char ** argv)
42+ : qtmir::MirServerApplication(argc, argv, { qtmir::SetWindowManagementPolicy<WindowManagementPolicy>() })
43+ , m_qmlArgs(this)
44+{
45+ setApplicationName(QStringLiteral("unity8"));
46+ setOrganizationName(QStringLiteral("Canonical"));
47+
48+ setupQmlEngine();
49+
50+ if (m_qmlArgs.deviceName().isEmpty()) {
51+ char buffer[200];
52+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
53+ m_qmlArgs.setDeviceName(QString(buffer));
54+ }
55+
56+ // The testability driver is only loaded by QApplication but not by QGuiApplication.
57+ // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
58+ if (m_qmlArgs.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
59+ QLibrary testLib(QStringLiteral("qttestability"));
60+ if (testLib.load()) {
61+ typedef void (*TasInitialize)(void);
62+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
63+ if (initFunction) {
64+ initFunction();
65+ } else {
66+ qCritical("Library qttestability resolve failed!");
67+ }
68+ } else {
69+ qCritical("Library qttestability load failed!");
70+ }
71+ }
72+
73+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
74+ textdomain("unity8");
75+
76+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &m_qmlArgs);
77+ m_qmlEngine->rootContext()->setContextProperty("DebuggingController", new DebuggingController(this));
78+
79+ QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
80+ bool ok;
81+ int pxpgu = pxpguEnv.toInt(&ok);
82+ if (!ok) {
83+ pxpgu = 8;
84+ }
85+ m_qmlEngine->rootContext()->setContextProperty("internalGu", pxpgu);
86+ m_qmlEngine->load(m_qmlArgs.qmlfie());
87+
88+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
89+ // You will need this if you want to interact with touch-only components using a mouse
90+ // Needed only when manually testing on a desktop.
91+ if (m_qmlArgs.hasMouseToTouch()) {
92+ m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
93+ }
94+ #endif
95+
96+ if (m_qmlArgs.mode().compare("greeter") == 0) {
97+ if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
98+ qDebug() << "Unable to send unity8-greeter-started event to Upstart";
99+ }
100+ }
101+}
102+
103+UnityApplication::~UnityApplication()
104+{
105+ destroyResources();
106+}
107+
108+void UnityApplication::destroyResources()
109+{
110+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
111+ delete m_mouseTouchAdaptor;
112+ m_mouseTouchAdaptor = nullptr;
113+ #endif
114+
115+ delete m_qmlEngine;
116+ m_qmlEngine = nullptr;
117+}
118+
119+void UnityApplication::setupQmlEngine()
120+{
121+ m_qmlEngine = new QQmlApplicationEngine(this);
122+
123+ m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
124+
125+ prependImportPaths(m_qmlEngine, ::overrideImportPaths());
126+ appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
127+
128+ m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
129+
130+ QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
131+}
src/ShellApplication.h → src/UnityApplication.hrenamed+10−20
@@ -14,11 +14,11 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef SHELLAPPLICATION_H
17+#ifndef UNITYAPPLICATION_H
18−#define SHELLAPPLICATION_H
18+#define UNITYAPPLICATION_H
19
19
20#include <QGuiApplication>
20#include <QGuiApplication>
21−#include <QQmlEngine>
21+#include <QQmlApplicationEngine>
22#include <QQuickView>
22#include <QQuickView>
23#include <QScopedPointer>
23#include <QScopedPointer>
24
24
@@ -28,36 +28,26 @@
28#include "MouseTouchAdaptor.h"
28#include "MouseTouchAdaptor.h"
29#endif
29#endif
30
30
31−#include "SecondaryWindow.h"
31+#include <qtmir/mirserverapplication.h>
32−#include "ShellView.h"
33
32
34−class ShellApplication : public QGuiApplication
33+class UnityApplication : public qtmir::MirServerApplication
35{
34{
36 Q_OBJECT
35 Q_OBJECT
37public:
36public:
38− ShellApplication(int & argc, char ** argv, bool isMirServer);
37+ UnityApplication(int & argc, char ** argv);
39− virtual ~ShellApplication();
38+ virtual ~UnityApplication();
40
39
41 void destroyResources();
40 void destroyResources();
42−public Q_SLOTS:
43− // called by qtmir
44− void onScreenAboutToBeRemoved(QScreen *screen);
45−
46−private Q_SLOTS:
47− void onScreenAdded(QScreen*);
48
41
49private:
42private:
50− void setupQmlEngine(bool isMirServer);
43+ void setupQmlEngine();
51− QString m_deviceName;
52 ApplicationArguments m_qmlArgs;
44 ApplicationArguments m_qmlArgs;
53− ShellView *m_shellView{nullptr};
54− SecondaryWindow *m_secondaryWindow{nullptr};
55
45
56 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
46 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
57 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
47 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
58 #endif
48 #endif
59
49
60− QQmlEngine *m_qmlEngine{nullptr};
50+ QQmlApplicationEngine *m_qmlEngine{nullptr};
61};
51};
62
52
63−#endif // SHELLAPPLICATION_H
53+#endif // UNITYAPPLICATION_H
src/UnityCommandLineParser.cpp+8
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include "UnityCommandLineParser.h"
17#include "UnityCommandLineParser.h"
18+#include <paths.h>
18
19
19#include <QDebug>
20#include <QDebug>
20
21
@@ -60,6 +61,11 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
60 QStringLiteral("mode"), QStringLiteral("full-greeter"));
61 QStringLiteral("mode"), QStringLiteral("full-greeter"));
61 parser.addOption(modeOption);
62 parser.addOption(modeOption);
62
63
64+ QCommandLineOption qmlfileOption(QStringLiteral("qmlfile"),
65+ QStringLiteral("The base qml file to load"),
66+ QStringLiteral("qmlfile"), ::qmlDirectory() + "/ShellApplication.qml");
67+ parser.addOption(qmlfileOption);
68+
63 // Treat args with single dashes the same as arguments with two dashes
69 // Treat args with single dashes the same as arguments with two dashes
64 // Ex: -fullscreen == --fullscreen
70 // Ex: -fullscreen == --fullscreen
65 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
71 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
@@ -85,6 +91,8 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
85 m_hasFullscreen = parser.isSet(fullscreenOption);
91 m_hasFullscreen = parser.isSet(fullscreenOption);
86 m_deviceName = parser.value(devicenameOption);
92 m_deviceName = parser.value(devicenameOption);
87 resolveMode(parser, modeOption);
93 resolveMode(parser, modeOption);
94+
95+ m_qmlfile = parser.value(qmlfileOption);
88}
96}
89
97
90int UnityCommandLineParser::parsePixelsValue(const QString &str)
98int UnityCommandLineParser::parsePixelsValue(const QString &str)
src/UnityCommandLineParser.h+4−1
@@ -36,8 +36,10 @@ public:
36 bool hasFullscreen() const { return m_hasFullscreen; }
36 bool hasFullscreen() const { return m_hasFullscreen; }
37 QString deviceName() const { return m_deviceName; }
37 QString deviceName() const { return m_deviceName; }
38 QString mode() const { return m_mode; }
38 QString mode() const { return m_mode; }
39−private:
40
39
40+ QString qmlfie() const { return m_qmlfile; }
41+
42+protected:
41 int parsePixelsValue(const QString &str);
43 int parsePixelsValue(const QString &str);
42 static float getenvFloat(const char* name, float defaultValue);
44 static float getenvFloat(const char* name, float defaultValue);
43 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
45 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
@@ -55,6 +57,7 @@ private:
55 bool m_hasFullscreen;
57 bool m_hasFullscreen;
56 QString m_deviceName;
58 QString m_deviceName;
57 QString m_mode;
59 QString m_mode;
60+ QString m_qmlfile;
58};
61};
59
62
60#endif // UNITY_COMMAND_LINE_PARSER_H
63#endif // UNITY_COMMAND_LINE_PARSER_H
src/WindowManagementPolicy.cppnew file+78
@@ -0,0 +1,78 @@
1+/*
2+ * Copyright (C) 2017 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 "WindowManagementPolicy.h"
18+
19+WindowManagementPolicy::WindowManagementPolicy(const miral::WindowManagerTools &tools, qtmir::WindowManagementPolicyPrivate &dd)
20+ : qtmir::WindowManagementPolicy(tools, dd)
21+ , m_dummyWorkspace(this->tools.create_workspace())
22+{
23+ wmPolicyInterface = this;
24+
25+ // we must always have a active workspace.
26+ m_activeWorkspace = m_dummyWorkspace;
27+}
28+
29+void WindowManagementPolicy::advise_new_window(miral::WindowInfo const& window_info)
30+{
31+ qtmir::WindowManagementPolicy::advise_new_window(window_info);
32+
33+ auto const parent = window_info.parent();
34+
35+ auto activeWorkspace = m_activeWorkspace.lock();
36+ if (!parent && activeWorkspace)
37+ tools.add_tree_to_workspace(window_info.window(), activeWorkspace);
38+}
39+
40+std::shared_ptr<miral::Workspace> WindowManagementPolicy::createWorkspace()
41+{
42+ auto workspace = tools.create_workspace();
43+ m_workspaces.insert(workspace);
44+
45+ if (m_activeWorkspace.lock() == m_dummyWorkspace) {
46+ moveWorkspaceContentToWorkspace(workspace, m_dummyWorkspace);
47+ m_activeWorkspace = workspace;
48+ }
49+ return workspace;
50+}
51+
52+void WindowManagementPolicy::releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
53+{
54+ auto iter = m_workspaces.find(workspace);
55+ if (iter != m_workspaces.end()) m_workspaces.erase(iter);
56+
57+ if (m_workspaces.size() == 0) {
58+ m_activeWorkspace = m_dummyWorkspace;
59+ moveWorkspaceContentToWorkspace(m_dummyWorkspace, workspace);
60+ }
61+}
62+
63+void WindowManagementPolicy::forEachWindowInWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::function<void (const miral::Window &)> &callback)
64+{
65+ tools.for_each_window_in_workspace(workspace, callback);
66+}
67+
68+void WindowManagementPolicy::moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &toWorkspace, const std::shared_ptr<miral::Workspace> &fromWorkspace)
69+{
70+ tools.move_workspace_content_to_workspace(toWorkspace, fromWorkspace);
71+}
72+
73+void WindowManagementPolicy::setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
74+{
75+ if (m_activeWorkspace.lock() == workspace)
76+ return;
77+ m_activeWorkspace = workspace ? workspace : m_dummyWorkspace;
78+}
src/WindowManagementPolicy.hnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 2017 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_WINDOWMANAGEMENTPOLICY_H
18+#define UNITY_WINDOWMANAGEMENTPOLICY_H
19+
20+#include <qtmir/windowmanagementpolicy.h>
21+#include "wmpolicyinterface.h"
22+
23+#include <unordered_set>
24+
25+class Q_DECL_EXPORT WindowManagementPolicy : public qtmir::WindowManagementPolicy,
26+ public WMPolicyInterface
27+{
28+public:
29+ WindowManagementPolicy(const miral::WindowManagerTools &tools, qtmir::WindowManagementPolicyPrivate& dd);
30+
31+ void advise_new_window(miral::WindowInfo const& window_info) override;
32+
33+ // From WMPolicyInterface
34+ std::shared_ptr<miral::Workspace> createWorkspace() override;
35+
36+ void releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace) override;
37+
38+ void forEachWindowInWorkspace(
39+ std::shared_ptr<miral::Workspace> const& workspace,
40+ std::function<void(miral::Window const& window)> const& callback) override;
41+
42+ void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &toWorkspace,
43+ const std::shared_ptr<miral::Workspace> &fromWorkspace) override;
44+
45+ void setActiveWorkspace(const std::shared_ptr<miral::Workspace>& workspace) override;
46+
47+private:
48+ std::weak_ptr<miral::Workspace> m_activeWorkspace;
49+
50+ std::unordered_set<std::shared_ptr<miral::Workspace>> m_workspaces;
51+ const std::shared_ptr<miral::Workspace> m_dummyWorkspace;
52+};
53+
54+#endif // UNITY_WINDOWMANAGEMENTPOLICY_H
src/libunity8-private/CMakeLists.txt+1
@@ -8,6 +8,7 @@ set(lib${LIB_NAME}_SRCS
8 abstractdbusservicemonitor.cpp
8 abstractdbusservicemonitor.cpp
9 unitydbusobject.cpp
9 unitydbusobject.cpp
10 unitydbusvirtualobject.cpp
10 unitydbusvirtualobject.cpp
11+ wmpolicyinterface.cpp
11 )
12 )
12
13
13add_library(${LIB_NAME} SHARED
14add_library(${LIB_NAME} SHARED
src/SecondaryWindow.h → src/libunity8-private/wmpolicyinterface.cpprenamed+6−12
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,11 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_SECONDARY_WINDOW_H
17+#include "wmpolicyinterface.h"
18−#define UNITY_SECONDARY_WINDOW_H
19
18
20−#include <QQuickView>
19+WMPolicyInterface* wmPolicyInterface = nullptr;
21
20
22−class SecondaryWindow : public QQuickView
21+WMPolicyInterface *WMPolicyInterface::instance()
23{
22{
24− Q_OBJECT
23+ return wmPolicyInterface;
25−
26−public:
27− SecondaryWindow(QQmlEngine *engine);
28−};
24+}
29−
30−#endif // UNITY_SECONDARY_WINDOW_H
src/libunity8-private/wmpolicyinterface.hnew file+51
@@ -0,0 +1,51 @@
1+/*
2+ * Copyright (C) 2017 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 WMPOLICYINTERFACE_H
18+#define WMPOLICYINTERFACE_H
19+
20+#include <memory>
21+#include <functional>
22+
23+#include <qglobal.h>
24+
25+namespace miral {
26+class Workspace;
27+class Window;
28+}
29+
30+class Q_DECL_EXPORT WMPolicyInterface
31+{
32+public:
33+ static WMPolicyInterface *instance();
34+
35+ virtual std::shared_ptr<miral::Workspace> createWorkspace() = 0;
36+
37+ virtual void releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace) = 0;
38+
39+ virtual void forEachWindowInWorkspace(
40+ std::shared_ptr<miral::Workspace> const& workspace,
41+ std::function<void(miral::Window const& window)> const& callback) = 0;
42+
43+ virtual void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &toWorkspace,
44+ const std::shared_ptr<miral::Workspace> &fromWorkspace) = 0;
45+
46+ virtual void setActiveWorkspace(const std::shared_ptr<miral::Workspace>& workspace) = 0;
47+};
48+
49+extern Q_DECL_EXPORT WMPolicyInterface* wmPolicyInterface;
50+
51+#endif // WMPOLICYINTERFACE_H
src/main.cpp+4−8
@@ -15,9 +15,10 @@
15 */
15 */
16
16
17// local
17// local
18−#include "ShellApplication.h"
18+#include "UnityApplication.h"
19#include "qmldebuggerutils.h"
19#include "qmldebuggerutils.h"
20#include "UnixSignalHandler.h"
20#include "UnixSignalHandler.h"
21+#include <paths.h>
21
22
22#include <QTranslator>
23#include <QTranslator>
23#include <QLibraryInfo>
24#include <QLibraryInfo>
@@ -27,17 +28,12 @@ int main(int argc, const char *argv[])
27{
28{
28 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
29 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
29
30
30− bool isMirServer = qgetenv("QT_QPA_PLATFORM") == "mirserver";
31− if (!isMirServer && qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
32− setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
33− isMirServer = true;
34− }
35−
36 if (enableQmlDebugger(argc, argv)) {
31 if (enableQmlDebugger(argc, argv)) {
37 QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
32 QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
38 }
33 }
39
34
40− ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
35+ auto *application = new UnityApplication(argc,
36+ (char**)argv);
41
37
42 UnixSignalHandler signalHandler([]{
38 UnixSignalHandler signalHandler([]{
43 QGuiApplication::exit(0);
39 QGuiApplication::exit(0);
tests/mocks/CMakeLists.txt+1
@@ -43,6 +43,7 @@ add_subdirectory(SessionBroadcast)
43add_subdirectory(Ubuntu)
43add_subdirectory(Ubuntu)
44add_subdirectory(Unity)
44add_subdirectory(Unity)
45add_subdirectory(QtMultimedia)
45add_subdirectory(QtMultimedia)
46+add_subdirectory(WindowManager)
46add_subdirectory(Wizard)
47add_subdirectory(Wizard)
47add_subdirectory(Utils)
48add_subdirectory(Utils)
48add_subdirectory(UInput)
49add_subdirectory(UInput)
tests/mocks/Cursor/Cursor.qml+49−2
@@ -15,8 +15,11 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
18
22
19−Item {
20 property int topBoundaryOffset // effectively panel height
23 property int topBoundaryOffset // effectively panel height
21 property Item confiningItem
24 property Item confiningItem
22
25
@@ -30,5 +33,49 @@ Item {
30 signal pushStopped()
33 signal pushStopped()
31 signal mouseMoved()
34 signal mouseMoved()
32
35
36+ width: units.gu(2)
37+ height: units.gu(2)
38+ antialiasing: true
39+
40+ onPaint: {
41+ var ctx = getContext("2d");
42+ ctx.save();
43+ ctx.clearRect(0,0,width, height);
44+ ctx.strokeStyle = "#000000";
45+ ctx.lineWidth = 1
46+ ctx.fillStyle = "#ffffff";
47+ ctx.globalAlpha = 1.0;
48+ ctx.lineJoin = "round";
49+ ctx.beginPath();
50+
51+ // put rectangle in the middle
52+ // draw the rectangle
53+ ctx.moveTo(width/2,height);
54+ ctx.lineTo(width, height/2);
55+ ctx.lineTo(0,0);
56+
57+ ctx.closePath();
58+ ctx.fill();
59+ ctx.stroke();
60+ ctx.restore();
61+ }
62+
63+ Connections {
64+ target: UInput
33− onMouseMoved: opacity = 1;
65+ onMouseMoved: {
66+ var newX = root.x;
67+ newX += dx;
68+ if (newX < 0) newX = 0;
69+ else if (newX >= parent.width) newX = parent.width-1;
70+
71+ var newY = root.y;
72+ newY += dy;
73+ if (newY < 0) newY = 0;
74+ else if (newY >= parent.height) newY = parent.height-1;
75+
76+ root.x = newX;
77+ root.y = newY;
78+ root.mouseMoved();
79+ }
80+ }
34}
81}
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
18#include "fake_gsettings.h"
18#include "fake_gsettings.h"
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
21
23
22static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
24static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23{
25{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
31 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
33 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
32 "GSettingsSchema can only be used inside of a GSettings component");
34 "GSettingsSchema can only be used inside of a GSettings component");
33}
35}
36+
37+void FakeGSettingsQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
38+{
39+ QQmlExtensionPlugin::initializeEngine(engine, uri);
40+
41+ QString usageType = qgetenv("UNITY_MOCK_DESKTOP");
42+ if (!usageType.isEmpty()) {
43+ GSettingsControllerQml::instance()->setUsageMode("Windowed");
44+ }
45+}
tests/mocks/GSettings.1.0/plugin.h+1
@@ -26,6 +26,7 @@ class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
26
26
27public:
27public:
28 void registerTypes(const char *uri) override;
28 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
29};
30};
30
31
31#endif // PLUGIN_H
32#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
22void MockUInputPlugin::registerTypes(const char *uri)
27void MockUInputPlugin::registerTypes(const char *uri)
23{
28{
24 Q_ASSERT(uri == QLatin1String("UInput"));
29 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
31}
tests/mocks/Unity/Application/CMakeLists.txt+26−1
@@ -1,3 +1,12 @@
1+pkg_check_modules(MIRTEST mirtest>=0.26 REQUIRED)
2+
3+include_directories(
4+ SYSTEM
5+ ${MIRTEST_INCLUDE_DIRS}
6+ ${MIRAL_INCLUDE_DIRS}
7+ ${libunity8-private_SOURCE_DIR}
8+)
9+
1set(FakeUnityApplicationQml_SOURCES
10set(FakeUnityApplicationQml_SOURCES
2 plugin.cpp
11 plugin.cpp
3 ApplicationInfo.cpp
12 ApplicationInfo.cpp
@@ -19,11 +28,27 @@ set(FakeUnityApplicationQml_SOURCES
19 resources/surfaces.qrc
28 resources/surfaces.qrc
20)
29)
21
30
22−add_library(FakeUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
31+add_library(FakeUnityApplicationQml MODULE
32+ ${FakeUnityApplicationQml_SOURCES}
33+)
34+
35+#add_dependencies(FakeUnityApplicationQml windowmanagementpolicy)
36+
37+target_link_libraries(FakeUnityApplicationQml
38+ ${MIRTEST_LDFLAGS}
39+ ${MIRAL_LDFLAGS}
40+ mockwindowmanagmentpolicy
41+)
23
42
24add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
43add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
25set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
44set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
26
45
46+target_link_libraries(NonMirUnityApplicationQml
47+ ${MIRTEST_LDFLAGS}
48+ ${MIRAL_LDFLAGS}
49+ mockwindowmanagmentpolicy
50+)
51+
27qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
52qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
28qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
53qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
29
54
tests/mocks/Unity/Application/MirSurfaceItem.cpp+27−4
@@ -189,9 +189,15 @@ void MirSurfaceItem::createQmlContentItem()
189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
190 m_qmlItem->setParentItem(this);
190 m_qmlItem->setParentItem(this);
191
191
192+ if (m_fillMode == FillMode::Stretch && width() != 0 && height() != 0) {
193+ m_qmlItem->setWidth(width());
194+ m_qmlItem->setHeight(height());
195+ } else {
192− m_qmlItem->setWidth(m_surfaceWidth);
196+ m_qmlItem->setWidth(m_surfaceWidth);
193− m_qmlItem->setHeight(m_surfaceHeight);
197+ m_qmlItem->setHeight(m_surfaceHeight);
198+ }
194
199
200+ qDebug() << "set size to" << m_surfaceWidth;
195 setImplicitWidth(m_qmlItem->implicitWidth());
201 setImplicitWidth(m_qmlItem->implicitWidth());
196 setImplicitHeight(m_qmlItem->implicitHeight());
202 setImplicitHeight(m_qmlItem->implicitHeight());
197
203
@@ -308,6 +314,16 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
308 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
314 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
309 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
315 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
310 connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
316 connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
317+ connect(m_qmlSurface, &MirSurface::sizeChanged, this, [this] () {
318+ if (m_fillMode == FillMode::Stretch) {
319+ m_qmlItem->setSize(QSize(this->width(), this->height()));
320+ } else {
321+ m_qmlItem->setSize(m_qmlSurface->size());
322+ }
323+ setImplicitSize(m_qmlSurface->width(), m_qmlSurface->height());
324+ });
325+ m_surfaceWidth = surface->size().width();
326+ m_surfaceHeight = surface->size().height();
311
327
312 QUrl qmlComponentFilePath;
328 QUrl qmlComponentFilePath;
313 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
329 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
@@ -321,6 +337,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
321 switch (m_qmlContentComponent->status()) {
337 switch (m_qmlContentComponent->status()) {
322 case QQmlComponent::Ready:
338 case QQmlComponent::Ready:
323 createQmlContentItem();
339 createQmlContentItem();
340+ qDebug() << "content created" << m_surfaceWidth << implicitWidth() << width();
324 break;
341 break;
325 case QQmlComponent::Loading:
342 case QQmlComponent::Loading:
326 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
343 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
@@ -399,9 +416,15 @@ void MirSurfaceItem::updateSurfaceSize()
399 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
416 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
400 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
417 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
401 if (m_qmlItem) {
418 if (m_qmlItem) {
419+ if (m_fillMode == FillMode::Stretch) {
420+ m_qmlItem->setWidth(width());
421+ m_qmlItem->setHeight(height());
422+ } else {
402− m_qmlItem->setWidth(m_surfaceWidth);
423+ m_qmlItem->setWidth(m_surfaceWidth);
403− m_qmlItem->setHeight(m_surfaceHeight);
424+ m_qmlItem->setHeight(m_surfaceHeight);
425+ }
404 }
426 }
427+ qDebug() << this << "setting implicitsize" << m_surfaceWidth << m_surfaceHeight;
405 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
428 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
406 }
429 }
407}
430}
tests/mocks/Unity/Application/SurfaceManager.cpp+60−2
@@ -18,10 +18,12 @@
18
18
19#include "ApplicationInfo.h"
19#include "ApplicationInfo.h"
20#include "VirtualKeyboard.h"
20#include "VirtualKeyboard.h"
21+#include "../../WindowManager/WindowManagementPolicy.h"
21
22
22#include <paths.h>
23#include <paths.h>
24+#include <mirtest/mir/test/doubles/stub_surface.h>
23
25
24−#define SURFACEMANAGER_DEBUG 0
26+#define SURFACEMANAGER_DEBUG 1
25
27
26#if SURFACEMANAGER_DEBUG
28#if SURFACEMANAGER_DEBUG
27#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
29#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
@@ -31,6 +33,12 @@
31
33
32namespace unityapi = unity::shell::application;
34namespace unityapi = unity::shell::application;
33
35
36+uint qHash(const WindowWrapper &key, uint)
37+{
38+ std::shared_ptr<mir::scene::Surface> surface = key.window;
39+ return (quintptr)surface.get();
40+}
41+
34SurfaceManager *SurfaceManager::m_instance = nullptr;
42SurfaceManager *SurfaceManager::m_instance = nullptr;
35
43
36SurfaceManager *SurfaceManager::instance()
44SurfaceManager *SurfaceManager::instance()
@@ -44,6 +52,21 @@ SurfaceManager::SurfaceManager(QObject *)
44
52
45 Q_ASSERT(m_instance == nullptr);
53 Q_ASSERT(m_instance == nullptr);
46 m_instance = this;
54 m_instance = this;
55+
56+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowAdded,
57+ this, [this](const miral::Window& window) {
58+ Q_EMIT surfaceCreated(surfaceFor(window));
59+ });
60+
61+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAddedToWorkspace,
62+ this, [this](const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows) {
63+ Q_EMIT surfacesAddedToWorkspace(workspace, find(windows));
64+ });
65+
66+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAboutToBeRemovedFromWorkspace,
67+ this, [this](const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows) {
68+ Q_EMIT surfacesAboutToBeRemovedFromWorkspace(workspace, find(windows));
69+ });
47}
70}
48
71
49SurfaceManager::~SurfaceManager()
72SurfaceManager::~SurfaceManager()
@@ -58,6 +81,29 @@ SurfaceManager::~SurfaceManager()
58 m_instance = nullptr;
81 m_instance = nullptr;
59}
82}
60
83
84+
85+
86+QVector<MirSurfaceInterface*> SurfaceManager::find(const std::vector<miral::Window> &windows) const
87+{
88+ QVector<unityapi::MirSurfaceInterface*> surfaces;
89+ for (size_t i = 0; i < windows.size(); i++) {
90+ auto mirSurface = surfaceFor(windows[i]);
91+ if (mirSurface) {
92+ surfaces.push_back(mirSurface);
93+ }
94+ }
95+ return surfaces;
96+}
97+
98+MirSurfaceInterface *SurfaceManager::surfaceFor(const miral::Window& window) const
99+{
100+ auto iter = m_windowToSurface.find({window});
101+ if (iter != m_windowToSurface.end()) {
102+ return *iter;
103+ }
104+ return nullptr;
105+}
106+
61MirSurface *SurfaceManager::createSurface(const QString& name,
107MirSurface *SurfaceManager::createSurface(const QString& name,
62 Mir::Type type,
108 Mir::Type type,
63 Mir::State state,
109 Mir::State state,
@@ -75,7 +121,12 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
75
121
76void SurfaceManager::registerSurface(MirSurface *surface)
122void SurfaceManager::registerSurface(MirSurface *surface)
77{
123{
124+ auto fakeSurface = std::make_shared<mir::test::doubles::StubSurface>();
125+ WindowWrapper window{miral::Window(nullptr, fakeSurface), fakeSurface};
126+
78 m_surfaces.prepend(surface);
127 m_surfaces.prepend(surface);
128+ m_windowToSurface.insert(window, surface);
129+ m_surfaceToWindow.insert(surface, window);
79
130
80 if (!surface->parentSurface()) {
131 if (!surface->parentSurface()) {
81 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
132 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
@@ -92,6 +143,8 @@ void SurfaceManager::registerSurface(MirSurface *surface)
92
143
93 const QString persistentId = surface->persistentId();
144 const QString persistentId = surface->persistentId();
94 connect(surface, &QObject::destroyed, this, [=]() {
145 connect(surface, &QObject::destroyed, this, [=]() {
146+ WindowWrapper key = m_surfaceToWindow.take(surface);
147+ m_windowToSurface.remove(key);
95 this->onSurfaceDestroyed(surface, persistentId);
148 this->onSurfaceDestroyed(surface, persistentId);
96 });
149 });
97
150
@@ -99,7 +152,12 @@ void SurfaceManager::registerSurface(MirSurface *surface)
99
152
100void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
153void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
101{
154{
155+ if (Q_UNLIKELY(!m_surfaceToWindow.contains(surface))) {
102− Q_EMIT surfaceCreated(surface);
156+ Q_EMIT surfaceCreated(surface);
157+ return;
158+ }
159+
160+ WindowManagementPolicy::instance()->addWindow(m_surfaceToWindow[surface].window);
103}
161}
104
162
105void SurfaceManager::setNewSurfaceMinimumWidth(int value)
163void SurfaceManager::setNewSurfaceMinimumWidth(int value)
tests/mocks/Unity/Application/SurfaceManager.h+14
@@ -20,12 +20,20 @@
20#include <QObject>
20#include <QObject>
21
21
22#include <unity/shell/application/SurfaceManagerInterface.h>
22#include <unity/shell/application/SurfaceManagerInterface.h>
23+#include <miral/window.h>
23
24
24#include "MirSurface.h"
25#include "MirSurface.h"
25#include "VirtualKeyboard.h"
26#include "VirtualKeyboard.h"
26
27
27class ApplicationInfo;
28class ApplicationInfo;
28
29
30+struct WindowWrapper {
31+ miral::Window window;
32+ std::shared_ptr<mir::scene::Surface> session{nullptr}; // Keeps the window surface alive.
33+ bool operator==(const WindowWrapper& other) const { return window==other.window; }
34+};
35+uint qHash(const WindowWrapper &key, uint seed = 0);
36+
29class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
37class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
30{
38{
31 Q_OBJECT
39 Q_OBJECT
@@ -42,6 +50,8 @@ public:
42
50
43 static SurfaceManager *instance();
51 static SurfaceManager *instance();
44
52
53+ unity::shell::application::MirSurfaceInterface *surfaceFor(const miral::Window& window) const override;
54+
45 // SurfaceManagerInterface
55 // SurfaceManagerInterface
46 void raise(unity::shell::application::MirSurfaceInterface *surface) override;
56 void raise(unity::shell::application::MirSurfaceInterface *surface) override;
47 void activate(unity::shell::application::MirSurfaceInterface *surface) override;
57 void activate(unity::shell::application::MirSurfaceInterface *surface) override;
@@ -95,6 +105,7 @@ private:
95 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
105 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
96 void focusFirstAvailableSurface();
106 void focusFirstAvailableSurface();
97 void registerSurface(MirSurface *surface);
107 void registerSurface(MirSurface *surface);
108+ QVector<unity::shell::application::MirSurfaceInterface*> find(const std::vector<miral::Window> &windows) const;
98
109
99 static SurfaceManager *m_instance;
110 static SurfaceManager *m_instance;
100
111
@@ -110,6 +121,9 @@ private:
110
121
111 QList<MirSurface*> m_surfaces;
122 QList<MirSurface*> m_surfaces;
112
123
124+ QHash<WindowWrapper, unity::shell::application::MirSurfaceInterface*> m_windowToSurface;
125+ QHash<unity::shell::application::MirSurfaceInterface*, WindowWrapper> m_surfaceToWindow;
126+
113 VirtualKeyboard *m_virtualKeyboard{nullptr};
127 VirtualKeyboard *m_virtualKeyboard{nullptr};
114};
128};
115
129
tests/mocks/Unity/Application/plugin.cpp+7−1
@@ -38,6 +38,12 @@ QObject* mirSingleton(QQmlEngine*, QJSEngine*)
38{
38{
39 return new MirMock;
39 return new MirMock;
40}
40}
41+
42+QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
43+{
44+ return new SurfaceManager;
45+}
46+
41} // anonymous namespace
47} // anonymous namespace
42
48
43void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
49void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -59,7 +65,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
59
65
60 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
66 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
61 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
67 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
62− qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
68+ qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
63}
69}
64
70
65void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
71void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Unity/CMakeLists.txt−1
@@ -6,7 +6,6 @@ add_subdirectory(InputInfo)
6add_subdirectory(Launcher)
6add_subdirectory(Launcher)
7add_subdirectory(Notifications)
7add_subdirectory(Notifications)
8add_subdirectory(DashCommunicator)
8add_subdirectory(DashCommunicator)
9−add_subdirectory(Screens)
10add_subdirectory(Platform)
9add_subdirectory(Platform)
11
10
12pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
11pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
tests/mocks/Unity/InputInfo/InputWindow.qmlnew file+68
@@ -0,0 +1,68 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.InputInfo 0.1
4+
5+Rectangle {
6+ id: rect
7+ color: "white"
8+ width: parent.width
9+ height: parent.height
10+
11+ Action {
12+ id: mouseAction
13+ text: checked ? "Remove Mouse" : "Add Mouse"
14+ onTriggered: {
15+ if (checked) {
16+ console.log("ADD")
17+ MockInputDeviceBackend.addMockDevice("/mouse0", InputInfo.Mouse);
18+ } else {
19+ console.log("REMOVE")
20+ MockInputDeviceBackend.removeDevice("/mouse0");
21+ }
22+ }
23+ iconName: "input-mouse-symbolic"
24+ checkable: true
25+ checked: false
26+ }
27+
28+ Action {
29+ id: kbAction
30+ text: checked ? "Remove Keyboard" : "Add Keyboard"
31+ onTriggered: {
32+ if (checked) {
33+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
34+ } else {
35+ MockInputDeviceBackend.removeDevice("/kbd0");
36+ }
37+ }
38+ iconName: "input-keyboard-symbolic"
39+ checkable: true
40+ checked: false
41+ }
42+
43+ Column {
44+ anchors {
45+ fill: parent
46+ margins: units.gu(1)
47+ }
48+ spacing: units.gu(1)
49+
50+ Button {
51+ anchors {
52+ left: parent.left
53+ right: parent.right
54+ }
55+ action: mouseAction
56+ color: mouseAction.checked ? UbuntuColors.red : UbuntuColors.green
57+ }
58+
59+ Button {
60+ anchors {
61+ left: parent.left
62+ right: parent.right
63+ }
64+ action: kbAction
65+ color: kbAction.checked ? UbuntuColors.red : UbuntuColors.green
66+ }
67+ }
68+}
tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2
@@ -18,10 +18,32 @@
18
18
19#include "qinputdeviceinfo_mock_p.h"
19#include "qinputdeviceinfo_mock_p.h"
20
20
21+#include <QQuickView>
22+#include <QQmlComponent>
23+#include <QQuickItem>
24+#include <paths.h>
25+
21−MockController::MockController(QObject *parent):
26+MockController::MockController(QQmlEngine *engine)
22− QObject(parent)
23{
27{
28+ QQmlComponent component(engine);
29+ component.setData("import QtQuick 2.4; import Unity.InputInfo 0.1; InputWindow { }", ::qmlDirectory());
30+ QObject *myObject = component.create();
31+ QQuickItem *item = qobject_cast<QQuickItem*>(myObject);
32+ if (item) {
33+ auto window = new QQuickView();
34+ window->setTitle("Input");
35+ item->setParentItem(window->contentItem());
36+ window->setResizeMode(QQuickView::SizeRootObjectToView);
37+ window->setWidth(200);
38+ window->setHeight(100);
39+ window->show();
40+ }
41+}
24
42
43+MockController *MockController::instance(QQmlEngine *engine)
44+{
45+ static MockController* controller = new MockController(engine);
46+ return controller;
25}
47}
26
48
27QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
49QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
tests/mocks/Unity/InputInfo/mockcontroller.h+5−1
@@ -20,13 +20,17 @@
20#include <QObject>
20#include <QObject>
21#include "qinputinfo.h"
21#include "qinputinfo.h"
22
22
23+class QQmlEngine;
24+
23class MockController: public QObject
25class MockController: public QObject
24{
26{
25 Q_OBJECT
27 Q_OBJECT
26public:
28public:
27− MockController(QObject *parent = 0);
29+ MockController(QQmlEngine *engine);
28 ~MockController() = default;
30 ~MockController() = default;
29
31
32+ static MockController *instance(QQmlEngine *engine);
33+
30 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
34 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
31 Q_INVOKABLE void removeDevice(const QString &devicePath);
35 Q_INVOKABLE void removeDevice(const QString &devicePath);
32};
36};
tests/mocks/Unity/InputInfo/plugin.cpp+1−2
@@ -26,9 +26,8 @@
26
26
27static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
27static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
28{
28{
29− Q_UNUSED(engine)
30 Q_UNUSED(scriptEngine)
29 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
30+ return MockController::instance(engine);
32}
31}
33
32
34void InputInfoPlugin::registerTypes(const char *uri)
33void InputInfoPlugin::registerTypes(const char *uri)
tests/mocks/Unity/InputInfo/qmldir+2
@@ -1,2 +1,4 @@
1module Unity.InputInfo
1module Unity.InputInfo
2plugin MockInputInfo
2plugin MockInputInfo
3+
4+InputWindow 0.1 InputWindow.qml
tests/mocks/Unity/Screens/CMakeLists.txtdeleted−14
@@ -1,14 +0,0 @@
1−include_directories(
2− ${CMAKE_CURRENT_SOURCE_DIR}
3−)
4−
5−set(MockScreens_SOURCES
6− plugin.cpp
7− screens.cpp
8−)
9−
10−add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
11−
12−qt5_use_modules(MockScreensPlugin Gui Qml)
13−
14−add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
tests/mocks/Unity/Screens/plugin.cppdeleted−29
@@ -1,29 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "plugin.h"
18−#include "screens.h"
19−
20−#include <QScreen>
21−
22−void UnityScreensPlugin::registerTypes(const char* uri)
23−{
24− Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
25−
26− qRegisterMetaType<QScreen*>("QScreen*");
27−
28− qmlRegisterType<Screens>(uri, 0, 1, "Screens");
29−}
tests/mocks/Unity/Screens/qmldirdeleted−2
@@ -1,2 +0,0 @@
1−module Unity.Screens
2−plugin MockScreensPlugin
tests/mocks/Unity/Screens/screens.cppdeleted−71
@@ -1,71 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "screens.h"
18−
19−// Qt
20−#include <QGuiApplication>
21−#include <QScreen>
22−#include <QDebug>
23−
24−Q_DECLARE_METATYPE(QScreen*)
25−
26−Screens::Screens(QObject *parent) :
27− QAbstractListModel(parent)
28−{
29− // start with one screen attached
30− m_screenList.append(new Screen());
31−}
32−
33−Screens::~Screens() noexcept
34−{
35− qDeleteAll(m_screenList);
36− m_screenList.clear();
37−}
38−
39−QHash<int, QByteArray> Screens::roleNames() const
40−{
41− QHash<int, QByteArray> roles;
42− roles[ScreenRole] = "screen";
43− roles[OutputTypeRole] = "outputType";
44− return roles;
45−}
46−
47−QVariant Screens::data(const QModelIndex &index, int role) const
48−{
49− if (!index.isValid() || index.row() >= m_screenList.size()) {
50− return QVariant();
51− }
52−
53− switch(role) {
54− case ScreenRole:
55− return QVariant::fromValue(m_screenList.at(index.row())->qScreen);
56− case OutputTypeRole:
57− return m_screenList.at(index.row())->outputTypes;
58− }
59−
60− return QVariant();
61−}
62−
63−int Screens::rowCount(const QModelIndex &) const
64−{
65− return count();
66−}
67−
68−int Screens::count() const
69−{
70− return m_screenList.size();
71−}
tests/mocks/WindowManager/CMakeLists.txtnew file+49
@@ -0,0 +1,49 @@
1+include_directories(
2+ SYSTEM
3+ ${QTMIRSERVER_INCLUDE_DIRS}
4+)
5+
6+include_directories(
7+ ${CMAKE_CURRENT_SOURCE_DIR}
8+ ${CMAKE_CURRENT_BINARY_DIR}
9+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager
10+ ${libunity8-private_SOURCE_DIR}
11+)
12+
13+add_library(mockwindowmanagmentpolicy SHARED
14+ WindowManagementPolicy.cpp
15+)
16+target_link_libraries(mockwindowmanagmentpolicy
17+ ${MIRAL_LDFLAGS}
18+ unity8-private
19+)
20+qt5_use_modules(mockwindowmanagmentpolicy Core)
21+
22+set(WINDOWMANAGER_SRC
23+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Screen.cpp
24+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Screens.cpp
25+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/TopLevelWindowModel.cpp
26+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Window.cpp
27+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceManager.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceModel.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Workspace.cpp
30+ MockScreens.cpp
31+ ScreenWindow.cpp
32+ WindowManagerPlugin.cpp
33+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
34+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
35+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
36+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
37+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
38+)
39+
40+add_library(MockWindowManager-qml SHARED ${WINDOWMANAGER_SRC})
41+
42+target_link_libraries(MockWindowManager-qml
43+ ${QTMIRSERVER_LDFLAGS}
44+ mockwindowmanagmentpolicy
45+)
46+
47+qt5_use_modules(MockWindowManager-qml Qml Quick Gui)
48+
49+add_unity8_mock(WindowManager 1.0 WindowManager TARGETS MockWindowManager-qml)
tests/mocks/WindowManager/MockScreens.cppnew file+140
@@ -0,0 +1,140 @@
1+/*
2+ * Copyright (C) 2016-2017 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 "MockScreens.h"
18+
19+// qtmirserver
20+#include <qtmir/screen.h>
21+
22+namespace {
23+
24+class MockScreen : public qtmir::Screen
25+{
26+ Q_OBJECT
27+public:
28+ MockScreen()
29+ {
30+ m_sizes.append(new qtmir::ScreenMode(50, QSize(640,480)));
31+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1280,1024)));
32+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1440,900)));
33+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1920,1080)));
34+ m_physicalSize = QSize(800,568);
35+ }
36+
37+ qtmir::OutputId outputId() const override { return m_id; }
38+ bool used() const override { return m_used; }
39+ QString name() const override { return m_name; }
40+ float scale() const override { return m_scale; }
41+ QSizeF physicalSize() const override { return m_physicalSize; }
42+ qtmir::FormFactor formFactor() const override { return m_formFactor; }
43+ qtmir::OutputTypes outputType() const override { return m_outputType; }
44+ MirPowerMode powerMode() const override { return m_powerMode; }
45+ Qt::ScreenOrientation orientation() const override { return m_orientation; }
46+ QPoint position() const override { return m_position; }
47+ uint currentModeIndex() const override { return m_currentModeIndex; }
48+ bool isActive() const override { return m_active; }
49+
50+ QQmlListProperty<qtmir::ScreenMode> availableModes() override {
51+ return QQmlListProperty<qtmir::ScreenMode>(this, m_sizes);
52+ }
53+
54+ void setActive(bool active) override {
55+ if (m_active != active) {
56+ m_active = active;
57+ Q_EMIT activeChanged(m_active);
58+ }
59+ }
60+
61+ QScreen* qscreen() const override { return nullptr; }
62+
63+ qtmir::ScreenConfiguration *beginConfiguration() const override {
64+ auto config = new qtmir::ScreenConfiguration;
65+ config->valid = true;
66+ config->id = m_id;
67+ config->used = m_used;
68+ config->topLeft = m_position;
69+ config->currentModeIndex = m_currentModeIndex;
70+ config->powerMode = m_powerMode;
71+ config->scale = m_scale;
72+ config->formFactor = m_formFactor;
73+ return config;
74+ }
75+
76+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration) override {
77+ m_used = configuration->used;
78+ m_position = configuration->topLeft;
79+ m_currentModeIndex = configuration->currentModeIndex;
80+ m_powerMode = configuration->powerMode;
81+ m_scale = configuration->scale;
82+ m_formFactor = configuration->formFactor;
83+ return true;
84+ }
85+
86+public:
87+ qtmir::OutputId m_id{0};
88+ bool m_active{false};
89+ bool m_used{true};
90+ QString m_name;
91+ qtmir::OutputTypes m_outputType{qtmir::Unknown};
92+ MirPowerMode m_powerMode{mir_power_mode_on};
93+ Qt::ScreenOrientation m_orientation{Qt::PrimaryOrientation};
94+ float m_scale{1.0};
95+ qtmir::FormFactor m_formFactor{qtmir::FormFactorMonitor};
96+ QPoint m_position;
97+ uint m_currentModeIndex{0};
98+ QList<qtmir::ScreenMode*> m_sizes;
99+ QSizeF m_physicalSize;
100+};
101+}
102+
103+MockScreens::MockScreens()
104+{
105+ bool ok = false;
106+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
107+ if (!ok) screenCount = 1;
108+ QPoint lastPoint(0,0);
109+ for (int i = 0; i < screenCount; ++i) {
110+ auto screen = new MockScreen();
111+ screen->m_id = qtmir::OutputId{i};
112+ screen->m_active = i == 0;
113+ screen->m_name = QString("Monitor %1").arg(i);
114+ screen->m_position = QPoint(lastPoint.x(), lastPoint.y());
115+ screen->m_currentModeIndex = 3;
116+ m_mocks.append(screen);
117+
118+ lastPoint.rx() += screen->m_sizes[screen->m_currentModeIndex]->size.width();
119+ }
120+}
121+
122+MockScreens::~MockScreens()
123+{
124+ qDeleteAll(m_mocks);
125+}
126+
127+QVector<qtmir::Screen *> MockScreens::screens() const
128+{
129+ return m_mocks;
130+}
131+
132+qtmir::Screen *MockScreens::activeScreen() const
133+{
134+ Q_FOREACH(auto screen, m_mocks) {
135+ if (screen->isActive()) return screen;
136+ }
137+ return nullptr;
138+}
139+
140+#include "MockScreens.moc"
tests/mocks/Unity/Screens/plugin.h → tests/mocks/WindowManager/MockScreens.hrenamed+27−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it under
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
5 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,12 +14,33 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#include <QQmlExtensionPlugin>
17+#ifndef MOCK_SCREENS_H
18−#include <QtQml/qqml.h>
18+#define MOCK_SCREENS_H
19
19
20−class UnityScreensPlugin : public QQmlExtensionPlugin {
20+#include <QAbstractListModel>
21+#include <QSharedPointer>
22+
23+#include <qtmir/screens.h>
24+
25+class Screen;
26+namespace qtmir
27+{
28+class Screens;
29+}
30+
31+class MockScreens : public qtmir::Screens
32+{
21 Q_OBJECT
33 Q_OBJECT
22− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0")
23public:
34public:
24− void registerTypes(const char* uri) override;
35+ MockScreens();
36+ ~MockScreens();
37+
38+ QVector<qtmir::Screen*> screens() const override;
39+
40+ qtmir::Screen *activeScreen() const override;
41+
42+private:
43+ QVector<qtmir::Screen*> m_mocks;
25};
44};
45+
46+#endif // MOCK_SCREENS_H
tests/mocks/WindowManager/ScreenWindow.cppnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2016-2017 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 "ScreenWindow.h"
18+#include "Screen.h"
19+
20+// Qt
21+#include <QGuiApplication>
22+#include <QDebug>
23+
24+ScreenWindow::ScreenWindow(QQuickWindow *parent)
25+ : QQuickWindow(parent)
26+{
27+ if (qGuiApp->platformName() != QLatin1String("mirserver")) {
28+ qCritical("Not using 'mirserver' QPA plugin. Using ScreenWindow may produce unknown results.");
29+ }
30+}
31+
32+ScreenWindow::~ScreenWindow()
33+{
34+}
35+
36+Screen *ScreenWindow::screenWrapper() const
37+{
38+ return m_screen.data();
39+}
40+
41+void ScreenWindow::setScreenWrapper(Screen *screen)
42+{
43+ if (m_screen != screen) {
44+ m_screen = screen;
45+ Q_EMIT screenWrapperChanged();
46+ }
47+ QQuickWindow::setScreen(screen->qscreen());
48+}
tests/mocks/WindowManager/ScreenWindow.hnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2016-2017 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 UNITY_SCREENWINDOW_H
18+#define UNITY_SCREENWINDOW_H
19+
20+#include <QQuickWindow>
21+#include <QPointer>
22+
23+#include "Screen.h"
24+
25+class ScreenAdapter;
26+
27+/*
28+ * ScreenWindow - wrapper of QQuickWindow to enable QML to specify destination screen.
29+**/
30+class ScreenWindow : public QQuickWindow
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(Screen *screen READ screenWrapper WRITE setScreenWrapper NOTIFY screenWrapperChanged)
34+ Q_PROPERTY(int winId READ winId CONSTANT)
35+public:
36+ explicit ScreenWindow(QQuickWindow *parent = 0);
37+ ~ScreenWindow();
38+
39+ Screen *screenWrapper() const;
40+ void setScreenWrapper(Screen *screen);
41+
42+Q_SIGNALS:
43+ void screenWrapperChanged();
44+
45+private:
46+ QPointer<Screen> m_screen;
47+};
48+
49+#endif // UNITY_SCREENWINDOW_H
tests/mocks/WindowManager/WindowManagementPolicy.cppnew file+101
@@ -0,0 +1,101 @@
1+/*
2+ * Copyright (C) 2017 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 "WindowManagementPolicy.h"
18+
19+#include <QDebug>
20+
21+WindowManagementPolicy::WindowManagementPolicy()
22+ : m_dummyWorkspace(std::shared_ptr<miral::Workspace>())
23+{
24+ wmPolicyInterface = this;
25+ m_activeWorkspace = m_dummyWorkspace;
26+}
27+
28+WindowManagementPolicy *WindowManagementPolicy::instance()
29+{
30+ static WindowManagementPolicy* wmPolicy(new WindowManagementPolicy);
31+ return wmPolicy;
32+}
33+
34+std::shared_ptr<miral::Workspace> WindowManagementPolicy::createWorkspace()
35+{
36+ auto workspace = std::make_shared<miral::Workspace>();
37+ m_workspaces.insert(workspace);
38+
39+ if (m_activeWorkspace.lock() == m_dummyWorkspace) {
40+ moveWorkspaceContentToWorkspace(workspace, m_dummyWorkspace);
41+ m_activeWorkspace = workspace;
42+ }
43+ return workspace;
44+}
45+
46+void WindowManagementPolicy::releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
47+{
48+ auto iter = m_workspaces.find(workspace);
49+ if (iter != m_workspaces.end()) m_workspaces.erase(iter);
50+
51+ if (m_workspaces.size() == 0) {
52+ m_activeWorkspace = m_dummyWorkspace;
53+ moveWorkspaceContentToWorkspace(m_dummyWorkspace, workspace);
54+ }
55+}
56+
57+void WindowManagementPolicy::forEachWindowInWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::function<void (const miral::Window &)> &callback)
58+{
59+ QMultiMap<miral::Workspace*, miral::Window>::iterator i = m_windows.find(workspace.get());
60+ while (i != m_windows.end() && i.key() == workspace.get()) {
61+ callback(i.value());
62+ ++i;
63+ }
64+}
65+
66+void WindowManagementPolicy::moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &to, const std::shared_ptr<miral::Workspace> &from)
67+{
68+ std::vector<miral::Window> windows;
69+
70+ QMultiMap<miral::Workspace*, miral::Window>::iterator i = m_windows.find(from.get());
71+ while (i != m_windows.end() && i.key() == from.get()) {
72+ windows.push_back(i.value());
73+ ++i;
74+ }
75+ Q_EMIT windowsAboutToBeRemovedFromWorkspace(from, windows);
76+ m_windows.remove(from.get());
77+
78+ Q_FOREACH(miral::Window window, windows) {
79+ m_windows.insert(to.get(), window);
80+ }
81+ Q_EMIT windowsAddedToWorkspace(to, windows);
82+}
83+
84+void WindowManagementPolicy::addWindow(const miral::Window &window)
85+{
86+ Q_EMIT windowAdded(window);
87+
88+ auto activeWorkspace = m_activeWorkspace.lock();
89+ if (activeWorkspace) {
90+ m_windows.insert(activeWorkspace.get(), window);
91+
92+ Q_EMIT windowsAddedToWorkspace(activeWorkspace, {window});
93+ }
94+}
95+
96+void WindowManagementPolicy::setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
97+{
98+ if (m_activeWorkspace.lock() == workspace)
99+ return;
100+ m_activeWorkspace = workspace ? workspace : m_dummyWorkspace;
101+}
tests/mocks/WindowManager/WindowManagementPolicy.hnew file+72
@@ -0,0 +1,72 @@
1+/*
2+ * Copyright (C) 2017 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 MOCKWINDOMANAGEMENTPOLICY_H
18+#define MOCKWINDOMANAGEMENTPOLICY_H
19+
20+#include "wmpolicyinterface.h"
21+
22+#include <QObject>
23+#include <QMultiMap>
24+#include <QVector>
25+
26+#include <miral/window.h>
27+#include <memory>
28+#include <unordered_set>
29+
30+namespace miral {
31+class Workspace {};
32+}
33+
34+// A Fake window management policy for the mock.
35+class Q_DECL_EXPORT WindowManagementPolicy : public QObject,
36+ public WMPolicyInterface
37+{
38+ Q_OBJECT
39+public:
40+ WindowManagementPolicy();
41+
42+ // for use in mocks
43+ static WindowManagementPolicy *instance();
44+
45+ // From WMPolicyInterface
46+ std::shared_ptr<miral::Workspace> createWorkspace() override;
47+
48+ void releaseWorkspace(const std::shared_ptr<miral::Workspace>& workspace) override;
49+
50+ void forEachWindowInWorkspace(std::shared_ptr<miral::Workspace> const& workspace,
51+ std::function<void(miral::Window const&)> const& callback) override;
52+
53+ void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace>& to,
54+ const std::shared_ptr<miral::Workspace>& from) override;
55+
56+ void setActiveWorkspace(const std::shared_ptr<miral::Workspace>& workspace) override;
57+
58+ void addWindow(const miral::Window& window);
59+
60+Q_SIGNALS:
61+ void windowAdded(const miral::Window& window);
62+ void windowsAddedToWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows);
63+ void windowsAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows);
64+
65+private:
66+ std::weak_ptr<miral::Workspace> m_activeWorkspace;
67+ std::shared_ptr<miral::Workspace> m_dummyWorkspace;
68+ std::unordered_set<std::shared_ptr<miral::Workspace>> m_workspaces;
69+
70+ QMultiMap<miral::Workspace*, miral::Window> m_windows;
71+};
72+#endif // UNITY_WINDOWMANAGEMENTPOLICY_H
tests/mocks/WindowManager/WindowManagerPlugin.cppnew file+71
@@ -0,0 +1,71 @@
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 "WindowManagerPlugin.h"
18+
19+#include "Screens.h"
20+#include "Screen.h"
21+#include "MockScreens.h"
22+#include "ScreenWindow.h"
23+#include "TopLevelWindowModel.h"
24+#include "Window.h"
25+#include "WorkspaceManager.h"
26+#include "Workspace.h"
27+#include "WorkspaceModel.h"
28+#include "WindowManagementPolicy.h"
29+
30+#include <QtQml>
31+
32+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
33+{
34+ Q_UNUSED(engine)
35+ Q_UNUSED(scriptEngine)
36+ return WorkspaceManager::instance();
37+}
38+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
39+ Q_UNUSED(engine);
40+ Q_UNUSED(scriptEngine);
41+ QSharedPointer<qtmir::Screens> mockScreens(new MockScreens());
42+ return new Screens(mockScreens);
43+}
44+
45+void WindowManagerPlugin::registerTypes(const char *uri)
46+{
47+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
48+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
49+ qmlRegisterUncreatableType<WorkspaceModel>(uri, 1, 0, "WorkspaceModel", "Not a creatable type");
50+ qmlRegisterSingletonType<Screens>(uri, 1, 0, "Screens", screensSingleton);
51+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", "ScreenMode is not creatable.");
52+ qmlRegisterUncreatableType<Workspace>(uri, 1, 0, "Workspace", "Workspace is not creatable.");
53+
54+ qRegisterMetaType<Screen*>("Screen*");
55+ qRegisterMetaType<ScreensProxy*>("ScreensProxy*");
56+ qRegisterMetaType<Workspace*>("Workspace*");
57+
58+ qRegisterMetaType<Window*>("Window*");
59+ qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
60+
61+ qmlRegisterType<ScreenWindow>(uri, 1, 0, "ScreenWindow");
62+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
63+}
64+
65+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
66+{
67+ QQmlExtensionPlugin::initializeEngine(engine, uri);
68+
69+ // Make sure we've initialized the wm policy.
70+ WindowManagementPolicy::instance();
71+}
tests/mocks/WindowManager/WindowManagerPlugin.hnew file+33
@@ -0,0 +1,33 @@
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 WINDOWMANAGER_PLUGIN_H
18+#define WINDOWMANAGER_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class WindowManagerPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri) override;
30+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
31+};
32+
33+#endif // WINDOWMANAGER_PLUGIN_H
tests/mocks/WindowManager/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module WindowManager
2+plugin MockWindowManager-qml
tests/qmltests/ApplicationMenuDataLoader.qml+1−4
@@ -6,11 +6,8 @@ import Unity.Indicators 0.1 as Indicators
6
6
7Object {
7Object {
8
8
9− property alias surfaceManager: sMgrHandler.target
10−
11 Connections {
9 Connections {
12− id: sMgrHandler
13− target: null
10+ target: SurfaceManager
14 onSurfaceCreated: {
11 onSurfaceCreated: {
15 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
12 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
16
13
tests/qmltests/ApplicationMenus/tst_MenuBar.qml−2
@@ -42,10 +42,8 @@ Item {
42 value: false
42 value: false
43 }
43 }
44
44
45− SurfaceManager { id: sMgr }
46 ApplicationMenuDataLoader {
45 ApplicationMenuDataLoader {
47 id: appMenuData
46 id: appMenuData
48− surfaceManager: sMgr
49 }
47 }
50
48
51 Rectangle {
49 Rectangle {
tests/qmltests/CMakeLists.txt+1
@@ -5,6 +5,7 @@ add_unity8_qmltest(. OrientedShell)
5add_unity8_qmltest(. DisabledScreenNotice)
5add_unity8_qmltest(. DisabledScreenNotice)
6add_unity8_qmltest(. Shell)
6add_unity8_qmltest(. Shell)
7add_unity8_qmltest(. ShellWithPin)
7add_unity8_qmltest(. ShellWithPin)
8+add_unity8_qmltest(. ShellApplication)
8add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest_data(. EdgeBarrierControls)
10add_unity8_qmltest_data(. EdgeBarrierControls)
10add_unity8_qmltest_data(. ApplicationMenuDataLoader)
11add_unity8_qmltest_data(. ApplicationMenuDataLoader)
tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2
@@ -39,11 +39,11 @@ Rectangle {
39
39
40 SignalSpy {
40 SignalSpy {
41 id: mouseEventSpy1
41 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
43 }
43 }
44 SignalSpy {
44 SignalSpy {
45 id: mouseEventSpy2
45 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
47 }
47 }
48
48
49 UnityTestCase {
49 UnityTestCase {
tests/qmltests/Panel/tst_ActiveCallHint.qml−2
@@ -41,8 +41,6 @@ Item {
41 elapsedTimerRunning: true
41 elapsedTimerRunning: true
42 }
42 }
43
43
44− SurfaceManager {}
45−
46 ActiveCallHint {
44 ActiveCallHint {
47 id: callHint
45 id: callHint
48 anchors {
46 anchors {
tests/qmltests/Panel/tst_Panel.qml+14−14
@@ -42,10 +42,10 @@ PanelTest {
42 value: keyboardAttached.checked
42 value: keyboardAttached.checked
43 }
43 }
44
44
45− SurfaceManager { id: sMgr }
45+ readonly property alias panelState: panel.panelState
46+
46 ApplicationMenuDataLoader {
47 ApplicationMenuDataLoader {
47 id: appMenuData
48 id: appMenuData
48− surfaceManager: sMgr
49 }
49 }
50
50
51 Component.onCompleted: {
51 Component.onCompleted: {
@@ -148,7 +148,7 @@ PanelTest {
148 Layout.fillWidth: true
148 Layout.fillWidth: true
149 CheckBox {
149 CheckBox {
150 id: windowControlsCB
150 id: windowControlsCB
151− onClicked: PanelState.decorationsVisible = checked
151+ onClicked: panelState.decorationsVisible = checked
152 }
152 }
153 Label {
153 Label {
154 text: "Show window decorations"
154 text: "Show window decorations"
@@ -159,7 +159,7 @@ PanelTest {
159 RowLayout {
159 RowLayout {
160 Layout.fillWidth: true
160 Layout.fillWidth: true
161 CheckBox {
161 CheckBox {
162− onClicked: PanelState.title = checked ? "Fake window title" : ""
162+ onClicked: panelState.title = checked ? "Fake window title" : ""
163 }
163 }
164 Label {
164 Label {
165 text: "Show fake window title"
165 text: "Show fake window title"
@@ -245,7 +245,7 @@ PanelTest {
245
245
246 SignalSpy {
246 SignalSpy {
247 id: windowControlButtonsSpy
247 id: windowControlButtonsSpy
248− target: PanelState
248+ target: panelState
249 signalName: "closeClicked"
249 signalName: "closeClicked"
250 }
250 }
251
251
@@ -255,8 +255,8 @@ PanelTest {
255 panel.fullscreenMode = false;
255 panel.fullscreenMode = false;
256 callManager.foregroundCall = null;
256 callManager.foregroundCall = null;
257
257
258− PanelState.title = "";
258+ panelState.title = "";
259− PanelState.decorationsVisible = false;
259+ panelState.decorationsVisible = false;
260
260
261 // Wait for the indicators to get into position.
261 // Wait for the indicators to get into position.
262 // (switches between normal and fullscreen modes are animated)
262 // (switches between normal and fullscreen modes are animated)
@@ -447,7 +447,7 @@ PanelTest {
447 }
447 }
448
448
449 function test_hint(data) {
449 function test_hint(data) {
450− PanelState.title = "Fake Title"
450+ panelState.title = "Fake Title"
451 panel.fullscreenMode = data.fullscreen;
451 panel.fullscreenMode = data.fullscreen;
452 callManager.foregroundCall = data.call;
452 callManager.foregroundCall = data.call;
453
453
@@ -489,7 +489,7 @@ PanelTest {
489 // menus, first by running the hint animation, then after dragging down will
489 // menus, first by running the hint animation, then after dragging down will
490 // expose more of the panel. Releasing the touch will complete the show.
490 // expose more of the panel. Releasing the touch will complete the show.
491 function test_drag_applicationMenu_down_shows_menu(data) {
491 function test_drag_applicationMenu_down_shows_menu(data) {
492− PanelState.title = "Fake Title";
492+ panelState.title = "Fake Title";
493 panel.fullscreenMode = data.fullscreen;
493 panel.fullscreenMode = data.fullscreen;
494 callManager.foregroundCall = data.call;
494 callManager.foregroundCall = data.call;
495
495
@@ -580,7 +580,7 @@ PanelTest {
580 }
580 }
581
581
582 function test_darkenedAreaEatsAllApplicationMenuEvents() {
582 function test_darkenedAreaEatsAllApplicationMenuEvents() {
583− PanelState.title = "Fake Title"
583+ panelState.title = "Fake Title"
584
584
585 // The center of the area not covered by the indicators menu
585 // The center of the area not covered by the indicators menu
586 // Ie, the visible darkened area behind the menu
586 // Ie, the visible darkened area behind the menu
@@ -701,7 +701,7 @@ PanelTest {
701 var windowControlArea = findChild(panel, "windowControlArea");
701 var windowControlArea = findChild(panel, "windowControlArea");
702 verify(windowControlArea, "Window control area should have been created in windowed mode")
702 verify(windowControlArea, "Window control area should have been created in windowed mode")
703
703
704− PanelState.decorationsVisible = true;
704+ panelState.decorationsVisible = true;
705 // click in very topleft corner and verify the close button got clicked too
705 // click in very topleft corner and verify the close button got clicked too
706 mouseMove(panel, 0, 0);
706 mouseMove(panel, 0, 0);
707 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
707 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
@@ -762,7 +762,7 @@ PanelTest {
762 }
762 }
763
763
764 function test_stagedApplicationMenuBarShowOnMouseHover() {
764 function test_stagedApplicationMenuBarShowOnMouseHover() {
765− PanelState.title = "Fake Title";
765+ panelState.title = "Fake Title";
766 panel.mode = "staged";
766 panel.mode = "staged";
767 mouseEmulation.checked = false;
767 mouseEmulation.checked = false;
768
768
@@ -780,7 +780,7 @@ PanelTest {
780 }
780 }
781
781
782 function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
782 function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
783− PanelState.title = "Fake Title";
783+ panelState.title = "Fake Title";
784 panel.mode = "windowed";
784 panel.mode = "windowed";
785 mouseEmulation.checked = false;
785 mouseEmulation.checked = false;
786
786
@@ -796,7 +796,7 @@ PanelTest {
796 tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
796 tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
797 tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
797 tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
798
798
799− PanelState.decorationsVisible = true;
799+ panelState.decorationsVisible = true;
800
800
801 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
801 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
802 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
802 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
tests/qmltests/Stage/tst_ApplicationWindow.qml−2
@@ -37,8 +37,6 @@ Rectangle {
37 }
37 }
38 property QtObject fakeApplication: null
38 property QtObject fakeApplication: null
39
39
40− SurfaceManager{}
41−
42 Loader {
40 Loader {
43 id: applicationWindowLoader
41 id: applicationWindowLoader
44 focus: true
42 focus: true
tests/qmltests/Stage/tst_DecoratedWindow.qml+5−3
@@ -46,10 +46,12 @@ Rectangle {
46 value: false
46 value: false
47 }
47 }
48
48
49− SurfaceManager { id: sMgr }
50 ApplicationMenuDataLoader {
49 ApplicationMenuDataLoader {
51 id: appMenuData
50 id: appMenuData
52− surfaceManager: sMgr
51+ }
52+
53+ PanelState {
54+ id: panelState
53 }
55 }
54
56
55 Item {
57 Item {
@@ -99,7 +101,7 @@ Rectangle {
99 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
101 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
100
102
101 Binding {
103 Binding {
102− target: PanelState
104+ target: panelState
103 property: "focusedPersistentSurfaceId"
105 property: "focusedPersistentSurfaceId"
104 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
106 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105 }
107 }
tests/qmltests/Stage/tst_DesktopStage.qml+9−6
@@ -44,6 +44,10 @@ Item {
44 value: false
44 value: false
45 }
45 }
46
46
47+ PanelState {
48+ id: panelState
49+ }
50+
47 Component.onCompleted: {
51 Component.onCompleted: {
48 ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
52 ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
49 ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
53 ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
@@ -63,16 +67,14 @@ Item {
63 }
67 }
64 }
68 }
65
69
66− SurfaceManager { id: sMgr }
67 ApplicationMenuDataLoader {
70 ApplicationMenuDataLoader {
68 id: appMenuData
71 id: appMenuData
69− surfaceManager: sMgr
70 }
72 }
71
73
72 TopLevelWindowModel {
74 TopLevelWindowModel {
73 id: topSurfaceList
75 id: topSurfaceList
74 applicationManager: ApplicationManager
76 applicationManager: ApplicationManager
75− surfaceManager: sMgr
77+ surfaceManager: SurfaceManager
76 }
78 }
77
79
78 Loader {
80 Loader {
@@ -102,6 +104,7 @@ Item {
102 topLevelSurfaceList: topSurfaceList
104 topLevelSurfaceList: topSurfaceList
103 interactive: true
105 interactive: true
104 mode: "windowed"
106 mode: "windowed"
107+ panelState: panelState
105 }
108 }
106 }
109 }
107 }
110 }
@@ -631,19 +634,19 @@ Item {
631 maximizeDelegate(facebookAppDelegate);
634 maximizeDelegate(facebookAppDelegate);
632
635
633 // verify the drop shadow is still not visible
636 // verify the drop shadow is still not visible
634− verify(PanelState.dropShadow == false);
637+ verify(panelState.dropShadow == false);
635
638
636 // start a foreground app, not maximized
639 // start a foreground app, not maximized
637 var dialerAppDelegate = startApplication("dialer-app");
640 var dialerAppDelegate = startApplication("dialer-app");
638
641
639 // verify the drop shadow becomes visible
642 // verify the drop shadow becomes visible
640− verify(PanelState.dropShadow == true);
643+ verify(panelState.dropShadow == true);
641
644
642 // close the maximized app
645 // close the maximized app
643 ApplicationManager.stopApplication("facebook-webapp");
646 ApplicationManager.stopApplication("facebook-webapp");
644
647
645 // verify the drop shadow is gone
648 // verify the drop shadow is gone
646− tryCompare(PanelState, "dropShadow", false);
649+ tryCompare(panelState, "dropShadow", false);
647 }
650 }
648
651
649 function test_threeFingerTapShowsWindowControls_data() {
652 function test_threeFingerTapShowsWindowControls_data() {
tests/qmltests/Stage/tst_PhoneStage.qml+1−3
@@ -31,10 +31,8 @@ Item {
31
31
32 property var greeter: { fullyShown: true }
32 property var greeter: { fullyShown: true }
33
33
34− SurfaceManager { id: sMgr }
35 ApplicationMenuDataLoader {
34 ApplicationMenuDataLoader {
36 id: appMenuData
35 id: appMenuData
37− surfaceManager: sMgr
38 }
36 }
39
37
40 Stage {
38 Stage {
@@ -50,7 +48,7 @@ Item {
50 topLevelSurfaceList: TopLevelWindowModel {
48 topLevelSurfaceList: TopLevelWindowModel {
51 id: topLevelSurfaceList
49 id: topLevelSurfaceList
52 applicationManager: ApplicationManager
50 applicationManager: ApplicationManager
53− surfaceManager: sMgr
51+ surfaceManager: SurfaceManager
54 }
52 }
55 Component.onCompleted: {
53 Component.onCompleted: {
56 ApplicationManager.startApplication("unity8-dash");
54 ApplicationManager.startApplication("unity8-dash");
tests/qmltests/Stage/tst_SurfaceContainer.qml−2
@@ -51,8 +51,6 @@ Rectangle {
51 }
51 }
52 }
52 }
53
53
54− SurfaceManager {}
55−
56 Loader {
54 Loader {
57 id: surfaceContainerLoader
55 id: surfaceContainerLoader
58 focus: true
56 focus: true
tests/qmltests/Stage/tst_TabletStage.qml+1−3
@@ -35,10 +35,8 @@ Rectangle {
35
35
36 property var greeter: { fullyShown: true }
36 property var greeter: { fullyShown: true }
37
37
38− SurfaceManager { id: sMgr }
39 ApplicationMenuDataLoader {
38 ApplicationMenuDataLoader {
40 id: appMenuData
39 id: appMenuData
41− surfaceManager: sMgr
42 }
40 }
43
41
44 Stage {
42 Stage {
@@ -59,7 +57,7 @@ Rectangle {
59 topLevelSurfaceList: TopLevelWindowModel {
57 topLevelSurfaceList: TopLevelWindowModel {
60 id: topLevelSurfaceList
58 id: topLevelSurfaceList
61 applicationManager: ApplicationManager
59 applicationManager: ApplicationManager
62− surfaceManager: sMgr
60+ surfaceManager: SurfaceManager
63 }
61 }
64 Component.onCompleted: {
62 Component.onCompleted: {
65 ApplicationManager.startApplication("unity8-dash");
63 ApplicationManager.startApplication("unity8-dash");
tests/qmltests/Stage/tst_WindowDecoration.qml−2
@@ -47,10 +47,8 @@ Item {
47 property string name: "webbrowser"
47 property string name: "webbrowser"
48 }
48 }
49
49
50− SurfaceManager { id: sMgr }
51 ApplicationMenuDataLoader {
50 ApplicationMenuDataLoader {
52 id: appMenuData
51 id: appMenuData
53− surfaceManager: sMgr
54 }
52 }
55
53
56 UnityMenuModel {
54 UnityMenuModel {
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8
@@ -19,7 +19,6 @@ import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20import Unity.Test 0.1
20import Unity.Test 0.1
21import ".."
21import ".."
22−import "../../../qml/Components/PanelState"
23import "../../../qml/Stage"
22import "../../../qml/Stage"
24import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
25import Ubuntu.Components.ListItems 1.3 as ListItem
24import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -31,12 +30,6 @@ Item {
31 height: units.gu(60)
30 height: units.gu(60)
32 width: units.gu(85)
31 width: units.gu(85)
33
32
34− Binding {
35− target: PanelState
36− property: "panelHeight"
37− value: units.gu(3)
38− }
39−
40 Component {
33 Component {
41 id: fakeWindowComponent
34 id: fakeWindowComponent
42
35
@@ -362,7 +355,7 @@ Item {
362
355
363 // Make sure it's again where we left it in normal state before destroying
356 // Make sure it's again where we left it in normal state before destroying
364 compare(fakeWindow.requestedX >= 0, true)
357 compare(fakeWindow.requestedX >= 0, true)
365− compare(fakeWindow.requestedY >= PanelState.panelHeight, true)
358+ compare(fakeWindow.requestedY >= 0, true)
366 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
359 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
367 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
360 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
368
361
tests/qmltests/Tutorial/tst_Tutorial.qml+1−3
@@ -326,9 +326,7 @@ Rectangle {
326 }
326 }
327
327
328 function ensureInputMethodSurface() {
328 function ensureInputMethodSurface() {
329− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
330− verify(surfaceManager);
331− surfaceManager.createInputMethodSurface();
329+ SurfaceManager.createInputMethodSurface();
332
330
333 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
331 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
334 }
332 }
tests/qmltests/tst_OrientedShell.qml+19−5
@@ -30,7 +30,6 @@ import Utils 0.1
30
30
31import "../../qml"
31import "../../qml"
32import "../../qml/Components"
32import "../../qml/Components"
33−import "../../qml/Components/PanelState"
34import "Stage"
33import "Stage"
35
34
36Rectangle {
35Rectangle {
@@ -82,6 +81,12 @@ Rectangle {
82 deviceFilter: InputInfo.Keyboard
81 deviceFilter: InputInfo.Keyboard
83 }
82 }
84
83
84+ QtObject {
85+ id: _screenWindow
86+ property bool primary: true
87+ }
88+ property alias screenWindow: _screenWindow
89+
85 property int physicalOrientation0
90 property int physicalOrientation0
86 property int physicalOrientation90
91 property int physicalOrientation90
87 property int physicalOrientation180
92 property int physicalOrientation180
@@ -446,6 +451,15 @@ Rectangle {
446 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
451 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
447 property Item shell
452 property Item shell
448 property QtObject topLevelSurfaceList
453 property QtObject topLevelSurfaceList
454+ property var panelState: undefined
455+
456+ onOrientedShellChanged: {
457+ if (orientedShell) {
458+ panelState = findInvisibleChild(orientedShell, "panelState");
459+ } else {
460+ panelState = undefined;
461+ }
462+ }
449
463
450 SignalSpy { id: signalSpy }
464 SignalSpy { id: signalSpy }
451 SignalSpy { id: signalSpy2 }
465 SignalSpy { id: signalSpy2 }
@@ -1510,13 +1524,13 @@ Rectangle {
1510 print("exptectedAngle", expectedAngle, point.x, point.y)
1524 print("exptectedAngle", expectedAngle, point.x, point.y)
1511 switch (expectedAngle) {
1525 switch (expectedAngle) {
1512 case 0:
1526 case 0:
1513− return point.x === 0 && point.y === PanelState.panelHeight;
1527+ return point.x === 0 && point.y === panelState.panelHeight;
1514 case 90:
1528 case 90:
1515− return point.x === orientedShell.width - PanelState.panelHeight && point.y === 0;
1529+ return point.x === orientedShell.width - panelState.panelHeight && point.y === 0;
1516 case 180:
1530 case 180:
1517− return point.x === orientedShell.width && point.y === orientedShell.height - PanelState.panelHeight;
1531+ return point.x === orientedShell.width && point.y === orientedShell.height - panelState.panelHeight;
1518 default: // 270
1532 default: // 270
1519− return point.x === PanelState.panelHeight && point.y === orientedShell.height;
1533+ return point.x === panelState.panelHeight && point.y === orientedShell.height;
1520 }
1534 }
1521 }
1535 }
1522
1536
tests/qmltests/tst_Shell.qml+22−15
@@ -61,14 +61,21 @@ Rectangle {
61 onShellChanged: {
61 onShellChanged: {
62 if (shell) {
62 if (shell) {
63 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
63 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
64− appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
64+ panelState = testCase.findInvisibleChild(shell, "panelState");
65 } else {
65 } else {
66 topLevelSurfaceList = null;
66 topLevelSurfaceList = null;
67− appMenuData.surfaceManager = null;
67+ panelState = undefined;
68 }
68 }
69 }
69 }
70
70
71 property var topLevelSurfaceList: null
71 property var topLevelSurfaceList: null
72+ property var panelState: undefined
73+
74+ QtObject {
75+ id: _screenWindow
76+ property bool primary: true
77+ }
78+ property alias screenWindow: _screenWindow
72
79
73 Item {
80 Item {
74 id: shellContainer
81 id: shellContainer
@@ -261,6 +268,7 @@ Rectangle {
261 anchors { left: parent.left; right: parent.right }
268 anchors { left: parent.left; right: parent.right }
262 activeFocusOnPress: false
269 activeFocusOnPress: false
263 model: ["phone", "tablet", "desktop"]
270 model: ["phone", "tablet", "desktop"]
271+ selectedIndex: 2
264 onSelectedIndexChanged: {
272 onSelectedIndexChanged: {
265 shellLoader.state = model[selectedIndex];
273 shellLoader.state = model[selectedIndex];
266 }
274 }
@@ -274,6 +282,7 @@ Rectangle {
274 anchors { left: parent.left; right: parent.right }
282 anchors { left: parent.left; right: parent.right }
275 activeFocusOnPress: false
283 activeFocusOnPress: false
276 model: ["phone", "tablet", "desktop"]
284 model: ["phone", "tablet", "desktop"]
285+ selectedIndex: 2
277 }
286 }
278 MouseTouchEmulationCheckbox {
287 MouseTouchEmulationCheckbox {
279 id: mouseEmulation
288 id: mouseEmulation
@@ -680,9 +689,7 @@ Rectangle {
680 }
689 }
681
690
682 function ensureInputMethodSurface() {
691 function ensureInputMethodSurface() {
683− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
684− verify(surfaceManager);
685− surfaceManager.createInputMethodSurface();
692+ SurfaceManager.createInputMethodSurface();
686
693
687 tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
694 tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
688 }
695 }
@@ -1868,21 +1875,21 @@ Rectangle {
1868 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1875 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1869
1876
1870 tryCompare(appDelegate, "state", "normal");
1877 tryCompare(appDelegate, "state", "normal");
1871− tryCompare(PanelState, "decorationsVisible", false)
1878+ tryCompare(panelState, "decorationsVisible", false)
1872
1879
1873 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1880 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1874 tryCompare(appDelegate, "state", "maximized");
1881 tryCompare(appDelegate, "state", "maximized");
1875− tryCompare(PanelState, "decorationsVisible", true)
1882+ tryCompare(panelState, "decorationsVisible", true)
1876
1883
1877 ApplicationManager.stopApplication(application.appId);
1884 ApplicationManager.stopApplication(application.appId);
1878− tryCompare(PanelState, "decorationsVisible", false)
1885+ tryCompare(panelState, "decorationsVisible", false)
1879
1886
1880 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1887 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1881 // They won't be gone until those surface items are destroyed.
1888 // They won't be gone until those surface items are destroyed.
1882 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1889 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1883
1890
1884 ApplicationManager.startApplication(application.appId);
1891 ApplicationManager.startApplication(application.appId);
1885− tryCompare(PanelState, "decorationsVisible", true)
1892+ tryCompare(panelState, "decorationsVisible", true)
1886 }
1893 }
1887
1894
1888 function test_newAppHasValidGeometry() {
1895 function test_newAppHasValidGeometry() {
@@ -1939,7 +1946,7 @@ Rectangle {
1939 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1946 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1940 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1947 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1941
1948
1942− compare(appDelegate.y >= PanelState.panelHeight, true);
1949+ compare(appDelegate.y >= panelState.panelHeight, true);
1943 }
1950 }
1944
1951
1945 function test_cantResizeWindowUnderPanel() {
1952 function test_cantResizeWindowUnderPanel() {
@@ -1967,7 +1974,7 @@ Rectangle {
1967 mouseMove(decoration, decoration.width/2, -units.gu(100));
1974 mouseMove(decoration, decoration.width/2, -units.gu(100));
1968
1975
1969 // verify we don't go past the panel
1976 // verify we don't go past the panel
1970− compare(appDelegate.y >= PanelState.panelHeight, true);
1977+ compare(appDelegate.y >= panelState.panelHeight, true);
1971 }
1978 }
1972
1979
1973 function test_restoreWindowStateFixesIfUnderPanel() {
1980 function test_restoreWindowStateFixesIfUnderPanel() {
@@ -1991,7 +1998,7 @@ Rectangle {
1991 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
1998 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
1992
1999
1993 appDelegate = appRepeater.itemAt(0);
2000 appDelegate = appRepeater.itemAt(0);
1994− compare(appDelegate.y >= PanelState.panelHeight, true);
2001+ compare(appDelegate.y >= panelState.panelHeight, true);
1995 }
2002 }
1996
2003
1997 function test_lifecyclePolicyForNonTouchApp_data() {
2004 function test_lifecyclePolicyForNonTouchApp_data() {
@@ -2639,7 +2646,7 @@ Rectangle {
2639 function test_oskDisplacesWindow_data() {
2646 function test_oskDisplacesWindow_data() {
2640 return [
2647 return [
2641 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2648 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2642− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2649+ {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: panelState.panelHeight, oskEnabled: true},
2643 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2650 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2644 ]
2651 ]
2645 }
2652 }
@@ -2662,8 +2669,8 @@ Rectangle {
2662 dashAppDelegate.windowedY = data.windowY;
2669 dashAppDelegate.windowedY = data.windowY;
2663 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2670 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2664 var initialY = dashAppDelegate.y;
2671 var initialY = dashAppDelegate.y;
2665− print("intial", initialY, "panel", PanelState.panelHeight);
2672+ print("intial", initialY, "panel", panelState.panelHeight);
2666− verify(initialY > PanelState.panelHeight);
2673+ verify(initialY > panelState.panelHeight);
2667
2674
2668 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2675 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2669 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2676 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
tests/qmltests/tst_ShellApplication.qmlnew file+161
@@ -0,0 +1,161 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import LightDM.FullLightDM 0.1 as LightDM
20+import LightDMController 0.1
21+import Unity.Application 0.1
22+import Unity.Test 0.1
23+
24+import "../../qml"
25+import "Stage"
26+
27+Rectangle {
28+ id: root
29+ color: "grey"
30+ width: units.gu(100)
31+ height: units.gu(71)
32+
33+ QtObject {
34+ id: args
35+ property bool hasFullscreen: false
36+ property bool hasFrameless: false
37+ property bool hasGeometry: true
38+ property size windowGeometry: Qt.size(root.width, root.height)
39+ property string deviceName: "desktop"
40+ property string mode: "full-greeter"
41+ }
42+ property alias applicationArguments: args
43+
44+ Component.onCompleted: {
45+ // must set the mock mode before loading the Shell
46+ LightDMController.userMode = "single";
47+ }
48+
49+ Flickable {
50+ id: controls
51+ contentHeight: controlRect.height
52+
53+ anchors.top: root.top
54+ anchors.bottom: root.bottom
55+ anchors.right: root.right
56+ width: units.gu(30)
57+
58+ Rectangle {
59+ id: controlRect
60+ anchors { left: parent.left; right: parent.right }
61+ color: "darkgrey"
62+ height: childrenRect.height + units.gu(2)
63+
64+ Column {
65+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
66+ spacing: units.gu(1)
67+
68+ Flow {
69+ spacing: units.gu(1)
70+ anchors { left: parent.left; right: parent.right }
71+
72+ Button {
73+ text: "Show Greeter"
74+ activeFocusOnPress: false
75+ onClicked: {
76+ LightDM.Greeter.showGreeter();
77+ }
78+ }
79+ Button {
80+ text: "Hide Greeter"
81+ activeFocusOnPress: false
82+ onClicked: {
83+ LightDM.Greeter.hideGreeter()
84+ }
85+ }
86+ }
87+
88+ MouseTouchEmulationCheckbox {
89+ id: mouseEmulation
90+ checked: true
91+ }
92+
93+ Row {
94+ anchors { left: parent.left; right: parent.right }
95+ CheckBox {
96+ id: autohideLauncherCheckbox
97+ onCheckedChanged: {
98+ GSettingsController.setAutohideLauncher(checked)
99+ }
100+ }
101+ Label {
102+ text: "Autohide launcher"
103+ }
104+ }
105+
106+ Label { text: "Applications"; font.bold: true }
107+
108+ Button {
109+ text: "Start all apps"
110+ width: parent.width
111+ activeFocusOnPress: false
112+ onClicked: {
113+ for (var i = 0; i < ApplicationManager.availableApplications.length; i++) {
114+ var appId = ApplicationManager.availableApplications[i];
115+ ApplicationManager.startApplication(appId)
116+ }
117+ }
118+ }
119+
120+ Repeater {
121+ id: appCheckBoxRepeater
122+ model: ApplicationManager.availableApplications
123+ ApplicationCheckBox {
124+ appId: modelData
125+ }
126+ }
127+
128+ Label {
129+ text: "Fingerprint"
130+ }
131+ Row {
132+ Button {
133+ text: "Success"
134+ onClicked: {
135+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
136+ var uid = 0;
137+ for (var i = 0; i < LightDM.Users.count; i++) {
138+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == AccountsService.user) {
139+ uid = LightDM.Users.data(i, LightDM.UserRoles.UidRole);
140+ break;
141+ }
142+ }
143+ biometryd.operation.mockSuccess(uid);
144+ }
145+ }
146+
147+ Button {
148+ text: "Failure"
149+ onClicked: {
150+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
151+ biometryd.operation.mockFailure("error");
152+ }
153+ }
154+ }
155+ }
156+ }
157+ }
158+
159+ ShellApplication {
160+ }
161+}
tests/uqmlscene/main.cpp+2
@@ -479,6 +479,8 @@ int main(int argc, char ** argv)
479 // TODO: as soon as the engine construction completes, the debug service is
479 // TODO: as soon as the engine construction completes, the debug service is
480 // listening for connections. But actually we aren't ready to debug anything.
480 // listening for connections. But actually we aren't ready to debug anything.
481 QQmlEngine engine;
481 QQmlEngine engine;
482+ engine.rootContext()->setContextProperty("DebuggingController", new DebuggingController(&app));
483+
482 QQmlComponent *component = new QQmlComponent(&engine);
484 QQmlComponent *component = new QQmlComponent(&engine);
483 for (int i = 0; i < imports.size(); ++i)
485 for (int i = 0; i < imports.size(); ++i)
484 engine.addImportPath(imports.at(i));
486 engine.addImportPath(imports.at(i));