Merge lp:~samuel-thibault/compiz/ezoom_focus_tracking into lp:compiz/0.9.13

Proposed by Samuel thibault
Status: Needs review
Proposed branch: lp:~samuel-thibault/compiz/ezoom_focus_tracking
Merge into: lp:compiz/0.9.13
Diff against target: 2188 lines (+1695/-52)
21 files modified
debian/compiz-dev.install (+1/-0)
debian/compiz-plugins-default.install (+2/-0)
debian/control (+1/-0)
plugins/ezoom/CMakeLists.txt (+1/-1)
plugins/ezoom/ezoom.xml.in (+48/-11)
plugins/ezoom/src/ezoom.cpp (+196/-37)
plugins/ezoom/src/ezoom.h (+31/-3)
plugins/focuspoll/CMakeLists.txt (+9/-0)
plugins/focuspoll/compiz-focuspoll.pc.in (+12/-0)
plugins/focuspoll/focuspoll.xml.in (+29/-0)
plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h (+70/-0)
plugins/focuspoll/include/accessibilitywatcher/focusinfo.h (+71/-0)
plugins/focuspoll/include/focuspoll/focuspoll.h (+50/-0)
plugins/focuspoll/src/accessibilitywatcher.cpp (+732/-0)
plugins/focuspoll/src/focusinfo.cpp (+123/-0)
plugins/focuspoll/src/focuspoll.cpp (+225/-0)
plugins/focuspoll/src/private.h (+75/-0)
plugins/mousepoll/src/mousepoll.cpp (+14/-0)
plugins/mousepoll/src/private.h (+1/-0)
plugins/showmouse/showmouse.xml.in (+3/-0)
po/POTFILES.in (+1/-0)
To merge this branch: bzr merge lp:~samuel-thibault/compiz/ezoom_focus_tracking
Reviewer Review Type Date Requested Status
Compiz Maintainers Pending
Review via email: mp+345941@code.launchpad.net

Commit message

ezoom: add focus tracking based on at-spi a11y events.

This adds a focuspoll module which tracks focus events from a11y, used by ezoom concurrently to mouse events so the zoom moves nicely while working with the keyboard only.

Description of the change

Hello,

This is a resubmission of ksamak's work in https://code.launchpad.net/~ksamak/compiz/ezoom_focus_tracking, with (I believe) all raised comments addressed, notably:

- avoid the singleton technique
- avoid useless copies
- using smart pointers to avoid any memory leak
- better document at-spi workarounds

Samuel

To post a comment you must log in.
4174. By Samuel thibault

Fix corrupted iterator and explain why

4175. By Samuel thibault

merge from trunk

4176. By Samuel thibault

rework the translation correction to integrate it better and thus fix various translation oddities

4177. By Samuel thibault

Only warp mouse if it is out of zoomed screen

4178. By Samuel thibault

fix bogus unique method

4179. By Samuel thibault

Make event coordinates incorrect by default so they get ignored by default

4180. By Samuel thibault

Be more defensive against bogus event coordinates

4181. By Samuel thibault

Also make alternate coordinates bogus by default

4182. By Samuel thibault

Simplify source code

4183. By Samuel thibault

Avoid crashes in case the child went away in between

4184. By Samuel thibault

Automatically look at the first element of a table instead of the table

4185. By Samuel thibault

Also consider mouse clicks for its tracking delays

Otherwise when using both mouse and focus tracking tracking in ezoom, if one
clicks after a one-second wait and that produces a focus tracking event, the
mouse wraps there. The one-second delay should also apply here, which is a mere
matter of making mouse clicks be reported by mousepoll.

Revision history for this message
Alberts Muktupāvels (muktupavels) wrote :

You have unresolved conflict pushed. See POTFILES.in.

"This adds a focuspoll module which tracks focus events from a11y"
Are all changes here related to this? For example I see there are max value change that seems unrelated. Same with some description changes...

If these changes does not depend on focuspoll or are unreleated to adding focuspoll I would like to see these changes to be split out inside separate merge requests. Smaller changes are easier to review and could be merged without waiting for full review on this.

Revision history for this message
Samuel thibault (samuel-thibault) wrote :

The conflict is there simply since the very recent merging of my other i18n branch.

Yes, all changes are related to this, I moved what I could find out into other branches (the i18n branch is one of them).

The max value is actually related: with focus tracking, the current maximum zoom movement speed is not enough for some users which want to get the new position very quickly. The description changes are also due to the new possibility of the combination of mouse and focus tracking.

Really I believe I moved what I could: this contains only the addition of the focuspoll plugin (which is mere addition of code) and its integration in ezoom with the corresponding changes: option tuning, zoom computation etc.

4186. By Samuel thibault

merge from trunk

4187. By Samuel thibault

Add change lost during merge (?!)

4188. By Samuel thibault

Drop unused option

4189. By Samuel thibault

Add missing deletes

4190. By Samuel thibault

fix build

4191. By Samuel thibault

Avoid tracking non-expanded menu items

4192. By Samuel thibault

Ignore zeroed component areas

4193. By Samuel thibault

fix memleak of focus info

4194. By Samuel thibault

Extend workaround for missing coords at end of text

4195. By Samuel thibault

Fix crash in menus

4196. By Samuel thibault

Add missing piece of backport

4197. By Samuel thibault

fix build

4198. By Samuel thibault

Add missing delete

4199. By Samuel thibault

Add missing frees and dups

4200. By Samuel thibault

Do not duplicate constant strings

4201. By Samuel thibault

Better handle table focus

4202. By Samuel thibault

Guard against AT-SPI parent loops

4203. By Samuel thibault

Fix some missing references

Revision history for this message
Dmitry Shachnev (mitya57) wrote :

We have recently switched Compiz to Git. Can you please re-submit this (and other proposals if they are still actual) against https://git.launchpad.net/compiz ?

I don’t have much knowledge of the Compiz code yet, but I will do my best to review them.

Unmerged revisions

4203. By Samuel thibault

Fix some missing references

4202. By Samuel thibault

Guard against AT-SPI parent loops

4201. By Samuel thibault

Better handle table focus

4200. By Samuel thibault

Do not duplicate constant strings

4199. By Samuel thibault

Add missing frees and dups

4198. By Samuel thibault

Add missing delete

4197. By Samuel thibault

fix build

4196. By Samuel thibault

Add missing piece of backport

4195. By Samuel thibault

Fix crash in menus

4194. By Samuel thibault

Extend workaround for missing coords at end of text

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/compiz-dev.install'
2--- debian/compiz-dev.install 2016-07-14 15:21:59 +0000
3+++ debian/compiz-dev.install 2018-10-23 08:53:35 +0000
4@@ -7,6 +7,7 @@
5 debian/tmp/usr/lib/*/pkgconfig/compiz-composite.pc
6 debian/tmp/usr/lib/*/pkgconfig/compiz-cube.pc
7 debian/tmp/usr/lib/*/pkgconfig/compiz-mousepoll.pc
8+debian/tmp/usr/lib/*/pkgconfig/compiz-focuspoll.pc
9 debian/tmp/usr/lib/*/pkgconfig/compiz-opengl.pc
10 debian/tmp/usr/lib/*/pkgconfig/compiz.pc
11 debian/tmp/usr/lib/*/pkgconfig/compiz-scale.pc
12
13=== modified file 'debian/compiz-plugins-default.install'
14--- debian/compiz-plugins-default.install 2017-04-26 08:18:38 +0000
15+++ debian/compiz-plugins-default.install 2018-10-23 08:53:35 +0000
16@@ -26,6 +26,8 @@
17 usr/share/compiz/*matecompat.*
18 usr/lib/*/compiz/*mousepoll.*
19 usr/share/compiz/*mousepoll.*
20+usr/lib/*/compiz/*focuspoll.*
21+usr/share/compiz/*focuspoll.*
22 usr/lib/*/compiz/*colorfilter.*
23 usr/share/compiz/*colorfilter.*
24 usr/share/compiz/colorfilter
25
26=== modified file 'debian/control'
27--- debian/control 2018-07-24 10:32:11 +0000
28+++ debian/control 2018-10-23 08:53:35 +0000
29@@ -15,6 +15,7 @@
30 quilt (>= 0.40),
31 libcairo2-dev,
32 libdbus-glib-1-dev,
33+ libatspi2.0-dev,
34 libgl1-mesa-dev (>= 6.5.1) [!armhf !armel] | libgl-dev [!armhf !armel],
35 libegl1-mesa-dev [armhf armel], libgles2-mesa-dev [armhf armel],
36 libboost-dev,
37
38=== modified file 'plugins/ezoom/CMakeLists.txt'
39--- plugins/ezoom/CMakeLists.txt 2012-05-16 17:40:27 +0000
40+++ plugins/ezoom/CMakeLists.txt 2018-10-23 08:53:35 +0000
41@@ -2,4 +2,4 @@
42
43 include (CompizPlugin)
44
45-compiz_plugin (ezoom PLUGINDEPS composite opengl mousepoll)
46+compiz_plugin (ezoom PLUGINDEPS composite opengl mousepoll focuspoll)
47
48=== modified file 'plugins/ezoom/ezoom.xml.in'
49--- plugins/ezoom/ezoom.xml.in 2018-07-11 15:30:30 +0000
50+++ plugins/ezoom/ezoom.xml.in 2018-10-23 08:53:35 +0000
51@@ -7,6 +7,8 @@
52 <plugin>expo</plugin>
53 <plugin>decor</plugin>
54 <plugin>mousepoll</plugin>
55+ <plugin>focuspoll</plugin>
56+ <plugin>showmouse</plugin>
57 </relation>
58 <relation type="before">
59 <plugin>staticswitcher</plugin>
60@@ -15,6 +17,7 @@
61 <requirement>
62 <plugin>opengl</plugin>
63 <plugin>mousepoll</plugin>
64+ <plugin>focuspoll</plugin>
65 </requirement>
66 </deps>
67 <_short>Enhanced Zoom Desktop</_short>
68@@ -440,12 +443,17 @@
69 <_short>Focus Tracking</_short>
70 <option type="bool" name="follow_focus">
71 <_short>Enable focus tracking</_short>
72- <_long>Move the zoom area when focus changes.</_long>
73+ <_long>Move the zoom area when focus changes. You may see the mousepoll and focuspoll modules options for more</_long>
74 <default>true</default>
75 </option>
76+ <option type="bool" name="follow_window_focus">
77+ <_short>Enable focus tracking when switching windows</_short>
78+ <_long>Move the zoom area to the center of a newly switched window.</_long>
79+ <default>false</default>
80+ </option>
81 <option type="bool" name="focus_fit_window">
82- <_short>Fit zoom level to window on focus change</_short>
83- <_long>Fit the zoomed area to the window when the zoomed area moves as a result of focus tracking.</_long>
84+ <_short>Fit zoom level to newly switched window</_short>
85+ <_long>Fit the zoomed area to the window when it switches.</_long>
86 <default>false</default>
87 </option>
88 <option type="float" name="autoscale_min">
89@@ -458,15 +466,39 @@
90 </option>
91 <option type="bool" name="always_focus_fit_window">
92 <_short>Always fit to window on focus track</_short>
93- <_long>Fit the zoomed area to the window when the zoomed area moves as a result of focus tracking. Even when not initially zoomed in. </_long>
94+ <_long>Fit the zoomed area to the window when the zoomed area moves as a result of focus tracking, even when not initially zoomed in. </_long>
95 <default>false</default>
96 </option>
97- <option type="int" name="follow_focus_delay">
98- <_short>Follow Focus Delay</_short>
99- <_long>Only attempt to center newly focused windows if the mouse hasn't moved in this amount of seconds. Use this to avoid jumping when using sloppy focus.</_long>
100- <default>0</default>
101- <min>0</min>
102- <max>15</max>
103+ <option type="float" name="follow_focus_delay">
104+ <_short>Tracking delay from mouse to focus mode</_short>
105+ <_long>Only attempt to follow focus if the mouse hasn't moved in this amount of seconds. Use this to avoid zoom jumping everywhere on the screen due to external events while trying to use the mouse.</_long>
106+ <default>1</default>
107+ <min>0</min>
108+ <max>5</max>
109+ <precision>0.1</precision>
110+ </option>
111+ <option type="float" name="follow_mouse_delay">
112+ <_short>Tracking delay from focus to mouse mode</_short>
113+ <_long>Only attempt to follow mouse if the focus hasn't moved in this amount of seconds. Use this to avoid jumping only due to small accidental mouse movements. This does not have effect if the mouse warp option is enabled.</_long>
114+ <default>0.1</default>
115+ <min>0</min>
116+ <max>5</max>
117+ <precision>0.1</precision>
118+ </option>
119+ <option type="bool" name="always_center_mouse">
120+ <_short>Keep Mouse centered inside zoom area</_short>
121+ <_long>If activated, the mouse will always be centered when in mouse sync mode, if not, it will move around the zoom area</_long>
122+ <default>true</default>
123+ </option>
124+ <option type="bool" name="restrain_zoom_to_screen">
125+ <_short>Restrain the zoom area inside the screen</_short>
126+ <_long>If enabled, zoom area will stay inside the screen, if not, zoom area can go over screen borders (it will show empty black space)</_long>
127+ <default>true</default>
128+ </option>
129+ <option type="bool" name="warp_mouse_to_focus">
130+ <_short>When switching focus back to mouse, warp mouse inside the zoomed area</_short>
131+ <_long>When following a keyboard focus, one can want the mouse to come back into the field of vision before moving it. avoids jumping to another part of the screen when switching mouse/keyboard focus.</_long>
132+ <default>true</default>
133 </option>
134 </group>
135 <group>
136@@ -476,7 +508,7 @@
137 <_long>Zoom Change Speed</_long>
138 <default>25</default>
139 <min>0.1</min>
140- <max>100</max>
141+ <max>500</max>
142 <precision>0.1</precision>
143 </option>
144 <option type="float" name="timestep">
145@@ -487,6 +519,11 @@
146 <max>50</max>
147 <precision>0.1</precision>
148 </option>
149+ <option type="bool" name="instant_pan">
150+ <_short>Move the zoom area instantly</_short>
151+ <_long>When enabled, zoom area is warped instantly to focus position. When disabled, zoom area makes a smooth move to the focus position</_long>
152+ <default>false</default>
153+ </option>
154 </group>
155 </options>
156 </plugin>
157
158=== modified file 'plugins/ezoom/src/ezoom.cpp'
159--- plugins/ezoom/src/ezoom.cpp 2017-12-05 14:45:06 +0000
160+++ plugins/ezoom/src/ezoom.cpp 2018-10-23 08:53:35 +0000
161@@ -168,6 +168,39 @@
162 zs->gScreen->setTextureFilter (oldFilter);
163 }
164
165+/* Return current time in seconds */
166+static inline double
167+getTime (void)
168+{
169+ struct timespec tp;
170+ clock_gettime (CLOCK_MONOTONIC, &tp);
171+ return tp.tv_sec + tp.tv_nsec / 1e9;
172+}
173+
174+/* This returns the proper translation correction depending on the desired
175+ * centering (exact center, small overflow, or exact screen fit) */
176+float
177+EZoomScreen::getTranslateCorrection (float zoom)
178+{
179+ if (optionGetAlwaysCenterMouse ())
180+ {
181+ // Bring to X,Y position the exact zoom center instead of
182+ // fitting screen
183+ return 1.0;
184+ }
185+ else if (!optionGetRestrainZoomToScreen ())
186+ {
187+ // Overflow a bit beyond screen so we can clearly see the corner and the
188+ // mouse
189+ return (1.0f - zoom) / EZOOM_SCREEN_BORDER_OFFSET;
190+ }
191+ else
192+ {
193+ // Keep an exact fit to screen
194+ return 1.0f - zoom;
195+ }
196+}
197+
198 /* Returns the distance to the defined edge in zoomed pixels. */
199 int
200 EZoomScreen::distanceToEdge (int out,
201@@ -197,10 +230,26 @@
202
203 /* Update/set translations based on zoom level and real translate. */
204 void
205-EZoomScreen::ZoomArea::updateActualTranslates ()
206+EZoomScreen::ZoomArea::updateActualTranslates (EZoomScreen *screen)
207 {
208- xtrans = -realXTranslate * (1.0f - currentZoom);
209- ytrans = realYTranslate * (1.0f - currentZoom);
210+ float translateCorrection;
211+ float limit = 0.5 * (1.0f - currentZoom);
212+
213+ translateCorrection = screen->getTranslateCorrection(currentZoom);
214+
215+ xtrans = -realXTranslate * translateCorrection;
216+ ytrans = realYTranslate * translateCorrection;
217+
218+ if (screen->optionGetRestrainZoomToScreen ()) {
219+ if (xtrans < -limit)
220+ xtrans = -limit;
221+ if (xtrans > limit)
222+ xtrans = limit;
223+ if (ytrans < -limit)
224+ ytrans = -limit;
225+ if (ytrans > limit)
226+ ytrans = limit;
227+ }
228 }
229
230 /* Returns true if the head in question is currently moving.
231@@ -244,7 +293,6 @@
232 ytrans (0.0f),
233 locked (false)
234 {
235- updateActualTranslates ();
236 }
237
238 EZoomScreen::ZoomArea::ZoomArea () :
239@@ -365,7 +413,7 @@
240
241 adjustXYVelocity (out, chunk);
242 adjustZoomVelocity (out, chunk);
243- zooms.at (out).updateActualTranslates ();
244+ zooms.at (out).updateActualTranslates (this);
245
246 if (!isZoomed (out))
247 {
248@@ -597,8 +645,6 @@
249 * mouse pointer. This is to allow input, and is NOT necessary
250 * when input redirection is available to us or if we're cheating
251 * and using a scaled mouse cursor to imitate IR.
252- * The center is not the center of the screen. This is the target-center;
253- * that is, it's the point that's the same regardless of zoom level.
254 */
255 void
256 EZoomScreen::setCenter (int x,
257@@ -616,13 +662,13 @@
258 zooms.at (out).yTranslate = (float)
259 ((y - o->y1 ()) - o->height () / 2) / (o->height ());
260
261- if (instant)
262+ if (instant || optionGetInstantPan ())
263 {
264 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
265 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
266 zooms.at (out).yVelocity = 0.0f;
267 zooms.at (out).xVelocity = 0.0f;
268- zooms.at (out).updateActualTranslates ();
269+ zooms.at (out).updateActualTranslates (this);
270 }
271
272 if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
273@@ -661,7 +707,7 @@
274 {
275 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
276 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
277- zooms.at (out).updateActualTranslates ();
278+ zooms.at (out).updateActualTranslates (this);
279 }
280
281 if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
282@@ -703,9 +749,21 @@
283 void
284 EZoomScreen::enableMousePolling ()
285 {
286- pollHandle.start ();
287- lastChange = time(NULL);
288- mouse = MousePoller::getCurrentPosition ();
289+ pollMouseHandle.start ();
290+ lastMouseChange = getTime ();
291+ mouse = MousePoller::getCurrentPosition ();
292+}
293+
294+/* Enables polling of focus position */
295+void
296+EZoomScreen::enableFocusPolling ()
297+{
298+ if (!optionGetFollowFocus ())
299+ {
300+ return;
301+ }
302+ pollFocusHandle.start ();
303+ lastFocusChange = getTime ();
304 }
305
306 /* Sets the zoom (or scale) level.
307@@ -722,9 +780,12 @@
308 value = 1.0f;
309 else
310 {
311- if (!pollHandle.active ())
312+ if (!pollMouseHandle.active ())
313 enableMousePolling ();
314
315+ if (!pollFocusHandle.active ())
316+ enableFocusPolling ();
317+
318 grabbed |= (1 << zooms.at (out).output);
319 cursorZoomActive (out);
320 }
321@@ -770,7 +831,7 @@
322 {
323 int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
324
325- if (!isInMovement (out))
326+ if (!isInMovement (out) || nonMouseFocusTracking)
327 return;
328
329 CompOutput *o = &screen->outputDevs ().at (out);
330@@ -810,17 +871,30 @@
331 int oHeight = o->height ();
332 int halfOWidth = oWidth / 2;
333 int halfOHeight = oHeight / 2;
334+ float translateCorrection = getTranslateCorrection (za.currentZoom);
335+ float xTranslate = za.realXTranslate * translateCorrection ;
336+ float yTranslate = za.realYTranslate * translateCorrection ;
337+ float limit = 0.5 * (1.0 - za.currentZoom);
338+
339+ if (optionGetRestrainZoomToScreen ()) {
340+ if (xTranslate < -limit)
341+ xTranslate = -limit;
342+ if (xTranslate > limit)
343+ xTranslate = limit;
344+ if (yTranslate < -limit)
345+ yTranslate = -limit;
346+ if (yTranslate > limit)
347+ yTranslate = limit;
348+ }
349
350 x -= o->x1 ();
351 y -= o->y1 ();
352
353- *resultX = x - (za.realXTranslate *
354- (1.0f - za.currentZoom) * oWidth) - halfOWidth;
355+ *resultX = x - (xTranslate * oWidth) - halfOWidth;
356 *resultX /= za.currentZoom;
357 *resultX += halfOWidth;
358 *resultX += o->x1 ();
359- *resultY = y - (za.realYTranslate *
360- (1.0f - za.currentZoom) * oHeight) - halfOHeight;
361+ *resultY = y - (yTranslate * oHeight) - halfOHeight;
362 *resultY /= za.currentZoom;
363 *resultY += halfOHeight;
364 *resultY += o->y1 ();
365@@ -1058,7 +1132,7 @@
366
367 if (zooms.at (out).currentZoom == 1.0f)
368 {
369- lastChange = time(NULL);
370+ lastMouseChange = getTime ();
371 mouse = MousePoller::getCurrentPosition ();
372 }
373
374@@ -1129,20 +1203,88 @@
375 void
376 EZoomScreen::updateMousePosition (const CompPoint &p)
377 {
378+ auto localTime = getTime ();
379 mouse.setX (p.x ());
380 mouse.setY (p.y ());
381
382 int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
383- lastChange = time(NULL);
384-
385- if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
386- !isInMovement (out))
387- setCenter (mouse.x (), mouse.y (), true);
388-
389+ if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse)
390+ {
391+ if (optionGetWarpMouseToFocus ()) {
392+ CompOutput *o = &screen->outputDevs ().at (out);
393+ int zoomedMouseX, zoomedMouseY;
394+ bool pointerInZoom;
395+
396+ convertToZoomed (out, mouse.x (), mouse.y (),
397+ &zoomedMouseX, &zoomedMouseY);
398+ pointerInZoom = zoomedMouseX >= o->x1 ()
399+ && zoomedMouseX < o->x1 () + o->width ()
400+ && zoomedMouseY >= o->y1 ()
401+ && zoomedMouseY < o->y1 () + o->height ();
402+
403+ if (lastMouseChange <= lastFocusChange && nonMouseFocusTracking && !pointerInZoom)
404+ {
405+ // Mouse taking back control of focus, but is out of zoom, wrap
406+ // it back to focus
407+ // FocusTracking is always centered
408+ screen->warpPointer (
409+ zooms.at (out).realXTranslate * o->width ()
410+ + o->width () / 2 + o->x1 () - pointerX,
411+ zooms.at (out).realYTranslate * o->height ()
412+ + o->height () / 2 + o->y1 () - pointerY);
413+ setCenter (pointerX, pointerY, true);
414+ }
415+ else
416+ {
417+ if (!isInMovement (out))
418+ {
419+ setCenter (mouse.x (), mouse.y (), true);
420+ }
421+ }
422+ lastMouseChange = localTime;
423+ }
424+ // respect a timing in case user has merely grazed his mouse
425+ else if (localTime - lastFocusChange > optionGetFollowMouseDelay () && !isInMovement (out))
426+ {
427+ setCenter (mouse.x (), mouse.y (), true);
428+ lastMouseChange = localTime;
429+ }
430+ nonMouseFocusTracking = false;
431+ }
432 cursorMoved ();
433 cScreen->damageScreen ();
434 }
435
436+void
437+EZoomScreen::updateFocusPosition (const CompRect &rect)
438+{
439+ auto localTime = getTime ();
440+ int out = screen->outputDeviceForPoint (rect.x (), rect.y ());
441+ if (localTime - lastMouseChange > optionGetFollowFocusDelay ())
442+ {
443+ int zoomAreaWidth = screen->outputDevs ().at (out).width () * zooms.at (out).newZoom;
444+ int zoomAreaHeight = screen->outputDevs ().at (out).height () * zooms.at (out).newZoom;
445+ int posX = rect.x () + rect.width () / 2;
446+ int posY = rect.y () + rect.height () / 2;
447+ if (rect.width () > zoomAreaWidth)
448+ {
449+ // target rectangle is too big for the zoom area, aim at
450+ // the top-left corner of the target
451+ posX -= (rect.width () - zoomAreaWidth) / 2;
452+ }
453+ if (rect.height () > zoomAreaHeight)
454+ {
455+ posY -= (rect.height () - zoomAreaHeight) / 2;
456+ }
457+ setCenter (posX, posY, false);
458+ focus.setX (posX);
459+ focus.setY (posY);
460+ nonMouseFocusTracking = true;
461+ lastFocusChange = localTime;
462+ }
463+ cScreen->damageScreen ();
464+}
465+
466 /* Timeout handler to poll the mouse. Returns false (and thereby does not
467 * get re-added to the queue) when zoom is not active. */
468 void
469@@ -1154,11 +1296,21 @@
470 {
471 cursorMoved ();
472
473- if (pollHandle.active ())
474- pollHandle.stop ();
475+ if (pollMouseHandle.active ())
476+ pollMouseHandle.stop ();
477 }
478 }
479
480+/* Timeout handler to focusPoll. */
481+void
482+EZoomScreen::updateFocusInterval (const CompRect &rect)
483+{
484+ updateFocusPosition (rect);
485+
486+ if (!grabbed && pollFocusHandle.active ())
487+ pollFocusHandle.stop ();
488+}
489+
490 /* Free a cursor */
491 void
492 EZoomScreen::freeCursor (CursorTexture *cursor)
493@@ -1586,7 +1738,7 @@
494 int out = screen->outputDeviceForPoint (pointerX, pointerY);
495
496 if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
497- !isInMovement (out))
498+ !isInMovement (out) && !nonMouseFocusTracking)
499 setCenter (pointerX, pointerY, true);
500
501 setScale (out, zooms.at (out).newZoom / optionGetZoomFactor ());
502@@ -1914,8 +2066,8 @@
503
504 if (w == NULL ||
505 w->id () == screen->activeWindow () ||
506- time(NULL) - lastChange < optionGetFollowFocusDelay () ||
507- !optionGetFollowFocus ())
508+ getTime () - lastMouseChange < optionGetFollowFocusDelay () ||
509+ !optionGetFollowWindowFocus ())
510 return;
511
512 int out = screen->outputDeviceForGeometry (w->geometry ());
513@@ -2008,7 +2160,9 @@
514 gScreen (GLScreen::get (screen)),
515 grabbed (0),
516 grabIndex (0),
517- lastChange (0),
518+ lastMouseChange (0.),
519+ lastFocusChange (0.),
520+ nonMouseFocusTracking (false),
521 cursorInfoSelected (false),
522 cursorHidden (false)
523 {
524@@ -2042,8 +2196,10 @@
525 zooms.push_back (za);
526 }
527
528- pollHandle.setCallback (boost::bind (
529- &EZoomScreen::updateMouseInterval, this, _1));
530+ pollMouseHandle.setCallback (boost::bind (&EZoomScreen::updateMouseInterval,
531+ this, _1));
532+ pollFocusHandle.setCallback (boost::bind (&EZoomScreen::updateFocusInterval,
533+ this, _1));
534
535 optionSetZoomInButtonInitiate (boost::bind (&EZoomScreen::zoomIn, this, _1,
536 _2, _3));
537@@ -2136,8 +2292,10 @@
538
539 EZoomScreen::~EZoomScreen ()
540 {
541- if (pollHandle.active ())
542- pollHandle.stop ();
543+ if (pollMouseHandle.active ())
544+ pollMouseHandle.stop ();
545+ if (pollFocusHandle.active ())
546+ pollFocusHandle.stop ();
547
548 if (zooms.size ())
549 zooms.clear ();
550@@ -2152,7 +2310,8 @@
551 if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION) &&
552 CompPlugin::checkPluginABI ("composite", COMPIZ_COMPOSITE_ABI) &&
553 CompPlugin::checkPluginABI ("opengl", COMPIZ_OPENGL_ABI) &&
554- CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI))
555+ CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI) &&
556+ CompPlugin::checkPluginABI ("focuspoll", COMPIZ_FOCUSPOLL_ABI))
557 return true;
558
559 return false;
560
561=== modified file 'plugins/ezoom/src/ezoom.h'
562--- plugins/ezoom/src/ezoom.h 2016-07-01 15:37:19 +0000
563+++ plugins/ezoom/src/ezoom.h 2018-10-23 08:53:35 +0000
564@@ -43,12 +43,19 @@
565 #include <composite/composite.h>
566 #include <opengl/opengl.h>
567 #include <mousepoll/mousepoll.h>
568+#include <focuspoll/focuspoll.h>
569
570
571 #include "ezoom_options.h"
572
573 #include <cmath>
574
575+// Some Users wish to see better the edges of the screen. Without this, mouse
576+// reaches the border of the zoomarea around screen edges, and mouse can become
577+// invisible (especially around bottom/right). Zooming out a bit allows to see
578+// both the edge and the mouse reaching the edge.
579+#define EZOOM_SCREEN_BORDER_OFFSET 0.92
580+
581 enum SpecificZoomTarget
582 {
583 ZoomTarget1 = 0,
584@@ -160,25 +167,29 @@
585 ZoomArea ();
586
587 void
588- updateActualTranslates ();
589+ updateActualTranslates (EZoomScreen *screen);
590 };
591
592 public:
593
594 std::vector <ZoomArea> zooms; // list of zooms (different zooms for each output)
595 CompPoint mouse; // we get this from mousepoll
596+ CompPoint focus; // we get this from focuspoll
597 unsigned long int grabbed;
598 CompScreen::GrabHandle grabIndex; // for zoomBox
599- time_t lastChange;
600+ double lastMouseChange;
601+ double lastFocusChange;
602 CursorTexture cursor; // the texture for the faux-cursor
603 // we paint to do fake input
604 // handling
605+ bool nonMouseFocusTracking;
606 bool cursorInfoSelected;
607 bool cursorHidden;
608 CompRect box;
609 CompPoint clickPos;
610
611- MousePoller pollHandle; // mouse poller object
612+ MousePoller pollMouseHandle; // mouse poller object
613+ FocusPoller pollFocusHandle; // focus poller object
614
615 private:
616
617@@ -222,6 +233,9 @@
618 adjustXYVelocity (int out,
619 float chunk);
620
621+ float
622+ getTranslateCorrection (float zoom);
623+
624 void
625 drawBox (const GLMatrix &transform,
626 CompOutput *output,
627@@ -231,6 +245,11 @@
628 setCenter (int x,
629 int y,
630 bool instant);
631+ void
632+ setCenter (int x,
633+ int y,
634+ bool instant,
635+ bool center);
636
637 void
638 setZoomArea (int x,
639@@ -250,6 +269,9 @@
640 enableMousePolling ();
641
642 void
643+ enableFocusPolling ();
644+
645+ void
646 setScale (int out,
647 float value);
648
649@@ -295,6 +317,12 @@
650 void
651 updateMouseInterval (const CompPoint &p);
652
653+ void
654+ updateFocusPosition (const CompRect &retc);
655+
656+ void
657+ updateFocusInterval (const CompRect &rect);
658+
659 /* Make dtor */
660 void
661 freeCursor (CursorTexture * cursor);
662
663=== added directory 'plugins/focuspoll'
664=== added file 'plugins/focuspoll/CMakeLists.txt'
665--- plugins/focuspoll/CMakeLists.txt 1970-01-01 00:00:00 +0000
666+++ plugins/focuspoll/CMakeLists.txt 2018-10-23 08:53:35 +0000
667@@ -0,0 +1,9 @@
668+find_package (Compiz REQUIRED)
669+
670+include (CompizPlugin)
671+include_directories (include/accessibilitywatcher)
672+
673+pkg_search_module (ATSPI REQUIRED atspi-2)
674+
675+compiz_plugin (focuspoll PKGDEPS atspi-2)
676+
677
678=== added file 'plugins/focuspoll/compiz-focuspoll.pc.in'
679--- plugins/focuspoll/compiz-focuspoll.pc.in 1970-01-01 00:00:00 +0000
680+++ plugins/focuspoll/compiz-focuspoll.pc.in 2018-10-23 08:53:35 +0000
681@@ -0,0 +1,12 @@
682+prefix=@prefix@
683+exec_prefix=@exec_prefix@
684+libdir=@libdir@
685+includedir=@includedir@
686+
687+Name: compiz-focuspoll
688+Description: Focuspoll plugin for compiz
689+Version: @VERSION@
690+
691+Requires: compiz
692+Libs: -L${libdir}/compiz -lfocuspoll
693+Cflags: @COMPIZ_CFLAGS@ -I${includedir}/compiz
694
695=== added file 'plugins/focuspoll/focuspoll.xml.in'
696--- plugins/focuspoll/focuspoll.xml.in 1970-01-01 00:00:00 +0000
697+++ plugins/focuspoll/focuspoll.xml.in 2018-10-23 08:53:35 +0000
698@@ -0,0 +1,29 @@
699+<?xml version="1.0" encoding="UTF-8"?>
700+<compiz>
701+ <plugin name="focuspoll" useBcop="true">
702+ <deps>
703+ <relation type="after">
704+ <plugin>opengl</plugin>
705+ </relation>
706+ </deps>
707+ <_short>Focus position polling</_short>
708+ <_long>Updates the focus pointer position from the accessibility library AT-SPI through dbus</_long>
709+ <category>Utility</category>
710+ <options>
711+ <_short>Misc</_short>
712+ <option type="bool" name="ignore_links">
713+ <_short>Ignores link focuses</_short>
714+ <_long>If enabled, focuspoll will not track the focus events of hypertext link items</_long>
715+ <default>true</default>
716+ </option>
717+ <option type="int" name="focus_poll_interval">
718+ <_short>Focus Poll Interval</_short>
719+ <_long>How often to poll DBus for focus changes, in milliseconds. Reduce this to reduce choppy behavior.</_long>
720+ <default>10</default>
721+ <min>1</min>
722+ <max>500</max>
723+ </option>
724+ </options>
725+ </plugin>
726+</compiz>
727+
728
729=== added directory 'plugins/focuspoll/include'
730=== added directory 'plugins/focuspoll/include/accessibilitywatcher'
731=== added file 'plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h'
732--- plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 1970-01-01 00:00:00 +0000
733+++ plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 2018-10-23 08:53:35 +0000
734@@ -0,0 +1,70 @@
735+/*
736+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
737+ *
738+ * This file is part of compiz.
739+ *
740+ * this program is free software: you can redistribute it and/or modify
741+ * it under the terms of the GNU General Public License as published by the Free
742+ * Software Foundation, either version 3 of the License, or (at your option) any
743+ * later version.
744+ *
745+ * This program is distributed in the hope that it will be useful, but
746+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
747+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
748+ * details.
749+ *
750+ * You should have received a copy of the GNU General Public License along
751+ * with this program. If not, see <http://www.gnu.org/licenses/>.
752+ */
753+
754+#ifndef ACCESSIBILITY_WATCHER_H
755+#define ACCESSIBILITY_WATCHER_H
756+
757+#include <deque>
758+#include <vector>
759+
760+#include "focusinfo.h"
761+
762+#include <atspi/atspi.h>
763+
764+class AccessibilityWatcher
765+{
766+ public:
767+ AccessibilityWatcher ();
768+ ~AccessibilityWatcher ();
769+
770+ void setActive (bool);
771+
772+ void setIgnoreLinks (bool);
773+ void setScreenLimits (int, int);
774+ int getScreenWidth (void);
775+ int getScreenHeight (void);
776+
777+ std::deque<FocusInfo *> getFocusQueue (void);
778+ void resetFocusQueue (void);
779+ bool returnToPrevMenu (void);
780+
781+ void registerEvent (const AtspiEvent *event, const gchar *type);
782+
783+ private:
784+ bool mActive;
785+ int screenWidth;
786+ int screenHeight;
787+ static bool ignoreLinks;
788+ std::deque<FocusInfo*> focusList;
789+ std::vector<FocusInfo*> previouslyActiveMenus;
790+
791+ AtspiEventListener *focusListener;
792+ AtspiEventListener *caretMoveListener;
793+ AtspiEventListener *selectedListener;
794+ AtspiEventListener *descendantChangedListener;
795+
796+ void addWatches (void);
797+ void removeWatches (void);
798+
799+ bool appSpecificFilter (FocusInfo *focusInfo, const AtspiEvent* event);
800+ bool filterBadEvents (const FocusInfo *event);
801+ void getAlternativeCaret (FocusInfo *focus, const AtspiEvent* event);
802+};
803+
804+#endif
805
806=== added file 'plugins/focuspoll/include/accessibilitywatcher/focusinfo.h'
807--- plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 1970-01-01 00:00:00 +0000
808+++ plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 2018-10-23 08:53:35 +0000
809@@ -0,0 +1,71 @@
810+/*
811+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
812+ *
813+ * This file is part of compiz.
814+ *
815+ * this program is free software: you can redistribute it and/or modify
816+ * it under the terms of the GNU General Public License as published by the Free
817+ * Software Foundation, either version 3 of the License, or (at your option) any
818+ * later version.
819+ *
820+ * This program is distributed in the hope that it will be useful, but
821+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
822+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
823+ * details.
824+ *
825+ * You should have received a copy of the GNU General Public License along
826+ * with this program. If not, see <http://www.gnu.org/licenses/>.
827+ */
828+
829+#ifndef FOCUS_INFO_H
830+#define FOCUS_INFO_H
831+
832+#include <string>
833+#include <sstream>
834+#include <glib.h>
835+#include <core/point.h>
836+#include <core/size.h>
837+#include <core/rect.h>
838+
839+
840+class FocusInfo
841+{
842+ public:
843+
844+ FocusInfo (const gchar * type = "",
845+ gchar * name = g_strdup (""),
846+ gchar * label = g_strdup (""),
847+ gchar * role = g_strdup (""),
848+ gchar * application = g_strdup (""),
849+ int x = -1,
850+ int y = -1,
851+ int width = -1,
852+ int height = -1);
853+
854+ FocusInfo (const FocusInfo &dup);
855+
856+ ~FocusInfo ();
857+
858+ int x, y, w, h;
859+ int xAlt, yAlt, wAlt, hAlt;
860+ const gchar * type;
861+ gchar * name;
862+ gchar * label;
863+ gchar * role;
864+ gchar * application;
865+
866+ // AT-SPI events that are interesting to know about the event
867+ bool active;
868+ bool focused;
869+ bool selected;
870+
871+ const gchar * getType (void);
872+ CompPoint getPosition (void);
873+ CompSize getSize (void);
874+ CompRect getBBox (void);
875+
876+ bool operator== (const FocusInfo &other) const;
877+ bool operator!= (const FocusInfo &other) const;
878+};
879+
880+#endif
881
882=== added directory 'plugins/focuspoll/include/focuspoll'
883=== added file 'plugins/focuspoll/include/focuspoll/focuspoll.h'
884--- plugins/focuspoll/include/focuspoll/focuspoll.h 1970-01-01 00:00:00 +0000
885+++ plugins/focuspoll/include/focuspoll/focuspoll.h 2018-10-23 08:53:35 +0000
886@@ -0,0 +1,50 @@
887+/*
888+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
889+ *
890+ * This file is part of compiz.
891+ *
892+ * this program is free software: you can redistribute it and/or modify
893+ * it under the terms of the GNU General Public License as published by the Free
894+ * Software Foundation, either version 3 of the License, or (at your option) any
895+ * later version.
896+ *
897+ * This program is distributed in the hope that it will be useful, but
898+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
899+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
900+ * details.
901+ *
902+ * You should have received a copy of the GNU General Public License along
903+ * with this program. If not, see <http://www.gnu.org/licenses/>.
904+ */
905+
906+#ifndef _COMPIZ_FOCUSPOLL_H
907+#define _COMPIZ_FOCUSPOLL_H
908+
909+#define COMPIZ_FOCUSPOLL_ABI 1
910+
911+#include <core/rect.h>
912+#include <boost/function.hpp>
913+#include <accessibilitywatcher/focusinfo.h>
914+
915+class FocusPoller
916+{
917+ public:
918+ typedef boost::function <void (const CompRect &)> CallBack;
919+
920+ FocusPoller ();
921+ ~FocusPoller ();
922+
923+ void setCallback (CallBack callback);
924+ void start (void);
925+ void stop (void);
926+ bool active (void);
927+
928+ private:
929+ bool mActive;
930+ CallBack mCallback;
931+ CompRect focusRect;
932+
933+ friend class FocuspollScreen;
934+};
935+
936+#endif
937
938=== added directory 'plugins/focuspoll/src'
939=== added file 'plugins/focuspoll/src/accessibilitywatcher.cpp'
940--- plugins/focuspoll/src/accessibilitywatcher.cpp 1970-01-01 00:00:00 +0000
941+++ plugins/focuspoll/src/accessibilitywatcher.cpp 2018-10-23 08:53:35 +0000
942@@ -0,0 +1,732 @@
943+/*
944+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
945+ * Copyright (C) 2018 Samuel Thibault <sthibault@hypra.fr>
946+ *
947+ * This file is part of compiz.
948+ *
949+ * this program is free software: you can redistribute it and/or modify
950+ * it under the terms of the GNU General Public License as published by the Free
951+ * Software Foundation, either version 3 of the License, or (at your option) any
952+ * later version.
953+ *
954+ * This program is distributed in the hope that it will be useful, but
955+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
956+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
957+ * details.
958+ *
959+ * You should have received a copy of the GNU General Public License along
960+ * with this program. If not, see <http://www.gnu.org/licenses/>.
961+ */
962+
963+#include <iostream>
964+#include <memory>
965+
966+#include <string.h>
967+#include <stdlib.h>
968+#include <unistd.h>
969+
970+#include "accessibilitywatcher.h"
971+
972+// When getting a bounding box bigger than this size, consider that this is not
973+// a precise caret bounding box, and try to find another source of precise caret position
974+// TODO: make them options
975+namespace {
976+ const int A11YWATCHER_MAX_CARET_WIDTH = 50;
977+ const int A11YWATCHER_MAX_CARET_HEIGHT = 70;
978+}
979+
980+/*
981+ * Wrapper that automatically calls g_object_unref on pointer scope release.
982+ *
983+ * Instead of
984+ *
985+ * AtspiFoo x = atspi_... ()
986+ * atspi_... (x);
987+ * g_object_unref (x);
988+ *
989+ * use
990+ *
991+ * auto x = unique_gobject (atspi_... ());;
992+ * atspi_... (x.get ());
993+ *
994+ * And instead of
995+ *
996+ * AtspiFoo x = event->source;
997+ * g_object_ref (x);
998+ * atspi_... (x);
999+ * g_object_unref (x);
1000+ *
1001+ * use
1002+ *
1003+ * auto x = unique_gobject_ref (event->source);;
1004+ * atspi_... (x.get ());
1005+ * */
1006+
1007+struct unique_gobject_deleter {
1008+ void operator () (gpointer obj)
1009+ {
1010+ if (obj)
1011+ g_object_unref (obj);
1012+ }
1013+};
1014+
1015+template <typename T>
1016+std::unique_ptr <T, unique_gobject_deleter>
1017+unique_gobject (T* ptr)
1018+{
1019+
1020+ return std::unique_ptr <T, unique_gobject_deleter> (ptr);
1021+}
1022+
1023+template <typename T>
1024+std::unique_ptr <T, unique_gobject_deleter>
1025+unique_gobject_ref (T* ptr)
1026+{
1027+ if (ptr)
1028+ g_object_ref (ptr);
1029+ return std::unique_ptr <T, unique_gobject_deleter> (ptr);
1030+}
1031+
1032+/*
1033+ * Similarly for a g_array of gobjects.
1034+ */
1035+
1036+struct unique_gobject_garray_deleter {
1037+ void operator () (GArray *array)
1038+ {
1039+ for (guint i = 0; i < array->len; ++i)
1040+ {
1041+ gpointer obj = g_array_index (array, gpointer, i);
1042+ if (obj)
1043+ g_object_unref (obj);
1044+ }
1045+ g_array_unref (array);
1046+ }
1047+};
1048+
1049+template <typename T>
1050+std::unique_ptr <T, unique_gobject_garray_deleter>
1051+unique_gobject_garray (T* ptr)
1052+{
1053+
1054+ return std::unique_ptr <T, unique_gobject_garray_deleter> (ptr);
1055+}
1056+
1057+/*
1058+ * Similarly for calling g_free on pointer scope release.
1059+ */
1060+
1061+struct unique_gmem_deleter {
1062+ void operator () (gpointer obj)
1063+ {
1064+ g_free (obj);
1065+ }
1066+};
1067+
1068+template <typename T>
1069+std::unique_ptr <T, unique_gmem_deleter>
1070+unique_gmem (T* ptr)
1071+{
1072+
1073+ return std::unique_ptr <T, unique_gmem_deleter> (ptr);
1074+}
1075+
1076+
1077+
1078+bool AccessibilityWatcher::ignoreLinks = false;
1079+
1080+static void
1081+onCaretMove (const AtspiEvent *event, void *data)
1082+{
1083+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
1084+ watcher->registerEvent (event, "caret");
1085+}
1086+
1087+static void
1088+onSelectedChange (const AtspiEvent *event, void *data)
1089+{
1090+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
1091+ watcher->registerEvent (event, "state-changed:selected");
1092+}
1093+
1094+static void
1095+onFocus (const AtspiEvent *event, void *data)
1096+{
1097+ /* We only care about focus/selection gain
1098+ * there's no detail1 on focus loss in AT-SPI specs */
1099+ if (!event->detail1)
1100+ return;
1101+
1102+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
1103+ watcher->registerEvent (event, "focus");
1104+}
1105+
1106+static void
1107+onDescendantChanged (const AtspiEvent *event, void *data)
1108+{
1109+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
1110+ watcher->registerEvent (event, "active-descendant-changed");
1111+}
1112+
1113+AccessibilityWatcher::AccessibilityWatcher () :
1114+ mActive (false),
1115+ screenWidth (0),
1116+ screenHeight (0),
1117+ focusListener (NULL),
1118+ caretMoveListener (NULL),
1119+ selectedListener (NULL),
1120+ descendantChangedListener (NULL)
1121+{
1122+ atspi_init ();
1123+ atspi_set_main_context (g_main_context_default ());
1124+
1125+ focusListener = atspi_event_listener_new (reinterpret_cast <AtspiEventListenerCB> (onFocus), this, NULL);
1126+ caretMoveListener = atspi_event_listener_new (reinterpret_cast <AtspiEventListenerCB> (onCaretMove), this, NULL);
1127+ selectedListener = atspi_event_listener_new (reinterpret_cast <AtspiEventListenerCB> (onSelectedChange), this, NULL);
1128+ descendantChangedListener = atspi_event_listener_new (reinterpret_cast <AtspiEventListenerCB> (onDescendantChanged), this, NULL);
1129+
1130+ addWatches ();
1131+}
1132+
1133+AccessibilityWatcher::~AccessibilityWatcher ()
1134+{
1135+ removeWatches ();
1136+ g_object_unref (focusListener);
1137+ g_object_unref (caretMoveListener);
1138+ g_object_unref (selectedListener);
1139+ g_object_unref (descendantChangedListener);
1140+};
1141+
1142+static gchar *
1143+getLabel (AtspiAccessible *accessible)
1144+{
1145+ auto relations = unique_gobject_garray (atspi_accessible_get_relation_set (accessible, NULL));
1146+ if (relations.get () == NULL)
1147+ {
1148+ return g_strdup ("");
1149+ }
1150+
1151+ for (guint i = 0; i < relations.get ()->len; ++i) {
1152+ AtspiRelation *relation = g_array_index (relations.get (), AtspiRelation*, i);
1153+ if (relation == NULL)
1154+ continue;
1155+ if (atspi_relation_get_relation_type (relation) == ATSPI_RELATION_LABELLED_BY)
1156+ {
1157+ auto target = unique_gobject (atspi_relation_get_target (relation, 0));
1158+ gchar * res_label = atspi_accessible_get_name (target.get (), NULL);
1159+ return (res_label == NULL) ? g_strdup ("") : res_label;
1160+ }
1161+ }
1162+ return g_strdup ("");
1163+}
1164+
1165+void
1166+AccessibilityWatcher::setIgnoreLinks (bool val)
1167+{
1168+ ignoreLinks = val;
1169+}
1170+
1171+void
1172+AccessibilityWatcher::setScreenLimits (int x, int y)
1173+{
1174+ screenWidth = x;
1175+ screenHeight = y;
1176+}
1177+
1178+int
1179+AccessibilityWatcher::getScreenWidth ()
1180+{
1181+ return screenWidth;
1182+}
1183+
1184+int
1185+AccessibilityWatcher::getScreenHeight ()
1186+{
1187+ return screenHeight;
1188+}
1189+
1190+void
1191+AccessibilityWatcher::registerEvent (const AtspiEvent *event, const gchar *type)
1192+{
1193+ // type is registered from filter on calling event
1194+ auto application = unique_gobject (atspi_accessible_get_application (event->source, NULL));
1195+ FocusInfo *res = new FocusInfo (type,
1196+ atspi_accessible_get_name (event->source, NULL),
1197+ getLabel (event->source),
1198+ atspi_accessible_get_role_name (event->source, NULL),
1199+ atspi_accessible_get_name (application.get (), NULL));
1200+
1201+ if (!res->active)
1202+ {
1203+ // prevents skipping events that are not designated as active. we check the activeness of parents.
1204+ auto parent = unique_gobject (atspi_accessible_get_parent (event->source, NULL));
1205+ while (parent.get ())
1206+ {
1207+ auto stateSet = unique_gobject (atspi_accessible_get_state_set (parent.get ()));
1208+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_ACTIVE))
1209+ {
1210+ res->active = true;
1211+ }
1212+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_EXPANDABLE))
1213+ {
1214+ if (!atspi_state_set_contains (stateSet.get (), ATSPI_STATE_EXPANDED))
1215+ {
1216+ auto role = unique_gmem (atspi_accessible_get_role_name (parent.get (), NULL));
1217+ if (!strcmp (role.get (), "menu"))
1218+ {
1219+ // parent is expandable but not expanded, we do not want to track what is happening inside
1220+ delete (res);
1221+ return;
1222+ }
1223+ }
1224+ }
1225+ auto child = unique_gobject (atspi_accessible_get_parent (parent.get (), NULL));
1226+ if (child.get () == parent.get ())
1227+ {
1228+ // parent loop !? escape this trap...
1229+ break;
1230+ }
1231+ parent = unique_gobject_ref (child.get ());
1232+ }
1233+ }
1234+
1235+ auto component_target = unique_gobject_ref (event->source);
1236+
1237+ if (strcmp (res->type, "active-descendant-changed") == 0)
1238+ {
1239+ component_target = unique_gobject (atspi_accessible_get_child_at_index (component_target.get (), event->detail1, NULL));
1240+ if (!component_target.get ())
1241+ {
1242+ delete (res);
1243+ return;
1244+ }
1245+ }
1246+
1247+ if (strcmp (unique_gmem (atspi_accessible_get_role_name (component_target.get (), NULL)).get (), "tree table") == 0)
1248+ {
1249+ // This is a table, it is not generally useful to look at the whole
1250+ // table, but rather just at the selected file, if any, otherwise the first
1251+ // file, if any.
1252+ decltype (component_target) sub_target = NULL;
1253+ auto selection = unique_gobject (atspi_accessible_get_selection_iface (component_target.get ()));
1254+
1255+ if (selection.get ())
1256+ {
1257+ sub_target = unique_gobject (atspi_selection_get_selected_child (selection.get (), 0, NULL));
1258+ }
1259+
1260+ if (sub_target.get () == NULL)
1261+ {
1262+ // No selection, try to get first real child.
1263+ unsigned i = 0;
1264+
1265+ while (1) {
1266+ sub_target = unique_gobject (atspi_accessible_get_child_at_index (component_target.get (), i, NULL));
1267+ i++;
1268+
1269+ if (sub_target.get () == NULL)
1270+ // No other children than table column header
1271+ break;
1272+
1273+ if (strcmp (unique_gmem (atspi_accessible_get_role_name (sub_target.get (), NULL)).get (), "table column header") != 0)
1274+ // Found a real child
1275+ break;
1276+ }
1277+ }
1278+
1279+ if (sub_target.get ())
1280+ component_target = unique_gobject_ref (sub_target.get ());
1281+ }
1282+
1283+ auto component = unique_gobject (atspi_accessible_get_component (component_target.get ()));
1284+
1285+ if (component.get ())
1286+ {
1287+ auto size = unique_gmem (atspi_component_get_extents (component.get (), ATSPI_COORD_TYPE_SCREEN, NULL));
1288+ res->x = size.get ()->x;
1289+ res->y = size.get ()->y;
1290+ res->w = size.get ()->width;
1291+ res->h = size.get ()->height;
1292+ }
1293+ // let's get the caret offset, and then its position for a caret event
1294+ if (strcmp (type, "caret") == 0)
1295+ {
1296+ auto text = unique_gobject (atspi_accessible_get_text (event->source));
1297+ if (!text.get ())
1298+ {
1299+ delete (res);
1300+ return;
1301+ }
1302+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
1303+ // if we are not at the beginning of the text, take the extent of the character under caret
1304+ // otherwise keep the whole widget
1305+ if (event->detail1)
1306+ {
1307+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
1308+ res->x = size.get ()->x;
1309+ res->y = size.get ()->y;
1310+ res->w = size.get ()->width;
1311+ res->h = size.get ()->height;
1312+ }
1313+ // correcting a missing offset when caret is at end of text
1314+ if (((res->x == 0 && res->y == 0) ||
1315+ res->x + res->w < 0 ||
1316+ res->y + res->h < 0)
1317+ && offset > 0)
1318+ {
1319+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset-1, ATSPI_COORD_TYPE_SCREEN, NULL));
1320+ res->x = size.get ()->x;
1321+ res->y = size.get ()->y;
1322+ res->w = size.get ()->width;
1323+ res->h = size.get ()->height;
1324+ }
1325+ // when result is obviously not a caret size
1326+ if (strcmp (event->type, "object:text-caret-moved") == 0 && (res->w > A11YWATCHER_MAX_CARET_WIDTH || res->h > A11YWATCHER_MAX_CARET_HEIGHT))
1327+ {
1328+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
1329+ res->x = size.get ()->x;
1330+ res->y = size.get ()->y;
1331+ res->w = size.get ()->width;
1332+ res->h = size.get ()->height;
1333+ if (strcmp (type, "caret") == 0 && strcmp (event->type, "object:text-caret-moved") == 0 && (res->w > A11YWATCHER_MAX_CARET_WIDTH || res->h > A11YWATCHER_MAX_CARET_HEIGHT))
1334+ {
1335+ res->x = 0;
1336+ res->y = 0;
1337+ }
1338+ }
1339+
1340+ // still no offset, it's probably a newline and we're at bugzilla #1319273 (with new paragraph obj)
1341+ if (res->x == 0 && res->y == 0 &&
1342+ (strcmp (event->type, "object:text-changed:insert") == 0 ||
1343+ strcmp (event->type, "object:text-changed:removed") == 0 ||
1344+ strcmp (event->type, "object:text-caret-moved") == 0)) {
1345+ res->x = res->xAlt;
1346+ res->y = res->yAlt;
1347+ res->w = res->wAlt;
1348+ res->h = res->hAlt;
1349+ }
1350+ }
1351+
1352+ // getting the states on event
1353+ auto stateSet = unique_gobject (atspi_accessible_get_state_set (event->source));
1354+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_FOCUSED))
1355+ {
1356+ res->focused = true;
1357+ // reset potential menu stack
1358+ for (auto info: previouslyActiveMenus) {
1359+ delete (info);
1360+ }
1361+ previouslyActiveMenus.clear ();
1362+ }
1363+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_SELECTED))
1364+ {
1365+ res->selected = true;
1366+ }
1367+ if (strcmp (res->type, "state-changed:selected") == 0 && event->detail1 == 1)
1368+ {
1369+ res->selected = true;
1370+ FocusInfo *dup = new FocusInfo (*res);
1371+ // add to stack of menus
1372+ previouslyActiveMenus.push_back (dup);
1373+ }
1374+
1375+ if (appSpecificFilter (res, event))
1376+ {
1377+ return;
1378+ }
1379+ if (filterBadEvents (res))
1380+ {
1381+ delete (res);
1382+ return;
1383+ }
1384+ while (focusList.size () >= 5) { // don't keep the whole history
1385+ auto iter = focusList.begin ();
1386+ auto info = *iter;
1387+ focusList.erase (iter);
1388+ delete (info);
1389+ }
1390+ focusList.push_back (res);
1391+}
1392+
1393+bool
1394+AccessibilityWatcher::appSpecificFilter (FocusInfo *focus, const AtspiEvent* event)
1395+{
1396+ if (strcmp (focus->type, "state-changed:selected") == 0 && // emulates on-change:selected missing event for menus
1397+ (strcmp (focus->role, "menu item") == 0 ||
1398+ strcmp (focus->role, "menu") == 0 ||
1399+ strcmp (focus->role, "check menu item") == 0 ||
1400+ strcmp (focus->role, "radio menu item") == 0 ||
1401+ strcmp (focus->role, "tearoff menu item") == 0) &&
1402+ strcmp (focus->application, "mate-panel") != 0)
1403+ {
1404+ if (!focus->selected && returnToPrevMenu ())
1405+ {
1406+ // The submenu item told us that he lost selection. We have thus
1407+ // returned to the parent menu (which unfortunately won't tell us
1408+ // anything)
1409+ delete (focus);
1410+ return true;
1411+ }
1412+ focus->active = true;
1413+ }
1414+ if (strcmp (focus->application, "soffice") == 0 && strcmp (focus->role, "paragraph") == 0)
1415+ { // LO-calc: avoid spam event from main edit line
1416+ auto parent = unique_gobject (atspi_accessible_get_parent (event->source, NULL));
1417+ auto parentLabel = unique_gmem (atspi_accessible_get_name (parent.get (), NULL));
1418+ if (!strcmp (parentLabel.get (), "Input line") ||
1419+ !strcmp (parentLabel.get (), "Ligne de saisie"))
1420+ {
1421+ delete (focus);
1422+ return true;
1423+ }
1424+ }
1425+ if (strcmp (focus->application, "Icedove") == 0 || strcmp (focus->application, "Thunderbird") == 0)
1426+ {
1427+ if (strcmp (focus->type, "caret") == 0)
1428+ {
1429+ auto text = unique_gobject (atspi_accessible_get_text (event->source)); // next if deals with a special newline char, that remained buggy. hypra issue #430
1430+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
1431+ auto string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
1432+ auto stringM1 = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset - 1, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
1433+ gchar character = string.get ()->content[0];
1434+ gchar characterM1 = stringM1.get ()->content[0];
1435+
1436+ if (offset == atspi_text_get_character_count (text.get (), NULL) && character == '\0' && characterM1 == '\n')
1437+ {
1438+ getAlternativeCaret (focus, event);
1439+ focus->x = focus->xAlt;
1440+ focus->y = focus->yAlt + focus->hAlt;
1441+ focus->w = focus->wAlt;
1442+ focus->h = focus->hAlt;
1443+ }
1444+ if (!(focus->x == 0 && focus->y == 0))
1445+ { // prevents compose window loss of tracking in HTML mode (active flag ok, but no focused flag)
1446+ focusList.push_back (focus);
1447+ return true;
1448+ }
1449+ auto component = unique_gobject (atspi_accessible_get_component (event->source));
1450+ if (component.get ())
1451+ {
1452+ auto size = unique_gmem (atspi_component_get_extents (component.get (), ATSPI_COORD_TYPE_SCREEN, NULL));
1453+ focus->x = size.get ()->x;
1454+ focus->y = size.get ()->y;
1455+ focus->w = 7;
1456+ focus->h = size.get ()->height;
1457+ focusList.push_back (focus);
1458+ return true;
1459+ }
1460+ }
1461+ }
1462+ if (strcmp (focus->application, "Firefox") == 0)
1463+ {
1464+ if (ignoreLinks && strcmp (focus->type, "caret") != 0 && strcmp (focus->role, "link") == 0)
1465+ {
1466+ delete (focus);
1467+ return true;
1468+ }
1469+ // prevents status bar focus in firefox
1470+ if (strcmp (focus->type, "caret") == 0 &&
1471+ (strcmp (event->type, "object:text-changed:insert:system") == 0 ||
1472+ strcmp (event->type, "object:text-changed:delete:system") == 0)) {
1473+ delete (focus);
1474+ return true;
1475+ }
1476+ if (strcmp (focus->type, "focus") == 0 && strcmp (focus->role, "document frame") == 0)
1477+ { // general page parasite event
1478+ delete (focus);
1479+ return true;
1480+ }
1481+ if (strcmp (focus->type, "caret") == 0 && !(focus->x == 0 && focus->y == 0))
1482+ {
1483+ focusList.push_back (focus);
1484+ return true;
1485+ }
1486+ getAlternativeCaret (focus, event);
1487+ if (strcmp (focus->type, "caret") == 0 && !(focus->xAlt == 0 && focus->yAlt == 0))
1488+ {
1489+ focus->x = focus->xAlt;
1490+ focus->y = focus->yAlt + focus->hAlt;
1491+ focus->w = focus->wAlt;
1492+ focus->h = focus->hAlt;
1493+ focusList.push_back (focus);
1494+ return true;
1495+ }
1496+ }
1497+ if (strcmp (focus->application, "evince") == 0 && strcmp (focus->type, "state-changed:selected") == 0 && strcmp (focus->role, "icon") == 0)
1498+ { // LO-calc: avoid spam event from main edit line
1499+ delete (focus);
1500+ return true; // ignores the parasite event from evince icon
1501+ }
1502+ return false;
1503+}
1504+
1505+bool
1506+AccessibilityWatcher::filterBadEvents (const FocusInfo *event)
1507+{
1508+ if (strcmp (event->type, "caret") == 0 && event->x ==0 && event->y == 0)
1509+ {
1510+ return true;
1511+ }
1512+ if (!event->active)
1513+ {
1514+ return true;
1515+ }
1516+ if (!event->focused && !event->selected)
1517+ {
1518+ return true;
1519+ }
1520+ if (event->w < 0 ||
1521+ event->h < 0)
1522+ {
1523+ return true;
1524+ }
1525+ if (event->x == 0 &&
1526+ event->y == 0 &&
1527+ event->w == 0 &&
1528+ event->h == 0)
1529+ {
1530+ return true;
1531+ }
1532+ if (event->x + event->w < 0 ||
1533+ event->y + event->h < 0)
1534+ {
1535+ return true;
1536+ }
1537+ if (getScreenWidth () != 0 && getScreenHeight () != 0 &&
1538+ (event->x > getScreenWidth () ||
1539+ event->y > getScreenHeight () ||
1540+ event->w > getScreenWidth () ||
1541+ event->h > getScreenHeight ()))
1542+ {
1543+ return true;
1544+ }
1545+ return false;
1546+}
1547+
1548+/*
1549+ * This simulates a "selected" event from the parent menu when closing
1550+ * a submenu.
1551+ */
1552+bool
1553+AccessibilityWatcher::returnToPrevMenu ()
1554+{
1555+ if (previouslyActiveMenus.size () > 1)
1556+ {
1557+ previouslyActiveMenus.pop_back ();
1558+ FocusInfo *dup = new FocusInfo (*previouslyActiveMenus.back ());
1559+ focusList.push_back (dup);
1560+ return true;
1561+ }
1562+ return false;
1563+}
1564+
1565+/*
1566+ * Tries to extrapolate a missing caret position from other text characters.
1567+ * is used as last resort when application doesn't respect at-spi standarts,
1568+ * or at-spi bugs.
1569+ */
1570+void
1571+AccessibilityWatcher::getAlternativeCaret (FocusInfo *focus, const AtspiEvent* event)
1572+{
1573+ auto text = unique_gobject (atspi_accessible_get_text (event->source));
1574+ if (!text.get ())
1575+ return;
1576+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
1577+ auto string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
1578+ gchar caretChar = string.get ()->content[0];
1579+
1580+ // if we're at a newline, sometimes at-spi isn't giving us a caret position. unknown bug in some apps.
1581+ if (caretChar == '\n' || caretChar == '\0')
1582+ {
1583+ // gives the last empty line the right focus.
1584+ int lines = atspi_text_get_character_count (text.get (), NULL) == offset ? 1 : 0;
1585+ int charIndex = 1;
1586+ bool charExtentsFound = false;
1587+
1588+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
1589+ // try and find the character on upper line to extrapolate position from. no more that 300 char, we risk lag.
1590+ while (!charExtentsFound && charIndex <= offset && charIndex < 300) {
1591+ size = unique_gmem (atspi_text_get_character_extents (text.get (), offset - charIndex, ATSPI_COORD_TYPE_SCREEN, NULL));
1592+ string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset - charIndex, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
1593+ caretChar = string.get ()->content[0];
1594+ // if we found a caret, check we're at beginning of line (or of text) to extrapolate position
1595+ if (size.get ()->x != 0 || size.get ()->y != 0)
1596+ {
1597+ if (offset - charIndex -1 >= 0 && unique_gmem (atspi_text_get_string_at_offset (text.get (), offset - charIndex -1, ATSPI_TEXT_GRANULARITY_CHAR, NULL)).get ()->content[0] == '\n')
1598+ {
1599+ charExtentsFound = true; // first character of upper line has been found
1600+ }
1601+ else if (offset - charIndex -1 == 0)
1602+ {
1603+ size = unique_gmem (atspi_text_get_character_extents (text.get (), 0, ATSPI_COORD_TYPE_SCREEN, NULL));
1604+ // first character of upper line has been found
1605+ charExtentsFound = true;
1606+ }
1607+ }
1608+ else if (caretChar == '\n')
1609+ {
1610+ ++lines;
1611+ }
1612+ ++charIndex;
1613+ }
1614+ focus->xAlt = size.get ()->x;
1615+ focus->yAlt = size.get ()->y + (lines-1) * size.get ()->height;
1616+ focus->wAlt = size.get ()->width;
1617+ focus->hAlt = size.get ()->height;
1618+ }
1619+}
1620+
1621+
1622+/* Register to events */
1623+void
1624+AccessibilityWatcher::addWatches ()
1625+{
1626+ atspi_event_listener_register (focusListener, "object:state-changed:focused", NULL);
1627+ atspi_event_listener_register (caretMoveListener, "object:text-caret-moved", NULL);
1628+ atspi_event_listener_register (caretMoveListener, "object:text-changed:inserted", NULL);
1629+ atspi_event_listener_register (caretMoveListener, "object:text-changed:removed", NULL);
1630+ atspi_event_listener_register (selectedListener, "object:state-changed:selected", NULL);
1631+ atspi_event_listener_register (descendantChangedListener, "object:active-descendant-changed", NULL);
1632+ mActive = true;
1633+}
1634+
1635+void
1636+AccessibilityWatcher::removeWatches ()
1637+{
1638+ atspi_event_listener_deregister (focusListener, "object:state-changed:focused", NULL);
1639+ atspi_event_listener_deregister (caretMoveListener, "object:text-caret-moved", NULL);
1640+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:inserted", NULL);
1641+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:removed", NULL);
1642+ atspi_event_listener_deregister (selectedListener, "object:state-changed:selected", NULL);
1643+ atspi_event_listener_deregister (descendantChangedListener, "object:active-descendant-changed", NULL);
1644+ mActive = false;
1645+}
1646+
1647+void
1648+AccessibilityWatcher::setActive (bool activate)
1649+{
1650+ if (mActive && !activate)
1651+ {
1652+ removeWatches ();
1653+ }
1654+ else if (!mActive && activate)
1655+ {
1656+ addWatches ();
1657+ }
1658+}
1659+
1660+std::deque <FocusInfo *>
1661+AccessibilityWatcher::getFocusQueue ()
1662+{
1663+ return focusList;
1664+}
1665+
1666+void
1667+AccessibilityWatcher::resetFocusQueue ()
1668+{
1669+ for (auto info: focusList) {
1670+ delete (info);
1671+ }
1672+ focusList.clear ();
1673+}
1674+
1675
1676=== added file 'plugins/focuspoll/src/focusinfo.cpp'
1677--- plugins/focuspoll/src/focusinfo.cpp 1970-01-01 00:00:00 +0000
1678+++ plugins/focuspoll/src/focusinfo.cpp 2018-10-23 08:53:35 +0000
1679@@ -0,0 +1,123 @@
1680+/*
1681+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
1682+ *
1683+ * This file is part of compiz.
1684+ *
1685+ * this program is free software: you can redistribute it and/or modify
1686+ * it under the terms of the GNU General Public License as published by the Free
1687+ * Software Foundation, either version 3 of the License, or (at your option) any
1688+ * later version.
1689+ *
1690+ * This program is distributed in the hope that it will be useful, but
1691+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1692+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1693+ * details.
1694+ *
1695+ * You should have received a copy of the GNU General Public License along
1696+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1697+ */
1698+
1699+#include "focusinfo.h"
1700+
1701+#include <stdio.h>
1702+#include <string.h>
1703+
1704+FocusInfo::FocusInfo (const gchar * type,
1705+ gchar * name,
1706+ gchar * label,
1707+ gchar * role,
1708+ gchar * application,
1709+ int x,
1710+ int y,
1711+ int width,
1712+ int height) :
1713+ x (x),
1714+ y (y),
1715+ w (width),
1716+ h (height),
1717+ xAlt (-1),
1718+ yAlt (-1),
1719+ wAlt (-1),
1720+ hAlt (-1),
1721+ type (type),
1722+ name (name),
1723+ label (label),
1724+ role (role),
1725+ application (application),
1726+ active (false),
1727+ focused (false),
1728+ selected (false)
1729+{
1730+}
1731+
1732+FocusInfo::FocusInfo (const FocusInfo &dup)
1733+{
1734+ x = dup.x;
1735+ y = dup.y;
1736+ w = dup.w;
1737+ h = dup.h;
1738+ xAlt = dup.xAlt;
1739+ yAlt = dup.yAlt;
1740+ wAlt = dup.wAlt;
1741+ hAlt = dup.hAlt;
1742+ type = dup.type;
1743+ name = strdup(dup.name);
1744+ label = strdup(dup.label);
1745+ role = strdup(dup.role);
1746+ application = strdup(dup.application);
1747+ active = dup.active;
1748+ focused = dup.focused;
1749+ selected = dup.selected;
1750+}
1751+
1752+FocusInfo::~FocusInfo (void)
1753+{
1754+ g_free (name);
1755+ g_free (label);
1756+ g_free (role);
1757+ g_free (application);
1758+}
1759+
1760+const gchar *
1761+FocusInfo::FocusInfo::getType (void)
1762+{
1763+ return type;
1764+}
1765+
1766+CompPoint
1767+FocusInfo::getPosition (void)
1768+{
1769+ return CompPoint (x, y);
1770+}
1771+
1772+CompSize
1773+FocusInfo::getSize (void)
1774+{
1775+ return CompSize (w, h);
1776+}
1777+
1778+CompRect
1779+FocusInfo::getBBox (void)
1780+{
1781+ return CompRect (x, y, w, h);
1782+}
1783+
1784+bool
1785+FocusInfo::operator== (const FocusInfo& other) const
1786+{
1787+ return (other.x == x &&
1788+ other.y == y &&
1789+ other.w == w &&
1790+ other.h == h &&
1791+ !strcmp (other.type, type) &&
1792+ !strcmp (other.name, name) &&
1793+ !strcmp (other.label, label) &&
1794+ !strcmp (other.application, application) &&
1795+ !strcmp (other.role, role));
1796+};
1797+
1798+bool
1799+FocusInfo::operator!= (const FocusInfo& other) const
1800+{
1801+ return !(*this == other);
1802+};
1803
1804=== added file 'plugins/focuspoll/src/focuspoll.cpp'
1805--- plugins/focuspoll/src/focuspoll.cpp 1970-01-01 00:00:00 +0000
1806+++ plugins/focuspoll/src/focuspoll.cpp 2018-10-23 08:53:35 +0000
1807@@ -0,0 +1,225 @@
1808+/*
1809+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
1810+ *
1811+ * This file is part of compiz.
1812+ *
1813+ * this program is free software: you can redistribute it and/or modify
1814+ * it under the terms of the GNU General Public License as published by the Free
1815+ * Software Foundation, either version 3 of the License, or (at your option) any
1816+ * later version.
1817+ *
1818+ * This program is distributed in the hope that it will be useful, but
1819+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1820+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1821+ * details.
1822+ *
1823+ * You should have received a copy of the GNU General Public License along
1824+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1825+ */
1826+
1827+#include "private.h"
1828+
1829+COMPIZ_PLUGIN_20090315 (focuspoll, FocuspollPluginVTable);
1830+
1831+bool
1832+FocuspollScreen::updatePosition ()
1833+{
1834+ if (!a11ywatcher->getFocusQueue ().empty ())
1835+ {
1836+ CompRect focusRect = a11ywatcher->getFocusQueue ().back ()->getBBox ();
1837+ for (std::list<FocusPoller *>::iterator it = pollers.begin (); it != pollers.end ();)
1838+ {
1839+ FocusPoller *poller = *it;
1840+ // Update iterator to next element before calling the callback which
1841+ // might remove the poller from the list.
1842+ ++it;
1843+ poller->mCallback (focusRect);
1844+ }
1845+ }
1846+ a11ywatcher->resetFocusQueue ();
1847+ return true;
1848+}
1849+
1850+bool
1851+FocuspollScreen::addTimer (FocusPoller *poller)
1852+{
1853+ bool start = pollers.empty ();
1854+
1855+ std::list <FocusPoller *>::iterator it =
1856+ std::find (pollers.begin (), pollers.end (), poller);
1857+
1858+ if (it != pollers.end ())
1859+ return false;
1860+
1861+ pollers.insert (it, poller);
1862+
1863+ if (start)
1864+ {
1865+ a11ywatcher->setActive (true);
1866+ timer.start ();
1867+ }
1868+
1869+ return true;
1870+}
1871+
1872+void
1873+FocuspollScreen::removeTimer (FocusPoller *poller)
1874+{
1875+ std::list <FocusPoller *>::iterator it =
1876+ std::find (pollers.begin (), pollers.end (), poller);
1877+
1878+ if (it == pollers.end ())
1879+ return;
1880+
1881+ pollers.erase (it);
1882+
1883+ if (pollers.empty ())
1884+ {
1885+ a11ywatcher->setActive (false);
1886+ timer.stop ();
1887+ }
1888+}
1889+
1890+void
1891+FocusPoller::setCallback (FocusPoller::CallBack callback)
1892+{
1893+ bool wasActive = mActive;
1894+
1895+ if (mActive)
1896+ stop ();
1897+
1898+ mCallback = callback;
1899+
1900+ if (wasActive)
1901+ start ();
1902+}
1903+
1904+void
1905+FocusPoller::start ()
1906+{
1907+ FOCUSPOLL_SCREEN (screen);
1908+
1909+ if (!fs)
1910+ {
1911+ compLogMessage ("focuspoll", CompLogLevelWarn,
1912+ "Plugin version mismatch, can't start focus poller.");
1913+
1914+ return;
1915+ }
1916+
1917+ if (mCallback.empty ())
1918+ {
1919+ compLogMessage ("focuspoll", CompLogLevelWarn,
1920+ "Can't start focus poller without callback.");
1921+ return;
1922+ }
1923+
1924+ fs->addTimer (this);
1925+
1926+ mActive = true;
1927+}
1928+
1929+void
1930+FocusPoller::stop ()
1931+{
1932+ FOCUSPOLL_SCREEN (screen);
1933+
1934+ /* Prevent broken plugins from calling stop () twice */
1935+
1936+ if (!mActive)
1937+ return;
1938+
1939+ if (!fs)
1940+ {
1941+ compLogMessage ("focuspoll",
1942+ CompLogLevelWarn,
1943+ "Plugin version mismatch, can't stop focus poller.");
1944+ return;
1945+ }
1946+
1947+ mActive = false;
1948+ fs->removeTimer (this);
1949+}
1950+
1951+bool
1952+FocusPoller::active ()
1953+{
1954+ return mActive;
1955+}
1956+
1957+FocusPoller::FocusPoller () :
1958+ mActive (false),
1959+ mCallback (NULL) {
1960+ }
1961+
1962+CompSize
1963+FocuspollScreen::getScreenLimits () {
1964+ int x =0, y = 0;
1965+ for (auto dev : screen->outputDevs ()) {
1966+ x = std::max (x, dev.x () + dev.width ());
1967+ y = std::max (y, dev.y () + dev.height ());
1968+ }
1969+ return CompSize (x, y);
1970+}
1971+
1972+void
1973+FocuspollScreen::updateTimer ()
1974+{
1975+ float timeout = optionGetFocusPollInterval ();
1976+ timer.setTimes (timeout, timeout * 1.5);
1977+}
1978+
1979+FocusPoller::~FocusPoller ()
1980+{
1981+ if (mActive) {
1982+ stop ();
1983+ }
1984+}
1985+
1986+void
1987+FocuspollScreen::setOptions ()
1988+{
1989+ a11ywatcher->setIgnoreLinks (optionGetIgnoreLinks ());
1990+}
1991+
1992+template class PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>;
1993+
1994+FocuspollScreen::FocuspollScreen (CompScreen *screen) :
1995+ PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI> (screen)
1996+{
1997+ a11ywatcher = new AccessibilityWatcher ();
1998+
1999+ CompSize screenLimit = getScreenLimits ();
2000+ a11ywatcher->setScreenLimits (screenLimit.width (), screenLimit.height ());
2001+
2002+ updateTimer ();
2003+ timer.setCallback (boost::bind (&FocuspollScreen::updatePosition, this));
2004+ optionSetFocusPollIntervalNotify (boost::bind (&FocuspollScreen::updateTimer, this));
2005+
2006+ optionSetIgnoreLinksNotify (boost::bind (&FocuspollScreen::setOptions, this));
2007+
2008+}
2009+
2010+FocuspollScreen::~FocuspollScreen ()
2011+{
2012+ delete a11ywatcher;
2013+}
2014+
2015+bool
2016+FocuspollPluginVTable::init ()
2017+{
2018+ if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION))
2019+ {
2020+ CompPrivate p;
2021+ p.uval = COMPIZ_FOCUSPOLL_ABI;
2022+ screen->storeValue ("focuspoll_ABI", p);
2023+ return true;
2024+ }
2025+ return false;
2026+}
2027+
2028+void
2029+FocuspollPluginVTable::fini ()
2030+{
2031+ screen->eraseValue ("focuspoll_ABI");
2032+}
2033
2034=== added file 'plugins/focuspoll/src/private.h'
2035--- plugins/focuspoll/src/private.h 1970-01-01 00:00:00 +0000
2036+++ plugins/focuspoll/src/private.h 2018-10-23 08:53:35 +0000
2037@@ -0,0 +1,75 @@
2038+/*
2039+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
2040+ *
2041+ * This file is part of compiz.
2042+ *
2043+ * this program is free software: you can redistribute it and/or modify
2044+ * it under the terms of the GNU General Public License as published by the Free
2045+ * Software Foundation, either version 3 of the License, or (at your option) any
2046+ * later version.
2047+ *
2048+ * This program is distributed in the hope that it will be useful, but
2049+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
2050+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2051+ * details.
2052+ *
2053+ * You should have received a copy of the GNU General Public License along
2054+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2055+ */
2056+
2057+#include <core/core.h>
2058+#include <core/pluginclasshandler.h>
2059+#include <core/timer.h>
2060+
2061+#include <focuspoll/focuspoll.h>
2062+#include <accessibilitywatcher/accessibilitywatcher.h>
2063+
2064+#include "focuspoll_options.h"
2065+
2066+typedef enum _FocuspollOptions
2067+{
2068+ MP_DISPLAY_OPTION_FOCUS_POLL_INTERVAL,
2069+ MP_DISPLAY_OPTION_NUM
2070+} FocuspollDisplayOptions;
2071+
2072+class FocuspollScreen;
2073+extern template class PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>;
2074+
2075+class FocuspollScreen :
2076+ public PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>,
2077+ public FocuspollOptions
2078+{
2079+ public:
2080+ FocuspollScreen (CompScreen *screen);
2081+ ~FocuspollScreen (void);
2082+
2083+ std::list <FocusPoller *> pollers;
2084+ CompTimer timer;
2085+ CompTimer settingsTimer;
2086+ CompRect focusRect;
2087+
2088+ bool updatePosition (void);
2089+ bool addTimer (FocusPoller *poller);
2090+ void removeTimer (FocusPoller *poller);
2091+ void updateTimer (void);
2092+
2093+ private:
2094+ AccessibilityWatcher* a11ywatcher;
2095+
2096+ CompSize getScreenLimits (void);
2097+ void setOptions (void);
2098+
2099+};
2100+
2101+#define FOCUSPOLL_SCREEN(s) \
2102+ FocuspollScreen *fs = FocuspollScreen::get (s)
2103+
2104+#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
2105+
2106+class FocuspollPluginVTable :
2107+ public CompPlugin::VTableForScreen <FocuspollScreen, COMPIZ_FOCUSPOLL_ABI>
2108+{
2109+ public:
2110+ bool init ();
2111+ void fini ();
2112+};
2113
2114=== modified file 'plugins/mousepoll/src/mousepoll.cpp'
2115--- plugins/mousepoll/src/mousepoll.cpp 2013-05-16 10:47:54 +0000
2116+++ plugins/mousepoll/src/mousepoll.cpp 2018-10-23 08:53:35 +0000
2117@@ -32,6 +32,7 @@
2118 int winX, winY;
2119 int w = screen->width (), h = screen->height ();
2120 unsigned int maskReturn;
2121+ unsigned int newbuttons;
2122
2123 bool status = XQueryPointer (screen->dpy (), screen->root (),
2124 &root, &child, &rootX, &rootY,
2125@@ -40,12 +41,25 @@
2126 if (!status || rootX > w || rootY > h || screen->root () != root)
2127 return false;
2128
2129+ newbuttons = maskReturn &
2130+ (Button1Mask |
2131+ Button2Mask |
2132+ Button3Mask |
2133+ Button4Mask |
2134+ Button5Mask);
2135+
2136 if (rootX != pos.x () || rootY != pos.y ())
2137 {
2138 pos.set (rootX, rootY);
2139 return true;
2140 }
2141
2142+ if (newbuttons != buttons)
2143+ {
2144+ buttons = newbuttons;
2145+ return true;
2146+ }
2147+
2148 return false;
2149 }
2150
2151
2152=== modified file 'plugins/mousepoll/src/private.h'
2153--- plugins/mousepoll/src/private.h 2013-06-28 10:21:21 +0000
2154+++ plugins/mousepoll/src/private.h 2018-10-23 08:53:35 +0000
2155@@ -49,6 +49,7 @@
2156 CompTimer timer;
2157
2158 CompPoint pos;
2159+ unsigned int buttons;
2160
2161 bool
2162 updatePosition ();
2163
2164=== modified file 'plugins/showmouse/showmouse.xml.in'
2165--- plugins/showmouse/showmouse.xml.in 2018-04-11 13:25:42 +0000
2166+++ plugins/showmouse/showmouse.xml.in 2018-10-23 08:53:35 +0000
2167@@ -11,6 +11,9 @@
2168 <plugin>cube</plugin>
2169 <plugin>decor</plugin>
2170 </relation>
2171+ <relation type="before">
2172+ <plugin>ezoom</plugin>
2173+ </relation>
2174 <requirement>
2175 <plugin>opengl</plugin>
2176 <plugin>mousepoll</plugin>
2177
2178=== modified file 'po/POTFILES.in'
2179--- po/POTFILES.in 2018-04-11 13:07:18 +0000
2180+++ po/POTFILES.in 2018-10-23 08:53:35 +0000
2181@@ -31,6 +31,7 @@
2182 plugins/fade/fade.xml.in
2183 plugins/fadedesktop/fadedesktop.xml.in
2184 plugins/firepaint/firepaint.xml.in
2185+plugins/focuspoll/focuspoll.xml.in
2186 plugins/freewins/freewins.xml.in
2187 plugins/gears/gears.xml.in
2188 plugins/gnomecompat/gnomecompat.xml.in

Subscribers

People subscribed via source and target branches