on 389a48b help sign in
plugins/Cursor/CMakeLists.txt+1
since splitsnapshots
unifiedsplit
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 CursorImageInfo.cpp
15 CursorImageInfo.cpp
16 CursorImageProvider.cpp
16 CursorImageProvider.cpp
17 MousePointer.cpp
17 MousePointer.cpp
18+ InputDispatcherFilter.cpp
18 # We need to run moc on this header
19 # We need to run moc on this header
19 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 )
21 )
plugins/Cursor/InputDispatcherFilter.cppnew file+143
@@ -0,0 +1,143 @@
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+ QMouseEvent* me = static_cast<QMouseEvent*>(e);
76+
77+ // Local position gives relative change of mouse pointer.
78+ QPointF localPos = me->localPos();
79+ QPointF globalPos = me->screenPos();
80+
81+ // Adjust the position
82+ QPointF oldPos(mousePosition.isNull() ? globalPos : mousePosition);
83+ QPointF newPos = adjustedPositionForMovement(oldPos, localPos);
84+
85+ QScreen* currentScreen = screenAt(newPos);
86+ if (currentScreen) {
87+ QRect screenRect = currentScreen->geometry();
88+ qreal unadjustedX = (oldPos + localPos).x();
89+ if (unadjustedX < screenRect.left()) {
90+ Q_EMIT pushedLeftBoundary(currentScreen, qAbs(unadjustedX - screenRect.left()), me->buttons());
91+ } else if (unadjustedX > screenRect.right()) {
92+ Q_EMIT pushedRightBoundary(currentScreen, qAbs(unadjustedX - screenRect.right()), me->buttons());
93+ }
94+ }
95+
96+ // Send the event
97+ QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
98+ eCopy.setTimestamp(me->timestamp());
99+ o->event(&eCopy);
100+ return true;
101+ }
102+ default:
103+ break;
104+ }
105+ return false;
106+}
107+
108+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
109+{
110+ QPointF adjusted = pt + movement;
111+
112+ auto screen = screenAt(adjusted); // first check if our move was to a valid screen.
113+ if (screen) {
114+ return adjusted;
115+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
116+ QRectF screenBounds = screen->geometry();
117+ // bound the new position to the old screen geometry
118+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
119+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
120+ } else {
121+ auto screens = QGuiApplication::screens();
122+
123+ // center of first screen with a pointer.
124+ Q_FOREACH(QScreen* screen, screens) {
125+ Q_FOREACH(MousePointer* pointer, m_pointers) {
126+ if (pointer->screen() == screen) {
127+ return screen->geometry().center();
128+ }
129+ }
130+ }
131+ }
132+ return adjusted;
133+}
134+
135+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
136+{
137+ Q_FOREACH(MousePointer* pointer, m_pointers) {
138+ QScreen* screen = pointer->screen();
139+ if (screen && screen->geometry().contains(pt.toPoint()))
140+ return screen;
141+ }
142+ return nullptr;
143+}
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 @@
16
16
17#include "MousePointer.h"
17#include "MousePointer.h"
18#include "CursorImageProvider.h"
18#include "CursorImageProvider.h"
19−
19+#include "InputDispatcherFilter.h"
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
22
20
23#include <QQuickWindow>
21#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
26
22
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
28
25
29MousePointer::MousePointer(QQuickItem *parent)
26MousePointer::MousePointer(QQuickItem *parent)
30 : MirMousePointerInterface(parent)
27 : MirMousePointerInterface(parent)
31 , m_cursorName(QStringLiteral("left_ptr"))
28 , m_cursorName(QStringLiteral("left_ptr"))
32 , m_themeName(QStringLiteral("default"))
29 , m_themeName(QStringLiteral("default"))
33{
30{
34−}
31+ InputDispatcherFilter::instance()->registerPointer(this);
35
32
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
33+ auto mouseMovedFunc = [this]() {
37− Qt::KeyboardModifiers modifiers)
38−{
39− if (!parentItem()) {
40− return;
34+ if (!isEnabled() || !window()) return;
41− }
35+ QPointF globalPosition = mapToItem(nullptr, QPointF(0, 0));
42−
36+ InputDispatcherFilter::instance()->setPosition(globalPosition);
43− if (!movement.isNull()) {
44 Q_EMIT mouseMoved();
37 Q_EMIT mouseMoved();
45− }
38+ };
46−
39+ connect(this, &QQuickItem::xChanged, this, mouseMovedFunc);
47− m_accumulatedMovement += movement;
40+ connect(this, &QQuickItem::yChanged, this, mouseMovedFunc);
48− // don't apply the fractional part
41+
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
42+ connect(this, &QQuickItem::enabledChanged, this, [this]() {
50− m_accumulatedMovement -= appliedMovement;
43+ if (!isEnabled()) setVisible(false);
51−
44+ });
52− qreal newX = x() + appliedMovement.x();
45+
53− qreal newY = y() + appliedMovement.y();
46+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
54− const qreal sceneWidth = parentItem()->width();
47+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
55− const qreal sceneHeight = parentItem()->height();
48+ if (window() && window()->screen() == screen) {
56−
49+ Q_EMIT pushedLeftBoundary(amount, buttons);
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;
86 }
50 }
87− }
51+ });
88
52
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+}
90
60
91− setX(qBound(0.0, newX, sceneWidth - 1));
61+MousePointer::~MousePointer()
92− setY(qBound(0.0, newY, sceneHeight - 1));
62+{
63+ registerScreen(nullptr);
64+ InputDispatcherFilter::instance()->unregisterPointer(this);
65+}
93
66
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
67+void MousePointer::handleMouseEvent(ulong /*timestamp*/, QPointF /*movement*/, Qt::MouseButtons /*buttons*/,
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
96− buttons, modifiers);
68+ Qt::KeyboardModifiers /*modifiers*/)
69+{
97}
70}
98
71
99void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
72void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,15 +94,8 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121 }
94 }
122}
95}
123
96
124−void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
97+void MousePointer::handleWheelEvent(ulong /*timestamp*/, QPoint /*angleDelta*/, Qt::KeyboardModifiers /*modifiers*/)
125{
98{
126− if (!parentItem()) {
127− return;
128− }
129−
130− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
131− QWindowSystemInterface::handleWheelEvent(window(), timestamp, scenePosition /* local */, scenePosition /* global */,
132− QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
133}
99}
134
100
135int MousePointer::topBoundaryOffset() const
101int MousePointer::topBoundaryOffset() const
@@ -182,7 +148,7 @@ void MousePointer::registerScreen(QScreen *screen)
182 if (m_registeredScreen) {
148 if (m_registeredScreen) {
183 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
149 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184 if (previousCursor) {
150 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
151+ previousCursor->unregisterMousePointer(this);
186 } else {
152 } else {
187 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
153 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188 }
154 }
@@ -193,7 +159,7 @@ void MousePointer::registerScreen(QScreen *screen)
193 if (m_registeredScreen) {
159 if (m_registeredScreen) {
194 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
160 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195 if (cursor) {
161 if (cursor) {
196− cursor->setMousePointer(this);
162+ cursor->registerMousePointer(this);
197 } else {
163 } else {
198 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
164 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199 }
165 }
@@ -233,3 +199,4 @@ void MousePointer::setConfiningItem(QQuickItem *item)
233 Q_EMIT confiningItemChanged();
199 Q_EMIT confiningItemChanged();
234 }
200 }
235}
201}
202+
plugins/Cursor/MousePointer.h+4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
32public:
32public:
33 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
34
35
35 void setCursorName(const QString &qtCursorName) override;
36 void setCursorName(const QString &qtCursorName) override;
36 QString cursorName() const override { return m_cursorName; }
37 QString cursorName() const override { return m_cursorName; }
@@ -46,6 +47,8 @@ public:
46 int topBoundaryOffset() const;
47 int topBoundaryOffset() const;
47 void setTopBoundaryOffset(int topBoundaryOffset);
48 void setTopBoundaryOffset(int topBoundaryOffset);
48
49
50+ QScreen* screen() const { return m_registeredScreen; }
51+
49public Q_SLOTS:
52public Q_SLOTS:
50 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
53 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
51 Qt::KeyboardModifiers modifiers) override;
54 Qt::KeyboardModifiers modifiers) override;
@@ -90,3 +93,4 @@ private:
90};
93};
91
94
92#endif // MOUSEPOINTER_H
95#endif // MOUSEPOINTER_H
96+
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
22void UInputPlugin::registerTypes(const char *uri)
28void UInputPlugin::registerTypes(const char *uri)
23{
29{
24 Q_ASSERT(uri == QLatin1String("UInput"));
30 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
32}
qml/Components/VirtualTouchPad.qml+13−14
@@ -24,10 +24,9 @@ import "../Components"
24
24
25Item {
25Item {
26 id: root
26 id: root
27− property var uinput: UInput {
27+
28− Component.onCompleted: createMouse();
28+ Component.onCompleted: UInput.createMouse()
29− Component.onDestruction: removeMouse();
29+ Component.onDestruction: UInput.removeMouse()
30− }
31
30
32 Component.onCompleted: {
31 Component.onCompleted: {
33 if (!settings.touchpadTutorialHasRun) {
32 if (!settings.touchpadTutorialHasRun) {
@@ -85,7 +84,7 @@ Item {
85 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
84 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
86 && clickTimer.running) {
85 && clickTimer.running) {
87 clickTimer.stop();
86 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
87+ UInput.pressMouse(UInput.ButtonLeft)
89 isDoubleClick = true;
88 isDoubleClick = true;
90 }
89 }
91 isClick = true;
90 isClick = true;
@@ -104,7 +103,7 @@ Item {
104
103
105 onReleased: {
104 onReleased: {
106 if (isDoubleClick || isDrag) {
105 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
106+ UInput.releaseMouse(UInput.ButtonLeft)
108 isDoubleClick = false;
107 isDoubleClick = false;
109 }
108 }
110 if (isClick) {
109 if (isClick) {
@@ -120,8 +119,8 @@ Item {
120 interval: 200
119 interval: 200
121 property int button: UInput.ButtonLeft
120 property int button: UInput.ButtonLeft
122 onTriggered: {
121 onTriggered: {
123− uinput.pressMouse(button);
122+ UInput.pressMouse(button);
124− uinput.releaseMouse(button);
123+ UInput.releaseMouse(button);
125 }
124 }
126 function scheduleClick(button) {
125 function scheduleClick(button) {
127 clickTimer.button = button;
126 clickTimer.button = button;
@@ -138,7 +137,7 @@ Item {
138 isDrag = true;
137 isDrag = true;
139 }
138 }
140
139
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
140+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142 }
141 }
143
142
144 function scroll(touchPoints) {
143 function scroll(touchPoints) {
@@ -166,7 +165,7 @@ Item {
166 dh /= 2;
165 dh /= 2;
167 dv /= 2;
166 dv /= 2;
168
167
169− uinput.scrollMouse(dh, dv);
168+ UInput.scrollMouse(dh, dv);
170 }
169 }
171
170
172 touchPoints: [
171 touchPoints: [
@@ -189,8 +188,8 @@ Item {
189 objectName: "leftButton"
188 objectName: "leftButton"
190 Layout.fillWidth: true
189 Layout.fillWidth: true
191 Layout.fillHeight: true
190 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
191+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
192+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194 property bool highlight: false
193 property bool highlight: false
195 UbuntuShape {
194 UbuntuShape {
196 anchors.fill: parent
195 anchors.fill: parent
@@ -204,8 +203,8 @@ Item {
204 objectName: "rightButton"
203 objectName: "rightButton"
205 Layout.fillWidth: true
204 Layout.fillWidth: true
206 Layout.fillHeight: true
205 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
206+ onPressed: UInput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
207+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209 property bool highlight: false
208 property bool highlight: false
210 UbuntuShape {
209 UbuntuShape {
211 anchors.fill: parent
210 anchors.fill: parent
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
25import Unity.Session 0.1
25import Unity.Session 0.1
26
26
27import "." 0.1
27import "." 0.1
28+import ".." 0.1
28import "../Components"
29import "../Components"
29
30
30Showable {
31Showable {
@@ -234,7 +235,7 @@ Showable {
234 if (forcedUnlock && shown) {
235 if (forcedUnlock && shown) {
235 hideView();
236 hideView();
236 if (hideNow) {
237 if (hideNow) {
237− root.hideNow(); // skip hide animation
238+ ShellNotifier.greeter.hide(true); // skip hide animation
238 }
239 }
239 }
240 }
240 }
241 }
@@ -397,7 +398,7 @@ Showable {
397 onEmergencyCall: root.emergencyCall()
398 onEmergencyCall: root.emergencyCall()
398 onRequiredChanged: {
399 onRequiredChanged: {
399 if (!loader.item.required) {
400 if (!loader.item.required) {
400− root.hide();
401+ ShellNotifier.greeter.hide(false);
401 }
402 }
402 }
403 }
403 }
404 }
@@ -529,12 +530,29 @@ Showable {
529 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
530 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
530 }
531 }
531
532
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+
532 Connections {
550 Connections {
533 target: DBusUnitySessionService
551 target: DBusUnitySessionService
534 onLockRequested: root.forceShow()
552 onLockRequested: root.forceShow()
535 onUnlocked: {
553 onUnlocked: {
536 root.forcedUnlock = true;
554 root.forcedUnlock = true;
537− root.hideNow();
555+ ShellNotifier.greeter.hide(true);
538 }
556 }
539 }
557 }
540
558
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 notifyAboutToFocusApp: (function(appId) { return; })
37+ property var notifyAppFocused: (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/Shell.qml+15−15
@@ -249,7 +249,6 @@ StyledItem {
249 var mappedCoords = mapFromItem(null, pos.x, pos.y);
249 var mappedCoords = mapFromItem(null, pos.x, pos.y);
250 cursor.x = mappedCoords.x;
250 cursor.x = mappedCoords.x;
251 cursor.y = mappedCoords.y;
251 cursor.y = mappedCoords.y;
252− cursor.mouseNeverMoved = false;
253 }
252 }
254 }
253 }
255
254
@@ -333,8 +332,13 @@ StyledItem {
333 objectName: "greeterLoader"
332 objectName: "greeterLoader"
334 anchors.fill: parent
333 anchors.fill: parent
335 anchors.topMargin: panel.panelHeight
334 anchors.topMargin: panel.panelHeight
336− sourceComponent: shell.mode != "shell" ? integratedGreeter :
335+ sourceComponent: {
336+ if (shell.mode != "shell") {
337+ if (screenWindow.primary) return integratedGreeter;
338+ return secondaryGreeter;
339+ }
337− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
340+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
341+ }
338 onLoaded: {
342 onLoaded: {
339 item.objectName = "greeter"
343 item.objectName = "greeter"
340 }
344 }
@@ -374,6 +378,13 @@ StyledItem {
374 }
378 }
375 }
379 }
376
380
381+ Component {
382+ id: secondaryGreeter
383+ SecondaryGreeter {
384+ hides: [launcher, panel.indicators]
385+ }
386+ }
387+
377 Timer {
388 Timer {
378 // See powerConnection for why this is useful
389 // See powerConnection for why this is useful
379 id: showGreeterDelayed
390 id: showGreeterDelayed
@@ -689,23 +700,13 @@ StyledItem {
689
700
690 Cursor {
701 Cursor {
691 id: cursor
702 id: cursor
692− objectName: "cursor"
693 visible: shell.hasMouse
703 visible: shell.hasMouse
694 z: itemGrabber.z + 1
704 z: itemGrabber.z + 1
705+ opacity: 0
695 topBoundaryOffset: panel.panelHeight
706 topBoundaryOffset: panel.panelHeight
696
707
697 confiningItem: stage.itemConfiningMouseCursor
708 confiningItem: stage.itemConfiningMouseCursor
698
709
699− property bool mouseNeverMoved: true
700− Binding {
701− target: cursor; property: "x"; value: shell.width / 2
702− when: cursor.mouseNeverMoved && cursor.visible
703− }
704− Binding {
705− target: cursor; property: "y"; value: shell.height / 2
706− when: cursor.mouseNeverMoved && cursor.visible
707− }
708−
709 height: units.gu(3)
710 height: units.gu(3)
710
711
711 readonly property var previewRectangle: stage.previewRectangle.target &&
712 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -760,7 +761,6 @@ StyledItem {
760 }
761 }
761
762
762 onMouseMoved: {
763 onMouseMoved: {
763− mouseNeverMoved = false;
764 cursor.opacity = 1;
764 cursor.opacity = 1;
765 }
765 }
766
766
qml/ShellNotifier.qmlnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+/*
18+ Shared signals & properties on multi-window desktop
19+ */
20+
21+pragma Singleton
22+import QtQuick 2.4
23+
24+QtObject {
25+ property var greeter: QtObject {
26+ signal show()
27+ signal hide(bool now)
28+
29+ property bool shown: false
30+ }
31+}
qml/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ShellNotifier 0.1 ShellNotifier.qml
tests/mocks/Cursor/Cursor.qml+50−2
@@ -15,8 +15,11 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
18
22
19−Item {
20 property int topBoundaryOffset // effectively panel height
23 property int topBoundaryOffset // effectively panel height
21 property Item confiningItem
24 property Item confiningItem
22
25
@@ -30,5 +33,50 @@ Item {
30 signal pushStopped()
33 signal pushStopped()
31 signal mouseMoved()
34 signal mouseMoved()
32
35
36+ width: units.gu(2)
37+ height: units.gu(2)
38+ antialiasing: true
39+
40+ onPaint: {
41+ var ctx = getContext("2d");
42+ ctx.save();
43+ ctx.clearRect(0,0,width, height);
44+ ctx.strokeStyle = "#000000";
45+ ctx.lineWidth = 1
46+ ctx.fillStyle = "#ffffff";
47+ ctx.globalAlpha = 1.0;
48+ ctx.lineJoin = "round";
49+ ctx.beginPath();
50+
51+ // put rectangle in the middle
52+ // draw the rectangle
53+ ctx.moveTo(width/2,height);
54+ ctx.lineTo(width, height/2);
55+ ctx.lineTo(0,0);
56+
57+ ctx.closePath();
58+ ctx.fill();
59+ ctx.stroke();
60+ ctx.restore();
61+ }
62+
63+ Connections {
64+ target: UInput
33− onMouseMoved: opacity = 1;
65+ onMouseMoved: {
66+ var newX = root.x;
67+ newX += dx;
68+ if (newX < 0) newX = 0;
69+ else if (newX >= parent.width) newX = parent.width-1;
70+
71+ var newY = root.y;
72+ newY += dy;
73+ if (newY < 0) newY = 0;
74+ else if (newY >= parent.height) newY = parent.height-1;
75+
76+ root.x = newX;
77+ root.y = newY;
78+ root.mouseMoved();
79+ }
80+ }
34}
81}
82+
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
18#include "fake_gsettings.h"
18#include "fake_gsettings.h"
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
21
23
22static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
24static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23{
25{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
31 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
33 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
32 "GSettingsSchema can only be used inside of a GSettings component");
34 "GSettingsSchema can only be used inside of a GSettings component");
33}
35}
36+
37+void FakeGSettingsQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
38+{
39+ QQmlExtensionPlugin::initializeEngine(engine, uri);
40+
41+ QString usageType = qgetenv("UNITY_MOCK_DESKTOP");
42+ if (!usageType.isEmpty()) {
43+ GSettingsControllerQml::instance()->setUsageMode("Windowed");
44+ }
45+}
tests/mocks/GSettings.1.0/plugin.h+1
@@ -26,6 +26,7 @@ class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
26
26
27public:
27public:
28 void registerTypes(const char *uri) override;
28 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
29};
30};
30
31
31#endif // PLUGIN_H
32#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
22void MockUInputPlugin::registerTypes(const char *uri)
27void MockUInputPlugin::registerTypes(const char *uri)
23{
28{
24 Q_ASSERT(uri == QLatin1String("UInput"));
29 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
31}
tests/mocks/Unity/InputInfo/mockcontroller.cpp+6
@@ -24,6 +24,12 @@ MockController::MockController(QObject *parent):
24
24
25}
25}
26
26
27+MockController *MockController::instance()
28+{
29+ static MockController* controller = new MockController;
30+ return controller;
31+}
32+
27QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
33QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
28{
34{
29 return QInputDeviceManagerPrivate::instance()->addMockDevice(devicePath, type);
35 return QInputDeviceManagerPrivate::instance()->addMockDevice(devicePath, type);
tests/mocks/Unity/InputInfo/mockcontroller.h+2
@@ -27,6 +27,8 @@ public:
27 MockController(QObject *parent = 0);
27 MockController(QObject *parent = 0);
28 ~MockController() = default;
28 ~MockController() = default;
29
29
30+ static MockController *instance();
31+
30 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
32 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
31 Q_INVOKABLE void removeDevice(const QString &devicePath);
33 Q_INVOKABLE void removeDevice(const QString &devicePath);
32};
34};
tests/mocks/Unity/InputInfo/plugin.cpp+11−1
@@ -28,7 +28,7 @@ static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
28{
28{
29 Q_UNUSED(engine)
29 Q_UNUSED(engine)
30 Q_UNUSED(scriptEngine)
30 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
31+ return MockController::instance();
32}
32}
33
33
34void InputInfoPlugin::registerTypes(const char *uri)
34void InputInfoPlugin::registerTypes(const char *uri)
@@ -44,3 +44,13 @@ void InputInfoPlugin::registerTypes(const char *uri)
44 // MockController
44 // MockController
45 qmlRegisterSingletonType<MockController>(uri, major, minor, "MockInputDeviceBackend", backendProvider);
45 qmlRegisterSingletonType<MockController>(uri, major, minor, "MockInputDeviceBackend", backendProvider);
46}
46}
47+
48+void InputInfoPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
49+{
50+ QQmlExtensionPlugin::initializeEngine(engine, uri);
51+
52+ if (qEnvironmentVariableIsSet("UNITY_MOCK_DESKTOP")) {
53+ MockController::instance()->addMockDevice("/mouse0", QInputDevice::Mouse);
54+ MockController::instance()->addMockDevice("/kbd0", QInputDevice::Keyboard);
55+ }
56+}
tests/mocks/Unity/InputInfo/plugin.h+1
@@ -25,6 +25,7 @@ class InputInfoPlugin : public QQmlExtensionPlugin
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26public:
26public:
27 void registerTypes(const char *uri) override;
27 void registerTypes(const char *uri) override;
28+ void initializeEngine(QQmlEngine *engine, const char *uri);
28};
29};
29
30
30#endif // INPUTINFO_PLUGIN_H
31#endif // INPUTINFO_PLUGIN_H
tests/mocks/Unity/Screens/CMakeLists.txt+2−1
@@ -5,10 +5,11 @@ include_directories(
5set(MockScreens_SOURCES
5set(MockScreens_SOURCES
6 plugin.cpp
6 plugin.cpp
7 screens.cpp
7 screens.cpp
8+ screenwindow.cpp
8)
9)
9
10
10add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
11add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
11
12
12−qt5_use_modules(MockScreensPlugin Gui Qml)
13+qt5_use_modules(MockScreensPlugin Gui Qml Quick)
13
14
14add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
15add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
tests/mocks/Unity/Screens/plugin.cpp+3
@@ -16,6 +16,7 @@
16
16
17#include "plugin.h"
17#include "plugin.h"
18#include "screens.h"
18#include "screens.h"
19+#include "screenwindow.h"
19
20
20#include <QScreen>
21#include <QScreen>
21
22
@@ -26,4 +27,6 @@ void UnityScreensPlugin::registerTypes(const char* uri)
26 qRegisterMetaType<QScreen*>("QScreen*");
27 qRegisterMetaType<QScreen*>("QScreen*");
27
28
28 qmlRegisterType<Screens>(uri, 0, 1, "Screens");
29 qmlRegisterType<Screens>(uri, 0, 1, "Screens");
30+ qmlRegisterType<ScreenWindow>(uri, 0, 1, "ScreenWindow");
31+ qmlRegisterRevision<QWindow,1>(uri, 0, 1);
29}
32}
tests/mocks/Unity/Screens/screens.cpp+6−2
@@ -26,8 +26,12 @@ Q_DECLARE_METATYPE(QScreen*)
26Screens::Screens(QObject *parent) :
26Screens::Screens(QObject *parent) :
27 QAbstractListModel(parent)
27 QAbstractListModel(parent)
28{
28{
29− // start with one screen attached
29+ bool ok = false;
30+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
31+ if (!ok) screenCount = 1;
32+ for (int i = 0; i < screenCount; ++i) {
30− m_screenList.append(new Screen());
33+ m_screenList.append(new Screen());
34+ }
31}
35}
32
36
33Screens::~Screens()
37Screens::~Screens()
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 {
39
39
40 SignalSpy {
40 SignalSpy {
41 id: mouseEventSpy1
41 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
43 }
43 }
44 SignalSpy {
44 SignalSpy {
45 id: mouseEventSpy2
45 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
47 }
47 }
48
48
49 UnityTestCase {
49 UnityTestCase {