on d73aca0 help sign in
126 files changed+5816−1000
since splitsnapshots
unifiedsplit
CMakeLists.txt+7−3 debian/control+10−4 debian/unity8.install+4 plugins/Cursor/CMakeLists.txt+1 plugins/Cursor/InputDispatcherFilter.cpp+175 plugins/Cursor/InputDispatcherFilter.h+62 plugins/Cursor/MousePointer.cpp+63−78 plugins/Cursor/MousePointer.h+5−4 plugins/UInput/plugin.cpp+7−1 plugins/Unity/CMakeLists.txt+1 plugins/Unity/Debug/CMakeLists.txt+30 plugins/Unity/Debug/consolelog.cpp+221 plugins/Unity/Debug/consolelog.h+87 plugins/Unity/Debug/plugin.cpp+31 src/ShellView.h → plugins/Unity/Debug/plugin.h+9−11 plugins/Unity/Debug/qmldir+3 plugins/WindowManager/CMakeLists.txt+24−2 plugins/WindowManager/Screen.cpp+294 plugins/WindowManager/Screen.h+133 plugins/WindowManager/ScreenAttached.cpp+149 plugins/WindowManager/ScreenAttached.h+56 plugins/WindowManager/ScreenWindow.cpp+45 plugins/WindowManager/ScreenWindow.h+49 plugins/WindowManager/Screens.cpp+213 plugins/WindowManager/Screens.h+93 plugins/WindowManager/TopLevelWindowModel.cpp+193−40 plugins/WindowManager/TopLevelWindowModel.h+23 plugins/WindowManager/WindowManagerPlugin.cpp+36−1 plugins/WindowManager/Workspace.cpp+146 plugins/WindowManager/Workspace.h+100 plugins/WindowManager/WorkspaceManager.cpp+117 plugins/WindowManager/WorkspaceManager.h+58 plugins/WindowManager/WorkspaceModel.cpp+220 plugins/WindowManager/WorkspaceModel.h+91 qml/CMakeLists.txt+1−1 qml/Components/PanelState/PanelState.qml−1 qml/Components/PanelState/qmldir+1−1 qml/Components/VirtualTouchPad.qml+14−20 qml/DisabledScreenNotice.qml+2−1 qml/Greeter/Greeter.qml+21−3 qml/Greeter/SecondaryGreeter.qml+75 qml/OrientedShell.qml+7−10 qml/Panel/Panel.qml+14−14 qml/Shell.qml+50−12 qml/ShellApplication.qml+48 qml/ShellNotifier.qml+30 qml/ShellScreen.qml+63 qml/Stage/DecoratedWindow.qml+3−1 qml/Stage/FakeMaximizeDelegate.qml+12−11 qml/Stage/Spread/ScreensAndWorkspaces.qml+179 qml/Stage/Spread/Spread.qml+6−7 qml/Stage/Spread/SpreadDelegateInputArea.qml+26−11 qml/Stage/Spread/WorkspacePreview.qml+227 qml/Stage/Spread/Workspaces.qml+343 qml/Stage/Stage.qml+142−104 qml/Stage/SurfaceContainer.qml+2 qml/Stage/WorkspacePreview.qml+212 qml/Stage/graphics/multi-monitor_leave.png qml/qmldir+1 src/ApplicationArguments.cpp+11−2 src/ApplicationArguments.h+17−15 src/CMakeLists.txt+10−4 src/DebuggingController.cpp+12−1 src/DebuggingController.h+14 src/SecondaryWindow.cpp−40 src/ShellApplication.cpp−216 src/ShellView.cpp−63 src/UnityApplication.cpp+131 src/ShellApplication.h → src/UnityApplication.h+10−20 src/UnityCommandLineParser.cpp+8 src/UnityCommandLineParser.h+4−1 src/WindowManagementPolicy.cpp+78 src/WindowManagementPolicy.h+54 src/libunity8-private/CMakeLists.txt+1 src/SecondaryWindow.h → src/libunity8-private/wmpolicyinterface.cpp+6−12 src/libunity8-private/wmpolicyinterface.h+51 src/main.cpp+4−8 tests/mocks/CMakeLists.txt+1 tests/mocks/Cursor/Cursor.qml+49−2 tests/mocks/GSettings.1.0/plugin.cpp+12 tests/mocks/GSettings.1.0/plugin.h+1 tests/mocks/UInput/plugin.cpp+6−1 tests/mocks/Unity/Application/CMakeLists.txt+26−1 tests/mocks/Unity/Application/MirSurfaceItem.cpp+27−4 tests/mocks/Unity/Application/SurfaceManager.cpp+60−2 tests/mocks/Unity/Application/SurfaceManager.h+14 tests/mocks/Unity/Application/plugin.cpp+7−1 tests/mocks/Unity/CMakeLists.txt−1 tests/mocks/Unity/InputInfo/InputWindow.qml+68 tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2 tests/mocks/Unity/InputInfo/mockcontroller.h+5−1 tests/mocks/Unity/InputInfo/plugin.cpp+1−2 tests/mocks/Unity/InputInfo/qmldir+2 tests/mocks/Unity/Screens/CMakeLists.txt−14 tests/mocks/Unity/Screens/qmldir−2 tests/mocks/Unity/Screens/screens.cpp−71 tests/mocks/Unity/Screens/screens.h−82 tests/mocks/WindowManager/CMakeLists.txt+51 tests/mocks/Unity/Screens/plugin.cpp → tests/mocks/WindowManager/MockScreenWindow.cpp+14−9 tests/mocks/Unity/Screens/plugin.h → tests/mocks/WindowManager/MockScreenWindow.h+11−6 tests/mocks/WindowManager/MockScreens.cpp+213 tests/mocks/WindowManager/MockScreens.h+54 tests/mocks/WindowManager/WindowManagementPolicy.cpp+101 tests/mocks/WindowManager/WindowManagementPolicy.h+72 tests/mocks/WindowManager/WindowManagerPlugin.cpp+75 tests/mocks/WindowManager/WindowManagerPlugin.h+33 tests/mocks/WindowManager/qmldir+2 tests/qmltests/ApplicationMenuDataLoader.qml+1−4 tests/qmltests/ApplicationMenus/tst_MenuBar.qml−2 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2 tests/qmltests/Panel/tst_ActiveCallHint.qml−2 tests/qmltests/Panel/tst_Panel.qml+14−14 tests/qmltests/Stage/tst_ApplicationWindow.qml−2 tests/qmltests/Stage/tst_DecoratedWindow.qml+5−3 tests/qmltests/Stage/tst_DesktopStage.qml+9−6 tests/qmltests/Stage/tst_PhoneStage.qml+1−3 tests/qmltests/Stage/tst_SurfaceContainer.qml−2 tests/qmltests/Stage/tst_TabletStage.qml+1−3 tests/qmltests/Stage/tst_WindowDecoration.qml−2 tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8 tests/qmltests/Tutorial/tst_Tutorial.qml+1−3 tests/qmltests/tst_OrientedShell.qml+19−5 tests/qmltests/tst_Shell.qml+22−15 tests/qmltests/tst_ShellApplication.qml+162 tests/uqmlscene/main.cpp+2
CMakeLists.txt+7−3
@@ -70,15 +70,17 @@ find_package(Qt5DBus 5.6 REQUIRED)
7070find_package(Qt5Concurrent 5.6 REQUIRED)
7171find_package(Qt5Sql 5.6 REQUIRED)
7272
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)
7475pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
7576pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
7677pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
7778pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
7879pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
7980pkg_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)
8182pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
83+pkg_check_modules(MIRAL REQUIRED miral)
8284
8385### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
8486find_path(UBUNTUGESTUREPRIV
@@ -113,7 +115,9 @@ if(SHELL_PLUGINDIR STREQUAL "")
113115 message(FATAL_ERROR "Could not determine plugin import dir.")
114116endif()
115117
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)
117121
118122if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
119123 option(Werror "Treat warnings as errors" ON)
debian/control+10−4
@@ -31,20 +31,22 @@ Build-Depends: cmake,
3131 libqmenumodel-dev (>= 0.2.11),
3232 libqt5svg5-dev,
3333 libqt5xmlpatterns5-dev,
34+ libqtmirserver-dev (>= 0.6.0),
3435 libsystemsettings-dev,
35− libubuntu-app-launch2-dev,
36+ libubuntu-app-launch3-dev,
3637 libubuntu-download-manager-common-dev,
3738 libubuntugestures5-dev (>= 1.3.2030),
3839 libubuntugestures5-private-dev (>= 1.3.2030),
3940 libudev-dev,
4041 libudm-common-dev,
41− libunity-api-dev (>= 8.2),
42+ libunity-api-dev (>= 8.3),
4243 libusermetricsoutput1-dev,
4344# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4445 libx11-dev[!arm64 !armhf],
4546 libxcb1-dev[!arm64 !armhf],
4647 libxi-dev[!arm64 !armhf],
4748# End of X11 libs
49+ mirtest-dev,
4850 pkg-config,
4951 python3-all:any,
5052 python3-setuptools,
@@ -70,6 +72,9 @@ Build-Depends: cmake,
7072 qtdeclarative5-ubuntu-settings-components (>= 0.11),
7173 ttf-ubuntu-font-family,
7274 xvfb,
75+# mirtest pkgconfig requires these, but doesn't have a deb dependency. Bug lp:1633537
76+ libboost-filesystem-dev,
77+ libboost-system-dev,
7378Standards-Version: 3.9.4
7479Homepage: http://launchpad.net/unity
7580# If you aren't a member of ~unity-team but need to upload
@@ -131,6 +136,7 @@ Depends: dmz-cursor-theme,
131136 qml-module-ubuntu-web,
132137 qtdeclarative5-qtmir-plugin (>= 0.4.8),
133138 qtdeclarative5-ubuntu-telephony0.1,
139+ qtmir-desktop (>= 0.6.0) | qtmir-android (>= 0.6.0),
134140 ubuntu-system-settings (>= 0.4),
135141 unity-launcher-impl-10,
136142 unity8-common (= ${source:Version}),
@@ -162,7 +168,7 @@ Depends: qml-module-qtquick-layouts,
162168 qttranslations5-l10n,
163169 ubuntu-thumbnailer-impl-0,
164170 ubuntu-wallpapers,
165− unity-application-impl-26,
171+ unity-application-impl-27,
166172 unity-notifications-impl-3,
167173 unity-plugin-scopes | unity-scopes-impl,
168174 unity-scopes-impl-12,
@@ -212,7 +218,7 @@ Depends: dbus-test-runner,
212218 ${misc:Depends},
213219 ${shlibs:Depends},
214220Provides: unity-application-impl,
215− unity-application-impl-26,
221+ unity-application-impl-27,
216222 unity8-fake-env,
217223Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
218224 unity8-fake-env,
debian/unity8.install+4
@@ -16,7 +16,11 @@ usr/share/unity8/Rotation
1616usr/share/unity8/DeviceConfiguration.qml
1717usr/share/unity8/OrientedShell.qml
1818usr/share/unity8/DisabledScreenNotice.qml
19+usr/share/unity8/qmldir
1920usr/share/unity8/Shell.qml
21+usr/share/unity8/ShellApplication.qml
22+usr/share/unity8/ShellNotifier.qml
23+usr/share/unity8/ShellScreen.qml
2024usr/share/unity8/Stage
2125usr/share/unity8/Tutorial
2226usr/share/unity8/Wizard
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
1515 CursorImageInfo.cpp
1616 CursorImageProvider.cpp
1717 MousePointer.cpp
18+ InputDispatcherFilter.cpp
1819 # We need to run moc on this header
1920 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
2021 )
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 @@
1616
1717#include "MousePointer.h"
1818#include "CursorImageProvider.h"
19−
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
19+#include "InputDispatcherFilter.h"
2220
2321#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
2622
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
2825
2926MousePointer::MousePointer(QQuickItem *parent)
3027 : MirMousePointerInterface(parent)
3128 , m_cursorName(QStringLiteral("left_ptr"))
3229 , m_themeName(QStringLiteral("default"))
3330{
34−}
35−
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
37− Qt::KeyboardModifiers modifiers)
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
44− Q_EMIT mouseMoved();
45− }
46−
47− m_accumulatedMovement += movement;
48− // don't apply the fractional part
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
50− m_accumulatedMovement -= appliedMovement;
51−
52− qreal newX = x() + appliedMovement.x();
53− qreal newY = y() + appliedMovement.y();
54− const qreal sceneWidth = parentItem()->width();
55− const qreal sceneHeight = parentItem()->height();
56−
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
60− m_pushing = true;
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
64− m_pushing = true;
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
68− m_pushing = true;
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
72− m_pushing = true;
73− } else if (newX < 0) { // left edge
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
75− m_pushing = true;
76− } else if (newX >= sceneWidth) { // right edge
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
78− m_pushing = true;
79− } else if (newY < m_topBoundaryOffset) { // top edge
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
81− m_pushing = true;
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
83− if (m_pushing) {
31+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
32+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
33+ if (window() && window()->screen() == screen) {
34+ Q_EMIT pushedLeftBoundary(amount, buttons);
35+ }
36+ });
37+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
38+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
39+ if (window() && window()->screen() == screen) {
40+ Q_EMIT pushedRightBoundary(amount, buttons);
41+ }
42+ });
43+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
44+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
45+ if (window() && window()->screen() == screen) {
46+ Q_EMIT pushedTopBoundary(amount, buttons);
47+ }
48+ });
49+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
50+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
51+ if (window() && window()->screen() == screen) {
52+ Q_EMIT pushedTopLeftCorner(amount, buttons);
53+ }
54+ });
55+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
56+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
57+ if (window() && window()->screen() == screen) {
58+ Q_EMIT pushedTopRightCorner(amount, buttons);
59+ }
60+ });
61+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
62+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
63+ if (window() && window()->screen() == screen) {
64+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
65+ }
66+ });
67+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
68+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
69+ if (window() && window()->screen() == screen) {
70+ Q_EMIT pushedBottomRightCorner(amount, buttons);
71+ }
72+ });
73+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
74+ this, [this](QScreen* screen) {
75+ if (window() && window()->screen() == screen) {
8476 Q_EMIT pushStopped();
85− m_pushing = false;
8677 }
87− }
78+ });
8879
89− applyItemConfinement(newX, newY);
90−
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
80+ InputDispatcherFilter::instance()->registerPointer(this);
81+}
9382
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
96− buttons, modifiers);
83+MousePointer::~MousePointer()
84+{
85+ registerScreen(nullptr);
86+ InputDispatcherFilter::instance()->unregisterPointer(this);
9787}
9888
9989void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121111 }
122112}
123113
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−
135114int MousePointer::topBoundaryOffset() const
136115{
137116 return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
182161 if (m_registeredScreen) {
183162 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184163 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
164+ previousCursor->unregisterMousePointer(this);
186165 } else {
187166 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188167 }
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
193172 if (m_registeredScreen) {
194173 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195174 if (cursor) {
196− cursor->setMousePointer(this);
175+ cursor->registerMousePointer(this);
197176 } else {
198177 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199178 }
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
216195 }
217196}
218197
198+void MousePointer::moveTo(const QPoint &position)
199+{
200+ setPosition(position);
201+ Q_EMIT mouseMoved();
202+}
203+
219204void MousePointer::setCustomCursor(const QCursor &customCursor)
220205{
221206 CursorImageProvider::instance()->setCustomCursor(customCursor);
plugins/Cursor/MousePointer.h+5−4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
3131 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
3232public:
3333 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
3435
3536 void setCursorName(const QString &qtCursorName) override;
3637 QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ public:
3839 void setThemeName(const QString &themeName) override;
3940 QString themeName() const override { return m_themeName; }
4041
42+ void moveTo(const QPoint& position) override;
43+
4144 void setCustomCursor(const QCursor &) override;
4245
4346 QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ public:
4649 int topBoundaryOffset() const;
4750 void setTopBoundaryOffset(int topBoundaryOffset);
4851
49−public Q_SLOTS:
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;
52+ QScreen* screen() const { return m_registeredScreen; }
5353
5454Q_SIGNALS:
5555 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private:
7979 QPointer<QScreen> m_registeredScreen;
8080 QString m_cursorName;
8181 QString m_themeName;
82+ bool m_active;
8283
8384 // Accumulated, unapplied, mouse movement.
8485 QPointF m_accumulatedMovement;
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
1919
2020#include <QtQml/qqml.h>
2121
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
2228void UInputPlugin::registerTypes(const char *uri)
2329{
2430 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
2632}
plugins/Unity/CMakeLists.txt+1
@@ -1,5 +1,6 @@
11add_subdirectory(ApplicationMenu)
22add_subdirectory(Connectivity)
3+add_subdirectory(Debug)
34add_subdirectory(Indicators)
45add_subdirectory(Launcher)
56add_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 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_SHELL_VIEW_H
18−#define UNITY_SHELL_VIEW_H
17+#ifndef UNITYDEBUG_PLUGIN_H
18+#define UNITYDEBUG_PLUGIN_H
1919
20−#include <QQuickView>
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
2122
22−class ShellView : public QQuickView
23+class UnityDebugPlugin : public QQmlExtensionPlugin
2324{
2425 Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
2527
2628public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
28−
29−private Q_SLOTS:
30− void onWidthChanged(int);
31− void onHeightChanged(int);
29+ void registerTypes(const char *uri) override;
3230};
3331
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+24−2
@@ -1,16 +1,38 @@
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+
111set(WINDOWMANAGER_SRC
212 TopLevelWindowModel.cpp
313 Window.cpp
414 WindowManagerPlugin.cpp
15+ Screen.cpp
16+ ScreenAttached.cpp
17+ Screens.cpp
18+ ScreenWindow.cpp
19+ Workspace.cpp
20+ WorkspaceManager.cpp
21+ WorkspaceModel.cpp
522 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
623 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
724 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
825 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
926 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
10− )
27+)
1128
1229add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
1330
31+target_link_libraries(windowmanager-qml
32+ ${QTMIRSERVER_LDFLAGS}
33+ unity8-private
34+)
35+
1436qt5_use_modules(windowmanager-qml Qml Quick Gui)
1537
16−add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
38+add_unity8_plugin(WindowManager 1.0 WindowManager TARGETS windowmanager-qml)
plugins/WindowManager/Screen.cppnew file+294
@@ -0,0 +1,294 @@
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 "Screens.h"
19+#include "WorkspaceManager.h"
20+#include "Workspace.h"
21+
22+ScreenInterface::ScreenInterface(QObject *parent)
23+ : QObject(parent)
24+{
25+}
26+
27+void ScreenInterface::connectToScreen(qtmir::Screen *screen)
28+{
29+ m_wrapped = screen;
30+ connect(screen, &qtmir::Screen::usedChanged, this, &ScreenInterface::usedChanged);
31+ connect(screen, &qtmir::Screen::nameChanged, this, &ScreenInterface::nameChanged);
32+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &ScreenInterface::outputTypeChanged);
33+ connect(screen, &qtmir::Screen::scaleChanged, this, &ScreenInterface::scaleChanged);
34+ connect(screen, &qtmir::Screen::formFactorChanged, this, &ScreenInterface::formFactorChanged);
35+ connect(screen, &qtmir::Screen::physicalSizeChanged, this, &ScreenInterface::physicalSizeChanged);
36+ connect(screen, &qtmir::Screen::positionChanged, this, &ScreenInterface::positionChanged);
37+ connect(screen, &qtmir::Screen::activeChanged, this, &ScreenInterface::activeChanged);
38+ connect(screen, &qtmir::Screen::currentModeIndexChanged, this, &ScreenInterface::currentModeIndexChanged);
39+ connect(screen, &qtmir::Screen::availableModesChanged, this, &ScreenInterface::availableModesChanged);
40+}
41+
42+qtmir::OutputId ScreenInterface::outputId() const
43+{
44+ if (!m_wrapped) return qtmir::OutputId(-1);
45+ return m_wrapped->outputId();
46+}
47+
48+bool ScreenInterface::used() const
49+{
50+ if (!m_wrapped) return false;
51+ return m_wrapped->used();
52+}
53+
54+QString ScreenInterface::name() const
55+{
56+ if (!m_wrapped) return QString();
57+ return m_wrapped->name();
58+}
59+
60+float ScreenInterface::scale() const
61+{
62+ if (!m_wrapped) return 1.0;
63+ return m_wrapped->scale();
64+}
65+
66+QSizeF ScreenInterface::physicalSize() const
67+{
68+ if (!m_wrapped) return QSizeF();
69+ return m_wrapped->physicalSize();
70+}
71+
72+qtmir::FormFactor ScreenInterface::formFactor() const
73+{
74+ if (!m_wrapped) return qtmir::FormFactorUnknown;
75+ return m_wrapped->formFactor();
76+}
77+
78+qtmir::OutputTypes ScreenInterface::outputType() const
79+{
80+ if (!m_wrapped) return qtmir::Unknown;
81+ return m_wrapped->outputType();
82+}
83+
84+MirPowerMode ScreenInterface::powerMode() const
85+{
86+ if (!m_wrapped) return mir_power_mode_on;
87+ return m_wrapped->powerMode();
88+}
89+
90+Qt::ScreenOrientation ScreenInterface::orientation() const
91+{
92+ if (!m_wrapped) return Qt::PrimaryOrientation;
93+ return m_wrapped->orientation();
94+}
95+
96+QPoint ScreenInterface::position() const
97+{
98+ if (!m_wrapped) return QPoint();
99+ return m_wrapped->position();
100+}
101+
102+QQmlListProperty<qtmir::ScreenMode> ScreenInterface::availableModes()
103+{
104+ if (!m_wrapped) return QQmlListProperty<qtmir::ScreenMode>();
105+ return m_wrapped->availableModes();
106+}
107+
108+uint ScreenInterface::currentModeIndex() const
109+{
110+ if (!m_wrapped) return -1;
111+ return m_wrapped->currentModeIndex();
112+}
113+
114+bool ScreenInterface::isActive() const
115+{
116+ if (!m_wrapped) return false;
117+ return m_wrapped->isActive();
118+}
119+
120+void ScreenInterface::activate()
121+{
122+ setActive(true);
123+}
124+
125+void ScreenInterface::setActive(bool active)
126+{
127+ if (!m_wrapped) return;
128+ m_wrapped->setActive(active);
129+}
130+
131+QScreen *ScreenInterface::qscreen() const
132+{
133+ if (!m_wrapped) return nullptr;
134+ return m_wrapped->qscreen();
135+}
136+
137+qtmir::ScreenConfiguration *ScreenInterface::beginConfiguration() const
138+{
139+ if (!m_wrapped) return nullptr;
140+ return m_wrapped->beginConfiguration();
141+}
142+
143+bool ScreenInterface::applyConfiguration(qtmir::ScreenConfiguration *configuration)
144+{
145+ if (!m_wrapped) return false;
146+ return m_wrapped->applyConfiguration(configuration);
147+}
148+
149+QString ScreenInterface::outputTypeName() const
150+{
151+ switch (m_wrapped->outputType()) {
152+ case qtmir::Unknown:
153+ return tr("Unknown");
154+ case qtmir::VGA:
155+ return tr("VGA");
156+ case qtmir::DVII:
157+ case qtmir::DVID:
158+ case qtmir::DVIA:
159+ return tr("DVI");
160+ case qtmir::Composite:
161+ return tr("Composite");
162+ case qtmir::SVideo:
163+ return tr("S-Video");
164+ case qtmir::LVDS:
165+ case qtmir::NinePinDIN:
166+ case qtmir::EDP:
167+ return tr("Internal");
168+ case qtmir::Component:
169+ return tr("Component");
170+ case qtmir::DisplayPort:
171+ return tr("DisplayPort");
172+ case qtmir::HDMIA:
173+ case qtmir::HDMIB:
174+ return tr("HDMI");
175+ case qtmir::TV:
176+ return tr("TV");
177+ }
178+ return QString();
179+}
180+
181+void ScreenInterface::sync(ScreenInterface *proxy)
182+{
183+ if (!proxy) return;
184+ workspaces()->sync(proxy->workspaces());
185+}
186+
187+Screen::Screen(qtmir::Screen* wrapped)
188+ : m_workspaces(new WorkspaceModel)
189+{
190+ connectToScreen(wrapped);
191+
192+ // Connect the active workspace to activate the screen.
193+ connect(m_workspaces.data(), &WorkspaceModel::workspaceInserted, this, [this](int, Workspace* workspace) {
194+ connect(workspace, &Workspace::activeChanged, this, [this, workspace](bool active) {
195+ if (active) {
196+ setCurrentWorkspace(workspace);
197+ activate();
198+ }
199+ });
200+ if (workspace->isActive()) {
201+ activate();
202+ setCurrentWorkspace(workspace);
203+ }
204+ if (!m_currentWorspace) {
205+ setCurrentWorkspace(workspace);
206+ }
207+ });
208+ connect(m_workspaces.data(), &WorkspaceModel::workspaceRemoved, this, [this](Workspace* workspace) {
209+ disconnect(workspace, &Workspace::activeChanged, this, 0);
210+ if (workspace == m_currentWorspace) {
211+ resetCurrentWorkspace();
212+ }
213+ });
214+ connect(this, &Screen::activeChanged, this, [this](bool active) {
215+ if (active && m_currentWorspace) {
216+ m_currentWorspace->activate();
217+ }
218+ });
219+
220+ WorkspaceManager::instance()->createWorkspace()->assign(m_workspaces.data());
221+ WorkspaceManager::instance()->createWorkspace()->assign(m_workspaces.data());
222+}
223+
224+void Screen::resetCurrentWorkspace()
225+{
226+ auto newCurrent = m_workspaces->rowCount() > 0 ? m_workspaces->get(0) : nullptr;
227+ if (m_currentWorspace != newCurrent) {
228+ m_currentWorspace = newCurrent;
229+ Q_EMIT currentWorkspaceChanged(newCurrent);
230+ }
231+}
232+
233+
234+WorkspaceModel *Screen::workspaces() const
235+{
236+ return m_workspaces.data();
237+}
238+
239+Workspace *Screen::currentWorkspace() const
240+{
241+ return m_currentWorspace.data();
242+}
243+
244+void Screen::setCurrentWorkspace(Workspace *workspace)
245+{
246+ if (m_currentWorspace != workspace) {
247+ m_currentWorspace = workspace;
248+ Q_EMIT currentWorkspaceChanged(workspace);
249+ }
250+}
251+
252+ScreenProxy::ScreenProxy(ScreenInterface *const screen)
253+ : m_workspaces(new WorkspaceModelProxy(screen->workspaces()))
254+ , m_original(screen)
255+{
256+ connectToScreen(screen->wrapped());
257+
258+ auto updateCurrentWorkspaceFn = [this](Workspace* realWorkspace) {
259+ Q_FOREACH(Workspace* workspace, m_workspaces->list()) {
260+ auto p = qobject_cast<WorkspaceProxy*>(workspace);
261+ if (p && p->proxyObject() == realWorkspace) {
262+ if (m_currentWorspace != p) {
263+ m_currentWorspace = p;
264+ Q_EMIT currentWorkspaceChanged(p);
265+ }
266+ }
267+ }
268+ };
269+ connect(screen, &ScreenInterface::currentWorkspaceChanged, this, updateCurrentWorkspaceFn);
270+ updateCurrentWorkspaceFn(screen->currentWorkspace());
271+}
272+
273+WorkspaceModel *ScreenProxy::workspaces() const
274+{
275+ return m_workspaces.data();
276+}
277+
278+Workspace *ScreenProxy::currentWorkspace() const
279+{
280+ return m_currentWorspace.data();
281+}
282+
283+void ScreenProxy::setCurrentWorkspace(Workspace *workspace)
284+{
285+ auto p = qobject_cast<WorkspaceProxy*>(workspace);
286+ if (p) {
287+ m_original->setCurrentWorkspace(p->proxyObject());
288+ }
289+}
290+
291+void ScreenProxy::addWorkspace()
292+{
293+ (new WorkspaceProxy(WorkspaceManager::instance()->createWorkspace()))->assign(workspaces());
294+}
plugins/WindowManager/Screen.hnew file+133
@@ -0,0 +1,133 @@
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+class ScreenAttached;
12+
13+class ScreenInterface: public QObject
14+{
15+ Q_OBJECT
16+
17+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
18+
19+ Q_PROPERTY(bool used READ used NOTIFY usedChanged)
20+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
21+ Q_PROPERTY(qtmir::OutputTypes outputType READ outputType NOTIFY outputTypeChanged)
22+ Q_PROPERTY(float scale READ scale NOTIFY scaleChanged)
23+ Q_PROPERTY(qtmir::FormFactor formFactor READ formFactor NOTIFY formFactorChanged)
24+ Q_PROPERTY(MirPowerMode powerMode READ powerMode NOTIFY powerModeChanged)
25+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged)
26+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
27+ Q_PROPERTY(uint currentModeIndex READ currentModeIndex NOTIFY currentModeIndexChanged)
28+ Q_PROPERTY(QQmlListProperty<qtmir::ScreenMode> availableModes READ availableModes NOTIFY availableModesChanged)
29+ Q_PROPERTY(QSizeF physicalSize READ physicalSize NOTIFY physicalSizeChanged)
30+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeChanged)
31+
32+ Q_PROPERTY(WorkspaceModel* workspaces READ workspaces CONSTANT)
33+ Q_PROPERTY(Workspace* currentWorkspace READ currentWorkspace WRITE setCurrentWorkspace NOTIFY currentWorkspaceChanged)
34+public:
35+ // From qtmir::Screen
36+ qtmir::OutputId outputId() const;
37+ bool used() const;
38+ QString name() const;
39+ float scale() const;
40+ QSizeF physicalSize() const;
41+ qtmir::FormFactor formFactor() const;
42+ qtmir::OutputTypes outputType() const;
43+ MirPowerMode powerMode() const;
44+ Qt::ScreenOrientation orientation() const;
45+ QPoint position() const;
46+ QQmlListProperty<qtmir::ScreenMode> availableModes();
47+ uint currentModeIndex() const;
48+ bool isActive() const;
49+ void setActive(bool active);
50+ QScreen* qscreen() const;
51+ qtmir::ScreenConfiguration *beginConfiguration() const;
52+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration);
53+ QString outputTypeName() const;
54+
55+ virtual WorkspaceModel* workspaces() const = 0;
56+ virtual Workspace *currentWorkspace() const = 0;
57+ virtual void setCurrentWorkspace(Workspace* workspace) = 0;
58+
59+ void sync(ScreenInterface* proxy);
60+
61+ qtmir::Screen* wrapped() const { return m_wrapped; }
62+
63+public Q_SLOTS:
64+ void activate();
65+
66+Q_SIGNALS:
67+ void usedChanged();
68+ void nameChanged();
69+ void outputTypeChanged();
70+ void scaleChanged();
71+ void formFactorChanged();
72+ void powerModeChanged();
73+ void orientationChanged();
74+ void positionChanged();
75+ void currentModeIndexChanged();
76+ void physicalSizeChanged();
77+ void availableModesChanged();
78+ void activeChanged(bool active);
79+ void currentWorkspaceChanged(Workspace*);
80+
81+protected:
82+ ScreenInterface(QObject* parent = 0);
83+
84+ void connectToScreen(qtmir::Screen* screen);
85+
86+protected:
87+ QPointer<qtmir::Screen> m_wrapped;
88+};
89+
90+
91+class Screen : public ScreenInterface
92+{
93+ Q_OBJECT
94+public:
95+ explicit Screen(qtmir::Screen*const wrapped);
96+
97+ // From qtmir::Screen
98+ WorkspaceModel* workspaces() const override;
99+ Workspace *currentWorkspace() const override;
100+ void setCurrentWorkspace(Workspace* workspace) override;
101+
102+ static ScreenAttached *qmlAttachedProperties(QObject *owner);
103+
104+protected:
105+ void resetCurrentWorkspace();
106+
107+ const QScopedPointer<WorkspaceModel> m_workspaces;
108+ QPointer<Workspace> m_currentWorspace;
109+};
110+
111+class ScreenProxy : public ScreenInterface
112+{
113+ Q_OBJECT
114+public:
115+ explicit ScreenProxy(ScreenInterface*const screen);
116+
117+ // From qtmir::Screen
118+ WorkspaceModel* workspaces() const override;
119+ Workspace *currentWorkspace() const override;
120+ void setCurrentWorkspace(Workspace* workspace) override;
121+
122+ ScreenInterface* proxyObject() const { return m_original.data(); }
123+
124+public Q_SLOTS:
125+ void addWorkspace();
126+
127+private:
128+ const QScopedPointer<WorkspaceModel> m_workspaces;
129+ const QPointer<ScreenInterface> m_original;
130+ QPointer<Workspace> m_currentWorspace;
131+};
132+
133+#endif // SCREEN_H
plugins/WindowManager/ScreenAttached.cppnew file+149
@@ -0,0 +1,149 @@
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 "ScreenAttached.h"
18+#include "ScreenWindow.h"
19+#include "Screens.h"
20+
21+#include <QQuickItem>
22+#include <QScreen>
23+
24+ScreenAttached::ScreenAttached(QObject *owner)
25+ : ScreenInterface(owner)
26+ , m_window(nullptr)
27+{
28+ if (auto item = qobject_cast<QQuickItem*>(owner)) {
29+ connect(item, &QQuickItem::windowChanged, this, &ScreenAttached::windowChanged);
30+ windowChanged(item->window());
31+ } else if (auto window = qobject_cast<QQuickWindow*>(owner)) {
32+ windowChanged(window);
33+ }
34+}
35+
36+WorkspaceModel *ScreenAttached::workspaces() const
37+{
38+ if (!m_screen) return nullptr;
39+ return m_screen->workspaces();
40+}
41+
42+Workspace *ScreenAttached::currentWorkspace() const
43+{
44+ if (!m_screen) return nullptr;
45+ return m_screen->currentWorkspace();
46+}
47+
48+void ScreenAttached::setCurrentWorkspace(Workspace *workspace)
49+{
50+ if (!m_screen) return;
51+ return m_screen->setCurrentWorkspace(workspace);
52+}
53+
54+void ScreenAttached::windowChanged(QQuickWindow *window)
55+{
56+ if (m_window) {
57+ disconnect(m_window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
58+ }
59+
60+ m_window = window;
61+ auto screenWindow = qobject_cast<ScreenWindow*>(window);
62+
63+ if (screenWindow) {
64+ screenChanged2(screenWindow->screenWrapper());
65+ connect(screenWindow, &ScreenWindow::screenWrapperChanged, this, &ScreenAttached::screenChanged2);
66+ } else {
67+ screenChanged(window ? window->screen() : NULL);
68+ if (window) {
69+ connect(window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
70+ }
71+ }
72+}
73+
74+void ScreenAttached::screenChanged(QScreen *qscreen)
75+{
76+ // Find a screen that matches.
77+ // Should only get here in mocks if we don't have a ScreenWindow
78+ ScreenInterface* screen{nullptr};
79+ Q_FOREACH(auto s, Screens::self()->list()) {
80+ if (s->qscreen() == qscreen) {
81+ screen = s;
82+ }
83+ }
84+ screenChanged2(screen);
85+}
86+
87+void ScreenAttached::screenChanged2(ScreenInterface* screen)
88+{
89+ if (screen == m_screen) return;
90+
91+ ScreenInterface* oldScreen = m_screen;
92+ m_screen = screen;
93+
94+ if (oldScreen)
95+ oldScreen->disconnect(this);
96+
97+ if (!screen)
98+ return; //Don't bother emitting signals, because the new values are garbage anyways
99+
100+ if (!oldScreen || screen->isActive() != oldScreen->isActive())
101+ Q_EMIT activeChanged(screen->isActive());
102+ if (!oldScreen || screen->used() != oldScreen->used())
103+ Q_EMIT usedChanged();
104+ if (!oldScreen || screen->name() != oldScreen->name())
105+ Q_EMIT nameChanged();
106+ if (!oldScreen || screen->outputType() != oldScreen->outputType())
107+ Q_EMIT outputTypeChanged();
108+ if (!oldScreen || screen->scale() != oldScreen->scale())
109+ Q_EMIT scaleChanged();
110+ if (!oldScreen || screen->formFactor() != oldScreen->formFactor())
111+ Q_EMIT formFactorChanged();
112+ if (!oldScreen || screen->powerMode() != oldScreen->powerMode())
113+ Q_EMIT powerModeChanged();
114+ if (!oldScreen || screen->orientation() != oldScreen->orientation())
115+ Q_EMIT orientationChanged();
116+ if (!oldScreen || screen->position() != oldScreen->position())
117+ Q_EMIT positionChanged();
118+ if (!oldScreen || screen->currentModeIndex() != oldScreen->currentModeIndex())
119+ Q_EMIT currentModeIndexChanged();
120+ if (!oldScreen || screen->physicalSize() != oldScreen->physicalSize())
121+ Q_EMIT physicalSizeChanged();
122+
123+ if (oldScreen) {
124+ QVector<qtmir::ScreenMode*> oldModes;
125+ auto oldModesQmlList = oldScreen->availableModes();
126+ for (int i = 0; i < oldModesQmlList.count(&oldModesQmlList); i++) {
127+ oldModes << oldModesQmlList.at(&oldModesQmlList, i);
128+ }
129+
130+ QVector<qtmir::ScreenMode*> newModes;
131+ auto newModesQmlList = screen->availableModes();
132+ for (int i = 0; i < newModesQmlList.count(&newModesQmlList); i++) {
133+ newModes << newModesQmlList.at(&newModesQmlList, i);
134+ }
135+
136+ if (newModes != newModes) {
137+ Q_EMIT availableModesChanged();
138+ }
139+ } else {
140+ Q_EMIT availableModesChanged();
141+ }
142+
143+ connectToScreen(screen->wrapped());
144+}
145+
146+ScreenAttached *WMScreen::qmlAttachedProperties(QObject *owner)
147+{
148+ return new ScreenAttached(owner);
149+}
plugins/WindowManager/ScreenAttached.hnew file+56
@@ -0,0 +1,56 @@
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+#ifndef SCREENATTACHED_H
18+#define SCREENATTACHED_H
19+
20+#include "Screen.h"
21+
22+#include <QQmlEngine>
23+#include <QtQml>
24+
25+class QQuickWindow;
26+
27+class ScreenAttached : public ScreenInterface
28+{
29+ Q_OBJECT
30+public:
31+ ScreenAttached(QObject* owner);
32+
33+ WorkspaceModel* workspaces() const override;
34+ Workspace *currentWorkspace() const override;
35+ void setCurrentWorkspace(Workspace* workspace) override;
36+
37+private Q_SLOTS:
38+ void windowChanged(QQuickWindow*);
39+ void screenChanged(QScreen*);
40+ void screenChanged2(ScreenInterface* screen);
41+
42+private:
43+ QPointer<ScreenInterface> m_screen;
44+ QQuickWindow* m_window;
45+};
46+
47+class WMScreen : public QObject
48+{
49+ Q_OBJECT
50+public:
51+ static ScreenAttached *qmlAttachedProperties(QObject *owner);
52+};
53+
54+QML_DECLARE_TYPEINFO(WMScreen, QML_HAS_ATTACHED_PROPERTIES)
55+
56+#endif // SCREENATTACHED_H
plugins/WindowManager/ScreenWindow.cppnew file+45
@@ -0,0 +1,45 @@
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+}
28+
29+ScreenWindow::~ScreenWindow()
30+{
31+}
32+
33+Screen *ScreenWindow::screenWrapper() const
34+{
35+ return m_screen.data();
36+}
37+
38+void ScreenWindow::setScreenWrapper(Screen *screen)
39+{
40+ if (m_screen != screen) {
41+ m_screen = screen;
42+ Q_EMIT screenWrapperChanged(screen);
43+ }
44+ QQuickWindow::setScreen(screen->qscreen());
45+}
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(Screen* screen);
44+
45+private:
46+ QPointer<Screen> m_screen;
47+};
48+
49+#endif // UNITY_SCREENWINDOW_H
plugins/WindowManager/Screens.cppnew file+213
@@ -0,0 +1,213 @@
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::m_self{nullptr};
31+
32+Screens::Screens(const QSharedPointer<qtmir::Screens>& model)
33+ : m_wrapped(model)
34+{
35+ m_self = this;
36+ connect(m_wrapped.data(), &qtmir::Screens::screenAdded, this, &Screens::onScreenAdded);
37+ connect(m_wrapped.data(), &qtmir::Screens::screenRemoved, this, &Screens::onScreenRemoved);
38+ connect(m_wrapped.data(), &qtmir::Screens::activeScreenChanged, this, &Screens::activeScreenChanged);
39+
40+ Q_FOREACH(qtmir::Screen* screen, m_wrapped->screens()) {
41+ auto screenWrapper(new Screen(screen));
42+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
43+ m_screens.push_back(screenWrapper);
44+ }
45+}
46+
47+Screens::~Screens()
48+{
49+ qDeleteAll(m_screens);
50+ m_screens.clear();
51+}
52+
53+Screens::Screens(const Screens &other)
54+ : QAbstractListModel(nullptr)
55+ , m_wrapped(other.m_wrapped)
56+{
57+}
58+
59+QHash<int, QByteArray> Screens::roleNames() const
60+{
61+ QHash<int, QByteArray> roles;
62+ roles[ScreenRole] = "screen";
63+ return roles;
64+}
65+
66+QVariant Screens::data(const QModelIndex &index, int role) const
67+{
68+ if (!index.isValid() || index.row() >= m_screens.size()) {
69+ return QVariant();
70+ }
71+
72+ switch(role) {
73+ case ScreenRole:
74+ return QVariant::fromValue(m_screens.at(index.row()));
75+ } // switch
76+
77+ return QVariant();
78+}
79+
80+int Screens::rowCount(const QModelIndex &) const
81+{
82+ return count();
83+}
84+
85+int Screens::count() const
86+{
87+ return m_screens.size();
88+}
89+
90+QVariant Screens::activeScreen() const
91+{
92+ for (int i = 0; i < m_screens.count(); i++) {
93+ if (m_screens[i]->isActive()) return i;
94+ }
95+ return QVariant();
96+}
97+
98+Screens *Screens::self()
99+{
100+ return Screens::m_self;
101+}
102+
103+ScreensProxy *Screens::createProxy()
104+{
105+ return new ScreensProxy(this);
106+}
107+
108+void Screens::sync(Screens *proxy)
109+{
110+ if (!proxy) return;
111+
112+ const auto& proxyList = proxy->list();
113+ for (int i = 0; i < m_screens.count() && i < proxyList.count(); ++i) {
114+ m_screens[i]->sync(proxyList[i]);
115+ }
116+
117+ // need to clean up all the workspaces we unassigned.
118+ WorkspaceManager::instance()->destroyFloatingWorkspaces();
119+}
120+
121+void Screens::activateScreen(const QVariant& vindex)
122+{
123+ bool ok = false;
124+ int index = vindex.toInt(&ok);
125+ if (!ok || index < 0 || m_screens.count() <= index) return;
126+
127+ auto screen = m_screens.at(index);
128+ screen->setActive(true);
129+}
130+
131+void Screens::onScreenAdded(qtmir::Screen *added)
132+{
133+ Q_FOREACH(auto screenWrapper, m_screens) {
134+ if (screenWrapper->wrapped() == added) return;
135+ }
136+
137+ beginInsertRows(QModelIndex(), count(), count());
138+ auto screenWrapper(new Screen(added));
139+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
140+ m_screens.push_back(screenWrapper);
141+ endInsertRows();
142+ Q_EMIT screenAdded(screenWrapper);
143+ Q_EMIT countChanged();
144+}
145+
146+void Screens::onScreenRemoved(qtmir::Screen *removed)
147+{
148+ int index = 0;
149+ QMutableVectorIterator<ScreenInterface*> iter(m_screens);
150+ while(iter.hasNext()) {
151+ auto screenWrapper = iter.next();
152+ if (screenWrapper->wrapped() == removed) {
153+
154+ beginRemoveRows(QModelIndex(), index, index);
155+ iter.remove();
156+ endRemoveRows();
157+
158+ Q_EMIT screenRemoved(screenWrapper);
159+ Q_EMIT countChanged();
160+
161+ screenWrapper->deleteLater();
162+ break;
163+ }
164+ index++;
165+ }
166+}
167+
168+ScreensProxy::ScreensProxy(Screens * const screens)
169+ : Screens(*screens)
170+ , m_original(screens)
171+{
172+ connect(screens, &Screens::screenAdded, this, [this](ScreenInterface *added) {
173+ Q_FOREACH(auto screen, m_screens) {
174+ auto proxy = static_cast<ScreenProxy*>(screen);
175+ if (proxy->proxyObject() == added) return;
176+ }
177+
178+ beginInsertRows(QModelIndex(), count(), count());
179+ auto screenWrapper(new ScreenProxy(added));
180+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
181+ m_screens.push_back(screenWrapper);
182+ endInsertRows();
183+ Q_EMIT screenAdded(screenWrapper);
184+ Q_EMIT countChanged();
185+ });
186+
187+ connect(screens, &Screens::screenRemoved, this, [this](ScreenInterface *removed) {
188+ int index = 0;
189+ QMutableVectorIterator<ScreenInterface*> iter(m_screens);
190+ while(iter.hasNext()) {
191+ auto proxy = static_cast<ScreenProxy*>(iter.next());
192+ if (proxy->proxyObject() == removed) {
193+
194+ beginRemoveRows(QModelIndex(), index, index);
195+ iter.remove();
196+ endRemoveRows();
197+
198+ Q_EMIT screenRemoved(proxy);
199+ Q_EMIT countChanged();
200+
201+ delete proxy;
202+ break;
203+ }
204+ index++;
205+ }
206+ });
207+
208+ Q_FOREACH(ScreenInterface* screen, screens->list()) {
209+ auto screenWrapper(new ScreenProxy(screen));
210+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
211+ m_screens.push_back(new ScreenProxy(screen));
212+ }
213+}
plugins/WindowManager/Screens.hnew file+93
@@ -0,0 +1,93 @@
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 UNITY_SCREENS_H
18+#define UNITY_SCREENS_H
19+
20+#include <QAbstractListModel>
21+#include <QSharedPointer>
22+#include <QPointer>
23+
24+namespace qtmir
25+{
26+class Screen;
27+class Screens;
28+}
29+
30+class ScreenInterface;
31+class ScreensProxy;
32+
33+class Screens : public QAbstractListModel
34+{
35+ Q_OBJECT
36+ Q_PROPERTY(int count READ count NOTIFY countChanged)
37+ Q_PROPERTY(QVariant activeScreen READ activeScreen WRITE activateScreen NOTIFY activeScreenChanged)
38+
39+public:
40+ enum ItemRoles {
41+ ScreenRole = Qt::UserRole + 1
42+ };
43+
44+ explicit Screens(const QSharedPointer<qtmir::Screens>& model);
45+ ~Screens();
46+
47+ Q_INVOKABLE ScreensProxy *createProxy();
48+ Q_INVOKABLE void sync(Screens *proxy);
49+
50+ /* QAbstractItemModel */
51+ QHash<int, QByteArray> roleNames() const override;
52+ QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
53+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
54+
55+ int count() const;
56+ QVariant activeScreen() const;
57+
58+ const QVector<ScreenInterface*>& list() const { return m_screens; }
59+
60+ static Screens *self();
61+
62+public Q_SLOTS:
63+ void activateScreen(const QVariant& index);
64+
65+Q_SIGNALS:
66+ void countChanged();
67+ void activeScreenChanged();
68+
69+ void screenAdded(ScreenInterface* screen);
70+ void screenRemoved(ScreenInterface* screen);
71+
72+private Q_SLOTS:
73+ void onScreenAdded(qtmir::Screen *screen);
74+ void onScreenRemoved(qtmir::Screen *screen);
75+
76+protected:
77+ Screens(const Screens& other);
78+
79+ QVector<ScreenInterface*> m_screens;
80+ QSharedPointer<qtmir::Screens> m_wrapped;
81+ static Screens* m_self;
82+};
83+
84+class ScreensProxy : public Screens
85+{
86+public:
87+ ScreensProxy(Screens*const screens);
88+
89+private:
90+ const QPointer<Screens> m_original;
91+};
92+
93+#endif // SCREENS_H
plugins/WindowManager/TopLevelWindowModel.cpp+193−40
@@ -24,11 +24,16 @@
2424#include <unity/shell/application/SurfaceManagerInterface.h>
2525
2626// Qt
27−#include <QGuiApplication>
2827#include <QDebug>
2928
3029// local
3130#include "Window.h"
31+#include "Workspace.h"
32+#include "wmpolicyinterface.h"
33+
34+// qtmir
35+#include <qtmir/mirserverapplication.h>
36+#include <qtmir/windowmodelnotifier.h>
3237
3338Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
3439
@@ -55,7 +60,6 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
5560 beginResetModel();
5661
5762 if (m_applicationManager) {
58− m_windowModel.clear();
5963 disconnect(m_applicationManager, 0, this, 0);
6064 }
6165
@@ -64,26 +68,27 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
6468 if (m_applicationManager) {
6569 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
6670 this, [this](const QModelIndex &/*parent*/, int first, int last) {
67− for (int i = first; i <= last; ++i) {
68− auto application = m_applicationManager->get(i);
69− addApplication(application);
70− }
71− });
71+ if (!m_workspace || !m_workspace->isActive())
72+ return;
73+
74+ for (int i = first; i <= last; ++i) {
75+ auto application = m_applicationManager->get(i);
76+ addApplication(application);
77+ }
78+ });
7279
7380 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
7481 this, [this](const QModelIndex &/*parent*/, int first, int last) {
75− for (int i = first; i <= last; ++i) {
76− auto application = m_applicationManager->get(i);
77− removeApplication(application);
78− }
79− });
80−
81− for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82− auto application = m_applicationManager->get(i);
83− addApplication(application);
84− }
82+ for (int i = first; i <= last; ++i) {
83+ auto application = m_applicationManager->get(i);
84+ removeApplication(application);
85+ }
86+ });
8587 }
8688
89+ Q_EMIT applicationManagerChanged(m_applicationManager);
90+ refreshWindows();
91+
8792 endResetModel();
8893 m_modelState = IdleState;
8994}
@@ -96,6 +101,11 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
96101
97102 DEBUG_MSG << "(" << surfaceManager << ")";
98103
104+ Q_ASSERT(m_modelState == IdleState);
105+ m_modelState = ResettingState;
106+
107+ beginResetModel();
108+
99109 if (m_surfaceManager) {
100110 disconnect(m_surfaceManager, 0, this, 0);
101111 }
@@ -107,9 +117,44 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
107117 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108118 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109119 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);
110125 }
111126
127+ refreshWindows();
112128 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;
113158}
114159
115160void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
@@ -179,6 +224,7 @@ void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *su
179224 Window *window = createWindow(surface);
180225
181226 m_windowModel.prepend(ModelEntry(window, application));
227+ m_allSurfaces.insert(surface);
182228 if (surface) {
183229 connectSurface(surface);
184230 }
@@ -313,6 +359,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
313359 auto window = m_windowModel[i].window;
314360 window->setSurface(nullptr);
315361 window->setFocused(false);
362+ m_allSurfaces.remove(surface);
316363 INFO_MSG << " Removed surface from entry. After: " << toString();
317364 }
318365}
@@ -328,38 +375,102 @@ Window *TopLevelWindowModel::createWindow(unityapi::MirSurfaceInterface *surface
328375 return qmlWindow;
329376}
330377
331−void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
378+
379+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface */*surface*/)
332380{
333− DEBUG_MSG << "(" << surface << ")";
381+}
334382
335− if (surface->parentSurface()) {
336− // Wrap it in a Window so that we keep focusedWindow() up to date.
337− Window *window = createWindow(surface);
338− connect(surface, &QObject::destroyed, window, [=](){
339− window->setSurface(nullptr);
340− window->deleteLater();
341− });
342− } else {
343− if (surface->type() == Mir::InputMethodType) {
344− setInputMethodWindow(createWindow(surface));
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+
392+ if (surface->parentSurface()) {
393+ // Wrap it in a Window so that we keep focusedWindow() up to date.
394+ Window *window = createWindow(surface);
395+ connect(surface, &QObject::destroyed, window, [=](){
396+ window->setSurface(nullptr);
397+ window->deleteLater();
398+ });
345399 } else {
346− auto *application = m_applicationManager->findApplicationWithSurface(surface);
347− if (application) {
348− prependSurface(surface, application);
400+ if (surface->type() == Mir::InputMethodType) {
401+ setInputMethodWindow(createWindow(surface));
349402 } else {
350− // 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.
352− // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
353− Window *promptWindow = createWindow(surface);
354− connect(surface, &QObject::destroyed, promptWindow, [=](){
355− promptWindow->setSurface(nullptr);
356− promptWindow->deleteLater();
357− });
403+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
404+ if (application) {
405+ prependSurface(surface, application);
406+ } else {
407+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
408+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
409+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
410+ Window *promptWindow = createWindow(surface);
411+ connect(surface, &QObject::destroyed, promptWindow, [=](){
412+ promptWindow->setSurface(nullptr);
413+ promptWindow->deleteLater();
414+ });
415+ }
358416 }
359417 }
360418 }
361419}
362420
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+
363474void TopLevelWindowModel::removeAt(int index)
364475{
365476 if (m_modelState == IdleState) {
@@ -376,6 +487,7 @@ void TopLevelWindowModel::removeAt(int index)
376487 window->setFocused(false);
377488
378489 m_windowModel.removeAt(index);
490+ m_allSurfaces.remove(window->surface());
379491
380492 if (m_modelState == RemovingState) {
381493 endRemoveRows();
@@ -674,3 +786,44 @@ void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
674786 }
675787 }
676788}
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 @@
2020#include <QAbstractListModel>
2121#include <QLoggingCategory>
2222
23+#include <memory>
24+
2325Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
2426
2527class Window;
28+class Workspace;
29+
30+namespace miral { class Workspace; }
2631
2732namespace unity {
2833 namespace shell {
@@ -70,6 +75,11 @@ class TopLevelWindowModel : public QAbstractListModel
7075 */
7176 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
7277
78+ Q_PROPERTY(Workspace* workspace
79+ READ workspace
80+ WRITE setWorkspace
81+ NOTIFY workspaceChanged)
82+
7383 Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
7484 READ surfaceManager
7585 WRITE setSurfaceManager
@@ -111,6 +121,9 @@ public:
111121
112122 // Own API
113123
124+ Workspace* workspace() const { return m_workspace; }
125+ void setWorkspace(Workspace* workspace);
126+
114127 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
115128 Window* focusedWindow() const;
116129
@@ -178,8 +191,14 @@ Q_SIGNALS:
178191
179192 void nextIdChanged();
180193
194+ void workspaceChanged(Workspace*);
195+
181196private Q_SLOTS:
182197 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);
183202 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
184203
185204 void onModificationsStarted();
@@ -219,6 +238,7 @@ private:
219238 void activateEmptyWindow(Window *window);
220239
221240 void activateTopMostWindowWithoutId(int forbiddenId);
241+ void refreshWindows();
222242
223243 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
224244
@@ -235,6 +255,9 @@ private:
235255 QVector<ModelEntry> m_windowModel;
236256 Window* m_inputMethodWindow{nullptr};
237257 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;
238261
239262 int m_nextId{1};
240263 // Just something big enough that we don't risk running out of unused id numbers.
plugins/WindowManager/WindowManagerPlugin.cpp+36−1
@@ -16,16 +16,51 @@
1616
1717#include "WindowManagerPlugin.h"
1818
19+#include "Screen.h"
20+#include "ScreenAttached.h"
21+#include "Screens.h"
22+#include "ScreenWindow.h"
1923#include "TopLevelWindowModel.h"
2024#include "Window.h"
25+#include "WorkspaceManager.h"
26+#include "Workspace.h"
27+#include "WorkspaceModel.h"
2128
2229#include <QtQml>
30+#include <qtmir/qtmir.h>
31+
32+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
33+
34+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
35+{
36+ Q_UNUSED(engine)
37+ Q_UNUSED(scriptEngine)
38+ return WorkspaceManager::instance();
39+}
40+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
41+ Q_UNUSED(engine);
42+ Q_UNUSED(scriptEngine);
43+ return new Screens(qtmir::get_screen_model());
44+}
2345
2446void WindowManagerPlugin::registerTypes(const char *uri)
2547{
2648 qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
49+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
50+ qmlRegisterUncreatableType<WorkspaceModel>(uri, 1, 0, "WorkspaceModel", notInstantiatable);
51+ qmlRegisterSingletonType<Screens>(uri, 1, 0, "Screens", screensSingleton);
52+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", notInstantiatable);
53+ qmlRegisterUncreatableType<Workspace>(uri, 1, 0, "Workspace", notInstantiatable);
2754
28− qRegisterMetaType<Window*>("Window*");
55+ qRegisterMetaType<Screen*>("Screen*");
56+ qRegisterMetaType<ScreensProxy*>("ScreensProxy*");
57+ qRegisterMetaType<Workspace*>("Workspace*");
2958
59+ qRegisterMetaType<Window*>("Window*");
3060 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
61+
62+ qmlRegisterType<ScreenWindow>(uri, 1, 0, "ScreenWindow");
63+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
64+
65+ qmlRegisterUncreatableType<WMScreen>(uri, 1, 0, "WMScreen", notInstantiatable);
3166}
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+220
@@ -0,0 +1,220 @@
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 workspaceInserted(index, 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+
82+ Q_EMIT workspaceMoved(from, to);
83+ }
84+}
85+
86+int WorkspaceModel::indexOf(Workspace *workspace) const
87+{
88+ return m_workspaces.indexOf(workspace);
89+}
90+
91+Workspace *WorkspaceModel::get(int index) const
92+{
93+ if (index < 0 || index >= rowCount()) return nullptr;
94+ return m_workspaces.at(index);
95+}
96+
97+int WorkspaceModel::rowCount(const QModelIndex &) const
98+{
99+ return m_workspaces.count();
100+}
101+
102+QVariant WorkspaceModel::data(const QModelIndex &index, int role) const
103+{
104+ if (index.row() < 0 || index.row() >= m_workspaces.size())
105+ return QVariant();
106+
107+ if (role == WorkspaceRole) {
108+ Workspace *workspace = m_workspaces.at(index.row());
109+ return QVariant::fromValue(workspace);
110+ } else {
111+ return QVariant();
112+ }
113+}
114+
115+void WorkspaceModel::sync(WorkspaceModel *proxy)
116+{
117+ if (!proxy) return;
118+ m_syncing = proxy;
119+ const auto& proxyList = proxy->list();
120+
121+ // check for removals
122+ int removedIndexWhichWasActive = -1;
123+ QVector<Workspace*> dpCpy(this->list());
124+ Q_FOREACH(auto workspace, dpCpy) {
125+
126+ bool found = false;
127+ Q_FOREACH(auto p, proxyList) {
128+ auto workspaceProxy = qobject_cast<WorkspaceProxy*>(p);
129+ if (workspaceProxy->proxyObject() == workspace) {
130+ found = true;
131+ break;
132+ }
133+ }
134+ if (!found) {
135+ if (workspace->isActive()) {
136+ removedIndexWhichWasActive = indexOf(workspace);
137+ }
138+ workspace->unassign();
139+ }
140+ }
141+
142+ // existing
143+ QSet<Workspace*> newWorkspaces;
144+ for (int i = 0; i < proxyList.count(); i++) {
145+ auto workspaceProxy = qobject_cast<WorkspaceProxy*>(proxyList[i]);
146+ auto workspace = workspaceProxy->proxyObject();
147+
148+ int oldIndex = this->indexOf(workspace);
149+
150+ if (oldIndex < 0) {
151+ workspace->assign(this, QVariant(i));
152+ } else if (oldIndex != i) {
153+ this->move(oldIndex, i);
154+ }
155+ newWorkspaces.insert(workspace);
156+ }
157+
158+ // Make sure we have at least one workspace in the model.
159+ if (rowCount() == 0) {
160+ Workspace* workspace = WorkspaceManager::instance()->createWorkspace();
161+ workspace->assign(this);
162+ (new WorkspaceProxy(workspace))->assign(proxy);
163+ }
164+
165+ if (removedIndexWhichWasActive != -1) {
166+ int newActiveIndex = qMin(removedIndexWhichWasActive, this->rowCount()-1);
167+ Workspace* newActiveWorkspace = newActiveIndex >= 0 ? this->get(newActiveIndex) : nullptr;
168+
169+ WorkspaceManager::instance()->setActiveWorkspace(newActiveWorkspace);
170+ }
171+ m_syncing = nullptr;
172+}
173+
174+bool WorkspaceModel::isSyncingWith(WorkspaceModel *m)
175+{
176+ return m_syncing == m;
177+}
178+
179+WorkspaceModelProxy::WorkspaceModelProxy(WorkspaceModel * const model)
180+ : m_original(model)
181+{
182+ Q_FOREACH(auto workspace, model->list()) {
183+ auto proxy = new WorkspaceProxy(workspace);
184+ QQmlEngine::setObjectOwnership(proxy, QQmlEngine::CppOwnership);
185+ proxy->assign(this);
186+ }
187+ connect(m_original, &WorkspaceModel::workspaceInserted, this, [this](int index, Workspace* inserted) {
188+ if (m_original->isSyncingWith(this)) return;
189+
190+ (new WorkspaceProxy(inserted))->assign(this, index);
191+ });
192+ connect(m_original, &WorkspaceModel::workspaceRemoved, this, [this](Workspace* removed) {
193+ if (m_original->isSyncingWith(this)) return;
194+
195+ for (int i = 0; i < rowCount(); i++) {
196+ auto workspaceProxy = qobject_cast<WorkspaceProxy*>(get(i));
197+ auto w = workspaceProxy->proxyObject();
198+ if (w == removed) {
199+ remove(workspaceProxy);
200+ break;
201+ }
202+ }
203+ });
204+ connect(m_original, &WorkspaceModel::workspaceMoved, this, [this](int from, int to) {
205+ if (m_original->isSyncingWith(this)) return;
206+
207+ move(from, to);
208+ });
209+}
210+
211+WorkspaceModelProxy::~WorkspaceModelProxy()
212+{
213+ qDeleteAll(m_workspaces.toList()); // make a copy so the list doesnt edit itself during delete.
214+ m_workspaces.clear();
215+}
216+
217+void WorkspaceModelProxy::move(int from, int to)
218+{
219+ WorkspaceModel::move(from, to);
220+}
plugins/WindowManager/WorkspaceModel.hnew file+91
@@ -0,0 +1,91 @@
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+ bool isSyncingWith(WorkspaceModel*);
65+
66+Q_SIGNALS:
67+ void countChanged();
68+
69+ void workspaceInserted(int index, Workspace *workspace);
70+ void workspaceRemoved(Workspace *workspace);
71+ void workspaceMoved(int from, int to);
72+
73+protected:
74+ QVector<Workspace*> m_workspaces;
75+ WorkspaceModel* m_syncing;
76+};
77+
78+class WorkspaceModelProxy : public WorkspaceModel
79+{
80+ Q_OBJECT
81+public:
82+ WorkspaceModelProxy(WorkspaceModel*const model);
83+ ~WorkspaceModelProxy();
84+
85+ Q_INVOKABLE void move(int from, int to) override;
86+
87+protected:
88+ const QPointer<WorkspaceModel> m_original;
89+};
90+
91+#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)
22
33install(FILES ${QML_JS_FILES}
44 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−pragma Singleton
1817import QtQuick 2.4
1918
2019QtObject {
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
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import Qt.labs.settings 1.0
21−import Unity.Screens 0.1
21+import WindowManager 1.0
2222import UInput 0.1
2323import "../Components"
2424
2525Item {
2626 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
3127
3228 Component.onCompleted: {
29+ UInput.createMouse();
3330 if (!settings.touchpadTutorialHasRun) {
3431 root.runTutorial()
3532 }
3633 }
34+ Component.onDestruction: UInput.removeMouse()
3735
3836 function runTutorial() {
3937 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
8583 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
8684 && clickTimer.running) {
8785 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
8987 isDoubleClick = true;
9088 }
9189 isClick = true;
@@ -104,7 +102,7 @@ Item {
104102
105103 onReleased: {
106104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108106 isDoubleClick = false;
109107 }
110108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120118 interval: 200
121119 property int button: UInput.ButtonLeft
122120 onTriggered: {
123− uinput.pressMouse(button);
124− uinput.releaseMouse(button);
121+ UInput.pressMouse(button);
122+ UInput.releaseMouse(button);
125123 }
126124 function scheduleClick(button) {
127125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138136 isDrag = true;
139137 }
140138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142140 }
143141
144142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166164 dh /= 2;
167165 dv /= 2;
168166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170168 }
171169
172170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189187 objectName: "leftButton"
190188 Layout.fillWidth: true
191189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194192 property bool highlight: false
195193 UbuntuShape {
196194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204202 objectName: "rightButton"
205203 Layout.fillWidth: true
206204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209207 property bool highlight: false
210208 UbuntuShape {
211209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239237 }
240238 }
241239
242− Screens {
243− id: screens
244− }
245−
246240 InputMethod {
247241 id: inputMethod
248242 // 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
250244 objectName: "inputMethod"
251245 anchors.fill: parent
252246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
2828 property var screen: Screen
2929 property var orientationLock: OrientationLock
3030
31+ property alias deviceConfiguration: _deviceConfiguration
3132 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
3334 name: applicationArguments.deviceName
3435 }
3536
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
2525import Unity.Session 0.1
2626
2727import "." 0.1
28+import ".." 0.1
2829import "../Components"
2930
3031Showable {
@@ -227,7 +228,7 @@ Showable {
227228 if (forcedUnlock && shown) {
228229 hideView();
229230 if (hideNow) {
230− root.hideNow(); // skip hide animation
231+ ShellNotifier.greeter.hide(true); // skip hide animation
231232 }
232233 }
233234 }
@@ -375,7 +376,7 @@ Showable {
375376 onEmergencyCall: root.emergencyCall()
376377 onRequiredChanged: {
377378 if (!loader.item.required) {
378− root.hide();
379+ ShellNotifier.greeter.hide(false);
379380 }
380381 }
381382 }
@@ -498,12 +499,29 @@ Showable {
498499 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499500 }
500501
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+
501519 Connections {
502520 target: DBusUnitySessionService
503521 onLockRequested: root.forceShow()
504522 onUnlocked: {
505523 root.forcedUnlock = true;
506− root.hideNow();
524+ ShellNotifier.greeter.hide(true);
507525 }
508526 }
509527
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
1818import QtQuick.Window 2.2
1919import Unity.InputInfo 0.1
2020import Unity.Session 0.1
21−import Unity.Screens 0.1
21+import WindowManager 1.0
2222import Utils 0.1
2323import GSettings 1.0
2424import "Components"
@@ -32,15 +32,16 @@ Item {
3232 implicitWidth: units.gu(40)
3333 implicitHeight: units.gu(71)
3434
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+
3538 onWidthChanged: calculateUsageMode();
3639
3740 DeviceConfiguration {
38− id: deviceConfiguration
41+ id: _deviceConfiguration
3942 name: applicationArguments.deviceName
4043 }
4144
42− property alias orientations: d.orientations
43−
4445 Item {
4546 id: d
4647
@@ -157,10 +158,6 @@ Item {
157158 return false;
158159 }
159160
160− Screens {
161− id: screens
162− }
163−
164161 property int orientation
165162 onPhysicalOrientationChanged: {
166163 if (!orientationLocked) {
@@ -258,7 +255,7 @@ Item {
258255
259256 Shell {
260257 id: shell
261− objectName: "shell"
258+ objectName: "shell-"+screenWindow.objectName
262259 width: root.width
263260 height: root.height
264261 orientation: root.angleToOrientation(orientationAngle)
@@ -272,7 +269,7 @@ Item {
272269 // have multiple keyboards around. For now we only enable one keyboard at a time
273270 // thus hiding it here if there is a physical one around or if we have a second
274271 // 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) ||
276273 forceOSKEnabled
277274
278275 usageScenario: {
qml/Panel/Panel.qml+14−14
@@ -47,8 +47,8 @@ Item {
4747 property bool fullscreenMode: false
4848 property real panelAreaShowProgress: 1.0
4949 property bool greeterShown: false
50−
5150 property string mode: "staged"
51+ property PanelState panelState
5252
5353 MouseArea {
5454 id: backMouseEater
@@ -65,14 +65,14 @@ Item {
6565 }
6666
6767 Binding {
68− target: PanelState
68+ target: panelState
6969 property: "panelHeight"
7070 value: minimizedPanelHeight
7171 }
7272
7373 RegisteredApplicationMenuModel {
7474 id: registeredMenuModel
75− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
7676 }
7777
7878 QtObject {
@@ -83,11 +83,11 @@ Item {
8383 !indicators.shown &&
8484 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
8585
86− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
87− PanelState.decorationsAlwaysVisible
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
87+ panelState.decorationsAlwaysVisible
8888
8989 property bool showPointerMenu: revealControls &&
90− (PanelState.decorationsVisible || mode == "staged")
90+ (panelState.decorationsVisible || mode == "staged")
9191
9292 property bool enablePointerMenu: revealControls &&
9393 applicationMenus.available &&
@@ -139,7 +139,7 @@ Item {
139139 fill: panelAreaBackground
140140 bottomMargin: -units.gu(1)
141141 }
142− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
143143 source: "graphics/rectangular_dropshadow.sci"
144144 }
145145
@@ -192,12 +192,12 @@ Item {
192192 visible: opacity != 0
193193 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
194194
195− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
195+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
196196 windowIsMaximized: true
197− onCloseClicked: PanelState.closeClicked()
198− onMinimizeClicked: PanelState.minimizeClicked()
199− onMaximizeClicked: PanelState.restoreClicked()
200− closeButtonShown: PanelState.closeButtonShown
197+ onCloseClicked: panelState.closeClicked()
198+ onMinimizeClicked: panelState.minimizeClicked()
199+ onMaximizeClicked: panelState.restoreClicked()
200+ closeButtonShown: panelState.closeButtonShown
201201 }
202202
203203 Loader {
@@ -219,7 +219,7 @@ Item {
219219 anchors.left: parent.left
220220 anchors.margins: units.gu(1)
221221 height: menuBarLoader.height
222− enableKeyFilter: valid && PanelState.decorationsVisible
222+ enableKeyFilter: valid && panelState.decorationsVisible
223223 unityMenuModel: __applicationMenus.model
224224
225225 Connections {
@@ -267,7 +267,7 @@ Item {
267267 }
268268
269269 showRowTitle: !expanded
270− rowTitle: PanelState.title
270+ rowTitle: panelState.title
271271 rowItemDelegate: ActionItem {
272272 id: actionItem
273273 property int ownIndex: index
qml/Shell.qml+50−12
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
@@ -38,12 +38,14 @@ import "Notifications"
3838import "Stage"
3939import "Tutorial"
4040import "Wizard"
41+import "Components/PanelState"
4142import Unity.Notifications 1.0 as NotificationBackend
4243import Unity.Session 0.1
4344import Unity.DashCommunicator 0.1
4445import Unity.Indicators 0.1 as Indicators
4546import Cursor 1.1
4647import WindowManager 1.0
48+import Unity.Debug 0.1 as Debug
4749
4850
4951StyledItem {
@@ -185,6 +187,7 @@ StyledItem {
185187 if (ApplicationManager.findApplication(appId)) {
186188 ApplicationManager.requestFocusApplication(appId);
187189 } else {
190+ print("calling startApplication. active workspace is", WorkspaceManager.activeWorkspace)
188191 ApplicationManager.startApplication(appId);
189192 }
190193 }
@@ -259,21 +262,23 @@ StyledItem {
259262 schema.id: "com.canonical.Unity8"
260263 }
261264
265+ PanelState {
266+ id: panelState
267+ objectName: "panelState"
268+ }
269+
262270 Item {
263271 id: stages
264272 objectName: "stages"
265273 width: parent.width
266274 height: parent.height
267275
268− SurfaceManager {
269− id: surfaceMan
270− objectName: "surfaceManager"
271− }
272276 TopLevelWindowModel {
273277 id: topLevelSurfaceList
274278 objectName: "topLevelSurfaceList"
275279 applicationManager: ApplicationManager // it's a singleton
276− surfaceManager: surfaceMan
280+ surfaceManager: SurfaceManager
281+ workspace: screenWindow.screen.currentWorkspace
277282 }
278283
279284 Stage {
@@ -315,6 +320,7 @@ StyledItem {
315320 altTabPressed: physicalKeysMapper.altTabPressed
316321 oskEnabled: shell.oskEnabled
317322 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
323+ panelState: panelState
318324
319325 onSpreadShownChanged: {
320326 panel.indicators.hide();
@@ -369,8 +375,13 @@ StyledItem {
369375 objectName: "greeterLoader"
370376 anchors.fill: parent
371377 anchors.topMargin: panel.panelHeight
372− sourceComponent: shell.mode != "shell" ? integratedGreeter :
373− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
378+ sourceComponent: {
379+ if (shell.mode != "shell") {
380+ if (screenWindow.primary) return integratedGreeter;
381+ return secondaryGreeter;
382+ }
383+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
384+ }
374385 onLoaded: {
375386 item.objectName = "greeter"
376387 }
@@ -420,6 +431,13 @@ StyledItem {
420431 }
421432 }
422433
434+ Component {
435+ id: secondaryGreeter
436+ SecondaryGreeter {
437+ hides: [launcher, panel.indicators]
438+ }
439+ }
440+
423441 Timer {
424442 // See powerConnection for why this is useful
425443 id: showGreeterDelayed
@@ -533,6 +551,7 @@ StyledItem {
533551 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
534552 || greeter.hasLockedApp
535553 greeterShown: greeter && greeter.shown
554+ panelState: panelState
536555 }
537556
538557 Launcher {
@@ -772,12 +791,13 @@ StyledItem {
772791
773792 Cursor {
774793 id: cursor
775− objectName: "cursor"
776− visible: shell.hasMouse
794+ objectName: "Pointer-"+screenWindow.objectName
795+
777796 z: itemGrabber.z + 1
797+ opacity: 0
778798 topBoundaryOffset: panel.panelHeight
779−
780− confiningItem: stage.itemConfiningMouseCursor
799+ enabled: shell.hasMouse && screenWindow.active
800+ visible: enabled
781801
782802 property bool mouseNeverMoved: true
783803 Binding {
@@ -789,6 +809,8 @@ StyledItem {
789809 when: cursor.mouseNeverMoved && cursor.visible
790810 }
791811
812+ confiningItem: stage.itemConfiningMouseCursor
813+
792814 height: units.gu(3)
793815
794816 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -875,4 +897,20 @@ StyledItem {
875897 }
876898 }
877899 }
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+ }
878916}
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 {
7272
7373 readonly property Item clientAreaItem: applicationWindow
7474
75+ property PanelState panelState
76+
7577 signal closeClicked()
7678 signal maximizeClicked()
7779 signal maximizeHorizontallyClicked()
@@ -225,7 +227,7 @@ FocusScope {
225227 enableMenus: {
226228 return active &&
227229 surface &&
228− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
230+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
229231 }
230232 menu: sharedAppModel.model
231233
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
3535 property int leftMargin
3636 property real appContainerWidth
3737 property real appContainerHeight
38+ property PanelState panelState
3839
3940 readonly property real hintThreshold: 0.1
4041
@@ -178,7 +179,7 @@ Rectangle {
178179 ParallelAnimation {
179180 id: fakeMaximizeAnimation
180181 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 }
182183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184185 }
@@ -186,39 +187,39 @@ Rectangle {
186187 ParallelAnimation {
187188 id: fakeMaximizeLeftAnimation
188189 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 }
190191 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 }
192193 }
193194
194195 ParallelAnimation {
195196 id: fakeMaximizeRightAnimation
196197 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 }
198199 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 }
200201 }
201202
202203 ParallelAnimation {
203204 id: fakeMaximizeTopLeftAnimation
204205 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 }
206207 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 }
208209 }
209210
210211 ParallelAnimation {
211212 id: fakeMaximizeTopRightAnimation
212213 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 }
214215 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 }
216217 }
217218
218219 ParallelAnimation {
219220 id: fakeMaximizeBottomLeftAnimation
220221 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 }
222223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224225 }
@@ -226,7 +227,7 @@ Rectangle {
226227 ParallelAnimation {
227228 id: fakeMaximizeBottomRightAnimation
228229 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 }
230231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232233 }
qml/Stage/Spread/ScreensAndWorkspaces.qmlnew file+179
@@ -0,0 +1,179 @@
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+
30+ UbuntuShape {
31+ id: header
32+ anchors { left: parent.left; top: parent.top; right: parent.right }
33+ height: units.gu(7)
34+ backgroundColor: "white"
35+ z: 1
36+
37+ DropArea {
38+ anchors.fill: parent
39+ keys: ["workspace"]
40+
41+ onEntered: {
42+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
43+ drag.source.inDropArea = true;
44+ }
45+
46+ onExited: {
47+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
48+ drag.source.inDropArea = false;
49+ }
50+
51+ onDropped: {
52+ drag.source.inDropArea = false;
53+ }
54+ }
55+
56+ Column {
57+ anchors.fill: parent
58+ anchors.margins: units.gu(1)
59+
60+ Label {
61+ text: model.screen.name
62+ color: "black"
63+ }
64+
65+ Label {
66+ text: model.screen.outputTypeName
67+ color: "black"
68+ fontSize: "x-small"
69+ }
70+
71+ Label {
72+ text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
73+ color: "black"
74+ fontSize: "x-small"
75+ }
76+ }
77+
78+ MouseArea {
79+ anchors.fill: parent
80+
81+ onClicked: {
82+ PopupUtils.open(contextMenu)
83+ }
84+ }
85+
86+ Component {
87+ id: contextMenu
88+ ActionSelectionPopover {
89+ actions: ActionList {
90+ Action {
91+ text: "Add workspace"
92+ onTriggered: {
93+ screen.addWorkspace();
94+ Screens.sync(root.screensProxy)
95+ }
96+ }
97+ }
98+ }
99+ }
100+ }
101+
102+
103+ Workspaces {
104+ id: workspaces
105+ height: parent.height - header.height - units.gu(2)
106+ width: {
107+ if (screensProxy.count == 1) {
108+ return Math.min(implicitWidth, root.width - units.gu(8));
109+ }
110+ return Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
111+ }
112+
113+ Behavior on width { UbuntuNumberAnimation {} }
114+ anchors.bottom: parent.bottom
115+ anchors.bottomMargin: units.gu(1)
116+ anchors.horizontalCenter: parent.horizontalCenter
117+ screen: model.screen
118+ background: root.background
119+
120+ workspaceModel: model.screen.workspaces
121+
122+ onCommitScreenSetup: Screens.sync(root.screensProxy)
123+ }
124+ }
125+ }
126+ }
127+
128+ Rectangle {
129+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
130+ width: units.gu(5)
131+ color: "#33000000"
132+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
133+ MouseArea {
134+ id: leftScrollArea
135+ anchors.fill: parent
136+ hoverEnabled: true
137+ onPressed: mouse.accepted = false;
138+ }
139+ DropArea {
140+ id: leftFakeDropArea
141+ anchors.fill: parent
142+ keys: ["application", "workspace"]
143+ }
144+ }
145+ Rectangle {
146+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
147+ width: units.gu(5)
148+ color: "#33000000"
149+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
150+ MouseArea {
151+ id: rightScrollArea
152+ anchors.fill: parent
153+ hoverEnabled: true
154+ onPressed: mouse.accepted = false;
155+ }
156+ DropArea {
157+ id: rightFakeDropArea
158+ anchors.fill: parent
159+ keys: ["application", "workspace"]
160+ }
161+ }
162+ Timer {
163+ repeat: true
164+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
165+ interval: UbuntuAnimation.SlowDuration
166+ triggeredOnStart: true
167+ onTriggered: {
168+ var newOffset = row.anchors.horizontalCenterOffset;
169+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
170+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
171+ newOffset += units.gu(20)
172+ } else {
173+ newOffset -= units.gu(20)
174+ }
175+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
176+ row.anchors.horizontalCenterOffset = newOffset;
177+ }
178+ }
179+}
qml/Stage/Spread/Spread.qml+6−7
@@ -28,10 +28,10 @@ Item {
2828 property var spreadFlickable
2929
3030 // some config options
31− property real contentMargin: 0.16 * root.height
32− property real contentTopMargin: contentMargin
33− property real contentBottomMargin: 0.35 * contentMargin
34− property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
31+ property real contentMargin: 0.1 * root.height
32+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
33+ property real contentBottomMargin: contentMargin
34+ property real windowTitleTopMargin: contentMargin - windowTitle.height
3535 property int stackItemCount: 3
3636 property real leftRotationAngle: 22
3737 property real rightRotationAngle: 32
@@ -51,7 +51,7 @@ Item {
5151
5252 readonly property real spreadWidth: rightStackXPos - leftStackXPos
5353 readonly property real spreadHeight: root.height
54− readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
54+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
5555 readonly property real spreadItemWidth: stackHeight
5656
5757 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
@@ -93,8 +93,7 @@ Item {
9393
9494 readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
9595
96− readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth ,0) / (2 * spreadWidth)
97−
96+ readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + (leftStackXPos - leftMargin) * 2, 0) / (2 * spreadWidth)
9897
9998 readonly property var curve: BezierCurve {
10099 controlPoint2: {'x': 0.19, 'y': 0.00}
qml/Stage/Spread/SpreadDelegateInputArea.qml+26−11
@@ -85,15 +85,15 @@ Item {
8585 }
8686
8787 // Event eater
88− MouseArea {
89− anchors.fill: parent
90− onClicked: root.clicked()
91− onWheel: wheel.accepted = true
92− }
88+// MouseArea {
89+// anchors.fill: parent
90+// onClicked: root.clicked()
91+// onWheel: wheel.accepted = true
92+// }
9393
9494 MultiPointTouchArea {
9595 anchors.fill: parent
96− mouseEnabled: false
96+// mouseEnabled: false
9797 maximumTouchPoints: 1
9898 property int offset: 0
9999
@@ -109,7 +109,7 @@ Item {
109109 }
110110
111111 onTouchUpdated: {
112− if (!d.moving) {
112+ if (!d.moving || !tp.pressed) {
113113 if (Math.abs(tp.startY - tp.y) > d.threshold) {
114114 d.moving = true;
115115 d.dragEvents = []
@@ -119,17 +119,32 @@ Item {
119119 }
120120 }
121121
122− if (root.closeable) {
123− d.distance = tp.y - tp.startY - offset
122+
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+
124133 } else {
125− var value = tp.y - tp.startY - offset;
126− d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
134+ if (root.closeable) {
135+ d.distance = value
136+ } else {
137+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
138+ }
127139 }
128140
129141 d.pushDragEvent(tp);
130142 }
131143
132144 onReleased: {
145+ print("released!")
146+ fakeDragItem.surface = null;
147+
133148 if (!d.moving) {
134149 root.clicked()
135150 }
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+343
@@ -0,0 +1,343 @@
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+ boundsBehavior: Flickable.StopAtBounds
67+
68+ orientation: ListView.Horizontal
69+ spacing: units.gu(1)
70+ leftMargin: itemWidth
71+ rightMargin: itemWidth
72+
73+ property var screenSize: screen.availableModes[screen.currentModeIndex].size
74+ property int itemWidth: height * screenSize.width / screenSize.height
75+ property int foldingAreaWidth: units.gu(10)
76+ property real realContentX: contentX - originX + leftMargin
77+ property int dropItemIndex: -1
78+ property int hoveredWorkspaceIndex: -1
79+
80+ function getDropIndex(drag) {
81+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
82+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
83+ if (index < 0) index = 0;
84+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
85+ if (index > upperLimit) index = upperLimit;
86+ return index;
87+ }
88+
89+ function progressiveScroll(mouseX) {
90+ var progress = Math.max(0, Math.min(1, (mouseX - listView.foldingAreaWidth) / (width - listView.leftMargin * 2 - listView.foldingAreaWidth * 2)))
91+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
92+ }
93+
94+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
95+
96+ delegate: Item {
97+ id: workspaceDelegate
98+ objectName: "delegate" + index
99+ height: parent.height
100+ width: listView.itemWidth
101+ Behavior on width { UbuntuNumberAnimation {} }
102+ visible: listView.dropItemIndex !== index
103+
104+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
105+ property int distanceFromLeft: itemX //- listView.leftMargin
106+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
107+
108+ property int maxAngle: 40
109+
110+ property int itemAngle: {
111+ if (index == 0) {
112+ if (distanceFromLeft < 0) {
113+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
114+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
115+ }
116+ return 0
117+ }
118+ if (index == listView.count - 1) {
119+ if (distanceFromRight < 0) {
120+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
121+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
122+ }
123+ return 0
124+ }
125+
126+ if (distanceFromLeft < listView.foldingAreaWidth) {
127+ // itemX : 10gu = p : 100
128+ var progress = distanceFromLeft / listView.foldingAreaWidth
129+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
130+ }
131+ if (distanceFromRight < listView.foldingAreaWidth) {
132+ var progress = distanceFromRight / listView.foldingAreaWidth
133+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
134+ }
135+ return 0
136+ }
137+
138+ property int itemOffset: {
139+ var rotationOffset = (1 - Math.cos(itemAngle * Math.PI / 360)) * listView.itemWidth
140+ if (index == 0) {
141+ if (distanceFromLeft < 0) {
142+ return -distanceFromLeft - rotationOffset
143+ }
144+ return 0
145+ }
146+ if (index == listView.count - 1) {
147+ if (distanceFromRight < 0) {
148+ return distanceFromRight + rotationOffset
149+ }
150+ return 0
151+ }
152+
153+ if (itemX < -listView.foldingAreaWidth) {
154+ return -itemX - rotationOffset
155+ }
156+ if (distanceFromLeft < listView.foldingAreaWidth) {
157+ return (listView.foldingAreaWidth - distanceFromLeft) / 2 - rotationOffset
158+ }
159+
160+ if (distanceFromRight < -listView.foldingAreaWidth) {
161+ return distanceFromRight + rotationOffset
162+ }
163+
164+ if (distanceFromRight < listView.foldingAreaWidth) {
165+ return -(listView.foldingAreaWidth - distanceFromRight) / 2 + rotationOffset
166+ }
167+
168+ return 0
169+ }
170+
171+// onItemXChanged: if (index == 1) print("x", itemX, listView.contentX)
172+
173+ z: itemOffset < 0 ? itemOffset : -itemOffset
174+ transform: [
175+ Rotation {
176+ angle: itemAngle
177+ axis { x: 0; y: 1; z: 0 }
178+ origin { x: itemAngle < 0 ? listView.itemWidth : 0; y: height / 2 }
179+ },
180+ Translate {
181+ x: itemOffset
182+ }
183+ ]
184+
185+ WorkspacePreview {
186+ height: listView.height
187+ width: listView.itemWidth
188+ background: root.background
189+ screenHeight: screen.availableModes[screen.currentModeIndex].size.height
190+ containsDrag: listView.hoveredWorkspaceIndex == index
191+ }
192+ MouseArea {
193+ anchors.fill: parent
194+ onClicked: {
195+ screenWindow.screen.currentWorkspace = model.workspace
196+ }
197+ }
198+
199+ MouseArea {
200+ id: closeMouseArea
201+ objectName: "closeMouseArea"
202+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 }
203+ hoverEnabled: true
204+ height: units.gu(4)
205+ width: height
206+
207+ onClicked: {
208+ model.workspace.unassign();
209+ root.commitScreenSetup();
210+ }
211+ Image {
212+ id: closeImage
213+ source: "../graphics/window-close.svg"
214+ anchors.fill: closeMouseArea
215+ anchors.margins: units.gu(1)
216+ sourceSize.width: width
217+ sourceSize.height: height
218+ readonly property var mousePos: hoverMouseArea.mapToItem(workspaceDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
219+ readonly property bool shown: (hoverMouseArea.containsMouse || parent.containsMouse)
220+ && mousePos.y < workspaceDelegate.width / 4
221+ && mousePos.y > -units.gu(2)
222+ && mousePos.x > -units.gu(2)
223+ && mousePos.x < workspaceDelegate.height / 4
224+ opacity: shown ? 1 : 0
225+ visible: opacity > 0
226+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
227+
228+ }
229+ }
230+ }
231+
232+ MouseArea {
233+ id: hoverMouseArea
234+ anchors.fill: parent
235+ hoverEnabled: true
236+ propagateComposedEvents: true
237+ anchors.leftMargin: listView.leftMargin
238+ anchors.rightMargin: listView.rightMargin
239+
240+ property int draggedIndex: -1
241+
242+ property int startX: 0
243+ property int startY: 0
244+
245+ onMouseXChanged: {
246+ if (!pressed || dragging) {
247+ listView.progressiveScroll(mouseX)
248+ }
249+ }
250+ onMouseYChanged: {
251+ if (Math.abs(mouseY - startY) > units.gu(3)) {
252+ drag.axis = Drag.XAndYAxis;
253+ }
254+ }
255+
256+ onReleased: {
257+ var result = fakeDragItem.Drag.drop();
258+// if (result == Qt.IgnoreAction) {
259+// WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
260+// }
261+ root.commitScreenSetup();
262+ drag.target = null;
263+ }
264+
265+ property bool dragging: drag.active
266+ onDraggingChanged: {
267+ if (drag.active) {
268+ print("drai", draggedIndex)
269+ var ws = listView.model.get(draggedIndex);
270+ print("ws:", ws)
271+ ws.unassign();
272+// listView.model.remove(draggedIndex, 1)
273+ }
274+ }
275+
276+ onPressed: {
277+ startX = mouseX;
278+ startY = mouseY;
279+ if (listView.model.count < 2) return;
280+
281+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
282+ draggedIndex = listView.indexAt(coords.x, coords.y)
283+ var clickedItem = listView.itemAt(coords.x, coords.y)
284+
285+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
286+ fakeDragItem.x = itemCoords.x
287+ fakeDragItem.y = itemCoords.y
288+ fakeDragItem.workspace = listView.model.get(draggedIndex)
289+
290+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
291+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
292+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
293+
294+ drag.axis = Drag.YAxis;
295+ drag.target = fakeDragItem;
296+ }
297+
298+ WorkspacePreview {
299+ id: fakeDragItem
300+ height: listView.height
301+ width: listView.itemWidth
302+ background: root.background
303+ screenHeight: screen.availableModes[screen.currentModeIndex].size.height
304+ visible: Drag.active
305+
306+ Drag.active: hoverMouseArea.drag.active
307+ Drag.keys: ['workspace']
308+
309+ property var workspace
310+ property bool inDropArea: false
311+
312+ Rectangle {
313+ anchors.fill: parent
314+ color: "#33000000"
315+ opacity: parent.inDropArea ? 0 : 1
316+ Behavior on opacity { UbuntuNumberAnimation { } }
317+ Rectangle {
318+ anchors.centerIn: parent
319+ width: units.gu(6)
320+ height: units.gu(6)
321+ radius: width / 2
322+ color: "#aa000000"
323+ }
324+
325+ Icon {
326+ height: units.gu(3)
327+ width: height
328+ anchors.centerIn: parent
329+ name: "edit-delete"
330+ color: "white"
331+ }
332+ }
333+
334+ states: [
335+ State {
336+ when: fakeDragItem.Drag.active
337+ ParentChange { target: fakeDragItem; parent: shell }
338+ }
339+ ]
340+ }
341+ }
342+ }
343+}
qml/Stage/Stage.qml+142−104
@@ -47,6 +47,7 @@ FocusScope {
4747 property bool oskEnabled: false
4848 property rect inputMethodRect
4949 property real rightEdgePushProgress: 0
50+ property PanelState panelState
5051
5152 // Configuration
5253 property string mode: "staged"
@@ -349,23 +350,23 @@ FocusScope {
349350 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
350351 }
351352
352− Component.onCompleted: priv.updateMainAndSideStageIndexes();
353+ Component.onCompleted: {priv.updateMainAndSideStageIndexes(); print("size", root.width, root.height)}
353354
354355 Connections {
355− target: PanelState
356+ target: panelState
356357 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
357358 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
358359 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
359360 }
360361
361362 Binding {
362− target: PanelState
363+ target: panelState
363364 property: "decorationsVisible"
364365 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
365366 }
366367
367368 Binding {
368− target: PanelState
369+ target: panelState
369370 property: "title"
370371 value: {
371372 if (priv.focusedAppDelegate !== null) {
@@ -380,7 +381,7 @@ FocusScope {
380381 }
381382
382383 Binding {
383− target: PanelState
384+ target: panelState
384385 property: "focusedPersistentSurfaceId"
385386 value: {
386387 if (priv.focusedAppDelegate !== null) {
@@ -394,21 +395,21 @@ FocusScope {
394395 }
395396
396397 Binding {
397− target: PanelState
398+ target: panelState
398399 property: "dropShadow"
399400 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
400401 }
401402
402403 Binding {
403− target: PanelState
404+ target: panelState
404405 property: "closeButtonShown"
405406 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
406407 }
407408
408409 Component.onDestruction: {
409− PanelState.title = "";
410− PanelState.decorationsVisible = false;
411− PanelState.dropShadow = false;
410+ panelState.title = "";
411+ panelState.decorationsVisible = false;
412+ panelState.dropShadow = false;
412413 }
413414
414415 Instantiator {
@@ -454,6 +455,7 @@ FocusScope {
454455 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
455456 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
456457 PropertyChanges { target: wallpaper; visible: false }
458+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
457459 },
458460 State {
459461 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
@@ -502,10 +504,13 @@ FocusScope {
502504 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
503505 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
504506 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
507+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
505508 },
506509 Transition {
507510 to: "spread"
508511 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 }
509514 },
510515 Transition {
511516 from: "spread"
@@ -563,10 +568,19 @@ FocusScope {
563568 visible: false
564569 }
565570
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+
566580 Spread {
567581 id: spreadItem
568582 objectName: "spreadItem"
569− anchors.fill: appContainer
583+ anchors { left: parent.left; bottom: parent.bottom; right: parent.right; top: screensAndWorkspaces.bottom }
570584 leftMargin: root.leftMargin
571585 model: root.topLevelSurfaceList
572586 spreadFlickable: floatingFlickable
@@ -575,6 +589,83 @@ FocusScope {
575589 onLeaveSpread: {
576590 priv.goneToSpread = false;
577591 }
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+ }
578669 }
579670
580671 Connections {
@@ -669,10 +760,27 @@ FocusScope {
669760 }
670761 }
671762
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+
672780 Item {
673781 id: boundariesForWindowPlacement
674782 anchors.fill: parent
675− anchors.topMargin: PanelState.panelHeight
783+ anchors.topMargin: panelState.panelHeight
676784 visible: false
677785 }
678786
@@ -704,6 +812,9 @@ FocusScope {
704812 }
705813 z: normalZ
706814
815+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
816+ Behavior on opacity { UbuntuNumberAnimation {} }
817+
707818 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
708819 // match what the actual surface size is.
709820 // Don't write to those, they will be set by states
@@ -764,7 +875,7 @@ FocusScope {
764875 target: appDelegate
765876 property: "y"
766877 value: appDelegate.requestedY -
767− Math.min(appDelegate.requestedY - PanelState.panelHeight,
878+ Math.min(appDelegate.requestedY - panelState.panelHeight,
768879 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
769880 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
770881 && root.inputMethodRect.height > 0
@@ -1148,7 +1259,7 @@ FocusScope {
11481259 progress: 0
11491260 targetHeight: spreadItem.stackHeight
11501261 targetX: spreadMaths.targetX
1151− startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1262+ startY: appDelegate.fullscreen ? 0 : panelState.panelHeight
11521263 targetY: spreadMaths.targetY
11531264 targetAngle: spreadMaths.targetAngle
11541265 targetScale: spreadMaths.targetScale
@@ -1267,9 +1378,9 @@ FocusScope {
12671378 PropertyChanges {
12681379 target: appDelegate
12691380 x: stageMaths.itemX
1270− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1381+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12711382 requestedWidth: appContainer.width
1272− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1383+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
12731384 visuallyMaximized: true
12741385 visible: appDelegate.x < root.width
12751386 }
@@ -1295,10 +1406,10 @@ FocusScope {
12951406 PropertyChanges {
12961407 target: appDelegate
12971408 x: stageMaths.itemX
1298− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1409+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12991410 z: stageMaths.itemZ
13001411 requestedWidth: stageMaths.itemWidth
1301− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1412+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
13021413 visuallyMaximized: true
13031414 visible: appDelegate.x < root.width
13041415 }
@@ -1363,9 +1474,9 @@ FocusScope {
13631474 PropertyChanges {
13641475 target: appDelegate
13651476 windowedX: root.leftMargin
1366− windowedY: PanelState.panelHeight
1477+ windowedY: panelState.panelHeight
13671478 windowedWidth: (appContainer.width - root.leftMargin)/2
1368− windowedHeight: appContainer.height - PanelState.panelHeight
1479+ windowedHeight: appContainer.height - panelState.panelHeight
13691480 }
13701481 },
13711482 State {
@@ -1382,9 +1493,9 @@ FocusScope {
13821493 PropertyChanges {
13831494 target: appDelegate
13841495 windowedX: root.leftMargin
1385− windowedY: PanelState.panelHeight
1496+ windowedY: panelState.panelHeight
13861497 windowedWidth: (appContainer.width - root.leftMargin)/2
1387− windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1498+ windowedHeight: (appContainer.height - panelState.panelHeight)/2
13881499 }
13891500 },
13901501 State {
@@ -1401,7 +1512,7 @@ FocusScope {
14011512 PropertyChanges {
14021513 target: appDelegate
14031514 windowedX: root.leftMargin
1404− windowedY: (appContainer.height + PanelState.panelHeight)/2
1515+ windowedY: (appContainer.height + panelState.panelHeight)/2
14051516 windowedWidth: (appContainer.width - root.leftMargin)/2
14061517 windowedHeight: appContainer.height/2
14071518 }
@@ -1423,8 +1534,8 @@ FocusScope {
14231534 State {
14241535 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
14251536 extend: "normal"
1426− PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1427− windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1537+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: panelState.panelHeight;
1538+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - panelState.panelHeight }
14281539 },
14291540 State {
14301541 name: "minimized"; when: appDelegate.minimized
@@ -1540,7 +1651,7 @@ FocusScope {
15401651 ]
15411652
15421653 Binding {
1543− target: PanelState
1654+ target: panelState
15441655 property: "decorationsAlwaysVisible"
15451656 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
15461657 }
@@ -1718,12 +1829,15 @@ FocusScope {
17181829 objectName: "closeMouseArea"
17191830 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
17201831 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1721− visible: !appDelegate.isDash && dragArea.distance == 0
1832+ readonly property bool shown: !appDelegate.isDash && dragArea.distance == 0
17221833 && index == spreadItem.highlightedIndex
17231834 && mousePos.y < (decoratedWindow.height / 3)
17241835 && mousePos.y > -units.gu(4)
17251836 && mousePos.x > -units.gu(4)
17261837 && mousePos.x < (decoratedWindow.width * 2 / 3)
1838+ opacity: shown ? 1 : 0
1839+ visible: opacity > 0
1840+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
17271841 height: units.gu(6)
17281842 width: height
17291843
@@ -1785,83 +1899,7 @@ FocusScope {
17851899 leftMargin: root.leftMargin
17861900 appContainerWidth: appContainer.width
17871901 appContainerHeight: appContainer.height
1788− }
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"}
1902+ panelState: root.panelState
18651903 }
18661904
18671905 PropertyAnimation {
qml/Stage/SurfaceContainer.qml+2
@@ -66,6 +66,8 @@ FocusScope {
6666 fillMode: MirSurfaceItem.PadOrCrop
6767 consumesInput: true
6868
69+ onSurfaceChanged: print("surface changed", surface, implicitWidth, implicitHeight)
70+
6971 surfaceWidth: root.requestedWidth
7072 surfaceHeight: root.requestedHeight
7173
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 @@
1616
1717#include "ApplicationArguments.h"
1818
19−ApplicationArguments::ApplicationArguments(QObject *parent)
20− : QObject(parent)
19+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
20+ : QObject(app)
21+ , UnityCommandLineParser(*app)
2122{
2223}
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 @@
2323#include <QSize>
2424#include <QString>
2525
26−class ApplicationArguments : public QObject
26+#include "UnityCommandLineParser.h"
27+
28+class ApplicationArguments : public QObject,
29+ public UnityCommandLineParser
2730{
2831 Q_OBJECT
2932 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3033 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+
3144public:
32− ApplicationArguments(QObject *parent = nullptr);
45+ ApplicationArguments(QCoreApplication *app);
3346
34− 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; }
47+ void setDeviceName(const QString &deviceName);
4148
42− void setMode(const QString &mode) { m_mode = mode; }
43− QString mode() const { return m_mode; }
49+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
4450
4551Q_SIGNALS:
4652 void deviceNameChanged(const QString&);
47−
48−private:
49− QString m_deviceName;
50− QString m_mode;
5153};
5254
5355#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt+10−4
@@ -7,6 +7,7 @@ include_directories(
77 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
88 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
99 ${CONNECTIVITY_INCLUDE_DIRS}
10+ ${QTMIRSERVER_INCLUDE_DIRS}
1011)
1112
1213include_directories(
@@ -27,12 +28,11 @@ set(SOURCE_FILES
2728 ApplicationArguments.cpp
2829 main.cpp
2930 CachingNetworkManagerFactory.cpp
30− SecondaryWindow.cpp
31− ShellApplication.cpp
32− ShellView.cpp
31+ UnityApplication.cpp
3332 UnityCommandLineParser.cpp
3433 UnixSignalHandler.cpp
3534 DebuggingController.cpp
35+ WindowManagementPolicy.cpp
3636 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
3737)
3838
@@ -47,7 +47,13 @@ qt5_use_modules(${SHELL_APP} DBus Gui Qml Quick Test)
4747if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
4848 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
4949endif()
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+)
5157
5258if (ENABLE_TOUCH_EMULATION)
5359 target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
src/DebuggingController.cpp+12−1
@@ -49,7 +49,8 @@ public:
4949};
5050
5151DebuggingController::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)
5354{
5455}
5556
@@ -90,3 +91,13 @@ void DebuggingController::SetLoggingFilterRules(const QString &filterRules)
9091{
9192 QLoggingCategory::setFilterRules(filterRules);
9293}
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
3232 Q_OBJECT
3333 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity8.Debugging")
3434
35+ Q_PROPERTY(bool logOverlay READ logOverlay NOTIFY logOverlayChanged)
36+
3537public:
3638 DebuggingController(QObject *parent = nullptr);
3739 ~DebuggingController() = default;
3840
41+ bool logOverlay() const { return m_logOverlay; }
42+
3943public Q_SLOTS:
4044 /**
4145 * Set the QSG_VISUALIZE mode. This follows the vlues supported by Qt in
@@ -53,5 +57,15 @@ public Q_SLOTS:
5357 */
5458 Q_SCRIPTABLE void SetLoggingFilterRules(const QString &filterRules);
5559
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;
5670};
5771#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 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef SHELLAPPLICATION_H
18−#define SHELLAPPLICATION_H
17+#ifndef UNITYAPPLICATION_H
18+#define UNITYAPPLICATION_H
1919
2020#include <QGuiApplication>
21−#include <QQmlEngine>
21+#include <QQmlApplicationEngine>
2222#include <QQuickView>
2323#include <QScopedPointer>
2424
@@ -28,36 +28,26 @@
2828#include "MouseTouchAdaptor.h"
2929#endif
3030
31−#include "SecondaryWindow.h"
32−#include "ShellView.h"
31+#include <qtmir/mirserverapplication.h>
3332
34−class ShellApplication : public QGuiApplication
33+class UnityApplication : public qtmir::MirServerApplication
3534{
3635 Q_OBJECT
3736public:
38− ShellApplication(int & argc, char ** argv, bool isMirServer);
39− virtual ~ShellApplication();
37+ UnityApplication(int & argc, char ** argv);
38+ virtual ~UnityApplication();
4039
4140 void destroyResources();
42−public Q_SLOTS:
43− // called by qtmir
44− void onScreenAboutToBeRemoved(QScreen *screen);
45−
46−private Q_SLOTS:
47− void onScreenAdded(QScreen*);
4841
4942private:
50− void setupQmlEngine(bool isMirServer);
51− QString m_deviceName;
43+ void setupQmlEngine();
5244 ApplicationArguments m_qmlArgs;
53− ShellView *m_shellView{nullptr};
54− SecondaryWindow *m_secondaryWindow{nullptr};
5545
5646 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
5747 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
5848 #endif
5949
60− QQmlEngine *m_qmlEngine{nullptr};
50+ QQmlApplicationEngine *m_qmlEngine{nullptr};
6151};
6252
63−#endif // SHELLAPPLICATION_H
53+#endif // UNITYAPPLICATION_H
src/UnityCommandLineParser.cpp+8
@@ -15,6 +15,7 @@
1515 */
1616
1717#include "UnityCommandLineParser.h"
18+#include <paths.h>
1819
1920#include <QDebug>
2021
@@ -60,6 +61,11 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
6061 QStringLiteral("mode"), QStringLiteral("full-greeter"));
6162 parser.addOption(modeOption);
6263
64+ QCommandLineOption qmlfileOption(QStringLiteral("qmlfile"),
65+ QStringLiteral("The base qml file to load"),
66+ QStringLiteral("qmlfile"), ::qmlDirectory() + "/ShellApplication.qml");
67+ parser.addOption(qmlfileOption);
68+
6369 // Treat args with single dashes the same as arguments with two dashes
6470 // Ex: -fullscreen == --fullscreen
6571 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
@@ -85,6 +91,8 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
8591 m_hasFullscreen = parser.isSet(fullscreenOption);
8692 m_deviceName = parser.value(devicenameOption);
8793 resolveMode(parser, modeOption);
94+
95+ m_qmlfile = parser.value(qmlfileOption);
8896}
8997
9098int UnityCommandLineParser::parsePixelsValue(const QString &str)
src/UnityCommandLineParser.h+4−1
@@ -36,8 +36,10 @@ public:
3636 bool hasFullscreen() const { return m_hasFullscreen; }
3737 QString deviceName() const { return m_deviceName; }
3838 QString mode() const { return m_mode; }
39−private:
4039
40+ QString qmlfie() const { return m_qmlfile; }
41+
42+protected:
4143 int parsePixelsValue(const QString &str);
4244 static float getenvFloat(const char* name, float defaultValue);
4345 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
@@ -55,6 +57,7 @@ private:
5557 bool m_hasFullscreen;
5658 QString m_deviceName;
5759 QString m_mode;
60+ QString m_qmlfile;
5861};
5962
6063#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
88 abstractdbusservicemonitor.cpp
99 unitydbusobject.cpp
1010 unitydbusvirtualobject.cpp
11+ wmpolicyinterface.cpp
1112 )
1213
1314add_library(${LIB_NAME} SHARED
src/SecondaryWindow.h → src/libunity8-private/wmpolicyinterface.cpprenamed+6−12
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,11 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_SECONDARY_WINDOW_H
18−#define UNITY_SECONDARY_WINDOW_H
17+#include "wmpolicyinterface.h"
1918
20−#include <QQuickView>
19+WMPolicyInterface* wmPolicyInterface = nullptr;
2120
22−class SecondaryWindow : public QQuickView
21+WMPolicyInterface *WMPolicyInterface::instance()
2322{
24− Q_OBJECT
25−
26−public:
27− SecondaryWindow(QQmlEngine *engine);
28−};
29−
30−#endif // UNITY_SECONDARY_WINDOW_H
23+ return wmPolicyInterface;
24+}
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 @@
1515 */
1616
1717// local
18−#include "ShellApplication.h"
18+#include "UnityApplication.h"
1919#include "qmldebuggerutils.h"
2020#include "UnixSignalHandler.h"
21+#include <paths.h>
2122
2223#include <QTranslator>
2324#include <QLibraryInfo>
@@ -27,17 +28,12 @@ int main(int argc, const char *argv[])
2728{
2829 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
2930
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−
3631 if (enableQmlDebugger(argc, argv)) {
3732 QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
3833 }
3934
40− ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
35+ auto *application = new UnityApplication(argc,
36+ (char**)argv);
4137
4238 UnixSignalHandler signalHandler([]{
4339 QGuiApplication::exit(0);
tests/mocks/CMakeLists.txt+1
@@ -43,6 +43,7 @@ add_subdirectory(SessionBroadcast)
4343add_subdirectory(Ubuntu)
4444add_subdirectory(Unity)
4545add_subdirectory(QtMultimedia)
46+add_subdirectory(WindowManager)
4647add_subdirectory(Wizard)
4748add_subdirectory(Utils)
4849add_subdirectory(UInput)
tests/mocks/Cursor/Cursor.qml+49−2
@@ -15,8 +15,11 @@
1515 */
1616
1717import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
1822
19−Item {
2023 property int topBoundaryOffset // effectively panel height
2124 property Item confiningItem
2225
@@ -30,5 +33,49 @@ Item {
3033 signal pushStopped()
3134 signal mouseMoved()
3235
33− onMouseMoved: opacity = 1;
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
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+ }
3481}
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
1818#include "fake_gsettings.h"
1919
2020#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
2123
2224static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
2325{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
3133 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
3234 "GSettingsSchema can only be used inside of a GSettings component");
3335}
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
2626
2727public:
2828 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
2930};
3031
3132#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
1919
2020#include <QtQml/qqml.h>
2121
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
2227void MockUInputPlugin::registerTypes(const char *uri)
2328{
2429 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
2631}
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+
110set(FakeUnityApplicationQml_SOURCES
211 plugin.cpp
312 ApplicationInfo.cpp
@@ -19,11 +28,27 @@ set(FakeUnityApplicationQml_SOURCES
1928 resources/surfaces.qrc
2029)
2130
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+)
2342
2443add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
2544set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
2645
46+target_link_libraries(NonMirUnityApplicationQml
47+ ${MIRTEST_LDFLAGS}
48+ ${MIRAL_LDFLAGS}
49+ mockwindowmanagmentpolicy
50+)
51+
2752qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
2853qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
2954
tests/mocks/Unity/Application/MirSurfaceItem.cpp+27−4
@@ -189,9 +189,15 @@ void MirSurfaceItem::createQmlContentItem()
189189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
190190 m_qmlItem->setParentItem(this);
191191
192− m_qmlItem->setWidth(m_surfaceWidth);
193− m_qmlItem->setHeight(m_surfaceHeight);
192+ if (m_fillMode == FillMode::Stretch && width() != 0 && height() != 0) {
193+ m_qmlItem->setWidth(width());
194+ m_qmlItem->setHeight(height());
195+ } else {
196+ m_qmlItem->setWidth(m_surfaceWidth);
197+ m_qmlItem->setHeight(m_surfaceHeight);
198+ }
194199
200+ qDebug() << "set size to" << m_surfaceWidth;
195201 setImplicitWidth(m_qmlItem->implicitWidth());
196202 setImplicitHeight(m_qmlItem->implicitHeight());
197203
@@ -308,6 +314,16 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
308314 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
309315 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
310316 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();
311327
312328 QUrl qmlComponentFilePath;
313329 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
@@ -321,6 +337,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
321337 switch (m_qmlContentComponent->status()) {
322338 case QQmlComponent::Ready:
323339 createQmlContentItem();
340+ qDebug() << "content created" << m_surfaceWidth << implicitWidth() << width();
324341 break;
325342 case QQmlComponent::Loading:
326343 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
@@ -399,9 +416,15 @@ void MirSurfaceItem::updateSurfaceSize()
399416 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
400417 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
401418 if (m_qmlItem) {
402− m_qmlItem->setWidth(m_surfaceWidth);
403− m_qmlItem->setHeight(m_surfaceHeight);
419+ if (m_fillMode == FillMode::Stretch) {
420+ m_qmlItem->setWidth(width());
421+ m_qmlItem->setHeight(height());
422+ } else {
423+ m_qmlItem->setWidth(m_surfaceWidth);
424+ m_qmlItem->setHeight(m_surfaceHeight);
425+ }
404426 }
427+ qDebug() << this << "setting implicitsize" << m_surfaceWidth << m_surfaceHeight;
405428 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
406429 }
407430}
tests/mocks/Unity/Application/SurfaceManager.cpp+60−2
@@ -18,10 +18,12 @@
1818
1919#include "ApplicationInfo.h"
2020#include "VirtualKeyboard.h"
21+#include "../../WindowManager/WindowManagementPolicy.h"
2122
2223#include <paths.h>
24+#include <mirtest/mir/test/doubles/stub_surface.h>
2325
24−#define SURFACEMANAGER_DEBUG 0
26+#define SURFACEMANAGER_DEBUG 1
2527
2628#if SURFACEMANAGER_DEBUG
2729#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
@@ -31,6 +33,12 @@
3133
3234namespace unityapi = unity::shell::application;
3335
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+
3442SurfaceManager *SurfaceManager::m_instance = nullptr;
3543
3644SurfaceManager *SurfaceManager::instance()
@@ -44,6 +52,21 @@ SurfaceManager::SurfaceManager(QObject *)
4452
4553 Q_ASSERT(m_instance == nullptr);
4654 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+ });
4770}
4871
4972SurfaceManager::~SurfaceManager()
@@ -58,6 +81,29 @@ SurfaceManager::~SurfaceManager()
5881 m_instance = nullptr;
5982}
6083
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+
61107MirSurface *SurfaceManager::createSurface(const QString& name,
62108 Mir::Type type,
63109 Mir::State state,
@@ -75,7 +121,12 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
75121
76122void SurfaceManager::registerSurface(MirSurface *surface)
77123{
124+ auto fakeSurface = std::make_shared<mir::test::doubles::StubSurface>();
125+ WindowWrapper window{miral::Window(nullptr, fakeSurface), fakeSurface};
126+
78127 m_surfaces.prepend(surface);
128+ m_windowToSurface.insert(window, surface);
129+ m_surfaceToWindow.insert(surface, window);
79130
80131 if (!surface->parentSurface()) {
81132 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
@@ -92,6 +143,8 @@ void SurfaceManager::registerSurface(MirSurface *surface)
92143
93144 const QString persistentId = surface->persistentId();
94145 connect(surface, &QObject::destroyed, this, [=]() {
146+ WindowWrapper key = m_surfaceToWindow.take(surface);
147+ m_windowToSurface.remove(key);
95148 this->onSurfaceDestroyed(surface, persistentId);
96149 });
97150
@@ -99,7 +152,12 @@ void SurfaceManager::registerSurface(MirSurface *surface)
99152
100153void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
101154{
102− Q_EMIT surfaceCreated(surface);
155+ if (Q_UNLIKELY(!m_surfaceToWindow.contains(surface))) {
156+ Q_EMIT surfaceCreated(surface);
157+ return;
158+ }
159+
160+ WindowManagementPolicy::instance()->addWindow(m_surfaceToWindow[surface].window);
103161}
104162
105163void SurfaceManager::setNewSurfaceMinimumWidth(int value)
tests/mocks/Unity/Application/SurfaceManager.h+14
@@ -20,12 +20,20 @@
2020#include <QObject>
2121
2222#include <unity/shell/application/SurfaceManagerInterface.h>
23+#include <miral/window.h>
2324
2425#include "MirSurface.h"
2526#include "VirtualKeyboard.h"
2627
2728class ApplicationInfo;
2829
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+
2937class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
3038{
3139 Q_OBJECT
@@ -42,6 +50,8 @@ public:
4250
4351 static SurfaceManager *instance();
4452
53+ unity::shell::application::MirSurfaceInterface *surfaceFor(const miral::Window& window) const override;
54+
4555 // SurfaceManagerInterface
4656 void raise(unity::shell::application::MirSurfaceInterface *surface) override;
4757 void activate(unity::shell::application::MirSurfaceInterface *surface) override;
@@ -95,6 +105,7 @@ private:
95105 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
96106 void focusFirstAvailableSurface();
97107 void registerSurface(MirSurface *surface);
108+ QVector<unity::shell::application::MirSurfaceInterface*> find(const std::vector<miral::Window> &windows) const;
98109
99110 static SurfaceManager *m_instance;
100111
@@ -110,6 +121,9 @@ private:
110121
111122 QList<MirSurface*> m_surfaces;
112123
124+ QHash<WindowWrapper, unity::shell::application::MirSurfaceInterface*> m_windowToSurface;
125+ QHash<unity::shell::application::MirSurfaceInterface*, WindowWrapper> m_surfaceToWindow;
126+
113127 VirtualKeyboard *m_virtualKeyboard{nullptr};
114128};
115129
tests/mocks/Unity/Application/plugin.cpp+7−1
@@ -38,6 +38,12 @@ QObject* mirSingleton(QQmlEngine*, QJSEngine*)
3838{
3939 return new MirMock;
4040}
41+
42+QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
43+{
44+ return new SurfaceManager;
45+}
46+
4147} // anonymous namespace
4248
4349void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -59,7 +65,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
5965
6066 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
6167 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
62− qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
68+ qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
6369}
6470
6571void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Unity/CMakeLists.txt−1
@@ -6,7 +6,6 @@ add_subdirectory(InputInfo)
66add_subdirectory(Launcher)
77add_subdirectory(Notifications)
88add_subdirectory(DashCommunicator)
9−add_subdirectory(Screens)
109add_subdirectory(Platform)
1110
1211pkg_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 @@
1818
1919#include "qinputdeviceinfo_mock_p.h"
2020
21−MockController::MockController(QObject *parent):
22− QObject(parent)
21+#include <QQuickView>
22+#include <QQmlComponent>
23+#include <QQuickItem>
24+#include <paths.h>
25+
26+MockController::MockController(QQmlEngine *engine)
2327{
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+}
2442
43+MockController *MockController::instance(QQmlEngine *engine)
44+{
45+ static MockController* controller = new MockController(engine);
46+ return controller;
2547}
2648
2749QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
tests/mocks/Unity/InputInfo/mockcontroller.h+5−1
@@ -20,13 +20,17 @@
2020#include <QObject>
2121#include "qinputinfo.h"
2222
23+class QQmlEngine;
24+
2325class MockController: public QObject
2426{
2527 Q_OBJECT
2628public:
27− MockController(QObject *parent = 0);
29+ MockController(QQmlEngine *engine);
2830 ~MockController() = default;
2931
32+ static MockController *instance(QQmlEngine *engine);
33+
3034 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
3135 Q_INVOKABLE void removeDevice(const QString &devicePath);
3236};
tests/mocks/Unity/InputInfo/plugin.cpp+1−2
@@ -26,9 +26,8 @@
2626
2727static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
2828{
29− Q_UNUSED(engine)
3029 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
30+ return MockController::instance(engine);
3231}
3332
3433void InputInfoPlugin::registerTypes(const char *uri)
tests/mocks/Unity/InputInfo/qmldir+2
@@ -1,2 +1,4 @@
11module Unity.InputInfo
22plugin 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/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/Unity/Screens/screens.hdeleted−82
@@ -1,82 +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−#ifndef SCREENS_H
18−#define SCREENS_H
19−
20−#include <QAbstractListModel>
21−#include <QScreen>
22−
23−class Screen;
24−
25−class Screens : public QAbstractListModel
26−{
27− Q_OBJECT
28−
29− Q_PROPERTY(int count READ count NOTIFY countChanged)
30−
31−public:
32− enum ItemRoles {
33− ScreenRole = Qt::UserRole + 1,
34− OutputTypeRole
35− };
36−
37− enum OutputTypes {
38− Unknown,
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−
56− explicit Screens(QObject *parent = 0);
57− virtual ~Screens() noexcept;
58−
59− /* QAbstractItemModel */
60− QHash<int, QByteArray> roleNames() const override;
61− QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
62− int rowCount(const QModelIndex &parent = QModelIndex()) const override;
63−
64− int count() const;
65−
66−Q_SIGNALS:
67− void countChanged();
68− void screenAdded(QScreen *screen);
69− void screenRemoved(QScreen *screen);
70−
71−private:
72− QList<Screen *> m_screenList;
73−};
74−
75−class Screen
76−{
77−public:
78− Screens::OutputTypes outputTypes = Screens::Unknown;
79− QScreen *qScreen = nullptr;
80−};
81−
82−#endif // SCREENS_H
tests/mocks/WindowManager/CMakeLists.txtnew file+51
@@ -0,0 +1,51 @@
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/ScreenAttached.cpp
25+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Screens.cpp
26+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/ScreenWindow.cpp
27+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/TopLevelWindowModel.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Window.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceManager.cpp
30+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceModel.cpp
31+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Workspace.cpp
32+ MockScreens.cpp
33+ MockScreenWindow.cpp
34+ WindowManagerPlugin.cpp
35+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
36+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
37+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
38+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
39+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
40+)
41+
42+add_library(MockWindowManager-qml SHARED ${WINDOWMANAGER_SRC})
43+
44+target_link_libraries(MockWindowManager-qml
45+ ${QTMIRSERVER_LDFLAGS}
46+ mockwindowmanagmentpolicy
47+)
48+
49+qt5_use_modules(MockWindowManager-qml Qml Quick Gui)
50+
51+add_unity8_mock(WindowManager 1.0 WindowManager TARGETS MockWindowManager-qml)
tests/mocks/Unity/Screens/plugin.cpp → tests/mocks/WindowManager/MockScreenWindow.cpprenamed+14−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,16 +14,21 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#include "plugin.h"
18−#include "screens.h"
17+#include "MockScreenWindow.h"
18+#include "MockScreens.h"
1919
20−#include <QScreen>
20+// Qt
21+#include <QGuiApplication>
22+#include <QDebug>
2123
22−void UnityScreensPlugin::registerTypes(const char* uri)
24+MockScreenWindow::MockScreenWindow(QQuickWindow *parent)
25+ : ScreenWindow(parent)
2326{
24− Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
25−
26− qRegisterMetaType<QScreen*>("QScreen*");
27+ connect(this, &ScreenWindow::screenWrapperChanged, this, [this]() {
28+ MockScreens::instance()->connectWindow(this);
29+ });
30+}
2731
28− qmlRegisterType<Screens>(uri, 0, 1, "Screens");
32+MockScreenWindow::~MockScreenWindow()
33+{
2934}
tests/mocks/Unity/Screens/plugin.h → tests/mocks/WindowManager/MockScreenWindow.hrenamed+11−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,12 +14,17 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#include <QQmlExtensionPlugin>
18−#include <QtQml/qqml.h>
17+#ifndef MOCK_SCREENWINDOW_H
18+#define MOCK_SCREENWINDOW_H
1919
20−class UnityScreensPlugin : public QQmlExtensionPlugin {
20+#include "ScreenWindow.h"
21+
22+class MockScreenWindow : public ScreenWindow
23+{
2124 Q_OBJECT
22− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0")
2325public:
24− void registerTypes(const char* uri) override;
26+ explicit MockScreenWindow(QQuickWindow *parent = 0);
27+ ~MockScreenWindow();
2528};
29+
30+#endif // MOCK_SCREENWINDOW_H
tests/mocks/WindowManager/MockScreens.cppnew file+213
@@ -0,0 +1,213 @@
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+#include "ScreenWindow.h"
19+
20+// qtmirserver
21+#include <qtmir/screen.h>
22+
23+#include <QGuiApplication>
24+#include <QWindow>
25+#include <QWeakPointer>
26+
27+namespace {
28+
29+QWeakPointer<MockScreens> m_screens;
30+
31+class MockScreen : public qtmir::Screen
32+{
33+ Q_OBJECT
34+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeNameChanged)
35+public:
36+ MockScreen()
37+ {
38+ m_sizes.append(new qtmir::ScreenMode(50, QSize(640,480)));
39+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1280,1024)));
40+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1440,900)));
41+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1920,1080)));
42+ m_physicalSize = QSize(800,568);
43+ }
44+ ~MockScreen() {
45+ qDeleteAll(m_sizes);
46+ m_sizes.clear();
47+ }
48+
49+ void connectToWindow(QWindow* w)
50+ {
51+ if (m_connectedWindow == w) return;
52+
53+ if (m_connectedWindow) {
54+ disconnect(m_connectedWindow.data());
55+ m_sizes.takeFirst()->deleteLater();
56+ }
57+
58+ m_connectedWindow = w;
59+
60+ if (w) {
61+ connect(w, &ScreenWindow::heightChanged, this, [this](int height) {
62+ m_sizes.first()->size.rheight() = height;
63+ Q_EMIT availableModesChanged();
64+
65+ });
66+ connect(w, &ScreenWindow::widthChanged, this, [this](int width) {
67+ m_sizes.first()->size.rwidth() = width;
68+ Q_EMIT availableModesChanged();
69+ });
70+
71+ m_sizes.push_front(new qtmir::ScreenMode(50, w->size()));
72+ Q_EMIT availableModesChanged();
73+ }
74+ }
75+
76+ qtmir::OutputId outputId() const override { return m_id; }
77+ bool used() const override { return m_used; }
78+ QString name() const override { return m_name; }
79+ float scale() const override { return m_scale; }
80+ QSizeF physicalSize() const override { return m_physicalSize; }
81+ qtmir::FormFactor formFactor() const override { return m_formFactor; }
82+ qtmir::OutputTypes outputType() const override { return m_outputType; }
83+ QString outputTypeName() const { return QStringLiteral("Internal"); }
84+ MirPowerMode powerMode() const override { return m_powerMode; }
85+ Qt::ScreenOrientation orientation() const override { return m_orientation; }
86+ QPoint position() const override { return m_position; }
87+ uint currentModeIndex() const override { return m_currentModeIndex; }
88+ bool isActive() const override { return m_active; }
89+
90+ QQmlListProperty<qtmir::ScreenMode> availableModes() override {
91+ return QQmlListProperty<qtmir::ScreenMode>(this, m_sizes);
92+ }
93+
94+ void setActive(bool active) override {
95+ if (m_active != active) {
96+ m_active = active;
97+ Q_EMIT activeChanged(m_active);
98+ }
99+ }
100+
101+ QScreen* qscreen() const override {
102+ if (qGuiApp->topLevelWindows().count() > 0) {
103+ return qGuiApp->topLevelWindows()[0]->screen();
104+ }
105+ return qGuiApp->primaryScreen();
106+ }
107+
108+ qtmir::ScreenConfiguration *beginConfiguration() const override {
109+ auto config = new qtmir::ScreenConfiguration;
110+ config->valid = true;
111+ config->id = m_id;
112+ config->used = m_used;
113+ config->topLeft = m_position;
114+ config->currentModeIndex = m_currentModeIndex;
115+ config->powerMode = m_powerMode;
116+ config->scale = m_scale;
117+ config->formFactor = m_formFactor;
118+ return config;
119+ }
120+
121+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration) override {
122+ m_used = configuration->used;
123+ m_position = configuration->topLeft;
124+ m_currentModeIndex = configuration->currentModeIndex;
125+ m_powerMode = configuration->powerMode;
126+ m_scale = configuration->scale;
127+ m_formFactor = configuration->formFactor;
128+ return true;
129+ }
130+
131+Q_SIGNALS:
132+ void outputTypeNameChanged();
133+
134+public:
135+ qtmir::OutputId m_id{0};
136+ bool m_active{false};
137+ bool m_used{true};
138+ QString m_name;
139+ qtmir::OutputTypes m_outputType{qtmir::Unknown};
140+ MirPowerMode m_powerMode{mir_power_mode_on};
141+ Qt::ScreenOrientation m_orientation{Qt::PrimaryOrientation};
142+ float m_scale{1.0};
143+ qtmir::FormFactor m_formFactor{qtmir::FormFactorMonitor};
144+ QPoint m_position;
145+ uint m_currentModeIndex{0};
146+ QList<qtmir::ScreenMode*> m_sizes;
147+ QSizeF m_physicalSize;
148+
149+ QPointer<QWindow> m_connectedWindow;
150+};
151+}
152+
153+MockScreens::MockScreens()
154+{
155+ bool ok = false;
156+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
157+ if (!ok) screenCount = 1;
158+ QPoint lastPoint(0,0);
159+ for (int i = 0; i < screenCount; ++i) {
160+ auto screen = new MockScreen();
161+ screen->m_id = qtmir::OutputId{i};
162+ screen->m_active = i == 0;
163+ screen->m_name = QString("Monitor %1").arg(i);
164+ screen->m_position = QPoint(lastPoint.x(), lastPoint.y());
165+ screen->m_currentModeIndex = 3;
166+ m_mocks.append(screen);
167+
168+ lastPoint.rx() += screen->m_sizes[screen->m_currentModeIndex]->size.width();
169+ }
170+}
171+
172+MockScreens::~MockScreens()
173+{
174+ qDeleteAll(m_mocks);
175+ m_mocks.clear();
176+}
177+
178+QVector<qtmir::Screen *> MockScreens::screens() const
179+{
180+ return m_mocks;
181+}
182+
183+qtmir::Screen *MockScreens::activeScreen() const
184+{
185+ Q_FOREACH(auto screen, m_mocks) {
186+ if (screen->isActive()) return screen;
187+ }
188+ return nullptr;
189+}
190+
191+QSharedPointer<MockScreens> MockScreens::instance()
192+{
193+ if (!m_screens) {
194+ QSharedPointer<MockScreens> screens(new MockScreens());
195+ m_screens = screens.toWeakRef();
196+ return screens;
197+ } else {
198+ return m_screens.lock();
199+ }
200+}
201+
202+void MockScreens::connectWindow(ScreenWindow *w)
203+{
204+ Screen* screen = w->screenWrapper();
205+ if (!screen) return;
206+
207+ auto mockScreen = qobject_cast<MockScreen*>(screen->wrapped());
208+ if (mockScreen) {
209+ mockScreen->connectToWindow(w);
210+ }
211+}
212+
213+#include "MockScreens.moc"
tests/mocks/WindowManager/MockScreens.hnew file+54
@@ -0,0 +1,54 @@
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 MOCK_SCREENS_H
18+#define MOCK_SCREENS_H
19+
20+#include <QAbstractListModel>
21+#include <QSharedPointer>
22+
23+#include <qtmir/screens.h>
24+
25+namespace qtmir
26+{
27+class Screens;
28+}
29+
30+class Screen;
31+class ScreenWindow;
32+
33+class MockScreens : public qtmir::Screens
34+{
35+ Q_OBJECT
36+public:
37+ MockScreens();
38+ ~MockScreens();
39+
40+ QVector<qtmir::Screen*> screens() const override;
41+
42+ qtmir::Screen *activeScreen() const override;
43+
44+ static QSharedPointer<MockScreens> instance();
45+
46+public Q_SLOTS:
47+ void connectWindow(ScreenWindow *w);
48+
49+private:
50+ QVector<qtmir::Screen*> m_mocks;
51+};
52+
53+
54+#endif // MOCK_SCREENS_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+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+#include "WindowManagerPlugin.h"
18+
19+#include "MockScreens.h"
20+#include "MockScreenWindow.h"
21+#include "Screen.h"
22+#include "ScreenAttached.h"
23+#include "Screens.h"
24+#include "TopLevelWindowModel.h"
25+#include "Window.h"
26+#include "WorkspaceManager.h"
27+#include "Workspace.h"
28+#include "WorkspaceModel.h"
29+#include "WindowManagementPolicy.h"
30+
31+#include <QtQml>
32+
33+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
34+
35+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
36+{
37+ Q_UNUSED(engine)
38+ Q_UNUSED(scriptEngine)
39+ return WorkspaceManager::instance();
40+}
41+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
42+ Q_UNUSED(engine);
43+ Q_UNUSED(scriptEngine);
44+ return new Screens(MockScreens::instance());
45+}
46+
47+void WindowManagerPlugin::registerTypes(const char *uri)
48+{
49+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
50+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
51+ qmlRegisterUncreatableType<WorkspaceModel>(uri, 1, 0, "WorkspaceModel", notInstantiatable);
52+ qmlRegisterSingletonType<Screens>(uri, 1, 0, "Screens", screensSingleton);
53+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", notInstantiatable);
54+ qmlRegisterUncreatableType<Workspace>(uri, 1, 0, "Workspace", notInstantiatable);
55+
56+ qRegisterMetaType<Screen*>("Screen*");
57+ qRegisterMetaType<ScreensProxy*>("ScreensProxy*");
58+ qRegisterMetaType<Workspace*>("Workspace*");
59+
60+ qRegisterMetaType<Window*>("Window*");
61+ qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
62+
63+ qmlRegisterType<MockScreenWindow>(uri, 1, 0, "ScreenWindow");
64+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
65+
66+ qmlRegisterUncreatableType<WMScreen>(uri, 1, 0, "WMScreen", notInstantiatable);
67+}
68+
69+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
70+{
71+ QQmlExtensionPlugin::initializeEngine(engine, uri);
72+
73+ // Make sure we've initialized the wm policy.
74+ WindowManagementPolicy::instance();
75+}
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
66
77Object {
88
9− property alias surfaceManager: sMgrHandler.target
10−
119 Connections {
12− id: sMgrHandler
13− target: null
10+ target: SurfaceManager
1411 onSurfaceCreated: {
1512 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
1613
tests/qmltests/ApplicationMenus/tst_MenuBar.qml−2
@@ -42,10 +42,8 @@ Item {
4242 value: false
4343 }
4444
45− SurfaceManager { id: sMgr }
4645 ApplicationMenuDataLoader {
4746 id: appMenuData
48− surfaceManager: sMgr
4947 }
5048
5149 Rectangle {
tests/qmltests/CMakeLists.txt+1
@@ -5,6 +5,7 @@ add_unity8_qmltest(. OrientedShell)
55add_unity8_qmltest(. DisabledScreenNotice)
66add_unity8_qmltest(. Shell)
77add_unity8_qmltest(. ShellWithPin)
8+add_unity8_qmltest(. ShellApplication)
89add_unity8_qmltest(. DeviceConfiguration)
910add_unity8_qmltest_data(. EdgeBarrierControls)
1011add_unity8_qmltest_data(. ApplicationMenuDataLoader)
tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2
@@ -39,11 +39,11 @@ Rectangle {
3939
4040 SignalSpy {
4141 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
4343 }
4444 SignalSpy {
4545 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
4747 }
4848
4949 UnityTestCase {
tests/qmltests/Panel/tst_ActiveCallHint.qml−2
@@ -41,8 +41,6 @@ Item {
4141 elapsedTimerRunning: true
4242 }
4343
44− SurfaceManager {}
45−
4644 ActiveCallHint {
4745 id: callHint
4846 anchors {
tests/qmltests/Panel/tst_Panel.qml+14−14
@@ -42,10 +42,10 @@ PanelTest {
4242 value: keyboardAttached.checked
4343 }
4444
45− SurfaceManager { id: sMgr }
45+ readonly property alias panelState: panel.panelState
46+
4647 ApplicationMenuDataLoader {
4748 id: appMenuData
48− surfaceManager: sMgr
4949 }
5050
5151 Component.onCompleted: {
@@ -148,7 +148,7 @@ PanelTest {
148148 Layout.fillWidth: true
149149 CheckBox {
150150 id: windowControlsCB
151− onClicked: PanelState.decorationsVisible = checked
151+ onClicked: panelState.decorationsVisible = checked
152152 }
153153 Label {
154154 text: "Show window decorations"
@@ -159,7 +159,7 @@ PanelTest {
159159 RowLayout {
160160 Layout.fillWidth: true
161161 CheckBox {
162− onClicked: PanelState.title = checked ? "Fake window title" : ""
162+ onClicked: panelState.title = checked ? "Fake window title" : ""
163163 }
164164 Label {
165165 text: "Show fake window title"
@@ -245,7 +245,7 @@ PanelTest {
245245
246246 SignalSpy {
247247 id: windowControlButtonsSpy
248− target: PanelState
248+ target: panelState
249249 signalName: "closeClicked"
250250 }
251251
@@ -255,8 +255,8 @@ PanelTest {
255255 panel.fullscreenMode = false;
256256 callManager.foregroundCall = null;
257257
258− PanelState.title = "";
259− PanelState.decorationsVisible = false;
258+ panelState.title = "";
259+ panelState.decorationsVisible = false;
260260
261261 // Wait for the indicators to get into position.
262262 // (switches between normal and fullscreen modes are animated)
@@ -447,7 +447,7 @@ PanelTest {
447447 }
448448
449449 function test_hint(data) {
450− PanelState.title = "Fake Title"
450+ panelState.title = "Fake Title"
451451 panel.fullscreenMode = data.fullscreen;
452452 callManager.foregroundCall = data.call;
453453
@@ -489,7 +489,7 @@ PanelTest {
489489 // menus, first by running the hint animation, then after dragging down will
490490 // expose more of the panel. Releasing the touch will complete the show.
491491 function test_drag_applicationMenu_down_shows_menu(data) {
492− PanelState.title = "Fake Title";
492+ panelState.title = "Fake Title";
493493 panel.fullscreenMode = data.fullscreen;
494494 callManager.foregroundCall = data.call;
495495
@@ -580,7 +580,7 @@ PanelTest {
580580 }
581581
582582 function test_darkenedAreaEatsAllApplicationMenuEvents() {
583− PanelState.title = "Fake Title"
583+ panelState.title = "Fake Title"
584584
585585 // The center of the area not covered by the indicators menu
586586 // Ie, the visible darkened area behind the menu
@@ -701,7 +701,7 @@ PanelTest {
701701 var windowControlArea = findChild(panel, "windowControlArea");
702702 verify(windowControlArea, "Window control area should have been created in windowed mode")
703703
704− PanelState.decorationsVisible = true;
704+ panelState.decorationsVisible = true;
705705 // click in very topleft corner and verify the close button got clicked too
706706 mouseMove(panel, 0, 0);
707707 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
@@ -762,7 +762,7 @@ PanelTest {
762762 }
763763
764764 function test_stagedApplicationMenuBarShowOnMouseHover() {
765− PanelState.title = "Fake Title";
765+ panelState.title = "Fake Title";
766766 panel.mode = "staged";
767767 mouseEmulation.checked = false;
768768
@@ -780,7 +780,7 @@ PanelTest {
780780 }
781781
782782 function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
783− PanelState.title = "Fake Title";
783+ panelState.title = "Fake Title";
784784 panel.mode = "windowed";
785785 mouseEmulation.checked = false;
786786
@@ -796,7 +796,7 @@ PanelTest {
796796 tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
797797 tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
798798
799− PanelState.decorationsVisible = true;
799+ panelState.decorationsVisible = true;
800800
801801 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
802802 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 {
3737 }
3838 property QtObject fakeApplication: null
3939
40− SurfaceManager{}
41−
4240 Loader {
4341 id: applicationWindowLoader
4442 focus: true
tests/qmltests/Stage/tst_DecoratedWindow.qml+5−3
@@ -46,10 +46,12 @@ Rectangle {
4646 value: false
4747 }
4848
49− SurfaceManager { id: sMgr }
5049 ApplicationMenuDataLoader {
5150 id: appMenuData
52− surfaceManager: sMgr
51+ }
52+
53+ PanelState {
54+ id: panelState
5355 }
5456
5557 Item {
@@ -99,7 +101,7 @@ Rectangle {
99101 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
100102
101103 Binding {
102− target: PanelState
104+ target: panelState
103105 property: "focusedPersistentSurfaceId"
104106 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105107 }
tests/qmltests/Stage/tst_DesktopStage.qml+9−6
@@ -44,6 +44,10 @@ Item {
4444 value: false
4545 }
4646
47+ PanelState {
48+ id: panelState
49+ }
50+
4751 Component.onCompleted: {
4852 ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
4953 ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
@@ -63,16 +67,14 @@ Item {
6367 }
6468 }
6569
66− SurfaceManager { id: sMgr }
6770 ApplicationMenuDataLoader {
6871 id: appMenuData
69− surfaceManager: sMgr
7072 }
7173
7274 TopLevelWindowModel {
7375 id: topSurfaceList
7476 applicationManager: ApplicationManager
75− surfaceManager: sMgr
77+ surfaceManager: SurfaceManager
7678 }
7779
7880 Loader {
@@ -102,6 +104,7 @@ Item {
102104 topLevelSurfaceList: topSurfaceList
103105 interactive: true
104106 mode: "windowed"
107+ panelState: panelState
105108 }
106109 }
107110 }
@@ -631,19 +634,19 @@ Item {
631634 maximizeDelegate(facebookAppDelegate);
632635
633636 // verify the drop shadow is still not visible
634− verify(PanelState.dropShadow == false);
637+ verify(panelState.dropShadow == false);
635638
636639 // start a foreground app, not maximized
637640 var dialerAppDelegate = startApplication("dialer-app");
638641
639642 // verify the drop shadow becomes visible
640− verify(PanelState.dropShadow == true);
643+ verify(panelState.dropShadow == true);
641644
642645 // close the maximized app
643646 ApplicationManager.stopApplication("facebook-webapp");
644647
645648 // verify the drop shadow is gone
646− tryCompare(PanelState, "dropShadow", false);
649+ tryCompare(panelState, "dropShadow", false);
647650 }
648651
649652 function test_threeFingerTapShowsWindowControls_data() {
tests/qmltests/Stage/tst_PhoneStage.qml+1−3
@@ -31,10 +31,8 @@ Item {
3131
3232 property var greeter: { fullyShown: true }
3333
34− SurfaceManager { id: sMgr }
3534 ApplicationMenuDataLoader {
3635 id: appMenuData
37− surfaceManager: sMgr
3836 }
3937
4038 Stage {
@@ -50,7 +48,7 @@ Item {
5048 topLevelSurfaceList: TopLevelWindowModel {
5149 id: topLevelSurfaceList
5250 applicationManager: ApplicationManager
53− surfaceManager: sMgr
51+ surfaceManager: SurfaceManager
5452 }
5553 Component.onCompleted: {
5654 ApplicationManager.startApplication("unity8-dash");
tests/qmltests/Stage/tst_SurfaceContainer.qml−2
@@ -51,8 +51,6 @@ Rectangle {
5151 }
5252 }
5353
54− SurfaceManager {}
55−
5654 Loader {
5755 id: surfaceContainerLoader
5856 focus: true
tests/qmltests/Stage/tst_TabletStage.qml+1−3
@@ -35,10 +35,8 @@ Rectangle {
3535
3636 property var greeter: { fullyShown: true }
3737
38− SurfaceManager { id: sMgr }
3938 ApplicationMenuDataLoader {
4039 id: appMenuData
41− surfaceManager: sMgr
4240 }
4341
4442 Stage {
@@ -59,7 +57,7 @@ Rectangle {
5957 topLevelSurfaceList: TopLevelWindowModel {
6058 id: topLevelSurfaceList
6159 applicationManager: ApplicationManager
62− surfaceManager: sMgr
60+ surfaceManager: SurfaceManager
6361 }
6462 Component.onCompleted: {
6563 ApplicationManager.startApplication("unity8-dash");
tests/qmltests/Stage/tst_WindowDecoration.qml−2
@@ -47,10 +47,8 @@ Item {
4747 property string name: "webbrowser"
4848 }
4949
50− SurfaceManager { id: sMgr }
5150 ApplicationMenuDataLoader {
5251 id: appMenuData
53− surfaceManager: sMgr
5452 }
5553
5654 UnityMenuModel {
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8
@@ -19,7 +19,6 @@ import QtQuick.Layouts 1.1
1919import QtTest 1.0
2020import Unity.Test 0.1
2121import ".."
22−import "../../../qml/Components/PanelState"
2322import "../../../qml/Stage"
2423import Ubuntu.Components 1.3
2524import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -31,12 +30,6 @@ Item {
3130 height: units.gu(60)
3231 width: units.gu(85)
3332
34− Binding {
35− target: PanelState
36− property: "panelHeight"
37− value: units.gu(3)
38− }
39−
4033 Component {
4134 id: fakeWindowComponent
4235
@@ -362,7 +355,7 @@ Item {
362355
363356 // Make sure it's again where we left it in normal state before destroying
364357 compare(fakeWindow.requestedX >= 0, true)
365− compare(fakeWindow.requestedY >= PanelState.panelHeight, true)
358+ compare(fakeWindow.requestedY >= 0, true)
366359 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
367360 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
368361
tests/qmltests/Tutorial/tst_Tutorial.qml+1−3
@@ -326,9 +326,7 @@ Rectangle {
326326 }
327327
328328 function ensureInputMethodSurface() {
329− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
330− verify(surfaceManager);
331− surfaceManager.createInputMethodSurface();
329+ SurfaceManager.createInputMethodSurface();
332330
333331 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
334332 }
tests/qmltests/tst_OrientedShell.qml+19−5
@@ -30,7 +30,6 @@ import Utils 0.1
3030
3131import "../../qml"
3232import "../../qml/Components"
33−import "../../qml/Components/PanelState"
3433import "Stage"
3534
3635Rectangle {
@@ -82,6 +81,12 @@ Rectangle {
8281 deviceFilter: InputInfo.Keyboard
8382 }
8483
84+ QtObject {
85+ id: _screenWindow
86+ property bool primary: true
87+ }
88+ property alias screenWindow: _screenWindow
89+
8590 property int physicalOrientation0
8691 property int physicalOrientation90
8792 property int physicalOrientation180
@@ -446,6 +451,15 @@ Rectangle {
446451 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
447452 property Item shell
448453 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+ }
449463
450464 SignalSpy { id: signalSpy }
451465 SignalSpy { id: signalSpy2 }
@@ -1510,13 +1524,13 @@ Rectangle {
15101524 print("exptectedAngle", expectedAngle, point.x, point.y)
15111525 switch (expectedAngle) {
15121526 case 0:
1513− return point.x === 0 && point.y === PanelState.panelHeight;
1527+ return point.x === 0 && point.y === panelState.panelHeight;
15141528 case 90:
1515− return point.x === orientedShell.width - PanelState.panelHeight && point.y === 0;
1529+ return point.x === orientedShell.width - panelState.panelHeight && point.y === 0;
15161530 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;
15181532 default: // 270
1519− return point.x === PanelState.panelHeight && point.y === orientedShell.height;
1533+ return point.x === panelState.panelHeight && point.y === orientedShell.height;
15201534 }
15211535 }
15221536
tests/qmltests/tst_Shell.qml+22−15
@@ -61,14 +61,21 @@ Rectangle {
6161 onShellChanged: {
6262 if (shell) {
6363 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
64− appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
64+ panelState = testCase.findInvisibleChild(shell, "panelState");
6565 } else {
6666 topLevelSurfaceList = null;
67− appMenuData.surfaceManager = null;
67+ panelState = undefined;
6868 }
6969 }
7070
7171 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
7279
7380 Item {
7481 id: shellContainer
@@ -261,6 +268,7 @@ Rectangle {
261268 anchors { left: parent.left; right: parent.right }
262269 activeFocusOnPress: false
263270 model: ["phone", "tablet", "desktop"]
271+ selectedIndex: 2
264272 onSelectedIndexChanged: {
265273 shellLoader.state = model[selectedIndex];
266274 }
@@ -274,6 +282,7 @@ Rectangle {
274282 anchors { left: parent.left; right: parent.right }
275283 activeFocusOnPress: false
276284 model: ["phone", "tablet", "desktop"]
285+ selectedIndex: 2
277286 }
278287 MouseTouchEmulationCheckbox {
279288 id: mouseEmulation
@@ -680,9 +689,7 @@ Rectangle {
680689 }
681690
682691 function ensureInputMethodSurface() {
683− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
684− verify(surfaceManager);
685− surfaceManager.createInputMethodSurface();
692+ SurfaceManager.createInputMethodSurface();
686693
687694 tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
688695 }
@@ -1868,21 +1875,21 @@ Rectangle {
18681875 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
18691876
18701877 tryCompare(appDelegate, "state", "normal");
1871− tryCompare(PanelState, "decorationsVisible", false)
1878+ tryCompare(panelState, "decorationsVisible", false)
18721879
18731880 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
18741881 tryCompare(appDelegate, "state", "maximized");
1875− tryCompare(PanelState, "decorationsVisible", true)
1882+ tryCompare(panelState, "decorationsVisible", true)
18761883
18771884 ApplicationManager.stopApplication(application.appId);
1878− tryCompare(PanelState, "decorationsVisible", false)
1885+ tryCompare(panelState, "decorationsVisible", false)
18791886
18801887 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
18811888 // They won't be gone until those surface items are destroyed.
18821889 tryCompareFunction(function() { return application.surfaceList.count }, 0);
18831890
18841891 ApplicationManager.startApplication(application.appId);
1885− tryCompare(PanelState, "decorationsVisible", true)
1892+ tryCompare(panelState, "decorationsVisible", true)
18861893 }
18871894
18881895 function test_newAppHasValidGeometry() {
@@ -1939,7 +1946,7 @@ Rectangle {
19391946 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
19401947 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
19411948
1942− compare(appDelegate.y >= PanelState.panelHeight, true);
1949+ compare(appDelegate.y >= panelState.panelHeight, true);
19431950 }
19441951
19451952 function test_cantResizeWindowUnderPanel() {
@@ -1967,7 +1974,7 @@ Rectangle {
19671974 mouseMove(decoration, decoration.width/2, -units.gu(100));
19681975
19691976 // verify we don't go past the panel
1970− compare(appDelegate.y >= PanelState.panelHeight, true);
1977+ compare(appDelegate.y >= panelState.panelHeight, true);
19711978 }
19721979
19731980 function test_restoreWindowStateFixesIfUnderPanel() {
@@ -1991,7 +1998,7 @@ Rectangle {
19911998 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
19921999
19932000 appDelegate = appRepeater.itemAt(0);
1994− compare(appDelegate.y >= PanelState.panelHeight, true);
2001+ compare(appDelegate.y >= panelState.panelHeight, true);
19952002 }
19962003
19972004 function test_lifecyclePolicyForNonTouchApp_data() {
@@ -2639,7 +2646,7 @@ Rectangle {
26392646 function test_oskDisplacesWindow_data() {
26402647 return [
26412648 {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},
26432650 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
26442651 ]
26452652 }
@@ -2662,8 +2669,8 @@ Rectangle {
26622669 dashAppDelegate.windowedY = data.windowY;
26632670 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
26642671 var initialY = dashAppDelegate.y;
2665− print("intial", initialY, "panel", PanelState.panelHeight);
2666− verify(initialY > PanelState.panelHeight);
2672+ print("intial", initialY, "panel", panelState.panelHeight);
2673+ verify(initialY > panelState.panelHeight);
26672674
26682675 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
26692676 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
tests/qmltests/tst_ShellApplication.qmlnew file+162
@@ -0,0 +1,162 @@
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 "../../qml/Components"
26+import "Stage"
27+
28+Rectangle {
29+ id: root
30+ color: "grey"
31+ width: units.gu(100)
32+ height: units.gu(71)
33+
34+ QtObject {
35+ id: args
36+ property bool hasFullscreen: false
37+ property bool hasFrameless: false
38+ property bool hasGeometry: true
39+ property size windowGeometry: Qt.size(root.width, root.height)
40+ property string deviceName: "desktop"
41+ property string mode: "full-greeter"
42+ }
43+ property alias applicationArguments: args
44+
45+ Component.onCompleted: {
46+ // must set the mock mode before loading the Shell
47+ LightDMController.userMode = "single";
48+ }
49+
50+ Flickable {
51+ id: controls
52+ contentHeight: controlRect.height
53+
54+ anchors.top: root.top
55+ anchors.bottom: root.bottom
56+ anchors.right: root.right
57+ width: units.gu(30)
58+
59+ Rectangle {
60+ id: controlRect
61+ anchors { left: parent.left; right: parent.right }
62+ color: "darkgrey"
63+ height: childrenRect.height + units.gu(2)
64+
65+ Column {
66+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
67+ spacing: units.gu(1)
68+
69+ Flow {
70+ spacing: units.gu(1)
71+ anchors { left: parent.left; right: parent.right }
72+
73+ Button {
74+ text: "Show Greeter"
75+ activeFocusOnPress: false
76+ onClicked: {
77+ LightDM.Greeter.showGreeter();
78+ }
79+ }
80+ Button {
81+ text: "Hide Greeter"
82+ activeFocusOnPress: false
83+ onClicked: {
84+ LightDM.Greeter.hideGreeter()
85+ }
86+ }
87+ }
88+
89+ MouseTouchEmulationCheckbox {
90+ id: mouseEmulation
91+ checked: true
92+ }
93+
94+ Row {
95+ anchors { left: parent.left; right: parent.right }
96+ CheckBox {
97+ id: autohideLauncherCheckbox
98+ onCheckedChanged: {
99+ GSettingsController.setAutohideLauncher(checked)
100+ }
101+ }
102+ Label {
103+ text: "Autohide launcher"
104+ }
105+ }
106+
107+ Label { text: "Applications"; font.bold: true }
108+
109+ Button {
110+ text: "Start all apps"
111+ width: parent.width
112+ activeFocusOnPress: false
113+ onClicked: {
114+ for (var i = 0; i < ApplicationManager.availableApplications.length; i++) {
115+ var appId = ApplicationManager.availableApplications[i];
116+ ApplicationManager.startApplication(appId)
117+ }
118+ }
119+ }
120+
121+ Repeater {
122+ id: appCheckBoxRepeater
123+ model: ApplicationManager.availableApplications
124+ ApplicationCheckBox {
125+ appId: modelData
126+ }
127+ }
128+
129+ Label {
130+ text: "Fingerprint"
131+ }
132+ Row {
133+ Button {
134+ text: "Success"
135+ onClicked: {
136+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
137+ var uid = 0;
138+ for (var i = 0; i < LightDM.Users.count; i++) {
139+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == AccountsService.user) {
140+ uid = LightDM.Users.data(i, LightDM.UserRoles.UidRole);
141+ break;
142+ }
143+ }
144+ biometryd.operation.mockSuccess(uid);
145+ }
146+ }
147+
148+ Button {
149+ text: "Failure"
150+ onClicked: {
151+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
152+ biometryd.operation.mockFailure("error");
153+ }
154+ }
155+ }
156+ }
157+ }
158+ }
159+
160+ ShellApplication {
161+ }
162+}
tests/uqmlscene/main.cpp+2
@@ -479,6 +479,8 @@ int main(int argc, char ** argv)
479479 // TODO: as soon as the engine construction completes, the debug service is
480480 // listening for connections. But actually we aren't ready to debug anything.
481481 QQmlEngine engine;
482+ engine.rootContext()->setContextProperty("DebuggingController", new DebuggingController(&app));
483+
482484 QQmlComponent *component = new QQmlComponent(&engine);
483485 for (int i = 0; i < imports.size(); ++i)
484486 engine.addImportPath(imports.at(i));