on xenial help sign in
multiwindow cursor
commit5cf02ba40dc4eeed71670f8272d2ec2e3cd45ad6 authorNick Dedekind <nick.dedekind@canonical.com> dateThu 12 Jan 2017, 14:03 (10 years ago) parentb2c4c3a
7 files changed+296−88
plugins/Cursor/CMakeLists.txt+1 plugins/Cursor/InputDispatcherFilter.cpp+143 plugins/Cursor/InputDispatcherFilter.h+57 plugins/Cursor/MousePointer.cpp+40−73 plugins/Cursor/MousePointer.h+4 qml/Shell.qml+1−13 tests/mocks/Cursor/Cursor.qml+50−2
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+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 @@
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+
qml/Shell.qml+1−13
@@ -249,7 +249,6 @@ StyledItem {
249249 var mappedCoords = mapFromItem(null, pos.x, pos.y);
250250 cursor.x = mappedCoords.x;
251251 cursor.y = mappedCoords.y;
252− cursor.mouseNeverMoved = false;
253252 }
254253 }
255254
@@ -689,23 +688,13 @@ StyledItem {
689688
690689 Cursor {
691690 id: cursor
692− objectName: "cursor"
693691 visible: shell.hasMouse
694692 z: itemGrabber.z + 1
693+ opacity: 0
695694 topBoundaryOffset: panel.panelHeight
696695
697696 confiningItem: stage.itemConfiningMouseCursor
698697
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−
709698 height: units.gu(3)
710699
711700 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -760,7 +749,6 @@ StyledItem {
760749 }
761750
762751 onMouseMoved: {
763− mouseNeverMoved = false;
764752 cursor.opacity = 1;
765753 }
766754
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+