on acb5d8b help sign in
CMakeLists.txt+5−1
since splitsnapshots
unifiedsplit
@@ -71,6 +71,7 @@ find_package(Qt5Concurrent 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
72
72
73pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
73pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
74+pkg_check_modules(QTMIRSERVER REQUIRED qtmirserver>=0.6.0)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
77pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
@@ -79,6 +80,7 @@ pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
79pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
80pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
80pkg_check_modules(UAL REQUIRED ubuntu-app-launch-3)
81pkg_check_modules(UAL REQUIRED ubuntu-app-launch-3)
81pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
82pkg_check_modules(UBUNTUGESTURES REQUIRED UbuntuGestures)
83+pkg_check_modules(MIRAL REQUIRED miral)
82
84
83### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
85### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
84find_path(UBUNTUGESTUREPRIV
86find_path(UBUNTUGESTUREPRIV
@@ -113,7 +115,9 @@ if(SHELL_PLUGINDIR STREQUAL "")
113 message(FATAL_ERROR "Could not determine plugin import dir.")
115 message(FATAL_ERROR "Could not determine plugin import dir.")
114endif()
116endif()
115
117
116−set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
118+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-permissive -pedantic -Wall -Wextra")
119+
120+set (CMAKE_CXX_STANDARD 14)
117
121
118if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
122if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
119 option(Werror "Treat warnings as errors" ON)
123 option(Werror "Treat warnings as errors" ON)
debian/control+7−1
@@ -31,6 +31,7 @@ Build-Depends: cmake,
31 libqmenumodel-dev (>= 0.2.12),
31 libqmenumodel-dev (>= 0.2.12),
32 libqt5svg5-dev,
32 libqt5svg5-dev,
33 libqt5xmlpatterns5-dev,
33 libqt5xmlpatterns5-dev,
34+ libqtmirserver-dev (>= 0.6.0),
34 libsystemsettings-dev,
35 libsystemsettings-dev,
35 libubuntu-app-launch3-dev,
36 libubuntu-app-launch3-dev,
36 libubuntu-download-manager-common-dev,
37 libubuntu-download-manager-common-dev,
@@ -38,13 +39,14 @@ Build-Depends: cmake,
38 libubuntugestures5-private-dev (>= 1.3.2030),
39 libubuntugestures5-private-dev (>= 1.3.2030),
39 libudev-dev,
40 libudev-dev,
40 libudm-common-dev,
41 libudm-common-dev,
41− libunity-api-dev (>= 8.6),
42+ libunity-api-dev (>= 8.7),
42 libusermetricsoutput1-dev,
43 libusermetricsoutput1-dev,
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44 libx11-dev[!arm64 !armhf],
45 libx11-dev[!arm64 !armhf],
45 libxcb1-dev[!arm64 !armhf],
46 libxcb1-dev[!arm64 !armhf],
46 libxi-dev[!arm64 !armhf],
47 libxi-dev[!arm64 !armhf],
47# End of X11 libs
48# End of X11 libs
49+ mirtest-dev,
48 pkg-config,
50 pkg-config,
49 python3-all:any,
51 python3-all:any,
50 python3-setuptools,
52 python3-setuptools,
@@ -70,6 +72,9 @@ Build-Depends: cmake,
70 qtdeclarative5-ubuntu-settings-components (>= 0.11),
72 qtdeclarative5-ubuntu-settings-components (>= 0.11),
71 ttf-ubuntu-font-family,
73 ttf-ubuntu-font-family,
72 xvfb,
74 xvfb,
75+# mirtest pkgconfig requires these, but doesn't have a deb dependency. Bug lp:1633537
76+ libboost-filesystem-dev,
77+ libboost-system-dev,
73Standards-Version: 3.9.4
78Standards-Version: 3.9.4
74Homepage: http://launchpad.net/unity
79Homepage: http://launchpad.net/unity
75# If you aren't a member of ~unity-team but need to upload
80# If you aren't a member of ~unity-team but need to upload
@@ -131,6 +136,7 @@ Depends: dmz-cursor-theme,
131 qml-module-ubuntu-web,
136 qml-module-ubuntu-web,
132 qtdeclarative5-qtmir-plugin (>= 0.4.8),
137 qtdeclarative5-qtmir-plugin (>= 0.4.8),
133 qtdeclarative5-ubuntu-telephony0.1,
138 qtdeclarative5-ubuntu-telephony0.1,
139+ qtmir-desktop (>= 0.6.0) | qtmir-android (>= 0.6.0),
134 ubuntu-system-settings (>= 0.4),
140 ubuntu-system-settings (>= 0.4),
135 unity-launcher-impl-12,
141 unity-launcher-impl-12,
136 unity8-common (= ${source:Version}),
142 unity8-common (= ${source:Version}),
debian/unity8.install+5
@@ -16,7 +16,12 @@ usr/share/unity8/Rotation
16usr/share/unity8/DeviceConfiguration.qml
16usr/share/unity8/DeviceConfiguration.qml
17usr/share/unity8/OrientedShell.qml
17usr/share/unity8/OrientedShell.qml
18usr/share/unity8/DisabledScreenNotice.qml
18usr/share/unity8/DisabledScreenNotice.qml
19+usr/share/unity8/ErrorApplication.qml
20+usr/share/unity8/qmldir
19usr/share/unity8/Shell.qml
21usr/share/unity8/Shell.qml
22+usr/share/unity8/ShellApplication.qml
23+usr/share/unity8/ShellNotifier.qml
24+usr/share/unity8/ShellScreen.qml
20usr/share/unity8/Stage
25usr/share/unity8/Stage
21usr/share/unity8/Tutorial
26usr/share/unity8/Tutorial
22usr/share/unity8/Wizard
27usr/share/unity8/Wizard
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 CursorImageInfo.cpp
15 CursorImageInfo.cpp
16 CursorImageProvider.cpp
16 CursorImageProvider.cpp
17 MousePointer.cpp
17 MousePointer.cpp
18+ InputDispatcherFilter.cpp
18 # We need to run moc on this header
19 # We need to run moc on this header
19 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 )
21 )
plugins/Cursor/InputDispatcherFilter.cppnew file+175
@@ -0,0 +1,175 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "InputDispatcherFilter.h"
18+#include "MousePointer.h"
19+
20+#include <QEvent>
21+#include <QGuiApplication>
22+#include <QQuickWindow>
23+#include <QScreen>
24+#include <QtMath>
25+#include <qpa/qplatformnativeinterface.h>
26+#include <qpa/qplatformscreen.h>
27+
28+InputDispatcherFilter *InputDispatcherFilter::instance()
29+{
30+ static InputDispatcherFilter filter;
31+ return &filter;
32+}
33+
34+InputDispatcherFilter::InputDispatcherFilter(QObject *parent)
35+ : QObject(parent)
36+ , m_pushing(false)
37+{
38+ QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface();
39+ m_inputDispatcher = static_cast<QObject*>(ni->nativeResourceForIntegration("InputDispatcher"));
40+ if (m_inputDispatcher) {
41+ m_inputDispatcher->installEventFilter(this);
42+ }
43+}
44+
45+void InputDispatcherFilter::registerPointer(MousePointer *pointer)
46+{
47+ // allow first registered pointer to be visible.
48+ m_pointers.insert(pointer);
49+}
50+
51+void InputDispatcherFilter::unregisterPointer(MousePointer *pointer)
52+{
53+ m_pointers.remove(pointer);
54+}
55+
56+bool InputDispatcherFilter::eventFilter(QObject *o, QEvent *e)
57+{
58+ if (o != m_inputDispatcher) return false;
59+
60+ switch (e->type()) {
61+ case QEvent::MouseMove:
62+ case QEvent::MouseButtonPress:
63+ case QEvent::MouseButtonRelease:
64+ {
65+ // if we don't have any pointers, filter all mouse events.
66+ auto pointer = currentPointer();
67+ if (!pointer) return true;
68+
69+ QMouseEvent* me = static_cast<QMouseEvent*>(e);
70+
71+ // Local position gives relative change of mouse pointer.
72+ QPointF movement = me->localPos();
73+
74+ // Adjust the position
75+ QPointF oldPos = pointer->window()->geometry().topLeft() + pointer->position();
76+ QPointF newPos = adjustedPositionForMovement(oldPos, movement);
77+
78+ QScreen* currentScreen = screenAt(newPos);
79+ if (currentScreen) {
80+ QRect screenRect = currentScreen->geometry();
81+ qreal newX = (oldPos + movement).x();
82+ qreal newY = (oldPos + movement).y();
83+
84+ if (newX <= screenRect.left() && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top left corner
85+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY- screenRect.top() - pointer->topBoundaryOffset(), 2));
86+ Q_EMIT pushedTopLeftCorner(currentScreen, qAbs(distance), me->buttons());
87+ m_pushing = true;
88+ } else if (newX >= screenRect.right()-1 && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top right corner
89+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY - screenRect.top() - pointer->topBoundaryOffset(), 2));
90+ Q_EMIT pushedTopRightCorner(currentScreen, qAbs(distance), me->buttons());
91+ m_pushing = true;
92+ } else if (newX < 0 && newY >= screenRect.bottom()-1) { // bottom left corner
93+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-screenRect.bottom(), 2));
94+ Q_EMIT pushedBottomLeftCorner(currentScreen, qAbs(distance), me->buttons());
95+ m_pushing = true;
96+ } else if (newX >= screenRect.right()-1 && newY >= screenRect.bottom()-1) { // bottom right corner
97+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY-screenRect.bottom(), 2));
98+ Q_EMIT pushedBottomRightCorner(currentScreen, qAbs(distance), me->buttons());
99+ m_pushing = true;
100+ } else if (newX < screenRect.left()) { // left edge
101+ Q_EMIT pushedLeftBoundary(currentScreen, qAbs(newX), me->buttons());
102+ m_pushing = true;
103+ } else if (newX >= screenRect.right()) { // right edge
104+ Q_EMIT pushedRightBoundary(currentScreen, newX - (screenRect.right() - 1), me->buttons());
105+ m_pushing = true;
106+ } else if (newY < screenRect.top() + pointer->topBoundaryOffset()) { // top edge
107+ Q_EMIT pushedTopBoundary(currentScreen, qAbs(newY - screenRect.top() - pointer->topBoundaryOffset()), me->buttons());
108+ m_pushing = true;
109+ } else if (Q_LIKELY(newX > 0 && newX < screenRect.right()-1 && newY > 0 && newY < screenRect.bottom()-1)) { // normal pos, not pushing
110+ if (m_pushing) {
111+ Q_EMIT pushStopped(currentScreen);
112+ m_pushing = false;
113+ }
114+ }
115+ }
116+
117+ // Send the event
118+ QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
119+ eCopy.setTimestamp(me->timestamp());
120+ o->event(&eCopy);
121+ return true;
122+ }
123+ default:
124+ break;
125+ }
126+ return false;
127+}
128+
129+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
130+{
131+ QPointF adjusted = pt + movement;
132+
133+ auto screen = screenAt(adjusted); // first check if our move was to a screen with an enabled pointer.
134+ if (screen) {
135+ return adjusted;
136+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
137+ QRectF screenBounds = screen->geometry();
138+ // bound the new position to the old screen geometry
139+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
140+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
141+ } else {
142+ auto screens = QGuiApplication::screens();
143+
144+ // center of first screen with a pointer.
145+ Q_FOREACH(QScreen* screen, screens) {
146+ Q_FOREACH(MousePointer* pointer, m_pointers) {
147+ if (pointer->isEnabled() && pointer->screen() == screen) {
148+ return screen->geometry().center();
149+ }
150+ }
151+ }
152+ }
153+ return adjusted;
154+}
155+
156+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
157+{
158+ Q_FOREACH(MousePointer* pointer, m_pointers) {
159+ if (!pointer->isEnabled()) continue;
160+
161+ QScreen* screen = pointer->screen();
162+ if (screen && screen->geometry().contains(pt.toPoint()))
163+ return screen;
164+ }
165+ return nullptr;
166+}
167+
168+MousePointer *InputDispatcherFilter::currentPointer() const
169+{
170+ Q_FOREACH(MousePointer* pointer, m_pointers) {
171+ if (!pointer->isEnabled()) continue;
172+ return pointer;
173+ }
174+ return nullptr;
175+}
plugins/Cursor/InputDispatcherFilter.hnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef INPUTDISPATCHERFILTER_H
18+#define INPUTDISPATCHERFILTER_H
19+
20+#include <QObject>
21+#include <QPointF>
22+#include <QSet>
23+
24+class MousePointer;
25+class QScreen;
26+
27+class InputDispatcherFilter : public QObject
28+{
29+ Q_OBJECT
30+public:
31+ static InputDispatcherFilter *instance();
32+
33+ void registerPointer(MousePointer* pointer);
34+ void unregisterPointer(MousePointer* pointer);
35+
36+Q_SIGNALS:
37+ void pushedLeftBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
38+ void pushedRightBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
39+ void pushedTopBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
40+ void pushedTopLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
41+ void pushedTopRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
42+ void pushedBottomLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
43+ void pushedBottomRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
44+ void pushStopped(QScreen* screen);
45+
46+protected:
47+ InputDispatcherFilter(QObject* parent = nullptr);
48+
49+ bool eventFilter(QObject *o, QEvent *e) override;
50+
51+ QPointF adjustedPositionForMovement(const QPointF& pt, const QPointF& movement) const;
52+ QScreen* screenAt(const QPointF& pt) const;
53+
54+ MousePointer* currentPointer() const;
55+
56+private:
57+ QObject* m_inputDispatcher;
58+ QSet<MousePointer*> m_pointers;
59+ bool m_pushing;
60+};
61+
62+#endif // INPUTDISPATCHERFILTER_H
plugins/Cursor/MousePointer.cpp+63−78
@@ -16,84 +16,74 @@
16
16
17#include "MousePointer.h"
17#include "MousePointer.h"
18#include "CursorImageProvider.h"
18#include "CursorImageProvider.h"
19−
19+#include "InputDispatcherFilter.h"
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
22
20
23#include <QQuickWindow>
21#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
26
22
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
28
25
29MousePointer::MousePointer(QQuickItem *parent)
26MousePointer::MousePointer(QQuickItem *parent)
30 : MirMousePointerInterface(parent)
27 : MirMousePointerInterface(parent)
31 , m_cursorName(QStringLiteral("left_ptr"))
28 , m_cursorName(QStringLiteral("left_ptr"))
32 , m_themeName(QStringLiteral("default"))
29 , m_themeName(QStringLiteral("default"))
33{
30{
34−}
31+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
35−
32+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
33+ if (window() && window()->screen() == screen) {
37− Qt::KeyboardModifiers modifiers)
34+ Q_EMIT pushedLeftBoundary(amount, buttons);
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
44− Q_EMIT mouseMoved();
45− }
35+ }
46−
36+ });
47− m_accumulatedMovement += movement;
37+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
48− // don't apply the fractional part
38+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
39+ if (window() && window()->screen() == screen) {
50− m_accumulatedMovement -= appliedMovement;
40+ Q_EMIT pushedRightBoundary(amount, buttons);
51−
41+ }
52− qreal newX = x() + appliedMovement.x();
42+ });
53− qreal newY = y() + appliedMovement.y();
43+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
54− const qreal sceneWidth = parentItem()->width();
44+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
55− const qreal sceneHeight = parentItem()->height();
45+ if (window() && window()->screen() == screen) {
56−
46+ Q_EMIT pushedTopBoundary(amount, buttons);
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
47+ }
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
48+ });
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
49+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
60− m_pushing = true;
50+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
51+ if (window() && window()->screen() == screen) {
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
52+ Q_EMIT pushedTopLeftCorner(amount, buttons);
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
53+ }
64− m_pushing = true;
54+ });
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
55+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
56+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
57+ if (window() && window()->screen() == screen) {
68− m_pushing = true;
58+ Q_EMIT pushedTopRightCorner(amount, buttons);
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
59+ }
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
60+ });
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
61+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
72− m_pushing = true;
62+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
73− } else if (newX < 0) { // left edge
63+ if (window() && window()->screen() == screen) {
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
64+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
75− m_pushing = true;
65+ }
76− } else if (newX >= sceneWidth) { // right edge
66+ });
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
67+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
78− m_pushing = true;
68+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
79− } else if (newY < m_topBoundaryOffset) { // top edge
69+ if (window() && window()->screen() == screen) {
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
70+ Q_EMIT pushedBottomRightCorner(amount, buttons);
81− m_pushing = true;
71+ }
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
72+ });
83− if (m_pushing) {
73+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
74+ this, [this](QScreen* screen) {
75+ if (window() && window()->screen() == screen) {
84 Q_EMIT pushStopped();
76 Q_EMIT pushStopped();
85− m_pushing = false;
86 }
77 }
87− }
78+ });
88
79
89− applyItemConfinement(newX, newY);
80+ InputDispatcherFilter::instance()->registerPointer(this);
90−
81+}
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
93
82
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
83+MousePointer::~MousePointer()
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
84+{
96− buttons, modifiers);
85+ registerScreen(nullptr);
86+ InputDispatcherFilter::instance()->unregisterPointer(this);
97}
87}
98
88
99void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
89void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121 }
111 }
122}
112}
123
113
124−void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
125−{
126− if (!parentItem()) {
127− return;
128− }
129−
130− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
131− QWindowSystemInterface::handleWheelEvent(window(), timestamp, scenePosition /* local */, scenePosition /* global */,
132− QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
133−}
134−
135int MousePointer::topBoundaryOffset() const
114int MousePointer::topBoundaryOffset() const
136{
115{
137 return m_topBoundaryOffset;
116 return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
182 if (m_registeredScreen) {
161 if (m_registeredScreen) {
183 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
162 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184 if (previousCursor) {
163 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
164+ previousCursor->unregisterMousePointer(this);
186 } else {
165 } else {
187 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
166 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188 }
167 }
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
193 if (m_registeredScreen) {
172 if (m_registeredScreen) {
194 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
173 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195 if (cursor) {
174 if (cursor) {
196− cursor->setMousePointer(this);
175+ cursor->registerMousePointer(this);
197 } else {
176 } else {
198 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
177 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199 }
178 }
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
216 }
195 }
217}
196}
218
197
198+void MousePointer::moveTo(const QPoint &position)
199+{
200+ setPosition(position);
201+ Q_EMIT mouseMoved();
202+}
203+
219void MousePointer::setCustomCursor(const QCursor &customCursor)
204void MousePointer::setCustomCursor(const QCursor &customCursor)
220{
205{
221 CursorImageProvider::instance()->setCustomCursor(customCursor);
206 CursorImageProvider::instance()->setCustomCursor(customCursor);
plugins/Cursor/MousePointer.h+5−4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
32public:
32public:
33 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
34
35
35 void setCursorName(const QString &qtCursorName) override;
36 void setCursorName(const QString &qtCursorName) override;
36 QString cursorName() const override { return m_cursorName; }
37 QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ public:
38 void setThemeName(const QString &themeName) override;
39 void setThemeName(const QString &themeName) override;
39 QString themeName() const override { return m_themeName; }
40 QString themeName() const override { return m_themeName; }
40
41
42+ void moveTo(const QPoint& position) override;
43+
41 void setCustomCursor(const QCursor &) override;
44 void setCustomCursor(const QCursor &) override;
42
45
43 QQuickItem* confiningItem() const;
46 QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ public:
46 int topBoundaryOffset() const;
49 int topBoundaryOffset() const;
47 void setTopBoundaryOffset(int topBoundaryOffset);
50 void setTopBoundaryOffset(int topBoundaryOffset);
48
51
49−public Q_SLOTS:
52+ QScreen* screen() const { return m_registeredScreen; }
50− void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
51− Qt::KeyboardModifiers modifiers) override;
52− void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers) override;
53
53
54Q_SIGNALS:
54Q_SIGNALS:
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private:
79 QPointer<QScreen> m_registeredScreen;
79 QPointer<QScreen> m_registeredScreen;
80 QString m_cursorName;
80 QString m_cursorName;
81 QString m_themeName;
81 QString m_themeName;
82+ bool m_active;
82
83
83 // Accumulated, unapplied, mouse movement.
84 // Accumulated, unapplied, mouse movement.
84 QPointF m_accumulatedMovement;
85 QPointF m_accumulatedMovement;
plugins/GlobalShortcut/globalshortcut.cpp−15
@@ -56,11 +56,6 @@ void GlobalShortcut::setActive(bool active)
56 Q_EMIT activeChanged(active);
56 Q_EMIT activeChanged(active);
57}
57}
58
58
59−void GlobalShortcut::componentComplete()
60−{
61− connect(this, &QQuickItem::windowChanged, this, &GlobalShortcut::setupFilterOnWindow);
62−}
63−
64void GlobalShortcut::keyPressEvent(QKeyEvent * event)
59void GlobalShortcut::keyPressEvent(QKeyEvent * event)
65{
60{
66 if (!m_active) return;
61 if (!m_active) return;
@@ -76,13 +71,3 @@ void GlobalShortcut::keyReleaseEvent(QKeyEvent * event)
76 event->accept();
71 event->accept();
77 Q_EMIT released(m_shortcut.toString());
72 Q_EMIT released(m_shortcut.toString());
78}
73}
79−
80−void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
81−{
82− if (!window) {
83−// qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
84− return;
85− }
86−
87− registry->setupFilterOnWindow((qulonglong) window->winId());
88−}
plugins/GlobalShortcut/globalshortcut.h−4
@@ -53,7 +53,6 @@ public:
53 void setActive(bool active);
53 void setActive(bool active);
54
54
55protected:
55protected:
56− void componentComplete() override;
57 void keyPressEvent(QKeyEvent * event) override;
56 void keyPressEvent(QKeyEvent * event) override;
58 void keyReleaseEvent(QKeyEvent * event) override;
57 void keyReleaseEvent(QKeyEvent * event) override;
59
58
@@ -69,9 +68,6 @@ Q_SIGNALS:
69 void released(const QString &shortcut);
68 void released(const QString &shortcut);
70 void activeChanged(bool active);
69 void activeChanged(bool active);
71
70
72−private Q_SLOTS:
73− void setupFilterOnWindow(QQuickWindow* window);
74−
75private:
71private:
76 QVariant m_shortcut;
72 QVariant m_shortcut;
77 bool m_active = true;
73 bool m_active = true;
plugins/GlobalShortcut/globalshortcutregistry.cpp+7−16
@@ -21,11 +21,11 @@
21
21
22#include "globalshortcutregistry.h"
22#include "globalshortcutregistry.h"
23
23
24−static qulonglong s_windowId = 0;
25−
26GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
24GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
27 : QObject(parent)
25 : QObject(parent)
28{
26{
27+ connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &GlobalShortcutRegistry::setupFilterOnWindow);
28+ setupFilterOnWindow(qGuiApp->focusWindow());
29}
29}
30
30
31GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
31GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
@@ -90,7 +90,7 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
90 if (m_shortcuts.contains(seq)) {
90 if (m_shortcuts.contains(seq)) {
91 const auto shortcuts = m_shortcuts.value(seq);
91 const auto shortcuts = m_shortcuts.value(seq);
92 Q_FOREACH(const auto &shortcut, shortcuts) {
92 Q_FOREACH(const auto &shortcut, shortcuts) {
93− if (shortcut) {
93+ if (shortcut && shortcut->window() == obj) {
94 qApp->sendEvent(shortcut, &eCopy);
94 qApp->sendEvent(shortcut, &eCopy);
95 }
95 }
96 }
96 }
@@ -102,24 +102,15 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
102 return QObject::eventFilter(obj, event);
102 return QObject::eventFilter(obj, event);
103}
103}
104
104
105−void GlobalShortcutRegistry::setupFilterOnWindow(qulonglong wid)
105+void GlobalShortcutRegistry::setupFilterOnWindow(QWindow* window)
106{
106{
107− if (wid == s_windowId) {
108− return;
109− }
110−
111 if (m_filteredWindow) {
107 if (m_filteredWindow) {
112 m_filteredWindow->removeEventFilter(this);
108 m_filteredWindow->removeEventFilter(this);
113 m_filteredWindow.clear();
109 m_filteredWindow.clear();
114− s_windowId = 0;
115 }
110 }
116
111
117− Q_FOREACH(QWindow *window, qApp->allWindows()) {
118− if (window && window->winId() == wid) {
112+ if (window) {
119− m_filteredWindow = window;
113+ m_filteredWindow = window;
120− window->installEventFilter(this);
114+ window->installEventFilter(this);
121− s_windowId = wid;
122− break;
123− }
124 }
115 }
125}
116}
plugins/GlobalShortcut/globalshortcutregistry.h+2−2
@@ -52,9 +52,9 @@ public:
52 void addShortcut(const QVariant &seq, GlobalShortcut * sc);
52 void addShortcut(const QVariant &seq, GlobalShortcut * sc);
53
53
54 /**
54 /**
55− * Sets up key events filtering on window @p wid
55+ * Sets up key events filtering on window @p window
56 */
56 */
57− void setupFilterOnWindow(qulonglong wid);
57+ void setupFilterOnWindow(QWindow* window);
58
58
59protected:
59protected:
60 bool eventFilter(QObject *obj, QEvent *event) override;
60 bool eventFilter(QObject *obj, QEvent *event) override;
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
22void UInputPlugin::registerTypes(const char *uri)
28void UInputPlugin::registerTypes(const char *uri)
23{
29{
24 Q_ASSERT(uri == QLatin1String("UInput"));
30 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
32}
plugins/Unity/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1add_subdirectory(ApplicationMenu)
1add_subdirectory(ApplicationMenu)
2add_subdirectory(Connectivity)
2add_subdirectory(Connectivity)
3+add_subdirectory(Debug)
3add_subdirectory(Indicators)
4add_subdirectory(Indicators)
4add_subdirectory(Launcher)
5add_subdirectory(Launcher)
5add_subdirectory(Session)
6add_subdirectory(Session)
plugins/Unity/Debug/CMakeLists.txtnew file+30
@@ -0,0 +1,30 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+)
5+
6+include_directories(
7+ SYSTEM
8+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
9+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10+ ${GIO_INCLUDE_DIRS}
11+)
12+
13+set(QMLPLUGIN_SRC
14+ consolelog.cpp
15+ plugin.cpp
16+ )
17+
18+add_library(Debug-qml SHARED
19+ ${QMLPLUGIN_SRC}
20+ )
21+
22+# Because this is an internal support library, we want
23+# to expose all symbols in it. Consider changing this
24+# either to a static library or just using the
25+# files directly in targets.
26+set_target_properties(Debug-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
27+
28+qt5_use_modules(Debug-qml Qml Quick Concurrent)
29+
30+add_unity8_plugin(Unity.Debug 0.1 Unity/Debug TARGETS Debug-qml)
plugins/Unity/Debug/consolelog.cppnew file+221
@@ -0,0 +1,221 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "consolelog.h"
18+
19+#include <unistd.h>
20+#include <stdio.h>
21+#include <thread>
22+#include <iostream>
23+#include <sys/ioctl.h>
24+
25+#include <functional>
26+
27+namespace
28+{
29+
30+int wait_fd(std::function<int(void)> fn)
31+{
32+ int ret = -1;
33+ bool fd_blocked = false;
34+ do
35+ {
36+ ret = fn();
37+ fd_blocked = (errno == EINTR || errno == EBUSY);
38+ if (fd_blocked)
39+ QThread::msleep(10);
40+ }
41+ while (ret < 0);
42+ return ret;
43+}
44+
45+#define PIPE_WAIT(val) wait_fd(std::bind(pipe, val))
46+#define DUP_WAIT(fd) wait_fd(std::bind(dup, fd))
47+#define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2))
48+#define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd))
49+
50+}
51+
52+LogRedirector::LogRedirector()
53+ : m_ref(0)
54+ , m_stop(false)
55+{
56+ setObjectName("ConsoleLog");
57+
58+ // make stdout & stderr streams unbuffered
59+ // so that we don't need to flush the streams
60+ // before capture and after capture
61+ // (fflush can cause a deadlock if the stream is currently being used)
62+ setvbuf(stdout,NULL,_IONBF,0);
63+ setvbuf(stderr,NULL,_IONBF,0);
64+}
65+
66+void LogRedirector::run()
67+{
68+ PIPE_WAIT(m_pipe);
69+
70+ int oldStdOut = DUP_WAIT(fileno(stdout));
71+ int oldStdErr = DUP_WAIT(fileno(stderr));
72+
73+ DUP2_WAIT(m_pipe[WRITE], fileno(stdout));
74+ DUP2_WAIT(m_pipe[WRITE], fileno(stderr));
75+ CLOSE_WAIT(m_pipe[WRITE]);
76+
77+ while(true) {
78+ {
79+ QMutexLocker lock(&m_mutex);
80+ if (m_stop) break;
81+ }
82+ checkLog();
83+ QThread::msleep(50);
84+ }
85+
86+ DUP2_WAIT(oldStdOut, fileno(stdout));
87+ DUP2_WAIT(oldStdErr, fileno(stderr));
88+
89+ CLOSE_WAIT(oldStdOut);
90+ CLOSE_WAIT(oldStdErr);
91+ CLOSE_WAIT(m_pipe[READ]);
92+}
93+
94+
95+void LogRedirector::checkLog()
96+{
97+ // Do not allow read to block with no data.
98+ // If we stop the thread while waiting a read,
99+ // it will block the main thread waiting for this thread to stop.
100+ int count = 0;
101+ ioctl(m_pipe[READ], FIONREAD, &count);
102+ if (count <= 0) return;
103+
104+ std::string captured;
105+ std::string buf;
106+ const int bufSize = 1024;
107+ buf.resize(bufSize);
108+ int bytesRead = 0;
109+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
110+ while(bytesRead == bufSize)
111+ {
112+ captured += buf;
113+ bytesRead = 0;
114+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
115+ }
116+ if (bytesRead > 0)
117+ {
118+ buf.resize(bytesRead);
119+ captured += buf;
120+ }
121+
122+ if (!captured.empty()) {
123+ Q_EMIT log(QString::fromStdString(captured));
124+ }
125+}
126+
127+LogRedirector *LogRedirector::instance()
128+{
129+ static LogRedirector* log = nullptr;
130+ if (!log) {
131+ log = new LogRedirector();
132+ }
133+ return log;
134+}
135+
136+void LogRedirector::add(ConsoleLog* logger)
137+{
138+ QMutexLocker lock(&m_mutex);
139+ connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection);
140+
141+ m_ref++;
142+ if (!LogRedirector::instance()->isRunning()) {
143+ m_stop = false;
144+ LogRedirector::instance()->start();
145+ }
146+}
147+
148+void LogRedirector::remove(ConsoleLog* logger)
149+{
150+ QMutexLocker lock(&m_mutex);
151+ disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged);
152+
153+ m_ref = qMax(m_ref-1, 0);
154+ if (m_ref == 0 && LogRedirector::instance()->isRunning()) {
155+ m_stop = true;
156+ lock.unlock();
157+ LogRedirector::instance()->wait();
158+ }
159+}
160+
161+ConsoleLog::ConsoleLog(QObject *parent)
162+ : QObject(parent)
163+ , m_enabled(false)
164+ , m_maxLines(60)
165+{
166+ auto updateEnabled = [this]() {
167+ if (m_enabled) {
168+ LogRedirector::instance()->add(this);
169+ } else {
170+ LogRedirector::instance()->remove(this);
171+ }
172+ };
173+ connect(this, &ConsoleLog::enabledChanged, this, updateEnabled);
174+}
175+
176+ConsoleLog::~ConsoleLog()
177+{
178+ if (m_enabled) {
179+ LogRedirector::instance()->remove(this);
180+ }
181+}
182+
183+void ConsoleLog::setEnabled(bool enabled)
184+{
185+ if (m_enabled == enabled) {
186+ return;
187+ }
188+
189+ m_enabled = enabled;
190+ Q_EMIT enabledChanged();
191+}
192+
193+QString ConsoleLog::out() const
194+{
195+ return m_out.join("\n");
196+}
197+
198+void ConsoleLog::setMaxLines(int maxLines)
199+{
200+ if (m_maxLines == maxLines) {
201+ return;
202+ }
203+
204+ m_maxLines = maxLines;
205+ while (m_out.count() > m_maxLines) {
206+ m_out.removeLast();
207+ }
208+ Q_EMIT outChanged();
209+ Q_EMIT maxLinesChanged();
210+}
211+
212+void ConsoleLog::logged(QString captured)
213+{
214+ QStringList li = captured.split("\n", QString::SkipEmptyParts);
215+ li << m_out;
216+ m_out = li;
217+ while (m_out.count() > m_maxLines) {
218+ m_out.removeLast();
219+ }
220+ Q_EMIT outChanged();
221+}
plugins/Unity/Debug/consolelog.hnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef CONSOLELOG_H
18+#define CONSOLELOG_H
19+
20+#include <QThread>
21+#include <QMutex>
22+#include <QSet>
23+
24+class ConsoleLog;
25+
26+class LogRedirector : public QThread
27+{
28+ Q_OBJECT
29+public:
30+ static LogRedirector *instance();
31+
32+ void add(ConsoleLog* logger);
33+ void remove(ConsoleLog* logger);
34+
35+private Q_SLOTS:
36+ void checkLog();
37+
38+Q_SIGNALS:
39+ void log(QString log);
40+
41+private:
42+ LogRedirector();
43+ void run() Q_DECL_OVERRIDE;
44+
45+ QMutex m_mutex;
46+ int m_pipe[2];
47+ int m_ref;
48+ bool m_stop;
49+
50+ enum PIPES { READ, WRITE };
51+};
52+
53+class ConsoleLog : public QObject
54+{
55+ Q_OBJECT
56+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
57+ Q_PROPERTY(QString out READ out NOTIFY outChanged)
58+ Q_PROPERTY(int maxLines READ maxLines WRITE setMaxLines NOTIFY maxLinesChanged)
59+public:
60+ explicit ConsoleLog(QObject *parent = 0);
61+ ~ConsoleLog();
62+
63+ bool isEnabled() const { return m_enabled; }
64+ void setEnabled(bool enabled);
65+
66+ QString out() const;
67+
68+ int maxLines() const { return m_maxLines; }
69+ void setMaxLines(int maxLines);
70+
71+public Q_SLOTS:
72+ void logged(QString captured);
73+
74+Q_SIGNALS:
75+ void enabledChanged();
76+ void outChanged();
77+ void maxLinesChanged();
78+
79+private:
80+ void updateEnabled();
81+
82+ QStringList m_out;
83+ bool m_enabled;
84+ int m_maxLines;
85+};
86+
87+#endif // CONSOLELOG_H
plugins/Unity/Debug/plugin.cppnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+// Qt
18+#include <QtQml/qqml.h>
19+
20+// self
21+#include "plugin.h"
22+
23+// local
24+#include "consolelog.h"
25+
26+void UnityDebugPlugin::registerTypes(const char *uri)
27+{
28+ Q_ASSERT(uri == QLatin1String("Unity.Debug"));
29+
30+ qmlRegisterType<ConsoleLog>(uri, 0, 1, "ConsoleLog");
31+}
plugins/Unity/Debug/plugin.hnew file+32
@@ -0,0 +1,32 @@
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 UNITYDEBUG_PLUGIN_H
18+#define UNITYDEBUG_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class UnityDebugPlugin : 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+};
31+
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+27−2
@@ -1,18 +1,43 @@
1+include_directories(
2+ SYSTEM
3+ ${QTMIRSERVER_INCLUDE_DIRS}
4+)
5+
6+include_directories(
7+ ${CMAKE_CURRENT_SOURCE_DIR}
8+ ${libunity8-private_SOURCE_DIR}
9+)
10+
1set(WINDOWMANAGER_SRC
11set(WINDOWMANAGER_SRC
2 TopLevelWindowModel.cpp
12 TopLevelWindowModel.cpp
3 Window.cpp
13 Window.cpp
4 WindowManagerPlugin.cpp
14 WindowManagerPlugin.cpp
15+ Screen.cpp
16+ ScreenAttached.cpp
17+ Screens.cpp
18+ ScreensConfiguration.cpp
19+ ScreenWindow.cpp
20+ WindowManagerObjects.cpp
21+ Workspace.cpp
22+ WorkspaceManager.cpp
23+ WorkspaceModel.cpp
5 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
24 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
25+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
6 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
26 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
7 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
27 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
28 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
10− )
30+)
11
31
12add_definitions(-DWINDOWMANAGERQML_LIBRARY)
32add_definitions(-DWINDOWMANAGERQML_LIBRARY)
13
33
14add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
34add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
15
35
36+target_link_libraries(windowmanager-qml
37+ ${QTMIRSERVER_LDFLAGS}
38+ unity8-private
39+)
40+
16qt5_use_modules(windowmanager-qml Qml Quick Gui)
41qt5_use_modules(windowmanager-qml Qml Quick Gui)
17
42
18−add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
43+add_unity8_plugin(WindowManager 1.0 WindowManager TARGETS windowmanager-qml)
plugins/WindowManager/Screen.cppnew file+298
@@ -0,0 +1,298 @@
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+Screen::Screen(QObject *parent)
23+ : QObject(parent)
24+{
25+}
26+
27+void Screen::connectToScreen(qtmir::Screen *screen)
28+{
29+ m_wrapped = screen;
30+ connect(screen, &qtmir::Screen::usedChanged, this, &Screen::usedChanged);
31+ connect(screen, &qtmir::Screen::nameChanged, this, &Screen::nameChanged);
32+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeChanged);
33+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeNameChanged);
34+ connect(screen, &qtmir::Screen::scaleChanged, this, &Screen::scaleChanged);
35+ connect(screen, &qtmir::Screen::formFactorChanged, this, &Screen::formFactorChanged);
36+ connect(screen, &qtmir::Screen::physicalSizeChanged, this, &Screen::physicalSizeChanged);
37+ connect(screen, &qtmir::Screen::positionChanged, this, &Screen::positionChanged);
38+ connect(screen, &qtmir::Screen::activeChanged, this, &Screen::activeChanged);
39+ connect(screen, &qtmir::Screen::currentModeIndexChanged, this, &Screen::currentModeIndexChanged);
40+ connect(screen, &qtmir::Screen::availableModesChanged, this, &Screen::availableModesChanged);
41+}
42+
43+void Screen::connectToScreen(Screen *screen)
44+{
45+ connectToScreen(screen->wrapped());
46+ connect(screen, &Screen::currentWorkspaceChanged, this, &Screen::currentWorkspaceChanged);
47+}
48+
49+qtmir::OutputId Screen::outputId() const
50+{
51+ if (!m_wrapped) return qtmir::OutputId(-1);
52+ return m_wrapped->outputId();
53+}
54+
55+bool Screen::used() const
56+{
57+ if (!m_wrapped) return false;
58+ return m_wrapped->used();
59+}
60+
61+QString Screen::name() const
62+{
63+ if (!m_wrapped) return QString();
64+ return m_wrapped->name();
65+}
66+
67+float Screen::scale() const
68+{
69+ if (!m_wrapped) return 1.0;
70+ return m_wrapped->scale();
71+}
72+
73+QSizeF Screen::physicalSize() const
74+{
75+ if (!m_wrapped) return QSizeF();
76+ return m_wrapped->physicalSize();
77+}
78+
79+qtmir::FormFactor Screen::formFactor() const
80+{
81+ if (!m_wrapped) return qtmir::FormFactorUnknown;
82+ return m_wrapped->formFactor();
83+}
84+
85+qtmir::OutputTypes Screen::outputType() const
86+{
87+ if (!m_wrapped) return qtmir::Unknown;
88+ return m_wrapped->outputType();
89+}
90+
91+MirPowerMode Screen::powerMode() const
92+{
93+ if (!m_wrapped) return mir_power_mode_on;
94+ return m_wrapped->powerMode();
95+}
96+
97+Qt::ScreenOrientation Screen::orientation() const
98+{
99+ if (!m_wrapped) return Qt::PrimaryOrientation;
100+ return m_wrapped->orientation();
101+}
102+
103+QPoint Screen::position() const
104+{
105+ if (!m_wrapped) return QPoint();
106+ return m_wrapped->position();
107+}
108+
109+QQmlListProperty<qtmir::ScreenMode> Screen::availableModes()
110+{
111+ if (!m_wrapped) return QQmlListProperty<qtmir::ScreenMode>();
112+ return m_wrapped->availableModes();
113+}
114+
115+uint Screen::currentModeIndex() const
116+{
117+ if (!m_wrapped) return -1;
118+ return m_wrapped->currentModeIndex();
119+}
120+
121+bool Screen::isActive() const
122+{
123+ if (!m_wrapped) return false;
124+ return m_wrapped->isActive();
125+}
126+
127+void Screen::activate()
128+{
129+ setActive(true);
130+}
131+
132+void Screen::setActive(bool active)
133+{
134+ if (!m_wrapped) return;
135+ m_wrapped->setActive(active);
136+}
137+
138+QScreen *Screen::qscreen() const
139+{
140+ if (!m_wrapped) return nullptr;
141+ return m_wrapped->qscreen();
142+}
143+
144+qtmir::ScreenConfiguration *Screen::beginConfiguration() const
145+{
146+ if (!m_wrapped) return nullptr;
147+ return m_wrapped->beginConfiguration();
148+}
149+
150+bool Screen::applyConfiguration(qtmir::ScreenConfiguration *configuration)
151+{
152+ if (!m_wrapped) return false;
153+ return m_wrapped->applyConfiguration(configuration);
154+}
155+
156+QString Screen::outputTypeName() const
157+{
158+ switch (m_wrapped->outputType()) {
159+ case qtmir::Unknown:
160+ return tr("Unknown");
161+ case qtmir::VGA:
162+ return tr("VGA");
163+ case qtmir::DVII:
164+ case qtmir::DVID:
165+ case qtmir::DVIA:
166+ return tr("DVI");
167+ case qtmir::Composite:
168+ return tr("Composite");
169+ case qtmir::SVideo:
170+ return tr("S-Video");
171+ case qtmir::LVDS:
172+ case qtmir::NinePinDIN:
173+ case qtmir::EDP:
174+ return tr("Internal");
175+ case qtmir::Component:
176+ return tr("Component");
177+ case qtmir::DisplayPort:
178+ return tr("DisplayPort");
179+ case qtmir::HDMIA:
180+ case qtmir::HDMIB:
181+ return tr("HDMI");
182+ case qtmir::TV:
183+ return tr("TV");
184+ }
185+ return QString();
186+}
187+
188+void Screen::setSyncing(bool syncing)
189+{
190+ workspaces()->setSyncing(syncing);
191+}
192+
193+void Screen::sync(Screen *proxy)
194+{
195+ if (!proxy) return;
196+ workspaces()->sync(proxy->workspaces());
197+}
198+
199+ConcreteScreen::ConcreteScreen(qtmir::Screen* wrapped)
200+ : m_workspaces(new WorkspaceModel)
201+{
202+ connectToScreen(wrapped);
203+
204+ // Connect the active workspace to activate the screen.
205+ connect(m_workspaces.data(), &WorkspaceModel::workspaceInserted, this, [this](int, Workspace* workspace) {
206+ connect(workspace, &Workspace::activeChanged, this, [this, workspace](bool active) {
207+ if (active) {
208+ setCurrentWorkspace(workspace);
209+ activate();
210+ }
211+ });
212+ if (workspace->isActive()) {
213+ activate();
214+ setCurrentWorkspace(workspace);
215+ }
216+ if (!m_currentWorspace) {
217+ setCurrentWorkspace(workspace);
218+ }
219+ });
220+ connect(m_workspaces.data(), &WorkspaceModel::workspaceRemoved, this, [this](Workspace* workspace) {
221+ disconnect(workspace, &Workspace::activeChanged, this, 0);
222+ if (workspace == m_currentWorspace) {
223+ resetCurrentWorkspace();
224+ }
225+ });
226+ connect(this, &ConcreteScreen::activeChanged, this, [this](bool active) {
227+ if (active && m_currentWorspace) {
228+ m_currentWorspace->activate();
229+ }
230+ });
231+}
232+
233+void ConcreteScreen::resetCurrentWorkspace()
234+{
235+ auto newCurrent = m_workspaces->rowCount() > 0 ? m_workspaces->get(0) : nullptr;
236+ if (m_currentWorspace != newCurrent) {
237+ m_currentWorspace = newCurrent;
238+ Q_EMIT currentWorkspaceChanged(newCurrent);
239+ }
240+}
241+
242+
243+WorkspaceModel *ConcreteScreen::workspaces() const
244+{
245+ return m_workspaces.data();
246+}
247+
248+Workspace *ConcreteScreen::currentWorkspace() const
249+{
250+ return m_currentWorspace.data();
251+}
252+
253+void ConcreteScreen::setCurrentWorkspace(Workspace *workspace)
254+{
255+ if (m_currentWorspace != workspace) {
256+ m_currentWorspace = workspace;
257+ Q_EMIT currentWorkspaceChanged(workspace);
258+ }
259+}
260+
261+ProxyScreen::ProxyScreen(Screen *const screen)
262+ : m_workspaces(new ProxyWorkspaceModel(screen->workspaces()))
263+ , m_original(screen)
264+{
265+ connectToScreen(screen);
266+
267+ auto updateCurrentWorkspaceFn = [this](Workspace* realWorkspace) {
268+ Q_FOREACH(Workspace* workspace, m_workspaces->list()) {
269+ auto p = qobject_cast<ProxyWorkspace*>(workspace);
270+ if (p && p->proxyObject() == realWorkspace) {
271+ if (m_currentWorspace != p) {
272+ m_currentWorspace = p;
273+ Q_EMIT currentWorkspaceChanged(p);
274+ }
275+ }
276+ }
277+ };
278+ connect(screen, &Screen::currentWorkspaceChanged, this, updateCurrentWorkspaceFn);
279+ updateCurrentWorkspaceFn(screen->currentWorkspace());
280+}
281+
282+WorkspaceModel *ProxyScreen::workspaces() const
283+{
284+ return m_workspaces.data();
285+}
286+
287+Workspace *ProxyScreen::currentWorkspace() const
288+{
289+ return m_currentWorspace.data();
290+}
291+
292+void ProxyScreen::setCurrentWorkspace(Workspace *workspace)
293+{
294+ auto p = qobject_cast<ProxyWorkspace*>(workspace);
295+ if (p) {
296+ m_original->setCurrentWorkspace(p->proxyObject());
297+ }
298+}
plugins/WindowManager/Screen.hnew file+132
@@ -0,0 +1,132 @@
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 ProxyScreen;
11+class ScreenAttached;
12+
13+class Screen: public QObject
14+{
15+ Q_OBJECT
16+
17+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
18+ Q_PROPERTY(bool used READ used NOTIFY usedChanged)
19+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
20+ Q_PROPERTY(qtmir::OutputTypes outputType READ outputType NOTIFY outputTypeChanged)
21+ Q_PROPERTY(float scale READ scale NOTIFY scaleChanged)
22+ Q_PROPERTY(qtmir::FormFactor formFactor READ formFactor NOTIFY formFactorChanged)
23+ Q_PROPERTY(MirPowerMode powerMode READ powerMode NOTIFY powerModeChanged)
24+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged)
25+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
26+ Q_PROPERTY(uint currentModeIndex READ currentModeIndex NOTIFY currentModeIndexChanged)
27+ Q_PROPERTY(QQmlListProperty<qtmir::ScreenMode> availableModes READ availableModes NOTIFY availableModesChanged)
28+ Q_PROPERTY(QSizeF physicalSize READ physicalSize NOTIFY physicalSizeChanged)
29+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeChanged)
30+ Q_PROPERTY(WorkspaceModel* workspaces READ workspaces CONSTANT)
31+ Q_PROPERTY(Workspace* currentWorkspace READ currentWorkspace WRITE setCurrentWorkspace NOTIFY currentWorkspaceChanged)
32+public:
33+ // From qtmir::Screen
34+ qtmir::OutputId outputId() const;
35+ bool used() const;
36+ QString name() const;
37+ float scale() const;
38+ QSizeF physicalSize() const;
39+ qtmir::FormFactor formFactor() const;
40+ qtmir::OutputTypes outputType() const;
41+ MirPowerMode powerMode() const;
42+ Qt::ScreenOrientation orientation() const;
43+ QPoint position() const;
44+ QQmlListProperty<qtmir::ScreenMode> availableModes();
45+ uint currentModeIndex() const;
46+ bool isActive() const;
47+ void setActive(bool active);
48+ QScreen* qscreen() const;
49+ QString outputTypeName() const;
50+
51+ qtmir::ScreenConfiguration *beginConfiguration() const;
52+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration);
53+
54+ virtual WorkspaceModel* workspaces() const = 0;
55+ virtual Workspace *currentWorkspace() const = 0;
56+ virtual void setCurrentWorkspace(Workspace* workspace) = 0;
57+
58+ void setSyncing(bool);
59+ void sync(Screen* 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 outputTypeNameChanged();
71+ void scaleChanged();
72+ void formFactorChanged();
73+ void powerModeChanged();
74+ void orientationChanged();
75+ void positionChanged();
76+ void currentModeIndexChanged();
77+ void physicalSizeChanged();
78+ void availableModesChanged();
79+ void activeChanged(bool active);
80+ void currentWorkspaceChanged(Workspace*);
81+
82+protected:
83+ Screen(QObject* parent = 0);
84+
85+ void connectToScreen(qtmir::Screen* screen);
86+ void connectToScreen(Screen* screen);
87+
88+protected:
89+ QPointer<qtmir::Screen> m_wrapped;
90+};
91+
92+
93+class ConcreteScreen : public Screen
94+{
95+ Q_OBJECT
96+public:
97+ explicit ConcreteScreen(qtmir::Screen*const wrapped);
98+
99+ // From qtmir::Screen
100+ WorkspaceModel* workspaces() const override;
101+ Workspace *currentWorkspace() const override;
102+ void setCurrentWorkspace(Workspace* workspace) override;
103+
104+ static ScreenAttached *qmlAttachedProperties(QObject *owner);
105+
106+protected:
107+ void resetCurrentWorkspace();
108+
109+ const QScopedPointer<WorkspaceModel> m_workspaces;
110+ QPointer<Workspace> m_currentWorspace;
111+};
112+
113+class ProxyScreen : public Screen
114+{
115+ Q_OBJECT
116+public:
117+ explicit ProxyScreen(Screen*const screen);
118+
119+ // From qtmir::Screen
120+ WorkspaceModel* workspaces() const override;
121+ Workspace *currentWorkspace() const override;
122+ void setCurrentWorkspace(Workspace* workspace) override;
123+
124+ Screen* proxyObject() const { return m_original.data(); }
125+
126+private:
127+ const QScopedPointer<WorkspaceModel> m_workspaces;
128+ const QPointer<Screen> m_original;
129+ QPointer<Workspace> m_currentWorspace;
130+};
131+
132+#endif // SCREEN_H
plugins/WindowManager/ScreenAttached.cppnew file+151
@@ -0,0 +1,151 @@
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+ : Screen(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+ Screen* screen{nullptr};
79+ Q_FOREACH(auto s, ConcreteScreens::self()->list()) {
80+ if (s->qscreen() == qscreen) {
81+ screen = s;
82+ }
83+ }
84+ screenChanged2(screen);
85+}
86+
87+void ScreenAttached::screenChanged2(Screen* screen)
88+{
89+ if (screen == m_screen) return;
90+
91+ Screen* 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+ if (!oldScreen || screen->currentWorkspace() != oldScreen->currentWorkspace())
123+ Q_EMIT currentWorkspaceChanged(currentWorkspace());
124+
125+ if (oldScreen) {
126+ QVector<qtmir::ScreenMode*> oldModes;
127+ auto oldModesQmlList = oldScreen->availableModes();
128+ for (int i = 0; i < oldModesQmlList.count(&oldModesQmlList); i++) {
129+ oldModes << oldModesQmlList.at(&oldModesQmlList, i);
130+ }
131+
132+ QVector<qtmir::ScreenMode*> newModes;
133+ auto newModesQmlList = screen->availableModes();
134+ for (int i = 0; i < newModesQmlList.count(&newModesQmlList); i++) {
135+ newModes << newModesQmlList.at(&newModesQmlList, i);
136+ }
137+
138+ if (newModes != newModes) {
139+ Q_EMIT availableModesChanged();
140+ }
141+ } else {
142+ Q_EMIT availableModesChanged();
143+ }
144+
145+ connectToScreen(screen);
146+}
147+
148+ScreenAttached *WMScreen::qmlAttachedProperties(QObject *owner)
149+{
150+ return new ScreenAttached(owner);
151+}
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 Screen
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(Screen* screen);
41+
42+private:
43+ QPointer<Screen> 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+ConcreteScreen *ScreenWindow::screenWrapper() const
34+{
35+ return m_screen.data();
36+}
37+
38+void ScreenWindow::setScreenWrapper(ConcreteScreen *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(ConcreteScreen *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+ ConcreteScreen *screenWrapper() const;
40+ void setScreenWrapper(ConcreteScreen *screen);
41+
42+Q_SIGNALS:
43+ void screenWrapperChanged(ConcreteScreen* screen);
44+
45+private:
46+ QPointer<ConcreteScreen> m_screen;
47+};
48+
49+#endif // UNITY_SCREENWINDOW_H
plugins/WindowManager/Screens.cppnew file+244
@@ -0,0 +1,244 @@
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 "ScreensConfiguration.h"
19+#include "Screen.h"
20+#include "WorkspaceManager.h"
21+
22+// qtmirserver
23+#include <qtmir/screens.h>
24+#include <QGuiApplication>
25+#include <QQmlEngine>
26+
27+// Qt
28+#include <QScreen>
29+#include <QWindow>
30+
31+ConcreteScreens* ConcreteScreens::m_self{nullptr};
32+
33+Screens::Screens(const QSharedPointer<qtmir::Screens>& model)
34+ : m_wrapped(model)
35+{
36+}
37+
38+Screens::~Screens()
39+{
40+ qDeleteAll(m_screens);
41+ m_screens.clear();
42+}
43+
44+QHash<int, QByteArray> Screens::roleNames() const
45+{
46+ QHash<int, QByteArray> roles;
47+ roles[ScreenRole] = "screen";
48+ return roles;
49+}
50+
51+QVariant Screens::data(const QModelIndex &index, int role) const
52+{
53+ if (!index.isValid() || index.row() >= m_screens.size()) {
54+ return QVariant();
55+ }
56+
57+ switch(role) {
58+ case ScreenRole:
59+ return QVariant::fromValue(m_screens.at(index.row()));
60+ } // switch
61+
62+ return QVariant();
63+}
64+
65+int Screens::rowCount(const QModelIndex &) const
66+{
67+ return count();
68+}
69+
70+int Screens::indexOf(Screen *screen) const
71+{
72+ return m_screens.indexOf(screen);
73+}
74+
75+Screen *Screens::get(int index) const
76+{
77+ return m_screens.at(index);
78+}
79+
80+int Screens::count() const
81+{
82+ return m_screens.size();
83+}
84+
85+QVariant Screens::activeScreen() const
86+{
87+ for (int i = 0; i < m_screens.count(); i++) {
88+ if (m_screens[i]->isActive()) return i;
89+ }
90+ return QVariant();
91+}
92+
93+void Screens::activateScreen(const QVariant& vindex)
94+{
95+ bool ok = false;
96+ int index = vindex.toInt(&ok);
97+ if (!ok || index < 0 || m_screens.count() <= index) return;
98+
99+ auto screen = m_screens.at(index);
100+ screen->setActive(true);
101+}
102+
103+
104+ConcreteScreens::ConcreteScreens(const QSharedPointer<qtmir::Screens> &model, ScreensConfiguration* config)
105+ : Screens(model)
106+ , m_config(config)
107+{
108+ m_self = this;
109+ connect(m_wrapped.data(), &qtmir::Screens::screenAdded, this, &ConcreteScreens::onScreenAdded);
110+ connect(m_wrapped.data(), &qtmir::Screens::screenRemoved, this, &ConcreteScreens::onScreenRemoved);
111+ connect(m_wrapped.data(), &qtmir::Screens::activeScreenChanged, this, &ConcreteScreens::activeScreenChanged);
112+
113+ Q_FOREACH(qtmir::Screen* screen, m_wrapped->screens()) {
114+ auto screenWrapper(new ConcreteScreen(screen));
115+ m_config->load(screenWrapper);
116+
117+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
118+ m_screens.push_back(screenWrapper);
119+ }
120+}
121+
122+ConcreteScreens::~ConcreteScreens()
123+{
124+ Q_FOREACH(Screen* screen, m_screens) {
125+ m_config->save(screen);
126+ }
127+ delete m_config;
128+}
129+
130+ConcreteScreens *ConcreteScreens::self()
131+{
132+ return ConcreteScreens::m_self;
133+}
134+
135+ProxyScreens *ConcreteScreens::createProxy()
136+{
137+ return new ProxyScreens(this);
138+}
139+
140+void ConcreteScreens::sync(Screens *proxy)
141+{
142+ if (!proxy) return;
143+
144+ for (auto screen : m_screens) {
145+ screen->setSyncing(true);
146+ }
147+
148+ const auto& proxyList = proxy->list();
149+ for (int i = 0; i < m_screens.count() && i < proxyList.count(); ++i) {
150+ m_screens[i]->sync(proxyList[i]);
151+ }
152+
153+ for (auto screen : m_screens) {
154+ screen->setSyncing(false);
155+ }
156+}
157+
158+void ConcreteScreens::onScreenAdded(qtmir::Screen *added)
159+{
160+ Q_FOREACH(auto screenWrapper, m_screens) {
161+ if (screenWrapper->wrapped() == added) return;
162+ }
163+
164+ beginInsertRows(QModelIndex(), count(), count());
165+ auto screenWrapper(new ConcreteScreen(added));
166+ m_config->load(screenWrapper);
167+
168+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
169+ m_screens.push_back(screenWrapper);
170+ endInsertRows();
171+ Q_EMIT screenAdded(screenWrapper);
172+ Q_EMIT countChanged();
173+}
174+
175+void ConcreteScreens::onScreenRemoved(qtmir::Screen *removed)
176+{
177+ int index = 0;
178+ QMutableVectorIterator<Screen*> iter(m_screens);
179+ while(iter.hasNext()) {
180+ auto screenWrapper = iter.next();
181+ if (screenWrapper->wrapped() == removed) {
182+ m_config->save(screenWrapper);
183+
184+ beginRemoveRows(QModelIndex(), index, index);
185+ iter.remove();
186+ endRemoveRows();
187+
188+ Q_EMIT screenRemoved(screenWrapper);
189+ Q_EMIT countChanged();
190+
191+ screenWrapper->deleteLater();
192+ break;
193+ }
194+ index++;
195+ }
196+}
197+
198+
199+ProxyScreens::ProxyScreens(Screens * const screens)
200+ : Screens(screens->m_wrapped)
201+ , m_original(screens)
202+{
203+ connect(screens, &Screens::screenAdded, this, [this](Screen *added) {
204+ Q_FOREACH(auto screen, m_screens) {
205+ auto proxy = static_cast<ProxyScreen*>(screen);
206+ if (proxy->proxyObject() == added) return;
207+ }
208+
209+ beginInsertRows(QModelIndex(), count(), count());
210+ auto screenWrapper(new ProxyScreen(added));
211+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
212+ m_screens.push_back(screenWrapper);
213+ endInsertRows();
214+ Q_EMIT screenAdded(screenWrapper);
215+ Q_EMIT countChanged();
216+ });
217+
218+ connect(screens, &Screens::screenRemoved, this, [this](Screen *removed) {
219+ int index = 0;
220+ QMutableVectorIterator<Screen*> iter(m_screens);
221+ while(iter.hasNext()) {
222+ auto proxy = static_cast<ProxyScreen*>(iter.next());
223+ if (proxy->proxyObject() == removed) {
224+
225+ beginRemoveRows(QModelIndex(), index, index);
226+ iter.remove();
227+ endRemoveRows();
228+
229+ Q_EMIT screenRemoved(proxy);
230+ Q_EMIT countChanged();
231+
232+ delete proxy;
233+ break;
234+ }
235+ index++;
236+ }
237+ });
238+
239+ Q_FOREACH(Screen* screen, screens->list()) {
240+ auto screenWrapper(new ProxyScreen(screen));
241+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
242+ m_screens.push_back(new ProxyScreen(screen));
243+ }
244+}
plugins/WindowManager/Screens.hnew file+113
@@ -0,0 +1,113 @@
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 Screen;
31+class ProxyScreens;
32+class ScreensConfiguration;
33+
34+class Screens : public QAbstractListModel
35+{
36+ Q_OBJECT
37+ Q_PROPERTY(int count READ count NOTIFY countChanged)
38+ Q_PROPERTY(QVariant activeScreen READ activeScreen WRITE activateScreen NOTIFY activeScreenChanged)
39+
40+public:
41+ enum ItemRoles {
42+ ScreenRole = Qt::UserRole + 1
43+ };
44+
45+ virtual ~Screens();
46+
47+ /* QAbstractItemModel */
48+ QHash<int, QByteArray> roleNames() const override;
49+ QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
50+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
51+
52+ Q_INVOKABLE int indexOf(Screen*) const;
53+ Q_INVOKABLE Screen* get(int index) const;
54+
55+ int count() const;
56+ QVariant activeScreen() const;
57+
58+ bool isSyncing() const { return m_syncing; }
59+
60+ const QVector<Screen*>& list() const { return m_screens; }
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(Screen* screen);
70+ void screenRemoved(Screen* screen);
71+
72+protected:
73+ Screens(const QSharedPointer<qtmir::Screens>& model);
74+
75+ QVector<Screen*> m_screens;
76+ const QSharedPointer<qtmir::Screens> m_wrapped;
77+ bool m_syncing;
78+
79+ friend class ProxyScreens;
80+};
81+
82+class ConcreteScreens : public Screens
83+{
84+ Q_OBJECT
85+public:
86+ explicit ConcreteScreens(const QSharedPointer<qtmir::Screens>& model, ScreensConfiguration* config);
87+ ~ConcreteScreens();
88+
89+ Q_INVOKABLE ProxyScreens *createProxy();
90+ Q_INVOKABLE void sync(Screens *proxy);
91+
92+ static ConcreteScreens *self();
93+
94+protected Q_SLOTS:
95+ void onScreenAdded(qtmir::Screen *screen);
96+ void onScreenRemoved(qtmir::Screen *screen);
97+
98+private:
99+ ScreensConfiguration* m_config;
100+
101+ static ConcreteScreens* m_self;
102+};
103+
104+class ProxyScreens : public Screens
105+{
106+public:
107+ explicit ProxyScreens(Screens*const screens);
108+
109+private:
110+ const QPointer<Screens> m_original;
111+};
112+
113+#endif // SCREENS_H
plugins/WindowManager/ScreensConfiguration.cppnew file+94
@@ -0,0 +1,94 @@
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 "ScreensConfiguration.h"
18+#include "Screen.h"
19+#include "Workspace.h"
20+#include "WorkspaceManager.h"
21+
22+#include <QJsonObject>
23+#include <QJsonDocument>
24+#include <QJsonArray>
25+#include <QFile>
26+#include <QStandardPaths>
27+
28+namespace
29+{
30+QJsonArray jsonScreens;
31+}
32+
33+ScreensConfiguration::ScreensConfiguration()
34+{
35+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
36+ QFile f(dbPath + "workspaces");
37+
38+ if (f.open(QIODevice::ReadOnly)) {
39+ QByteArray saveData = f.readAll();
40+ QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
41+ QJsonObject json(loadDoc.object());
42+ jsonScreens = json["screens"].toArray();
43+ }
44+}
45+
46+ScreensConfiguration::~ScreensConfiguration()
47+{
48+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
49+ QFile f(dbPath + "workspaces");
50+ if (f.open(QIODevice::WriteOnly)) {
51+ QJsonObject json;
52+ json["screens"] = jsonScreens;
53+ QJsonDocument saveDoc(json);
54+ f.write(saveDoc.toJson());
55+ }
56+}
57+
58+void ScreensConfiguration::load(Screen *screen)
59+{
60+ int workspaces = 2;
61+ for (auto iter = jsonScreens.begin(); iter != jsonScreens.end(); ++iter) {
62+ QJsonObject jsonScreen = (*iter).toObject();
63+ if (jsonScreen["name"] == screen->name()) {
64+ QJsonValue jsonWorkspaces = jsonScreen["workspaces"];
65+ workspaces = qMax(jsonWorkspaces.toInt(workspaces), 1);
66+ break;
67+ }
68+ }
69+
70+ for (int i = 0; i < workspaces; i++) {
71+ WorkspaceManager::instance()->createWorkspace()->assign(screen->workspaces());
72+ }
73+}
74+
75+void ScreensConfiguration::save(Screen *screen)
76+{
77+ QJsonObject newJsonScreen;
78+ newJsonScreen["name"] = screen->name();
79+ newJsonScreen["workspaces"] = qMax(screen->workspaces()->rowCount(), 1);
80+
81+ auto iter = jsonScreens.begin();
82+ for (; iter != jsonScreens.end(); ++iter) {
83+ QJsonObject jsonScreen = (*iter).toObject();
84+ if (jsonScreen["name"] == screen->name()) {
85+ break;
86+ }
87+ }
88+
89+ if (iter == jsonScreens.end()) {
90+ jsonScreens.push_back(newJsonScreen);
91+ } else {
92+ *iter = newJsonScreen;
93+ }
94+}
src/ShellView.h → plugins/WindowManager/ScreensConfiguration.hrenamed+10−12
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_SHELL_VIEW_H
17+#ifndef SCREENSCONFIGURATION_H
18−#define UNITY_SHELL_VIEW_H
18+#define SCREENSCONFIGURATION_H
19
19
20−#include <QQuickView>
20+class Screen;
21
21
22−class ShellView : public QQuickView
22+class ScreensConfiguration
23{
23{
24− Q_OBJECT
25−
26public:
24public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
25+ explicit ScreensConfiguration();
26+ ~ScreensConfiguration();
28
27
29−private Q_SLOTS:
28+ void load(Screen* screen);
30− void onWidthChanged(int);
29+ void save(Screen* screen);
31− void onHeightChanged(int);
32};
30};
33
31
34−#endif // UNITY_SHELL_VIEW_H
32+#endif // SCREENSCONFIGURATION_H
plugins/WindowManager/TopLevelWindowModel.cpp+199−62
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include "TopLevelWindowModel.h"
17#include "TopLevelWindowModel.h"
18+#include "WindowManagerObjects.h"
18
19
19// unity-api
20// unity-api
20#include <unity/shell/application/ApplicationInfoInterface.h>
21#include <unity/shell/application/ApplicationInfoInterface.h>
@@ -24,11 +25,11 @@
24#include <unity/shell/application/SurfaceManagerInterface.h>
25#include <unity/shell/application/SurfaceManagerInterface.h>
25
26
26// Qt
27// Qt
27−#include <QGuiApplication>
28#include <QDebug>
28#include <QDebug>
29
29
30// local
30// local
31#include "Window.h"
31#include "Window.h"
32+#include "Workspace.h"
32
33
33Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34
35
@@ -37,7 +38,19 @@ Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
37
38
38namespace unityapi = unity::shell::application;
39namespace unityapi = unity::shell::application;
39
40
40−TopLevelWindowModel::TopLevelWindowModel()
41+TopLevelWindowModel::TopLevelWindowModel(Workspace* workspace)
42+ : m_workspace(workspace)
43+{
44+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::applicationManagerChanged,
45+ this, &TopLevelWindowModel::setApplicationManager);
46+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::surfaceManagerChanged,
47+ this, &TopLevelWindowModel::setSurfaceManager);
48+
49+ setApplicationManager(WindowManagerObjects::instance()->applicationManager());
50+ setSurfaceManager(WindowManagerObjects::instance()->surfaceManager());
51+}
52+
53+TopLevelWindowModel::~TopLevelWindowModel()
41{
54{
42}
55}
43
56
@@ -55,7 +68,6 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
55 beginResetModel();
68 beginResetModel();
56
69
57 if (m_applicationManager) {
70 if (m_applicationManager) {
58− m_windowModel.clear();
59 disconnect(m_applicationManager, 0, this, 0);
71 disconnect(m_applicationManager, 0, this, 0);
60 }
72 }
61
73
@@ -64,26 +76,26 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
64 if (m_applicationManager) {
76 if (m_applicationManager) {
65 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
77 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
66 this, [this](const QModelIndex &/*parent*/, int first, int last) {
78 this, [this](const QModelIndex &/*parent*/, int first, int last) {
79+ if (!m_workspace || !m_workspace->isActive())
80+ return;
81+
67− for (int i = first; i <= last; ++i) {
82+ for (int i = first; i <= last; ++i) {
68− auto application = m_applicationManager->get(i);
83+ auto application = m_applicationManager->get(i);
69− addApplication(application);
84+ addApplication(application);
70− }
85+ }
71− });
86+ });
72
87
73 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
88 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
74 this, [this](const QModelIndex &/*parent*/, int first, int last) {
89 this, [this](const QModelIndex &/*parent*/, int first, int last) {
75− for (int i = first; i <= last; ++i) {
90+ for (int i = first; i <= last; ++i) {
76− auto application = m_applicationManager->get(i);
91+ auto application = m_applicationManager->get(i);
77− removeApplication(application);
92+ removeApplication(application);
78− }
79− });
93+ }
80−
94+ });
81− for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
82− auto application = m_applicationManager->get(i);
83− addApplication(application);
84− }
85 }
95 }
86
96
97+ refreshWindows();
98+
87 endResetModel();
99 endResetModel();
88 m_modelState = IdleState;
100 m_modelState = IdleState;
89}
101}
@@ -96,6 +108,11 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
96
108
97 DEBUG_MSG << "(" << surfaceManager << ")";
109 DEBUG_MSG << "(" << surfaceManager << ")";
98
110
111+ Q_ASSERT(m_modelState == IdleState);
112+ m_modelState = ResettingState;
113+
114+ beginResetModel();
115+
99 if (m_surfaceManager) {
116 if (m_surfaceManager) {
100 disconnect(m_surfaceManager, 0, this, 0);
117 disconnect(m_surfaceManager, 0, this, 0);
101 }
118 }
@@ -103,13 +120,20 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
103 m_surfaceManager = surfaceManager;
120 m_surfaceManager = surfaceManager;
104
121
105 if (m_surfaceManager) {
122 if (m_surfaceManager) {
106− connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
107 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
123 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
108 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
124 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
109 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
125 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
126+
127+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAddedToWorkspace,
128+ this, &TopLevelWindowModel::onSurfacesAddedToWorkspace);
129+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAboutToBeRemovedFromWorkspace,
130+ this, &TopLevelWindowModel::onSurfacesAboutToBeRemovedFromWorkspace);
110 }
131 }
111
132
112− Q_EMIT surfaceManagerChanged(m_surfaceManager);
133+ refreshWindows();
134+
135+ endResetModel();
136+ m_modelState = IdleState;
113}
137}
114
138
115void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
139void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
@@ -149,6 +173,7 @@ void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface,
149 Q_ASSERT(surface != nullptr);
173 Q_ASSERT(surface != nullptr);
150
174
151 connectSurface(surface);
175 connectSurface(surface);
176+ m_allSurfaces.insert(surface);
152
177
153 bool filledPlaceholder = false;
178 bool filledPlaceholder = false;
154 for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
179 for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
@@ -225,15 +250,14 @@ void TopLevelWindowModel::connectWindow(Window *window)
225
250
226 connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
251 connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
227 if (window->surface()) {
252 if (window->surface()) {
228− // Condense changes to the focused window
229− // eg: Do focusedWindow=A to focusedWindow=B instead of
230− // focusedWindow=A to focusedWindow=null to focusedWindow=B
231 if (focused) {
253 if (focused) {
232− Q_ASSERT(m_newlyFocusedWindow == nullptr);
254+ setFocusedWindow(window);
233− m_focusedWindowChanged = true;
255+ m_focusedWindowCleared = false;
234− m_newlyFocusedWindow = window;
235 } else if (m_focusedWindow == window) {
256 } else if (m_focusedWindow == window) {
236− m_focusedWindowChanged = true;
257+ // Condense changes to the focused window
258+ // eg: Do focusedWindow=A to focusedWindow=B instead of
259+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
260+ m_focusedWindowCleared = true;
237 } else {
261 } else {
238 // don't clear the focused window if you were not there in the first place
262 // don't clear the focused window if you were not there in the first place
239 // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
263 // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
@@ -338,6 +362,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
338 auto window = m_windowModel[i].window;
362 auto window = m_windowModel[i].window;
339 window->setSurface(nullptr);
363 window->setSurface(nullptr);
340 window->setFocused(false);
364 window->setFocused(false);
365+ m_allSurfaces.remove(surface);
341 INFO_MSG << " Removed surface from entry. After: " << toString();
366 INFO_MSG << " Removed surface from entry. After: " << toString();
342 }
367 }
343}
368}
@@ -353,48 +378,107 @@ Window *TopLevelWindowModel::createWindow(unityapi::MirSurfaceInterface *surface
353 return qmlWindow;
378 return qmlWindow;
354}
379}
355
380
356−void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
381+void TopLevelWindowModel::onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
382+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces)
357{
383{
358− DEBUG_MSG << "(" << surface << ")";
384+ if (!m_workspace || !m_applicationManager) return;
385+ if (workspace != m_workspace->workspace()) return;
359
386
387+ Q_FOREACH(auto surface, surfaces) {
388+ if (m_allSurfaces.contains(surface)) continue;
389+
360− if (surface->parentSurface()) {
390+ if (surface->parentSurface()) {
361− // Wrap it in a Window so that we keep focusedWindow() up to date.
391+ // Wrap it in a Window so that we keep focusedWindow() up to date.
362− Window *window = createWindow(surface);
392+ Window *window = createWindow(surface);
363− connect(surface, &QObject::destroyed, window, [=](){
393+ connect(surface, &QObject::destroyed, window, [=](){
364− window->setSurface(nullptr);
394+ window->setSurface(nullptr);
365− window->deleteLater();
395+ window->deleteLater();
366− });
396+ });
367− } else {
368− if (surface->type() == Mir::InputMethodType) {
369− connectSurface(surface);
370− setInputMethodWindow(createWindow(surface));
371 } else {
397 } else {
398+ if (surface->type() == Mir::InputMethodType) {
399+ connectSurface(surface);
400+ setInputMethodWindow(createWindow(surface));
401+ } else {
372− auto *application = m_applicationManager->findApplicationWithSurface(surface);
402+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
373− if (application) {
403+ if (application) {
374− if (surface->state() == Mir::HiddenState) {
404+ if (surface->state() == Mir::HiddenState) {
375− // Ignore it until it's finally shown
405+ // Ignore it until it's finally shown
376− connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
406+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
377− Q_ASSERT(newState != Mir::HiddenState);
407+ Q_ASSERT(newState != Mir::HiddenState);
378− disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
408+ disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
409+ prependSurface(surface, application);
410+ });
411+ } else {
379 prependSurface(surface, application);
412 prependSurface(surface, application);
380− });
413+ }
381 } else {
414 } else {
382− prependSurface(surface, application);
415+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
416+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
417+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
418+ Window *promptWindow = createWindow(surface);
419+ connect(surface, &QObject::destroyed, promptWindow, [=](){
420+ promptWindow->setSurface(nullptr);
421+ promptWindow->deleteLater();
422+ });
383 }
423 }
384− } else {
385− // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
386− // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
387− // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
388− Window *promptWindow = createWindow(surface);
389− connect(surface, &QObject::destroyed, promptWindow, [=](){
390− promptWindow->setSurface(nullptr);
391− promptWindow->deleteLater();
392− });
393 }
424 }
394 }
425 }
395 }
426 }
396}
427}
397
428
429+void TopLevelWindowModel::onSurfacesAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace> &workspace,
430+ const QVector<unity::shell::application::MirSurfaceInterface *> surfaces)
431+{
432+ if (!m_workspace || workspace != m_workspace->workspace()) return;
433+
434+ int start = -1;
435+ int end = -1;
436+ for (auto iter = surfaces.constBegin(); iter != surfaces.constEnd();) {
437+ auto surface = *iter;
438+ iter++;
439+
440+ // Do removals in adjacent blocks.
441+ start = end = indexOf(surface);
442+ if (start == -1) {
443+ // probably a child surface
444+ m_allSurfaces.remove(surface);
445+ continue;
446+ }
447+ while(iter != surfaces.constEnd()) {
448+ int index = indexOf(*iter);
449+ if (index != end+1) {
450+ break;
451+ }
452+ end++;
453+ iter++;
454+ }
455+
456+ if (m_modelState == IdleState) {
457+ beginRemoveRows(QModelIndex(), start, end);
458+ m_modelState = RemovingState;
459+ } else {
460+ Q_ASSERT(m_modelState == ResettingState);
461+ // No point in signaling anything if we're resetting the whole model
462+ }
463+
464+ for (int index = start; index <= end; index++) {
465+ auto window = m_windowModel[start].window;
466+ window->setSurface(nullptr);
467+ window->setFocused(false);
468+
469+ m_windowModel.removeAt(start);
470+ m_allSurfaces.remove(surface);
471+ }
472+
473+ if (m_modelState == RemovingState) {
474+ endRemoveRows();
475+ Q_EMIT countChanged();
476+ Q_EMIT listChanged();
477+ m_modelState = IdleState;
478+ }
479+ }
480+}
481+
398void TopLevelWindowModel::deleteAt(int index)
482void TopLevelWindowModel::deleteAt(int index)
399{
483{
400 auto window = m_windowModel[index].window;
484 auto window = m_windowModel[index].window;
@@ -417,12 +501,14 @@ void TopLevelWindowModel::removeAt(int index)
417 }
501 }
418
502
419 auto window = m_windowModel[index].window;
503 auto window = m_windowModel[index].window;
504+ auto surface = window->surface();
420
505
421 if (!window->surface()) {
506 if (!window->surface()) {
422 window->setFocused(false);
507 window->setFocused(false);
423 }
508 }
424
509
425 m_windowModel.removeAt(index);
510 m_windowModel.removeAt(index);
511+ m_allSurfaces.remove(surface);
426
512
427 if (m_modelState == RemovingState) {
513 if (m_modelState == RemovingState) {
428 endRemoveRows();
514 endRemoveRows();
@@ -433,6 +519,7 @@ void TopLevelWindowModel::removeAt(int index)
433
519
434 if (m_focusedWindow == window) {
520 if (m_focusedWindow == window) {
435 setFocusedWindow(nullptr);
521 setFocusedWindow(nullptr);
522+ m_focusedWindowCleared = false;
436 }
523 }
437
524
438 INFO_MSG << " after " << toString();
525 INFO_MSG << " after " << toString();
@@ -700,12 +787,11 @@ void TopLevelWindowModel::onModificationsStarted()
700
787
701void TopLevelWindowModel::onModificationsEnded()
788void TopLevelWindowModel::onModificationsEnded()
702{
789{
703− if (m_focusedWindowChanged) {
790+ if (m_focusedWindowCleared) {
704− setFocusedWindow(m_newlyFocusedWindow);
791+ setFocusedWindow(nullptr);
705 }
792 }
706 // reset
793 // reset
707− m_focusedWindowChanged = false;
794+ m_focusedWindowCleared = false;
708− m_newlyFocusedWindow = nullptr;
709}
795}
710
796
711void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
797void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
@@ -719,3 +805,54 @@ void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
719 }
805 }
720 }
806 }
721}
807}
808+
809+void TopLevelWindowModel::refreshWindows()
810+{
811+ DEBUG_MSG << "()";
812+ clear();
813+
814+ if (!m_workspace || !m_applicationManager || !m_surfaceManager) return;
815+
816+ m_surfaceManager->forEachSurfaceInWorkspace(m_workspace->workspace(), [this](unity::shell::application::MirSurfaceInterface* surface) {
817+ if (surface->parentSurface()) {
818+ // Wrap it in a Window so that we keep focusedWindow() up to date.
819+ Window *window = createWindow(surface);
820+ connect(surface, &QObject::destroyed, window, [=](){
821+ window->setSurface(nullptr);
822+ window->deleteLater();
823+ });
824+ } else {
825+ if (surface->type() == Mir::InputMethodType) {
826+ setInputMethodWindow(createWindow(surface));
827+ } else {
828+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
829+ if (application) {
830+ prependSurface(surface, application);
831+ } else {
832+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
833+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
834+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
835+ Window *promptWindow = createWindow(surface);
836+ connect(surface, &QObject::destroyed, promptWindow, [=](){
837+ promptWindow->setSurface(nullptr);
838+ promptWindow->deleteLater();
839+ });
840+ }
841+ }
842+ }
843+ });
844+}
845+
846+void TopLevelWindowModel::clear()
847+{
848+ DEBUG_MSG << "()";
849+
850+ while(m_windowModel.count() > 0) {
851+ ModelEntry entry = m_windowModel.takeAt(0);
852+ disconnect(entry.window, 0, this, 0);
853+ delete entry.window;
854+ }
855+ m_allSurfaces.clear();
856+ setFocusedWindow(nullptr);
857+ m_focusedWindowCleared = false;
858+}
plugins/WindowManager/TopLevelWindowModel.h+21−23
@@ -20,11 +20,16 @@
20#include <QAbstractListModel>
20#include <QAbstractListModel>
21#include <QLoggingCategory>
21#include <QLoggingCategory>
22
22
23+#include <memory>
24+
23#include "WindowManagerGlobal.h"
25#include "WindowManagerGlobal.h"
24
26
25Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
27Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
26
28
27class Window;
29class Window;
30+class Workspace;
31+
32+namespace miral { class Workspace; }
28
33
29namespace unity {
34namespace unity {
30 namespace shell {
35 namespace shell {
@@ -72,16 +77,6 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
72 */
77 */
73 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
78 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
74
79
75− Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
76− READ surfaceManager
77− WRITE setSurfaceManager
78− NOTIFY surfaceManagerChanged)
79−
80− Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
81− READ applicationManager
82− WRITE setApplicationManager
83− NOTIFY applicationManagerChanged)
84−
85 /**
80 /**
86 The id to be used on the next entry created
81 The id to be used on the next entry created
87 Useful for tests
82 Useful for tests
@@ -100,7 +95,8 @@ public:
100 ApplicationRole = Qt::UserRole + 1,
95 ApplicationRole = Qt::UserRole + 1,
101 };
96 };
102
97
103− TopLevelWindowModel();
98+ TopLevelWindowModel(Workspace* workspace);
99+ ~TopLevelWindowModel();
104
100
105 // From QAbstractItemModel
101 // From QAbstractItemModel
106 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
102 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -111,17 +107,11 @@ public:
111 return roleNames;
107 return roleNames;
112 }
108 }
113
109
114− // Own API
110+ // Own API;
115
111
116 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
112 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
117 Window* focusedWindow() const;
113 Window* focusedWindow() const;
118
114
119− unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
120− void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
121−
122− unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
123− void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
124−
125 int nextId() const { return m_nextId; }
115 int nextId() const { return m_nextId; }
126
116
127public:
117public:
@@ -164,12 +154,13 @@ public:
164 */
154 */
165 Q_INVOKABLE void raiseId(int id);
155 Q_INVOKABLE void raiseId(int id);
166
156
157+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
158+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
159+
167Q_SIGNALS:
160Q_SIGNALS:
168 void countChanged();
161 void countChanged();
169 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
162 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
170 void focusedWindowChanged(Window *focusedWindow);
163 void focusedWindowChanged(Window *focusedWindow);
171− void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
172− void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
173
164
174 /**
165 /**
175 * @brief Emitted when the list changes
166 * @brief Emitted when the list changes
@@ -181,7 +172,10 @@ Q_SIGNALS:
181 void nextIdChanged();
172 void nextIdChanged();
182
173
183private Q_SLOTS:
174private Q_SLOTS:
175+ void onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
176+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces);
177+ void onSurfacesAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
184− void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
178+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces);
185 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
179 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
186
180
187 void onModificationsStarted();
181 void onModificationsStarted();
@@ -223,6 +217,8 @@ private:
223 void activateEmptyWindow(Window *window);
217 void activateEmptyWindow(Window *window);
224
218
225 void activateTopMostWindowWithoutId(int forbiddenId);
219 void activateTopMostWindowWithoutId(int forbiddenId);
220+ void refreshWindows();
221+ void clear();
226
222
227 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
223 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
228
224
@@ -239,6 +235,9 @@ private:
239 QVector<ModelEntry> m_windowModel;
235 QVector<ModelEntry> m_windowModel;
240 Window* m_inputMethodWindow{nullptr};
236 Window* m_inputMethodWindow{nullptr};
241 Window* m_focusedWindow{nullptr};
237 Window* m_focusedWindow{nullptr};
238+ Workspace* m_workspace{nullptr};
239+ // track all the surfaces we've been told about.
240+ QSet<unity::shell::application::MirSurfaceInterface*> m_allSurfaces;
242
241
243 int m_nextId{1};
242 int m_nextId{1};
244 // Just something big enough that we don't risk running out of unused id numbers.
243 // Just something big enough that we don't risk running out of unused id numbers.
@@ -259,8 +258,7 @@ private:
259 ModelState m_modelState{IdleState};
258 ModelState m_modelState{IdleState};
260
259
261 // Valid between modificationsStarted and modificationsEnded
260 // Valid between modificationsStarted and modificationsEnded
262− bool m_focusedWindowChanged{false};
261+ bool m_focusedWindowCleared{false};
263− Window *m_newlyFocusedWindow{nullptr};
264};
262};
265
263
266#endif // TOPLEVELWINDOWMODEL_H
264#endif // TOPLEVELWINDOWMODEL_H
plugins/WindowManager/WindowManagerObjects.cppnew file+47
@@ -0,0 +1,47 @@
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 "WindowManagerObjects.h"
18+
19+WindowManagerObjects::WindowManagerObjects(QObject *parent)
20+ : QObject(parent)
21+ , m_surfaceManager(nullptr)
22+ , m_applicationManager(nullptr)
23+{
24+}
25+
26+WindowManagerObjects *WindowManagerObjects::instance()
27+{
28+ static WindowManagerObjects* objects(new WindowManagerObjects());
29+ return objects;
30+}
31+
32+
33+void WindowManagerObjects::setSurfaceManager(unity::shell::application::SurfaceManagerInterface *surfaceManager)
34+{
35+ if (m_surfaceManager == surfaceManager) return;
36+
37+ m_surfaceManager = surfaceManager;
38+ Q_EMIT surfaceManagerChanged(surfaceManager);
39+}
40+
41+void WindowManagerObjects::setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager)
42+{
43+ if (m_applicationManager == applicationManager) return;
44+
45+ m_applicationManager = applicationManager;
46+ Q_EMIT applicationManagerChanged(applicationManager);
47+}
plugins/WindowManager/WindowManagerObjects.hnew file+66
@@ -0,0 +1,66 @@
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 WINDOWMANAGEROBJECTS_H
18+#define WINDOWMANAGEROBJECTS_H
19+
20+#include <QObject>
21+
22+#include "WindowManagerGlobal.h"
23+
24+namespace unity {
25+ namespace shell {
26+ namespace application {
27+ class SurfaceManagerInterface;
28+ class ApplicationManagerInterface;
29+ }
30+ }
31+}
32+
33+class WINDOWMANAGERQML_EXPORT WindowManagerObjects : public QObject
34+{
35+ Q_OBJECT
36+
37+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
38+ READ surfaceManager
39+ WRITE setSurfaceManager
40+ NOTIFY surfaceManagerChanged)
41+
42+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
43+ READ applicationManager
44+ WRITE setApplicationManager
45+ NOTIFY applicationManagerChanged)
46+public:
47+ explicit WindowManagerObjects(QObject *parent = 0);
48+
49+ static WindowManagerObjects *instance();
50+
51+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
52+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
53+
54+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
55+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
56+
57+Q_SIGNALS:
58+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
59+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
60+
61+private:
62+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
63+ unity::shell::application::ApplicationManagerInterface* m_applicationManager;
64+};
65+
66+#endif // WINDOWMANAGEROBJECTS_H
plugins/WindowManager/WindowManagerPlugin.cpp+56−2
@@ -16,16 +16,70 @@
16
16
17#include "WindowManagerPlugin.h"
17#include "WindowManagerPlugin.h"
18
18
19+#include "Screen.h"
20+#include "ScreenAttached.h"
21+#include "Screens.h"
22+#include "ScreensConfiguration.h"
23+#include "ScreenWindow.h"
19#include "TopLevelWindowModel.h"
24#include "TopLevelWindowModel.h"
20#include "Window.h"
25#include "Window.h"
26+#include "WindowManagerObjects.h"
27+#include "WorkspaceManager.h"
28+#include "Workspace.h"
29+#include "WorkspaceModel.h"
21
30
22#include <QtQml>
31#include <QtQml>
32+#include <qtmir/qtmir.h>
33+
34+namespace {
35+
36+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
37+
38+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
39+{
40+ Q_UNUSED(engine)
41+ Q_UNUSED(scriptEngine)
42+ return WorkspaceManager::instance();
43+}
44+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
45+ Q_UNUSED(engine);
46+ Q_UNUSED(scriptEngine);
47+ return ConcreteScreens::self();
48+}
49+QObject* objectsSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
50+ Q_UNUSED(engine);
51+ Q_UNUSED(scriptEngine);
52+ return WindowManagerObjects::instance();
53+}
54+
55+} // namspace
23
56
24void WindowManagerPlugin::registerTypes(const char *uri)
57void WindowManagerPlugin::registerTypes(const char *uri)
25{
58{
26− qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
59+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
60+ qmlRegisterUncreatableType<WorkspaceModel>(uri, 1, 0, "WorkspaceModel", notInstantiatable);
61+ qmlRegisterSingletonType<ConcreteScreens>(uri, 1, 0, "Screens", screensSingleton);
62+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", notInstantiatable);
63+ qmlRegisterSingletonType<WindowManagerObjects>(uri, 1, 0, "WindowManagerObjects", objectsSingleton);
27
64
28− qRegisterMetaType<Window*>("Window*");
65+ qRegisterMetaType<ConcreteScreen*>("ConcreteScreen*");
66+ qRegisterMetaType<ProxyScreens*>("ProxyScreens*");
67+ qRegisterMetaType<Workspace*>("Workspace*");
68+ qRegisterMetaType<TopLevelWindowModel*>("TopLevelWindowModel*");
29
69
70+ qRegisterMetaType<Window*>("Window*");
30 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
71 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
72+
73+ qmlRegisterType<ScreenWindow>(uri, 1, 0, "ScreenWindow");
74+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
75+
76+ qmlRegisterUncreatableType<WMScreen>(uri, 1, 0, "WMScreen", notInstantiatable);
77+}
78+
79+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
80+{
81+ QQmlExtensionPlugin::initializeEngine(engine, uri);
82+
83+ // Create Screens
84+ new ConcreteScreens(qtmir::get_screen_model(), new ScreensConfiguration());
31}
85}
plugins/WindowManager/WindowManagerPlugin.h+1
@@ -27,6 +27,7 @@ class WindowManagerPlugin : public QQmlExtensionPlugin
27
27
28public:
28public:
29 void registerTypes(const char *uri) override;
29 void registerTypes(const char *uri) override;
30+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
30};
31};
31
32
32#endif // WINDOWMANAGER_PLUGIN_H
33#endif // WINDOWMANAGER_PLUGIN_H
plugins/WindowManager/Workspace.cppnew file+156
@@ -0,0 +1,156 @@
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+{
31+ setObjectName((QString("Wks%1").arg(nextWorkspace++)));
32+}
33+
34+Workspace::Workspace(const Workspace &other)
35+ : QObject(nullptr)
36+ , m_workspace(other.m_workspace)
37+ , m_model(nullptr)
38+{
39+ setObjectName(other.objectName());
40+
41+ connect(&other, &Workspace::activeChanged, this, &Workspace::activeChanged);
42+}
43+
44+Workspace::~Workspace()
45+{
46+ if (m_model) {
47+ m_model->remove(this);
48+ }
49+}
50+
51+void Workspace::assign(WorkspaceModel *model, const QVariant& vIndex)
52+{
53+ if (m_model == model) return;
54+
55+ if (m_model) {
56+ disconnect(m_model, 0, this, 0);
57+ m_model->remove(this);
58+ }
59+
60+ m_model = model;
61+
62+ if (model) {
63+ int index = m_model->rowCount();
64+ if (vIndex.isValid() && vIndex.canConvert(QVariant::Int)) {
65+ index = vIndex.toInt();
66+ }
67+ m_model->insert(index, this);
68+
69+ connect(m_model, &QObject::destroyed, this, [this]() {
70+ m_model = nullptr;
71+ Q_EMIT unassigned();
72+ });
73+ Q_EMIT assigned();
74+ } else {
75+ Q_EMIT unassigned();
76+ }
77+}
78+
79+void Workspace::unassign()
80+{
81+ assign(nullptr);
82+}
83+
84+bool Workspace::isAssigned() const
85+{
86+ return m_model != nullptr;
87+}
88+
89+
90+ConcreteWorkspace::ConcreteWorkspace(QObject *parent)
91+ : Workspace(parent)
92+ , m_active(false)
93+ , m_windowModel(new TopLevelWindowModel(this))
94+{
95+ connect(WorkspaceManager::instance(), &WorkspaceManager::activeWorkspaceChanged, this, [this](Workspace* activeWorkspace) {
96+ bool newActive = activeWorkspace == this;
97+ if (newActive != m_active) {
98+ m_active = newActive;
99+ Q_EMIT activeChanged(m_active);
100+
101+ if (m_active) {
102+ WMPolicyInterface::instance()->setActiveWorkspace(m_workspace);
103+ }
104+ }
105+ });
106+}
107+
108+ConcreteWorkspace::~ConcreteWorkspace()
109+{
110+ WorkspaceManager::instance()->destroyWorkspace(this);
111+ WMPolicyInterface::instance()->releaseWorkspace(m_workspace);
112+}
113+
114+TopLevelWindowModel *ConcreteWorkspace::windowModel() const
115+{
116+ return m_windowModel.data();
117+}
118+
119+void ConcreteWorkspace::activate()
120+{
121+ WorkspaceManager::instance()->setActiveWorkspace(this);
122+}
123+
124+
125+ProxyWorkspace::ProxyWorkspace(Workspace * const workspace)
126+ : Workspace(*workspace)
127+ , m_original(workspace)
128+{
129+}
130+
131+void ProxyWorkspace::assign(WorkspaceModel *model, const QVariant &index)
132+{
133+ Workspace::assign(model, index);
134+}
135+
136+void ProxyWorkspace::unassign()
137+{
138+ Workspace::unassign();
139+}
140+
141+bool ProxyWorkspace::isActive() const
142+{
143+ return m_original ? m_original->isActive() : false;
144+}
145+
146+TopLevelWindowModel *ProxyWorkspace::windowModel() const
147+{
148+ return m_original ? m_original->windowModel() : nullptr;
149+}
150+
151+void ProxyWorkspace::activate()
152+{
153+ if (m_original) {
154+ m_original->activate();
155+ }
156+}
plugins/WindowManager/Workspace.hnew 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+#ifndef WINDOWMANAGER_WORKSPACE_H
18+#define WINDOWMANAGER_WORKSPACE_H
19+
20+#include <QObject>
21+#include <QVariant>
22+#include <QPointer>
23+#include <QSharedPointer>
24+
25+#include <memory>
26+#include <functional>
27+
28+#include "WindowManagerGlobal.h"
29+
30+class WorkspaceModel;
31+class TopLevelWindowModel;
32+
33+namespace miral { class Workspace; }
34+
35+namespace unity {
36+ namespace shell {
37+ namespace application {
38+ class MirSurfaceInterface;
39+ }
40+ }
41+}
42+
43+class WINDOWMANAGERQML_EXPORT Workspace : public QObject
44+{
45+ Q_OBJECT
46+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
47+ Q_PROPERTY(TopLevelWindowModel* windowModel READ windowModel CONSTANT)
48+public:
49+ virtual ~Workspace();
50+
51+ virtual void assign(WorkspaceModel* model, const QVariant& index = QVariant());
52+ virtual void unassign();
53+
54+ virtual bool isActive() const = 0;
55+ virtual TopLevelWindowModel *windowModel() const = 0;
56+
57+ std::shared_ptr<miral::Workspace> workspace() const { return m_workspace; }
58+ bool isAssigned() const;
59+
60+public Q_SLOTS:
61+ virtual void activate() = 0;
62+
63+Q_SIGNALS:
64+ void assigned();
65+ void unassigned();
66+
67+ void activeChanged(bool);
68+
69+protected:
70+ Workspace(QObject *parent = nullptr);
71+ Workspace(Workspace const& other);
72+
73+ std::shared_ptr<miral::Workspace> m_workspace;
74+ WorkspaceModel* m_model;
75+};
76+
77+class WINDOWMANAGERQML_EXPORT ConcreteWorkspace : public Workspace
78+{
79+public:
80+ ~ConcreteWorkspace();
81+
82+ bool isActive() const override { return m_active; }
83+ TopLevelWindowModel *windowModel() const override;
84+ void activate() override;
85+
86+private:
87+ explicit ConcreteWorkspace(QObject *parent = nullptr);
88+
89+ bool m_active;
90+ const QScopedPointer<TopLevelWindowModel> m_windowModel;
91+
92+ friend class WorkspaceManager;
93+};
94+
95+class ProxyWorkspace : public Workspace
96+{
97+ Q_OBJECT
98+public:
99+ explicit ProxyWorkspace(Workspace*const workspace);
100+ ~ProxyWorkspace() = default;
101+
102+ Q_INVOKABLE void assign(WorkspaceModel* model, const QVariant& index = QVariant()) override;
103+
104+ bool isActive() const override;
105+ TopLevelWindowModel *windowModel() const override;
106+ void activate() override;
107+
108+ Workspace* proxyObject() const { return m_original.data(); }
109+
110+public Q_SLOTS:
111+ void unassign() override;
112+
113+private:
114+ const QPointer<Workspace> m_original;
115+};
116+
117+#endif // WINDOWMANAGER_WORKSPACE_H
plugins/WindowManager/WorkspaceManager.cppnew file+106
@@ -0,0 +1,106 @@
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+#include "WindowManagerObjects.h"
21+#include <unity/shell/application/SurfaceManagerInterface.h>
22+
23+// Qt
24+#include <QGuiApplication>
25+#include <QScreen>
26+#include <QQmlEngine>
27+
28+WorkspaceManager *WorkspaceManager::instance()
29+{
30+ static WorkspaceManager* workspaceManager(new WorkspaceManager());
31+ return workspaceManager;
32+}
33+
34+WorkspaceManager::WorkspaceManager()
35+ : m_activeWorkspace(nullptr)
36+{
37+}
38+
39+Workspace *WorkspaceManager::createWorkspace()
40+{
41+ auto workspace = new ConcreteWorkspace(this);
42+ QQmlEngine::setObjectOwnership(workspace, QQmlEngine::CppOwnership);
43+ m_allWorkspaces.insert(workspace);
44+
45+ if (m_allWorkspaces.count() == 0 && m_activeWorkspace) {
46+ setActiveWorkspace(nullptr);
47+ } else if (m_allWorkspaces.count() == 1) {
48+ setActiveWorkspace(workspace);
49+ }
50+
51+ return workspace;
52+}
53+
54+void WorkspaceManager::destroyWorkspace(Workspace *workspace)
55+{
56+ if (!workspace) return;
57+
58+ if (workspace->isAssigned()) {
59+ workspace->unassign();
60+ }
61+ m_allWorkspaces.remove(workspace);
62+
63+ if (m_activeWorkspace == workspace) {
64+ setActiveWorkspace(m_allWorkspaces.count() ? *m_allWorkspaces.begin() : nullptr);
65+ }
66+ if (m_activeWorkspace) {
67+ moveWorkspaceContentToWorkspace(m_activeWorkspace, workspace);
68+ }
69+
70+ disconnect(workspace, 0, this, 0);
71+}
72+
73+void WorkspaceManager::moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface *surface, Workspace *workspace)
74+{
75+ auto surfaceManager = WindowManagerObjects::instance()->surfaceManager();
76+ if (surfaceManager) {
77+ surfaceManager->moveSurfaceToWorkspace(surface, workspace->workspace());
78+ }
79+}
80+
81+void WorkspaceManager::moveWorkspaceContentToWorkspace(Workspace *to, Workspace *from)
82+{
83+ auto surfaceManager = WindowManagerObjects::instance()->surfaceManager();
84+ if (surfaceManager) {
85+ surfaceManager->moveWorkspaceContentToWorkspace(to->workspace(), from->workspace());
86+ }
87+}
88+
89+Workspace *WorkspaceManager::activeWorkspace() const
90+{
91+ return m_activeWorkspace;
92+}
93+
94+void WorkspaceManager::setActiveWorkspace(Workspace *workspace)
95+{
96+ if (workspace != m_activeWorkspace) {
97+ m_activeWorkspace = workspace;
98+ Q_EMIT activeWorkspaceChanged(workspace);
99+ }
100+}
101+
102+void WorkspaceManager::setActiveWorkspace2(Workspace *workspace)
103+{
104+ if (!workspace) return;
105+ workspace->activate();
106+}
plugins/WindowManager/WorkspaceManager.hnew file+69
@@ -0,0 +1,69 @@
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 <QQmlListProperty>
21+
22+#include "WindowManagerGlobal.h"
23+#include "WorkspaceModel.h"
24+
25+class Workspace;
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class MirSurfaceInterface;
31+ class SurfaceManagerInterface;
32+ }
33+ }
34+}
35+
36+class WINDOWMANAGERQML_EXPORT WorkspaceManager : public QObject
37+{
38+ Q_OBJECT
39+ Q_PROPERTY(Workspace* activeWorkspace READ activeWorkspace WRITE setActiveWorkspace2 NOTIFY activeWorkspaceChanged)
40+
41+public:
42+ WorkspaceManager();
43+ static WorkspaceManager* instance();
44+
45+ Workspace* activeWorkspace() const;
46+ void setActiveWorkspace(Workspace* workspace);
47+
48+ Workspace* createWorkspace();
49+ void destroyWorkspace(Workspace* workspace);
50+
51+ void destroyFloatingWorkspaces();
52+
53+ Q_INVOKABLE void moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
54+ Workspace* workspace);
55+
56+ Q_INVOKABLE void moveWorkspaceContentToWorkspace(Workspace* to, Workspace* from);
57+
58+Q_SIGNALS:
59+ void activeWorkspaceChanged(Workspace*);
60+
61+private:
62+ void setActiveWorkspace2(Workspace* workspace);
63+
64+ QSet<Workspace*> m_allWorkspaces;
65+ Workspace* m_activeWorkspace;
66+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
67+};
68+
69+#endif // WORKSPACEMANAGER_H
plugins/WindowManager/WorkspaceModel.cppnew file+252
@@ -0,0 +1,252 @@
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+WorkspaceModel::~WorkspaceModel()
34+{
35+ qDeleteAll(m_workspaces.toList()); // make a copy so the list doesnt edit itself during delete.
36+ m_workspaces.clear();
37+}
38+
39+void WorkspaceModel::append(Workspace *workspace)
40+{
41+ insert(m_workspaces.count(), workspace);
42+}
43+
44+void WorkspaceModel::insert(int index, Workspace *workspace)
45+{
46+ beginInsertRows(QModelIndex(), index, index);
47+
48+ m_workspaces.insert(index, workspace);
49+
50+ endInsertRows();
51+
52+ Q_EMIT workspaceInserted(index, workspace);
53+ Q_EMIT countChanged();
54+}
55+
56+void WorkspaceModel::remove(Workspace *workspace)
57+{
58+ int index = m_workspaces.indexOf(workspace);
59+ if (index < 0) return;
60+
61+ beginRemoveRows(QModelIndex(), index, index);
62+
63+ m_workspaces.removeAt(index);
64+ insertUnassigned(workspace);
65+
66+ endRemoveRows();
67+
68+ Q_EMIT workspaceRemoved(workspace);
69+ Q_EMIT countChanged();
70+}
71+
72+void WorkspaceModel::move(int from, int to)
73+{
74+ if (from == to) return;
75+ DEBUG_MSG << " from=" << from << " to=" << to;
76+
77+ if (from >= 0 && from < m_workspaces.size() && to >= 0 && to < m_workspaces.size()) {
78+ QModelIndex parent;
79+
80+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
81+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
82+ const auto &window = m_windowModel.takeAt(from);
83+ m_workspaces.insert(to, window);
84+#else
85+ m_workspaces.move(from, to);
86+#endif
87+ endMoveRows();
88+
89+ Q_EMIT workspaceMoved(from, to);
90+ }
91+}
92+
93+int WorkspaceModel::indexOf(Workspace *workspace) const
94+{
95+ return m_workspaces.indexOf(workspace);
96+}
97+
98+Workspace *WorkspaceModel::get(int index) const
99+{
100+ if (index < 0 || index >= rowCount()) return nullptr;
101+ return m_workspaces.at(index);
102+}
103+
104+int WorkspaceModel::rowCount(const QModelIndex &) const
105+{
106+ return m_workspaces.count();
107+}
108+
109+QVariant WorkspaceModel::data(const QModelIndex &index, int role) const
110+{
111+ if (index.row() < 0 || index.row() >= m_workspaces.size())
112+ return QVariant();
113+
114+ if (role == WorkspaceRole) {
115+ Workspace *workspace = m_workspaces.at(index.row());
116+ return QVariant::fromValue(workspace);
117+ } else {
118+ return QVariant();
119+ }
120+}
121+
122+void WorkspaceModel::setSyncing(bool syncing)
123+{
124+ m_syncing = syncing;
125+}
126+
127+void WorkspaceModel::sync(WorkspaceModel *proxy)
128+{
129+ if (!proxy) return;
130+ const auto& proxyList = proxy->list();
131+
132+ // check for removals
133+ int removedIndexWhichWasActive = -1;
134+ QVector<Workspace*> dpCpy(this->list());
135+ Q_FOREACH(auto workspace, dpCpy) {
136+
137+ bool found = false;
138+ Q_FOREACH(auto p, proxyList) {
139+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(p);
140+ if (workspaceProxy->proxyObject() == workspace) {
141+ found = true;
142+ break;
143+ }
144+ }
145+ if (!found) {
146+ if (workspace->isActive()) {
147+ removedIndexWhichWasActive = indexOf(workspace);
148+ }
149+ workspace->unassign();
150+ }
151+ }
152+
153+ // existing
154+ QSet<Workspace*> newWorkspaces;
155+ for (int i = 0; i < proxyList.count(); i++) {
156+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(proxyList[i]);
157+ auto workspace = workspaceProxy->proxyObject();
158+
159+ int oldIndex = this->indexOf(workspace);
160+
161+ if (oldIndex < 0) {
162+ workspace->assign(this, QVariant(i));
163+ } else if (oldIndex != i) {
164+ this->move(oldIndex, i);
165+ }
166+ newWorkspaces.insert(workspace);
167+ }
168+
169+ // Make sure we have at least one workspace in the model.
170+ if (rowCount() == 0) {
171+ Workspace* workspace = WorkspaceManager::instance()->createWorkspace();
172+ workspace->assign(this);
173+ (new ProxyWorkspace(workspace))->assign(proxy);
174+ }
175+
176+ if (removedIndexWhichWasActive != -1) {
177+ int newActiveIndex = qMin(removedIndexWhichWasActive, this->rowCount()-1);
178+ Workspace* newActiveWorkspace = newActiveIndex >= 0 ? this->get(newActiveIndex) : nullptr;
179+
180+ WorkspaceManager::instance()->setActiveWorkspace(newActiveWorkspace);
181+ }
182+
183+ proxy->finishSync();
184+ finishSync();
185+}
186+
187+void WorkspaceModel::finishSync()
188+{
189+ QSet<Workspace*> dpCpy(m_unassignedWorkspaces);
190+ Q_FOREACH(auto workspace, dpCpy) {
191+ delete workspace;
192+ }
193+ m_unassignedWorkspaces.clear();
194+}
195+
196+void WorkspaceModel::insertUnassigned(Workspace *workspace)
197+{
198+ m_unassignedWorkspaces.insert(workspace);
199+ connect(workspace, &Workspace::assigned, this, [=]() {
200+ m_unassignedWorkspaces.remove(workspace);
201+ disconnect(workspace, &Workspace::assigned, this, 0);
202+ });
203+ connect(workspace, &QObject::destroyed, this, [=]() {
204+ m_unassignedWorkspaces.remove(workspace);
205+ });
206+}
207+
208+
209+ProxyWorkspaceModel::ProxyWorkspaceModel(WorkspaceModel * const model)
210+ : m_original(model)
211+{
212+ Q_FOREACH(auto workspace, model->list()) {
213+ auto proxy = new ProxyWorkspace(workspace);
214+ QQmlEngine::setObjectOwnership(proxy, QQmlEngine::CppOwnership);
215+ proxy->assign(this);
216+ }
217+ connect(m_original, &WorkspaceModel::workspaceInserted, this, [this](int index, Workspace* inserted) {
218+ if (m_original->isSyncing()) return;
219+
220+ (new ProxyWorkspace(inserted))->assign(this, index);
221+ });
222+ connect(m_original, &WorkspaceModel::workspaceRemoved, this, [this](Workspace* removed) {
223+ if (m_original->isSyncing()) return;
224+
225+ for (int i = 0; i < rowCount(); i++) {
226+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(get(i));
227+ auto w = workspaceProxy->proxyObject();
228+ if (w == removed) {
229+ remove(workspaceProxy);
230+ break;
231+ }
232+ }
233+ });
234+ connect(m_original, &WorkspaceModel::workspaceMoved, this, [this](int from, int to) {
235+ if (m_original->isSyncing()) return;
236+
237+ move(from, to);
238+ });
239+}
240+
241+void ProxyWorkspaceModel::move(int from, int to)
242+{
243+ WorkspaceModel::move(from, to);
244+}
245+
246+void ProxyWorkspaceModel::addWorkspace()
247+{
248+ auto newWorkspace = WorkspaceManager::instance()->createWorkspace();
249+ m_original->insertUnassigned(newWorkspace);
250+
251+ (new ProxyWorkspace(newWorkspace))->assign(this);
252+}
plugins/WindowManager/WorkspaceModel.hnew file+102
@@ -0,0 +1,102 @@
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 ProxyWorkspaceModel;
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+ ~WorkspaceModel();
45+
46+ void append(Workspace *workspace);
47+ void insert(int index, Workspace *workspace);
48+ void remove(Workspace* workspace);
49+ virtual void move(int from, int to);
50+
51+ Q_INVOKABLE int indexOf(Workspace *workspace) const;
52+ Q_INVOKABLE Workspace* get(int index) const;
53+
54+ // From QAbstractItemModel
55+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
56+ QVariant data(const QModelIndex& index, int role) const override;
57+ QHash<int, QByteArray> roleNames() const override {
58+ QHash<int, QByteArray> roleNames { {WorkspaceRole, "workspace"} };
59+ return roleNames;
60+ }
61+
62+ const QVector<Workspace*>& list() const { return m_workspaces; }
63+
64+ void setSyncing(bool);
65+ void sync(WorkspaceModel* proxy);
66+ void finishSync();
67+
68+ bool isSyncing() { return m_syncing; }
69+
70+Q_SIGNALS:
71+ void countChanged();
72+
73+ void workspaceInserted(int index, Workspace *workspace);
74+ void workspaceRemoved(Workspace *workspace);
75+ void workspaceMoved(int from, int to);
76+
77+protected:
78+ void insertUnassigned(Workspace* workspace);
79+
80+ QVector<Workspace*> m_workspaces;
81+ QSet<Workspace*> m_unassignedWorkspaces;
82+ bool m_syncing;
83+
84+ friend class ProxyWorkspaceModel;
85+};
86+
87+class ProxyWorkspaceModel : public WorkspaceModel
88+{
89+ Q_OBJECT
90+public:
91+ explicit ProxyWorkspaceModel(WorkspaceModel*const model);
92+
93+ Q_INVOKABLE void move(int from, int to) override;
94+
95+public Q_SLOTS:
96+ void addWorkspace();
97+
98+protected:
99+ const QPointer<WorkspaceModel> m_original;
100+};
101+
102+#endif // WORKSPACEMODEL_H
qml/ApplicationMenus/MenuBar.qml+2−1
@@ -29,6 +29,7 @@ Item {
29 property bool enableKeyFilter: false
29 property bool enableKeyFilter: false
30 property real overflowWidth: width
30 property real overflowWidth: width
31 property bool windowMoving: false
31 property bool windowMoving: false
32+ property var panelState: null
32
33
33 // read from outside
34 // read from outside
34 readonly property bool valid: rowRepeater.count > 0
35 readonly property bool valid: rowRepeater.count > 0
@@ -106,7 +107,7 @@ Item {
106
107
107 Component {
108 Component {
108 id: menuComponent
109 id: menuComponent
109− MenuPopup { }
110+ MenuPopup { panelState: root.panelState }
110 }
111 }
111
112
112 Repeater {
113 Repeater {
qml/ApplicationMenus/MenuPopup.qml+3−1
@@ -26,6 +26,7 @@ UbuntuShape {
26 id: root
26 id: root
27 objectName: "menu"
27 objectName: "menu"
28 backgroundColor: theme.palette.normal.overlay
28 backgroundColor: theme.palette.normal.overlay
29+ property PanelState panelState
29
30
30 signal childActivated()
31 signal childActivated()
31
32
@@ -103,7 +104,7 @@ UbuntuShape {
103 property real __minimumWidth: units.gu(20)
104 property real __minimumWidth: units.gu(20)
104 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
105 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
105 property real __minimumHeight: units.gu(2)
106 property real __minimumHeight: units.gu(2)
106− property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
107+ property real __maximumHeight: ApplicationMenusLimits.screenHeight - panelState.panelHeight
107
108
108 signal dismissAll()
109 signal dismissAll()
109
110
@@ -487,6 +488,7 @@ UbuntuShape {
487 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
488 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
488 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
489 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
489 item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
490 item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
491+ item.panelState = Qt.binding(function() { return root.panelState; });
490 }
492 }
491
493
492 Keys.onLeftPressed: retreat()
494 Keys.onLeftPressed: retreat()
qml/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−file(GLOB QML_JS_FILES *.qml *.js)
1+file(GLOB QML_JS_FILES *.qml *.js qmldir)
2
2
3install(FILES ${QML_JS_FILES}
3install(FILES ${QML_JS_FILES}
4 DESTINATION ${SHELL_APP_DIR}
4 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−pragma Singleton
18import QtQuick 2.4
17import QtQuick 2.4
19
18
20QtObject {
19QtObject {
qml/Components/PanelState/qmldir+1−1
@@ -1 +1 @@
1−singleton PanelState 1.0 PanelState.qml
1+PanelState 1.0 PanelState.qml
qml/Components/VirtualTouchPad.qml+14−20
@@ -18,22 +18,20 @@ import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Qt.labs.settings 1.0
20import Qt.labs.settings 1.0
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import UInput 0.1
22import UInput 0.1
23import "../Components"
23import "../Components"
24
24
25Item {
25Item {
26 id: root
26 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
31
27
32 Component.onCompleted: {
28 Component.onCompleted: {
29+ UInput.createMouse();
33 if (!settings.touchpadTutorialHasRun) {
30 if (!settings.touchpadTutorialHasRun) {
34 root.runTutorial()
31 root.runTutorial()
35 }
32 }
36 }
33 }
34+ Component.onDestruction: UInput.removeMouse()
37
35
38 function runTutorial() {
36 function runTutorial() {
39 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
37 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
85 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
83 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
86 && clickTimer.running) {
84 && clickTimer.running) {
87 clickTimer.stop();
85 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
89 isDoubleClick = true;
87 isDoubleClick = true;
90 }
88 }
91 isClick = true;
89 isClick = true;
@@ -104,7 +102,7 @@ Item {
104
102
105 onReleased: {
103 onReleased: {
106 if (isDoubleClick || isDrag) {
104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108 isDoubleClick = false;
106 isDoubleClick = false;
109 }
107 }
110 if (isClick) {
108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120 interval: 200
118 interval: 200
121 property int button: UInput.ButtonLeft
119 property int button: UInput.ButtonLeft
122 onTriggered: {
120 onTriggered: {
123− uinput.pressMouse(button);
121+ UInput.pressMouse(button);
124− uinput.releaseMouse(button);
122+ UInput.releaseMouse(button);
125 }
123 }
126 function scheduleClick(button) {
124 function scheduleClick(button) {
127 clickTimer.button = button;
125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138 isDrag = true;
136 isDrag = true;
139 }
137 }
140
138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142 }
140 }
143
141
144 function scroll(touchPoints) {
142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166 dh /= 2;
164 dh /= 2;
167 dv /= 2;
165 dv /= 2;
168
166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170 }
168 }
171
169
172 touchPoints: [
170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189 objectName: "leftButton"
187 objectName: "leftButton"
190 Layout.fillWidth: true
188 Layout.fillWidth: true
191 Layout.fillHeight: true
189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194 property bool highlight: false
192 property bool highlight: false
195 UbuntuShape {
193 UbuntuShape {
196 anchors.fill: parent
194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204 objectName: "rightButton"
202 objectName: "rightButton"
205 Layout.fillWidth: true
203 Layout.fillWidth: true
206 Layout.fillHeight: true
204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209 property bool highlight: false
207 property bool highlight: false
210 UbuntuShape {
208 UbuntuShape {
211 anchors.fill: parent
209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239 }
237 }
240 }
238 }
241
239
242− Screens {
243− id: screens
244− }
245−
246 InputMethod {
240 InputMethod {
247 id: inputMethod
241 id: inputMethod
248 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
242 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
249− enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
243+ enabled: Screens.count > 1 && settings.oskEnabled && !tutorial.running
250 objectName: "inputMethod"
244 objectName: "inputMethod"
251 anchors.fill: parent
245 anchors.fill: parent
252 }
246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
28 property var screen: Screen
28 property var screen: Screen
29 property var orientationLock: OrientationLock
29 property var orientationLock: OrientationLock
30
30
31+ property alias deviceConfiguration: _deviceConfiguration
31 DeviceConfiguration {
32 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
33 name: applicationArguments.deviceName
34 name: applicationArguments.deviceName
34 }
35 }
35
36
qml/ErrorApplication.qmlnew file+67
@@ -0,0 +1,67 @@
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 QtQuick.Window 2.2
19+import Ubuntu.Components 1.3
20+import WindowManager 1.0
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ ScreenWindow {
27+ id: window
28+ objectName: "screen"+index
29+ screen: model.screen
30+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
31+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
32+
33+ Binding {
34+ when: applicationArguments.hasGeometry
35+ target: window
36+ property: "width"
37+ value: applicationArguments.windowGeometry.width
38+ }
39+ Binding {
40+ when: applicationArguments.hasGeometry
41+ target: window
42+ property: "height"
43+ value: applicationArguments.windowGeometry.height
44+ }
45+
46+ Loader {
47+ width: window.width
48+ height: window.height
49+
50+ Rectangle {
51+ color: "white"
52+ Column {
53+ spacing: units.gu(1)
54+
55+ Label {
56+ text: "Unity encountered an unrecoverable error while loading:"
57+ fontSize: "large"
58+ }
59+
60+ Label {
61+ text: errorString
62+ }
63+ }
64+ }
65+ }
66+ }
67+}
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
25import Unity.Session 0.1
25import Unity.Session 0.1
26
26
27import "." 0.1
27import "." 0.1
28+import ".." 0.1
28import "../Components"
29import "../Components"
29
30
30Showable {
31Showable {
@@ -227,7 +228,7 @@ Showable {
227 if (forcedUnlock && shown) {
228 if (forcedUnlock && shown) {
228 hideView();
229 hideView();
229 if (hideNow) {
230 if (hideNow) {
230− root.hideNow(); // skip hide animation
231+ ShellNotifier.greeter.hide(true); // skip hide animation
231 }
232 }
232 }
233 }
233 }
234 }
@@ -375,7 +376,7 @@ Showable {
375 onEmergencyCall: root.emergencyCall()
376 onEmergencyCall: root.emergencyCall()
376 onRequiredChanged: {
377 onRequiredChanged: {
377 if (!loader.item.required) {
378 if (!loader.item.required) {
378− root.hide();
379+ ShellNotifier.greeter.hide(false);
379 }
380 }
380 }
381 }
381 }
382 }
@@ -498,12 +499,29 @@ Showable {
498 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499 }
500 }
500
501
502+ Connections {
503+ target: ShellNotifier.greeter
504+ onHide: {
505+ if (now) {
506+ root.hideNow(); // skip hide animation
507+ } else {
508+ root.hide();
509+ }
510+ }
511+ }
512+
513+ Binding {
514+ target: ShellNotifier.greeter
515+ property: "shown"
516+ value: root.shown
517+ }
518+
501 Connections {
519 Connections {
502 target: DBusUnitySessionService
520 target: DBusUnitySessionService
503 onLockRequested: root.forceShow()
521 onLockRequested: root.forceShow()
504 onUnlocked: {
522 onUnlocked: {
505 root.forcedUnlock = true;
523 root.forcedUnlock = true;
506− root.hideNow();
524+ ShellNotifier.greeter.hide(true);
507 }
525 }
508 }
526 }
509
527
qml/Greeter/SecondaryGreeter.qmlnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+import "../Components"
21+import ".." 0.1
22+
23+Showable {
24+ id: root
25+
26+ readonly property bool active: required || hasLockedApp
27+
28+ readonly property bool hasLockedApp: lockedApp !== ""
29+ readonly property bool locked: false
30+ readonly property bool waiting: false
31+ readonly property bool fullyShown: shown
32+
33+ property string lockedApp: ""
34+
35+ function forceShow() { show(); }
36+ property var notifyAppFocusRequested: (function(appId) { return; })
37+ property var notifyUserRequestedApp: (function(appId) { return; })
38+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
39+
40+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
41+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
42+
43+ shown: ShellNotifier.greeter.shown
44+ Component.onCompleted: opacity = shown ? 1 : 0
45+ visible: opacity != 0
46+
47+ Rectangle {
48+ anchors.fill: parent
49+ color: UbuntuColors.purple
50+ }
51+
52+ MouseArea {
53+ anchors.fill: parent
54+ acceptedButtons: Qt.AllButtons
55+ onWheel: wheel.accepted = true
56+ }
57+
58+ Connections {
59+ target: ShellNotifier.greeter
60+ onHide: {
61+ if (now) {
62+ root.hideNow(); // skip hide animation
63+ } else {
64+ root.hide();
65+ }
66+ }
67+ onShownChanged: {
68+ if (ShellNotifier.greeter.shown) {
69+ root.show();
70+ } else {
71+ root.hide();
72+ }
73+ }
74+ }
75+}
qml/OrientedShell.qml+6−9
@@ -18,7 +18,7 @@ import QtQuick 2.4
18import QtQuick.Window 2.2
18import QtQuick.Window 2.2
19import Unity.InputInfo 0.1
19import Unity.InputInfo 0.1
20import Unity.Session 0.1
20import Unity.Session 0.1
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import Utils 0.1
22import Utils 0.1
23import GSettings 1.0
23import GSettings 1.0
24import "Components"
24import "Components"
@@ -32,15 +32,16 @@ Item {
32 implicitWidth: units.gu(40)
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
33 implicitHeight: units.gu(71)
34
34
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+
35 onWidthChanged: calculateUsageMode();
38 onWidthChanged: calculateUsageMode();
36
39
37 DeviceConfiguration {
40 DeviceConfiguration {
38− id: deviceConfiguration
41+ id: _deviceConfiguration
39 name: applicationArguments.deviceName
42 name: applicationArguments.deviceName
40 }
43 }
41
44
42− property alias orientations: d.orientations
43−
44 Item {
45 Item {
45 id: d
46 id: d
46
47
@@ -157,10 +158,6 @@ Item {
157 return false;
158 return false;
158 }
159 }
159
160
160− Screens {
161− id: screens
162− }
163−
164 property int orientation
161 property int orientation
165 onPhysicalOrientationChanged: {
162 onPhysicalOrientationChanged: {
166 if (!orientationLocked) {
163 if (!orientationLocked) {
@@ -264,7 +261,7 @@ Item {
264
261
265 Shell {
262 Shell {
266 id: shell
263 id: shell
267− objectName: "shell"
264+ objectName: "shell-"+screenWindow.objectName
268 width: root.width
265 width: root.width
269 height: root.height
266 height: root.height
270 orientation: root.angleToOrientation(orientationAngle)
267 orientation: root.angleToOrientation(orientationAngle)
qml/Panel/Panel.qml+15−13
@@ -48,6 +48,7 @@ Item {
48 property bool hasKeyboard: false
48 property bool hasKeyboard: false
49
49
50 property string mode: "staged"
50 property string mode: "staged"
51+ property PanelState panelState
51
52
52 MouseArea {
53 MouseArea {
53 id: backMouseEater
54 id: backMouseEater
@@ -64,14 +65,14 @@ Item {
64 }
65 }
65
66
66 Binding {
67 Binding {
67− target: PanelState
68+ target: panelState
68 property: "panelHeight"
69 property: "panelHeight"
69 value: minimizedPanelHeight
70 value: minimizedPanelHeight
70 }
71 }
71
72
72 RegisteredApplicationMenuModel {
73 RegisteredApplicationMenuModel {
73 id: registeredMenuModel
74 id: registeredMenuModel
74− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
75 }
76 }
76
77
77 QtObject {
78 QtObject {
@@ -82,11 +83,11 @@ Item {
82 !indicators.shown &&
83 !indicators.shown &&
83 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
84 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
84
85
85− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
86− PanelState.decorationsAlwaysVisible
87+ panelState.decorationsAlwaysVisible
87
88
88 property bool showPointerMenu: revealControls &&
89 property bool showPointerMenu: revealControls &&
89− (PanelState.decorationsVisible || mode == "staged")
90+ (panelState.decorationsVisible || mode == "staged")
90
91
91 property bool enablePointerMenu: revealControls &&
92 property bool enablePointerMenu: revealControls &&
92 applicationMenus.available &&
93 applicationMenus.available &&
@@ -138,7 +139,7 @@ Item {
138 fill: panelAreaBackground
139 fill: panelAreaBackground
139 bottomMargin: -units.gu(1)
140 bottomMargin: -units.gu(1)
140 }
141 }
141− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
142 source: "graphics/rectangular_dropshadow.sci"
143 source: "graphics/rectangular_dropshadow.sci"
143 }
144 }
144
145
@@ -195,12 +196,12 @@ Item {
195 visible: opacity != 0
196 visible: opacity != 0
196 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
197 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
197
198
198− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
199+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
199 windowIsMaximized: true
200 windowIsMaximized: true
200− onCloseClicked: PanelState.closeClicked()
201+ onCloseClicked: panelState.closeClicked()
201− onMinimizeClicked: PanelState.minimizeClicked()
202+ onMinimizeClicked: panelState.minimizeClicked()
202− onMaximizeClicked: PanelState.restoreClicked()
203+ onMaximizeClicked: panelState.restoreClicked()
203− closeButtonShown: PanelState.closeButtonShown
204+ closeButtonShown: panelState.closeButtonShown
204 }
205 }
205
206
206 Loader {
207 Loader {
@@ -223,8 +224,9 @@ Item {
223 anchors.left: parent.left
224 anchors.left: parent.left
224 anchors.margins: units.gu(1)
225 anchors.margins: units.gu(1)
225 height: menuBarLoader.height
226 height: menuBarLoader.height
226− enableKeyFilter: valid && PanelState.decorationsVisible
227+ enableKeyFilter: valid && panelState.decorationsVisible
227 unityMenuModel: __applicationMenus.model
228 unityMenuModel: __applicationMenus.model
229+ panelState: root.panelState
228
230
229 Connections {
231 Connections {
230 target: __applicationMenus
232 target: __applicationMenus
@@ -236,7 +238,7 @@ Item {
236 onShownChanged: bar.dismiss();
238 onShownChanged: bar.dismiss();
237 }
239 }
238
240
239− onDoubleClicked: PanelState.restoreClicked()
241+ onDoubleClicked: panelState.restoreClicked()
240 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
242 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
241 }
243 }
242 }
244 }
qml/Shell.qml+58−23
@@ -1,4 +1,4 @@
1−/*
1+/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
@@ -38,12 +38,14 @@ import "Notifications"
38import "Stage"
38import "Stage"
39import "Tutorial"
39import "Tutorial"
40import "Wizard"
40import "Wizard"
41+import "Components/PanelState"
41import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Session 0.1
43import Unity.Session 0.1
43import Unity.DashCommunicator 0.1
44import Unity.DashCommunicator 0.1
44import Unity.Indicators 0.1 as Indicators
45import Unity.Indicators 0.1 as Indicators
45import Cursor 1.1
46import Cursor 1.1
46import WindowManager 1.0
47import WindowManager 1.0
48+import Unity.Debug 0.1 as Debug
47
49
48
50
49StyledItem {
51StyledItem {
@@ -96,6 +98,11 @@ StyledItem {
96
98
97 readonly property var mainApp: stage.mainApp
99 readonly property var mainApp: stage.mainApp
98
100
101+ readonly property var topLevelSurfaceList: {
102+ if (!WMScreen.currentWorkspace) return null;
103+ return WMScreen.currentWorkspace.windowModel
104+ }
105+
99 onMainAppChanged: {
106 onMainAppChanged: {
100 if (mainApp) {
107 if (mainApp) {
101 _onMainAppChanged(mainApp.appId);
108 _onMainAppChanged(mainApp.appId);
@@ -186,6 +193,7 @@ StyledItem {
186 if (ApplicationManager.findApplication(appId)) {
193 if (ApplicationManager.findApplication(appId)) {
187 ApplicationManager.requestFocusApplication(appId);
194 ApplicationManager.requestFocusApplication(appId);
188 } else {
195 } else {
196+ print("calling startApplication. active workspace is", WorkspaceManager.activeWorkspace)
189 ApplicationManager.startApplication(appId);
197 ApplicationManager.startApplication(appId);
190 }
198 }
191 }
199 }
@@ -260,23 +268,17 @@ StyledItem {
260 schema.id: "com.canonical.Unity8"
268 schema.id: "com.canonical.Unity8"
261 }
269 }
262
270
271+ PanelState {
272+ id: panelState
273+ objectName: "panelState"
274+ }
275+
263 Item {
276 Item {
264 id: stages
277 id: stages
265 objectName: "stages"
278 objectName: "stages"
266 width: parent.width
279 width: parent.width
267 height: parent.height
280 height: parent.height
268
281
269− SurfaceManager {
270− id: surfaceMan
271− objectName: "surfaceManager"
272− }
273− TopLevelWindowModel {
274− id: topLevelSurfaceList
275− objectName: "topLevelSurfaceList"
276− applicationManager: ApplicationManager // it's a singleton
277− surfaceManager: surfaceMan
278− }
279−
280 Stage {
282 Stage {
281 id: stage
283 id: stage
282 objectName: "stage"
284 objectName: "stage"
@@ -287,7 +289,7 @@ StyledItem {
287 background: wallpaperResolver.background
289 background: wallpaperResolver.background
288
290
289 applicationManager: ApplicationManager
291 applicationManager: ApplicationManager
290− topLevelSurfaceList: topLevelSurfaceList
292+ topLevelSurfaceList: shell.topLevelSurfaceList
291 inputMethodRect: inputMethod.visibleRect
293 inputMethodRect: inputMethod.visibleRect
292 rightEdgePushProgress: rightEdgeBarrier.progress
294 rightEdgePushProgress: rightEdgeBarrier.progress
293
295
@@ -316,6 +318,7 @@ StyledItem {
316 altTabPressed: physicalKeysMapper.altTabPressed
318 altTabPressed: physicalKeysMapper.altTabPressed
317 oskEnabled: shell.oskEnabled
319 oskEnabled: shell.oskEnabled
318 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
320 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
321+ panelState: panelState
319
322
320 onSpreadShownChanged: {
323 onSpreadShownChanged: {
321 panel.indicators.hide();
324 panel.indicators.hide();
@@ -356,7 +359,7 @@ StyledItem {
356 InputMethod {
359 InputMethod {
357 id: inputMethod
360 id: inputMethod
358 objectName: "inputMethod"
361 objectName: "inputMethod"
359− surface: topLevelSurfaceList.inputMethodSurface
362+ surface: shell.topLevelSurfaceList.inputMethodSurface
360 anchors {
363 anchors {
361 fill: parent
364 fill: parent
362 topMargin: panel.panelHeight
365 topMargin: panel.panelHeight
@@ -370,8 +373,13 @@ StyledItem {
370 objectName: "greeterLoader"
373 objectName: "greeterLoader"
371 anchors.fill: parent
374 anchors.fill: parent
372 anchors.topMargin: panel.panelHeight
375 anchors.topMargin: panel.panelHeight
373− sourceComponent: shell.mode != "shell" ? integratedGreeter :
376+ sourceComponent: {
377+ if (shell.mode != "shell") {
378+ if (screenWindow.primary) return integratedGreeter;
379+ return secondaryGreeter;
380+ }
374− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
381+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
382+ }
375 onLoaded: {
383 onLoaded: {
376 item.objectName = "greeter"
384 item.objectName = "greeter"
377 }
385 }
@@ -421,6 +429,13 @@ StyledItem {
421 }
429 }
422 }
430 }
423
431
432+ Component {
433+ id: secondaryGreeter
434+ SecondaryGreeter {
435+ hides: [launcher, panel.indicators]
436+ }
437+ }
438+
424 Timer {
439 Timer {
425 // See powerConnection for why this is useful
440 // See powerConnection for why this is useful
426 id: showGreeterDelayed
441 id: showGreeterDelayed
@@ -531,13 +546,14 @@ StyledItem {
531 && !stage.spreadShown
546 && !stage.spreadShown
532 }
547 }
533
548
534− readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
549+ readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
535− ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
550+ ? shell.topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
536 : false
551 : false
537 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
552 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
538 || greeter.hasLockedApp
553 || greeter.hasLockedApp
539 greeterShown: greeter && greeter.shown
554 greeterShown: greeter && greeter.shown
540 hasKeyboard: shell.hasKeyboard
555 hasKeyboard: shell.hasKeyboard
556+ panelState: panelState
541 }
557 }
542
558
543 Launcher {
559 Launcher {
@@ -785,12 +801,13 @@ StyledItem {
785
801
786 Cursor {
802 Cursor {
787 id: cursor
803 id: cursor
788− objectName: "cursor"
804+ objectName: "Pointer-"+screenWindow.objectName
789− visible: shell.hasMouse
805+
790 z: itemGrabber.z + 1
806 z: itemGrabber.z + 1
807+ opacity: 0
791 topBoundaryOffset: panel.panelHeight
808 topBoundaryOffset: panel.panelHeight
792−
809+ enabled: shell.hasMouse && screenWindow.active
793− confiningItem: stage.itemConfiningMouseCursor
810+ visible: enabled
794
811
795 property bool mouseNeverMoved: true
812 property bool mouseNeverMoved: true
796 Binding {
813 Binding {
@@ -802,6 +819,8 @@ StyledItem {
802 when: cursor.mouseNeverMoved && cursor.visible
819 when: cursor.mouseNeverMoved && cursor.visible
803 }
820 }
804
821
822+ confiningItem: stage.itemConfiningMouseCursor
823+
805 height: units.gu(3)
824 height: units.gu(3)
806
825
807 readonly property var previewRectangle: stage.previewRectangle.target &&
826 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -865,7 +884,7 @@ StyledItem {
865
884
866 // non-visual objects
885 // non-visual objects
867 KeymapSwitcher {
886 KeymapSwitcher {
868− focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
887+ focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
869 }
888 }
870 BrightnessControl {}
889 BrightnessControl {}
871
890
@@ -888,4 +907,20 @@ StyledItem {
888 }
907 }
889 }
908 }
890 }
909 }
910+
911+ Loader {
912+ z: shutdownFadeOutRectangle.z + 1
913+ active: DebuggingController.logOverlay
914+
915+ sourceComponent: Text {
916+ width: shell.width
917+ height: shell.height
918+
919+ text: consoleLog.out
920+ Debug.ConsoleLog {
921+ id: consoleLog
922+ enabled: true
923+ }
924+ }
925+ }
891}
926}
qml/ShellApplication.qmlnew file+60
@@ -0,0 +1,60 @@
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+import Unity.Application 0.1
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ ShellScreen {
27+ id: window
28+ objectName: "screen"+index
29+ screen: model.screen
30+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
31+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
32+
33+ Binding {
34+ when: applicationArguments.hasGeometry
35+ target: window
36+ property: "width"
37+ value: applicationArguments.windowGeometry.width
38+ }
39+ Binding {
40+ when: applicationArguments.hasGeometry
41+ target: window
42+ property: "height"
43+ value: applicationArguments.windowGeometry.height
44+ }
45+
46+ Component.onCompleted: screen.active = primary
47+ primary: index == 0
48+ }
49+
50+ property var windowManagerSurfaceManagerBinding: Binding {
51+ target: WindowManagerObjects
52+ property: "surfaceManager"
53+ value: SurfaceManager
54+ }
55+ property var windowManagerApplicationManagerBinding: Binding {
56+ target: WindowManagerObjects
57+ property: "applicationManager"
58+ value: ApplicationManager
59+ }
60+}
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+64
@@ -0,0 +1,64 @@
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+ id: loader
37+ width: screenWindow.width
38+ height: screenWindow.height
39+
40+ sourceComponent: {
41+ if (Screens.count > 1 && primary && deviceConfiguration.category !== "desktop") {
42+ return disabledScreenComponent;
43+ }
44+ return shellComponent;
45+ }
46+ }
47+
48+ Component {
49+ id: shellComponent
50+ OrientedShell {
51+ implicitWidth: screenWindow.width
52+ implicitHeight: screenWindow.height
53+
54+ deviceConfiguration {
55+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
56+ }
57+ }
58+ }
59+
60+ Component {
61+ id: disabledScreenComponent
62+ DisabledScreenNotice {}
63+ }
64+}
qml/Stage/ApplicationWindow.qml+9−207
@@ -50,37 +50,23 @@ FocusScope {
50 // transitions in stateGroup take place.
50 // transitions in stateGroup take place.
51 // More specifically, the moment surfaceContainer.surface gets updated relative to the
51 // More specifically, the moment surfaceContainer.surface gets updated relative to the
52 // other instructions.
52 // other instructions.
53+ print("AAA surface changed,", surface)
53 if (surface) {
54 if (surface) {
54 surfaceContainer.surface = surface;
55 surfaceContainer.surface = surface;
55− d.liveSurface = surface.live;
56− d.hadSurface = false;
56+ d.hadSurface = true;
57 surfaceInitTimer.start();
57 surfaceInitTimer.start();
58 } else {
58 } else {
59− if (d.surfaceInitialized) {
60− d.hadSurface = true;
61− }
62 d.surfaceInitialized = false;
59 d.surfaceInitialized = false;
63 surfaceContainer.surface = null;
60 surfaceContainer.surface = null;
64 }
61 }
65 }
62 }
66
63
64+ Component.onCompleted: print("AAA completed")
67 QtObject {
65 QtObject {
68 id: d
66 id: d
69
67
70− property bool liveSurface: false;
68+ onHadSurfaceChanged: print("AAA had surface changed,", hadSurface)
71− property var con: Connections {
69+ onSurfaceInitializedChanged: print("AAA surface initialized changed", surfaceInitialized)
72− target: root.surface
73− onLiveChanged: d.liveSurface = root.surface.live
74− }
75− // using liveSurface instead of root.surface.live because with the latter
76− // this expression is not reevaluated when root.surface changes
77− readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
78− && applicationState !== ApplicationInfoInterface.Running
79− onNeedToTakeScreenshotChanged: {
80− if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
81− screenshotImage.take();
82− }
83− }
84
70
85 // helpers so that we don't have to check for the existence of an application everywhere
71 // helpers so that we don't have to check for the existence of an application everywhere
86 // (in order to avoid breaking qml binding due to a javascript exception)
72 // (in order to avoid breaking qml binding due to a javascript exception)
@@ -135,39 +121,12 @@ FocusScope {
135 }
121 }
136 }
122 }
137
123
138− Timer {
139− id: surfaceIsOldTimer
140− interval: 1000
141− onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
142− }
143−
144− Image {
145− id: screenshotImage
146− objectName: "screenshotImage"
147− anchors.fill: parent
148− fillMode: Image.PreserveAspectCrop
149− horizontalAlignment: Image.AlignLeft
150− verticalAlignment: Image.AlignTop
151− antialiasing: !root.interactive
152− z: 1
153−
154− function take() {
155− // Save memory by using a half-resolution (thus quarter size) screenshot.
156− // Do not make this a binding, we can only take the screenshot once!
157− surfaceContainer.grabToImage(
158− function(result) {
159− screenshotImage.source = result.url;
160− },
161− Qt.size(root.width / 2, root.height / 2));
162− }
163− }
164−
165 Loader {
124 Loader {
166 id: splashLoader
125 id: splashLoader
167 visible: active
126 visible: active
168 active: false
127 active: false
169 anchors.fill: parent
128 anchors.fill: parent
170− z: screenshotImage.z + 1
129+ z: 1
171 sourceComponent: Component {
130 sourceComponent: Component {
172 Splash {
131 Splash {
173 id: splash
132 id: splash
@@ -239,166 +198,9 @@ FocusScope {
239 objectName: "applicationWindowStateGroup"
198 objectName: "applicationWindowStateGroup"
240 states: [
199 states: [
241 State {
200 State {
242− name: "void"
201+ name: "splash"
243− when:
244− d.hadSurface && (!root.surface || !d.surfaceInitialized)
245− &&
246− screenshotImage.status !== Image.Ready
247− },
248− State {
249− name: "splashScreen"
250− when:
251− !d.hadSurface && (!root.surface || !d.surfaceInitialized)
202+ when: !root.surface && !d.surfaceInitialized && !d.hadSurface
252− &&
253− screenshotImage.status !== Image.Ready
254− },
255− State {
256− name: "surface"
257− when:
258− (root.surface && d.surfaceInitialized)
259− &&
260− (d.liveSurface ||
261− (d.applicationState !== ApplicationInfoInterface.Running
262− && screenshotImage.status !== Image.Ready))
263− PropertyChanges {
264− target: root
265− implicitWidth: surfaceContainer.implicitWidth
266− implicitHeight: surfaceContainer.implicitHeight
267− }
268− },
269− State {
270− name: "screenshot"
271− when:
272− screenshotImage.status === Image.Ready
273− &&
274− (d.applicationState !== ApplicationInfoInterface.Running
275− || !root.surface || !d.surfaceInitialized)
276− },
277− State {
278− // This is a dead end. From here we expect the surface to be removed from the model
279− // shortly after we stop referencing to it in our SurfaceContainer.
280− name: "closed"
281− when:
282− // The surface died while the application is running. It must have been closed
283− // by the shell or the application decided to destroy it by itself
284− root.surface && d.surfaceInitialized && !d.liveSurface
285− && d.applicationState === ApplicationInfoInterface.Running
286− }
287− ]
288−
289− transitions: [
290− Transition {
291− from: ""; to: "splashScreen"
292− PropertyAction { target: splashLoader; property: "active"; value: true }
203+ PropertyChanges { target: splashLoader; active: true }
293− PropertyAction { target: surfaceContainer
294− property: "visible"; value: false }
295− },
296− Transition {
297− from: "splashScreen"; to: "surface"
298− SequentialAnimation {
299− PropertyAction { target: surfaceContainer
300− property: "opacity"; value: 0.0 }
301− PropertyAction { target: surfaceContainer
302− property: "visible"; value: true }
303− UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
304− from: 0.0; to: 1.0
305− duration: UbuntuAnimation.BriskDuration }
306− ScriptAction { script: {
307− splashLoader.active = false;
308− surfaceIsOldTimer.start();
309− } }
310− }
311− },
312− Transition {
313− from: "surface"; to: "splashScreen"
314− SequentialAnimation {
315− ScriptAction { script: {
316− surfaceIsOldTimer.stop();
317− d.surfaceOldEnoughToBeResized = false;
318− splashLoader.active = true;
319− surfaceContainer.visible = true;
320− } }
321− UbuntuNumberAnimation { target: splashLoader; property: "opacity";
322− from: 0.0; to: 1.0
323− duration: UbuntuAnimation.BriskDuration }
324− PropertyAction { target: surfaceContainer
325− property: "visible"; value: false }
326− }
327− },
328− Transition {
329− from: "surface"; to: "screenshot"
330− SequentialAnimation {
331− ScriptAction { script: {
332− surfaceIsOldTimer.stop();
333− d.surfaceOldEnoughToBeResized = false;
334− screenshotImage.visible = true;
335− } }
336− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
337− from: 0.0; to: 1.0
338− duration: UbuntuAnimation.BriskDuration }
339− ScriptAction { script: {
340− surfaceContainer.visible = false;
341− surfaceContainer.surface = null;
342− d.hadSurface = true;
343− } }
344− }
345− },
346− Transition {
347− from: "screenshot"; to: "surface"
348− SequentialAnimation {
349− PropertyAction { target: surfaceContainer
350− property: "visible"; value: true }
351− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
352− from: 1.0; to: 0.0
353− duration: UbuntuAnimation.BriskDuration }
354− ScriptAction { script: {
355− screenshotImage.visible = false;
356− screenshotImage.source = "";
357− surfaceIsOldTimer.start();
358− } }
359− }
360− },
361− Transition {
362− from: "splashScreen"; to: "screenshot"
363− SequentialAnimation {
364− PropertyAction { target: screenshotImage
365− property: "visible"; value: true }
366− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
367− from: 0.0; to: 1.0
368− duration: UbuntuAnimation.BriskDuration }
369− PropertyAction { target: splashLoader; property: "active"; value: false }
370− }
371− },
372− Transition {
373− from: "surface"; to: "void"
374− ScriptAction { script: {
375− surfaceIsOldTimer.stop();
376− d.surfaceOldEnoughToBeResized = false;
377− surfaceContainer.visible = false;
378− } }
379− },
380− Transition {
381− from: "void"; to: "surface"
382− SequentialAnimation {
383− PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
384− PropertyAction { target: surfaceContainer; property: "visible"; value: true }
385− UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
386− from: 0.0; to: 1.0
387− duration: UbuntuAnimation.BriskDuration }
388− ScriptAction { script: {
389− surfaceIsOldTimer.start();
390− } }
391− }
392− },
393− Transition {
394− to: "closed"
395− SequentialAnimation {
396− ScriptAction { script: {
397− surfaceContainer.visible = false;
398− surfaceContainer.surface = null;
399− d.hadSurface = true;
400− } }
401− }
402 }
204 }
403 ]
205 ]
404 }
206 }
qml/Stage/DecoratedWindow.qml+13−11
@@ -38,6 +38,7 @@ FocusScope {
38 property alias maximizeButtonShown: decoration.maximizeButtonShown
38 property alias maximizeButtonShown: decoration.maximizeButtonShown
39 property alias interactive: applicationWindow.interactive
39 property alias interactive: applicationWindow.interactive
40 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
40 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
41+ property PanelState panelState
41
42
42 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
43 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
43 property bool hasDecoration: true
44 property bool hasDecoration: true
@@ -118,15 +119,15 @@ FocusScope {
118 name: "preview"; when: root.scaleToPreviewProgress > 0
119 name: "preview"; when: root.scaleToPreviewProgress > 0
119 PropertyChanges {
120 PropertyChanges {
120 target: root
121 target: root
121− implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
122+ implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
122− implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
123+ implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
123 }
124 }
124 PropertyChanges {
125 PropertyChanges {
125 target: applicationWindow;
126 target: applicationWindow;
126− requestedWidth: applicationWindow.oldRequestedWidth
127+// requestedWidth: applicationWindow.oldRequestedWidth
127− requestedHeight: applicationWindow.oldRequestedHeight
128+// requestedHeight: applicationWindow.oldRequestedHeight
128− width: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
129+ width: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
129− height: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
130+ height: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
130 itemScale: root.implicitWidth / width
131 itemScale: root.implicitWidth / width
131 }
132 }
132 }
133 }
@@ -165,10 +166,10 @@ FocusScope {
165 height: implicitHeight
166 height: implicitHeight
166 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
167 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
167 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
168 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
168− property int oldRequestedWidth: requestedWidth
169+// property int oldRequestedWidth: requestedWidth
169− property int oldRequestedHeight: requestedHeight
170+// property int oldRequestedHeight: requestedHeight
170− onRequestedWidthChanged: oldRequestedWidth = requestedWidth
171+// onRequestedWidthChanged: oldRequestedWidth = requestedWidth
171− onRequestedHeightChanged: oldRequestedHeight = requestedHeight
172+// onRequestedHeightChanged: oldRequestedHeight = requestedHeight
172 focus: true
173 focus: true
173
174
174 property real itemScale: 1
175 property real itemScale: 1
@@ -211,6 +212,7 @@ FocusScope {
211
212
212 title: applicationWindow.title
213 title: applicationWindow.title
213 windowMoving: moveHandler.moving && !altDragHandler.dragging
214 windowMoving: moveHandler.moving && !altDragHandler.dragging
215+ panelState: root.panelState
214
216
215 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
217 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
216 Behavior on opacity { UbuntuNumberAnimation { } }
218 Behavior on opacity { UbuntuNumberAnimation { } }
@@ -234,7 +236,7 @@ FocusScope {
234 enableMenus: {
236 enableMenus: {
235 return active &&
237 return active &&
236 surface &&
238 surface &&
237− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
239+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
238 }
240 }
239 menu: sharedAppModel.model
241 menu: sharedAppModel.model
240
242
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
35 property int leftMargin
35 property int leftMargin
36 property real appContainerWidth
36 property real appContainerWidth
37 property real appContainerHeight
37 property real appContainerHeight
38+ property PanelState panelState
38
39
39 readonly property real hintThreshold: 0.1
40 readonly property real hintThreshold: 0.1
40
41
@@ -178,7 +179,7 @@ Rectangle {
178 ParallelAnimation {
179 ParallelAnimation {
179 id: fakeMaximizeAnimation
180 id: fakeMaximizeAnimation
180 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
182 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 }
185 }
@@ -186,39 +187,39 @@ Rectangle {
186 ParallelAnimation {
187 ParallelAnimation {
187 id: fakeMaximizeLeftAnimation
188 id: fakeMaximizeLeftAnimation
188 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
190 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
192 }
193 }
193
194
194 ParallelAnimation {
195 ParallelAnimation {
195 id: fakeMaximizeRightAnimation
196 id: fakeMaximizeRightAnimation
196 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
198 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
200 }
201 }
201
202
202 ParallelAnimation {
203 ParallelAnimation {
203 id: fakeMaximizeTopLeftAnimation
204 id: fakeMaximizeTopLeftAnimation
204 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
206 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
208 }
209 }
209
210
210 ParallelAnimation {
211 ParallelAnimation {
211 id: fakeMaximizeTopRightAnimation
212 id: fakeMaximizeTopRightAnimation
212 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
214 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
216 }
217 }
217
218
218 ParallelAnimation {
219 ParallelAnimation {
219 id: fakeMaximizeBottomLeftAnimation
220 id: fakeMaximizeBottomLeftAnimation
220 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
222 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 }
225 }
@@ -226,7 +227,7 @@ Rectangle {
226 ParallelAnimation {
227 ParallelAnimation {
227 id: fakeMaximizeBottomRightAnimation
228 id: fakeMaximizeBottomRightAnimation
228 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
230 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 }
233 }
qml/Stage/Spread/ScreensAndWorkspaces.qmlnew file+212
@@ -0,0 +1,212 @@
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+ signal closeSpread();
16+
17+ Row {
18+ id: row
19+ anchors.bottom: parent.bottom
20+ anchors.horizontalCenter: parent.horizontalCenter
21+ Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: UbuntuAnimation.SlowDuration } }
22+// anchors.left: parent.left
23+ spacing: units.gu(1)
24+
25+ Repeater {
26+ model: screensProxy
27+
28+ delegate: Item {
29+ height: root.height - units.gu(6)
30+ width: workspaces.width
31+
32+ UbuntuShape {
33+ id: header
34+ anchors { left: parent.left; top: parent.top; right: parent.right }
35+ height: units.gu(7)
36+ backgroundColor: "white"
37+ z: 1
38+
39+ DropArea {
40+ anchors.fill: parent
41+ keys: ["workspace"]
42+
43+ onEntered: {
44+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
45+ drag.source.inDropArea = true;
46+ }
47+
48+ onExited: {
49+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
50+ drag.source.inDropArea = false;
51+ }
52+
53+ onDropped: {
54+ drag.source.inDropArea = false;
55+ }
56+ }
57+
58+ Column {
59+ anchors.fill: parent
60+ anchors.margins: units.gu(1)
61+
62+ Label {
63+ text: model.screen.name
64+ color: "black"
65+ }
66+
67+ Label {
68+ text: model.screen.outputTypeName
69+ color: "black"
70+ fontSize: "x-small"
71+ }
72+
73+ Label {
74+ text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
75+ color: "black"
76+ fontSize: "x-small"
77+ }
78+ }
79+
80+ MouseArea {
81+ anchors.fill: parent
82+
83+ onClicked: {
84+ var obj = screensMenuComponent.createObject(header)
85+ obj.open(mouseX, mouseY)
86+ }
87+ }
88+
89+ Component {
90+ id: screensMenuComponent
91+ UbuntuShape {
92+ id: screensMenu
93+ width: units.gu(20)
94+ height: contentColumn.childrenRect.height
95+ backgroundColor: "white"
96+
97+ function open(mouseX, mouseY) {
98+ x = Math.max(0, Math.min(mouseX - width / 2, parent.width - width))
99+ y = mouseY + units.gu(1)
100+ }
101+
102+ InverseMouseArea {
103+ anchors.fill: parent
104+ onClicked: {
105+ screensMenu.destroy()
106+ }
107+ }
108+
109+ Column {
110+ id: contentColumn
111+ width: parent.width
112+ ListItem {
113+ height: layout.height
114+ highlightColor: "transparent"
115+ ListItemLayout {
116+ id: layout
117+ title.text: qsTr("Add workspace")
118+ title.color: "black"
119+ }
120+ onClicked: {
121+ screen.workspaces.addWorkspace();
122+ Screens.sync(root.screensProxy);
123+ screensMenu.destroy();
124+ }
125+ }
126+ }
127+ }
128+ }
129+ }
130+
131+ Workspaces {
132+ id: workspaces
133+ height: parent.height - header.height - units.gu(2)
134+ width: {
135+ var width = 0;
136+ if (screensProxy.count == 1) {
137+ width = Math.min(implicitWidth, root.width - units.gu(8));
138+ } else {
139+ width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
140+ }
141+ return Math.max(workspaces.minimumWidth, width);
142+ }
143+
144+ Behavior on width { UbuntuNumberAnimation {} }
145+ anchors.bottom: parent.bottom
146+ anchors.bottomMargin: units.gu(1)
147+ anchors.horizontalCenter: parent.horizontalCenter
148+ screen: model.screen
149+ background: root.background
150+
151+ workspaceModel: model.screen.workspaces
152+ readOnly: false
153+
154+ onCommitScreenSetup: Screens.sync(root.screensProxy)
155+ onCloseSpread: root.closeSpread();
156+ }
157+ }
158+ }
159+ }
160+
161+ Rectangle {
162+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
163+ width: units.gu(5)
164+ color: "#33000000"
165+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
166+ MouseArea {
167+ id: leftScrollArea
168+ anchors.fill: parent
169+ hoverEnabled: true
170+ onPressed: mouse.accepted = false;
171+ }
172+ DropArea {
173+ id: leftFakeDropArea
174+ anchors.fill: parent
175+ keys: ["application", "workspace"]
176+ }
177+ }
178+ Rectangle {
179+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
180+ width: units.gu(5)
181+ color: "#33000000"
182+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
183+ MouseArea {
184+ id: rightScrollArea
185+ anchors.fill: parent
186+ hoverEnabled: true
187+ onPressed: mouse.accepted = false;
188+ }
189+ DropArea {
190+ id: rightFakeDropArea
191+ anchors.fill: parent
192+ keys: ["application", "workspace"]
193+ }
194+ }
195+ Timer {
196+ repeat: true
197+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
198+ interval: UbuntuAnimation.SlowDuration
199+ triggeredOnStart: true
200+ onTriggered: {
201+ var newOffset = row.anchors.horizontalCenterOffset;
202+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
203+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
204+ newOffset += units.gu(20)
205+ } else {
206+ newOffset -= units.gu(20)
207+ }
208+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
209+ row.anchors.horizontalCenterOffset = newOffset;
210+ }
211+ }
212+}
qml/Stage/Spread/Spread.qml+7−8
@@ -28,10 +28,10 @@ Item {
28 property var spreadFlickable
28 property var spreadFlickable
29
29
30 // some config options
30 // some config options
31− property real contentMargin: 0.16 * root.height
31+ property real contentMargin: 0.1 * root.height
32− property real contentTopMargin: contentMargin
32+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
33− property real contentBottomMargin: 0.35 * contentMargin
33+ property real contentBottomMargin: contentMargin
34− property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
34+ property real windowTitleTopMargin: contentMargin - windowTitle.height
35 property int stackItemCount: 3
35 property int stackItemCount: 3
36 property real leftRotationAngle: 22
36 property real leftRotationAngle: 22
37 property real rightRotationAngle: 32
37 property real rightRotationAngle: 32
@@ -52,7 +52,7 @@ Item {
52
52
53 readonly property real spreadWidth: rightStackXPos - leftStackXPos
53 readonly property real spreadWidth: rightStackXPos - leftStackXPos
54 readonly property real spreadHeight: root.height
54 readonly property real spreadHeight: root.height
55− readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
55+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
56 readonly property real spreadItemWidth: stackHeight
56 readonly property real spreadItemWidth: stackHeight
57
57
58 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
58 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
@@ -92,10 +92,9 @@ Item {
92
92
93 readonly property real visibleItemCount: (spreadWidth / spreadItemWidth) / (1 - itemOverlap)
93 readonly property real visibleItemCount: (spreadWidth / spreadItemWidth) / (1 - itemOverlap)
94
94
95− readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
95+ readonly property real spreadTotalWidth: totalItemCount * spreadWidth / visibleItemCount
96−
97− readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth ,0) / (2 * spreadWidth)
98
96
97+ readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + (leftStackXPos - leftMargin) * 2, 0) / (2 * spreadWidth)
99
98
100 readonly property var curve: BezierCurve {
99 readonly property var curve: BezierCurve {
101 controlPoint2: {'x': 0.19, 'y': 0.00}
100 controlPoint2: {'x': 0.19, 'y': 0.00}
qml/Stage/Spread/SpreadDelegateInputArea.qml+36−17
@@ -28,6 +28,9 @@ Item {
28
28
29 readonly property alias distance: d.distance
29 readonly property alias distance: d.distance
30
30
31+ property var stage: null
32+ property var dragDelegate: null
33+
31 signal clicked()
34 signal clicked()
32 signal close()
35 signal close()
33
36
@@ -84,33 +87,27 @@ Item {
84 }
87 }
85 }
88 }
86
89
87− // Event eater
88− MouseArea {
89− anchors.fill: parent
90− onClicked: root.clicked()
91− onWheel: wheel.accepted = true
92− }
93−
94 MultiPointTouchArea {
90 MultiPointTouchArea {
95 anchors.fill: parent
91 anchors.fill: parent
96− mouseEnabled: false
97 maximumTouchPoints: 1
92 maximumTouchPoints: 1
98 property int offset: 0
93 property int offset: 0
99
94
95+ // tp.startY seems to be broken for mouse interaction... lets track it ourselves
96+ property int startY: 0
97+
100 touchPoints: [
98 touchPoints: [
101 TouchPoint {
99 TouchPoint {
102 id: tp
100 id: tp
103 }
101 }
104 ]
102 ]
105
103
106− onCanceled: {
104+ onPressed: {
107− d.moving = false
105+ startY = tp.y
108− animation.animate("center");
109 }
106 }
110
107
111 onTouchUpdated: {
108 onTouchUpdated: {
112− if (!d.moving) {
109+ if (!d.moving || !tp.pressed) {
113− if (Math.abs(tp.startY - tp.y) > d.threshold) {
110+ if (Math.abs(startY - tp.y) > d.threshold) {
114 d.moving = true;
111 d.moving = true;
115 d.dragEvents = []
112 d.dragEvents = []
116 offset = tp.y - tp.startY;
113 offset = tp.y - tp.startY;
@@ -119,17 +116,32 @@ Item {
119 }
116 }
120 }
117 }
121
118
122− if (root.closeable) {
119+
123− d.distance = tp.y - tp.startY - offset
120+ var value = tp.y - tp.startY - offset;
121+ if (value < 0) {
122+ var coords = mapToItem(stage, tp.x, tp.y);
123+ dragDelegate.Drag.hotSpot.x = dragDelegate.width / 2
124+ dragDelegate.Drag.hotSpot.y = units.gu(2)
125+ dragDelegate.x = coords.x - dragDelegate.Drag.hotSpot.x
126+ dragDelegate.y = coords.y - dragDelegate.Drag.hotSpot.y
127+ dragDelegate.Drag.active = true;
128+ dragDelegate.surface = model.window.surface;
129+
124 } else {
130 } else {
125− var value = tp.y - tp.startY - offset;
131+ if (root.closeable) {
132+ d.distance = value
133+ } else {
126− d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
134+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
135+ }
127 }
136 }
128
137
129 d.pushDragEvent(tp);
138 d.pushDragEvent(tp);
130 }
139 }
131
140
132 onReleased: {
141 onReleased: {
142+ var result = dragDelegate.Drag.drop();
143+ dragDelegate.surface = null;
144+
133 if (!d.moving) {
145 if (!d.moving) {
134 root.clicked()
146 root.clicked()
135 }
147 }
@@ -149,6 +161,13 @@ Item {
149 animation.animate("center")
161 animation.animate("center")
150 }
162 }
151 }
163 }
164+
165+ onCanceled: {
166+ dragDelegate.Drag.active = false;
167+ dragDelegate.surface = null;
168+ d.moving = false
169+ animation.animate("center");
170+ }
152 }
171 }
153
172
154 UbuntuNumberAnimation {
173 UbuntuNumberAnimation {
qml/Stage/Spread/WorkspacePreview.qmlnew file+275
@@ -0,0 +1,275 @@
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 var workspace
13+
14+ property string background
15+ property int screenHeight
16+
17+ property real previewScale: previewSpace.height / previewSpace.screenHeight
18+
19+ property bool containsDragLeft: false
20+ property bool containsDragRight: false
21+ property bool isActive: false
22+ property bool isSelected: false
23+
24+ Image {
25+ source: previewSpace.background
26+ anchors.fill: parent
27+ sourceSize.width: width
28+ sourceSize.height: height
29+
30+ Repeater {
31+ id: topLevelSurfaceRepeater
32+ model: visible ? workspace.windowModel : null
33+ delegate: Rectangle {
34+ width: surfaceItem.width
35+ height: surfaceItem.height + decorationHeight * previewScale
36+ x: model.window.position.x * previewScale
37+ y: (model.window.position.y - decorationHeight) * previewScale
38+ color: "blue"
39+ z: topLevelSurfaceRepeater.count - index
40+
41+ property int decorationHeight: units.gu(3)
42+
43+ WindowDecoration {
44+ width: surfaceItem.implicitWidth
45+ height: parent.decorationHeight
46+ transform: Scale {
47+ origin.x: 0
48+ origin.y: 0
49+ xScale: previewScale
50+ yScale: previewScale
51+ }
52+ title: model.window && model.window.surface ? model.window.surface.name : ""
53+ z: 3
54+ }
55+
56+ MirSurfaceItem {
57+ id: surfaceItem
58+ y: parent.decorationHeight * previewScale
59+ width: implicitWidth * previewScale
60+ height: implicitHeight * previewScale
61+ surfaceWidth: -1
62+ surfaceHeight: -1
63+ onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
64+ surface: model.window.surface
65+ }
66+ }
67+ }
68+
69+ }
70+
71+ Rectangle {
72+ anchors.fill: parent
73+ border.color: UbuntuColors.ash
74+ border.width: units.gu(.5)
75+ color: "transparent"
76+ visible: previewSpace.isActive
77+ }
78+
79+ Rectangle {
80+ anchors.fill: parent
81+ border.color: UbuntuColors.blue
82+ border.width: units.gu(.5)
83+ color: "transparent"
84+ visible: previewSpace.isSelected
85+ }
86+
87+ Rectangle {
88+ anchors.fill: parent
89+ anchors.rightMargin: parent.width / 2
90+ color: "#55000000"
91+ visible: previewSpace.containsDragLeft
92+
93+ Column {
94+ anchors.centerIn: parent
95+ spacing: units.gu(1)
96+ Icon {
97+ source: "../graphics/multi-monitor_drop-here.png"
98+ height: units.gu(4)
99+ width: height
100+ anchors.horizontalCenter: parent.horizontalCenter
101+ }
102+ Label {
103+ text: qsTr("Drop here")
104+ }
105+ }
106+ }
107+
108+ Rectangle {
109+ anchors.fill: parent
110+ anchors.leftMargin: parent.width / 2
111+ color: "#55000000"
112+ visible: previewSpace.containsDragRight
113+
114+ Column {
115+ anchors.centerIn: parent
116+ spacing: units.gu(1)
117+ Icon {
118+ source: "../graphics/multi-monitor_leave.png"
119+ height: units.gu(4)
120+ width: height
121+ anchors.horizontalCenter: parent.horizontalCenter
122+ }
123+ Label {
124+ text: qsTr("Drop and go")
125+ }
126+ }
127+ }
128+}
129+
130+ // Repeater {
131+ // id: topLevelSurfaceRepeater
132+ // model: visible ? topLevelSurfaceList : null
133+ // delegate: Rectangle {
134+ // width: surfaceItem.width
135+ // height: surfaceItem.height + decorationHeight * previewScale
136+ // x: model.window.position.x * previewScale
137+ // y: (model.window.position.y - decorationHeight) * previewScale
138+ // color: "blue"
139+ // z: topLevelSurfaceRepeater.count - index
140+
141+ // property int decorationHeight: units.gu(3)
142+
143+ // WindowDecoration {
144+ // width: surfaceItem.implicitWidth
145+ // height: parent.decorationHeight
146+ // transform: Scale {
147+ // origin.x: 0
148+ // origin.y: 0
149+ // xScale: previewScale
150+ // yScale: previewScale
151+ // }
152+ // title: model.window && model.window.surface ? model.window.surface.name : ""
153+ // z: 3
154+ // }
155+
156+ // MirSurfaceItem {
157+ // id: surfaceItem
158+ // y: parent.decorationHeight * previewScale
159+ // width: implicitWidth * previewScale
160+ // height: implicitHeight * previewScale
161+ // surfaceWidth: -1
162+ // surfaceHeight: -1
163+ // onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
164+ // surface: model.window.surface
165+ // }
166+ // }
167+ // }
168+// }
169+// }
170+
171+
172+// MouseArea {
173+// id: mouseArea
174+// anchors.centerIn: parent
175+// anchors.verticalCenterOffset: header.height / 2
176+// width: units.gu(8)
177+// height: width
178+// hoverEnabled: true
179+// opacity: containsMouse ? 1 : 0
180+
181+// Rectangle {
182+// anchors.fill: parent
183+// color: "#80000000"
184+// radius: height / 2
185+// }
186+
187+// NumberAnimation {
188+// target: canvas
189+// property: "progress"
190+// duration: 2000
191+// running: mouseArea.containsMouse
192+// from: 0
193+// to: 1
194+// }
195+
196+// Canvas {
197+// id: canvas
198+// height: parent.height
199+// width: height
200+// anchors.centerIn: parent
201+
202+// property real progress: 0.5
203+// property int lineWidth: units.dp(4)
204+// onProgressChanged: {
205+// requestPaint();
206+// if (progress == 1) {
207+// Screens.activateScreen(index);
208+// }
209+// }
210+
211+// rotation: -90
212+
213+// onPaint: {
214+// var ctx = canvas.getContext("2d");
215+// ctx.save();
216+// ctx.reset();
217+
218+// ctx.lineWidth = lineWidth;
219+
220+// ctx.strokeStyle = "#3bffffff"
221+// ctx.beginPath();
222+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2),false);
223+// ctx.stroke();
224+// ctx.closePath();
225+
226+// ctx.strokeStyle = "white"
227+// ctx.beginPath();
228+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2*(progress)),false);
229+// ctx.stroke();
230+// ctx.closePath();
231+
232+// ctx.restore();
233+// }
234+// }
235+
236+// Icon {
237+// source: "graphics/multi-monitor_leave.png"
238+// height: units.gu(4)
239+// width: height
240+// anchors.centerIn: parent
241+// }
242+// }
243+// }
244+
245+// FloatingFlickable {
246+// id: flickable
247+// anchors.fill: parent
248+// contentWidth: workspaceRepeater.count * previewSpace.screenWidth * previewScale
249+// property real progress: contentX / contentWidth
250+// onContentXChanged: print("contentX:", contentX, "progress:", progress)
251+// }
252+
253+// MouseArea {
254+// anchors.fill: parent
255+// hoverEnabled: true
256+// property int scrollAreaWidth: width / 3
257+
258+// onMouseXChanged: {
259+// var margins = flickable.width * 0.05;
260+
261+// // do we need to scroll?
262+// if (mouseX < scrollAreaWidth + margins) {
263+// var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
264+// var contentX = (1 - progress) * (flickable.contentWidth - flickable.width)
265+// flickable.contentX = Math.max(0, Math.min(flickable.contentX, contentX))
266+// }
267+// if (mouseX > flickable.width - scrollAreaWidth) {
268+// var progress = Math.min(1, (mouseX - (flickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
269+// var contentX = progress * (flickable.contentWidth - flickable.width)
270+// flickable.contentX = Math.min(flickable.contentWidth - flickable.width, Math.max(flickable.contentX, contentX))
271+// }
272+// }
273+// }
274+// }
275+//}
qml/Stage/Spread/Workspaces.qmlnew file+415
@@ -0,0 +1,415 @@
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+ readonly property int minimumWidth: {
11+ var count = Math.min(3, listView.count);
12+ return listView.itemWidth * count + listView.spacing * (count - 1)
13+ }
14+
15+ property QtObject screen: null
16+ property alias workspaceModel: listView.model
17+ property var background // TODO: should be stored in the workspace data
18+ property int selectedIndex: -1
19+ property bool readOnly: true
20+
21+ signal commitScreenSetup();
22+ signal closeSpread();
23+
24+ DropArea {
25+ anchors.fill: root
26+
27+ keys: ['workspace']
28+
29+ onEntered: {
30+ var index = listView.getDropIndex(drag);
31+ drag.source.workspace.assign(workspaceModel, index)
32+ drag.source.inDropArea = true;
33+ }
34+
35+ onPositionChanged: {
36+ var index = listView.getDropIndex(drag);
37+ if (listView.dropItemIndex == index) return;
38+ listView.model.move(listView.dropItemIndex, index, 1);
39+ listView.dropItemIndex = index;
40+ }
41+
42+ onExited: {
43+ drag.source.workspace.unassign()
44+ listView.dropItemIndex = -1;
45+ listView.hoveredWorkspaceIndex = -1;
46+ drag.source.inDropArea = false;
47+ }
48+
49+ onDropped: {
50+ drop.accept(Qt.MoveAction);
51+ listView.dropItemIndex = -1;
52+ drag.source.inDropArea = false;
53+ }
54+ }
55+ DropArea {
56+ anchors.fill: parent
57+ keys: ["application"]
58+
59+ onPositionChanged: {
60+ listView.progressiveScroll(drag.x)
61+ listView.updateDropProperties(drag)
62+ }
63+ onExited: {
64+ listView.hoveredWorkspaceIndex = -1
65+ }
66+ onDropped: {
67+ var surface = drag.source.surface;
68+ drag.source.surface = null;
69+ var workspace = listView.model.get(listView.hoveredWorkspaceIndex);
70+ WorkspaceManager.moveSurfaceToWorkspace(surface, workspace);
71+ drop.accept(Qt.MoveAction)
72+ if (listView.hoveredHalf == "right") {
73+ workspace.activate();
74+ root.closeSpread();
75+ }
76+ listView.hoveredWorkspaceIndex = -1
77+ }
78+ }
79+
80+ Item {
81+ // We need to clip the listview as it has left/right margins and it would
82+ // overlap with items next to it and eat mouse input. However, we can't
83+ // just clip at the actual bounds as the delegates have the close button
84+ // on hover which reaches a bit outside, so lets some margins for the clipping
85+ anchors.fill: parent
86+ anchors.margins: -units.gu(2)
87+ clip: true
88+
89+ ListView {
90+ id: listView
91+ anchors {
92+ fill: parent
93+ topMargin: -parent.anchors.margins
94+ bottomMargin: -parent.anchors.margins
95+ leftMargin: -itemWidth - parent.anchors.margins
96+ rightMargin: -itemWidth - parent.anchors.margins
97+ }
98+ boundsBehavior: Flickable.StopAtBounds
99+
100+ Behavior on contentX {
101+ SmoothedAnimation { duration: 200 }
102+ }
103+
104+ orientation: ListView.Horizontal
105+ spacing: units.gu(1)
106+ leftMargin: itemWidth
107+ rightMargin: itemWidth
108+
109+ Connections {
110+ target: screen
111+ onAvailableModesChanged: {
112+ print("blabla", screen.availableModes[screen.currentModeIndex].size.width, screen.availableModes[screen.currentModeIndex].size.height)
113+ }
114+ }
115+
116+ property int screenWidth: screen.availableModes[screen.currentModeIndex].size.width
117+ property int screenHeight: screen.availableModes[screen.currentModeIndex].size.height
118+ property int itemWidth: height * screenWidth / screenHeight
119+ property int foldingAreaWidth: itemWidth / 2
120+ property int maxAngle: 40
121+
122+ property real realContentX: contentX - originX + leftMargin
123+ property int dropItemIndex: -1
124+ property int hoveredWorkspaceIndex: -1
125+ property string hoveredHalf: "" // left or right
126+
127+ function getDropIndex(drag) {
128+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
129+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
130+ if (index < 0) index = 0;
131+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
132+ if (index > upperLimit) index = upperLimit;
133+ return index;
134+ }
135+
136+ function updateDropProperties(drag) {
137+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
138+ var index = Math.floor(drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing);
139+ if (index < 0) {
140+ listView.hoveredWorkspaceIndex = -1;
141+ listView.hoveredHalf = "";
142+ return;
143+ }
144+
145+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
146+ if (index > upperLimit) index = upperLimit;
147+ listView.hoveredWorkspaceIndex = index;
148+ print("updating drag:", (drag.x + listView.realContentX) % (listView.itemWidth + listView.spacing))
149+ var pixelsInTile = (drag.x + listView.realContentX) % (listView.itemWidth + listView.spacing);
150+ listView.hoveredHalf = (pixelsInTile / listView.itemWidth) < .5 ? "left" : "right";
151+ }
152+
153+ function progressiveScroll(mouseX) {
154+ var progress = Math.max(0, Math.min(1, (mouseX - listView.itemWidth) / (width - listView.leftMargin * 2 - listView.itemWidth * 2)))
155+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
156+ }
157+
158+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
159+
160+ delegate: Item {
161+ id: workspaceDelegate
162+ objectName: "delegate" + index
163+ height: parent.height
164+ width: listView.itemWidth
165+ Behavior on width { UbuntuNumberAnimation {} }
166+ visible: listView.dropItemIndex !== index
167+
168+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
169+ property int distanceFromLeft: itemX //- listView.leftMargin
170+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
171+
172+ property int itemAngle: {
173+ if (index == 0) {
174+ if (distanceFromLeft < 0) {
175+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
176+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
177+ }
178+ return 0
179+ }
180+ if (index == listView.count - 1) {
181+ if (distanceFromRight < 0) {
182+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
183+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
184+ }
185+ return 0
186+ }
187+
188+ if (distanceFromLeft < listView.foldingAreaWidth) {
189+ // itemX : 10gu = p : 100
190+ var progress = distanceFromLeft / listView.foldingAreaWidth
191+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
192+ }
193+ if (distanceFromRight < listView.foldingAreaWidth) {
194+ var progress = distanceFromRight / listView.foldingAreaWidth
195+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
196+ }
197+ return 0
198+ }
199+
200+ property int itemOffset: {
201+ if (index == 0) {
202+ if (distanceFromLeft < 0) {
203+ return -distanceFromLeft
204+ }
205+ return 0
206+ }
207+ if (index == listView.count - 1) {
208+ if (distanceFromRight < 0) {
209+ return distanceFromRight
210+ }
211+ return 0
212+ }
213+
214+ if (itemX < -listView.foldingAreaWidth) {
215+ return -itemX
216+ }
217+ if (distanceFromLeft < listView.foldingAreaWidth) {
218+ return (listView.foldingAreaWidth - distanceFromLeft) / 2
219+ }
220+
221+ if (distanceFromRight < -listView.foldingAreaWidth) {
222+ return distanceFromRight
223+ }
224+
225+ if (distanceFromRight < listView.foldingAreaWidth) {
226+ return -(listView.foldingAreaWidth - distanceFromRight) / 2
227+ }
228+
229+ return 0
230+ }
231+
232+ // onItemXChanged: if (index == 1) print("x", itemX, listView.contentX)
233+
234+ z: itemOffset < 0 ? itemOffset : -itemOffset
235+ transform: [
236+ Rotation {
237+ angle: itemAngle
238+ axis { x: 0; y: 1; z: 0 }
239+ origin { x: itemAngle < 0 ? listView.itemWidth : 0; y: height / 2 }
240+ },
241+ Translate {
242+ x: itemOffset
243+ }
244+ ]
245+
246+ WorkspacePreview {
247+ id: workspacePreview
248+ height: listView.height
249+ width: listView.itemWidth
250+ background: root.background
251+ screenHeight: listView.screenHeight
252+ containsDragLeft: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "left"
253+ containsDragRight: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "right"
254+ isActive: model.workspace.active
255+ isSelected: index === root.selectedIndex
256+ workspace: model.workspace
257+ }
258+ MouseArea {
259+ anchors.fill: parent
260+ onClicked: {
261+ model.workspace.activate();
262+ }
263+ onDoubleClicked: {
264+ model.workspace.activate();
265+ root.closeSpread();
266+ }
267+ }
268+
269+ MouseArea {
270+ id: closeMouseArea
271+ objectName: "closeMouseArea"
272+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 }
273+ hoverEnabled: true
274+ height: units.gu(4)
275+ width: height
276+ visible: !root.readOnly && listView.count > 1
277+
278+ onClicked: {
279+ model.workspace.unassign();
280+ root.commitScreenSetup();
281+ }
282+ Image {
283+ id: closeImage
284+ source: "../graphics/window-close.svg"
285+ anchors.fill: closeMouseArea
286+ anchors.margins: units.gu(1)
287+ sourceSize.width: width
288+ sourceSize.height: height
289+ readonly property var mousePos: hoverMouseArea.mapToItem(workspaceDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
290+ readonly property bool shown: (hoverMouseArea.containsMouse || parent.containsMouse)
291+ && mousePos.y < workspaceDelegate.width / 4
292+ && mousePos.y > -units.gu(2)
293+ && mousePos.x > -units.gu(2)
294+ && mousePos.x < workspaceDelegate.height / 4
295+ opacity: shown ? 1 : 0
296+ visible: opacity > 0
297+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
298+
299+ }
300+ }
301+ }
302+
303+ MouseArea {
304+ id: hoverMouseArea
305+ anchors.fill: parent
306+ hoverEnabled: true
307+ propagateComposedEvents: true
308+ anchors.leftMargin: listView.leftMargin
309+ anchors.rightMargin: listView.rightMargin
310+ enabled: !root.readOnly
311+
312+ property int draggedIndex: -1
313+
314+ property int startX: 0
315+ property int startY: 0
316+
317+ onMouseXChanged: {
318+ if (!pressed || dragging) {
319+ listView.progressiveScroll(mouseX)
320+ }
321+ }
322+ onMouseYChanged: {
323+ if (Math.abs(mouseY - startY) > units.gu(3)) {
324+ drag.axis = Drag.XAndYAxis;
325+ }
326+ }
327+
328+ onReleased: {
329+ var result = fakeDragItem.Drag.drop();
330+ // if (result == Qt.IgnoreAction) {
331+ // WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
332+ // }
333+ root.commitScreenSetup();
334+ drag.target = null;
335+ }
336+
337+ property bool dragging: drag.active
338+ onDraggingChanged: {
339+ if (drag.active) {
340+ print("drai", draggedIndex)
341+ var ws = listView.model.get(draggedIndex);
342+ print("ws:", ws)
343+ ws.unassign();
344+ // listView.model.remove(draggedIndex, 1)
345+ }
346+ }
347+
348+ onPressed: {
349+ startX = mouseX;
350+ startY = mouseY;
351+ if (listView.model.count < 2) return;
352+
353+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
354+ draggedIndex = listView.indexAt(coords.x, coords.y)
355+ var clickedItem = listView.itemAt(coords.x, coords.y)
356+
357+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
358+ fakeDragItem.x = itemCoords.x
359+ fakeDragItem.y = itemCoords.y
360+ fakeDragItem.workspace = listView.model.get(draggedIndex)
361+
362+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
363+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
364+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
365+
366+ drag.axis = Drag.YAxis;
367+ drag.target = fakeDragItem;
368+ }
369+
370+ WorkspacePreview {
371+ id: fakeDragItem
372+ height: listView.height
373+ width: listView.itemWidth
374+ background: root.background
375+ screenHeight: screen.availableModes[screen.currentModeIndex].size.height
376+ visible: Drag.active
377+
378+ Drag.active: hoverMouseArea.drag.active
379+ Drag.keys: ['workspace']
380+
381+ property bool inDropArea: false
382+
383+ Rectangle {
384+ anchors.fill: parent
385+ color: "#33000000"
386+ opacity: parent.inDropArea ? 0 : 1
387+ Behavior on opacity { UbuntuNumberAnimation { } }
388+ Rectangle {
389+ anchors.centerIn: parent
390+ width: units.gu(6)
391+ height: units.gu(6)
392+ radius: width / 2
393+ color: "#aa000000"
394+ }
395+
396+ Icon {
397+ height: units.gu(3)
398+ width: height
399+ anchors.centerIn: parent
400+ name: "edit-delete"
401+ color: "white"
402+ }
403+ }
404+
405+ states: [
406+ State {
407+ when: fakeDragItem.Drag.active
408+ ParentChange { target: fakeDragItem; parent: shell }
409+ }
410+ ]
411+ }
412+ }
413+ }
414+ }
415+}
qml/Stage/Stage.qml+207−119
@@ -47,6 +47,7 @@ FocusScope {
47 property bool oskEnabled: false
47 property bool oskEnabled: false
48 property rect inputMethodRect
48 property rect inputMethodRect
49 property real rightEdgePushProgress: 0
49 property real rightEdgePushProgress: 0
50+ property PanelState panelState
50
51
51 // Configuration
52 // Configuration
52 property string mode: "staged"
53 property string mode: "staged"
@@ -224,6 +225,23 @@ FocusScope {
224 active: priv.focusedAppDelegate !== null
225 active: priv.focusedAppDelegate !== null
225 }
226 }
226
227
228+ GlobalShortcut {
229+ id: showWorkspaceSwitcherShortcutLeft
230+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Left
231+ active: !workspaceSwitcher.active
232+ onTriggered: {
233+ workspaceSwitcher.showLeft()
234+ }
235+ }
236+ GlobalShortcut {
237+ id: showWorkspaceSwitcherShortcutRight
238+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Right
239+ active: !workspaceSwitcher.active
240+ onTriggered: {
241+ workspaceSwitcher.showRight()
242+ }
243+ }
244+
227 QtObject {
245 QtObject {
228 id: priv
246 id: priv
229 objectName: "DesktopStagePrivate"
247 objectName: "DesktopStagePrivate"
@@ -349,23 +367,23 @@ FocusScope {
349 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
367 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
350 }
368 }
351
369
352− Component.onCompleted: priv.updateMainAndSideStageIndexes();
370+ Component.onCompleted: priv.updateMainAndSideStageIndexes()
353
371
354 Connections {
372 Connections {
355− target: PanelState
373+ target: panelState
356 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
374 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
357 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
375 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
358 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
376 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
359 }
377 }
360
378
361 Binding {
379 Binding {
362− target: PanelState
380+ target: panelState
363 property: "decorationsVisible"
381 property: "decorationsVisible"
364 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
382 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
365 }
383 }
366
384
367 Binding {
385 Binding {
368− target: PanelState
386+ target: panelState
369 property: "title"
387 property: "title"
370 value: {
388 value: {
371 if (priv.focusedAppDelegate !== null) {
389 if (priv.focusedAppDelegate !== null) {
@@ -380,7 +398,7 @@ FocusScope {
380 }
398 }
381
399
382 Binding {
400 Binding {
383− target: PanelState
401+ target: panelState
384 property: "focusedPersistentSurfaceId"
402 property: "focusedPersistentSurfaceId"
385 value: {
403 value: {
386 if (priv.focusedAppDelegate !== null) {
404 if (priv.focusedAppDelegate !== null) {
@@ -394,21 +412,21 @@ FocusScope {
394 }
412 }
395
413
396 Binding {
414 Binding {
397− target: PanelState
415+ target: panelState
398 property: "dropShadow"
416 property: "dropShadow"
399 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
417 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
400 }
418 }
401
419
402 Binding {
420 Binding {
403− target: PanelState
421+ target: panelState
404 property: "closeButtonShown"
422 property: "closeButtonShown"
405 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
423 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
406 }
424 }
407
425
408 Component.onDestruction: {
426 Component.onDestruction: {
409− PanelState.title = "";
427+ panelState.title = "";
410− PanelState.decorationsVisible = false;
428+ panelState.decorationsVisible = false;
411− PanelState.dropShadow = false;
429+ panelState.dropShadow = false;
412 }
430 }
413
431
414 Instantiator {
432 Instantiator {
@@ -454,6 +472,7 @@ FocusScope {
454 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
472 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
455 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
473 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
456 PropertyChanges { target: wallpaper; visible: false }
474 PropertyChanges { target: wallpaper; visible: false }
475+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
457 },
476 },
458 State {
477 State {
459 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
478 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
@@ -502,10 +521,13 @@ FocusScope {
502 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
521 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
503 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
522 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
504 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
523 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
524+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
505 },
525 },
506 Transition {
526 Transition {
507 to: "spread"
527 to: "spread"
508 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
528 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
529+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
530+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
509 },
531 },
510 Transition {
532 Transition {
511 from: "spread"
533 from: "spread"
@@ -563,10 +585,21 @@ FocusScope {
563 visible: false
585 visible: false
564 }
586 }
565
587
588+ ScreensAndWorkspaces {
589+ id: screensAndWorkspaces
590+ anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.leftMargin }
591+ height: Math.max(units.gu(30), parent.height * .3)
592+ background: root.background
593+ opacity: 0
594+ visible: opacity > 0
595+
596+ onCloseSpread: priv.goneToSpread = false;
597+ }
598+
566 Spread {
599 Spread {
567 id: spreadItem
600 id: spreadItem
568 objectName: "spreadItem"
601 objectName: "spreadItem"
569− anchors.fill: appContainer
602+ anchors { left: parent.left; bottom: parent.bottom; right: parent.right; top: screensAndWorkspaces.bottom }
570 leftMargin: root.leftMargin
603 leftMargin: root.leftMargin
571 model: root.topLevelSurfaceList
604 model: root.topLevelSurfaceList
572 spreadFlickable: floatingFlickable
605 spreadFlickable: floatingFlickable
@@ -581,6 +614,83 @@ FocusScope {
581 appRepeater.itemAt(highlightedIndex).close();
614 appRepeater.itemAt(highlightedIndex).close();
582 }
615 }
583 }
616 }
617+
618+ FloatingFlickable {
619+ id: floatingFlickable
620+ objectName: "spreadFlickable"
621+ anchors.fill: parent
622+ enabled: false
623+ contentWidth: spreadItem.spreadTotalWidth
624+
625+ function snap(toIndex) {
626+ var delegate = appRepeater.itemAt(toIndex)
627+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
628+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
629+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
630+ snapAnimation.to = floatingFlickable.contentX - offset;
631+ snapAnimation.start();
632+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
633+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
634+ snapAnimation.to = floatingFlickable.contentX - offset;
635+ snapAnimation.start();
636+ }
637+ }
638+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
639+ }
640+
641+ MouseArea {
642+ id: hoverMouseArea
643+ objectName: "hoverMouseArea"
644+ anchors.fill: parent
645+ propagateComposedEvents: true
646+ hoverEnabled: true
647+ enabled: false
648+ visible: enabled
649+
650+ property int scrollAreaWidth: width / 3
651+ property bool progressiveScrollingEnabled: false
652+
653+ onPressed: mouse.accepted = false
654+
655+ onMouseXChanged: {
656+ mouse.accepted = false
657+
658+ if (hoverMouseArea.pressed) {
659+ return;
660+ }
661+
662+ // Find the hovered item and mark it active
663+ for (var i = appRepeater.count - 1; i >= 0; i--) {
664+ var appDelegate = appRepeater.itemAt(i);
665+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
666+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
667+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
668+ spreadItem.highlightedIndex = i;
669+ break;
670+ }
671+ }
672+
673+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
674+ var margins = floatingFlickable.width * 0.05;
675+
676+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
677+ progressiveScrollingEnabled = true
678+ }
679+
680+ // do we need to scroll?
681+ if (mouseX < scrollAreaWidth + margins) {
682+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
683+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
684+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
685+ }
686+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
687+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
688+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
689+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
690+ }
691+ }
692+ }
693+ }
584 }
694 }
585
695
586 Connections {
696 Connections {
@@ -675,10 +785,28 @@ FocusScope {
675 }
785 }
676 }
786 }
677
787
788+ MirSurfaceItem {
789+ id: fakeDragItem
790+ property real previewScale: .5
791+ height: (screensAndWorkspaces.height - units.gu(8)) / 2
792+ // w : h = iw : ih
793+ width: implicitWidth * height / implicitHeight
794+ surfaceWidth: -1
795+ surfaceHeight: -1
796+ opacity: surface != null ? 1 : 0
797+ Behavior on opacity { UbuntuNumberAnimation {} }
798+ visible: opacity > 0
799+
800+ Drag.active: surface != null
801+ Drag.keys: ["application"]
802+
803+ z: 1000
804+ }
805+
678 Item {
806 Item {
679 id: boundariesForWindowPlacement
807 id: boundariesForWindowPlacement
680 anchors.fill: parent
808 anchors.fill: parent
681− anchors.topMargin: PanelState.panelHeight
809+ anchors.topMargin: panelState.panelHeight
682 visible: false
810 visible: false
683 }
811 }
684
812
@@ -710,6 +838,9 @@ FocusScope {
710 }
838 }
711 z: normalZ
839 z: normalZ
712
840
841+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
842+ Behavior on opacity { UbuntuNumberAnimation {} }
843+
713 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
844 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
714 // match what the actual surface size is.
845 // match what the actual surface size is.
715 // Don't write to those, they will be set by states
846 // Don't write to those, they will be set by states
@@ -779,7 +910,7 @@ FocusScope {
779 target: appDelegate
910 target: appDelegate
780 property: "y"
911 property: "y"
781 value: appDelegate.requestedY -
912 value: appDelegate.requestedY -
782− Math.min(appDelegate.requestedY - PanelState.panelHeight,
913+ Math.min(appDelegate.requestedY - panelState.panelHeight,
783 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
914 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
784 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
915 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
785 && root.inputMethodRect.height > 0
916 && root.inputMethodRect.height > 0
@@ -890,7 +1021,6 @@ FocusScope {
890 function claimFocus() {
1021 function claimFocus() {
891 if (root.state == "spread") {
1022 if (root.state == "spread") {
892 spreadItem.highlightedIndex = index
1023 spreadItem.highlightedIndex = index
893− priv.goneToSpread = false;
894 }
1024 }
895 if (root.mode == "stagedWithSideStage") {
1025 if (root.mode == "stagedWithSideStage") {
896 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1026 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
@@ -1177,7 +1307,7 @@ FocusScope {
1177 progress: 0
1307 progress: 0
1178 targetHeight: spreadItem.stackHeight
1308 targetHeight: spreadItem.stackHeight
1179 targetX: spreadMaths.targetX
1309 targetX: spreadMaths.targetX
1180− startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1310+ startY: appDelegate.fullscreen ? 0 : panelState.panelHeight
1181 targetY: spreadMaths.targetY
1311 targetY: spreadMaths.targetY
1182 targetAngle: spreadMaths.targetAngle
1312 targetAngle: spreadMaths.targetAngle
1183 targetScale: spreadMaths.targetScale
1313 targetScale: spreadMaths.targetScale
@@ -1223,8 +1353,6 @@ FocusScope {
1223 y: spreadMaths.targetY
1353 y: spreadMaths.targetY
1224 z: index
1354 z: index
1225 height: spreadItem.spreadItemHeight
1355 height: spreadItem.spreadItemHeight
1226− requestedWidth: decoratedWindow.oldRequestedWidth
1227− requestedHeight: decoratedWindow.oldRequestedHeight
1228 visible: spreadMaths.itemVisible
1356 visible: spreadMaths.itemVisible
1229 }
1357 }
1230 PropertyChanges { target: dragArea; enabled: true }
1358 PropertyChanges { target: dragArea; enabled: true }
@@ -1244,8 +1372,6 @@ FocusScope {
1244 y: stagedRightEdgeMaths.animatedY
1372 y: stagedRightEdgeMaths.animatedY
1245 z: stagedRightEdgeMaths.animatedZ
1373 z: stagedRightEdgeMaths.animatedZ
1246 height: stagedRightEdgeMaths.animatedHeight
1374 height: stagedRightEdgeMaths.animatedHeight
1247− requestedWidth: decoratedWindow.oldRequestedWidth
1248− requestedHeight: decoratedWindow.oldRequestedHeight
1249 visible: appDelegate.x < root.width
1375 visible: appDelegate.x < root.width
1250 }
1376 }
1251 PropertyChanges {
1377 PropertyChanges {
@@ -1275,8 +1401,6 @@ FocusScope {
1275 y: windowedRightEdgeMaths.animatedY
1401 y: windowedRightEdgeMaths.animatedY
1276 z: windowedRightEdgeMaths.animatedZ
1402 z: windowedRightEdgeMaths.animatedZ
1277 height: stagedRightEdgeMaths.animatedHeight
1403 height: stagedRightEdgeMaths.animatedHeight
1278− requestedWidth: decoratedWindow.oldRequestedWidth
1279− requestedHeight: decoratedWindow.oldRequestedHeight
1280 }
1404 }
1281 PropertyChanges {
1405 PropertyChanges {
1282 target: decoratedWindow
1406 target: decoratedWindow
@@ -1298,12 +1422,16 @@ FocusScope {
1298 PropertyChanges {
1422 PropertyChanges {
1299 target: appDelegate
1423 target: appDelegate
1300 x: stageMaths.itemX
1424 x: stageMaths.itemX
1301− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1425+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
1302− requestedWidth: appContainer.width
1303− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1304 visuallyMaximized: true
1426 visuallyMaximized: true
1305 visible: appDelegate.x < root.width
1427 visible: appDelegate.x < root.width
1306 }
1428 }
1429+ PropertyChanges {
1430+ target: appDelegate
1431+ requestedWidth: appContainer.width
1432+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
1433+ restoreEntryValues: false
1434+ }
1307 PropertyChanges {
1435 PropertyChanges {
1308 target: decoratedWindow
1436 target: decoratedWindow
1309 hasDecoration: false
1437 hasDecoration: false
@@ -1330,13 +1458,17 @@ FocusScope {
1330 PropertyChanges {
1458 PropertyChanges {
1331 target: appDelegate
1459 target: appDelegate
1332 x: stageMaths.itemX
1460 x: stageMaths.itemX
1333− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1461+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
1334 z: stageMaths.itemZ
1462 z: stageMaths.itemZ
1335− requestedWidth: stageMaths.itemWidth
1336− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1337 visuallyMaximized: true
1463 visuallyMaximized: true
1338 visible: appDelegate.x < root.width
1464 visible: appDelegate.x < root.width
1339 }
1465 }
1466+ PropertyChanges {
1467+ target: appDelegate
1468+ requestedWidth: stageMaths.itemWidth
1469+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
1470+ restoreEntryValues: false
1471+ }
1340 PropertyChanges {
1472 PropertyChanges {
1341 target: decoratedWindow
1473 target: decoratedWindow
1342 hasDecoration: false
1474 hasDecoration: false
@@ -1358,8 +1490,12 @@ FocusScope {
1358 requestedY: 0;
1490 requestedY: 0;
1359 visuallyMinimized: false;
1491 visuallyMinimized: false;
1360 visuallyMaximized: true
1492 visuallyMaximized: true
1493+ }
1494+ PropertyChanges {
1495+ target: appDelegate
1361− requestedWidth: appContainer.width - root.leftMargin;
1496+ requestedWidth: appContainer.width - root.leftMargin
1362− requestedHeight: appContainer.height;
1497+ requestedHeight: appContainer.height
1498+ restoreEntryValues: false
1363 }
1499 }
1364 PropertyChanges { target: touchControls; enabled: true }
1500 PropertyChanges { target: touchControls; enabled: true }
1365 },
1501 },
@@ -1369,8 +1505,12 @@ FocusScope {
1369 target: appDelegate;
1505 target: appDelegate;
1370 requestedX: 0
1506 requestedX: 0
1371 requestedY: 0
1507 requestedY: 0
1508+ }
1509+ PropertyChanges {
1510+ target: appDelegate
1372− requestedWidth: appContainer.width;
1511+ requestedWidth: appContainer.width
1373− requestedHeight: appContainer.height;
1512+ requestedHeight: appContainer.height
1513+ restoreEntryValues: false
1374 }
1514 }
1375 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1515 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1376 },
1516 },
@@ -1385,6 +1525,12 @@ FocusScope {
1385 PropertyChanges { target: touchControls; enabled: true }
1525 PropertyChanges { target: touchControls; enabled: true }
1386 PropertyChanges { target: resizeArea; enabled: true }
1526 PropertyChanges { target: resizeArea; enabled: true }
1387 PropertyChanges { target: decoratedWindow; shadowOpacity: .3}
1527 PropertyChanges { target: decoratedWindow; shadowOpacity: .3}
1528+ PropertyChanges {
1529+ target: appDelegate
1530+ requestedWidth: windowedWidth
1531+ requestedHeight: windowedHeight
1532+ restoreEntryValues: false
1533+ }
1388 },
1534 },
1389 State {
1535 State {
1390 name: "restored";
1536 name: "restored";
@@ -1402,9 +1548,9 @@ FocusScope {
1402 PropertyChanges {
1548 PropertyChanges {
1403 target: appDelegate
1549 target: appDelegate
1404 windowedX: root.leftMargin
1550 windowedX: root.leftMargin
1405− windowedY: PanelState.panelHeight
1551+ windowedY: panelState.panelHeight
1406 windowedWidth: (appContainer.width - root.leftMargin)/2
1552 windowedWidth: (appContainer.width - root.leftMargin)/2
1407− windowedHeight: appContainer.height - PanelState.panelHeight
1553+ windowedHeight: appContainer.height - panelState.panelHeight
1408 }
1554 }
1409 },
1555 },
1410 State {
1556 State {
@@ -1421,9 +1567,9 @@ FocusScope {
1421 PropertyChanges {
1567 PropertyChanges {
1422 target: appDelegate
1568 target: appDelegate
1423 windowedX: root.leftMargin
1569 windowedX: root.leftMargin
1424− windowedY: PanelState.panelHeight
1570+ windowedY: panelState.panelHeight
1425 windowedWidth: (appContainer.width - root.leftMargin)/2
1571 windowedWidth: (appContainer.width - root.leftMargin)/2
1426− windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1572+ windowedHeight: (appContainer.height - panelState.panelHeight)/2
1427 }
1573 }
1428 },
1574 },
1429 State {
1575 State {
@@ -1440,7 +1586,7 @@ FocusScope {
1440 PropertyChanges {
1586 PropertyChanges {
1441 target: appDelegate
1587 target: appDelegate
1442 windowedX: root.leftMargin
1588 windowedX: root.leftMargin
1443− windowedY: (appContainer.height + PanelState.panelHeight)/2
1589+ windowedY: (appContainer.height + panelState.panelHeight)/2
1444 windowedWidth: (appContainer.width - root.leftMargin)/2
1590 windowedWidth: (appContainer.width - root.leftMargin)/2
1445 windowedHeight: appContainer.height/2
1591 windowedHeight: appContainer.height/2
1446 }
1592 }
@@ -1462,8 +1608,8 @@ FocusScope {
1462 State {
1608 State {
1463 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1609 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1464 extend: "normal"
1610 extend: "normal"
1465− PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1611+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: panelState.panelHeight;
1466− windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1612+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - panelState.panelHeight }
1467 },
1613 },
1468 State {
1614 State {
1469 name: "minimized"; when: appDelegate.minimized
1615 name: "minimized"; when: appDelegate.minimized
@@ -1478,6 +1624,10 @@ FocusScope {
1478 }
1624 }
1479 }
1625 }
1480 ]
1626 ]
1627+ onStateChanged: {
1628+ print("item", index, "state changed", state)
1629+ }
1630+
1481 transitions: [
1631 transitions: [
1482 Transition {
1632 Transition {
1483 from: "staged,stagedWithSideStage"
1633 from: "staged,stagedWithSideStage"
@@ -1491,6 +1641,7 @@ FocusScope {
1491 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1641 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1492 },
1642 },
1493 Transition {
1643 Transition {
1644+ from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,staged,stagedWithSideStage,windowedRightEdge,stagedRightEdge";
1494 to: "spread"
1645 to: "spread"
1495 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1646 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1496 PropertyAction { target: appDelegate; properties: "z,visible" }
1647 PropertyAction { target: appDelegate; properties: "z,visible" }
@@ -1576,7 +1727,7 @@ FocusScope {
1576 ]
1727 ]
1577
1728
1578 Binding {
1729 Binding {
1579− target: PanelState
1730+ target: panelState
1580 property: "decorationsAlwaysVisible"
1731 property: "decorationsAlwaysVisible"
1581 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1732 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1582 }
1733 }
@@ -1597,6 +1748,7 @@ FocusScope {
1597 borderThickness: units.gu(2)
1748 borderThickness: units.gu(2)
1598 enabled: false
1749 enabled: false
1599 visible: enabled
1750 visible: enabled
1751+ readyToAssesBounds: !appDelegate._constructing
1600
1752
1601 onPressed: {
1753 onPressed: {
1602 appDelegate.activate();
1754 appDelegate.activate();
@@ -1620,17 +1772,12 @@ FocusScope {
1620 height: implicitHeight
1772 height: implicitHeight
1621 highlightSize: windowInfoItem.iconMargin / 2
1773 highlightSize: windowInfoItem.iconMargin / 2
1622 boundsItem: boundariesForWindowPlacement
1774 boundsItem: boundariesForWindowPlacement
1775+ panelState: root.panelState
1623 altDragEnabled: root.mode == "windowed"
1776 altDragEnabled: root.mode == "windowed"
1624
1777
1625 requestedWidth: appDelegate.requestedWidth
1778 requestedWidth: appDelegate.requestedWidth
1626 requestedHeight: appDelegate.requestedHeight
1779 requestedHeight: appDelegate.requestedHeight
1627
1780
1628− property int oldRequestedWidth: -1
1629− property int oldRequestedHeight: -1
1630−
1631− onRequestedWidthChanged: oldRequestedWidth = requestedWidth
1632− onRequestedHeightChanged: oldRequestedHeight = requestedHeight
1633−
1634 onCloseClicked: { appDelegate.close(); }
1781 onCloseClicked: { appDelegate.close(); }
1635 onMaximizeClicked: {
1782 onMaximizeClicked: {
1636 if (appDelegate.canBeMaximized) {
1783 if (appDelegate.canBeMaximized) {
@@ -1711,6 +1858,8 @@ FocusScope {
1711 anchors.fill: decoratedWindow
1858 anchors.fill: decoratedWindow
1712 enabled: false
1859 enabled: false
1713 closeable: !appDelegate.isDash
1860 closeable: !appDelegate.isDash
1861+ stage: root
1862+ dragDelegate: fakeDragItem
1714
1863
1715 onClicked: {
1864 onClicked: {
1716 spreadItem.highlightedIndex = index;
1865 spreadItem.highlightedIndex = index;
@@ -1753,12 +1902,15 @@ FocusScope {
1753 objectName: "closeMouseArea"
1902 objectName: "closeMouseArea"
1754 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1903 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1755 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1904 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1756− visible: !appDelegate.isDash && dragArea.distance == 0
1905+ readonly property bool shown: !appDelegate.isDash && dragArea.distance == 0
1757 && index == spreadItem.highlightedIndex
1906 && index == spreadItem.highlightedIndex
1758 && mousePos.y < (decoratedWindow.height / 3)
1907 && mousePos.y < (decoratedWindow.height / 3)
1759 && mousePos.y > -units.gu(4)
1908 && mousePos.y > -units.gu(4)
1760 && mousePos.x > -units.gu(4)
1909 && mousePos.x > -units.gu(4)
1761 && mousePos.x < (decoratedWindow.width * 2 / 3)
1910 && mousePos.x < (decoratedWindow.width * 2 / 3)
1911+ opacity: shown ? 1 : 0
1912+ visible: opacity > 0
1913+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
1762 height: units.gu(6)
1914 height: units.gu(6)
1763 width: height
1915 width: height
1764
1916
@@ -1820,83 +1972,19 @@ FocusScope {
1820 leftMargin: root.leftMargin
1972 leftMargin: root.leftMargin
1821 appContainerWidth: appContainer.width
1973 appContainerWidth: appContainer.width
1822 appContainerHeight: appContainer.height
1974 appContainerHeight: appContainer.height
1975+ panelState: root.panelState
1823 }
1976 }
1824
1977
1825− MouseArea {
1978+ WorkspaceSwitcher {
1826− id: hoverMouseArea
1979+ id: workspaceSwitcher
1827− objectName: "hoverMouseArea"
1980+ anchors.centerIn: parent
1828− anchors.fill: appContainer
1981+ height: units.gu(20)
1829− propagateComposedEvents: true
1982+ background: root.background
1830− hoverEnabled: true
1983+ onActiveChanged: {
1831− enabled: false
1984+ if (!active) {
1832− visible: enabled
1985+ appContainer.focus = true;
1833−
1834− property int scrollAreaWidth: width / 3
1835− property bool progressiveScrollingEnabled: false
1836−
1837− onMouseXChanged: {
1838− mouse.accepted = false
1839−
1840− if (hoverMouseArea.pressed) {
1841− return;
1842− }
1843−
1844− // Find the hovered item and mark it active
1845− for (var i = appRepeater.count - 1; i >= 0; i--) {
1846− var appDelegate = appRepeater.itemAt(i);
1847− var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1848− var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1849− if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1850− spreadItem.highlightedIndex = i;
1851− break;
1852− }
1853− }
1854−
1855− if (floatingFlickable.contentWidth > floatingFlickable.width) {
1856− var margins = floatingFlickable.width * 0.05;
1857−
1858− if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1859− progressiveScrollingEnabled = true
1860− }
1861−
1862− // do we need to scroll?
1863− if (mouseX < scrollAreaWidth + margins) {
1864− var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1865− var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1866− floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1867− }
1868− if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1869− var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1870− var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1871− floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1872− }
1873− }
1874− }
1875−
1876− onPressed: mouse.accepted = false
1877− }
1878−
1879− FloatingFlickable {
1880− id: floatingFlickable
1881− objectName: "spreadFlickable"
1882− anchors.fill: appContainer
1883− enabled: false
1884− contentWidth: spreadItem.spreadTotalWidth
1885−
1886− function snap(toIndex) {
1887− var delegate = appRepeater.itemAt(toIndex)
1888− var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1889− if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1890− var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1891− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1892− snapAnimation.start();
1893− } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1894− var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1895− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1896− snapAnimation.start();
1897 }
1986 }
1898 }
1987 }
1899− UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1900 }
1988 }
1901
1989
1902 PropertyAnimation {
1990 PropertyAnimation {
qml/Stage/SurfaceContainer.qml+2
@@ -66,6 +66,8 @@ FocusScope {
66 fillMode: MirSurfaceItem.PadOrCrop
66 fillMode: MirSurfaceItem.PadOrCrop
67 consumesInput: true
67 consumesInput: true
68
68
69+ onSurfaceChanged: print("surface changed", surface, implicitWidth, implicitHeight)
70+
69 surfaceWidth: root.requestedWidth
71 surfaceWidth: root.requestedWidth
70 surfaceHeight: root.requestedHeight
72 surfaceHeight: root.requestedHeight
71
73
qml/Stage/WindowDecoration.qml+2
@@ -34,6 +34,7 @@ MouseArea {
34 property var menu: undefined
34 property var menu: undefined
35 property bool enableMenus: true
35 property bool enableMenus: true
36 property bool windowMoving: false
36 property bool windowMoving: false
37+ property PanelState panelState
37
38
38 readonly property real buttonsWidth: buttons.width + row.spacing
39 readonly property real buttonsWidth: buttons.width + row.spacing
39
40
@@ -146,6 +147,7 @@ MouseArea {
146 enableKeyFilter: valid && root.active && root.enableMenus
147 enableKeyFilter: valid && root.active && root.enableMenus
147 unityMenuModel: root.menu
148 unityMenuModel: root.menu
148 windowMoving: root.windowMoving
149 windowMoving: root.windowMoving
150+ panelState: root.panelState
149
151
150 onPressed: root.onPressed(mouse)
152 onPressed: root.onPressed(mouse)
151 onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
153 onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
qml/Stage/WindowResizeArea.qml+27−4
@@ -36,35 +36,58 @@ MouseArea {
36 property int minWidth: 0
36 property int minWidth: 0
37 property int minHeight: 0
37 property int minHeight: 0
38
38
39+ property bool readyToAssesBounds: false
40+ onReadyToAssesBoundsChanged: d.reassesBounds()
41+
39 QtObject {
42 QtObject {
40 id: d
43 id: d
41
44
42 readonly property int maxSafeInt: 2147483647
45 readonly property int maxSafeInt: 2147483647
43 readonly property int maxSizeIncrement: units.gu(40)
46 readonly property int maxSizeIncrement: units.gu(40)
44
47
48+ function reassesBounds() {
49+ if (!readyToAssesBounds) return;
50+
51+ if (target.windowedWidth < minimumWidth) {
52+ target.windowedWidth = minimumWidth;
53+ }
54+ if (target.windowedHeight < minimumHeight) {
55+ target.windowedHeight = minimumHeight;
56+ }
57+ if (target.windowedHeight < minimumHeight) {
58+ target.windowedHeight = minimumHeight;
59+ }
60+ if (target.windowedWidth > maximumWidth) {
61+ target.windowedWidth = maximumWidth;
62+ }
63+ if (target.windowedHeight > maximumHeight) {
64+ target.windowedHeight = maximumHeight;
65+ }
66+ }
67+
45 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
68 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
46 onMinimumWidthChanged: {
69 onMinimumWidthChanged: {
47− if (target.windowedWidth < minimumWidth) {
70+ if (readyToAssesBounds && target.windowedWidth < minimumWidth) {
48 target.windowedWidth = minimumWidth;
71 target.windowedWidth = minimumWidth;
49 }
72 }
50 }
73 }
51 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
74 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
52 onMinimumHeightChanged: {
75 onMinimumHeightChanged: {
53− if (target.windowedHeight < minimumHeight) {
76+ if (readyToAssesBounds && target.windowedHeight < minimumHeight) {
54 target.windowedHeight = minimumHeight;
77 target.windowedHeight = minimumHeight;
55 }
78 }
56 }
79 }
57 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
80 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
58 ? root.target.maximumWidth : maxSafeInt
81 ? root.target.maximumWidth : maxSafeInt
59 onMaximumWidthChanged: {
82 onMaximumWidthChanged: {
60− if (target.windowedWidth > maximumWidth) {
83+ if (readyToAssesBounds && target.windowedWidth > maximumWidth) {
61 target.windowedWidth = maximumWidth;
84 target.windowedWidth = maximumWidth;
62 }
85 }
63 }
86 }
64 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
87 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
65 ? root.target.maximumHeight : maxSafeInt
88 ? root.target.maximumHeight : maxSafeInt
66 onMaximumHeightChanged: {
89 onMaximumHeightChanged: {
67− if (target.windowedHeight > maximumHeight) {
90+ if (readyToAssesBounds && target.windowedHeight > maximumHeight) {
68 target.windowedHeight = maximumHeight;
91 target.windowedHeight = maximumHeight;
69 }
92 }
70 }
93 }
qml/Stage/WindowStateSaver.qml+4−1
@@ -43,7 +43,10 @@ QtObject {
43 (target.fullscreen ? 0 : root.leftMargin)); });
43 (target.fullscreen ? 0 : root.leftMargin)); });
44 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
44 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
45
45
46− target.updateNormalGeometry();
46+ target.normalWidth = target.windowedWidth;
47+ target.normalHeight = target.windowedHeight;
48+ target.normalX = target.windowedX;
49+ target.normalY = target.windowedY;
47
50
48 // initialize the x/y to restore to
51 // initialize the x/y to restore to
49 target.restoredX = target.normalX;
52 target.restoredX = target.normalX;
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/WorkspaceSwitcher.qmlnew file+179
@@ -0,0 +1,179 @@
1+/*
2+ * Copyright (C) 2014-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 "Spread"
20+import WindowManager 1.0
21+import Unity.Application 0.1
22+
23+UbuntuShape {
24+ id: root
25+
26+ backgroundColor: "#F2111111"
27+ width: screensRow.childrenRect.width + units.gu(4)
28+ opacity: d.shown ? 1 : 0
29+ visible: opacity > 0
30+ Behavior on opacity { UbuntuNumberAnimation {} }
31+
32+ property var screensProxy: Screens.createProxy();
33+ property string background
34+
35+ readonly property alias active: d.active
36+
37+ function showLeft() {
38+ show();
39+ d.decreaseHighlight();
40+ }
41+ function showRight() {
42+ show();
43+ d.increaseHighlight();
44+ }
45+
46+ function show() {
47+ hideTimer.stop();
48+ d.altPressed = true;
49+ d.ctrlPressed = true;
50+ d.active = true;
51+ d.shown = true;
52+ focus = true;
53+
54+ d.highlightedScreenIndex = screensProxy.activeScreen;
55+ var activeScreen = screensProxy.get(screensProxy.activeScreen);
56+ d.highlightedWorkspaceIndex = activeScreen.workspaces.indexOf(activeScreen.currentWorkspace)
57+ }
58+
59+ QtObject {
60+ id: d
61+
62+ property bool active: false
63+ property bool shown: false
64+ property bool altPressed: false
65+ property bool ctrlPressed: false
66+
67+ property int highlightedScreenIndex: -1
68+ property int highlightedWorkspaceIndex: -1
69+
70+ function increaseHighlight() {
71+ var screen = screensProxy.get(highlightedScreenIndex);
72+ highlightedWorkspaceIndex++
73+ if (highlightedWorkspaceIndex >= screen.workspaces.count) {
74+ highlightedScreenIndex = (highlightedScreenIndex + 1) % screensProxy.count;
75+ highlightedWorkspaceIndex = 0;
76+ }
77+ }
78+ function decreaseHighlight() {
79+ highlightedWorkspaceIndex--;
80+ if (highlightedWorkspaceIndex < 0) {
81+ highlightedScreenIndex--;
82+ if (highlightedScreenIndex < 0) {
83+ highlightedScreenIndex = screensProxy.count - 1
84+ }
85+ var screen = screensProxy.get(highlightedScreenIndex);
86+ highlightedWorkspaceIndex = screen.workspaces.count -1;
87+ }
88+ }
89+ }
90+
91+ Timer {
92+ id: hideTimer
93+ interval: 1000
94+ onTriggered: d.shown = false;
95+ }
96+
97+ Keys.onPressed: {
98+ hideTimer.restart();
99+ switch (event.key) {
100+ case Qt.Key_Left:
101+ d.decreaseHighlight();
102+ break;
103+ case Qt.Key_Right:
104+ d.increaseHighlight();
105+ break;
106+ }
107+ }
108+ Keys.onReleased: {
109+ switch (event.key) {
110+ case Qt.Key_Alt:
111+ d.altPressed = false;
112+ break;
113+ case Qt.Key_Control:
114+ d.ctrlPressed = false;
115+ break;
116+ }
117+ if (!d.altPressed && !d.ctrlPressed) {
118+ print("starting hidetimer")
119+ d.active = false;
120+ hideTimer.start();
121+ focus = false;
122+ screensProxy.get(d.highlightedScreenIndex).workspaces.get(d.highlightedWorkspaceIndex).activate();
123+ }
124+ }
125+
126+ Row {
127+ id: screensRow
128+ anchors {
129+ top: parent.top; topMargin: units.gu(2)
130+ left: parent.left; leftMargin: units.gu(2)
131+ }
132+
133+ Repeater {
134+ model: screensProxy
135+
136+ delegate: Item {
137+ height: root.height - units.gu(4)
138+ width: workspaces.width
139+
140+ UbuntuShape {
141+ id: header
142+ anchors { left: parent.left; top: parent.top; right: parent.right }
143+ height: units.gu(4)
144+ backgroundColor: "white"
145+
146+ Label {
147+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
148+ text: model.screen.name
149+ color: UbuntuColors.ash
150+ }
151+ }
152+
153+ Workspaces {
154+ id: workspaces
155+ height: parent.height - header.height - units.gu(2)
156+ width: {
157+ var width = 0;
158+ if (screensProxy.count == 1) {
159+ width = Math.min(implicitWidth, root.width - units.gu(8));
160+ } else {
161+ width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
162+ }
163+ return Math.max(workspaces.minimumWidth, width);
164+ }
165+
166+ Behavior on width { UbuntuNumberAnimation {} }
167+ anchors.bottom: parent.bottom
168+ anchors.bottomMargin: units.gu(1)
169+ anchors.horizontalCenter: parent.horizontalCenter
170+ screen: model.screen
171+ background: root.background
172+ selectedIndex: d.highlightedScreenIndex == index ? d.highlightedWorkspaceIndex : -1
173+
174+ workspaceModel: model.screen.workspaces
175+ }
176+ }
177+ }
178+ }
179+}
qml/Stage/graphics/multi-monitor_drop-here.pngnew file

Binary file not shown.

qml/Stage/graphics/multi-monitor_leave.pngnew file

Binary file not shown.

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