=== modified file 'debian/compiz-dev.install'
--- debian/compiz-dev.install 2016-07-14 15:21:59 +0000
+++ debian/compiz-dev.install 2018-10-23 08:53:35 +0000
@@ -7,6 +7,7 @@
debian/tmp/usr/lib/*/pkgconfig/compiz-composite.pc
debian/tmp/usr/lib/*/pkgconfig/compiz-cube.pc
debian/tmp/usr/lib/*/pkgconfig/compiz-mousepoll.pc
+debian/tmp/usr/lib/*/pkgconfig/compiz-focuspoll.pc
debian/tmp/usr/lib/*/pkgconfig/compiz-opengl.pc
debian/tmp/usr/lib/*/pkgconfig/compiz.pc
debian/tmp/usr/lib/*/pkgconfig/compiz-scale.pc
=== modified file 'debian/compiz-plugins-default.install'
--- debian/compiz-plugins-default.install 2017-04-26 08:18:38 +0000
+++ debian/compiz-plugins-default.install 2018-10-23 08:53:35 +0000
@@ -26,6 +26,8 @@
usr/share/compiz/*matecompat.*
usr/lib/*/compiz/*mousepoll.*
usr/share/compiz/*mousepoll.*
+usr/lib/*/compiz/*focuspoll.*
+usr/share/compiz/*focuspoll.*
usr/lib/*/compiz/*colorfilter.*
usr/share/compiz/*colorfilter.*
usr/share/compiz/colorfilter
=== modified file 'debian/control'
--- debian/control 2018-07-24 10:32:11 +0000
+++ debian/control 2018-10-23 08:53:35 +0000
@@ -15,6 +15,7 @@
quilt (>= 0.40),
libcairo2-dev,
libdbus-glib-1-dev,
+ libatspi2.0-dev,
libgl1-mesa-dev (>= 6.5.1) [!armhf !armel] | libgl-dev [!armhf !armel],
libegl1-mesa-dev [armhf armel], libgles2-mesa-dev [armhf armel],
libboost-dev,
=== modified file 'plugins/ezoom/CMakeLists.txt'
--- plugins/ezoom/CMakeLists.txt 2012-05-16 17:40:27 +0000
+++ plugins/ezoom/CMakeLists.txt 2018-10-23 08:53:35 +0000
@@ -2,4 +2,4 @@
include (CompizPlugin)
-compiz_plugin (ezoom PLUGINDEPS composite opengl mousepoll)
+compiz_plugin (ezoom PLUGINDEPS composite opengl mousepoll focuspoll)
=== modified file 'plugins/ezoom/ezoom.xml.in'
--- plugins/ezoom/ezoom.xml.in 2018-07-11 15:30:30 +0000
+++ plugins/ezoom/ezoom.xml.in 2018-10-23 08:53:35 +0000
@@ -7,6 +7,8 @@
expo
decor
mousepoll
+ focuspoll
+ showmouse
staticswitcher
@@ -15,6 +17,7 @@
opengl
mousepoll
+ focuspoll
<_short>Enhanced Zoom Desktop
@@ -440,12 +443,17 @@
<_short>Focus Tracking
+
-
+
+
+
+
@@ -476,7 +508,7 @@
<_long>Zoom Change Speed
25
0.1
- 100
+ 500
0.1
+
=== modified file 'plugins/ezoom/src/ezoom.cpp'
--- plugins/ezoom/src/ezoom.cpp 2017-12-05 14:45:06 +0000
+++ plugins/ezoom/src/ezoom.cpp 2018-10-23 08:53:35 +0000
@@ -168,6 +168,39 @@
zs->gScreen->setTextureFilter (oldFilter);
}
+/* Return current time in seconds */
+static inline double
+getTime (void)
+{
+ struct timespec tp;
+ clock_gettime (CLOCK_MONOTONIC, &tp);
+ return tp.tv_sec + tp.tv_nsec / 1e9;
+}
+
+/* This returns the proper translation correction depending on the desired
+ * centering (exact center, small overflow, or exact screen fit) */
+float
+EZoomScreen::getTranslateCorrection (float zoom)
+{
+ if (optionGetAlwaysCenterMouse ())
+ {
+ // Bring to X,Y position the exact zoom center instead of
+ // fitting screen
+ return 1.0;
+ }
+ else if (!optionGetRestrainZoomToScreen ())
+ {
+ // Overflow a bit beyond screen so we can clearly see the corner and the
+ // mouse
+ return (1.0f - zoom) / EZOOM_SCREEN_BORDER_OFFSET;
+ }
+ else
+ {
+ // Keep an exact fit to screen
+ return 1.0f - zoom;
+ }
+}
+
/* Returns the distance to the defined edge in zoomed pixels. */
int
EZoomScreen::distanceToEdge (int out,
@@ -197,10 +230,26 @@
/* Update/set translations based on zoom level and real translate. */
void
-EZoomScreen::ZoomArea::updateActualTranslates ()
+EZoomScreen::ZoomArea::updateActualTranslates (EZoomScreen *screen)
{
- xtrans = -realXTranslate * (1.0f - currentZoom);
- ytrans = realYTranslate * (1.0f - currentZoom);
+ float translateCorrection;
+ float limit = 0.5 * (1.0f - currentZoom);
+
+ translateCorrection = screen->getTranslateCorrection(currentZoom);
+
+ xtrans = -realXTranslate * translateCorrection;
+ ytrans = realYTranslate * translateCorrection;
+
+ if (screen->optionGetRestrainZoomToScreen ()) {
+ if (xtrans < -limit)
+ xtrans = -limit;
+ if (xtrans > limit)
+ xtrans = limit;
+ if (ytrans < -limit)
+ ytrans = -limit;
+ if (ytrans > limit)
+ ytrans = limit;
+ }
}
/* Returns true if the head in question is currently moving.
@@ -244,7 +293,6 @@
ytrans (0.0f),
locked (false)
{
- updateActualTranslates ();
}
EZoomScreen::ZoomArea::ZoomArea () :
@@ -365,7 +413,7 @@
adjustXYVelocity (out, chunk);
adjustZoomVelocity (out, chunk);
- zooms.at (out).updateActualTranslates ();
+ zooms.at (out).updateActualTranslates (this);
if (!isZoomed (out))
{
@@ -597,8 +645,6 @@
* mouse pointer. This is to allow input, and is NOT necessary
* when input redirection is available to us or if we're cheating
* and using a scaled mouse cursor to imitate IR.
- * The center is not the center of the screen. This is the target-center;
- * that is, it's the point that's the same regardless of zoom level.
*/
void
EZoomScreen::setCenter (int x,
@@ -616,13 +662,13 @@
zooms.at (out).yTranslate = (float)
((y - o->y1 ()) - o->height () / 2) / (o->height ());
- if (instant)
+ if (instant || optionGetInstantPan ())
{
zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
zooms.at (out).yVelocity = 0.0f;
zooms.at (out).xVelocity = 0.0f;
- zooms.at (out).updateActualTranslates ();
+ zooms.at (out).updateActualTranslates (this);
}
if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
@@ -661,7 +707,7 @@
{
zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
- zooms.at (out).updateActualTranslates ();
+ zooms.at (out).updateActualTranslates (this);
}
if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
@@ -703,9 +749,21 @@
void
EZoomScreen::enableMousePolling ()
{
- pollHandle.start ();
- lastChange = time(NULL);
- mouse = MousePoller::getCurrentPosition ();
+ pollMouseHandle.start ();
+ lastMouseChange = getTime ();
+ mouse = MousePoller::getCurrentPosition ();
+}
+
+/* Enables polling of focus position */
+void
+EZoomScreen::enableFocusPolling ()
+{
+ if (!optionGetFollowFocus ())
+ {
+ return;
+ }
+ pollFocusHandle.start ();
+ lastFocusChange = getTime ();
}
/* Sets the zoom (or scale) level.
@@ -722,9 +780,12 @@
value = 1.0f;
else
{
- if (!pollHandle.active ())
+ if (!pollMouseHandle.active ())
enableMousePolling ();
+ if (!pollFocusHandle.active ())
+ enableFocusPolling ();
+
grabbed |= (1 << zooms.at (out).output);
cursorZoomActive (out);
}
@@ -770,7 +831,7 @@
{
int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
- if (!isInMovement (out))
+ if (!isInMovement (out) || nonMouseFocusTracking)
return;
CompOutput *o = &screen->outputDevs ().at (out);
@@ -810,17 +871,30 @@
int oHeight = o->height ();
int halfOWidth = oWidth / 2;
int halfOHeight = oHeight / 2;
+ float translateCorrection = getTranslateCorrection (za.currentZoom);
+ float xTranslate = za.realXTranslate * translateCorrection ;
+ float yTranslate = za.realYTranslate * translateCorrection ;
+ float limit = 0.5 * (1.0 - za.currentZoom);
+
+ if (optionGetRestrainZoomToScreen ()) {
+ if (xTranslate < -limit)
+ xTranslate = -limit;
+ if (xTranslate > limit)
+ xTranslate = limit;
+ if (yTranslate < -limit)
+ yTranslate = -limit;
+ if (yTranslate > limit)
+ yTranslate = limit;
+ }
x -= o->x1 ();
y -= o->y1 ();
- *resultX = x - (za.realXTranslate *
- (1.0f - za.currentZoom) * oWidth) - halfOWidth;
+ *resultX = x - (xTranslate * oWidth) - halfOWidth;
*resultX /= za.currentZoom;
*resultX += halfOWidth;
*resultX += o->x1 ();
- *resultY = y - (za.realYTranslate *
- (1.0f - za.currentZoom) * oHeight) - halfOHeight;
+ *resultY = y - (yTranslate * oHeight) - halfOHeight;
*resultY /= za.currentZoom;
*resultY += halfOHeight;
*resultY += o->y1 ();
@@ -1058,7 +1132,7 @@
if (zooms.at (out).currentZoom == 1.0f)
{
- lastChange = time(NULL);
+ lastMouseChange = getTime ();
mouse = MousePoller::getCurrentPosition ();
}
@@ -1129,20 +1203,88 @@
void
EZoomScreen::updateMousePosition (const CompPoint &p)
{
+ auto localTime = getTime ();
mouse.setX (p.x ());
mouse.setY (p.y ());
int out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
- lastChange = time(NULL);
-
- if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
- !isInMovement (out))
- setCenter (mouse.x (), mouse.y (), true);
-
+ if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse)
+ {
+ if (optionGetWarpMouseToFocus ()) {
+ CompOutput *o = &screen->outputDevs ().at (out);
+ int zoomedMouseX, zoomedMouseY;
+ bool pointerInZoom;
+
+ convertToZoomed (out, mouse.x (), mouse.y (),
+ &zoomedMouseX, &zoomedMouseY);
+ pointerInZoom = zoomedMouseX >= o->x1 ()
+ && zoomedMouseX < o->x1 () + o->width ()
+ && zoomedMouseY >= o->y1 ()
+ && zoomedMouseY < o->y1 () + o->height ();
+
+ if (lastMouseChange <= lastFocusChange && nonMouseFocusTracking && !pointerInZoom)
+ {
+ // Mouse taking back control of focus, but is out of zoom, wrap
+ // it back to focus
+ // FocusTracking is always centered
+ screen->warpPointer (
+ zooms.at (out).realXTranslate * o->width ()
+ + o->width () / 2 + o->x1 () - pointerX,
+ zooms.at (out).realYTranslate * o->height ()
+ + o->height () / 2 + o->y1 () - pointerY);
+ setCenter (pointerX, pointerY, true);
+ }
+ else
+ {
+ if (!isInMovement (out))
+ {
+ setCenter (mouse.x (), mouse.y (), true);
+ }
+ }
+ lastMouseChange = localTime;
+ }
+ // respect a timing in case user has merely grazed his mouse
+ else if (localTime - lastFocusChange > optionGetFollowMouseDelay () && !isInMovement (out))
+ {
+ setCenter (mouse.x (), mouse.y (), true);
+ lastMouseChange = localTime;
+ }
+ nonMouseFocusTracking = false;
+ }
cursorMoved ();
cScreen->damageScreen ();
}
+void
+EZoomScreen::updateFocusPosition (const CompRect &rect)
+{
+ auto localTime = getTime ();
+ int out = screen->outputDeviceForPoint (rect.x (), rect.y ());
+ if (localTime - lastMouseChange > optionGetFollowFocusDelay ())
+ {
+ int zoomAreaWidth = screen->outputDevs ().at (out).width () * zooms.at (out).newZoom;
+ int zoomAreaHeight = screen->outputDevs ().at (out).height () * zooms.at (out).newZoom;
+ int posX = rect.x () + rect.width () / 2;
+ int posY = rect.y () + rect.height () / 2;
+ if (rect.width () > zoomAreaWidth)
+ {
+ // target rectangle is too big for the zoom area, aim at
+ // the top-left corner of the target
+ posX -= (rect.width () - zoomAreaWidth) / 2;
+ }
+ if (rect.height () > zoomAreaHeight)
+ {
+ posY -= (rect.height () - zoomAreaHeight) / 2;
+ }
+ setCenter (posX, posY, false);
+ focus.setX (posX);
+ focus.setY (posY);
+ nonMouseFocusTracking = true;
+ lastFocusChange = localTime;
+ }
+ cScreen->damageScreen ();
+}
+
/* Timeout handler to poll the mouse. Returns false (and thereby does not
* get re-added to the queue) when zoom is not active. */
void
@@ -1154,11 +1296,21 @@
{
cursorMoved ();
- if (pollHandle.active ())
- pollHandle.stop ();
+ if (pollMouseHandle.active ())
+ pollMouseHandle.stop ();
}
}
+/* Timeout handler to focusPoll. */
+void
+EZoomScreen::updateFocusInterval (const CompRect &rect)
+{
+ updateFocusPosition (rect);
+
+ if (!grabbed && pollFocusHandle.active ())
+ pollFocusHandle.stop ();
+}
+
/* Free a cursor */
void
EZoomScreen::freeCursor (CursorTexture *cursor)
@@ -1586,7 +1738,7 @@
int out = screen->outputDeviceForPoint (pointerX, pointerY);
if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
- !isInMovement (out))
+ !isInMovement (out) && !nonMouseFocusTracking)
setCenter (pointerX, pointerY, true);
setScale (out, zooms.at (out).newZoom / optionGetZoomFactor ());
@@ -1914,8 +2066,8 @@
if (w == NULL ||
w->id () == screen->activeWindow () ||
- time(NULL) - lastChange < optionGetFollowFocusDelay () ||
- !optionGetFollowFocus ())
+ getTime () - lastMouseChange < optionGetFollowFocusDelay () ||
+ !optionGetFollowWindowFocus ())
return;
int out = screen->outputDeviceForGeometry (w->geometry ());
@@ -2008,7 +2160,9 @@
gScreen (GLScreen::get (screen)),
grabbed (0),
grabIndex (0),
- lastChange (0),
+ lastMouseChange (0.),
+ lastFocusChange (0.),
+ nonMouseFocusTracking (false),
cursorInfoSelected (false),
cursorHidden (false)
{
@@ -2042,8 +2196,10 @@
zooms.push_back (za);
}
- pollHandle.setCallback (boost::bind (
- &EZoomScreen::updateMouseInterval, this, _1));
+ pollMouseHandle.setCallback (boost::bind (&EZoomScreen::updateMouseInterval,
+ this, _1));
+ pollFocusHandle.setCallback (boost::bind (&EZoomScreen::updateFocusInterval,
+ this, _1));
optionSetZoomInButtonInitiate (boost::bind (&EZoomScreen::zoomIn, this, _1,
_2, _3));
@@ -2136,8 +2292,10 @@
EZoomScreen::~EZoomScreen ()
{
- if (pollHandle.active ())
- pollHandle.stop ();
+ if (pollMouseHandle.active ())
+ pollMouseHandle.stop ();
+ if (pollFocusHandle.active ())
+ pollFocusHandle.stop ();
if (zooms.size ())
zooms.clear ();
@@ -2152,7 +2310,8 @@
if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION) &&
CompPlugin::checkPluginABI ("composite", COMPIZ_COMPOSITE_ABI) &&
CompPlugin::checkPluginABI ("opengl", COMPIZ_OPENGL_ABI) &&
- CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI))
+ CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI) &&
+ CompPlugin::checkPluginABI ("focuspoll", COMPIZ_FOCUSPOLL_ABI))
return true;
return false;
=== modified file 'plugins/ezoom/src/ezoom.h'
--- plugins/ezoom/src/ezoom.h 2016-07-01 15:37:19 +0000
+++ plugins/ezoom/src/ezoom.h 2018-10-23 08:53:35 +0000
@@ -43,12 +43,19 @@
#include
#include
#include
+#include
#include "ezoom_options.h"
#include
+// Some Users wish to see better the edges of the screen. Without this, mouse
+// reaches the border of the zoomarea around screen edges, and mouse can become
+// invisible (especially around bottom/right). Zooming out a bit allows to see
+// both the edge and the mouse reaching the edge.
+#define EZOOM_SCREEN_BORDER_OFFSET 0.92
+
enum SpecificZoomTarget
{
ZoomTarget1 = 0,
@@ -160,25 +167,29 @@
ZoomArea ();
void
- updateActualTranslates ();
+ updateActualTranslates (EZoomScreen *screen);
};
public:
std::vector zooms; // list of zooms (different zooms for each output)
CompPoint mouse; // we get this from mousepoll
+ CompPoint focus; // we get this from focuspoll
unsigned long int grabbed;
CompScreen::GrabHandle grabIndex; // for zoomBox
- time_t lastChange;
+ double lastMouseChange;
+ double lastFocusChange;
CursorTexture cursor; // the texture for the faux-cursor
// we paint to do fake input
// handling
+ bool nonMouseFocusTracking;
bool cursorInfoSelected;
bool cursorHidden;
CompRect box;
CompPoint clickPos;
- MousePoller pollHandle; // mouse poller object
+ MousePoller pollMouseHandle; // mouse poller object
+ FocusPoller pollFocusHandle; // focus poller object
private:
@@ -222,6 +233,9 @@
adjustXYVelocity (int out,
float chunk);
+ float
+ getTranslateCorrection (float zoom);
+
void
drawBox (const GLMatrix &transform,
CompOutput *output,
@@ -231,6 +245,11 @@
setCenter (int x,
int y,
bool instant);
+ void
+ setCenter (int x,
+ int y,
+ bool instant,
+ bool center);
void
setZoomArea (int x,
@@ -250,6 +269,9 @@
enableMousePolling ();
void
+ enableFocusPolling ();
+
+ void
setScale (int out,
float value);
@@ -295,6 +317,12 @@
void
updateMouseInterval (const CompPoint &p);
+ void
+ updateFocusPosition (const CompRect &retc);
+
+ void
+ updateFocusInterval (const CompRect &rect);
+
/* Make dtor */
void
freeCursor (CursorTexture * cursor);
=== added directory 'plugins/focuspoll'
=== added file 'plugins/focuspoll/CMakeLists.txt'
--- plugins/focuspoll/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/CMakeLists.txt 2018-10-23 08:53:35 +0000
@@ -0,0 +1,9 @@
+find_package (Compiz REQUIRED)
+
+include (CompizPlugin)
+include_directories (include/accessibilitywatcher)
+
+pkg_search_module (ATSPI REQUIRED atspi-2)
+
+compiz_plugin (focuspoll PKGDEPS atspi-2)
+
=== added file 'plugins/focuspoll/compiz-focuspoll.pc.in'
--- plugins/focuspoll/compiz-focuspoll.pc.in 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/compiz-focuspoll.pc.in 2018-10-23 08:53:35 +0000
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: compiz-focuspoll
+Description: Focuspoll plugin for compiz
+Version: @VERSION@
+
+Requires: compiz
+Libs: -L${libdir}/compiz -lfocuspoll
+Cflags: @COMPIZ_CFLAGS@ -I${includedir}/compiz
=== added file 'plugins/focuspoll/focuspoll.xml.in'
--- plugins/focuspoll/focuspoll.xml.in 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/focuspoll.xml.in 2018-10-23 08:53:35 +0000
@@ -0,0 +1,29 @@
+
+
+
+
+
+ opengl
+
+
+ <_short>Focus position polling
+ <_long>Updates the focus pointer position from the accessibility library AT-SPI through dbus
+ Utility
+
+ <_short>Misc
+
+
+
+
+
+
=== added directory 'plugins/focuspoll/include'
=== added directory 'plugins/focuspoll/include/accessibilitywatcher'
=== added file 'plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h'
--- plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/include/accessibilitywatcher/accessibilitywatcher.h 2018-10-23 08:53:35 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef ACCESSIBILITY_WATCHER_H
+#define ACCESSIBILITY_WATCHER_H
+
+#include
+#include
+
+#include "focusinfo.h"
+
+#include
+
+class AccessibilityWatcher
+{
+ public:
+ AccessibilityWatcher ();
+ ~AccessibilityWatcher ();
+
+ void setActive (bool);
+
+ void setIgnoreLinks (bool);
+ void setScreenLimits (int, int);
+ int getScreenWidth (void);
+ int getScreenHeight (void);
+
+ std::deque getFocusQueue (void);
+ void resetFocusQueue (void);
+ bool returnToPrevMenu (void);
+
+ void registerEvent (const AtspiEvent *event, const gchar *type);
+
+ private:
+ bool mActive;
+ int screenWidth;
+ int screenHeight;
+ static bool ignoreLinks;
+ std::deque focusList;
+ std::vector previouslyActiveMenus;
+
+ AtspiEventListener *focusListener;
+ AtspiEventListener *caretMoveListener;
+ AtspiEventListener *selectedListener;
+ AtspiEventListener *descendantChangedListener;
+
+ void addWatches (void);
+ void removeWatches (void);
+
+ bool appSpecificFilter (FocusInfo *focusInfo, const AtspiEvent* event);
+ bool filterBadEvents (const FocusInfo *event);
+ void getAlternativeCaret (FocusInfo *focus, const AtspiEvent* event);
+};
+
+#endif
=== added file 'plugins/focuspoll/include/accessibilitywatcher/focusinfo.h'
--- plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/include/accessibilitywatcher/focusinfo.h 2018-10-23 08:53:35 +0000
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef FOCUS_INFO_H
+#define FOCUS_INFO_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+class FocusInfo
+{
+ public:
+
+ FocusInfo (const gchar * type = "",
+ gchar * name = g_strdup (""),
+ gchar * label = g_strdup (""),
+ gchar * role = g_strdup (""),
+ gchar * application = g_strdup (""),
+ int x = -1,
+ int y = -1,
+ int width = -1,
+ int height = -1);
+
+ FocusInfo (const FocusInfo &dup);
+
+ ~FocusInfo ();
+
+ int x, y, w, h;
+ int xAlt, yAlt, wAlt, hAlt;
+ const gchar * type;
+ gchar * name;
+ gchar * label;
+ gchar * role;
+ gchar * application;
+
+ // AT-SPI events that are interesting to know about the event
+ bool active;
+ bool focused;
+ bool selected;
+
+ const gchar * getType (void);
+ CompPoint getPosition (void);
+ CompSize getSize (void);
+ CompRect getBBox (void);
+
+ bool operator== (const FocusInfo &other) const;
+ bool operator!= (const FocusInfo &other) const;
+};
+
+#endif
=== added directory 'plugins/focuspoll/include/focuspoll'
=== added file 'plugins/focuspoll/include/focuspoll/focuspoll.h'
--- plugins/focuspoll/include/focuspoll/focuspoll.h 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/include/focuspoll/focuspoll.h 2018-10-23 08:53:35 +0000
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef _COMPIZ_FOCUSPOLL_H
+#define _COMPIZ_FOCUSPOLL_H
+
+#define COMPIZ_FOCUSPOLL_ABI 1
+
+#include
+#include
+#include
+
+class FocusPoller
+{
+ public:
+ typedef boost::function CallBack;
+
+ FocusPoller ();
+ ~FocusPoller ();
+
+ void setCallback (CallBack callback);
+ void start (void);
+ void stop (void);
+ bool active (void);
+
+ private:
+ bool mActive;
+ CallBack mCallback;
+ CompRect focusRect;
+
+ friend class FocuspollScreen;
+};
+
+#endif
=== added directory 'plugins/focuspoll/src'
=== added file 'plugins/focuspoll/src/accessibilitywatcher.cpp'
--- plugins/focuspoll/src/accessibilitywatcher.cpp 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/src/accessibilitywatcher.cpp 2018-10-23 08:53:35 +0000
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ * Copyright (C) 2018 Samuel Thibault
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "accessibilitywatcher.h"
+
+// When getting a bounding box bigger than this size, consider that this is not
+// a precise caret bounding box, and try to find another source of precise caret position
+// TODO: make them options
+namespace {
+ const int A11YWATCHER_MAX_CARET_WIDTH = 50;
+ const int A11YWATCHER_MAX_CARET_HEIGHT = 70;
+}
+
+/*
+ * Wrapper that automatically calls g_object_unref on pointer scope release.
+ *
+ * Instead of
+ *
+ * AtspiFoo x = atspi_... ()
+ * atspi_... (x);
+ * g_object_unref (x);
+ *
+ * use
+ *
+ * auto x = unique_gobject (atspi_... ());;
+ * atspi_... (x.get ());
+ *
+ * And instead of
+ *
+ * AtspiFoo x = event->source;
+ * g_object_ref (x);
+ * atspi_... (x);
+ * g_object_unref (x);
+ *
+ * use
+ *
+ * auto x = unique_gobject_ref (event->source);;
+ * atspi_... (x.get ());
+ * */
+
+struct unique_gobject_deleter {
+ void operator () (gpointer obj)
+ {
+ if (obj)
+ g_object_unref (obj);
+ }
+};
+
+template
+std::unique_ptr
+unique_gobject (T* ptr)
+{
+
+ return std::unique_ptr (ptr);
+}
+
+template
+std::unique_ptr
+unique_gobject_ref (T* ptr)
+{
+ if (ptr)
+ g_object_ref (ptr);
+ return std::unique_ptr (ptr);
+}
+
+/*
+ * Similarly for a g_array of gobjects.
+ */
+
+struct unique_gobject_garray_deleter {
+ void operator () (GArray *array)
+ {
+ for (guint i = 0; i < array->len; ++i)
+ {
+ gpointer obj = g_array_index (array, gpointer, i);
+ if (obj)
+ g_object_unref (obj);
+ }
+ g_array_unref (array);
+ }
+};
+
+template
+std::unique_ptr
+unique_gobject_garray (T* ptr)
+{
+
+ return std::unique_ptr (ptr);
+}
+
+/*
+ * Similarly for calling g_free on pointer scope release.
+ */
+
+struct unique_gmem_deleter {
+ void operator () (gpointer obj)
+ {
+ g_free (obj);
+ }
+};
+
+template
+std::unique_ptr
+unique_gmem (T* ptr)
+{
+
+ return std::unique_ptr (ptr);
+}
+
+
+
+bool AccessibilityWatcher::ignoreLinks = false;
+
+static void
+onCaretMove (const AtspiEvent *event, void *data)
+{
+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
+ watcher->registerEvent (event, "caret");
+}
+
+static void
+onSelectedChange (const AtspiEvent *event, void *data)
+{
+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
+ watcher->registerEvent (event, "state-changed:selected");
+}
+
+static void
+onFocus (const AtspiEvent *event, void *data)
+{
+ /* We only care about focus/selection gain
+ * there's no detail1 on focus loss in AT-SPI specs */
+ if (!event->detail1)
+ return;
+
+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
+ watcher->registerEvent (event, "focus");
+}
+
+static void
+onDescendantChanged (const AtspiEvent *event, void *data)
+{
+ AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
+ watcher->registerEvent (event, "active-descendant-changed");
+}
+
+AccessibilityWatcher::AccessibilityWatcher () :
+ mActive (false),
+ screenWidth (0),
+ screenHeight (0),
+ focusListener (NULL),
+ caretMoveListener (NULL),
+ selectedListener (NULL),
+ descendantChangedListener (NULL)
+{
+ atspi_init ();
+ atspi_set_main_context (g_main_context_default ());
+
+ focusListener = atspi_event_listener_new (reinterpret_cast (onFocus), this, NULL);
+ caretMoveListener = atspi_event_listener_new (reinterpret_cast (onCaretMove), this, NULL);
+ selectedListener = atspi_event_listener_new (reinterpret_cast (onSelectedChange), this, NULL);
+ descendantChangedListener = atspi_event_listener_new (reinterpret_cast (onDescendantChanged), this, NULL);
+
+ addWatches ();
+}
+
+AccessibilityWatcher::~AccessibilityWatcher ()
+{
+ removeWatches ();
+ g_object_unref (focusListener);
+ g_object_unref (caretMoveListener);
+ g_object_unref (selectedListener);
+ g_object_unref (descendantChangedListener);
+};
+
+static gchar *
+getLabel (AtspiAccessible *accessible)
+{
+ auto relations = unique_gobject_garray (atspi_accessible_get_relation_set (accessible, NULL));
+ if (relations.get () == NULL)
+ {
+ return g_strdup ("");
+ }
+
+ for (guint i = 0; i < relations.get ()->len; ++i) {
+ AtspiRelation *relation = g_array_index (relations.get (), AtspiRelation*, i);
+ if (relation == NULL)
+ continue;
+ if (atspi_relation_get_relation_type (relation) == ATSPI_RELATION_LABELLED_BY)
+ {
+ auto target = unique_gobject (atspi_relation_get_target (relation, 0));
+ gchar * res_label = atspi_accessible_get_name (target.get (), NULL);
+ return (res_label == NULL) ? g_strdup ("") : res_label;
+ }
+ }
+ return g_strdup ("");
+}
+
+void
+AccessibilityWatcher::setIgnoreLinks (bool val)
+{
+ ignoreLinks = val;
+}
+
+void
+AccessibilityWatcher::setScreenLimits (int x, int y)
+{
+ screenWidth = x;
+ screenHeight = y;
+}
+
+int
+AccessibilityWatcher::getScreenWidth ()
+{
+ return screenWidth;
+}
+
+int
+AccessibilityWatcher::getScreenHeight ()
+{
+ return screenHeight;
+}
+
+void
+AccessibilityWatcher::registerEvent (const AtspiEvent *event, const gchar *type)
+{
+ // type is registered from filter on calling event
+ auto application = unique_gobject (atspi_accessible_get_application (event->source, NULL));
+ FocusInfo *res = new FocusInfo (type,
+ atspi_accessible_get_name (event->source, NULL),
+ getLabel (event->source),
+ atspi_accessible_get_role_name (event->source, NULL),
+ atspi_accessible_get_name (application.get (), NULL));
+
+ if (!res->active)
+ {
+ // prevents skipping events that are not designated as active. we check the activeness of parents.
+ auto parent = unique_gobject (atspi_accessible_get_parent (event->source, NULL));
+ while (parent.get ())
+ {
+ auto stateSet = unique_gobject (atspi_accessible_get_state_set (parent.get ()));
+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_ACTIVE))
+ {
+ res->active = true;
+ }
+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_EXPANDABLE))
+ {
+ if (!atspi_state_set_contains (stateSet.get (), ATSPI_STATE_EXPANDED))
+ {
+ auto role = unique_gmem (atspi_accessible_get_role_name (parent.get (), NULL));
+ if (!strcmp (role.get (), "menu"))
+ {
+ // parent is expandable but not expanded, we do not want to track what is happening inside
+ delete (res);
+ return;
+ }
+ }
+ }
+ auto child = unique_gobject (atspi_accessible_get_parent (parent.get (), NULL));
+ if (child.get () == parent.get ())
+ {
+ // parent loop !? escape this trap...
+ break;
+ }
+ parent = unique_gobject_ref (child.get ());
+ }
+ }
+
+ auto component_target = unique_gobject_ref (event->source);
+
+ if (strcmp (res->type, "active-descendant-changed") == 0)
+ {
+ component_target = unique_gobject (atspi_accessible_get_child_at_index (component_target.get (), event->detail1, NULL));
+ if (!component_target.get ())
+ {
+ delete (res);
+ return;
+ }
+ }
+
+ if (strcmp (unique_gmem (atspi_accessible_get_role_name (component_target.get (), NULL)).get (), "tree table") == 0)
+ {
+ // This is a table, it is not generally useful to look at the whole
+ // table, but rather just at the selected file, if any, otherwise the first
+ // file, if any.
+ decltype (component_target) sub_target = NULL;
+ auto selection = unique_gobject (atspi_accessible_get_selection_iface (component_target.get ()));
+
+ if (selection.get ())
+ {
+ sub_target = unique_gobject (atspi_selection_get_selected_child (selection.get (), 0, NULL));
+ }
+
+ if (sub_target.get () == NULL)
+ {
+ // No selection, try to get first real child.
+ unsigned i = 0;
+
+ while (1) {
+ sub_target = unique_gobject (atspi_accessible_get_child_at_index (component_target.get (), i, NULL));
+ i++;
+
+ if (sub_target.get () == NULL)
+ // No other children than table column header
+ break;
+
+ if (strcmp (unique_gmem (atspi_accessible_get_role_name (sub_target.get (), NULL)).get (), "table column header") != 0)
+ // Found a real child
+ break;
+ }
+ }
+
+ if (sub_target.get ())
+ component_target = unique_gobject_ref (sub_target.get ());
+ }
+
+ auto component = unique_gobject (atspi_accessible_get_component (component_target.get ()));
+
+ if (component.get ())
+ {
+ auto size = unique_gmem (atspi_component_get_extents (component.get (), ATSPI_COORD_TYPE_SCREEN, NULL));
+ res->x = size.get ()->x;
+ res->y = size.get ()->y;
+ res->w = size.get ()->width;
+ res->h = size.get ()->height;
+ }
+ // let's get the caret offset, and then its position for a caret event
+ if (strcmp (type, "caret") == 0)
+ {
+ auto text = unique_gobject (atspi_accessible_get_text (event->source));
+ if (!text.get ())
+ {
+ delete (res);
+ return;
+ }
+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
+ // if we are not at the beginning of the text, take the extent of the character under caret
+ // otherwise keep the whole widget
+ if (event->detail1)
+ {
+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
+ res->x = size.get ()->x;
+ res->y = size.get ()->y;
+ res->w = size.get ()->width;
+ res->h = size.get ()->height;
+ }
+ // correcting a missing offset when caret is at end of text
+ if (((res->x == 0 && res->y == 0) ||
+ res->x + res->w < 0 ||
+ res->y + res->h < 0)
+ && offset > 0)
+ {
+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset-1, ATSPI_COORD_TYPE_SCREEN, NULL));
+ res->x = size.get ()->x;
+ res->y = size.get ()->y;
+ res->w = size.get ()->width;
+ res->h = size.get ()->height;
+ }
+ // when result is obviously not a caret size
+ if (strcmp (event->type, "object:text-caret-moved") == 0 && (res->w > A11YWATCHER_MAX_CARET_WIDTH || res->h > A11YWATCHER_MAX_CARET_HEIGHT))
+ {
+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
+ res->x = size.get ()->x;
+ res->y = size.get ()->y;
+ res->w = size.get ()->width;
+ res->h = size.get ()->height;
+ 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))
+ {
+ res->x = 0;
+ res->y = 0;
+ }
+ }
+
+ // still no offset, it's probably a newline and we're at bugzilla #1319273 (with new paragraph obj)
+ if (res->x == 0 && res->y == 0 &&
+ (strcmp (event->type, "object:text-changed:insert") == 0 ||
+ strcmp (event->type, "object:text-changed:removed") == 0 ||
+ strcmp (event->type, "object:text-caret-moved") == 0)) {
+ res->x = res->xAlt;
+ res->y = res->yAlt;
+ res->w = res->wAlt;
+ res->h = res->hAlt;
+ }
+ }
+
+ // getting the states on event
+ auto stateSet = unique_gobject (atspi_accessible_get_state_set (event->source));
+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_FOCUSED))
+ {
+ res->focused = true;
+ // reset potential menu stack
+ for (auto info: previouslyActiveMenus) {
+ delete (info);
+ }
+ previouslyActiveMenus.clear ();
+ }
+ if (atspi_state_set_contains (stateSet.get (), ATSPI_STATE_SELECTED))
+ {
+ res->selected = true;
+ }
+ if (strcmp (res->type, "state-changed:selected") == 0 && event->detail1 == 1)
+ {
+ res->selected = true;
+ FocusInfo *dup = new FocusInfo (*res);
+ // add to stack of menus
+ previouslyActiveMenus.push_back (dup);
+ }
+
+ if (appSpecificFilter (res, event))
+ {
+ return;
+ }
+ if (filterBadEvents (res))
+ {
+ delete (res);
+ return;
+ }
+ while (focusList.size () >= 5) { // don't keep the whole history
+ auto iter = focusList.begin ();
+ auto info = *iter;
+ focusList.erase (iter);
+ delete (info);
+ }
+ focusList.push_back (res);
+}
+
+bool
+AccessibilityWatcher::appSpecificFilter (FocusInfo *focus, const AtspiEvent* event)
+{
+ if (strcmp (focus->type, "state-changed:selected") == 0 && // emulates on-change:selected missing event for menus
+ (strcmp (focus->role, "menu item") == 0 ||
+ strcmp (focus->role, "menu") == 0 ||
+ strcmp (focus->role, "check menu item") == 0 ||
+ strcmp (focus->role, "radio menu item") == 0 ||
+ strcmp (focus->role, "tearoff menu item") == 0) &&
+ strcmp (focus->application, "mate-panel") != 0)
+ {
+ if (!focus->selected && returnToPrevMenu ())
+ {
+ // The submenu item told us that he lost selection. We have thus
+ // returned to the parent menu (which unfortunately won't tell us
+ // anything)
+ delete (focus);
+ return true;
+ }
+ focus->active = true;
+ }
+ if (strcmp (focus->application, "soffice") == 0 && strcmp (focus->role, "paragraph") == 0)
+ { // LO-calc: avoid spam event from main edit line
+ auto parent = unique_gobject (atspi_accessible_get_parent (event->source, NULL));
+ auto parentLabel = unique_gmem (atspi_accessible_get_name (parent.get (), NULL));
+ if (!strcmp (parentLabel.get (), "Input line") ||
+ !strcmp (parentLabel.get (), "Ligne de saisie"))
+ {
+ delete (focus);
+ return true;
+ }
+ }
+ if (strcmp (focus->application, "Icedove") == 0 || strcmp (focus->application, "Thunderbird") == 0)
+ {
+ if (strcmp (focus->type, "caret") == 0)
+ {
+ auto text = unique_gobject (atspi_accessible_get_text (event->source)); // next if deals with a special newline char, that remained buggy. hypra issue #430
+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
+ auto string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
+ auto stringM1 = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset - 1, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
+ gchar character = string.get ()->content[0];
+ gchar characterM1 = stringM1.get ()->content[0];
+
+ if (offset == atspi_text_get_character_count (text.get (), NULL) && character == '\0' && characterM1 == '\n')
+ {
+ getAlternativeCaret (focus, event);
+ focus->x = focus->xAlt;
+ focus->y = focus->yAlt + focus->hAlt;
+ focus->w = focus->wAlt;
+ focus->h = focus->hAlt;
+ }
+ if (!(focus->x == 0 && focus->y == 0))
+ { // prevents compose window loss of tracking in HTML mode (active flag ok, but no focused flag)
+ focusList.push_back (focus);
+ return true;
+ }
+ auto component = unique_gobject (atspi_accessible_get_component (event->source));
+ if (component.get ())
+ {
+ auto size = unique_gmem (atspi_component_get_extents (component.get (), ATSPI_COORD_TYPE_SCREEN, NULL));
+ focus->x = size.get ()->x;
+ focus->y = size.get ()->y;
+ focus->w = 7;
+ focus->h = size.get ()->height;
+ focusList.push_back (focus);
+ return true;
+ }
+ }
+ }
+ if (strcmp (focus->application, "Firefox") == 0)
+ {
+ if (ignoreLinks && strcmp (focus->type, "caret") != 0 && strcmp (focus->role, "link") == 0)
+ {
+ delete (focus);
+ return true;
+ }
+ // prevents status bar focus in firefox
+ if (strcmp (focus->type, "caret") == 0 &&
+ (strcmp (event->type, "object:text-changed:insert:system") == 0 ||
+ strcmp (event->type, "object:text-changed:delete:system") == 0)) {
+ delete (focus);
+ return true;
+ }
+ if (strcmp (focus->type, "focus") == 0 && strcmp (focus->role, "document frame") == 0)
+ { // general page parasite event
+ delete (focus);
+ return true;
+ }
+ if (strcmp (focus->type, "caret") == 0 && !(focus->x == 0 && focus->y == 0))
+ {
+ focusList.push_back (focus);
+ return true;
+ }
+ getAlternativeCaret (focus, event);
+ if (strcmp (focus->type, "caret") == 0 && !(focus->xAlt == 0 && focus->yAlt == 0))
+ {
+ focus->x = focus->xAlt;
+ focus->y = focus->yAlt + focus->hAlt;
+ focus->w = focus->wAlt;
+ focus->h = focus->hAlt;
+ focusList.push_back (focus);
+ return true;
+ }
+ }
+ if (strcmp (focus->application, "evince") == 0 && strcmp (focus->type, "state-changed:selected") == 0 && strcmp (focus->role, "icon") == 0)
+ { // LO-calc: avoid spam event from main edit line
+ delete (focus);
+ return true; // ignores the parasite event from evince icon
+ }
+ return false;
+}
+
+bool
+AccessibilityWatcher::filterBadEvents (const FocusInfo *event)
+{
+ if (strcmp (event->type, "caret") == 0 && event->x ==0 && event->y == 0)
+ {
+ return true;
+ }
+ if (!event->active)
+ {
+ return true;
+ }
+ if (!event->focused && !event->selected)
+ {
+ return true;
+ }
+ if (event->w < 0 ||
+ event->h < 0)
+ {
+ return true;
+ }
+ if (event->x == 0 &&
+ event->y == 0 &&
+ event->w == 0 &&
+ event->h == 0)
+ {
+ return true;
+ }
+ if (event->x + event->w < 0 ||
+ event->y + event->h < 0)
+ {
+ return true;
+ }
+ if (getScreenWidth () != 0 && getScreenHeight () != 0 &&
+ (event->x > getScreenWidth () ||
+ event->y > getScreenHeight () ||
+ event->w > getScreenWidth () ||
+ event->h > getScreenHeight ()))
+ {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * This simulates a "selected" event from the parent menu when closing
+ * a submenu.
+ */
+bool
+AccessibilityWatcher::returnToPrevMenu ()
+{
+ if (previouslyActiveMenus.size () > 1)
+ {
+ previouslyActiveMenus.pop_back ();
+ FocusInfo *dup = new FocusInfo (*previouslyActiveMenus.back ());
+ focusList.push_back (dup);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Tries to extrapolate a missing caret position from other text characters.
+ * is used as last resort when application doesn't respect at-spi standarts,
+ * or at-spi bugs.
+ */
+void
+AccessibilityWatcher::getAlternativeCaret (FocusInfo *focus, const AtspiEvent* event)
+{
+ auto text = unique_gobject (atspi_accessible_get_text (event->source));
+ if (!text.get ())
+ return;
+ auto offset = atspi_text_get_caret_offset (text.get (), NULL);
+ auto string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
+ gchar caretChar = string.get ()->content[0];
+
+ // if we're at a newline, sometimes at-spi isn't giving us a caret position. unknown bug in some apps.
+ if (caretChar == '\n' || caretChar == '\0')
+ {
+ // gives the last empty line the right focus.
+ int lines = atspi_text_get_character_count (text.get (), NULL) == offset ? 1 : 0;
+ int charIndex = 1;
+ bool charExtentsFound = false;
+
+ auto size = unique_gmem (atspi_text_get_character_extents (text.get (), offset, ATSPI_COORD_TYPE_SCREEN, NULL));
+ // try and find the character on upper line to extrapolate position from. no more that 300 char, we risk lag.
+ while (!charExtentsFound && charIndex <= offset && charIndex < 300) {
+ size = unique_gmem (atspi_text_get_character_extents (text.get (), offset - charIndex, ATSPI_COORD_TYPE_SCREEN, NULL));
+ string = unique_gmem (atspi_text_get_string_at_offset (text.get (), offset - charIndex, ATSPI_TEXT_GRANULARITY_CHAR, NULL));
+ caretChar = string.get ()->content[0];
+ // if we found a caret, check we're at beginning of line (or of text) to extrapolate position
+ if (size.get ()->x != 0 || size.get ()->y != 0)
+ {
+ 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')
+ {
+ charExtentsFound = true; // first character of upper line has been found
+ }
+ else if (offset - charIndex -1 == 0)
+ {
+ size = unique_gmem (atspi_text_get_character_extents (text.get (), 0, ATSPI_COORD_TYPE_SCREEN, NULL));
+ // first character of upper line has been found
+ charExtentsFound = true;
+ }
+ }
+ else if (caretChar == '\n')
+ {
+ ++lines;
+ }
+ ++charIndex;
+ }
+ focus->xAlt = size.get ()->x;
+ focus->yAlt = size.get ()->y + (lines-1) * size.get ()->height;
+ focus->wAlt = size.get ()->width;
+ focus->hAlt = size.get ()->height;
+ }
+}
+
+
+/* Register to events */
+void
+AccessibilityWatcher::addWatches ()
+{
+ atspi_event_listener_register (focusListener, "object:state-changed:focused", NULL);
+ atspi_event_listener_register (caretMoveListener, "object:text-caret-moved", NULL);
+ atspi_event_listener_register (caretMoveListener, "object:text-changed:inserted", NULL);
+ atspi_event_listener_register (caretMoveListener, "object:text-changed:removed", NULL);
+ atspi_event_listener_register (selectedListener, "object:state-changed:selected", NULL);
+ atspi_event_listener_register (descendantChangedListener, "object:active-descendant-changed", NULL);
+ mActive = true;
+}
+
+void
+AccessibilityWatcher::removeWatches ()
+{
+ atspi_event_listener_deregister (focusListener, "object:state-changed:focused", NULL);
+ atspi_event_listener_deregister (caretMoveListener, "object:text-caret-moved", NULL);
+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:inserted", NULL);
+ atspi_event_listener_deregister (caretMoveListener, "object:text-changed:removed", NULL);
+ atspi_event_listener_deregister (selectedListener, "object:state-changed:selected", NULL);
+ atspi_event_listener_deregister (descendantChangedListener, "object:active-descendant-changed", NULL);
+ mActive = false;
+}
+
+void
+AccessibilityWatcher::setActive (bool activate)
+{
+ if (mActive && !activate)
+ {
+ removeWatches ();
+ }
+ else if (!mActive && activate)
+ {
+ addWatches ();
+ }
+}
+
+std::deque
+AccessibilityWatcher::getFocusQueue ()
+{
+ return focusList;
+}
+
+void
+AccessibilityWatcher::resetFocusQueue ()
+{
+ for (auto info: focusList) {
+ delete (info);
+ }
+ focusList.clear ();
+}
+
=== added file 'plugins/focuspoll/src/focusinfo.cpp'
--- plugins/focuspoll/src/focusinfo.cpp 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/src/focusinfo.cpp 2018-10-23 08:53:35 +0000
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "focusinfo.h"
+
+#include
+#include
+
+FocusInfo::FocusInfo (const gchar * type,
+ gchar * name,
+ gchar * label,
+ gchar * role,
+ gchar * application,
+ int x,
+ int y,
+ int width,
+ int height) :
+ x (x),
+ y (y),
+ w (width),
+ h (height),
+ xAlt (-1),
+ yAlt (-1),
+ wAlt (-1),
+ hAlt (-1),
+ type (type),
+ name (name),
+ label (label),
+ role (role),
+ application (application),
+ active (false),
+ focused (false),
+ selected (false)
+{
+}
+
+FocusInfo::FocusInfo (const FocusInfo &dup)
+{
+ x = dup.x;
+ y = dup.y;
+ w = dup.w;
+ h = dup.h;
+ xAlt = dup.xAlt;
+ yAlt = dup.yAlt;
+ wAlt = dup.wAlt;
+ hAlt = dup.hAlt;
+ type = dup.type;
+ name = strdup(dup.name);
+ label = strdup(dup.label);
+ role = strdup(dup.role);
+ application = strdup(dup.application);
+ active = dup.active;
+ focused = dup.focused;
+ selected = dup.selected;
+}
+
+FocusInfo::~FocusInfo (void)
+{
+ g_free (name);
+ g_free (label);
+ g_free (role);
+ g_free (application);
+}
+
+const gchar *
+FocusInfo::FocusInfo::getType (void)
+{
+ return type;
+}
+
+CompPoint
+FocusInfo::getPosition (void)
+{
+ return CompPoint (x, y);
+}
+
+CompSize
+FocusInfo::getSize (void)
+{
+ return CompSize (w, h);
+}
+
+CompRect
+FocusInfo::getBBox (void)
+{
+ return CompRect (x, y, w, h);
+}
+
+bool
+FocusInfo::operator== (const FocusInfo& other) const
+{
+ return (other.x == x &&
+ other.y == y &&
+ other.w == w &&
+ other.h == h &&
+ !strcmp (other.type, type) &&
+ !strcmp (other.name, name) &&
+ !strcmp (other.label, label) &&
+ !strcmp (other.application, application) &&
+ !strcmp (other.role, role));
+};
+
+bool
+FocusInfo::operator!= (const FocusInfo& other) const
+{
+ return !(*this == other);
+};
=== added file 'plugins/focuspoll/src/focuspoll.cpp'
--- plugins/focuspoll/src/focuspoll.cpp 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/src/focuspoll.cpp 2018-10-23 08:53:35 +0000
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "private.h"
+
+COMPIZ_PLUGIN_20090315 (focuspoll, FocuspollPluginVTable);
+
+bool
+FocuspollScreen::updatePosition ()
+{
+ if (!a11ywatcher->getFocusQueue ().empty ())
+ {
+ CompRect focusRect = a11ywatcher->getFocusQueue ().back ()->getBBox ();
+ for (std::list::iterator it = pollers.begin (); it != pollers.end ();)
+ {
+ FocusPoller *poller = *it;
+ // Update iterator to next element before calling the callback which
+ // might remove the poller from the list.
+ ++it;
+ poller->mCallback (focusRect);
+ }
+ }
+ a11ywatcher->resetFocusQueue ();
+ return true;
+}
+
+bool
+FocuspollScreen::addTimer (FocusPoller *poller)
+{
+ bool start = pollers.empty ();
+
+ std::list ::iterator it =
+ std::find (pollers.begin (), pollers.end (), poller);
+
+ if (it != pollers.end ())
+ return false;
+
+ pollers.insert (it, poller);
+
+ if (start)
+ {
+ a11ywatcher->setActive (true);
+ timer.start ();
+ }
+
+ return true;
+}
+
+void
+FocuspollScreen::removeTimer (FocusPoller *poller)
+{
+ std::list ::iterator it =
+ std::find (pollers.begin (), pollers.end (), poller);
+
+ if (it == pollers.end ())
+ return;
+
+ pollers.erase (it);
+
+ if (pollers.empty ())
+ {
+ a11ywatcher->setActive (false);
+ timer.stop ();
+ }
+}
+
+void
+FocusPoller::setCallback (FocusPoller::CallBack callback)
+{
+ bool wasActive = mActive;
+
+ if (mActive)
+ stop ();
+
+ mCallback = callback;
+
+ if (wasActive)
+ start ();
+}
+
+void
+FocusPoller::start ()
+{
+ FOCUSPOLL_SCREEN (screen);
+
+ if (!fs)
+ {
+ compLogMessage ("focuspoll", CompLogLevelWarn,
+ "Plugin version mismatch, can't start focus poller.");
+
+ return;
+ }
+
+ if (mCallback.empty ())
+ {
+ compLogMessage ("focuspoll", CompLogLevelWarn,
+ "Can't start focus poller without callback.");
+ return;
+ }
+
+ fs->addTimer (this);
+
+ mActive = true;
+}
+
+void
+FocusPoller::stop ()
+{
+ FOCUSPOLL_SCREEN (screen);
+
+ /* Prevent broken plugins from calling stop () twice */
+
+ if (!mActive)
+ return;
+
+ if (!fs)
+ {
+ compLogMessage ("focuspoll",
+ CompLogLevelWarn,
+ "Plugin version mismatch, can't stop focus poller.");
+ return;
+ }
+
+ mActive = false;
+ fs->removeTimer (this);
+}
+
+bool
+FocusPoller::active ()
+{
+ return mActive;
+}
+
+FocusPoller::FocusPoller () :
+ mActive (false),
+ mCallback (NULL) {
+ }
+
+CompSize
+FocuspollScreen::getScreenLimits () {
+ int x =0, y = 0;
+ for (auto dev : screen->outputDevs ()) {
+ x = std::max (x, dev.x () + dev.width ());
+ y = std::max (y, dev.y () + dev.height ());
+ }
+ return CompSize (x, y);
+}
+
+void
+FocuspollScreen::updateTimer ()
+{
+ float timeout = optionGetFocusPollInterval ();
+ timer.setTimes (timeout, timeout * 1.5);
+}
+
+FocusPoller::~FocusPoller ()
+{
+ if (mActive) {
+ stop ();
+ }
+}
+
+void
+FocuspollScreen::setOptions ()
+{
+ a11ywatcher->setIgnoreLinks (optionGetIgnoreLinks ());
+}
+
+template class PluginClassHandler ;
+
+FocuspollScreen::FocuspollScreen (CompScreen *screen) :
+ PluginClassHandler (screen)
+{
+ a11ywatcher = new AccessibilityWatcher ();
+
+ CompSize screenLimit = getScreenLimits ();
+ a11ywatcher->setScreenLimits (screenLimit.width (), screenLimit.height ());
+
+ updateTimer ();
+ timer.setCallback (boost::bind (&FocuspollScreen::updatePosition, this));
+ optionSetFocusPollIntervalNotify (boost::bind (&FocuspollScreen::updateTimer, this));
+
+ optionSetIgnoreLinksNotify (boost::bind (&FocuspollScreen::setOptions, this));
+
+}
+
+FocuspollScreen::~FocuspollScreen ()
+{
+ delete a11ywatcher;
+}
+
+bool
+FocuspollPluginVTable::init ()
+{
+ if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION))
+ {
+ CompPrivate p;
+ p.uval = COMPIZ_FOCUSPOLL_ABI;
+ screen->storeValue ("focuspoll_ABI", p);
+ return true;
+ }
+ return false;
+}
+
+void
+FocuspollPluginVTable::fini ()
+{
+ screen->eraseValue ("focuspoll_ABI");
+}
=== added file 'plugins/focuspoll/src/private.h'
--- plugins/focuspoll/src/private.h 1970-01-01 00:00:00 +0000
+++ plugins/focuspoll/src/private.h 2018-10-23 08:53:35 +0000
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 Auboyneau Vincent
+ *
+ * This file is part of compiz.
+ *
+ * this program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "focuspoll_options.h"
+
+typedef enum _FocuspollOptions
+{
+ MP_DISPLAY_OPTION_FOCUS_POLL_INTERVAL,
+ MP_DISPLAY_OPTION_NUM
+} FocuspollDisplayOptions;
+
+class FocuspollScreen;
+extern template class PluginClassHandler ;
+
+class FocuspollScreen :
+ public PluginClassHandler ,
+ public FocuspollOptions
+{
+ public:
+ FocuspollScreen (CompScreen *screen);
+ ~FocuspollScreen (void);
+
+ std::list pollers;
+ CompTimer timer;
+ CompTimer settingsTimer;
+ CompRect focusRect;
+
+ bool updatePosition (void);
+ bool addTimer (FocusPoller *poller);
+ void removeTimer (FocusPoller *poller);
+ void updateTimer (void);
+
+ private:
+ AccessibilityWatcher* a11ywatcher;
+
+ CompSize getScreenLimits (void);
+ void setOptions (void);
+
+};
+
+#define FOCUSPOLL_SCREEN(s) \
+ FocuspollScreen *fs = FocuspollScreen::get (s)
+
+#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
+
+class FocuspollPluginVTable :
+ public CompPlugin::VTableForScreen
+{
+ public:
+ bool init ();
+ void fini ();
+};
=== modified file 'plugins/mousepoll/src/mousepoll.cpp'
--- plugins/mousepoll/src/mousepoll.cpp 2013-05-16 10:47:54 +0000
+++ plugins/mousepoll/src/mousepoll.cpp 2018-10-23 08:53:35 +0000
@@ -32,6 +32,7 @@
int winX, winY;
int w = screen->width (), h = screen->height ();
unsigned int maskReturn;
+ unsigned int newbuttons;
bool status = XQueryPointer (screen->dpy (), screen->root (),
&root, &child, &rootX, &rootY,
@@ -40,12 +41,25 @@
if (!status || rootX > w || rootY > h || screen->root () != root)
return false;
+ newbuttons = maskReturn &
+ (Button1Mask |
+ Button2Mask |
+ Button3Mask |
+ Button4Mask |
+ Button5Mask);
+
if (rootX != pos.x () || rootY != pos.y ())
{
pos.set (rootX, rootY);
return true;
}
+ if (newbuttons != buttons)
+ {
+ buttons = newbuttons;
+ return true;
+ }
+
return false;
}
=== modified file 'plugins/mousepoll/src/private.h'
--- plugins/mousepoll/src/private.h 2013-06-28 10:21:21 +0000
+++ plugins/mousepoll/src/private.h 2018-10-23 08:53:35 +0000
@@ -49,6 +49,7 @@
CompTimer timer;
CompPoint pos;
+ unsigned int buttons;
bool
updatePosition ();
=== modified file 'plugins/showmouse/showmouse.xml.in'
--- plugins/showmouse/showmouse.xml.in 2018-04-11 13:25:42 +0000
+++ plugins/showmouse/showmouse.xml.in 2018-10-23 08:53:35 +0000
@@ -11,6 +11,9 @@
cube
decor
+
+ ezoom
+
opengl
mousepoll
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2018-04-11 13:07:18 +0000
+++ po/POTFILES.in 2018-10-23 08:53:35 +0000
@@ -31,6 +31,7 @@
plugins/fade/fade.xml.in
plugins/fadedesktop/fadedesktop.xml.in
plugins/firepaint/firepaint.xml.in
+plugins/focuspoll/focuspoll.xml.in
plugins/freewins/freewins.xml.in
plugins/gears/gears.xml.in
plugins/gnomecompat/gnomecompat.xml.in