Merge lp:~ksamak/compiz/ezoom_focus_tracking into lp:compiz/0.9.13

Proposed by ksamak
Status: Needs review
Proposed branch: lp:~ksamak/compiz/ezoom_focus_tracking
Merge into: lp:compiz/0.9.13
Diff against target: 1617 lines (+1252/-39)
17 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 (+35/-8)
plugins/ezoom/src/ezoom.cpp (+123/-28)
plugins/ezoom/src/ezoom.h (+22/-2)
plugins/focuspoll/CMakeLists.txt (+9/-0)
plugins/focuspoll/compiz-focuspoll.pc.in (+12/-0)
plugins/focuspoll/focuspoll.xml.in (+34/-0)
plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h (+82/-0)
plugins/focuspoll/include/accessibilitywatcher/focusinfo.h (+76/-0)
plugins/focuspoll/include/focuspoll/focuspoll.h (+50/-0)
plugins/focuspoll/src/accessibilitywatcher.cpp (+506/-0)
plugins/focuspoll/src/focuspoll.cpp (+220/-0)
plugins/focuspoll/src/private.h (+75/-0)
plugins/showmouse/showmouse.xml.in (+3/-0)
To merge this branch: bzr merge lp:~ksamak/compiz/ezoom_focus_tracking
Reviewer Review Type Date Requested Status
ksamak (community) Needs Resubmitting
Andrea Azzarone Pending
Marco Trevisan (TreviƱo) Pending
Sam Spilsbury code, style Pending
Review via email: mp+321643@code.launchpad.net

This proposal supersedes a proposal from 2017-02-24.

Description of the change

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

To post a comment you must log in.
Revision history for this message
Andrea Azzarone (azzar1) wrote : Posted in a previous version of this proposal

Can you please link a bug to the MP?

Revision history for this message
Andrea Azzarone (azzar1) wrote : Posted in a previous version of this proposal

Ok this is the first part of the review. I'll continue later.

review: Needs Fixing
Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

Thanks for all the hard work in implementing this. There's a fair bit of work to be done before this can be integrated. In particular, this cannot be merged whilst it opens a separate display connection - that is a recipe for deadlocks.

review: Needs Fixing (code, style)
Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal
Revision history for this message
ksamak (ksamak) wrote : Posted in a previous version of this proposal

well, this plugin would indeed have been helpful.

Sam Spilsbury (smspillaz) wrote 18 hours ago:
I like the fact that this is a separate library, but this absolutely doesn't belong in the mousepoll plugin. The mouse poll plugin is supposed to do one thing and one thing only - update the position of the mouse every N milliseconds. Move all this into either a separate plugin or library.

I originally made a copy of the mousePoll plugin (called focusPoll), but for some unknown reason it... deadlocked, or froze at runtime. I couldn't find why, and no one had time to look at it, so I merged both logics in mousepoll, cause then, with sensibly the same methods, it didn't freeze.

I agree an additional plugin would serve better, so I'd appreciate some input on that problem.
there's a readyish branch on http://git.hypra.fr/hypra/compiz.git named focuspoll. Maybe you would have some insights as to what is blocking here. It might be, as you pointed, the call to open the display.
question: how would you circumvent the problem of the call to XOpenDisplay, if the whole a11ywatcher was a library? Is there a better way to get the screen info? that is without causing all those problems.

I will meditate on these ideas, and weight the pros and cons of the different solutions, and i'll then continue the work.
thank you for all the advice and corrections, including those pending below. I am available to discuss these matters further, or if you have more insight.

review: Needs Fixing
Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

> Maybe you would have some insights as to what is blocking here. It
> might be, as you pointed, the call to open the display.
> question: how would you circumvent the problem of the call to XOpenDisplay, if
> the whole a11ywatcher was a library? Is there a better way to get the screen
> info? that is without causing all those problems.

Yes, there's a global variable called 'screen' and you can access the current connection from there (screen->dpy()). If your library needs a reference to it, I'd suggest having the caller pass it in.

Revision history for this message
ksamak (ksamak) wrote : Posted in a previous version of this proposal

I've answered some of the following, and will superseed the merge request with the new version from the branch.
i thank you for your time, advice, and corrections.

Revision history for this message
ksamak (ksamak) : Posted in a previous version of this proposal
Revision history for this message
ksamak (ksamak) wrote : Posted in a previous version of this proposal

call for help on the matter of binding C callback to c++ member function.
thx

Revision history for this message
ksamak (ksamak) wrote : Posted in a previous version of this proposal

just a ping, i changed coding style again, on request from 3v1n0.

Revision history for this message
Andrea Azzarone (azzar1) wrote : Posted in a previous version of this proposal

I reviews part of the code. Fix it before I going on.

review: Needs Fixing
Revision history for this message
ksamak (ksamak) wrote : Posted in a previous version of this proposal

I fixed the code according to your specifications, i changed the whole coding style of my editor, thanks to the few lines i (finally) found here:
http://wiki.compiz.org/Development/CodingStyle
Sorry again for my misunderstanding tabs and coding style, i'll try my best to respect that from initial coding on, event though i find it less readable.
I thank you for your patience.

review: Needs Resubmitting
4122. By ksamak <ksamak@ksalaptop>

added precedence relation to start showmouse before ezoom

4123. By ksamak <ksamak@ksalaptop>

coding style

4124. By ksamak <ksamak@ksalaptop>

added rebranding of icedove to thunderbird support

Revision history for this message
ksamak (ksamak) wrote :

created lp:~ksamak/compiz/correct_mousetheme_targetting_for_ezoom, although i think it wasn't necessary. I'll delete it.
I add the update for the rebranding of icedove into thunderbird.
thank you.

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

Hello,

I have gone through the previous review from azzar1, and commented further.

Samuel

Revision history for this message
Samuel thibault (samuel-thibault) wrote : Posted in a previous version of this proposal

I have commented on the callback issue, to avoid the singleton.

Unmerged revisions

4124. By ksamak <ksamak@ksalaptop>

added rebranding of icedove to thunderbird support

4123. By ksamak <ksamak@ksalaptop>

coding style

4122. By ksamak <ksamak@ksalaptop>

added precedence relation to start showmouse before ezoom

4121. By ksamak <ksamak@ksalaptop>

modifications of code according to azzar's request

4120. By ksamak <ksamak@ksalaptop>

coding style modifs. changed if format on 3v1n0's request

4119. By ksamak <ksamak@ksalaptop>

coding style modifs

4118. By ksamak <ksamak@ksalaptop>

coding style modifs

4117. By ksamak <ksamak@ksalaptop>

minors code modifs

4116. By ksamak <ksamak@ksalaptop>

minors code modifs

4115. By ksamak <ksamak@ksalaptop>

corrected segfault, minors code modifs

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 2017-04-28 09:59:04 +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 2015-07-24 16:28:37 +0000
15+++ debian/compiz-plugins-default.install 2017-04-28 09:59:04 +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/*move.*
23 usr/share/compiz/*move.*
24 usr/lib/*/compiz/*opengl.*
25
26=== modified file 'debian/control'
27--- debian/control 2016-11-03 18:29:41 +0000
28+++ debian/control 2017-04-28 09:59:04 +0000
29@@ -14,6 +14,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 2017-04-28 09:59:04 +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 2016-07-01 15:37:19 +0000
50+++ plugins/ezoom/ezoom.xml.in 2017-04-28 09:59:04 +0000
51@@ -7,6 +7,7 @@
52 <plugin>expo</plugin>
53 <plugin>decor</plugin>
54 <plugin>mousepoll</plugin>
55+ <plugin>focuspoll</plugin>
56 </relation>
57 <relation type="before">
58 <plugin>staticswitcher</plugin>
59@@ -15,6 +16,7 @@
60 <requirement>
61 <plugin>opengl</plugin>
62 <plugin>mousepoll</plugin>
63+ <plugin>focuspoll</plugin>
64 </requirement>
65 </deps>
66 <_short>Enhanced Zoom Desktop</_short>
67@@ -425,12 +427,17 @@
68 <_short>Focus Tracking</_short>
69 <option type="bool" name="follow_focus">
70 <_short>Enable focus tracking</_short>
71- <_long>Move the zoom area when focus changes.</_long>
72+ <_long>Move the zoom area when focus changes. You may see the focuspoll module options for more</_long>
73 <default>true</default>
74 </option>
75+ <option type="bool" name="follow_window_focus">
76+ <_short>Enable focus tracking when switching windows</_short>
77+ <_long>Move the zoom area to the center of a newly switched window.</_long>
78+ <default>false</default>
79+ </option>
80 <option type="bool" name="focus_fit_window">
81- <_short>Fit zoom level to window on focus change</_short>
82- <_long>Fit the zoomed area to the window when the zoomed area moves as a result of focus tracking.</_long>
83+ <_short>Fit zoom level to newly switched window</_short>
84+ <_long>Fit the zoomed area to the window when it switches.</_long>
85 <default>false</default>
86 </option>
87 <option type="float" name="autoscale_min">
88@@ -443,16 +450,31 @@
89 </option>
90 <option type="bool" name="always_focus_fit_window">
91 <_short>Always fit to window on focus track</_short>
92- <_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>
93+ <_long>Fit the zoomed area to the newly switched window, even when not initially zoomed in. </_long>
94 <default>false</default>
95 </option>
96 <option type="int" name="follow_focus_delay">
97- <_short>Follow Focus Delay</_short>
98- <_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>
99- <default>0</default>
100+ <_short>tracking delay between mouse/keyboard modes</_short>
101+ <_long>Only attempt to follow focus if the mouse hasn't moved in this amount of seconds. Use this to avoid jumping everywhere on the screen.</_long>
102+ <default>1</default>
103 <min>0</min>
104 <max>15</max>
105 </option>
106+ <option type="bool" name="always_center_mouse">
107+ <_short>Keep Mouse centered inside zoom area</_short>
108+ <_long>If activated, the mouse will always be centered when in mouse sync mode, if not, it will move around the zoom area</_long>
109+ <default>true</default>
110+ </option>
111+ <option type="bool" name="restrain_zoom_to_screen">
112+ <_short>Restrain the zoom area inside the screen</_short>
113+ <_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>
114+ <default>true</default>
115+ </option>
116+ <option type="bool" name="warp_mouse_to_focus">
117+ <_short>when switching focus back to mouse, warp mouse inside the zoomed area</_short>
118+ <_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>
119+ <default>true</default>
120+ </option>
121 </group>
122 <group>
123 <_short>Animation</_short>
124@@ -461,7 +483,7 @@
125 <_long>Zoom Speed</_long>
126 <default>25</default>
127 <min>0.1</min>
128- <max>100</max>
129+ <max>500</max>
130 <precision>0.1</precision>
131 </option>
132 <option type="float" name="timestep">
133@@ -472,6 +494,11 @@
134 <max>50</max>
135 <precision>0.1</precision>
136 </option>
137+ <option type="bool" name="instant_pan">
138+ <_short>move the zoom area instantly</_short>
139+ <_long>when enabled, zoom area is warped instantly to focus position. When disabled, zoom area makes a smooth move to the focus position</_long>
140+ <default>false</default>
141+ </option>
142 </group>
143 </options>
144 </plugin>
145
146=== modified file 'plugins/ezoom/src/ezoom.cpp'
147--- plugins/ezoom/src/ezoom.cpp 2016-07-01 15:37:19 +0000
148+++ plugins/ezoom/src/ezoom.cpp 2017-04-28 09:59:04 +0000
149@@ -76,7 +76,6 @@
150
151 COMPIZ_PLUGIN_20090315 (ezoom, ZoomPluginVTable)
152
153-
154 /*
155 * This toggles paint functions. We don't need to continually run code when we
156 * are not doing anything.
157@@ -573,18 +572,45 @@
158 int y,
159 bool instant)
160 {
161+ setCenter (x, y, instant, false);
162+}
163+
164+void
165+EZoomScreen::setCenter (int x,
166+ int y,
167+ bool instant,
168+ bool center)
169+{
170 int out = screen->outputDeviceForPoint (x, y);
171 CompOutput *o = &screen->outputDevs ().at (out);
172
173 if (zooms.at (out).locked)
174 return;
175
176- zooms.at (out).xTranslate = (float)
177- ((x - o->x1 ()) - o->width () / 2) / (o->width ());
178- zooms.at (out).yTranslate = (float)
179- ((y - o->y1 ()) - o->height () / 2) / (o->height ());
180-
181- if (instant)
182+ // this correction allows constant centering of the mouse
183+ float zoomCorrection = std::max(1.0f - zooms.at (out).newZoom, 0.1f);
184+ if (!center) {
185+ zoomCorrection = EZOOM_SCREEN_BORDER_OFFSET;
186+ }
187+
188+ float zoom_x_inside = (float) (x - o->x1 () - o->width () / 2);
189+ // We need the zoom correction to center the mouse in zoomarea
190+ zoom_x_inside /= o->width () * zoomCorrection;
191+ float zoom_y_inside = (float) (y - o->y1 () - o->height () / 2);
192+ zoom_y_inside /= o->height () * zoomCorrection;
193+
194+ if (optionGetRestrainZoomToScreen ()) {
195+ if (zoom_x_inside < -0.5f) { zoom_x_inside = -0.5f;}
196+ if (zoom_x_inside > 0.5f) { zoom_x_inside = 0.5f;}
197+ if (zoom_y_inside < -0.5f) { zoom_y_inside = -0.5f;}
198+ if (zoom_y_inside > 0.5f) { zoom_y_inside = 0.5f;}
199+ }
200+
201+ zooms.at (out).xTranslate = zoom_x_inside;
202+ zooms.at (out).yTranslate = zoom_y_inside;
203+
204+
205+ if (instant || optionGetInstantPan ())
206 {
207 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
208 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
209@@ -671,11 +697,23 @@
210 void
211 EZoomScreen::enableMousePolling ()
212 {
213- pollHandle.start ();
214- lastChange = time(NULL);
215+ pollMouseHandle.start ();
216+ lastMouseChange = time (NULL);
217 mouse = MousePoller::getCurrentPosition ();
218 }
219
220+/* Enables polling of focus position */
221+void
222+EZoomScreen::enableFocusPolling ()
223+{
224+ if (!optionGetFollowFocus ())
225+ {
226+ return;
227+ }
228+ pollFocusHandle.start ();
229+ lastFocusChange = time (NULL);
230+}
231+
232 /* Sets the zoom (or scale) level.
233 * Cleans up if we are suddenly zoomed out.
234 */
235@@ -690,9 +728,12 @@
236 value = 1.0f;
237 else
238 {
239- if (!pollHandle.active ())
240+ if (!pollMouseHandle.active ())
241 enableMousePolling ();
242
243+ if (!pollFocusHandle.active ())
244+ enableFocusPolling ();
245+
246 grabbed |= (1 << zooms.at (out).output);
247 cursorZoomActive (out);
248 }
249@@ -738,7 +779,7 @@
250 {
251 int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
252
253- if (!isInMovement (out))
254+ if (!isInMovement (out) || nonMouseFocusTracking)
255 return;
256
257 CompOutput *o = &screen->outputDevs ().at (out);
258@@ -1026,7 +1067,7 @@
259
260 if (zooms.at (out).currentZoom == 1.0f)
261 {
262- lastChange = time(NULL);
263+ lastMouseChange = time (NULL);
264 mouse = MousePoller::getCurrentPosition ();
265 }
266
267@@ -1097,20 +1138,57 @@
268 void
269 EZoomScreen::updateMousePosition (const CompPoint &p)
270 {
271+ auto localTime = time (NULL);
272 mouse.setX (p.x ());
273 mouse.setY (p.y ());
274
275 int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
276- lastChange = time(NULL);
277-
278- if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
279- !isInMovement (out))
280- setCenter (mouse.x (), mouse.y (), true);
281-
282+
283+ if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse && !isInMovement (out))
284+ {
285+ if (optionGetWarpMouseToFocus ()) {
286+ // mouse taking back control of focus
287+ if (lastMouseChange <= lastFocusChange) {
288+ nonMouseFocusTracking = false;
289+ screen->warpPointer (mouse.x (), mouse.y ());
290+ restrainCursor (out);
291+ } else {
292+ setCenter (mouse.x (), mouse.y (), true, optionGetAlwaysCenterMouse ());
293+ }
294+ lastMouseChange = localTime;
295+ // respect a timing in case user has merely grazed his mouse
296+ } else if (localTime - lastFocusChange > optionGetFollowFocusDelay ()) {
297+ setCenter (mouse.x (), mouse.y (), true, optionGetAlwaysCenterMouse ());
298+ lastMouseChange = localTime;
299+ }
300+ }
301 cursorMoved ();
302 cScreen->damageScreen ();
303 }
304
305+void
306+EZoomScreen::updateFocusPosition (const CompRect &rect)
307+{
308+ auto localTime = time (NULL);
309+ int out = screen->outputDeviceForPoint (rect.x (), rect.y ());
310+ if (localTime - lastMouseChange > optionGetFollowFocusDelay ()) {
311+ int zoom_area_width = screen->outputDevs ().at (out).width () * zooms.at (out).currentZoom;
312+ int zoom_area_height = screen->outputDevs ().at (out).height () * zooms.at (out).currentZoom;
313+ int pos_x = rect.x ()+ rect.width () / 2;
314+ int pos_y = rect.y () + rect.height () / 2;
315+ if (rect.width () > zoom_area_width) { // corrects a too big target, aims at top-left corner
316+ pos_x -= (rect.width () - zoom_area_width) / 2;
317+ }
318+ if (rect.height () > zoom_area_height) {
319+ pos_y -= (rect.height () - zoom_area_height) / 2;
320+ }
321+ setCenter (pos_x, pos_y, false, true);
322+ nonMouseFocusTracking = true;
323+ lastFocusChange = localTime;
324+ }
325+ cScreen->damageScreen ();
326+}
327+
328 /* Timeout handler to poll the mouse. Returns false (and thereby does not
329 * get re-added to the queue) when zoom is not active. */
330 void
331@@ -1122,11 +1200,21 @@
332 {
333 cursorMoved ();
334
335- if (pollHandle.active ())
336- pollHandle.stop ();
337+ if (pollMouseHandle.active ())
338+ pollMouseHandle.stop ();
339 }
340 }
341
342+/* Timeout handler to focusPoll. */
343+void
344+EZoomScreen::updateFocusInterval (const CompRect &rect)
345+{
346+ updateFocusPosition (rect);
347+
348+ if (!grabbed && pollFocusHandle.active ())
349+ pollFocusHandle.stop ();
350+}
351+
352 /* Free a cursor */
353 void
354 EZoomScreen::freeCursor (CursorTexture *cursor)
355@@ -1878,8 +1966,8 @@
356
357 if (w == NULL ||
358 w->id () == screen->activeWindow () ||
359- time(NULL) - lastChange < optionGetFollowFocusDelay () ||
360- !optionGetFollowFocus ())
361+ time (NULL) - lastMouseChange < optionGetFollowFocusDelay () ||
362+ !optionGetFollowWindowFocus ())
363 return;
364
365 int out = screen->outputDeviceForGeometry (w->geometry ());
366@@ -1972,7 +2060,9 @@
367 gScreen (GLScreen::get (screen)),
368 grabbed (0),
369 grabIndex (0),
370- lastChange (0),
371+ lastMouseChange (0),
372+ lastFocusChange (0),
373+ nonMouseFocusTracking (false),
374 cursorInfoSelected (false),
375 cursorHidden (false)
376 {
377@@ -2006,8 +2096,10 @@
378 zooms.push_back (za);
379 }
380
381- pollHandle.setCallback (boost::bind (
382- &EZoomScreen::updateMouseInterval, this, _1));
383+ pollMouseHandle.setCallback (boost::bind(&EZoomScreen::updateMouseInterval,
384+ this, _1));
385+ pollFocusHandle.setCallback (boost::bind(&EZoomScreen::updateFocusInterval,
386+ this, _1));
387
388 optionSetZoomInButtonInitiate (boost::bind (&EZoomScreen::zoomIn, this, _1,
389 _2, _3));
390@@ -2100,8 +2192,10 @@
391
392 EZoomScreen::~EZoomScreen ()
393 {
394- if (pollHandle.active ())
395- pollHandle.stop ();
396+ if (pollMouseHandle.active ())
397+ pollMouseHandle.stop ();
398+ if (pollFocusHandle.active ())
399+ pollFocusHandle.stop ();
400
401 if (zooms.size ())
402 zooms.clear ();
403@@ -2116,7 +2210,8 @@
404 if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION) &&
405 CompPlugin::checkPluginABI ("composite", COMPIZ_COMPOSITE_ABI) &&
406 CompPlugin::checkPluginABI ("opengl", COMPIZ_OPENGL_ABI) &&
407- CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI))
408+ CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI) &&
409+ CompPlugin::checkPluginABI ("focuspoll", COMPIZ_FOCUSPOLL_ABI))
410 return true;
411
412 return false;
413
414=== modified file 'plugins/ezoom/src/ezoom.h'
415--- plugins/ezoom/src/ezoom.h 2016-07-01 15:37:19 +0000
416+++ plugins/ezoom/src/ezoom.h 2017-04-28 09:59:04 +0000
417@@ -43,12 +43,15 @@
418 #include <composite/composite.h>
419 #include <opengl/opengl.h>
420 #include <mousepoll/mousepoll.h>
421+#include <focuspoll/focuspoll.h>
422
423
424 #include "ezoom_options.h"
425
426 #include <cmath>
427
428+#define EZOOM_SCREEN_BORDER_OFFSET 0.92
429+
430 enum SpecificZoomTarget
431 {
432 ZoomTarget1 = 0,
433@@ -169,16 +172,19 @@
434 CompPoint mouse; // we get this from mousepoll
435 unsigned long int grabbed;
436 CompScreen::GrabHandle grabIndex; // for zoomBox
437- time_t lastChange;
438+ time_t lastMouseChange;
439+ time_t lastFocusChange;
440 CursorTexture cursor; // the texture for the faux-cursor
441 // we paint to do fake input
442 // handling
443+ bool nonMouseFocusTracking;
444 bool cursorInfoSelected;
445 bool cursorHidden;
446 CompRect box;
447 CompPoint clickPos;
448
449- MousePoller pollHandle; // mouse poller object
450+ MousePoller pollMouseHandle; // mouse poller object
451+ FocusPoller pollFocusHandle; // focus poller object
452
453 private:
454
455@@ -231,6 +237,11 @@
456 setCenter (int x,
457 int y,
458 bool instant);
459+ void
460+ setCenter (int x,
461+ int y,
462+ bool instant,
463+ bool center);
464
465 void
466 setZoomArea (int x,
467@@ -250,6 +261,9 @@
468 enableMousePolling ();
469
470 void
471+ enableFocusPolling ();
472+
473+ void
474 setScale (int out,
475 float value);
476
477@@ -295,6 +309,12 @@
478 void
479 updateMouseInterval (const CompPoint &p);
480
481+ void
482+ updateFocusPosition (const CompRect &retc);
483+
484+ void
485+ updateFocusInterval (const CompRect &rect);
486+
487 /* Make dtor */
488 void
489 freeCursor (CursorTexture * cursor);
490
491=== added directory 'plugins/focuspoll'
492=== added file 'plugins/focuspoll/CMakeLists.txt'
493--- plugins/focuspoll/CMakeLists.txt 1970-01-01 00:00:00 +0000
494+++ plugins/focuspoll/CMakeLists.txt 2017-04-28 09:59:04 +0000
495@@ -0,0 +1,9 @@
496+find_package (Compiz REQUIRED)
497+
498+include (CompizPlugin)
499+include_directories (include/accessibilitywatcher)
500+
501+pkg_search_module( ATSPI REQUIRED atspi-2 )
502+
503+compiz_plugin (focuspoll PKGDEPS atspi-2 )
504+
505
506=== added file 'plugins/focuspoll/compiz-focuspoll.pc.in'
507--- plugins/focuspoll/compiz-focuspoll.pc.in 1970-01-01 00:00:00 +0000
508+++ plugins/focuspoll/compiz-focuspoll.pc.in 2017-04-28 09:59:04 +0000
509@@ -0,0 +1,12 @@
510+prefix=@prefix@
511+exec_prefix=@exec_prefix@
512+libdir=@libdir@
513+includedir=@includedir@
514+
515+Name: compiz-focuspoll
516+Description: Focuspoll plugin for compiz
517+Version: @VERSION@
518+
519+Requires: compiz
520+Libs: -L${libdir}/compiz -lfocuspoll
521+Cflags: @COMPIZ_CFLAGS@ -I${includedir}/compiz
522
523=== added file 'plugins/focuspoll/focuspoll.xml.in'
524--- plugins/focuspoll/focuspoll.xml.in 1970-01-01 00:00:00 +0000
525+++ plugins/focuspoll/focuspoll.xml.in 2017-04-28 09:59:04 +0000
526@@ -0,0 +1,34 @@
527+<?xml version="1.0" encoding="UTF-8"?>
528+<compiz>
529+ <plugin name="focuspoll" useBcop="true">
530+ <_short>Focus position polling</_short>
531+ <_long>Updates the focus pointer position from the accessibility library AT-SPI through dbus</_long>
532+ <category>Utility</category>
533+ <options>
534+ <_short>Misc</_short>
535+ <option type="bool" name="track_components">
536+ <_short>track movement of components (like a menu, label, or button)</_short>
537+ <_long>if enabled, focuspoll will track the movements of every component able to provide focus.</_long>
538+ <default>true</default>
539+ </option>
540+ <option type="bool" name="track_caret">
541+ <_short>track movement of the caret (the cursor in text)</_short>
542+ <_long>if enabled, focuspoll will track the movements of the caret.</_long>
543+ <default>true</default>
544+ </option>
545+ <option type="bool" name="ignore_links">
546+ <_short>ignores link focuses</_short>
547+ <_long>if enabled, focuspoll will not track the focus events of link items</_long>
548+ <default>true</default>
549+ </option>
550+ <option type="int" name="focus_poll_interval">
551+ <_short>Focus Poll Interval</_short>
552+ <_long>How often to poll DBus for focus changes, in milliseconds. Reduce this to reduce choppy behavior.</_long>
553+ <default>10</default>
554+ <min>1</min>
555+ <max>500</max>
556+ </option>
557+ </options>
558+ </plugin>
559+</compiz>
560+
561
562=== added directory 'plugins/focuspoll/include'
563=== added directory 'plugins/focuspoll/include/accessibilitywatcher'
564=== added file 'plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h'
565--- plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 1970-01-01 00:00:00 +0000
566+++ plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 2017-04-28 09:59:04 +0000
567@@ -0,0 +1,82 @@
568+/*
569+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
570+ *
571+ * This file is part of compiz.
572+ *
573+ * this program is free software: you can redistribute it and/or modify
574+ * it under the terms of the GNU General Public License as published by the Free
575+ * Software Foundation, either version 3 of the License, or (at your option) any
576+ * later version.
577+ *
578+ * This program is distributed in the hope that it will be useful, but
579+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
580+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
581+ * details.
582+ *
583+ * You should have received a copy of the GNU General Public License along
584+ * with this program. If not, see <http://www.gnu.org/licenses/>.
585+ */
586+
587+#ifndef ACCESSIBILITY_WATCHER_H
588+#define ACCESSIBILITY_WATCHER_H
589+
590+#include <deque>
591+#include <vector>
592+
593+#include "focusinfo.h"
594+
595+#include <atspi/atspi.h>
596+
597+class AccessibilityWatcher {
598+ public:
599+ ~AccessibilityWatcher();
600+ static AccessibilityWatcher* getInstance();
601+
602+ void init();
603+ void quit();
604+
605+ void setIgnoreLinks(bool);
606+ void setScreenLimits(int, int);
607+ int getScreenWidth();
608+ int getScreenHeight();
609+
610+ std::deque<FocusInfo> getFocusQueue();
611+ void resetFocusQueue();
612+ bool returnToPrevMenu();
613+
614+ private:
615+ AccessibilityWatcher();
616+ static AccessibilityWatcher* instance;
617+
618+ bool initialized;
619+ int screenWidth;
620+ int screenHeight;
621+ static bool ignoreLinks;
622+ std::deque<FocusInfo> focusList;
623+ std::vector<FocusInfo> previouslyActiveMenus;
624+
625+ AtspiEventListener *focusListener;
626+ AtspiEventListener *caretMoveListener;
627+ AtspiEventListener *selectedListener;
628+ AtspiEventListener *descendantChangedListener;
629+
630+ void addWatches();
631+ void removeWatches();
632+
633+ static std::string getLabel(AtspiAccessible *accessible);
634+ const static gchar* atspiStateGetName (gint state);
635+ static std::string getStateSet (AtspiAccessible *accessible);
636+
637+ static void registerEvent(const AtspiEvent *event, const std::string& type);
638+ static bool appSpecificFilter(FocusInfo& focusInfo, const AtspiEvent* event);
639+ static bool filterBadEvents(const FocusInfo& event);
640+ static void getAlternativeCaret(FocusInfo& focus, const AtspiEvent* event);
641+
642+ static void onFocus(const AtspiEvent *event, void *data);
643+ static void onSelectedChange(const AtspiEvent *event, void *data);
644+ static void onCaretMove(const AtspiEvent *event, void *data);
645+ static void onDescendantChanged(const AtspiEvent *event, void *data);
646+
647+};
648+
649+#endif
650
651=== added file 'plugins/focuspoll/include/accessibilitywatcher/focusinfo.h'
652--- plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 1970-01-01 00:00:00 +0000
653+++ plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 2017-04-28 09:59:04 +0000
654@@ -0,0 +1,76 @@
655+/*
656+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
657+ *
658+ * This file is part of compiz.
659+ *
660+ * this program is free software: you can redistribute it and/or modify
661+ * it under the terms of the GNU General Public License as published by the Free
662+ * Software Foundation, either version 3 of the License, or (at your option) any
663+ * later version.
664+ *
665+ * This program is distributed in the hope that it will be useful, but
666+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
667+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
668+ * details.
669+ *
670+ * You should have received a copy of the GNU General Public License along
671+ * with this program. If not, see <http://www.gnu.org/licenses/>.
672+ */
673+
674+#ifndef FOCUS_INFO_H
675+#define FOCUS_INFO_H
676+
677+#include <tuple>
678+#include <string>
679+#include <sstream>
680+
681+
682+class FocusInfo {
683+ public:
684+
685+ FocusInfo(const std::string& type,
686+ const std::string& name,
687+ const std::string& label,
688+ const std::string& role,
689+ const std::string& application,
690+ int x,
691+ int y,
692+ int width,
693+ int height);
694+
695+ FocusInfo(const std::string& type,
696+ const std::string& name,
697+ const std::string& label,
698+ const std::string& role,
699+ const std::string& application);
700+
701+ FocusInfo();
702+
703+ int x, y, w, h;
704+ int xAlt, yAlt, wAlt, hAlt;
705+ std::string type;
706+ std::string name;
707+ std::string label;
708+ std::string role;
709+ std::string application;
710+
711+ // AT-SPI events that are interesting to know about the event
712+ bool enabled;
713+ bool active;
714+ bool focusable;
715+ bool focused;
716+ bool selected;
717+ bool showing;
718+ bool visible;
719+
720+ std::string getType();
721+ std::string summary();
722+ std::tuple<int, int> getPosition();
723+ std::tuple<int, int> getSize();
724+ std::tuple<int, int, int, int> getBBox();
725+
726+ bool operator== (const FocusInfo& other) const;
727+ bool operator!= (const FocusInfo& other) const;
728+};
729+
730+#endif
731
732=== added directory 'plugins/focuspoll/include/focuspoll'
733=== added file 'plugins/focuspoll/include/focuspoll/focuspoll.h'
734--- plugins/focuspoll/include/focuspoll/focuspoll.h 1970-01-01 00:00:00 +0000
735+++ plugins/focuspoll/include/focuspoll/focuspoll.h 2017-04-28 09:59:04 +0000
736@@ -0,0 +1,50 @@
737+/*
738+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
739+ *
740+ * This file is part of compiz.
741+ *
742+ * this program is free software: you can redistribute it and/or modify
743+ * it under the terms of the GNU General Public License as published by the Free
744+ * Software Foundation, either version 3 of the License, or (at your option) any
745+ * later version.
746+ *
747+ * This program is distributed in the hope that it will be useful, but
748+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
749+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
750+ * details.
751+ *
752+ * You should have received a copy of the GNU General Public License along
753+ * with this program. If not, see <http://www.gnu.org/licenses/>.
754+ */
755+
756+#ifndef _COMPIZ_FOCUSPOLL_H
757+#define _COMPIZ_FOCUSPOLL_H
758+
759+#define COMPIZ_FOCUSPOLL_ABI 1
760+
761+#include <boost/function.hpp>
762+#include <core/rect.h>
763+#include <accessibilitywatcher/focusinfo.h>
764+
765+class FocusPoller
766+{
767+ public:
768+ typedef boost::function<void(const CompRect &)> CallBack;
769+
770+ FocusPoller ();
771+ ~FocusPoller ();
772+
773+ void setCallback (CallBack callback);
774+ void start ();
775+ void stop ();
776+ bool active ();
777+
778+ private:
779+ bool mActive;
780+ CallBack mCallback;
781+ CompRect focusRect;
782+
783+ friend class FocuspollScreen;
784+};
785+
786+#endif
787
788=== added directory 'plugins/focuspoll/src'
789=== added file 'plugins/focuspoll/src/accessibilitywatcher.cpp'
790--- plugins/focuspoll/src/accessibilitywatcher.cpp 1970-01-01 00:00:00 +0000
791+++ plugins/focuspoll/src/accessibilitywatcher.cpp 2017-04-28 09:59:04 +0000
792@@ -0,0 +1,506 @@
793+/*
794+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
795+ *
796+ * This file is part of compiz.
797+ *
798+ * this program is free software: you can redistribute it and/or modify
799+ * it under the terms of the GNU General Public License as published by the Free
800+ * Software Foundation, either version 3 of the License, or (at your option) any
801+ * later version.
802+ *
803+ * This program is distributed in the hope that it will be useful, but
804+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
805+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
806+ * details.
807+ *
808+ * You should have received a copy of the GNU General Public License along
809+ * with this program. If not, see <http://www.gnu.org/licenses/>.
810+ */
811+
812+#include <iostream>
813+#include <functional>
814+
815+#include <boost/mem_fn.hpp>
816+#include <boost/bind.hpp>
817+#include <boost/function.hpp>
818+
819+#include <string.h>
820+#include <stdlib.h>
821+#include <unistd.h>
822+
823+#include "accessibilitywatcher.h"
824+
825+namespace {
826+const int A11YWATCHER_MAX_CARET_WIDTH = 50;
827+const int A11YWATCHER_MAX_CARET_HEIGHT = 70;
828+}
829+
830+AccessibilityWatcher* AccessibilityWatcher::instance = NULL;
831+bool AccessibilityWatcher::ignoreLinks = false;
832+
833+AccessibilityWatcher::AccessibilityWatcher() :
834+ initialized(false),
835+ screenWidth(0),
836+ screenHeight(0)
837+{
838+}
839+
840+AccessibilityWatcher::~AccessibilityWatcher() {
841+ quit();
842+};
843+
844+AccessibilityWatcher *AccessibilityWatcher::getInstance () {
845+ if (instance == NULL)
846+ {
847+ instance = new AccessibilityWatcher();
848+ }
849+ return instance;
850+}
851+
852+std::string AccessibilityWatcher::getLabel(AtspiAccessible *accessible) {
853+ GArray *relations = atspi_accessible_get_relation_set (accessible, NULL);
854+ if (relations == NULL)
855+ {
856+ return "";
857+ }
858+
859+ AtspiRelation *relation;
860+ for (unsigned i = 0; i < relations->len; ++i) {
861+ relation = g_array_index (relations, AtspiRelation*, i);
862+ if (atspi_relation_get_relation_type (relation) == ATSPI_RELATION_LABELLED_BY)
863+ {
864+ g_array_free (relations, true);
865+ g_object_unref(relations);
866+ auto res_label = atspi_accessible_get_name(atspi_relation_get_target(relation, 0), NULL);
867+ g_object_unref(relation);
868+ return (res_label == NULL) ? "" : res_label;
869+ }
870+ }
871+ if (relations->len > 0)
872+ {
873+ g_object_unref(relations);
874+ }
875+ return "";
876+}
877+
878+void AccessibilityWatcher::setIgnoreLinks(bool val) {
879+ ignoreLinks = val;
880+}
881+
882+void AccessibilityWatcher::setScreenLimits(int x, int y) {
883+ screenWidth = x;
884+ screenHeight = y;
885+}
886+
887+int AccessibilityWatcher::getScreenWidth () {
888+ return screenWidth;
889+}
890+
891+int AccessibilityWatcher::getScreenHeight () {
892+ return screenHeight;
893+}
894+
895+void AccessibilityWatcher::registerEvent(const AtspiEvent *event, const std::string &type) {
896+ // type is registered from filter on calling event
897+ FocusInfo res(type,
898+ atspi_accessible_get_name (event->source, NULL),
899+ getLabel (event->source),
900+ atspi_accessible_get_role_name (event->source, NULL),
901+ atspi_accessible_get_name(atspi_accessible_get_application (event->source, NULL), NULL));
902+
903+ if (!res.active)
904+ {
905+ // prevents skipping events that are not designated as active. we check the parent.
906+ auto parent = atspi_accessible_get_parent(event->source, NULL);
907+ while (!res.active && parent) {
908+ auto stateSet = atspi_accessible_get_state_set (child);
909+ if (atspi_state_set_contains(stateSet, ATSPI_STATE_ACTIVE))
910+ {
911+ res.active = true;
912+ }
913+ g_object_unref(stateSet);
914+ g_object_unref(parent);
915+ child = atspi_accessible_get_parent(child, NULL);
916+ }
917+ g_object_unref(parent);
918+ }
919+
920+ AtspiComponent* component;
921+ if (res.type == "active-descendant-changed")
922+ {
923+ auto accessible = atspi_accessible_get_child_at_index(event->source, event->detail1, NULL);
924+ component = atspi_accessible_get_component(accessible);
925+ g_object_unref(accessible);
926+ } else {
927+ component = atspi_accessible_get_component(event->source);
928+ }
929+
930+ if (component)
931+ {
932+ AtspiRect* size = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
933+ res.x = size->x;
934+ res.y = size->y;
935+ res.w = size->width;
936+ res.h = size->height;
937+ g_object_unref(component);
938+ }
939+ // let's get the caret offset, and then its position for a caret event
940+ if (type == "caret")
941+ {
942+ auto text = atspi_accessible_get_text(event->source);
943+ if (!text)
944+ {
945+ return;
946+ }
947+ auto offset = atspi_text_get_caret_offset(text, NULL);
948+ // if we're at the beginning of text, let the widget pos decide
949+ if (event->detail1)
950+ {
951+ AtspiRect *size = atspi_text_get_character_extents(text, offset, ATSPI_COORD_TYPE_SCREEN, NULL);
952+ res.x = size->x;
953+ res.y = size->y;
954+ res.w = size->width;
955+ res.h = size->height;
956+ }
957+ // correcting a missing offset to a caret move event in firefox
958+ if (res.x == 0 && res.y == 0 && offset > 0)
959+ {
960+ AtspiRect *size = atspi_text_get_character_extents(text, offset-1, ATSPI_COORD_TYPE_SCREEN, NULL);
961+ res.x = size->x;
962+ res.y = size->y;
963+ res.w = size->width;
964+ res.h = size->height;
965+ }
966+ // when result is obviously not a caret size
967+ if (strcmp(event->type, "object:text-caret-moved") == 0 && (res.w > A11YWATCHER_MAX_CARET_WIDTH || res.h > A11YWATCHER_MAX_CARET_HEIGHT))
968+ {
969+ AtspiRect *size = atspi_text_get_character_extents(text, offset, ATSPI_COORD_TYPE_SCREEN, NULL);
970+ res.x = size->x;
971+ res.y = size->y;
972+ res.w = size->width;
973+ res.h = size->height;
974+ if (type == "caret" && strcmp(event->type, "object:text-caret-moved") == 0 && (res.w > A11YWATCHER_MAX_CARET_WIDTH || res.h > A11YWATCHER_MAX_CARET_HEIGHT))
975+ {
976+ res.x = 0;
977+ res.y = 0;
978+ }
979+ }
980+ g_object_unref(text);
981+
982+ // still no offset, it's probably a newline and we're at bugzilla #1319273 (with new paragraph obj)
983+ if (res.x == 0 && res.y == 0 &&
984+ (strcmp(event->type, "object:text-changed:insert") == 0 ||
985+ strcmp(event->type, "object:text-changed:removed") == 0 ||
986+ strcmp(event->type, "object:text-caret-moved") == 0)) {
987+ res.x = res.xAlt;
988+ res.y = res.yAlt;
989+ res.w = res.wAlt;
990+ res.h = res.hAlt;
991+ }
992+ }
993+
994+ // getting the states on event
995+ auto stateSet = atspi_accessible_get_state_set (event->source);
996+ if (atspi_state_set_contains(stateSet, ATSPI_STATE_FOCUSED))
997+ {
998+ res.focused = true;
999+ AccessibilityWatcher::getInstance()->previouslyActiveMenus.clear(); // reset potential menu stack
1000+ }
1001+ if (atspi_state_set_contains(stateSet, ATSPI_STATE_SELECTED))
1002+ { // never works, GTK Bad implem?
1003+ res.selected = true;
1004+ }
1005+ if (res.type == "state-changed:selected" && event->detail1 == 1)
1006+ {
1007+ res.selected = true;
1008+ AccessibilityWatcher::getInstance()->previouslyActiveMenus.push_back(res); // add to stack of menus
1009+ }
1010+
1011+ if (appSpecificFilter(res, event))
1012+ {
1013+ return;
1014+ }
1015+ if (filterBadEvents(res))
1016+ {
1017+ return;
1018+ }
1019+ while (AccessibilityWatcher::getInstance()->focusList.size() >= 5) { // don't keep the whole history
1020+ AccessibilityWatcher::getInstance()->focusList.erase(AccessibilityWatcher::getInstance()->focusList.begin());
1021+ }
1022+ AccessibilityWatcher::getInstance()->focusList.push_back(res);
1023+}
1024+
1025+bool AccessibilityWatcher::appSpecificFilter(FocusInfo& focus, const AtspiEvent* event)
1026+{
1027+ if (focus.type == "state-changed:selected" && // emulates on-change:selected false behaviour. eg: for menus
1028+ (focus.role == "menu item" ||
1029+ focus.role == "menu" ||
1030+ focus.role == "check menu item" ||
1031+ focus.role == "radio menu item") &&
1032+ focus.application != "mate-panel")
1033+ {
1034+ if (!focus.selected && AccessibilityWatcher::getInstance()->returnToPrevMenu())
1035+ {
1036+ return true;
1037+ }
1038+ focus.active = true;
1039+ }
1040+ if (focus.application == "soffice" && focus.role == "paragraph")
1041+ { // LO-calc: avoid spam event from main edit line
1042+ auto parent = atspi_accessible_get_parent(event->source, NULL);
1043+ std::string parentLabel = atspi_accessible_get_name(parent, NULL);
1044+ if (parentLabel == "Input line" ||
1045+ parentLabel == "Ligne de saisie") {
1046+ return true;
1047+ }
1048+ }
1049+ if (focus.application == "Icedove" || focus.application == "Thunderbird")
1050+ {
1051+ if (focus.type == "caret")
1052+ {
1053+ auto text = atspi_accessible_get_text(event->source); // next if deals with a special newline char, that remained buggy. hypra issue #430
1054+ auto offset = atspi_text_get_caret_offset(text, NULL);
1055+ auto string = std::string(atspi_text_get_text_at_offset(text, offset, ATSPI_TEXT_BOUNDARY_CHAR, NULL)->content);
1056+ auto stringM1 = std::string(atspi_text_get_text_at_offset(text, offset -1, ATSPI_TEXT_BOUNDARY_CHAR, NULL)->content);
1057+ if (offset == atspi_text_get_character_count(text, NULL) && string == "\0" && (stringM1 == "\n" || int(stringM1.c_str()[0]) == -17))
1058+ {
1059+ getAlternativeCaret(focus, event);
1060+ focus.x = focus.xAlt;
1061+ focus.y = focus.yAlt + focus.hAlt;
1062+ focus.w = focus.wAlt;
1063+ focus.h = focus.hAlt;
1064+ }
1065+ if (!(focus.x == 0 && focus.y == 0))
1066+ { // prevents compose window loss of tracking in HTML mode (active flag ok, but no focused flag)
1067+ AccessibilityWatcher::getInstance()->focusList.push_back(focus);
1068+ return true;
1069+ }
1070+ auto component = atspi_accessible_get_component(event->source);
1071+ if (component)
1072+ {
1073+ AtspiRect* size = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
1074+ focus.x = size->x;
1075+ focus.y = size->y;
1076+ focus.w = 7;
1077+ focus.h = size->height;
1078+ AccessibilityWatcher::getInstance()->focusList.push_back(focus);
1079+ return true;
1080+ }
1081+ }
1082+ }
1083+ if (focus.application == "Firefox")
1084+ {
1085+ if (ignoreLinks && focus.type != "caret" && focus.role == "link")
1086+ {
1087+ return true;
1088+ }
1089+ if (focus.type == "caret" &&
1090+ (static_cast<std::string>(event->type) == "object:text-changed:insert:system" ||
1091+ static_cast<std::string>(event->type) == "object:text-changed:delete:system")) { // prevents status bar focus in firefox
1092+ return true;
1093+ }
1094+ if (focus.type == "focus" && focus.role == "document frame")
1095+ { // general page parasite event
1096+ return true;
1097+ }
1098+ if (focus.type == "caret" && !(focus.x == 0 && focus.y == 0))
1099+ {
1100+ AccessibilityWatcher::getInstance()->focusList.push_back(focus);
1101+ return true;
1102+ }
1103+ getAlternativeCaret(focus, event);
1104+ if (focus.type == "caret" && !(focus.xAlt == 0 && focus.yAlt == 0))
1105+ {
1106+ focus.x = focus.xAlt;
1107+ focus.y = focus.yAlt + focus.hAlt;
1108+ focus.w = focus.wAlt;
1109+ focus.h = focus.hAlt;
1110+ AccessibilityWatcher::getInstance()->focusList.push_back(focus);
1111+ return true;
1112+ }
1113+ }
1114+ if (focus.application == "evince" && focus.type == "state-changed:selected" && focus.role == "icon")
1115+ { // LO-calc: avoid spam event from main edit line
1116+ return true; // ignores the parasite event from evince icon
1117+ }
1118+ return false;
1119+}
1120+
1121+bool AccessibilityWatcher::filterBadEvents(const FocusInfo& event) {
1122+ if (event.type == "caret" && event.x ==0 && event.y == 0)
1123+ {
1124+ return true;
1125+ }
1126+ if (!event.active)
1127+ {
1128+ return true;
1129+ }
1130+ if (!event.focused && !event.selected)
1131+ {
1132+ return true;
1133+ }
1134+ if (AccessibilityWatcher::getInstance()->getScreenWidth() != 0 && AccessibilityWatcher::getInstance()->getScreenHeight() !=0 &&
1135+ (event.x > AccessibilityWatcher::getInstance()->getScreenWidth() ||
1136+ event.y > AccessibilityWatcher::getInstance()->getScreenHeight() || // TODO remove when non-singleton
1137+ event.x < 0 ||
1138+ event.y < 0 )) {
1139+ return true;
1140+ }
1141+ return false;
1142+}
1143+
1144+/*
1145+ * this is meant to emulate the missing "selected" argument in menu hierarchy.
1146+ * no idea whether this is a fail in GTK, or in ATSPI
1147+ */
1148+bool AccessibilityWatcher::returnToPrevMenu() {
1149+ if (previouslyActiveMenus.size() > 1)
1150+ {
1151+ previouslyActiveMenus.pop_back();
1152+ focusList.push_back(previouslyActiveMenus.back());
1153+ return true;
1154+ }
1155+ return false;
1156+}
1157+
1158+/*
1159+ * Tries to extrapolate a missing caret position from other text characters.
1160+ * is used as last resort when application doesn't respect at-spi standarts,
1161+ * or at-spi bugs.
1162+ */
1163+void AccessibilityWatcher::getAlternativeCaret(FocusInfo& focus, const AtspiEvent* event) {
1164+ auto text = atspi_accessible_get_text(event->source);
1165+ if (!text) { return; }
1166+ auto offset = atspi_text_get_caret_offset(text, NULL);
1167+ auto caretChar = std::string(atspi_text_get_string_at_offset(text, offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content);
1168+ bool hasSeenDeviceControl1 = false;
1169+
1170+ // if we're at a newline, sometimes at-spi isn't giving us a caret position. unknown bug in some apps.
1171+ if (caretChar == "\n" || caretChar == "\0")
1172+ {
1173+ // gives the last empty line the right focus.
1174+ int lines = atspi_text_get_character_count(text, NULL) == offset ? 1 : 0;
1175+ int charIndex = 1;
1176+ bool charExtentsFound = false;
1177+
1178+ AtspiRect *size = atspi_text_get_character_extents(text, offset, ATSPI_COORD_TYPE_SCREEN, NULL);
1179+ // try and find the character on upper line to extrapolate position from. no more that 300 char, we risk lag.
1180+ while (!charExtentsFound && charIndex <= offset && charIndex < 300) {
1181+ size = atspi_text_get_character_extents(text, offset - charIndex, ATSPI_COORD_TYPE_SCREEN, NULL);
1182+ caretChar = atspi_text_get_string_at_offset(text, offset - charIndex, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content;
1183+ // if we found a caret, check we're at beginning of line (or of text) to extrapolate position
1184+ if ((size->x != 0 || size->y != 0) && int(caretChar[0]) != -17)
1185+ {
1186+ if (offset - charIndex -1 >= 0 && std::string(atspi_text_get_string_at_offset(text, offset - charIndex -1, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content) == "\n")
1187+ {
1188+ charExtentsFound = true; // first character of upper line has been found
1189+ }
1190+ else if (offset - charIndex -1 == 0)
1191+ {
1192+ size = atspi_text_get_character_extents(text, 0, ATSPI_COORD_TYPE_SCREEN, NULL);
1193+ // first character of upper line has been found
1194+ charExtentsFound = true;
1195+ }
1196+ }
1197+ else if (caretChar == "\n" || int(caretChar[0]) == -17)
1198+ {
1199+ ++lines;
1200+ }
1201+ if (!hasSeenDeviceControl1 && int(caretChar[0]) == -17 )
1202+ {
1203+ hasSeenDeviceControl1 = true;
1204+ }
1205+ ++charIndex;
1206+ }
1207+ if (!hasSeenDeviceControl1)
1208+ {
1209+ lines--;
1210+ }
1211+ focus.xAlt = size->x;
1212+ focus.yAlt = size->y + (lines-1) * size->height;
1213+ focus.wAlt = size->width;
1214+ focus.hAlt = size->height;
1215+ }
1216+}
1217+
1218+
1219+void AccessibilityWatcher::onCaretMove (const AtspiEvent *event, void *data) {
1220+ registerEvent(event, "caret");
1221+}
1222+
1223+void AccessibilityWatcher::onSelectedChange (const AtspiEvent *event, void *data) {
1224+ registerEvent(event, "state-changed:selected");
1225+}
1226+
1227+void AccessibilityWatcher::onFocus (const AtspiEvent *event, void *data) {
1228+ /* We only care about focus/selection gain
1229+ * there's no detail1 on focus loss in AT-SPI specs */
1230+ if (!event->detail1) {return;}
1231+
1232+ registerEvent(event, "focus");
1233+}
1234+
1235+void AccessibilityWatcher::onDescendantChanged (const AtspiEvent *event, void *data) {
1236+ registerEvent(event, "active-descendant-changed");
1237+}
1238+
1239+/* Register to events */
1240+void AccessibilityWatcher::addWatches() {
1241+
1242+ //auto callback = boost::bind (&AccessibilityWatcher::onFocus, this, _1, _2);
1243+ //auto bound_fn = boost::bind<void>( boost::mem_fn(&AccessibilityWatcher::onFocus), this, _1, _2);
1244+ //auto bound_fn = boost::bind( &AccessibilityWatcher::onFocus, this, _1, _2);
1245+ //boost::function<void (const AtspiEvent *event, void *data)> callback(functor, this);
1246+
1247+
1248+ //std::function<void (AtspiEvent*, void*)> bound_fn = std::bind(&AccessibilityWatcher::onFocus, this, std::placeholders::_1, std::placeholders::_2);
1249+ //using sig = void (AtspiEvent*, void*);
1250+ //using tamere = std::function<sig>;
1251+ //AtspiEventListenerCB bound_fn = std::bind(&AccessibilityWatcher::onFocus, this, std::placeholders::_1, std::placeholders::_2);
1252+ //focusListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(bound_fn), NULL, NULL);
1253+
1254+ focusListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onFocus), NULL, NULL);
1255+ caretMoveListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onCaretMove), NULL, NULL);
1256+ selectedListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onSelectedChange), NULL, NULL);
1257+ descendantChangedListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onDescendantChanged), NULL, NULL);
1258+
1259+ atspi_event_listener_register (focusListener, "object:state-changed:focused", NULL);
1260+ atspi_event_listener_register (caretMoveListener, "object:text-caret-moved", NULL);
1261+ atspi_event_listener_register (caretMoveListener, "object:text-changed:inserted", NULL);
1262+ atspi_event_listener_register (caretMoveListener, "object:text-changed:removed", NULL);
1263+ atspi_event_listener_register (selectedListener, "object:state-changed:selected", NULL);
1264+ atspi_event_listener_register (descendantChangedListener, "object:active-descendant-changed", NULL);
1265+}
1266+
1267+void AccessibilityWatcher::removeWatches() {
1268+ atspi_event_listener_deregister (focusListener, "object:state-changed:focused", NULL);
1269+ atspi_event_listener_deregister (caretMoveListener, "object:text-caret-moved", NULL);
1270+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:inserted", NULL);
1271+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:removed", NULL);
1272+ atspi_event_listener_deregister (selectedListener, "object:state-changed:selected", NULL);
1273+ atspi_event_listener_deregister (descendantChangedListener, "object:active-descendant-changed", NULL);
1274+}
1275+
1276+void AccessibilityWatcher::init() {
1277+ if (initialized) { return; }
1278+
1279+ atspi_init ();
1280+ atspi_set_main_context(g_main_context_default());
1281+ addWatches();
1282+
1283+ initialized = true;
1284+}
1285+
1286+void AccessibilityWatcher::quit() {
1287+ removeWatches();
1288+ initialized = false;
1289+}
1290+
1291+std::deque<FocusInfo> AccessibilityWatcher::getFocusQueue() {
1292+ return focusList;
1293+}
1294+
1295+void AccessibilityWatcher::resetFocusQueue() {
1296+ focusList.clear();
1297+}
1298+
1299
1300=== added file 'plugins/focuspoll/src/focuspoll.cpp'
1301--- plugins/focuspoll/src/focuspoll.cpp 1970-01-01 00:00:00 +0000
1302+++ plugins/focuspoll/src/focuspoll.cpp 2017-04-28 09:59:04 +0000
1303@@ -0,0 +1,220 @@
1304+/*
1305+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
1306+ *
1307+ * This file is part of compiz.
1308+ *
1309+ * this program is free software: you can redistribute it and/or modify
1310+ * it under the terms of the GNU General Public License as published by the Free
1311+ * Software Foundation, either version 3 of the License, or (at your option) any
1312+ * later version.
1313+ *
1314+ * This program is distributed in the hope that it will be useful, but
1315+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1316+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1317+ * details.
1318+ *
1319+ * You should have received a copy of the GNU General Public License along
1320+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1321+ */
1322+
1323+#include "private.h"
1324+#include <iostream>
1325+
1326+COMPIZ_PLUGIN_20090315 (focuspoll, FocuspollPluginVTable);
1327+
1328+bool
1329+FocuspollScreen::updatePosition ()
1330+{
1331+ if (!a11ywatcher->getFocusQueue().empty()) {
1332+ int focusX, focusY, focusW, focusH;
1333+ std::tie(focusX, focusY, focusW, focusH) = a11ywatcher->getFocusQueue().back().getBBox();
1334+ focusRect = CompRect(focusX, focusY, focusW, focusH);
1335+ // for (auto poller : pollers) segfaults at focuspoller.stop, reverting TODO
1336+ for (std::list<FocusPoller *>::iterator it = pollers.begin (); it != pollers.end ();) {
1337+ FocusPoller *poller = *it;
1338+ ++it;
1339+
1340+ poller->mCallback(focusRect);
1341+ }
1342+ }
1343+ a11ywatcher->resetFocusQueue();
1344+ return true;
1345+}
1346+
1347+bool
1348+FocuspollScreen::addTimer (FocusPoller *poller)
1349+{
1350+ bool start = pollers.empty ();
1351+
1352+ std::list<FocusPoller *>::iterator it =
1353+ std::find (pollers.begin (), pollers.end (), poller);
1354+
1355+ if (it != pollers.end ())
1356+ return false;
1357+
1358+ pollers.insert (it, poller);
1359+
1360+ if (start)
1361+ {
1362+ //focusHasMoved();
1363+ timer.start();
1364+ }
1365+
1366+ return true;
1367+}
1368+
1369+void
1370+FocuspollScreen::removeTimer (FocusPoller *poller)
1371+{
1372+ std::list<FocusPoller *>::iterator it =
1373+ std::find (pollers.begin(), pollers.end (), poller);
1374+
1375+ if (it == pollers.end ())
1376+ return;
1377+
1378+ pollers.erase (it);
1379+
1380+ if (pollers.empty ()) {
1381+ timer.stop ();
1382+ }
1383+}
1384+
1385+void
1386+FocusPoller::setCallback (FocusPoller::CallBack callback)
1387+{
1388+ bool wasActive = mActive;
1389+
1390+ if (mActive)
1391+ stop ();
1392+
1393+ mCallback = callback;
1394+
1395+ if (wasActive)
1396+ start ();
1397+}
1398+
1399+void
1400+FocusPoller::start ()
1401+{
1402+ FOCUSPOLL_SCREEN (screen);
1403+
1404+ if (!fs)
1405+ {
1406+ compLogMessage ("focuspoll", CompLogLevelWarn,
1407+ "Plugin version mismatch, can't start focus poller.");
1408+
1409+ return;
1410+ }
1411+
1412+ if (mCallback.empty ())
1413+ {
1414+ compLogMessage ("focuspoll", CompLogLevelWarn,
1415+ "Can't start focus poller without callback.");
1416+ return;
1417+ }
1418+
1419+ fs->addTimer (this);
1420+
1421+ mActive = true;
1422+}
1423+
1424+void
1425+FocusPoller::stop ()
1426+{
1427+ FOCUSPOLL_SCREEN (screen);
1428+
1429+ /* Prevent broken plugins from calling stop () twice */
1430+
1431+ if (!mActive)
1432+ return;
1433+
1434+ if (!fs)
1435+ {
1436+ compLogMessage ("focuspoll",
1437+ CompLogLevelWarn,
1438+ "Plugin version mismatch, can't stop focus poller.");
1439+ return;
1440+ }
1441+
1442+ mActive = false;
1443+ fs->removeTimer (this);
1444+}
1445+
1446+bool
1447+FocusPoller::active ()
1448+{
1449+ return mActive;
1450+}
1451+
1452+FocusPoller::FocusPoller () :
1453+ mActive (false),
1454+ mCallback (NULL) {
1455+ }
1456+
1457+std::tuple<int, int> FocuspollScreen::getScreenLimits() {
1458+ int x =0, y = 0;
1459+ for (auto dev : screen->outputDevs()) {
1460+ x = std::max(x, dev.x() + dev.width());
1461+ y = std::max(y, dev.y() + dev.height());
1462+ }
1463+ return std::make_tuple(x, y);
1464+}
1465+
1466+void
1467+FocuspollScreen::updateTimer ()
1468+{
1469+ float timeout = optionGetFocusPollInterval ();
1470+ timer.setTimes (timeout, timeout * 1.5);
1471+}
1472+
1473+FocusPoller::~FocusPoller ()
1474+{
1475+ if (mActive) {
1476+ stop();
1477+ }
1478+}
1479+
1480+void
1481+FocuspollScreen::setOptions()
1482+{
1483+ a11ywatcher->setIgnoreLinks(optionGetIgnoreLinks());
1484+}
1485+
1486+template class PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>;
1487+
1488+FocuspollScreen::FocuspollScreen (CompScreen *screen) :
1489+ PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI> (screen)
1490+{
1491+ a11ywatcher = AccessibilityWatcher::getInstance();
1492+ a11ywatcher->init();
1493+
1494+ int screenLimitX, screenLimitY;
1495+ std::tie(screenLimitX, screenLimitY) = getScreenLimits();
1496+ a11ywatcher->setScreenLimits(screenLimitX, screenLimitY);
1497+
1498+ updateTimer ();
1499+ timer.setCallback (boost::bind (&FocuspollScreen::updatePosition, this));
1500+ optionSetFocusPollIntervalNotify (boost::bind (&FocuspollScreen::updateTimer, this));
1501+
1502+ optionSetIgnoreLinksNotify (boost::bind (&FocuspollScreen::setOptions, this));
1503+
1504+}
1505+
1506+bool
1507+FocuspollPluginVTable::init ()
1508+{
1509+ if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION))
1510+ {
1511+ CompPrivate p;
1512+ p.uval = COMPIZ_FOCUSPOLL_ABI;
1513+ screen->storeValue ("focuspoll_ABI", p);
1514+ return true;
1515+ }
1516+ return false;
1517+}
1518+
1519+void
1520+FocuspollPluginVTable::fini ()
1521+{
1522+ screen->eraseValue ("focuspoll_ABI");
1523+}
1524
1525=== added file 'plugins/focuspoll/src/private.h'
1526--- plugins/focuspoll/src/private.h 1970-01-01 00:00:00 +0000
1527+++ plugins/focuspoll/src/private.h 2017-04-28 09:59:04 +0000
1528@@ -0,0 +1,75 @@
1529+/*
1530+ * Copyright (C) 2016 Auboyneau Vincent <ksamak@riseup.net>
1531+ *
1532+ * This file is part of compiz.
1533+ *
1534+ * this program is free software: you can redistribute it and/or modify
1535+ * it under the terms of the GNU General Public License as published by the Free
1536+ * Software Foundation, either version 3 of the License, or (at your option) any
1537+ * later version.
1538+ *
1539+ * This program is distributed in the hope that it will be useful, but
1540+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1541+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1542+ * details.
1543+ *
1544+ * You should have received a copy of the GNU General Public License along
1545+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1546+ */
1547+
1548+#include <tuple>
1549+#include <core/core.h>
1550+#include <core/pluginclasshandler.h>
1551+#include <core/timer.h>
1552+
1553+#include <focuspoll/focuspoll.h>
1554+#include <accessibilitywatcher/accessibilitywatcher.h>
1555+
1556+#include "focuspoll_options.h"
1557+
1558+typedef enum _FocuspollOptions
1559+{
1560+ MP_DISPLAY_OPTION_FOCUS_POLL_INTERVAL,
1561+ MP_DISPLAY_OPTION_NUM
1562+} FocuspollDisplayOptions;
1563+
1564+class FocuspollScreen;
1565+extern template class PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>;
1566+
1567+class FocuspollScreen :
1568+ public PluginClassHandler <FocuspollScreen, CompScreen, COMPIZ_FOCUSPOLL_ABI>,
1569+ public FocuspollOptions
1570+{
1571+ public:
1572+ FocuspollScreen (CompScreen *screen);
1573+
1574+ std::list<FocusPoller *> pollers;
1575+ CompTimer timer;
1576+ CompTimer settingsTimer;
1577+ CompRect focusRect;
1578+
1579+ bool updatePosition ();
1580+ bool addTimer (FocusPoller *poller);
1581+ void removeTimer (FocusPoller *poller);
1582+ void updateTimer ();
1583+
1584+ private:
1585+ AccessibilityWatcher* a11ywatcher;
1586+
1587+ std::tuple<int, int> getScreenLimits();
1588+ void setOptions();
1589+
1590+};
1591+
1592+#define FOCUSPOLL_SCREEN(s) \
1593+ FocuspollScreen *fs = FocuspollScreen::get (s)
1594+
1595+#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
1596+
1597+class FocuspollPluginVTable :
1598+ public CompPlugin::VTableForScreen<FocuspollScreen, COMPIZ_FOCUSPOLL_ABI>
1599+{
1600+ public:
1601+ bool init ();
1602+ void fini ();
1603+};
1604
1605=== modified file 'plugins/showmouse/showmouse.xml.in'
1606--- plugins/showmouse/showmouse.xml.in 2016-05-31 17:30:42 +0000
1607+++ plugins/showmouse/showmouse.xml.in 2017-04-28 09:59:04 +0000
1608@@ -11,6 +11,9 @@
1609 <plugin>cube</plugin>
1610 <plugin>decor</plugin>
1611 </relation>
1612+ <relation type="before">
1613+ <plugin>ezoom</plugin>
1614+ </relation>
1615 <requirement>
1616 <plugin>opengl</plugin>
1617 <plugin>mousepoll</plugin>

Subscribers

People subscribed via source and target branches