Merge lp:~aacid/unity-2d/unity-2d_pointer_barrier into lp:unity-2d

Proposed by Albert Astals Cid
Status: Merged
Approved by: Gerry Boland
Approved revision: 974
Merged at revision: 963
Proposed branch: lp:~aacid/unity-2d/unity-2d_pointer_barrier
Merge into: lp:unity-2d
Diff against target: 1524 lines (+1187/-52)
22 files modified
CMakeLists.txt (+4/-0)
data/com.canonical.Unity2d.gschema.xml (+38/-0)
debian/control (+1/-0)
libunity-2d-private/CMakeLists.txt (+1/-0)
libunity-2d-private/Unity2d/plugin.cpp (+4/-0)
libunity-2d-private/src/CMakeLists.txt (+5/-0)
libunity-2d-private/src/decayedvalue.cpp (+65/-0)
libunity-2d-private/src/decayedvalue.h (+45/-0)
libunity-2d-private/src/pointerbarrier.cpp (+361/-0)
libunity-2d-private/src/pointerbarrier.h (+154/-0)
libunity-2d-private/src/pointerbarriermanager.cpp (+84/-0)
libunity-2d-private/src/pointerbarriermanager.h (+44/-0)
libunity-2d-private/tests/CMakeLists.txt (+4/-0)
libunity-2d-private/tests/pointerbarriertest.cpp (+312/-0)
shell/Shell.qml (+11/-18)
shell/common/visibilityBehaviors/AutoHideBehavior.qml (+6/-12)
shell/common/visibilityBehaviors/IntelliHideBehavior.qml (+5/-5)
shell/launcher/Launcher.qml (+23/-1)
shell/launcher/LauncherLoader.qml (+3/-13)
tests/launcher/autohide_show_tests.rb (+6/-0)
tests/launcher/autohide_show_tests_common.rb (+5/-3)
tests/launcher/autohide_show_tests_rtl.rb (+6/-0)
To merge this branch: bzr merge lp:~aacid/unity-2d/unity-2d_pointer_barrier
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Review via email: mp+94983@code.launchpad.net

Description of the change

Implementation of Pointer barriers and use in the launcher autohide behaviour

To post a comment you must log in.
Revision history for this message
Albert Astals Cid (aacid) wrote :

There is the issue that it crashes when run inside of VirtualBox, it crashes because the XFixesBarrierNotifyEvent->xany.display is set to 0 (happens only inside the VirtualBox VM)

Revision history for this message
Gerry Boland (gerboland) wrote :

Setup: 2 monitors, and shell in RTL mode (so launcher is on right of monitor 1, which borders monitor 2)

Steps to repro:
1. On screen 1 I push mouse against barrier, so launcher reveals
2. I push a bit harder so mouse passes through barrier to screen 2

Bug: Launcher stays open.

I also notice you drop the barrier while the launcher is open. Then for the above bug, there's no barrier stopping the mouse passing from screen 2 to screen 1.

Yes this bug is a bit "multi-monitor" :)

review: Needs Fixing (functional)
Revision history for this message
Gerry Boland (gerboland) wrote :

Removing intellihide, you'll need to remove reference to it from com.canonical.Unity2d.gschema.xml. Also will we need a way to migrate existing intellihide settings to autohide/fixed?

Revision history for this message
Gerry Boland (gerboland) wrote :

Checking with Unity, I noticed 3 differences:
1. barrier extends along whole edge of screen, including panel
2. pushing the bit of the barrier that meets the panel does *not* reveal launcher
3. as you push launcher barrier, a small shadow appears which indicates you should push harder to get launcher to open.

Revision history for this message
Gerry Boland (gerboland) wrote :

Also in RTL multi-monitor setup described above (https://code.launchpad.net/~aacid/unity-2d/unity-2d_pointer_barrier/+merge/94983/comments/205455)

If I push the left edge of screen 2 (which borders the launcher on screen 1), it makes the launcher reveal.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Bug: for hide-mode: 0, no barrier in effect at all
hide-mode 1,2: while launcher visible, no barrier in effect at all

Revision history for this message
Gerry Boland (gerboland) wrote :

Some minor code style comments (possibly my code to start with!):

In PointerBarrierWrapper::createBarrier(), please add braces around statements like:

+ if (!m_enabled)
+ return;

Can you add a debug comment explaining that barrier must be horizontal/vertical here:
+ if ((m_p1.x() != m_p2.x()) && (m_p1.y() != m_p2.y()))
+ return;

In PointerBarrierWrapper::decay(), your indentation is wrong, 2 spaces instead of the usual 4.

You should add yourself as an author to pointerbarrier.h

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Functionally this is perfect, nice!

Code-wise, I can find no problems with the math anywhere. There is a little more going on that I had expected, but I can see no way to avoid everything you've done. So again, nice job!

Just some names I think could be clearer:
- "breakp1" could simply be "p1" and so on.
- "trigger" more understandable with "triggerZone" maybe?
- DecayedValue::add maybe clearer with DecayedValue::addAndCheckExceedingTarget since it returns an important bool.

I also want to see more tests of this barrier. Could you write a couple of unit tests, which run under a fake X server (xvfb) - as all tests in libunity-2d-private/tests do right now. We'd need to verify that the barrier works inside xvfb first however.

You can use XTest to move the mouse around. The source code of xdotool is a nice place to see it being used.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Works great, and the simplification helps too. Test is good, I'm approving.
Thank you Albert!

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote :

Holding off approval until FFe obtained

973. By Albert Astals Cid

Merge lp:unity-2d

974. By Albert Astals Cid

Fix docu

Revision history for this message
Gerry Boland (gerboland) wrote :

FFe obtained, can now merge when tarmac instance happy again.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2012-03-01 17:42:03 +0000
3+++ CMakeLists.txt 2012-03-05 14:20:25 +0000
4@@ -45,6 +45,10 @@
5 pkg_check_modules(PANGO REQUIRED pango)
6 pkg_check_modules(DCONFQT REQUIRED dconf-qt)
7
8+# X11_XTest_FOUND is defined by find_package(X11 REQUIRED)
9+if (NOT X11_XTest_FOUND)
10+ message(SEND_ERROR "Xtest library not found")
11+endif (NOT X11_XTest_FOUND)
12
13 # GSettings schemas
14 pkg_check_modules(GLIB REQUIRED glib-2.0)
15
16=== modified file 'data/com.canonical.Unity2d.gschema.xml'
17--- data/com.canonical.Unity2d.gschema.xml 2012-02-13 14:59:11 +0000
18+++ data/com.canonical.Unity2d.gschema.xml 2012-03-05 14:20:25 +0000
19@@ -31,6 +31,44 @@
20 2: intellihide; same as auto hide but the launcher will disappear if a window is placed on top of it
21 </description>
22 </key>
23+ <key name="edge-responsiveness" type="d">
24+ <default>2</default>
25+ <summary>Responsiveness of the Launcher</summary>
26+ <description>How quickly the Launcher will reveal when you push the pointer against the
27+ monitor edge
28+ </description>
29+ </key>
30+ <key name="edge-decayrate" type="i">
31+ <default>1500</default>
32+ <summary>Decay rate of mouse pressure against barrier</summary>
33+ <description>The pressures against the monitor edge are continually added up, and when the sum
34+ hits a certain threshold the launcher reveals. To prevent a series of distinct gentle pushes
35+ of the pointer against the barrier from causing the launcher to reveal, we substract this
36+ parameter - the decay rate - at a steady rate
37+ </description>
38+ </key>
39+ <key name="edge-reveal-pressure" type="i">
40+ <default>2000</default>
41+ <summary>Launcher reveal edge pressure</summary>
42+ <description>The minimum pressure to press the pointer against the monitor edge to
43+ cause the launcher to reveal
44+ </description>
45+ </key>
46+ <key name="edge-stop-velocity" type="i">
47+ <default>6500</default>
48+ <summary>Barrier edge stop velocity</summary>
49+ <description>The minimum velocity the pointer needs to travel to pass through the barrier without
50+ any resistance. Only relevant for multi-monitor setups
51+ </description>
52+ </key>
53+ <key name="edge-overcome-pressure" type="i">
54+ <default>2000</default>
55+ <summary>Barrier edge overcome pressure
56+ </summary>
57+ <description>Minimum pressure the pointer needs to exert on the barrier for the barrier to drop.
58+ Only relevant for multi-monitor setups
59+ </description>
60+ </key>
61 </schema>
62 <schema path="/com/canonical/unity-2d/panel/" id="com.canonical.Unity2d.Panel" gettext-domain="unity-2d">
63 <key type="as" name="applets">
64
65=== modified file 'debian/control'
66--- debian/control 2012-02-17 13:14:40 +0000
67+++ debian/control 2012-03-05 14:20:25 +0000
68@@ -25,6 +25,7 @@
69 libunity-core-5.0-dev (>= 5.2.0),
70 libnux-2.0-dev (>= 2.4),
71 libxi-dev,
72+ libxtst-dev,
73 Standards-Version: 3.9.2
74 Vcs-Bzr: https://code.launchpad.net/~unity-2d-team/unity-2d/trunk
75
76
77=== modified file 'libunity-2d-private/CMakeLists.txt'
78--- libunity-2d-private/CMakeLists.txt 2011-12-08 19:00:51 +0000
79+++ libunity-2d-private/CMakeLists.txt 2012-03-05 14:20:25 +0000
80@@ -11,6 +11,7 @@
81 pkg_check_modules(DEE REQUIRED dee-1.0)
82 pkg_check_modules(XINPUT REQUIRED xi)
83 pkg_check_modules(GEIS REQUIRED libutouch-geis)
84+pkg_check_modules(XFIXES REQUIRED xfixes)
85
86 set(libunity-2d-private_SOVERSION 0)
87 set(libunity-2d-private_VERSION ${libunity-2d-private_SOVERSION}.0.0)
88
89=== modified file 'libunity-2d-private/Unity2d/plugin.cpp'
90--- libunity-2d-private/Unity2d/plugin.cpp 2012-02-28 12:30:17 +0000
91+++ libunity-2d-private/Unity2d/plugin.cpp 2012-03-05 14:20:25 +0000
92@@ -80,6 +80,8 @@
93 #include "unity2dpanel.h"
94 #include "strutmanager.h"
95
96+#include "pointerbarrier.h"
97+
98 #include <QtDeclarative/qdeclarative.h>
99 #include <QDeclarativeEngine>
100 #include <QDeclarativeContext>
101@@ -183,6 +185,8 @@
102
103 qmlRegisterType<Unity2dPanel>(uri, 0, 1, "Unity2dPanel");
104 qmlRegisterType<StrutManager>(uri, 0, 1, "StrutManager");
105+
106+ qmlRegisterType<PointerBarrierWrapper>(uri, 0, 1, "PointerBarrier");
107 }
108
109 void Unity2dPlugin::initializeEngine(QDeclarativeEngine *engine, const char *uri)
110
111=== modified file 'libunity-2d-private/src/CMakeLists.txt'
112--- libunity-2d-private/src/CMakeLists.txt 2012-02-28 12:30:17 +0000
113+++ libunity-2d-private/src/CMakeLists.txt 2012-03-05 14:20:25 +0000
114@@ -78,6 +78,9 @@
115 inputshaperectangle.cpp
116 inputshapemask.cpp
117 strutmanager.cpp
118+ pointerbarrier.cpp
119+ pointerbarriermanager.cpp
120+ decayedvalue.cpp
121 )
122
123 # Build
124@@ -106,6 +109,7 @@
125 ${DEE_INCLUDE_DIRS}
126 ${XINPUT_INCLUDE_DIRS}
127 ${GEIS_INCLUDE_DIRS}
128+ ${XFIXES_INCLUDE_DIRS}
129 )
130
131 add_library(${LIB_NAME} SHARED ${libunity-2d-private_SRCS} listmodelwrapper.h)
132@@ -142,6 +146,7 @@
133 ${DEE_LDFLAGS}
134 ${XINPUT_LDFLAGS}
135 ${GEIS_LDFLAGS}
136+ ${XFIXES_LDFLAGS}
137 )
138
139 # Install
140
141=== added file 'libunity-2d-private/src/decayedvalue.cpp'
142--- libunity-2d-private/src/decayedvalue.cpp 1970-01-01 00:00:00 +0000
143+++ libunity-2d-private/src/decayedvalue.cpp 2012-03-05 14:20:25 +0000
144@@ -0,0 +1,65 @@
145+/*
146+ * Copyright (C) 2012 Canonical, Ltd.
147+ *
148+ * This program is free software; you can redistribute it and/or modify
149+ * it under the terms of the GNU General Public License as published by
150+ * the Free Software Foundation; version 3.
151+ *
152+ * This program is distributed in the hope that it will be useful,
153+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
154+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
155+ * GNU General Public License for more details.
156+ *
157+ * You should have received a copy of the GNU General Public License
158+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
159+ */
160+
161+#include "decayedvalue.h"
162+
163+DecayedValue::DecayedValue()
164+ : m_value(0)
165+ , m_target(0)
166+ , m_decayRate(0)
167+{
168+ m_valueDecayTimer.setInterval(10);
169+ connect(&m_valueDecayTimer, SIGNAL(timeout()), this, SLOT(decay()));
170+}
171+
172+bool DecayedValue::addAndCheckExceedingTarget(int i)
173+{
174+ m_value += i;
175+ if (!m_valueDecayTimer.isActive()) {
176+ m_valueDecayTimer.start();
177+ }
178+ if (m_value > m_target) {
179+ m_value = 0;
180+ m_valueDecayTimer.stop();
181+ return true;
182+ } else {
183+ return false;
184+ }
185+}
186+
187+void DecayedValue::setDecayRate(int decayRate)
188+{
189+ m_decayRate = decayRate;
190+}
191+
192+void DecayedValue::setTarget(int target)
193+{
194+ m_target = target;
195+}
196+
197+void DecayedValue::decay() {
198+ const int partial_decay = m_decayRate / 100;
199+
200+ m_value -= partial_decay;
201+
202+ if (m_value <= 0)
203+ {
204+ m_value = 0;
205+ m_valueDecayTimer.stop();
206+ }
207+}
208+
209+#include "decayedvalue.moc"
210\ No newline at end of file
211
212=== added file 'libunity-2d-private/src/decayedvalue.h'
213--- libunity-2d-private/src/decayedvalue.h 1970-01-01 00:00:00 +0000
214+++ libunity-2d-private/src/decayedvalue.h 2012-03-05 14:20:25 +0000
215@@ -0,0 +1,45 @@
216+/*
217+ * Copyright (C) 2012 Canonical, Ltd.
218+ *
219+ * This program is free software; you can redistribute it and/or modify
220+ * it under the terms of the GNU General Public License as published by
221+ * the Free Software Foundation; version 3.
222+ *
223+ * This program is distributed in the hope that it will be useful,
224+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
225+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
226+ * GNU General Public License for more details.
227+ *
228+ * You should have received a copy of the GNU General Public License
229+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
230+ */
231+
232+#ifndef DECAYEDVALUE_H
233+#define DECAYEDVALUE_H
234+
235+#include <QObject>
236+
237+#include <QTimer>
238+
239+class DecayedValue : public QObject
240+{
241+ Q_OBJECT
242+public:
243+ DecayedValue();
244+
245+ bool addAndCheckExceedingTarget(int i);
246+
247+ void setDecayRate(int decayRate);
248+ void setTarget(int target);
249+
250+private Q_SLOTS:
251+ void decay();
252+
253+private:
254+ int m_value;
255+ int m_target;
256+ int m_decayRate;
257+ QTimer m_valueDecayTimer;
258+};
259+
260+#endif // DECAYEDVALUE_H
261
262=== added file 'libunity-2d-private/src/pointerbarrier.cpp'
263--- libunity-2d-private/src/pointerbarrier.cpp 1970-01-01 00:00:00 +0000
264+++ libunity-2d-private/src/pointerbarrier.cpp 2012-03-05 14:20:25 +0000
265@@ -0,0 +1,361 @@
266+/*
267+ * Copyright (C) 2012 Canonical, Ltd.
268+ *
269+ * This program is free software; you can redistribute it and/or modify
270+ * it under the terms of the GNU General Public License as published by
271+ * the Free Software Foundation; version 3.
272+ *
273+ * This program is distributed in the hope that it will be useful,
274+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
275+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
276+ * GNU General Public License for more details.
277+ *
278+ * You should have received a copy of the GNU General Public License
279+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
280+ */
281+
282+// Qt
283+#include <QDebug>
284+#include <QTimer>
285+#include <QX11Info>
286+
287+// libunity-2d
288+#include "pointerbarriermanager.h"
289+
290+// Self
291+#include "pointerbarrier.h"
292+
293+PointerBarrierWrapper::PointerBarrierWrapper(QObject *parent)
294+ : QObject(parent)
295+ , m_barrier(0)
296+ , m_triggerDirection(TriggerFromAnywhere)
297+ , m_triggerZoneEnabled(false)
298+ , m_threshold(-1)
299+ , m_maxVelocityMultiplier(-1)
300+ , m_decayRate(-1)
301+ , m_triggerPressure(-1)
302+ , m_breakPressure(-1)
303+ , m_smoothingTimer(new QTimer(this))
304+ , m_lastEventX(0)
305+ , m_lastEventY(0)
306+ , m_lastEventId(0)
307+ , m_smoothingCount(0)
308+ , m_smoothingAccumulator(0)
309+{
310+ m_smoothingTimer->setSingleShot(true);
311+ m_smoothingTimer->setInterval(75);
312+ connect(m_smoothingTimer, SIGNAL(timeout()), this, SLOT(smoother()));
313+
314+ PointerBarrierManager::instance()->addBarrier(this);
315+}
316+
317+PointerBarrierWrapper::~PointerBarrierWrapper()
318+{
319+ PointerBarrierManager::instance()->removeBarrier(this);
320+ destroyBarrier();
321+}
322+
323+QPointF PointerBarrierWrapper::p1() const
324+{
325+ return m_p1;
326+}
327+
328+void PointerBarrierWrapper::setP1(const QPointF& p)
329+{
330+ if (p != m_p1) {
331+ if (m_barrier != 0) {
332+ destroyBarrier();
333+ }
334+
335+ m_p1 = p;
336+ Q_EMIT p1Changed(p);
337+
338+ createBarrier();
339+ }
340+}
341+
342+QPointF PointerBarrierWrapper::p2() const
343+{
344+ return m_p2;
345+}
346+
347+void PointerBarrierWrapper::setP2(const QPointF& p)
348+{
349+ if (p != m_p2) {
350+ if (m_barrier != 0) {
351+ destroyBarrier();
352+ }
353+
354+ m_p2 = p;
355+ Q_EMIT p2Changed(p);
356+
357+ createBarrier();
358+ }
359+}
360+
361+QPointF PointerBarrierWrapper::triggerZoneP1() const
362+{
363+ return m_triggerZoneP1;
364+}
365+
366+void PointerBarrierWrapper::setTriggerZoneP1(const QPointF& p)
367+{
368+ if (p != m_triggerZoneP1) {
369+ m_triggerZoneP1 = p;
370+ Q_EMIT triggerZoneP1Changed(p);
371+
372+ handleTriggerZoneChanged();
373+ }
374+}
375+
376+QPointF PointerBarrierWrapper::triggerZoneP2() const
377+{
378+ return m_triggerZoneP2;
379+}
380+
381+void PointerBarrierWrapper::setTriggerZoneP2(const QPointF& p)
382+{
383+ if (p != m_triggerZoneP2) {
384+ m_triggerZoneP2 = p;
385+ Q_EMIT triggerZoneP2Changed(p);
386+
387+ handleTriggerZoneChanged();
388+ }
389+}
390+
391+PointerBarrierWrapper::TriggerDirection PointerBarrierWrapper::triggerDirection() const
392+{
393+ return m_triggerDirection;
394+}
395+
396+void PointerBarrierWrapper::setTriggerDirection(TriggerDirection direction)
397+{
398+ if (direction != m_triggerDirection) {
399+ m_triggerDirection = direction;
400+ Q_EMIT triggerDirectionChanged(direction);
401+ }
402+}
403+
404+bool PointerBarrierWrapper::triggerZoneEnabled() const
405+{
406+ return m_triggerZoneEnabled;
407+}
408+
409+void PointerBarrierWrapper::setTriggerZoneEnabled(bool enabled)
410+{
411+ if (m_triggerZoneEnabled != enabled) {
412+ m_triggerZoneEnabled = enabled;
413+ Q_EMIT triggerZoneEnabledChanged(enabled);
414+
415+ handleTriggerZoneChanged();
416+ }
417+}
418+
419+void PointerBarrierWrapper::createBarrier()
420+{
421+ if (m_threshold < 0) {
422+ return;
423+ }
424+
425+ if (!isPointAlignmentCorrect()) {
426+ return;
427+ }
428+
429+ Display *display = QX11Info::display();
430+
431+ m_barrier = XFixesCreatePointerBarrierVelocity(display,
432+ DefaultRootWindow(display),
433+ m_p1.x(), m_p1.y(),
434+ m_p2.x(), m_p2.y(),
435+ 0,
436+ m_threshold,
437+ 0,
438+ NULL);
439+ Q_ASSERT(m_barrier != 0);
440+}
441+
442+void PointerBarrierWrapper::destroyBarrier()
443+{
444+ if (m_barrier != 0) {
445+ XFixesDestroyPointerBarrier(QX11Info::display(), m_barrier);
446+ m_barrier = 0;
447+ }
448+}
449+
450+void PointerBarrierWrapper::doProcess(XFixesBarrierNotifyEvent *notifyEvent)
451+{
452+ m_lastEventX = notifyEvent->x;
453+ m_lastEventY = notifyEvent->y;
454+ m_lastEventId = notifyEvent->event_id;
455+ m_smoothingAccumulator += notifyEvent->velocity;
456+ m_smoothingCount++;
457+
458+ /* Gathers events for m_smoothingTimer->interval() miliseconds, then takes average */
459+ if (!m_smoothingTimer->isActive()) {
460+ m_smoothingTimer->start();
461+ }
462+}
463+
464+int PointerBarrierWrapper::threshold() const
465+{
466+ return m_threshold;
467+}
468+
469+void PointerBarrierWrapper::setThreshold(int threshold)
470+{
471+ if (m_threshold != threshold) {
472+ m_threshold = threshold;
473+ destroyBarrier();
474+ createBarrier();
475+ Q_EMIT thresholdChanged(threshold);
476+ }
477+}
478+
479+qreal PointerBarrierWrapper::maxVelocityMultiplier() const
480+{
481+ return m_maxVelocityMultiplier;
482+}
483+
484+void PointerBarrierWrapper::setMaxVelocityMultiplier(qreal maxVelocityMultiplier)
485+{
486+ if (maxVelocityMultiplier != m_maxVelocityMultiplier) {
487+ m_maxVelocityMultiplier = maxVelocityMultiplier;
488+ Q_EMIT maxVelocityMultiplierChanged(maxVelocityMultiplier);
489+
490+ updateRealDecayTargetPressures();
491+ }
492+}
493+
494+int PointerBarrierWrapper::decayRate() const
495+{
496+ return m_decayRate;
497+}
498+
499+void PointerBarrierWrapper::setDecayRate(int decayRate)
500+{
501+ if (decayRate != m_decayRate) {
502+ m_decayRate = decayRate;
503+ Q_EMIT decayRateChanged(decayRate);
504+
505+ updateRealDecayTargetPressures();
506+ }
507+}
508+
509+int PointerBarrierWrapper::triggerPressure() const
510+{
511+ return m_triggerPressure;
512+}
513+
514+void PointerBarrierWrapper::setTriggerPressure(int pressure)
515+{
516+ if (m_triggerPressure != pressure) {
517+ m_triggerPressure = pressure;
518+ Q_EMIT triggerPressureChanged(pressure);
519+
520+ updateRealDecayTargetPressures();
521+ }
522+}
523+
524+int PointerBarrierWrapper::breakPressure() const
525+{
526+ return m_breakPressure;
527+}
528+
529+void PointerBarrierWrapper::setBreakPressure(int breakPressure)
530+{
531+ if (m_breakPressure != breakPressure) {
532+ m_breakPressure = breakPressure;
533+ Q_EMIT breakPressureChanged(breakPressure);
534+
535+ updateRealDecayTargetPressures();
536+ }
537+}
538+
539+PointerBarrier PointerBarrierWrapper::barrier() const
540+{
541+ return m_barrier;
542+}
543+
544+void PointerBarrierWrapper::smoother()
545+{
546+ if (m_maxVelocityMultiplier < 0 || m_decayRate < 0 || m_breakPressure < 0) {
547+ qWarning() << "PointerBarrierWrapper::smoother: maxVelocityMultiplier, decayRate or breakPressure not set";
548+ return;
549+ }
550+
551+ if (m_smoothingCount <= 0) {
552+ return;
553+ }
554+ const int velocity = qMin<qreal>(600 * m_maxVelocityMultiplier, m_smoothingAccumulator / m_smoothingCount);
555+
556+ bool againstTrigger = false;
557+ if (m_triggerZoneEnabled && m_triggerZoneP1.x() == m_triggerZoneP2.x() && m_triggerZoneP1.y() <= m_lastEventY && m_triggerZoneP2.y() >= m_lastEventY) {
558+ againstTrigger = m_triggerDirection == TriggerFromAnywhere ||
559+ (m_triggerDirection == TriggerFromRight && m_lastEventX >= m_triggerZoneP1.x()) ||
560+ (m_triggerDirection == TriggerFromLeft && m_lastEventX < m_triggerZoneP1.x());
561+ }
562+ if (m_triggerZoneEnabled && m_triggerZoneP1.y() == m_triggerZoneP2.y() && m_triggerZoneP1.x() <= m_lastEventX && m_triggerZoneP2.x() >= m_lastEventX) {
563+ againstTrigger = m_triggerDirection == TriggerFromAnywhere ||
564+ (m_triggerDirection == TriggerFromTop && m_lastEventY >= m_triggerZoneP1.y()) ||
565+ (m_triggerDirection == TriggerFromBottom && m_lastEventY < m_triggerZoneP1.y());
566+ }
567+ if (againstTrigger) {
568+ if (m_triggerValue.addAndCheckExceedingTarget(velocity)) {
569+ Q_EMIT triggered();
570+ }
571+ } else {
572+ if (m_breakValue.addAndCheckExceedingTarget(velocity)) {
573+ Display *display = QX11Info::display();
574+ XFixesBarrierReleasePointer (display, m_barrier, m_lastEventId);
575+
576+ Q_EMIT broken();
577+ }
578+ }
579+
580+ m_smoothingAccumulator = 0;
581+ m_smoothingCount = 0;
582+}
583+
584+void PointerBarrierWrapper::updateRealDecayTargetPressures()
585+{
586+ // make the effect half as strong as specified as other values shouldn't scale
587+ // as quickly as the max velocity multiplier
588+ const float responsiveness_mult = ((m_maxVelocityMultiplier - 1) * .025) + 1;
589+ const int realDecayRate = m_decayRate * responsiveness_mult;
590+ m_triggerValue.setDecayRate(realDecayRate);
591+ m_breakValue.setDecayRate(realDecayRate);
592+ m_triggerValue.setTarget(m_triggerPressure * responsiveness_mult);
593+ m_breakValue.setTarget(m_breakPressure * responsiveness_mult);
594+}
595+
596+void PointerBarrierWrapper::handleTriggerZoneChanged()
597+{
598+ // Make sure barrier point alignment is still valid
599+ if (!isPointAlignmentCorrect()) {
600+ destroyBarrier();
601+ }
602+ // If there is no barrier try to create one now
603+ if (m_barrier == 0) {
604+ createBarrier();
605+ }
606+}
607+
608+bool PointerBarrierWrapper::isPointAlignmentCorrect() const
609+{
610+ bool alignmentCorrect = false;
611+
612+ // Outer points can't be the same
613+ if (m_p1 != m_p2) {
614+ // The two points need to be aligned either vertically or horizontally
615+ if (m_p1.x() == m_p2.x()) {
616+ alignmentCorrect = !m_triggerZoneEnabled || (m_triggerZoneP1.x() == m_p1.x() && m_triggerZoneP2.x() == m_p1.x());
617+ } else if (m_p1.y() == m_p2.y()) {
618+ alignmentCorrect = !m_triggerZoneEnabled || (m_triggerZoneP1.y() == m_p1.y() && m_triggerZoneP2.y() == m_p1.y());
619+ }
620+ }
621+
622+ return alignmentCorrect;
623+
624+}
625+
626+#include <pointerbarrier.moc>
627
628=== added file 'libunity-2d-private/src/pointerbarrier.h'
629--- libunity-2d-private/src/pointerbarrier.h 1970-01-01 00:00:00 +0000
630+++ libunity-2d-private/src/pointerbarrier.h 2012-03-05 14:20:25 +0000
631@@ -0,0 +1,154 @@
632+/*
633+ * Copyright (C) 2012 Canonical, Ltd.
634+ *
635+ * This program is free software; you can redistribute it and/or modify
636+ * it under the terms of the GNU General Public License as published by
637+ * the Free Software Foundation; version 3.
638+ *
639+ * This program is distributed in the hope that it will be useful,
640+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
641+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
642+ * GNU General Public License for more details.
643+ *
644+ * You should have received a copy of the GNU General Public License
645+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
646+ */
647+
648+#ifndef POINTERBARRIER_H
649+#define POINTERBARRIER_H
650+
651+#include <QObject>
652+#include <QPointF>
653+
654+// X11
655+#include <X11/extensions/Xfixes.h>
656+
657+#include "decayedvalue.h"
658+
659+struct PointerBarrierWrapperPrivate;
660+
661+class PointerBarrierWrapper : public QObject
662+{
663+ Q_OBJECT
664+ Q_PROPERTY(QPointF p1 READ p1 WRITE setP1 NOTIFY p1Changed)
665+ Q_PROPERTY(QPointF p2 READ p2 WRITE setP2 NOTIFY p2Changed)
666+ Q_PROPERTY(QPointF triggerZoneP1 READ triggerZoneP1 WRITE setTriggerZoneP1 NOTIFY triggerZoneP1Changed)
667+ Q_PROPERTY(QPointF triggerZoneP2 READ triggerZoneP2 WRITE setTriggerZoneP2 NOTIFY triggerZoneP2Changed)
668+ Q_PROPERTY(TriggerDirection triggerDirection READ triggerDirection WRITE setTriggerDirection NOTIFY triggerDirectionChanged)
669+ Q_PROPERTY(bool triggerZoneEnabled READ triggerZoneEnabled WRITE setTriggerZoneEnabled NOTIFY triggerZoneEnabledChanged)
670+ Q_PROPERTY(int threshold READ threshold WRITE setThreshold NOTIFY thresholdChanged)
671+ Q_PROPERTY(int maxVelocityMultiplier READ maxVelocityMultiplier WRITE setMaxVelocityMultiplier NOTIFY maxVelocityMultiplierChanged)
672+ Q_PROPERTY(int decayRate READ decayRate WRITE setDecayRate NOTIFY decayRateChanged)
673+ Q_PROPERTY(int triggerPressure READ triggerPressure WRITE setTriggerPressure NOTIFY triggerPressureChanged)
674+ Q_PROPERTY(int breakPressure READ breakPressure WRITE setBreakPressure NOTIFY breakPressureChanged)
675+
676+friend class PointerBarrierManager;
677+
678+public:
679+ enum TriggerDirection {
680+ TriggerFromAnywhere,
681+ TriggerFromRight,
682+ TriggerFromLeft,
683+ TriggerFromTop,
684+ TriggerFromBottom
685+ };
686+ Q_ENUMS(TriggerDirection)
687+
688+ PointerBarrierWrapper(QObject* parent = 0);
689+ ~PointerBarrierWrapper();
690+
691+ QPointF p1() const;
692+ void setP1(const QPointF &p);
693+
694+ QPointF p2() const;
695+ void setP2(const QPointF &p);
696+
697+ QPointF triggerZoneP1() const;
698+ void setTriggerZoneP1(const QPointF &p);
699+
700+ QPointF triggerZoneP2() const;
701+ void setTriggerZoneP2(const QPointF &p);
702+
703+ TriggerDirection triggerDirection() const;
704+ void setTriggerDirection(TriggerDirection direction);
705+
706+ bool triggerZoneEnabled() const;
707+ void setTriggerZoneEnabled(bool enabled);
708+
709+ int threshold() const;
710+ void setThreshold(int threshold);
711+
712+ qreal maxVelocityMultiplier() const;
713+ void setMaxVelocityMultiplier(qreal maxVelocityMultiplier);
714+
715+ int decayRate() const;
716+ void setDecayRate(int decayRate);
717+
718+ int triggerPressure() const;
719+ void setTriggerPressure(int pressure);
720+
721+ int breakPressure() const;
722+ void setBreakPressure(int pressure);
723+
724+ PointerBarrier barrier() const;
725+
726+Q_SIGNALS:
727+ void p1Changed(const QPointF &p1);
728+ void p2Changed(const QPointF &p2);
729+ void triggerZoneP1Changed(const QPointF &p1);
730+ void triggerZoneP2Changed(const QPointF &p2);
731+ void triggerDirectionChanged(TriggerDirection direction);
732+ void triggerZoneEnabledChanged(bool changed);
733+ void thresholdChanged(int threshold);
734+ void maxVelocityMultiplierChanged(qreal maxVelocityMultiplier);
735+ void decayRateChanged(int decayRate);
736+ void triggerPressureChanged(int breakPressure);
737+ void breakPressureChanged(int breakPressure);
738+
739+ void triggered();
740+ void broken();
741+
742+private Q_SLOTS:
743+ void smoother();
744+
745+private:
746+ Q_DISABLE_COPY(PointerBarrierWrapper);
747+
748+ void createBarrier();
749+ void destroyBarrier();
750+
751+ void doProcess(XFixesBarrierNotifyEvent *event);
752+
753+ void updateRealDecayTargetPressures();
754+
755+ void handleTriggerZoneChanged();
756+
757+ bool isPointAlignmentCorrect() const;
758+
759+ PointerBarrier m_barrier;
760+
761+ QPointF m_p1;
762+ QPointF m_p2;
763+ QPointF m_triggerZoneP1;
764+ QPointF m_triggerZoneP2;
765+ TriggerDirection m_triggerDirection;
766+ bool m_triggerZoneEnabled;
767+ int m_threshold;
768+ qreal m_maxVelocityMultiplier;
769+ int m_decayRate;
770+ int m_triggerPressure;
771+ int m_breakPressure;
772+
773+ QTimer *m_smoothingTimer;
774+
775+ int m_lastEventX;
776+ int m_lastEventY;
777+ int m_lastEventId;
778+ int m_smoothingCount;
779+ int m_smoothingAccumulator;
780+
781+ DecayedValue m_triggerValue;
782+ DecayedValue m_breakValue;
783+};
784+
785+#endif // POINTERBARRIER_H
786
787=== added file 'libunity-2d-private/src/pointerbarriermanager.cpp'
788--- libunity-2d-private/src/pointerbarriermanager.cpp 1970-01-01 00:00:00 +0000
789+++ libunity-2d-private/src/pointerbarriermanager.cpp 2012-03-05 14:20:25 +0000
790@@ -0,0 +1,84 @@
791+/*
792+ * Copyright (C) 2012 Canonical, Ltd.
793+ *
794+ * This program is free software; you can redistribute it and/or modify
795+ * it under the terms of the GNU General Public License as published by
796+ * the Free Software Foundation; version 3.
797+ *
798+ * This program is distributed in the hope that it will be useful,
799+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
800+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
801+ * GNU General Public License for more details.
802+ *
803+ * You should have received a copy of the GNU General Public License
804+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
805+ */
806+
807+// Special ordering to bypass evil X11 include
808+#include <QDebug>
809+
810+// Self
811+#include "pointerbarriermanager.h"
812+
813+// libunity-2d
814+#include "pointerbarrier.h"
815+
816+// Qt
817+#include <QX11Info>
818+
819+// X
820+#include <X11/extensions/Xfixes.h>
821+
822+PointerBarrierManager *PointerBarrierManager::instance()
823+{
824+ static PointerBarrierManager *bpm = NULL;
825+ if (bpm == NULL) bpm = new PointerBarrierManager();
826+ return bpm;
827+}
828+
829+PointerBarrierManager::PointerBarrierManager()
830+{
831+ Display *display = QX11Info::display();
832+
833+ XFixesQueryExtension(display, &m_eventBase, &m_errorBase);
834+
835+ Unity2dApplication* application = Unity2dApplication::instance();
836+ if (application == NULL) {
837+ /* This can happen for example when using qmlviewer to run the launcher */
838+ qWarning() << "The application is not an Unity2dApplication."
839+ "Barriers will not be monitored.";
840+ } else {
841+ application->installX11EventFilter(this);
842+ }
843+
844+ /* Enables barrier detection events - only call once!! */
845+ XFixesSelectBarrierInput(display, DefaultRootWindow(display), 0xdeadbeef);
846+}
847+
848+void PointerBarrierManager::addBarrier(PointerBarrierWrapper *barrier)
849+{
850+ m_barriers += barrier;
851+}
852+
853+void PointerBarrierManager::removeBarrier(PointerBarrierWrapper *barrier)
854+{
855+ m_barriers -= barrier;
856+}
857+
858+bool PointerBarrierManager::x11EventFilter(XEvent* event)
859+{
860+ if (event->type - m_eventBase == XFixesBarrierNotify) {
861+ XFixesBarrierNotifyEvent *notifyEvent = (XFixesBarrierNotifyEvent *)event;
862+
863+ if (notifyEvent->subtype == XFixesBarrierHitNotify) {
864+ Q_FOREACH (PointerBarrierWrapper *barrier, m_barriers) {
865+ if (barrier->barrier() == notifyEvent->barrier) {
866+ barrier->doProcess(notifyEvent);
867+ return true;
868+ }
869+ }
870+ }
871+ }
872+ return false;
873+
874+}
875
876=== added file 'libunity-2d-private/src/pointerbarriermanager.h'
877--- libunity-2d-private/src/pointerbarriermanager.h 1970-01-01 00:00:00 +0000
878+++ libunity-2d-private/src/pointerbarriermanager.h 2012-03-05 14:20:25 +0000
879@@ -0,0 +1,44 @@
880+/*
881+ * Copyright (C) 2012 Canonical, Ltd.
882+ *
883+ * This program is free software; you can redistribute it and/or modify
884+ * it under the terms of the GNU General Public License as published by
885+ * the Free Software Foundation; version 3.
886+ *
887+ * This program is distributed in the hope that it will be useful,
888+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
889+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
890+ * GNU General Public License for more details.
891+ *
892+ * You should have received a copy of the GNU General Public License
893+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
894+ */
895+
896+#ifndef POINTERBARRIERMANAGER_H
897+#define POINTERBARRIERMANAGER_H
898+
899+#include "unity2dapplication.h"
900+
901+class PointerBarrierWrapper;
902+
903+class PointerBarrierManager : protected AbstractX11EventFilter
904+{
905+public:
906+ static PointerBarrierManager *instance();
907+
908+ void addBarrier(PointerBarrierWrapper *barrier);
909+ void removeBarrier(PointerBarrierWrapper *barrier);
910+
911+protected:
912+ bool x11EventFilter(XEvent* event);
913+
914+private:
915+ Q_DISABLE_COPY(PointerBarrierManager);
916+
917+ PointerBarrierManager();
918+ QSet<PointerBarrierWrapper*> m_barriers;
919+ int m_eventBase;
920+ int m_errorBase;
921+};
922+
923+#endif // POINTERBARRIERMANAGER_H
924
925=== modified file 'libunity-2d-private/tests/CMakeLists.txt'
926--- libunity-2d-private/tests/CMakeLists.txt 2012-01-24 08:38:24 +0000
927+++ libunity-2d-private/tests/CMakeLists.txt 2012-03-05 14:20:25 +0000
928@@ -8,6 +8,7 @@
929 ${CMAKE_CURRENT_BINARY_DIR}
930 ${GLIB_INCLUDE_DIRS}
931 ${QT_QTTEST_INCLUDE_DIR}
932+ ${X11_XTest_INCLUDE_PATH}
933 )
934
935 set(LIBUNITY_2D_TEST_DIR ${libunity-2d-private_BINARY_DIR}/tests)
936@@ -37,7 +38,10 @@
937 listaggregatormodeltest
938 qsortfilterproxymodeltest
939 focuspathtest
940+ pointerbarriertest
941 )
942+
943+target_link_libraries(pointerbarriertest ${X11_XTest_LIB})
944
945 # unity2dtrtest - FIXME
946 #add_test(NAME unity2dtrtest_check
947
948=== added file 'libunity-2d-private/tests/pointerbarriertest.cpp'
949--- libunity-2d-private/tests/pointerbarriertest.cpp 1970-01-01 00:00:00 +0000
950+++ libunity-2d-private/tests/pointerbarriertest.cpp 2012-03-05 14:20:25 +0000
951@@ -0,0 +1,312 @@
952+/*
953+ * This file is part of unity-2d
954+ *
955+ * Copyright 2010 Canonical Ltd.
956+ *
957+ * Authors:
958+ * - Aurélien Gâteau <aurelien.gateau@canonical.com>
959+ *
960+ * This program is free software; you can redistribute it and/or modify
961+ * it under the terms of the GNU General Public License as published by
962+ * the Free Software Foundation; version 3.
963+ *
964+ * This program is distributed in the hope that it will be useful,
965+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
966+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
967+ * GNU General Public License for more details.
968+ *
969+ * You should have received a copy of the GNU General Public License
970+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
971+ */
972+
973+// Local
974+#include <unitytestmacro.h>
975+#include <pointerbarrier.h>
976+
977+// Qt
978+#include <QApplication>
979+#include <QSignalSpy>
980+#include <QX11Info>
981+#include <QtTestGui>
982+
983+#include <X11/extensions/XTest.h>
984+
985+class DisableTriggerZoneOnTriggerHelper : public QObject
986+{
987+ Q_OBJECT
988+public:
989+ DisableTriggerZoneOnTriggerHelper(PointerBarrierWrapper *barrier)
990+ {
991+ m_barrier = barrier;
992+ connect(barrier, SIGNAL(triggered()), this, SLOT(disable()));
993+ }
994+
995+public Q_SLOTS:
996+ void disable()
997+ {
998+ m_barrier->setTriggerZoneEnabled(false);
999+ }
1000+
1001+private:
1002+ PointerBarrierWrapper *m_barrier;
1003+};
1004+
1005+class PointerBarrierTest : public QObject
1006+{
1007+ Q_OBJECT
1008+private Q_SLOTS:
1009+ void testBreak()
1010+ {
1011+ Display *display = QX11Info::display();
1012+ PointerBarrierWrapper barrier;
1013+
1014+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1015+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1016+
1017+ XTestFakeMotionEvent(display, -1, 50, 50, 0);
1018+ QCOMPARE(QCursor::pos(), QPoint(50, 50));
1019+
1020+ barrier.setP1(QPointF(100, 0));
1021+ barrier.setP2(QPointF(100, 100));
1022+ barrier.setThreshold(6500);
1023+ barrier.setMaxVelocityMultiplier(2);
1024+ barrier.setDecayRate(1500);
1025+ barrier.setBreakPressure(2000);
1026+
1027+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1028+ // We are stopped by the barrier and instead in 350, 50 we are in 99, 50
1029+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1030+
1031+ QCOMPARE(brokenSpy.count(), 0);
1032+ QCOMPARE(triggeredSpy.count(), 0);
1033+
1034+ for (int i = 0; i < 10; ++i) {
1035+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1036+ QTest::qWait(100);
1037+ }
1038+ // We have broken the barrier and are somewhere else
1039+ QVERIFY(QCursor::pos() != QPoint(99, 50));
1040+ QCOMPARE(brokenSpy.count(), 1);
1041+ QCOMPARE(triggeredSpy.count(), 0);
1042+ }
1043+
1044+ void testStopArea()
1045+ {
1046+ Display *display = QX11Info::display();
1047+ PointerBarrierWrapper barrier;
1048+
1049+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1050+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1051+
1052+ XTestFakeMotionEvent(display, -1, 50, 150, 0);
1053+ QCOMPARE(QCursor::pos(), QPoint(50, 150));
1054+
1055+ barrier.setP1(QPointF(100, 0));
1056+ barrier.setP2(QPointF(100, 100));
1057+ barrier.setThreshold(6500);
1058+ barrier.setMaxVelocityMultiplier(2);
1059+ barrier.setDecayRate(1500);
1060+ barrier.setBreakPressure(2000);
1061+
1062+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1063+ // We are not stopped by the barrier because it's above us
1064+ // and are in 350, 150
1065+ QCOMPARE(QCursor::pos(), QPoint(350, 150));
1066+
1067+ QCOMPARE(brokenSpy.count(), 0);
1068+ QCOMPARE(triggeredSpy.count(), 0);
1069+ }
1070+
1071+ void testTrigger()
1072+ {
1073+ Display *display = QX11Info::display();
1074+ PointerBarrierWrapper barrier;
1075+
1076+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1077+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1078+
1079+ XTestFakeMotionEvent(display, -1, 50, 50, 0);
1080+ QCOMPARE(QCursor::pos(), QPoint(50, 50));
1081+
1082+ barrier.setP1(QPointF(100, 0));
1083+ barrier.setP2(QPointF(100, 100));
1084+ barrier.setTriggerZoneP1(QPointF(100, 0));
1085+ barrier.setTriggerZoneP2(QPointF(100, 100));
1086+ barrier.setTriggerZoneEnabled(true);
1087+ barrier.setThreshold(6500);
1088+ barrier.setMaxVelocityMultiplier(2);
1089+ barrier.setDecayRate(1500);
1090+ barrier.setTriggerPressure(2000);
1091+ barrier.setBreakPressure(2000);
1092+
1093+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1094+ // We are stopped by the barrier and instead in 350, 50 we are in 99, 50
1095+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1096+
1097+ QCOMPARE(brokenSpy.count(), 0);
1098+ QCOMPARE(triggeredSpy.count(), 0);
1099+
1100+ for (int i = 0; i < 10; ++i) {
1101+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1102+ QTest::qWait(100);
1103+ }
1104+ // We have triggered the barrier and are still there
1105+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1106+ QCOMPARE(brokenSpy.count(), 0);
1107+ QVERIFY(triggeredSpy.count() >= 1);
1108+ }
1109+
1110+ void testTriggerAndBreak()
1111+ {
1112+ Display *display = QX11Info::display();
1113+ PointerBarrierWrapper barrier;
1114+
1115+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1116+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1117+
1118+ XTestFakeMotionEvent(display, -1, 50, 50, 0);
1119+ QCOMPARE(QCursor::pos(), QPoint(50, 50));
1120+
1121+ barrier.setP1(QPointF(100, 0));
1122+ barrier.setP2(QPointF(100, 100));
1123+ barrier.setTriggerZoneP1(QPointF(100, 0));
1124+ barrier.setTriggerZoneP2(QPointF(100, 100));
1125+ barrier.setTriggerZoneEnabled(true);
1126+ barrier.setThreshold(6500);
1127+ barrier.setMaxVelocityMultiplier(2);
1128+ barrier.setDecayRate(1500);
1129+ barrier.setTriggerPressure(2000);
1130+ barrier.setBreakPressure(2000);
1131+
1132+ DisableTriggerZoneOnTriggerHelper helper(&barrier);
1133+
1134+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1135+ // We are stopped by the barrier and instead in 350, 50 we are in 99, 50
1136+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1137+
1138+ QCOMPARE(brokenSpy.count(), 0);
1139+ QCOMPARE(triggeredSpy.count(), 0);
1140+
1141+ for (int i = 0; i < 10; ++i) {
1142+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1143+ QTest::qWait(100);
1144+ }
1145+ // We have triggered and broken the barrier and somewhere else
1146+ QVERIFY(QCursor::pos() != QPoint(99, 50));
1147+ QCOMPARE(brokenSpy.count(), 1);
1148+ QCOMPARE(triggeredSpy.count(), 1);
1149+ }
1150+
1151+ void testTriggerWithoutAndBreak()
1152+ {
1153+ Display *display = QX11Info::display();
1154+ PointerBarrierWrapper barrier;
1155+
1156+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1157+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1158+
1159+ XTestFakeMotionEvent(display, -1, 50, 50, 0);
1160+ QCOMPARE(QCursor::pos(), QPoint(50, 50));
1161+
1162+ barrier.setP1(QPointF(100, 0));
1163+ barrier.setP2(QPointF(100, 100));
1164+ barrier.setTriggerZoneP1(QPointF(100, 0));
1165+ barrier.setTriggerZoneP2(QPointF(100, 100));
1166+ barrier.setTriggerZoneEnabled(true);
1167+ barrier.setThreshold(6500);
1168+ barrier.setMaxVelocityMultiplier(2);
1169+ barrier.setDecayRate(1500);
1170+ barrier.setTriggerPressure(2000);
1171+ barrier.setBreakPressure(2000);
1172+
1173+ DisableTriggerZoneOnTriggerHelper helper(&barrier);
1174+
1175+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1176+ // We are stopped by the barrier and instead in 350, 50 we are in 99, 50
1177+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1178+
1179+ QCOMPARE(brokenSpy.count(), 0);
1180+ QCOMPARE(triggeredSpy.count(), 0);
1181+
1182+ for (int i = 0; i < 10 && barrier.triggerZoneEnabled(); ++i) {
1183+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1184+ QTest::qWait(100);
1185+ }
1186+ // We have triggered the barrier
1187+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1188+ QCOMPARE(brokenSpy.count(), 0);
1189+ QCOMPARE(triggeredSpy.count(), 1);
1190+
1191+ // We can push a bit more without breaking the barrier
1192+ for (int i = 0; i < 2; ++i) {
1193+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1194+ QTest::qWait(100);
1195+ }
1196+ QCOMPARE(QCursor::pos(), QPoint(99, 50));
1197+ QCOMPARE(brokenSpy.count(), 0);
1198+ QCOMPARE(triggeredSpy.count(), 1);
1199+ }
1200+
1201+ void testTriggerAndBreakZones()
1202+ {
1203+ Display *display = QX11Info::display();
1204+ PointerBarrierWrapper barrier;
1205+
1206+ QSignalSpy brokenSpy(&barrier, SIGNAL(broken()));
1207+ QSignalSpy triggeredSpy(&barrier, SIGNAL(triggered()));
1208+
1209+ XTestFakeMotionEvent(display, -1, 50, 25, 0);
1210+ QCOMPARE(QCursor::pos(), QPoint(50, 25));
1211+
1212+ barrier.setP1(QPointF(100, 0));
1213+ barrier.setP2(QPointF(100, 200));
1214+ barrier.setTriggerZoneP1(QPointF(100, 50));
1215+ barrier.setTriggerZoneP2(QPointF(100, 150));
1216+ barrier.setTriggerZoneEnabled(true);
1217+ barrier.setThreshold(6500);
1218+ barrier.setMaxVelocityMultiplier(2);
1219+ barrier.setDecayRate(1500);
1220+ barrier.setTriggerPressure(2000);
1221+ barrier.setBreakPressure(2000);
1222+
1223+ DisableTriggerZoneOnTriggerHelper helper(&barrier);
1224+
1225+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1226+ // We are stopped by the barrier and instead in 350, 25 we are in 99, 25
1227+ QCOMPARE(QCursor::pos(), QPoint(99, 25));
1228+
1229+ QCOMPARE(brokenSpy.count(), 0);
1230+ QCOMPARE(triggeredSpy.count(), 0);
1231+
1232+ for (int i = 0; i < 10; ++i) {
1233+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1234+ QTest::qWait(100);
1235+ }
1236+
1237+ // We are above the trigger zone so we have only broke the barrier
1238+ QVERIFY(QCursor::pos() != QPoint(99, 25));
1239+ QCOMPARE(brokenSpy.count(), 1);
1240+ QCOMPARE(triggeredSpy.count(), 0);
1241+
1242+ // Go back
1243+ XTestFakeMotionEvent(display, -1, 50, 100, 0);
1244+
1245+ XTestFakeRelativeMotionEvent(display, 300, 0, 0);
1246+ // We are stopped by the barrier and instead in 350, 100 we are in 99, 100
1247+ QCOMPARE(QCursor::pos(), QPoint(99, 100));
1248+
1249+ for (int i = 0; i < 10; ++i) {
1250+ XTestFakeRelativeMotionEvent(display, 100, 0, 0);
1251+ QTest::qWait(100);
1252+ }
1253+
1254+ // We are in the trigger zone so we have triggered and broken the barrier
1255+ QVERIFY(QCursor::pos() != QPoint(99, 100));
1256+ QCOMPARE(brokenSpy.count(), 2);
1257+ QCOMPARE(triggeredSpy.count(), 1);
1258+ }
1259+};
1260+
1261+UAPP_TEST_MAIN(PointerBarrierTest)
1262+
1263+#include "pointerbarriertest.moc"
1264
1265=== modified file 'shell/Shell.qml'
1266--- shell/Shell.qml 2012-02-28 12:00:14 +0000
1267+++ shell/Shell.qml 2012-03-05 14:20:25 +0000
1268@@ -93,6 +93,13 @@
1269 if (dashLoader.status == Loader.Ready) dashLoader.item.deactivateAllLenses()
1270 }
1271 }
1272+ onGlobalPositionChanged: {
1273+ var x = declarativeView.globalPosition.x + (Utils.isLeftToRight() ? 0 : shell.width)
1274+ launcherLoader.item.barrierP1 = Qt.point(x, 0)
1275+ launcherLoader.item.barrierP2 = Qt.point(x, declarativeView.screen.geometry.height)
1276+ launcherLoader.item.barrierTriggerZoneP1 = Qt.point(x, declarativeView.globalPosition.y)
1277+ launcherLoader.item.barrierTriggerZoneP2 = Qt.point(x, declarativeView.globalPosition.y + launcherLoader.height)
1278+ }
1279 }
1280
1281 SpreadMonitor {
1282@@ -219,24 +226,10 @@
1283 Binding {
1284 target: launcherInputShape
1285 property: "rectangle"
1286- value: {
1287- // FIXME: this results in a 1px wide white rectangle on the launcher edge, we should switch
1288- // to cpp-based edge detection, and later XFixes barriers to get rid of that completely
1289- var somewhatShown = Utils.isLeftToRight() ? -launcherLoader.x < launcherLoader.width : launcherLoader.x < shell.width
1290- if (somewhatShown) {
1291- return Qt.rect(launcherLoader.x,
1292- launcherLoader.y,
1293- launcherLoader.width,
1294- launcherLoader.height)
1295- } else {
1296- // The outerEdgeMouseArea is one pixel bigger on each side so use it
1297- // when the launcher is hidden to have that extra pixel in the border
1298- return Qt.rect(launcherLoader.x + launcherLoader.outerEdgeMouseArea.x,
1299- launcherLoader.y,
1300- launcherLoader.outerEdgeMouseArea.width,
1301- launcherLoader.height)
1302- }
1303- }
1304+ value: Qt.rect(launcherLoader.x,
1305+ launcherLoader.y,
1306+ launcherLoader.width,
1307+ launcherLoader.height)
1308 when: !launcherLoaderXAnimation.running
1309 }
1310
1311
1312=== modified file 'shell/common/visibilityBehaviors/AutoHideBehavior.qml'
1313--- shell/common/visibilityBehaviors/AutoHideBehavior.qml 2012-03-02 13:48:01 +0000
1314+++ shell/common/visibilityBehaviors/AutoHideBehavior.qml 2012-03-05 14:20:25 +0000
1315@@ -18,13 +18,14 @@
1316
1317 import QtQuick 1.0
1318
1319-// Shows the target when it has the focus or when you move the
1320-// mouse for 500 msec to the edge of the target
1321+// Shows the target when it has the focus or when you trigger the pointer barrier
1322+// in the edge of the target
1323 // Hides the target when none of the above conditions are met
1324 // and you have not had the mouse over it during more than 1000 msec
1325-// To use this Behavior your target needs to provide two properties
1326+// To use this Behavior your target needs to provide one properties
1327 // - containsMouse: Defines if the mouse is inside the target
1328-// - outerEdgeContainsMouse: Defines if the mouse is in the edge of the target
1329+// and one signal
1330+// - barrierTriggered: Defines when the pointer barrier has been triggered
1331
1332 BaseBehavior {
1333 id: autoHide
1334@@ -51,12 +52,6 @@
1335 onTriggered: shownRegardlessOfFocus = false
1336 }
1337
1338- Timer {
1339- id: edgeHitTimer
1340- interval: 500
1341- onTriggered: shownRegardlessOfFocus = true
1342- }
1343-
1344 Connections {
1345 target: (autoHide.target !== undefined) ? autoHide.target : null
1346 onContainsMouseChanged: {
1347@@ -68,7 +63,6 @@
1348
1349 Connections {
1350 target: autoHide.target !== undefined ? autoHide.target : null
1351- onOuterEdgeContainsMouseChanged: edgeHitTimer.running = target.outerEdgeContainsMouse
1352- ignoreUnknownSignals: true
1353+ onBarrierTriggered: shownRegardlessOfFocus = true
1354 }
1355 }
1356
1357=== modified file 'shell/common/visibilityBehaviors/IntelliHideBehavior.qml'
1358--- shell/common/visibilityBehaviors/IntelliHideBehavior.qml 2012-03-02 13:48:01 +0000
1359+++ shell/common/visibilityBehaviors/IntelliHideBehavior.qml 2012-03-05 14:20:25 +0000
1360@@ -20,14 +20,14 @@
1361 import Unity2d 1.0
1362 import "../utils.js" as Utils
1363
1364-// Shows the target when it has the focus or when you move the
1365-// mouse for 500 msec to the edge of the target or there are no
1366-// windows that intersect with the target
1367+// Shows the target when it has the focus or when you trigger the pointer barrier
1368+// or there are no windows that intersect with the target
1369 // Hides the target when none of the above conditions are met
1370 // and you have not had the mouse over it during more than 1000 msec
1371-// To use this Behavior your target needs to provide two properties
1372+// To use this Behavior your target needs to provide one properties
1373 // - containsMouse: Defines if the mouse is inside the target
1374-// - outerEdgeContainsMouse: Defines if the mouse is in the edge of the target
1375+// and one signal
1376+// - barrierTriggered: Defines when the pointer barrier has been triggered
1377
1378 AutoHideBehavior {
1379 id: intellihide
1380
1381=== modified file 'shell/launcher/Launcher.qml'
1382--- shell/launcher/Launcher.qml 2012-02-27 10:50:43 +0000
1383+++ shell/launcher/Launcher.qml 2012-03-05 14:20:25 +0000
1384@@ -25,11 +25,33 @@
1385 id: launcher
1386 Accessible.name: "launcher"
1387
1388- property bool outerEdgeContainsMouse
1389+ signal barrierTriggered
1390+
1391 property bool shown
1392 property bool showMenus: true
1393
1394 property bool containsMouse: declarativeView.monitoredAreaContainsMouse
1395+ property variant barrierP1
1396+ property variant barrierP2
1397+ property variant barrierTriggerZoneP1
1398+ property variant barrierTriggerZoneP2
1399+
1400+ PointerBarrier {
1401+ id: barrier
1402+ triggerDirection: Utils.isLeftToRight() ? PointerBarrier.TriggerFromRight : PointerBarrier.TriggerFromLeft
1403+ triggerZoneEnabled: !shown
1404+ p1: barrierP1
1405+ p2: barrierP2
1406+ triggerZoneP1: barrierTriggerZoneP1
1407+ triggerZoneP2: barrierTriggerZoneP2
1408+ threshold: launcher2dConfiguration.edgeStopVelocity
1409+ maxVelocityMultiplier: launcher2dConfiguration.edgeResponsiveness
1410+ decayRate: launcher2dConfiguration.edgeDecayrate
1411+ triggerPressure: launcher2dConfiguration.edgeRevealPressure
1412+ breakPressure: launcher2dConfiguration.edgeOvercomePressure
1413+
1414+ onTriggered: launcher.barrierTriggered()
1415+ }
1416
1417 function hideMenu() {
1418 if (main.visibleMenu !== undefined) {
1419
1420=== modified file 'shell/launcher/LauncherLoader.qml'
1421--- shell/launcher/LauncherLoader.qml 2012-02-10 11:17:36 +0000
1422+++ shell/launcher/LauncherLoader.qml 2012-03-05 14:20:25 +0000
1423@@ -26,7 +26,6 @@
1424 source: "Launcher.qml"
1425 property variant visibilityController: visibilityController
1426 onLoaded: item.focus = true
1427- property alias outerEdgeMouseArea: outerEdge
1428
1429 VisibilityController {
1430 id: visibilityController
1431@@ -79,16 +78,7 @@
1432
1433 Binding {
1434 target: launcherLoader.item
1435- property: "outerEdgeContainsMouse"
1436- value: outerEdge.containsMouse && outerEdge.enabled
1437- }
1438-
1439- MouseArea {
1440- id: outerEdge
1441- anchors.fill: parent
1442- anchors.margins: -1
1443- hoverEnabled: !visibilityController.shown
1444- enabled: !visibilityController.shown
1445- }
1446-
1447+ property: "shown"
1448+ value: visibilityController.shown
1449+ }
1450 }
1451
1452=== modified file 'tests/launcher/autohide_show_tests.rb'
1453--- tests/launcher/autohide_show_tests.rb 2012-02-20 14:11:16 +0000
1454+++ tests/launcher/autohide_show_tests.rb 2012-03-05 14:20:25 +0000
1455@@ -55,6 +55,12 @@
1456 XDo::Mouse.move(0, 200, 0, true)
1457 end
1458
1459+ def mouse_push_screen_edge
1460+ (1..100).each do |i|
1461+ $SUT.execute_shell_command 'xdotool mousemove_relative -- -10 0'
1462+ end
1463+ end
1464+
1465 def move_mouse_to_launcher_inner_border()
1466 XDo::Mouse.move(LAUNCHER_WIDTH-1,200)
1467 end
1468
1469=== modified file 'tests/launcher/autohide_show_tests_common.rb'
1470--- tests/launcher/autohide_show_tests_common.rb 2012-02-20 16:44:00 +0000
1471+++ tests/launcher/autohide_show_tests_common.rb 2012-03-05 14:20:25 +0000
1472@@ -114,7 +114,7 @@
1473 # * Open application in position overlapping Launcher
1474 # * Verify Launcher hiding
1475 # * Move mouse to left of screen to reveal Launcher
1476-# * Verify Launcher shows but not immediately
1477+# * Verify Launcher shows only if we push the barrier
1478 # * Move mouse to the right, but still over the Launcher
1479 # * Verify Launcher still showing
1480 # * Move mouse further right to not overlap Launcher
1481@@ -128,8 +128,8 @@
1482 verify_launcher_hidden(TIMEOUT, 'Launcher visible with window in the way, should be hidden')
1483
1484 move_mouse_to_screen_edge()
1485- sleep 0.4
1486- verify_launcher_hidden(0, 'Launcher should not be visible immediately after mouse moves to the edge, has to wait 0.5 seconds to show')
1487+ verify_launcher_hidden(0, 'Launcher should not be visible immediately without pushing the edge')
1488+ mouse_push_screen_edge()
1489 verify_launcher_visible(TIMEOUT, 'Launcher hiding when mouse at edge of screen')
1490
1491 move_mouse_to_launcher_inner_border()
1492@@ -422,6 +422,7 @@
1493 if !tiles.empty?
1494 tile = tiles[0]
1495 move_mouse_to_screen_edge()
1496+ mouse_push_screen_edge()
1497 verify_launcher_visible(TIMEOUT, 'Launcher hiding when mouse at edge of screen, should be visible')
1498 tile.move_mouse()
1499 XDo::Mouse.click(nil, nil, :right)
1500@@ -457,6 +458,7 @@
1501
1502 bfb = @app.LauncherList( :name => 'main' ).LauncherList( :isBfb => true );
1503 move_mouse_to_screen_edge()
1504+ mouse_push_screen_edge()
1505 verify_launcher_visible(TIMEOUT, 'Launcher hiding when mouse at edge of screen, should be visible')
1506 bfb.move_mouse()
1507 bfb.tap()
1508
1509=== modified file 'tests/launcher/autohide_show_tests_rtl.rb'
1510--- tests/launcher/autohide_show_tests_rtl.rb 2012-02-20 14:11:16 +0000
1511+++ tests/launcher/autohide_show_tests_rtl.rb 2012-03-05 14:20:25 +0000
1512@@ -56,6 +56,12 @@
1513 XDo::Mouse.move(XDo::XWindow.display_geometry[0], 200, 0, true)
1514 end
1515
1516+ def mouse_push_screen_edge
1517+ (1..100).each do |i|
1518+ $SUT.execute_shell_command 'xdotool mousemove_relative 10 0'
1519+ end
1520+ end
1521+
1522 def move_mouse_to_launcher_inner_border()
1523 XDo::Mouse.move(XDo::XWindow.display_geometry[0] - LAUNCHER_WIDTH, 200)
1524 end

Subscribers

People subscribed via source and target branches