on c83033a help sign in
57 files changed+1049−533
since splitsnapshots
unifiedsplit
plugins/Cursor/CMakeLists.txt+1 plugins/Cursor/InputDispatcherFilter.cpp+146 plugins/Cursor/InputDispatcherFilter.h+57 plugins/Cursor/MousePointer.cpp+40−73 plugins/Cursor/MousePointer.h+4 plugins/UInput/plugin.cpp+7−1 qml/CMakeLists.txt+1−1 qml/Components/PanelState/PanelState.qml−1 qml/Components/PanelState/qmldir+1−1 qml/Components/VirtualTouchPad.qml+13−19 qml/DisabledScreenNotice.qml+2−1 qml/Greeter/Greeter.qml+21−3 qml/Greeter/SecondaryGreeter.qml+75 qml/OrientedShell.qml+7−9 qml/Panel/Panel.qml+14−14 qml/Shell.qml+25−21 qml/ShellApplication.qml+51 src/SecondaryWindow.h → qml/ShellNotifier.qml+12−12 qml/ShellScreen.qml+65 qml/Stage/DecoratedWindow.qml+4−1 qml/Stage/FakeMaximizeDelegate.qml+12−11 qml/Stage/MoveHandler.qml+8−7 qml/Stage/Stage.qml+29−25 qml/Stage/WindowControlsOverlay.qml+2 qml/qmldir+1 src/ApplicationArguments.cpp+11−2 src/ApplicationArguments.h+16−15 src/CMakeLists.txt−2 src/SecondaryWindow.cpp−40 src/ShellApplication.cpp+29−106 src/ShellApplication.h+2−14 src/ShellView.cpp−63 src/ShellView.h−34 src/UnityCommandLineParser.h+1−1 tests/mocks/Cursor/Cursor.qml+50−2 tests/mocks/GSettings.1.0/plugin.cpp+12 tests/mocks/GSettings.1.0/plugin.h+1 tests/mocks/UInput/plugin.cpp+6−1 tests/mocks/Unity/Application/SurfaceManager.cpp+1−1 tests/mocks/Unity/InputInfo/InputWindow.qml+68 tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2 tests/mocks/Unity/InputInfo/mockcontroller.h+5−1 tests/mocks/Unity/InputInfo/plugin.cpp+1−2 tests/mocks/Unity/InputInfo/qmldir+2 tests/mocks/Unity/Screens/CMakeLists.txt+2−1 tests/mocks/Unity/Screens/plugin.cpp+12−1 tests/mocks/Unity/Screens/screens.cpp+39−2 tests/mocks/Unity/Screens/screens.h+22−1 tests/mocks/Unity/Screens/screenwindow.cpp+32 tests/mocks/Unity/Screens/screenwindow.h+36 tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2 tests/qmltests/Panel/tst_Panel.qml+14−12 tests/qmltests/Stage/tst_DecoratedWindow.qml+5−1 tests/qmltests/Stage/tst_DesktopStage.qml+8−3 tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8 tests/qmltests/tst_OrientedShell.qml+24−5 tests/qmltests/tst_Shell.qml+25−11
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
1515 CursorImageInfo.cpp
1616 CursorImageProvider.cpp
1717 MousePointer.cpp
18+ InputDispatcherFilter.cpp
1819 # We need to run moc on this header
1920 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
2021 )
plugins/Cursor/InputDispatcherFilter.cppnew file+146
@@ -0,0 +1,146 @@
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 <QScreen>
23+#include <qpa/qplatformnativeinterface.h>
24+#include <qpa/qplatformscreen.h>
25+
26+InputDispatcherFilter *InputDispatcherFilter::instance()
27+{
28+ static InputDispatcherFilter filter;
29+ return &filter;
30+}
31+
32+InputDispatcherFilter::InputDispatcherFilter(QObject *parent)
33+ : QObject(parent)
34+{
35+ QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface();
36+ m_inputDispatcher = static_cast<QObject*>(ni->nativeResourceForIntegration("InputDispatcher"));
37+ if (m_inputDispatcher) {
38+ m_inputDispatcher->installEventFilter(this);
39+ }
40+}
41+
42+void InputDispatcherFilter::registerPointer(MousePointer *pointer)
43+{
44+ // allow first registered pointer to be visible.
45+ pointer->setVisible(m_pointers.count() == 0);
46+
47+ m_pointers.insert(pointer);
48+ connect(pointer, &MousePointer::mouseMoved, this, [this, pointer]() {
49+ Q_FOREACH(auto p, m_pointers) {
50+ p->setVisible(p == pointer);
51+ }
52+ });
53+}
54+
55+void InputDispatcherFilter::unregisterPointer(MousePointer *pointer)
56+{
57+ m_pointers.remove(pointer);
58+ disconnect(pointer, &MousePointer::mouseMoved, this, 0);
59+}
60+
61+void InputDispatcherFilter::setPosition(const QPointF &pos)
62+{
63+ mousePosition = pos;
64+}
65+
66+bool InputDispatcherFilter::eventFilter(QObject *o, QEvent *e)
67+{
68+ if (o != m_inputDispatcher) return false;
69+
70+ switch (e->type()) {
71+ case QEvent::MouseMove:
72+ case QEvent::MouseButtonPress:
73+ case QEvent::MouseButtonRelease:
74+ {
75+ // FIXME - removed all input filtering for now - this was for extended display
76+ return false;
77+
78+// QMouseEvent* me = static_cast<QMouseEvent*>(e);
79+
80+// // Local position gives relative change of mouse pointer.
81+// QPointF localPos = me->localPos();
82+// QPointF globalPos = me->screenPos();
83+
84+// // Adjust the position
85+// QPointF oldPos(mousePosition.isNull() ? globalPos : mousePosition);
86+// QPointF newPos = adjustedPositionForMovement(oldPos, localPos);
87+
88+// QScreen* currentScreen = screenAt(newPos);
89+// if (currentScreen) {
90+// QRect screenRect = currentScreen->geometry();
91+// qreal unadjustedX = (oldPos + localPos).x();
92+// if (unadjustedX < screenRect.left()) {
93+// Q_EMIT pushedLeftBoundary(currentScreen, qAbs(unadjustedX - screenRect.left()), me->buttons());
94+// } else if (unadjustedX > screenRect.right()) {
95+// Q_EMIT pushedRightBoundary(currentScreen, qAbs(unadjustedX - screenRect.right()), me->buttons());
96+// }
97+// }
98+
99+// // Send the event
100+// QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
101+// eCopy.setTimestamp(me->timestamp());
102+// o->event(&eCopy);
103+// return true;
104+ }
105+ default:
106+ break;
107+ }
108+ return false;
109+}
110+
111+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
112+{
113+ QPointF adjusted = pt + movement;
114+
115+ auto screen = screenAt(adjusted); // first check if our move was to a valid screen.
116+ if (screen) {
117+ return adjusted;
118+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
119+ QRectF screenBounds = screen->geometry();
120+ // bound the new position to the old screen geometry
121+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
122+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
123+ } else {
124+ auto screens = QGuiApplication::screens();
125+
126+ // center of first screen with a pointer.
127+ Q_FOREACH(QScreen* screen, screens) {
128+ Q_FOREACH(MousePointer* pointer, m_pointers) {
129+ if (pointer->screen() == screen) {
130+ return screen->geometry().center();
131+ }
132+ }
133+ }
134+ }
135+ return adjusted;
136+}
137+
138+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
139+{
140+ Q_FOREACH(MousePointer* pointer, m_pointers) {
141+ QScreen* screen = pointer->screen();
142+ if (screen && screen->geometry().contains(pt.toPoint()))
143+ return screen;
144+ }
145+ return nullptr;
146+}
plugins/Cursor/InputDispatcherFilter.hnew file+57
@@ -0,0 +1,57 @@
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+
35+ void unregisterPointer(MousePointer* pointer);
36+
37+ void setPosition(const QPointF &pos);
38+
39+Q_SIGNALS:
40+ void pushedLeftBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
41+ void pushedRightBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
42+
43+protected:
44+ InputDispatcherFilter(QObject* parent = nullptr);
45+
46+ bool eventFilter(QObject *o, QEvent *e) override;
47+
48+ QPointF adjustedPositionForMovement(const QPointF& pt, const QPointF& movement) const;
49+ QScreen* screenAt(const QPointF& pt) const;
50+
51+private:
52+ QObject* m_inputDispatcher;
53+ QSet<MousePointer*> m_pointers;
54+ QPointF mousePosition;
55+};
56+
57+#endif // INPUTDISPATCHERFILTER_H
plugins/Cursor/MousePointer.cpp+40−73
@@ -16,84 +16,57 @@
1616
1717#include "MousePointer.h"
1818#include "CursorImageProvider.h"
19−
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
19+#include "InputDispatcherFilter.h"
2220
2321#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
2622
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
2825
2926MousePointer::MousePointer(QQuickItem *parent)
3027 : MirMousePointerInterface(parent)
3128 , m_cursorName(QStringLiteral("left_ptr"))
3229 , m_themeName(QStringLiteral("default"))
3330{
34−}
31+ InputDispatcherFilter::instance()->registerPointer(this);
3532
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
37− Qt::KeyboardModifiers modifiers)
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
33+ auto mouseMovedFunc = [this]() {
34+ if (!isEnabled() || !window()) return;
35+ QPointF globalPosition = mapToItem(nullptr, QPointF(0, 0));
36+ InputDispatcherFilter::instance()->setPosition(globalPosition);
4437 Q_EMIT mouseMoved();
45− }
46−
47− m_accumulatedMovement += movement;
48− // don't apply the fractional part
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
50− m_accumulatedMovement -= appliedMovement;
51−
52− qreal newX = x() + appliedMovement.x();
53− qreal newY = y() + appliedMovement.y();
54− const qreal sceneWidth = parentItem()->width();
55− const qreal sceneHeight = parentItem()->height();
56−
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
60− m_pushing = true;
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
64− m_pushing = true;
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
68− m_pushing = true;
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
72− m_pushing = true;
73− } else if (newX < 0) { // left edge
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
75− m_pushing = true;
76− } else if (newX >= sceneWidth) { // right edge
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
78− m_pushing = true;
79− } else if (newY < m_topBoundaryOffset) { // top edge
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
81− m_pushing = true;
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
83− if (m_pushing) {
84− Q_EMIT pushStopped();
85− m_pushing = false;
38+ };
39+ connect(this, &QQuickItem::xChanged, this, mouseMovedFunc);
40+ connect(this, &QQuickItem::yChanged, this, mouseMovedFunc);
41+
42+ connect(this, &QQuickItem::enabledChanged, this, [this]() {
43+ if (!isEnabled()) setVisible(false);
44+ });
45+
46+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
47+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
48+ if (window() && window()->screen() == screen) {
49+ Q_EMIT pushedLeftBoundary(amount, buttons);
8650 }
87− }
51+ });
8852
89− applyItemConfinement(newX, newY);
53+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
54+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
55+ if (window() && window()->screen() == screen) {
56+ Q_EMIT pushedRightBoundary(amount, buttons);
57+ }
58+ });
59+}
9060
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
61+MousePointer::~MousePointer()
62+{
63+ registerScreen(nullptr);
64+ InputDispatcherFilter::instance()->unregisterPointer(this);
65+}
9366
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
96− buttons, modifiers);
67+void MousePointer::handleMouseEvent(ulong /*timestamp*/, QPointF /*movement*/, Qt::MouseButtons /*buttons*/,
68+ Qt::KeyboardModifiers /*modifiers*/)
69+{
9770}
9871
9972void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,15 +94,8 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
12194 }
12295}
12396
124−void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
97+void MousePointer::handleWheelEvent(ulong /*timestamp*/, QPoint /*angleDelta*/, Qt::KeyboardModifiers /*modifiers*/)
12598{
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);
13399}
134100
135101int MousePointer::topBoundaryOffset() const
@@ -182,7 +148,7 @@ void MousePointer::registerScreen(QScreen *screen)
182148 if (m_registeredScreen) {
183149 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184150 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
151+ previousCursor->unregisterMousePointer(this);
186152 } else {
187153 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188154 }
@@ -193,7 +159,7 @@ void MousePointer::registerScreen(QScreen *screen)
193159 if (m_registeredScreen) {
194160 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195161 if (cursor) {
196− cursor->setMousePointer(this);
162+ cursor->registerMousePointer(this);
197163 } else {
198164 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199165 }
@@ -233,3 +199,4 @@ void MousePointer::setConfiningItem(QQuickItem *item)
233199 Q_EMIT confiningItemChanged();
234200 }
235201}
202+
plugins/Cursor/MousePointer.h+4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
3131 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
3232public:
3333 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
3435
3536 void setCursorName(const QString &qtCursorName) override;
3637 QString cursorName() const override { return m_cursorName; }
@@ -46,6 +47,8 @@ public:
4647 int topBoundaryOffset() const;
4748 void setTopBoundaryOffset(int topBoundaryOffset);
4849
50+ QScreen* screen() const { return m_registeredScreen; }
51+
4952public Q_SLOTS:
5053 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
5154 Qt::KeyboardModifiers modifiers) override;
@@ -90,3 +93,4 @@ private:
9093};
9194
9295#endif // MOUSEPOINTER_H
96+
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
1919
2020#include <QtQml/qqml.h>
2121
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
2228void UInputPlugin::registerTypes(const char *uri)
2329{
2430 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
2632}
qml/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−file(GLOB QML_JS_FILES *.qml *.js)
1+file(GLOB QML_JS_FILES *.qml *.js qmldir)
22
33install(FILES ${QML_JS_FILES}
44 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−pragma Singleton
1817import QtQuick 2.4
1918
2019QtObject {
qml/Components/PanelState/qmldir+1−1
@@ -1 +1 @@
1−singleton PanelState 1.0 PanelState.qml
1+PanelState 1.0 PanelState.qml
qml/Components/VirtualTouchPad.qml+13−19
@@ -24,16 +24,14 @@ import "../Components"
2424
2525Item {
2626 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
3127
3228 Component.onCompleted: {
29+ UInput.createMouse();
3330 if (!settings.touchpadTutorialHasRun) {
3431 root.runTutorial()
3532 }
3633 }
34+ Component.onDestruction: UInput.removeMouse()
3735
3836 function runTutorial() {
3937 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
8583 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
8684 && clickTimer.running) {
8785 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
8987 isDoubleClick = true;
9088 }
9189 isClick = true;
@@ -104,7 +102,7 @@ Item {
104102
105103 onReleased: {
106104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108106 isDoubleClick = false;
109107 }
110108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120118 interval: 200
121119 property int button: UInput.ButtonLeft
122120 onTriggered: {
123− uinput.pressMouse(button);
124− uinput.releaseMouse(button);
121+ UInput.pressMouse(button);
122+ UInput.releaseMouse(button);
125123 }
126124 function scheduleClick(button) {
127125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138136 isDrag = true;
139137 }
140138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142140 }
143141
144142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166164 dh /= 2;
167165 dv /= 2;
168166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170168 }
171169
172170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189187 objectName: "leftButton"
190188 Layout.fillWidth: true
191189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194192 property bool highlight: false
195193 UbuntuShape {
196194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204202 objectName: "rightButton"
205203 Layout.fillWidth: true
206204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209207 property bool highlight: false
210208 UbuntuShape {
211209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239237 }
240238 }
241239
242− Screens {
243− id: screens
244− }
245−
246240 InputMethod {
247241 id: inputMethod
248242 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
249− enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
243+ enabled: Screens.count > 1 && settings.oskEnabled && !tutorial.running
250244 objectName: "inputMethod"
251245 anchors.fill: parent
252246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
2828 property var screen: Screen
2929 property var orientationLock: OrientationLock
3030
31+ property alias deviceConfiguration: _deviceConfiguration
3132 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
3334 name: applicationArguments.deviceName
3435 }
3536
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
2525import Unity.Session 0.1
2626
2727import "." 0.1
28+import ".." 0.1
2829import "../Components"
2930
3031Showable {
@@ -234,7 +235,7 @@ Showable {
234235 if (forcedUnlock && shown) {
235236 hideView();
236237 if (hideNow) {
237− root.hideNow(); // skip hide animation
238+ ShellNotifier.greeter.hide(true); // skip hide animation
238239 }
239240 }
240241 }
@@ -397,7 +398,7 @@ Showable {
397398 onEmergencyCall: root.emergencyCall()
398399 onRequiredChanged: {
399400 if (!loader.item.required) {
400− root.hide();
401+ ShellNotifier.greeter.hide(false);
401402 }
402403 }
403404 }
@@ -529,12 +530,29 @@ Showable {
529530 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
530531 }
531532
533+ Connections {
534+ target: ShellNotifier.greeter
535+ onHide: {
536+ if (now) {
537+ root.hideNow(); // skip hide animation
538+ } else {
539+ root.hide();
540+ }
541+ }
542+ }
543+
544+ Binding {
545+ target: ShellNotifier.greeter
546+ property: "shown"
547+ value: root.shown
548+ }
549+
532550 Connections {
533551 target: DBusUnitySessionService
534552 onLockRequested: root.forceShow()
535553 onUnlocked: {
536554 root.forcedUnlock = true;
537− root.hideNow();
555+ ShellNotifier.greeter.hide(true);
538556 }
539557 }
540558
qml/Greeter/SecondaryGreeter.qmlnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+import "../Components"
21+import ".." 0.1
22+
23+Showable {
24+ id: root
25+
26+ readonly property bool active: required || hasLockedApp
27+
28+ readonly property bool hasLockedApp: lockedApp !== ""
29+ readonly property bool locked: false
30+ readonly property bool waiting: false
31+ readonly property bool fullyShown: shown
32+
33+ property string lockedApp: ""
34+
35+ function forceShow() { show(); }
36+ property var notifyAppFocusRequested: (function(appId) { return; })
37+ property var notifyUserRequestedApp: (function(appId) { return; })
38+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
39+
40+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
41+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
42+
43+ shown: ShellNotifier.greeter.shown
44+ Component.onCompleted: opacity = shown ? 1 : 0
45+ visible: opacity != 0
46+
47+ Rectangle {
48+ anchors.fill: parent
49+ color: UbuntuColors.purple
50+ }
51+
52+ MouseArea {
53+ anchors.fill: parent
54+ acceptedButtons: Qt.AllButtons
55+ onWheel: wheel.accepted = true
56+ }
57+
58+ Connections {
59+ target: ShellNotifier.greeter
60+ onHide: {
61+ if (now) {
62+ root.hideNow(); // skip hide animation
63+ } else {
64+ root.hide();
65+ }
66+ }
67+ onShownChanged: {
68+ if (ShellNotifier.greeter.shown) {
69+ root.show();
70+ } else {
71+ root.hide();
72+ }
73+ }
74+ }
75+}
qml/OrientedShell.qml+7−9
@@ -32,15 +32,17 @@ Item {
3232 implicitWidth: units.gu(40)
3333 implicitHeight: units.gu(71)
3434
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+ property alias surfaceManager: shell.surfaceManager
38+
3539 onWidthChanged: calculateUsageMode();
3640
3741 DeviceConfiguration {
38− id: deviceConfiguration
42+ id: _deviceConfiguration
3943 name: applicationArguments.deviceName
4044 }
4145
42− property alias orientations: d.orientations
43−
4446 Item {
4547 id: d
4648
@@ -157,10 +159,6 @@ Item {
157159 return false;
158160 }
159161
160− Screens {
161− id: screens
162− }
163−
164162 property int orientation
165163 onPhysicalOrientationChanged: {
166164 if (!orientationLocked) {
@@ -258,7 +256,7 @@ Item {
258256
259257 Shell {
260258 id: shell
261− objectName: "shell"
259+ objectName: "shell-"+screenWindow.objectName
262260 width: root.width
263261 height: root.height
264262 orientation: root.angleToOrientation(orientationAngle)
@@ -271,7 +269,7 @@ Item {
271269 // have multiple keyboards around. For now we only enable one keyboard at a time
272270 // thus hiding it here if there is a physical one around or if we have a second
273271 // screen (the virtual touchpad & osk on the phone) attached.
274− oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
272+ oskEnabled: (keyboardsModel.count === 0 && Screens.count === 1) ||
275273 forceOSKEnabled
276274
277275 usageScenario: {
qml/Panel/Panel.qml+14−14
@@ -47,8 +47,8 @@ Item {
4747 property bool fullscreenMode: false
4848 property real panelAreaShowProgress: 1.0
4949 property bool greeterShown: false
50−
5150 property string mode: "staged"
51+ property PanelState panelState
5252
5353 MouseArea {
5454 id: backMouseEater
@@ -65,14 +65,14 @@ Item {
6565 }
6666
6767 Binding {
68− target: PanelState
68+ target: panelState
6969 property: "panelHeight"
7070 value: minimizedPanelHeight
7171 }
7272
7373 RegisteredApplicationMenuModel {
7474 id: registeredMenuModel
75− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
7676 }
7777
7878 QtObject {
@@ -83,11 +83,11 @@ Item {
8383 !indicators.shown &&
8484 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
8585
86− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
87− PanelState.decorationsAlwaysVisible
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
87+ panelState.decorationsAlwaysVisible
8888
8989 property bool showPointerMenu: revealControls &&
90− (PanelState.decorationsVisible || mode == "staged")
90+ (panelState.decorationsVisible || mode == "staged")
9191
9292 property bool enablePointerMenu: revealControls &&
9393 applicationMenus.available &&
@@ -139,7 +139,7 @@ Item {
139139 fill: panelAreaBackground
140140 bottomMargin: -units.gu(1)
141141 }
142− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
143143 source: "graphics/rectangular_dropshadow.sci"
144144 }
145145
@@ -192,12 +192,12 @@ Item {
192192 visible: opacity != 0
193193 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
194194
195− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
195+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
196196 windowIsMaximized: true
197− onCloseClicked: PanelState.closeClicked()
198− onMinimizeClicked: PanelState.minimizeClicked()
199− onMaximizeClicked: PanelState.restoreClicked()
200− closeButtonShown: PanelState.closeButtonShown
197+ onCloseClicked: panelState.closeClicked()
198+ onMinimizeClicked: panelState.minimizeClicked()
199+ onMaximizeClicked: panelState.restoreClicked()
200+ closeButtonShown: panelState.closeButtonShown
201201 }
202202
203203 Loader {
@@ -217,7 +217,7 @@ Item {
217217 anchors.left: parent.left
218218 anchors.margins: units.gu(1)
219219 height: menuBarLoader.height
220− enableKeyFilter: valid && PanelState.decorationsVisible
220+ enableKeyFilter: valid && panelState.decorationsVisible
221221 unityMenuModel: __applicationMenus.model
222222
223223 Connections {
@@ -265,7 +265,7 @@ Item {
265265 }
266266
267267 showRowTitle: !expanded
268− rowTitle: PanelState.title
268+ rowTitle: panelState.title
269269 rowItemDelegate: ActionItem {
270270 id: actionItem
271271 property int ownIndex: index
qml/Shell.qml+25−21
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,7 @@ import "Notifications"
3838import "Stage"
3939import "Tutorial"
4040import "Wizard"
41+import "Components/PanelState"
4142import Unity.Notifications 1.0 as NotificationBackend
4243import Unity.Session 0.1
4344import Unity.DashCommunicator 0.1
@@ -52,6 +53,7 @@ StyledItem {
5253 theme.name: "Ubuntu.Components.Themes.SuruDark"
5354
5455 // to be set from outside
56+ property alias surfaceManager: topLevelSurfaceList.surfaceManager
5557 property int orientationAngle: 0
5658 property int orientation
5759 property Orientations orientations
@@ -249,7 +251,6 @@ StyledItem {
249251 var mappedCoords = mapFromItem(null, pos.x, pos.y);
250252 cursor.x = mappedCoords.x;
251253 cursor.y = mappedCoords.y;
252− cursor.mouseNeverMoved = false;
253254 }
254255 }
255256
@@ -258,21 +259,21 @@ StyledItem {
258259 schema.id: "com.canonical.Unity8"
259260 }
260261
262+ PanelState {
263+ id: panelState
264+ objectName: "panelState"
265+ }
266+
261267 Item {
262268 id: stages
263269 objectName: "stages"
264270 width: parent.width
265271 height: parent.height
266272
267− SurfaceManager {
268− id: surfaceMan
269− objectName: "surfaceManager"
270− }
271273 TopLevelWindowModel {
272274 id: topLevelSurfaceList
273275 objectName: "topLevelSurfaceList"
274276 applicationManager: ApplicationManager // it's a singleton
275− surfaceManager: surfaceMan
276277 }
277278
278279 Stage {
@@ -313,6 +314,7 @@ StyledItem {
313314 altTabPressed: physicalKeysMapper.altTabPressed
314315 oskEnabled: shell.oskEnabled
315316 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
317+ panelState: panelState
316318
317319 onSpreadShownChanged: {
318320 panel.indicators.hide();
@@ -338,8 +340,13 @@ StyledItem {
338340 objectName: "greeterLoader"
339341 anchors.fill: parent
340342 anchors.topMargin: panel.panelHeight
341− sourceComponent: shell.mode != "shell" ? integratedGreeter :
342− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
343+ sourceComponent: {
344+ if (shell.mode != "shell") {
345+ if (screenWindow.primary) return integratedGreeter;
346+ return secondaryGreeter;
347+ }
348+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
349+ }
343350 onLoaded: {
344351 item.objectName = "greeter"
345352 }
@@ -379,6 +386,13 @@ StyledItem {
379386 }
380387 }
381388
389+ Component {
390+ id: secondaryGreeter
391+ SecondaryGreeter {
392+ hides: [launcher, panel.indicators]
393+ }
394+ }
395+
382396 Timer {
383397 // See powerConnection for why this is useful
384398 id: showGreeterDelayed
@@ -489,6 +503,7 @@ StyledItem {
489503 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
490504 || greeter.hasLockedApp
491505 greeterShown: greeter && greeter.shown
506+ panelState: panelState
492507 }
493508
494509 Launcher {
@@ -699,23 +714,13 @@ StyledItem {
699714
700715 Cursor {
701716 id: cursor
702− objectName: "cursor"
703717 visible: shell.hasMouse
704718 z: itemGrabber.z + 1
719+ opacity: 0
705720 topBoundaryOffset: panel.panelHeight
706721
707722 confiningItem: stage.itemConfiningMouseCursor
708723
709− property bool mouseNeverMoved: true
710− Binding {
711− target: cursor; property: "x"; value: shell.width / 2
712− when: cursor.mouseNeverMoved && cursor.visible
713− }
714− Binding {
715− target: cursor; property: "y"; value: shell.height / 2
716− when: cursor.mouseNeverMoved && cursor.visible
717− }
718−
719724 height: units.gu(3)
720725
721726 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -770,7 +775,6 @@ StyledItem {
770775 }
771776
772777 onMouseMoved: {
773− mouseNeverMoved = false;
774778 cursor.opacity = 1;
775779 }
776780
qml/ShellApplication.qmlnew file+51
@@ -0,0 +1,51 @@
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 Unity.Screens 0.1
20+import Unity.Application 0.1
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ property QtObject surfaceMan: SurfaceManager {}
27+
28+ ShellScreen {
29+ id: screen
30+ objectName: "screen"+index
31+ screen: model.screen
32+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
33+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
34+ surfaceManager: surfaceMan
35+
36+ Binding {
37+ when: applicationArguments.hasGeometry
38+ target: screen
39+ property: "width"
40+ value: applicationArguments.windowGeometry.width
41+ }
42+ Binding {
43+ when: applicationArguments.hasGeometry
44+ target: screen
45+ property: "height"
46+ value: applicationArguments.windowGeometry.height
47+ }
48+
49+ primary: index == 0
50+ }
51+}
src/SecondaryWindow.h → qml/ShellNotifier.qmlrenamed+12−12
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,17 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_SECONDARY_WINDOW_H
18−#define UNITY_SECONDARY_WINDOW_H
19−
20−#include <QQuickView>
17+/*
18+ Shared signals & properties on multi-window desktop
19+ */
2120
22−class SecondaryWindow : public QQuickView
23−{
24− Q_OBJECT
21+pragma Singleton
22+import QtQuick 2.4
2523
26−public:
27− SecondaryWindow(QQmlEngine *engine);
28−};
24+QtObject {
25+ property var greeter: QtObject {
26+ signal hide(bool now)
2927
30−#endif // UNITY_SECONDARY_WINDOW_H
28+ property bool shown: true
29+ }
30+}
qml/ShellScreen.qmlnew file+65
@@ -0,0 +1,65 @@
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 Unity.Screens 0.1
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+ property QtObject surfaceManager: null
30+
31+ DeviceConfiguration {
32+ id: deviceConfiguration
33+ name: applicationArguments.deviceName
34+ }
35+
36+ 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+ surfaceManager: screenWindow.surfaceManager
54+
55+ deviceConfiguration {
56+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
57+ }
58+ }
59+ }
60+
61+ Component {
62+ id: disabledScreenComponent
63+ DisabledScreenNotice {}
64+ }
65+}
qml/Stage/DecoratedWindow.qml+4−1
@@ -73,6 +73,8 @@ FocusScope {
7373
7474 readonly property Item clientAreaItem: applicationWindow
7575
76+ property PanelState panelState
77+
7678 signal closeClicked()
7779 signal maximizeClicked()
7880 signal maximizeHorizontallyClicked()
@@ -232,7 +234,7 @@ FocusScope {
232234 enableMenus: {
233235 return active &&
234236 surface &&
235− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
237+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
236238 }
237239 menu: sharedAppModel.model
238240
@@ -297,6 +299,7 @@ FocusScope {
297299 objectName: "moveHandler"
298300 target: root.parent
299301 buttonsWidth: decoration.buttonsWidth
302+ panelState: root.panelState
300303 }
301304
302305 Rectangle {
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
3535 property int leftMargin
3636 property real appContainerWidth
3737 property real appContainerHeight
38+ property PanelState panelState
3839
3940 readonly property real hintThreshold: 0.1
4041
@@ -178,7 +179,7 @@ Rectangle {
178179 ParallelAnimation {
179180 id: fakeMaximizeAnimation
180181 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
182183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184185 }
@@ -186,39 +187,39 @@ Rectangle {
186187 ParallelAnimation {
187188 id: fakeMaximizeLeftAnimation
188189 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
190191 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
192193 }
193194
194195 ParallelAnimation {
195196 id: fakeMaximizeRightAnimation
196197 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
198199 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
200201 }
201202
202203 ParallelAnimation {
203204 id: fakeMaximizeTopLeftAnimation
204205 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
206207 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
208209 }
209210
210211 ParallelAnimation {
211212 id: fakeMaximizeTopRightAnimation
212213 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
214215 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
216217 }
217218
218219 ParallelAnimation {
219220 id: fakeMaximizeBottomLeftAnimation
220221 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
222223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224225 }
@@ -226,7 +227,7 @@ Rectangle {
226227 ParallelAnimation {
227228 id: fakeMaximizeBottomRightAnimation
228229 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
230231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232233 }
qml/Stage/MoveHandler.qml+8−7
@@ -28,6 +28,7 @@ QtObject {
2828 property int stageWidth
2929 property int stageHeight
3030 property real buttonsWidth: 0
31+ property PanelState panelState
3132
3233 readonly property bool dragging: priv.dragging
3334
@@ -122,18 +123,18 @@ QtObject {
122123 // position. Mouse movement could have subpixel precision, yielding a fractional
123124 // mouse position.
124125 target.windowedX = Math.round(pos.x - priv.distanceX);
125− target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
126+ target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, panelState.panelHeight));
126127
127128 if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
128− if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
129+ if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < panelState.panelHeight + priv.triggerArea
129130 && target.canBeCornerMaximized) { // top left
130− priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
131+ priv.progress = priv.progressInCorner(0, panelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
131132 priv.resetEdges();
132133 priv.nearTopLeftCorner = true;
133134 root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
134− } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
135+ } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < panelState.panelHeight + priv.triggerArea
135136 && target.canBeCornerMaximized) { // top right
136− priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
137+ priv.progress = priv.progressInCorner(stageWidth, panelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
137138 priv.resetEdges();
138139 priv.nearTopRightCorner = true;
139140 root.fakeMaximizeTopRightAnimationRequested(priv.progress);
@@ -159,8 +160,8 @@ QtObject {
159160 priv.resetEdges();
160161 priv.nearRightEdge = true;
161162 root.fakeMaximizeRightAnimationRequested(priv.progress);
162− } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
163− priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
163+ } else if (sensingPoints.top.y < panelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
164+ priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, panelState.panelHeight + priv.triggerArea, 0, 0, 1);
164165 priv.resetEdges();
165166 priv.nearTopEdge = true;
166167 root.fakeMaximizeAnimationRequested(priv.progress);
qml/Stage/Stage.qml+29−25
@@ -46,6 +46,7 @@ FocusScope {
4646 property int leftMargin: 0
4747 property bool oskEnabled: false
4848 property rect inputMethodRect
49+ property PanelState panelState
4950
5051 // Configuration
5152 property string mode: "staged"
@@ -348,20 +349,20 @@ FocusScope {
348349 Component.onCompleted: priv.updateMainAndSideStageIndexes();
349350
350351 Connections {
351− target: PanelState
352+ target: panelState
352353 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
353354 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
354355 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
355356 }
356357
357358 Binding {
358− target: PanelState
359+ target: panelState
359360 property: "decorationsVisible"
360361 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
361362 }
362363
363364 Binding {
364− target: PanelState
365+ target: panelState
365366 property: "title"
366367 value: {
367368 if (priv.focusedAppDelegate !== null) {
@@ -376,7 +377,7 @@ FocusScope {
376377 }
377378
378379 Binding {
379− target: PanelState
380+ target: panelState
380381 property: "focusedPersistentSurfaceId"
381382 value: {
382383 if (priv.focusedAppDelegate !== null) {
@@ -390,21 +391,21 @@ FocusScope {
390391 }
391392
392393 Binding {
393− target: PanelState
394+ target: panelState
394395 property: "dropShadow"
395396 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
396397 }
397398
398399 Binding {
399− target: PanelState
400+ target: panelState
400401 property: "closeButtonShown"
401402 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
402403 }
403404
404405 Component.onDestruction: {
405− PanelState.title = "";
406− PanelState.decorationsVisible = false;
407− PanelState.dropShadow = false;
406+ panelState.title = "";
407+ panelState.decorationsVisible = false;
408+ panelState.dropShadow = false;
408409 }
409410
410411 Instantiator {
@@ -753,7 +754,7 @@ FocusScope {
753754 target: appDelegate
754755 property: "y"
755756 value: appDelegate.requestedY -
756− Math.min(appDelegate.requestedY - PanelState.panelHeight,
757+ Math.min(appDelegate.requestedY - panelState.panelHeight,
757758 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
758759 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
759760 && root.inputMethodRect.height > 0
@@ -890,7 +891,7 @@ FocusScope {
890891 screenWidth: appContainer.width
891892 screenHeight: appContainer.height
892893 leftMargin: root.leftMargin
893− minimumY: PanelState.panelHeight
894+ minimumY: panelState.panelHeight
894895 }
895896
896897 Connections {
@@ -1130,7 +1131,7 @@ FocusScope {
11301131 progress: 0
11311132 targetHeight: spreadItem.stackHeight
11321133 targetX: spreadMaths.targetX
1133− startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1134+ startY: appDelegate.fullscreen ? 0 : panelState.panelHeight
11341135 targetY: spreadMaths.targetY
11351136 targetAngle: spreadMaths.targetAngle
11361137 targetScale: spreadMaths.targetScale
@@ -1249,9 +1250,9 @@ FocusScope {
12491250 PropertyChanges {
12501251 target: appDelegate
12511252 x: stageMaths.itemX
1252− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1253+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12531254 requestedWidth: appContainer.width
1254− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1255+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
12551256 visuallyMaximized: true
12561257 visible: appDelegate.x < root.width
12571258 }
@@ -1277,10 +1278,10 @@ FocusScope {
12771278 PropertyChanges {
12781279 target: appDelegate
12791280 x: stageMaths.itemX
1280− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1281+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12811282 z: stageMaths.itemZ
12821283 requestedWidth: stageMaths.itemWidth
1283− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1284+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
12841285 visuallyMaximized: true
12851286 visible: appDelegate.x < root.width
12861287 }
@@ -1345,9 +1346,9 @@ FocusScope {
13451346 PropertyChanges {
13461347 target: appDelegate
13471348 windowedX: root.leftMargin
1348− windowedY: PanelState.panelHeight
1349+ windowedY: panelState.panelHeight
13491350 windowedWidth: (appContainer.width - root.leftMargin)/2
1350− windowedHeight: appContainer.height - PanelState.panelHeight
1351+ windowedHeight: appContainer.height - panelState.panelHeight
13511352 }
13521353 },
13531354 State {
@@ -1364,9 +1365,9 @@ FocusScope {
13641365 PropertyChanges {
13651366 target: appDelegate
13661367 windowedX: root.leftMargin
1367− windowedY: PanelState.panelHeight
1368+ windowedY: panelState.panelHeight
13681369 windowedWidth: (appContainer.width - root.leftMargin)/2
1369− windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1370+ windowedHeight: (appContainer.height - panelState.panelHeight)/2
13701371 }
13711372 },
13721373 State {
@@ -1383,7 +1384,7 @@ FocusScope {
13831384 PropertyChanges {
13841385 target: appDelegate
13851386 windowedX: root.leftMargin
1386− windowedY: (appContainer.height + PanelState.panelHeight)/2
1387+ windowedY: (appContainer.height + panelState.panelHeight)/2
13871388 windowedWidth: (appContainer.width - root.leftMargin)/2
13881389 windowedHeight: appContainer.height/2
13891390 }
@@ -1405,8 +1406,8 @@ FocusScope {
14051406 State {
14061407 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
14071408 extend: "normal"
1408− PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1409− windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1409+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: panelState.panelHeight;
1410+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - panelState.panelHeight }
14101411 },
14111412 State {
14121413 name: "minimized"; when: appDelegate.minimized
@@ -1505,7 +1506,7 @@ FocusScope {
15051506 ]
15061507
15071508 Binding {
1508− target: PanelState
1509+ target: panelState
15091510 property: "decorationsAlwaysVisible"
15101511 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
15111512 }
@@ -1520,7 +1521,7 @@ FocusScope {
15201521 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
15211522
15221523 target: appDelegate
1523− minimumY: PanelState.panelHeight // disallow resizing up past Panel
1524+ minimumY: panelState.panelHeight // disallow resizing up past Panel
15241525 minWidth: units.gu(10)
15251526 minHeight: units.gu(10)
15261527 borderThickness: units.gu(2)
@@ -1550,6 +1551,7 @@ FocusScope {
15501551 highlightSize: windowInfoItem.iconMargin / 2
15511552 stageWidth: appContainer.width
15521553 stageHeight: appContainer.height
1554+ panelState: root.panelState
15531555
15541556 requestedWidth: appDelegate.requestedWidth
15551557 requestedHeight: appDelegate.requestedHeight
@@ -1609,6 +1611,7 @@ FocusScope {
16091611 visible: enabled
16101612 stageWidth: appContainer.width
16111613 stageHeight: appContainer.height
1614+ panelState: root.panelState
16121615
16131616 onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
16141617 onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
@@ -1713,6 +1716,7 @@ FocusScope {
17131716 leftMargin: root.leftMargin
17141717 appContainerWidth: appContainer.width
17151718 appContainerHeight: appContainer.height
1719+ panelState: root.panelState
17161720 }
17171721
17181722 EdgeBarrier {
qml/Stage/WindowControlsOverlay.qml+2
@@ -27,6 +27,7 @@ Item {
2727
2828 // to be set from outside
2929 property Item target // appDelegate
30+ property PanelState panelState
3031 property alias stageWidth: moveHandler.stageWidth
3132 property alias stageHeight: moveHandler.stageHeight
3233
@@ -161,6 +162,7 @@ Item {
161162 id: moveHandler
162163 objectName: "moveHandler"
163164 target: root.target
165+ panelState: root.panelState
164166
165167 onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(amount)
166168 onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(amount)
qml/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ShellNotifier 0.1 ShellNotifier.qml
src/ApplicationArguments.cpp+11−2
@@ -16,7 +16,16 @@
1616
1717#include "ApplicationArguments.h"
1818
19−ApplicationArguments::ApplicationArguments(QObject *parent)
20− : QObject(parent)
19+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
20+ : QObject(app)
21+ , UnityCommandLineParser(*app)
2122{
2223}
24+
25+void ApplicationArguments::setDeviceName(const QString &deviceName)
26+{
27+ if (deviceName != m_deviceName) {
28+ m_deviceName = deviceName;
29+ Q_EMIT deviceNameChanged(m_deviceName);
30+ }
31+}
src/ApplicationArguments.h+16−15
@@ -23,31 +23,32 @@
2323#include <QSize>
2424#include <QString>
2525
26−class ApplicationArguments : public QObject
26+#include "UnityCommandLineParser.h"
27+
28+class ApplicationArguments : public QObject,
29+ public UnityCommandLineParser
2730{
2831 Q_OBJECT
2932 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3033 Q_PROPERTY(QString mode READ mode CONSTANT)
34+
35+ Q_PROPERTY(bool hasGeometry READ hasGeometry CONSTANT)
36+ Q_PROPERTY(QSize windowGeometry READ windowGeometry CONSTANT)
37+ Q_PROPERTY(bool hasTestability READ hasTestability CONSTANT)
38+ Q_PROPERTY(bool hasFrameless READ hasFrameless CONSTANT)
39+#ifdef UNITY8_ENABLE_TOUCH_EMULATION
40+ Q_PROPERTY(bool hasMouseToTouch READ hasMouseToTouch CONSTANT)
41+#endif
42+
3143public:
32− ApplicationArguments(QObject *parent = nullptr);
44+ ApplicationArguments(QCoreApplication *app);
3345
34− void setDeviceName(const QString &deviceName) {
35− if (deviceName != m_deviceName) {
36− m_deviceName = deviceName;
37− Q_EMIT deviceNameChanged(m_deviceName);
38− }
39− }
40− QString deviceName() const { return m_deviceName; }
46+ void setDeviceName(const QString &deviceName);
4147
42− void setMode(const QString &mode) { m_mode = mode; }
43− QString mode() const { return m_mode; }
48+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
4449
4550Q_SIGNALS:
4651 void deviceNameChanged(const QString&);
47−
48−private:
49− QString m_deviceName;
50− QString m_mode;
5152};
5253
5354#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt−2
@@ -27,9 +27,7 @@ set(SOURCE_FILES
2727 ApplicationArguments.cpp
2828 main.cpp
2929 CachingNetworkManagerFactory.cpp
30− SecondaryWindow.cpp
3130 ShellApplication.cpp
32− ShellView.cpp
3331 UnityCommandLineParser.cpp
3432 UnixSignalHandler.cpp
3533 DebuggingController.cpp
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.cpp+29−106
@@ -20,6 +20,7 @@
2020#include <QLibrary>
2121#include <QProcess>
2222#include <QScreen>
23+#include <QQmlContext>
2324
2425#include <libintl.h>
2526
@@ -34,30 +35,22 @@
3435
3536ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
3637 : QGuiApplication(argc, argv)
38+ , m_qmlArgs(this)
3739{
3840 setApplicationName(QStringLiteral("unity8"));
3941 setOrganizationName(QStringLiteral("Canonical"));
4042
41− connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
42−
4343 setupQmlEngine(isMirServer);
4444
45− UnityCommandLineParser parser(*this);
46−
47− if (!parser.deviceName().isEmpty()) {
48− m_deviceName = parser.deviceName();
49− } else {
45+ if (m_qmlArgs.deviceName().isEmpty()) {
5046 char buffer[200];
5147 property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
52− m_deviceName = QString(buffer);
48+ m_qmlArgs.setDeviceName(QString(buffer));
5349 }
54− m_qmlArgs.setDeviceName(m_deviceName);
55−
56− m_qmlArgs.setMode(parser.mode());
5750
5851 // The testability driver is only loaded by QApplication but not by QGuiApplication.
5952 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
60− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
53+ if (m_qmlArgs.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
6154 QLibrary testLib(QStringLiteral("qttestability"));
6255 if (testLib.load()) {
6356 typedef void (*TasInitialize)(void);
@@ -75,58 +68,41 @@ ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
7568 bindtextdomain("unity8", translationDirectory().toUtf8().data());
7669 textdomain("unity8");
7770
78− m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
79−
80− if (parser.windowGeometry().isValid()) {
81− m_shellView->setWidth(parser.windowGeometry().width());
82− m_shellView->setHeight(parser.windowGeometry().height());
83− }
71+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &m_qmlArgs);
8472
85− if (parser.hasFrameless()) {
86− m_shellView->setFlags(Qt::FramelessWindowHint);
73+ QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
74+ bool ok;
75+ int pxpgu = pxpguEnv.toInt(&ok);
76+ if (!ok) {
77+ pxpgu = 8;
8778 }
88−
79+ m_qmlEngine->rootContext()->setContextProperty("internalGu", pxpgu);
80+ m_qmlEngine->load(::qmlDirectory() + "/ShellApplication.qml");
8981
9082 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
9183 // You will need this if you want to interact with touch-only components using a mouse
9284 // Needed only when manually testing on a desktop.
93− if (parser.hasMouseToTouch()) {
85+ if (m_qmlArgs.hasMouseToTouch()) {
9486 m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
9587 }
9688 #endif
9789
9890 new DebuggingController(this);
9991
100− // Some hard-coded policy for now.
101− // NB: We don't support more than two screens at the moment
102− //
103− // TODO: Support an arbitrary number of screens and different policies
104− // (eg cloned desktop, several desktops, etc)
105− if (isMirServer && screens().count() == 2) {
106− m_shellView->setScreen(screens().at(1));
107− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
108−
109− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
110− m_secondaryWindow->setScreen(screens().at(0));
111− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
112− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
113− m_secondaryWindow->setVisible(true);
114− }
115−
116− if (parser.mode().compare("greeter") == 0) {
117− QSize primaryScreenSize = this->primaryScreen()->size();
118− m_shellView->setHeight(primaryScreenSize.height());
119− m_shellView->setWidth(primaryScreenSize.width());
120− m_shellView->show();
121− m_shellView->requestActivate();
122− if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
123− qDebug() << "Unable to send unity8-greeter-started event to Upstart";
124− }
125− } else if (isMirServer || parser.hasFullscreen()) {
126− m_shellView->showFullScreen();
127− } else {
128− m_shellView->show();
129− }
92+// if (parser.mode().compare("greeter") == 0) {
93+// QSize primaryScreenSize = this->primaryScreen()->size();
94+// m_shellView->setHeight(primaryScreenSize.height());
95+// m_shellView->setWidth(primaryScreenSize.width());
96+// m_shellView->show();
97+// m_shellView->requestActivate();
98+// if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
99+// qDebug() << "Unable to send unity8-greeter-started event to Upstart";
100+// }
101+// } else if (isMirServer || parser.hasFullscreen()) {
102+// m_shellView->showFullScreen();
103+// } else {
104+// m_shellView->show();
105+// }
130106}
131107
132108ShellApplication::~ShellApplication()
@@ -136,14 +112,6 @@ ShellApplication::~ShellApplication()
136112
137113void ShellApplication::destroyResources()
138114{
139− // Deletion order is important. Don't use QScopedPointers and the like
140− // Otherwise the process will hang on shutdown (bug somewhere I guess).
141− delete m_shellView;
142− m_shellView = nullptr;
143−
144− delete m_secondaryWindow;
145− m_secondaryWindow = nullptr;
146−
147115 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
148116 delete m_mouseTouchAdaptor;
149117 m_mouseTouchAdaptor = nullptr;
@@ -155,7 +123,7 @@ void ShellApplication::destroyResources()
155123
156124void ShellApplication::setupQmlEngine(bool isMirServer)
157125{
158− m_qmlEngine = new QQmlEngine(this);
126+ m_qmlEngine = new QQmlApplicationEngine(this);
159127
160128 m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
161129
@@ -169,48 +137,3 @@ void ShellApplication::setupQmlEngine(bool isMirServer)
169137
170138 QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
171139}
172−
173−void ShellApplication::onScreenAdded(QScreen * /*screen*/)
174−{
175− // TODO: Support an arbitrary number of screens and different policies
176− // (eg cloned desktop, several desktops, etc)
177− if (screens().count() == 2) {
178− m_shellView->setScreen(screens().at(1));
179− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
180− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
181− // its backing QPlatformWindow recreated). So lets refocus it.
182− m_shellView->requestActivate();
183− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
184− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
185− m_shellView->setVisible(true);
186−
187− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
188− m_secondaryWindow->setScreen(screens().at(0));
189−
190− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
191− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
192− m_secondaryWindow->setVisible(true);
193− }
194−}
195−
196−void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
197−{
198− // TODO: Support an arbitrary number of screens and different policies
199− // (eg cloned desktop, several desktops, etc)
200− if (screen == m_shellView->screen()) {
201− const QList<QScreen *> allScreens = screens();
202− Q_ASSERT(allScreens.count() > 1);
203− Q_ASSERT(allScreens.at(0) != screen);
204− Q_ASSERT(m_secondaryWindow);
205− delete m_secondaryWindow;
206− m_secondaryWindow = nullptr;
207− m_shellView->setScreen(allScreens.first());
208− m_qmlArgs.setDeviceName(m_deviceName);
209− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
210− // its backing QPlatformWindow recreated). So lets refocus it.
211− m_shellView->requestActivate();
212− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
213− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
214− m_shellView->setVisible(true);
215− }
216−}
src/ShellApplication.h+2−14
@@ -18,7 +18,7 @@
1818#define SHELLAPPLICATION_H
1919
2020#include <QGuiApplication>
21−#include <QQmlEngine>
21+#include <QQmlApplicationEngine>
2222#include <QQuickView>
2323#include <QScopedPointer>
2424
@@ -28,9 +28,6 @@
2828#include "MouseTouchAdaptor.h"
2929#endif
3030
31−#include "SecondaryWindow.h"
32−#include "ShellView.h"
33−
3431class ShellApplication : public QGuiApplication
3532{
3633 Q_OBJECT
@@ -39,25 +36,16 @@ public:
3936 virtual ~ShellApplication();
4037
4138 void destroyResources();
42−public Q_SLOTS:
43− // called by qtmir
44− void onScreenAboutToBeRemoved(QScreen *screen);
45−
46−private Q_SLOTS:
47− void onScreenAdded(QScreen*);
4839
4940private:
5041 void setupQmlEngine(bool isMirServer);
51− QString m_deviceName;
5242 ApplicationArguments m_qmlArgs;
53− ShellView *m_shellView{nullptr};
54− SecondaryWindow *m_secondaryWindow{nullptr};
5543
5644 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
5745 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
5846 #endif
5947
60− QQmlEngine *m_qmlEngine{nullptr};
48+ QQmlApplicationEngine *m_qmlEngine{nullptr};
6149};
6250
6351#endif // SHELLAPPLICATION_H
src/ShellView.cppdeleted−63
@@ -1,63 +0,0 @@
1−/*
2− * Copyright (C) 2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−#include "ShellView.h"
18−
19−// Qt
20−#include <QQmlContext>
21−#include <QQuickItem>
22−
23−// local
24−#include <paths.h>
25−
26−ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
27− : QQuickView(engine, nullptr)
28−{
29− setResizeMode(QQuickView::SizeRootObjectToView);
30− setColor("black");
31− setTitle(QStringLiteral("Unity8"));
32−
33− rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
34−
35− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
36− setSource(source);
37−
38− connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
39− connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
40−}
41−
42−void ShellView::onWidthChanged(int w)
43−{
44− // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
45− //
46− // There's at least one situation that's know to leave the root object with an outdated size.
47− // (really looks like Qt bug)
48− // Happens when starting unity8 with an external monitor already connected.
49− // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
50− // from the resize event handler, the root item doesn't get resized.
51− // TODO: Confirm the Qt bug and submit a patch upstream
52− if (rootObject()) {
53− rootObject()->setWidth(w);
54− }
55−}
56−
57−void ShellView::onHeightChanged(int h)
58−{
59− // See comment in ShellView::onWidthChanged()
60− if (rootObject()) {
61− rootObject()->setHeight(h);
62− }
63−}
src/ShellView.hdeleted−34
@@ -1,34 +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−#ifndef UNITY_SHELL_VIEW_H
18−#define UNITY_SHELL_VIEW_H
19−
20−#include <QQuickView>
21−
22−class ShellView : public QQuickView
23−{
24− Q_OBJECT
25−
26−public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
28−
29−private Q_SLOTS:
30− void onWidthChanged(int);
31− void onHeightChanged(int);
32−};
33−
34−#endif // UNITY_SHELL_VIEW_H
src/UnityCommandLineParser.h+1−1
@@ -36,8 +36,8 @@ public:
3636 bool hasFullscreen() const { return m_hasFullscreen; }
3737 QString deviceName() const { return m_deviceName; }
3838 QString mode() const { return m_mode; }
39−private:
4039
40+protected:
4141 int parsePixelsValue(const QString &str);
4242 static float getenvFloat(const char* name, float defaultValue);
4343 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
tests/mocks/Cursor/Cursor.qml+50−2
@@ -15,8 +15,11 @@
1515 */
1616
1717import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
1822
19−Item {
2023 property int topBoundaryOffset // effectively panel height
2124 property Item confiningItem
2225
@@ -30,5 +33,50 @@ Item {
3033 signal pushStopped()
3134 signal mouseMoved()
3235
33− onMouseMoved: opacity = 1;
36+ width: units.gu(2)
37+ height: units.gu(2)
38+ antialiasing: true
39+
40+ onPaint: {
41+ var ctx = getContext("2d");
42+ ctx.save();
43+ ctx.clearRect(0,0,width, height);
44+ ctx.strokeStyle = "#000000";
45+ ctx.lineWidth = 1
46+ ctx.fillStyle = "#ffffff";
47+ ctx.globalAlpha = 1.0;
48+ ctx.lineJoin = "round";
49+ ctx.beginPath();
50+
51+ // put rectangle in the middle
52+ // draw the rectangle
53+ ctx.moveTo(width/2,height);
54+ ctx.lineTo(width, height/2);
55+ ctx.lineTo(0,0);
56+
57+ ctx.closePath();
58+ ctx.fill();
59+ ctx.stroke();
60+ ctx.restore();
61+ }
62+
63+ Connections {
64+ target: UInput
65+ onMouseMoved: {
66+ var newX = root.x;
67+ newX += dx;
68+ if (newX < 0) newX = 0;
69+ else if (newX >= parent.width) newX = parent.width-1;
70+
71+ var newY = root.y;
72+ newY += dy;
73+ if (newY < 0) newY = 0;
74+ else if (newY >= parent.height) newY = parent.height-1;
75+
76+ root.x = newX;
77+ root.y = newY;
78+ root.mouseMoved();
79+ }
80+ }
3481}
82+
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
1818#include "fake_gsettings.h"
1919
2020#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
2123
2224static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
2325{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
3133 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
3234 "GSettingsSchema can only be used inside of a GSettings component");
3335}
36+
37+void FakeGSettingsQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
38+{
39+ QQmlExtensionPlugin::initializeEngine(engine, uri);
40+
41+ QString usageType = qgetenv("UNITY_MOCK_DESKTOP");
42+ if (!usageType.isEmpty()) {
43+ GSettingsControllerQml::instance()->setUsageMode("Windowed");
44+ }
45+}
tests/mocks/GSettings.1.0/plugin.h+1
@@ -26,6 +26,7 @@ class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
2626
2727public:
2828 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
2930};
3031
3132#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
1919
2020#include <QtQml/qqml.h>
2121
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
2227void MockUInputPlugin::registerTypes(const char *uri)
2328{
2429 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
2631}
tests/mocks/Unity/Application/SurfaceManager.cpp+1−1
@@ -21,7 +21,7 @@
2121
2222#include <paths.h>
2323
24−#define SURFACEMANAGER_DEBUG 0
24+#define SURFACEMANAGER_DEBUG 1
2525
2626#if SURFACEMANAGER_DEBUG
2727#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
tests/mocks/Unity/InputInfo/InputWindow.qmlnew file+68
@@ -0,0 +1,68 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.InputInfo 0.1
4+
5+Rectangle {
6+ id: rect
7+ color: "white"
8+ width: parent.width
9+ height: parent.height
10+
11+ Action {
12+ id: mouseAction
13+ text: checked ? "Remove Mouse" : "Add Mouse"
14+ onTriggered: {
15+ if (checked) {
16+ console.log("ADD")
17+ MockInputDeviceBackend.addMockDevice("/mouse0", InputInfo.Mouse);
18+ } else {
19+ console.log("REMOVE")
20+ MockInputDeviceBackend.removeDevice("/mouse0");
21+ }
22+ }
23+ iconName: "input-mouse-symbolic"
24+ checkable: true
25+ checked: false
26+ }
27+
28+ Action {
29+ id: kbAction
30+ text: checked ? "Remove Keyboard" : "Add Keyboard"
31+ onTriggered: {
32+ if (checked) {
33+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
34+ } else {
35+ MockInputDeviceBackend.removeDevice("/kbd0");
36+ }
37+ }
38+ iconName: "input-keyboard-symbolic"
39+ checkable: true
40+ checked: false
41+ }
42+
43+ Column {
44+ anchors {
45+ fill: parent
46+ margins: units.gu(1)
47+ }
48+ spacing: units.gu(1)
49+
50+ Button {
51+ anchors {
52+ left: parent.left
53+ right: parent.right
54+ }
55+ action: mouseAction
56+ color: mouseAction.checked ? UbuntuColors.red : UbuntuColors.green
57+ }
58+
59+ Button {
60+ anchors {
61+ left: parent.left
62+ right: parent.right
63+ }
64+ action: kbAction
65+ color: kbAction.checked ? UbuntuColors.red : UbuntuColors.green
66+ }
67+ }
68+}
tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2
@@ -18,10 +18,32 @@
1818
1919#include "qinputdeviceinfo_mock_p.h"
2020
21−MockController::MockController(QObject *parent):
22− QObject(parent)
21+#include <QQuickView>
22+#include <QQmlComponent>
23+#include <QQuickItem>
24+#include <paths.h>
25+
26+MockController::MockController(QQmlEngine *engine)
2327{
28+ QQmlComponent component(engine);
29+ component.setData("import QtQuick 2.4; import Unity.InputInfo 0.1; InputWindow { }", ::qmlDirectory());
30+ QObject *myObject = component.create();
31+ QQuickItem *item = qobject_cast<QQuickItem*>(myObject);
32+ if (item) {
33+ auto window = new QQuickView();
34+ window->setTitle("Input");
35+ item->setParentItem(window->contentItem());
36+ window->setResizeMode(QQuickView::SizeRootObjectToView);
37+ window->setWidth(200);
38+ window->setHeight(100);
39+ window->show();
40+ }
41+}
2442
43+MockController *MockController::instance(QQmlEngine *engine)
44+{
45+ static MockController* controller = new MockController(engine);
46+ return controller;
2547}
2648
2749QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
tests/mocks/Unity/InputInfo/mockcontroller.h+5−1
@@ -20,13 +20,17 @@
2020#include <QObject>
2121#include "qinputinfo.h"
2222
23+class QQmlEngine;
24+
2325class MockController: public QObject
2426{
2527 Q_OBJECT
2628public:
27− MockController(QObject *parent = 0);
29+ MockController(QQmlEngine *engine);
2830 ~MockController() = default;
2931
32+ static MockController *instance(QQmlEngine *engine);
33+
3034 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
3135 Q_INVOKABLE void removeDevice(const QString &devicePath);
3236};
tests/mocks/Unity/InputInfo/plugin.cpp+1−2
@@ -26,9 +26,8 @@
2626
2727static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
2828{
29− Q_UNUSED(engine)
3029 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
30+ return MockController::instance(engine);
3231}
3332
3433void InputInfoPlugin::registerTypes(const char *uri)
tests/mocks/Unity/InputInfo/qmldir+2
@@ -1,2 +1,4 @@
11module Unity.InputInfo
22plugin MockInputInfo
3+
4+InputWindow 0.1 InputWindow.qml
tests/mocks/Unity/Screens/CMakeLists.txt+2−1
@@ -5,10 +5,11 @@ include_directories(
55set(MockScreens_SOURCES
66 plugin.cpp
77 screens.cpp
8+ screenwindow.cpp
89)
910
1011add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
1112
12−qt5_use_modules(MockScreensPlugin Gui Qml)
13+qt5_use_modules(MockScreensPlugin Gui Qml Quick)
1314
1415add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
tests/mocks/Unity/Screens/plugin.cpp+12−1
@@ -16,14 +16,25 @@
1616
1717#include "plugin.h"
1818#include "screens.h"
19+#include "screenwindow.h"
1920
2021#include <QScreen>
2122
23+namespace {
24+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
25+ Q_UNUSED(engine);
26+ Q_UNUSED(scriptEngine);
27+ return new Screens();
28+}
29+}
30+
2231void UnityScreensPlugin::registerTypes(const char* uri)
2332{
2433 Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
2534
2635 qRegisterMetaType<QScreen*>("QScreen*");
2736
28− qmlRegisterType<Screens>(uri, 0, 1, "Screens");
37+ qmlRegisterSingletonType<Screens>(uri, 0, 1, "Screens", screensSingleton);
38+ qmlRegisterType<ScreenWindow>(uri, 0, 1, "ScreenWindow");
39+ qmlRegisterRevision<QWindow,1>(uri, 0, 1);
2940}
tests/mocks/Unity/Screens/screens.cpp+39−2
@@ -26,8 +26,20 @@ Q_DECLARE_METATYPE(QScreen*)
2626Screens::Screens(QObject *parent) :
2727 QAbstractListModel(parent)
2828{
29− // start with one screen attached
30− m_screenList.append(new Screen());
29+ bool ok = false;
30+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
31+ if (!ok) screenCount = 1;
32+ QPoint lastPoint(0,0);
33+ for (int i = 0; i < screenCount; ++i) {
34+ auto screen = new Screen();
35+ screen->enabled = i == 0;
36+ screen->name = QString("Monitor %1").arg(i);
37+ screen->sizes = { QSize(640,480), QSize(1024,748), QSize(1280,1024), QSize(1440,900), QSize(1920,1080) };
38+ screen->geometry = QRect(lastPoint.x(), lastPoint.y(), 1024, 786 );
39+ m_screenList.append(screen);
40+
41+ lastPoint.rx() += screen->geometry.width();
42+ }
3143}
3244
3345Screens::~Screens() noexcept
@@ -41,6 +53,12 @@ QHash<int, QByteArray> Screens::roleNames() const
4153 QHash<int, QByteArray> roles;
4254 roles[ScreenRole] = "screen";
4355 roles[OutputTypeRole] = "outputType";
56+ roles[EnabledRole] = "enabled";
57+ roles[NameRole] = "name";
58+ roles[ScaleRole] = "scale";
59+ roles[FormFactorRole] = "formFactor";
60+ roles[GeometryRole] = "geometry";
61+ roles[SizesRole] = "sizes";
4462 return roles;
4563}
4664
@@ -55,6 +73,25 @@ QVariant Screens::data(const QModelIndex &index, int role) const
5573 return QVariant::fromValue(m_screenList.at(index.row())->qScreen);
5674 case OutputTypeRole:
5775 return m_screenList.at(index.row())->outputTypes;
76+ case EnabledRole:
77+ return m_screenList.at(index.row())->enabled;
78+ case NameRole:
79+ return m_screenList.at(index.row())->name;
80+ case ScaleRole:
81+ return m_screenList.at(index.row())->scale;
82+ case FormFactorRole:
83+ return m_screenList.at(index.row())->formFactor;
84+ case GeometryRole:
85+ return m_screenList.at(index.row())->geometry;
86+ case SizesRole:
87+ {
88+ QVariantList sizes;
89+ auto availableSizes = m_screenList.at(index.row())->sizes;
90+ Q_FOREACH(auto size, availableSizes) {
91+ sizes.append(QVariant(size));
92+ }
93+ return sizes;
94+ }
5895 }
5996
6097 return QVariant();
tests/mocks/Unity/Screens/screens.h+22−1
@@ -31,7 +31,13 @@ class Screens : public QAbstractListModel
3131public:
3232 enum ItemRoles {
3333 ScreenRole = Qt::UserRole + 1,
34− OutputTypeRole
34+ OutputTypeRole,
35+ EnabledRole,
36+ NameRole,
37+ ScaleRole,
38+ FormFactorRole,
39+ GeometryRole,
40+ SizesRole
3541 };
3642
3743 enum OutputTypes {
@@ -53,6 +59,15 @@ public:
5359 };
5460 Q_ENUM(OutputTypes)
5561
62+ enum FormFactor {
63+ FormFactorUnknown,
64+ FormFactorPhone,
65+ FormFactorTablet,
66+ FormFactorMonitor,
67+ FormFactorTV,
68+ FormFactorProjector,
69+ };
70+
5671 explicit Screens(QObject *parent = 0);
5772 virtual ~Screens() noexcept;
5873
@@ -77,6 +92,12 @@ class Screen
7792public:
7893 Screens::OutputTypes outputTypes = Screens::Unknown;
7994 QScreen *qScreen = nullptr;
95+ bool enabled = false;
96+ QString name = QString();
97+ float scale = 1.0;
98+ Screens::FormFactor formFactor = Screens::FormFactorMonitor;
99+ QRect geometry;
100+ QList<QSize> sizes;
80101};
81102
82103#endif // SCREENS_H
tests/mocks/Unity/Screens/screenwindow.cppnew 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 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+
19+ScreenWindow::ScreenWindow(QWindow *parent)
20+ : QQuickWindow(parent)
21+{
22+}
23+
24+QScreen *ScreenWindow::screen() const
25+{
26+ return QQuickWindow::screen();
27+}
28+
29+void ScreenWindow::setScreen(QScreen *screen)
30+{
31+ QQuickWindow::setScreen(screen);
32+}
tests/mocks/Unity/Screens/screenwindow.hnew file+36
@@ -0,0 +1,36 @@
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 SCREENWINDOW_H
18+#define SCREENWINDOW_H
19+
20+#include <QQuickWindow>
21+
22+class ScreenWindow : public QQuickWindow
23+{
24+ Q_OBJECT
25+ Q_PROPERTY(QScreen *screen READ screen WRITE setScreen NOTIFY screenChanged)
26+public:
27+ ScreenWindow(QWindow *parent = 0);
28+
29+ QScreen *screen() const;
30+ void setScreen(QScreen *screen);
31+
32+Q_SIGNALS:
33+ void screenChanged(QScreen *screen);
34+};
35+
36+#endif // SCREENWINDOW_H
tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2
@@ -39,11 +39,11 @@ Rectangle {
3939
4040 SignalSpy {
4141 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
4343 }
4444 SignalSpy {
4545 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
4747 }
4848
4949 UnityTestCase {
tests/qmltests/Panel/tst_Panel.qml+14−12
@@ -42,6 +42,8 @@ PanelTest {
4242 value: keyboardAttached.checked
4343 }
4444
45+ readonly property alias panelState: panel.panelState
46+
4547 SurfaceManager { id: sMgr }
4648 ApplicationMenuDataLoader {
4749 id: appMenuData
@@ -148,7 +150,7 @@ PanelTest {
148150 Layout.fillWidth: true
149151 CheckBox {
150152 id: windowControlsCB
151− onClicked: PanelState.decorationsVisible = checked
153+ onClicked: panelState.decorationsVisible = checked
152154 }
153155 Label {
154156 text: "Show window decorations"
@@ -159,7 +161,7 @@ PanelTest {
159161 RowLayout {
160162 Layout.fillWidth: true
161163 CheckBox {
162− onClicked: PanelState.title = checked ? "Fake window title" : ""
164+ onClicked: panelState.title = checked ? "Fake window title" : ""
163165 }
164166 Label {
165167 text: "Show fake window title"
@@ -245,7 +247,7 @@ PanelTest {
245247
246248 SignalSpy {
247249 id: windowControlButtonsSpy
248− target: PanelState
250+ target: panelState
249251 signalName: "closeClicked"
250252 }
251253
@@ -255,8 +257,8 @@ PanelTest {
255257 panel.fullscreenMode = false;
256258 callManager.foregroundCall = null;
257259
258− PanelState.title = "";
259− PanelState.decorationsVisible = false;
260+ panelState.title = "";
261+ panelState.decorationsVisible = false;
260262
261263 // Wait for the indicators to get into position.
262264 // (switches between normal and fullscreen modes are animated)
@@ -445,7 +447,7 @@ PanelTest {
445447 }
446448
447449 function test_hint(data) {
448− PanelState.title = "Fake Title"
450+ panelState.title = "Fake Title"
449451 panel.fullscreenMode = data.fullscreen;
450452 callManager.foregroundCall = data.call;
451453
@@ -487,7 +489,7 @@ PanelTest {
487489 // menus, first by running the hint animation, then after dragging down will
488490 // expose more of the panel. Releasing the touch will complete the show.
489491 function test_drag_applicationMenu_down_shows_menu(data) {
490− PanelState.title = "Fake Title";
492+ panelState.title = "Fake Title";
491493 panel.fullscreenMode = data.fullscreen;
492494 callManager.foregroundCall = data.call;
493495
@@ -578,7 +580,7 @@ PanelTest {
578580 }
579581
580582 function test_darkenedAreaEatsAllApplicationMenuEvents() {
581− PanelState.title = "Fake Title"
583+ panelState.title = "Fake Title"
582584
583585 // The center of the area not covered by the indicators menu
584586 // Ie, the visible darkened area behind the menu
@@ -699,7 +701,7 @@ PanelTest {
699701 var windowControlArea = findChild(panel, "windowControlArea");
700702 verify(windowControlArea, "Window control area should have been created in windowed mode")
701703
702− PanelState.decorationsVisible = true;
704+ panelState.decorationsVisible = true;
703705 // click in very topleft corner and verify the close button got clicked too
704706 mouseMove(panel, 0, 0);
705707 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
@@ -760,7 +762,7 @@ PanelTest {
760762 }
761763
762764 function test_stagedApplicationMenuBarShowOnMouseHover() {
763− PanelState.title = "Fake Title";
765+ panelState.title = "Fake Title";
764766 panel.mode = "staged";
765767 mouseEmulation.checked = false;
766768
@@ -778,7 +780,7 @@ PanelTest {
778780 }
779781
780782 function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
781− PanelState.title = "Fake Title";
783+ panelState.title = "Fake Title";
782784 panel.mode = "windowed";
783785 mouseEmulation.checked = false;
784786
@@ -794,7 +796,7 @@ PanelTest {
794796 tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
795797 tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
796798
797− PanelState.decorationsVisible = true;
799+ panelState.decorationsVisible = true;
798800
799801 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
800802 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
tests/qmltests/Stage/tst_DecoratedWindow.qml+5−1
@@ -52,6 +52,10 @@ Rectangle {
5252 surfaceManager: sMgr
5353 }
5454
55+ PanelState {
56+ id: panelState
57+ }
58+
5559 Item {
5660 id: fakeShell
5761
@@ -99,7 +103,7 @@ Rectangle {
99103 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
100104
101105 Binding {
102− target: PanelState
106+ target: panelState
103107 property: "focusedPersistentSurfaceId"
104108 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105109 }
tests/qmltests/Stage/tst_DesktopStage.qml+8−3
@@ -43,6 +43,10 @@ Item {
4343 value: false
4444 }
4545
46+ PanelState {
47+ id: panelState
48+ }
49+
4650 Component.onCompleted: {
4751 QuickUtils.keyboardAttached = true;
4852 theme.name = "Ubuntu.Components.Themes.SuruDark";
@@ -100,6 +104,7 @@ Item {
100104 topLevelSurfaceList: topSurfaceList
101105 interactive: true
102106 mode: "windowed"
107+ panelState: panelState
103108 }
104109 }
105110 }
@@ -618,19 +623,19 @@ Item {
618623 maximizeDelegate(facebookAppDelegate);
619624
620625 // verify the drop shadow is still not visible
621− verify(PanelState.dropShadow == false);
626+ verify(panelState.dropShadow == false);
622627
623628 // start a foreground app, not maximized
624629 var dialerAppDelegate = startApplication("dialer-app");
625630
626631 // verify the drop shadow becomes visible
627− verify(PanelState.dropShadow == true);
632+ verify(panelState.dropShadow == true);
628633
629634 // close the maximized app
630635 ApplicationManager.stopApplication("facebook-webapp");
631636
632637 // verify the drop shadow is gone
633− tryCompare(PanelState, "dropShadow", false);
638+ tryCompare(panelState, "dropShadow", false);
634639 }
635640
636641 function test_threeFingerTapShowsWindowControls_data() {
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8
@@ -19,7 +19,6 @@ import QtQuick.Layouts 1.1
1919import QtTest 1.0
2020import Unity.Test 0.1
2121import ".."
22−import "../../../qml/Components/PanelState"
2322import "../../../qml/Stage"
2423import Ubuntu.Components 1.3
2524import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -31,12 +30,6 @@ Item {
3130 height: units.gu(60)
3231 width: units.gu(85)
3332
34− Binding {
35− target: PanelState
36− property: "panelHeight"
37− value: units.gu(3)
38− }
39−
4033 Component {
4134 id: fakeWindowComponent
4235
@@ -352,7 +345,7 @@ Item {
352345
353346 // Make sure it's again where we left it in normal state before destroying
354347 compare(fakeWindow.requestedX >= 0, true)
355− compare(fakeWindow.requestedY >= PanelState.panelHeight, true)
348+ compare(fakeWindow.requestedY >= 0, true)
356349 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
357350 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
358351
tests/qmltests/tst_OrientedShell.qml+24−5
@@ -29,7 +29,6 @@ import Utils 0.1
2929
3030import "../../qml"
3131import "../../qml/Components"
32−import "../../qml/Components/PanelState"
3332import "Stage"
3433
3534Rectangle {
@@ -75,6 +74,12 @@ Rectangle {
7574 deviceFilter: InputInfo.Keyboard
7675 }
7776
77+ QtObject {
78+ id: _screenWindow
79+ property bool primary: true
80+ }
81+ property alias screenWindow: _screenWindow
82+
7883 property int physicalOrientation0
7984 property int physicalOrientation90
8085 property int physicalOrientation180
@@ -167,6 +172,11 @@ Rectangle {
167172 Component.onDestruction: {
168173 orientedShellLoader.itemDestroyed = true;
169174 }
175+
176+ SurfaceManager {
177+ id: surfaceMan
178+ }
179+ surfaceManager: surfaceMan
170180 }
171181 }
172182 }
@@ -439,6 +449,15 @@ Rectangle {
439449 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
440450 property Item shell
441451 property QtObject topLevelSurfaceList
452+ property var panelState: undefined
453+
454+ onOrientedShellChanged: {
455+ if (orientedShell) {
456+ panelState = findInvisibleChild(orientedShell, "panelState");
457+ } else {
458+ panelState = undefined;
459+ }
460+ }
442461
443462 SignalSpy { id: signalSpy }
444463 SignalSpy { id: signalSpy2 }
@@ -1507,13 +1526,13 @@ Rectangle {
15071526 print("exptectedAngle", expectedAngle, point.x, point.y)
15081527 switch (expectedAngle) {
15091528 case 0:
1510− return point.x === 0 && point.y === PanelState.panelHeight;
1529+ return point.x === 0 && point.y === panelState.panelHeight;
15111530 case 90:
1512− return point.x === orientedShell.width - PanelState.panelHeight && point.y === 0;
1531+ return point.x === orientedShell.width - panelState.panelHeight && point.y === 0;
15131532 case 180:
1514− return point.x === orientedShell.width && point.y === orientedShell.height - PanelState.panelHeight;
1533+ return point.x === orientedShell.width && point.y === orientedShell.height - panelState.panelHeight;
15151534 default: // 270
1516− return point.x === PanelState.panelHeight && point.y === orientedShell.height;
1535+ return point.x === panelState.panelHeight && point.y === orientedShell.height;
15171536 }
15181537 }
15191538
tests/qmltests/tst_Shell.qml+25−11
@@ -62,13 +62,22 @@ Rectangle {
6262 if (shell) {
6363 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
6464 appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
65+ panelState = testCase.findInvisibleChild(shell, "panelState");
6566 } else {
6667 topLevelSurfaceList = null;
6768 appMenuData.surfaceManager = null;
69+ panelState = undefined;
6870 }
6971 }
7072
7173 property var topLevelSurfaceList: null
74+ property var panelState: undefined
75+
76+ QtObject {
77+ id: _screenWindow
78+ property bool primary: false
79+ }
80+ property alias screenWindow: _screenWindow
7281
7382 Item {
7483 id: shellContainer
@@ -143,6 +152,11 @@ Rectangle {
143152 Component.onDestruction: {
144153 shellLoader.itemDestroyed = true;
145154 }
155+
156+ SurfaceManager {
157+ id: surfaceMan
158+ }
159+ surfaceManager: surfaceMan
146160 }
147161 }
148162 }
@@ -679,7 +693,7 @@ Rectangle {
679693 }
680694
681695 function ensureInputMethodSurface() {
682− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
696+ var surfaceManager = shell.surfaceManager;
683697 verify(surfaceManager);
684698 surfaceManager.createInputMethodSurface();
685699
@@ -1886,21 +1900,21 @@ Rectangle {
18861900 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
18871901
18881902 tryCompare(appDelegate, "state", "normal");
1889− tryCompare(PanelState, "decorationsVisible", false)
1903+ tryCompare(panelState, "decorationsVisible", false)
18901904
18911905 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
18921906 tryCompare(appDelegate, "state", "maximized");
1893− tryCompare(PanelState, "decorationsVisible", true)
1907+ tryCompare(panelState, "decorationsVisible", true)
18941908
18951909 ApplicationManager.stopApplication(application.appId);
1896− tryCompare(PanelState, "decorationsVisible", false)
1910+ tryCompare(panelState, "decorationsVisible", false)
18971911
18981912 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
18991913 // They won't be gone until those surface items are destroyed.
19001914 tryCompareFunction(function() { return application.surfaceList.count }, 0);
19011915
19021916 ApplicationManager.startApplication(application.appId);
1903− tryCompare(PanelState, "decorationsVisible", true)
1917+ tryCompare(panelState, "decorationsVisible", true)
19041918 }
19051919
19061920 function test_newAppHasValidGeometry() {
@@ -1957,7 +1971,7 @@ Rectangle {
19571971 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
19581972 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
19591973
1960− compare(appDelegate.y >= PanelState.panelHeight, true);
1974+ compare(appDelegate.y >= panelState.panelHeight, true);
19611975 }
19621976
19631977 function test_cantResizeWindowUnderPanel() {
@@ -1985,7 +1999,7 @@ Rectangle {
19851999 mouseMove(decoration, decoration.width/2, -units.gu(100));
19862000
19872001 // verify we don't go past the panel
1988− compare(appDelegate.y >= PanelState.panelHeight, true);
2002+ compare(appDelegate.y >= panelState.panelHeight, true);
19892003 }
19902004
19912005 function test_restoreWindowStateFixesIfUnderPanel() {
@@ -2009,7 +2023,7 @@ Rectangle {
20092023 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
20102024
20112025 appDelegate = appRepeater.itemAt(0);
2012− compare(appDelegate.y >= PanelState.panelHeight, true);
2026+ compare(appDelegate.y >= panelState.panelHeight, true);
20132027 }
20142028
20152029 function test_lifecyclePolicyForNonTouchApp_data() {
@@ -2610,7 +2624,7 @@ Rectangle {
26102624 function test_oskDisplacesWindow_data() {
26112625 return [
26122626 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2613− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2627+ {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: panelState.panelHeight, oskEnabled: true},
26142628 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
26152629 ]
26162630 }
@@ -2633,8 +2647,8 @@ Rectangle {
26332647 dashAppDelegate.windowedY = data.windowY;
26342648 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
26352649 var initialY = dashAppDelegate.y;
2636− print("intial", initialY, "panel", PanelState.panelHeight);
2637− verify(initialY > PanelState.panelHeight);
2650+ print("intial", initialY, "panel", panelState.panelHeight);
2651+ verify(initialY > panelState.panelHeight);
26382652
26392653 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
26402654 tryCompare(dashAppDelegate, "y", data.targetDisplacement);