[Merge] lp:~ksamak/compiz/ezoom_focus_tracking into lp:compiz
Samuel thibault
samuel.thibault at ens-lyon.org
Tue Sep 19 11:09:55 UTC 2017
I have commented on the callback issue, to avoid the singleton.
Diff comments:
>
> === 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 2017-02-25 09:38:08 +0000
> @@ -0,0 +1,490 @@
> +/*
> + *
> + * Compiz focus tracking plugin
> + *
> + * Copyright : (C) 2016 Auboyneau Vincent
> + * E-mail : ksamak at riseup.net
> + *
> + *
> + * 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 2
> + * 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.
> + *
> + */
> +
> +#include <iostream>
> +#include <functional>
> +
> +#include <boost/mem_fn.hpp>
> +#include <boost/bind.hpp>
> +#include <boost/function.hpp>
> +
> +#include <string.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +#include "accessibilitywatcher.h"
> +
> +AccessibilityWatcher* AccessibilityWatcher::instance = NULL;
> +bool AccessibilityWatcher::ignoreLinks = false;
> +
> +AccessibilityWatcher::AccessibilityWatcher() :
> + screenWidth(0),
> + screenHeight(0),
> + initialized(false)
> +{
> +}
> +
> +AccessibilityWatcher* AccessibilityWatcher::getInstance() {
> + if(instance == NULL) {
> + instance = new AccessibilityWatcher();
> + }
> + return instance;
> +}
> +
> +std::string AccessibilityWatcher::getLabel(AtspiAccessible *accessible) {
> + GArray *relations = atspi_accessible_get_relation_set (accessible, NULL);
> + if (relations == NULL) {
> + //g_object_unref(relations);
> + return "";
> + }
> +
> + AtspiRelation *relation;
> + for (unsigned i = 0; i < relations->len; ++i) {
> + relation = g_array_index (relations, AtspiRelation*, i);
> + if (atspi_relation_get_relation_type (relation) == ATSPI_RELATION_LABELLED_BY) {
> + g_object_unref(relations);
> + return atspi_accessible_get_name(atspi_relation_get_target(relation, 0), NULL);
> + }
> + }
> + if (relations->len > 0) {
> + g_object_unref(relations);
> + }
> + return "";
> +}
> +
> +void AccessibilityWatcher::setIgnoreLinks(bool val) {
> + ignoreLinks = val;
> +}
> +
> +void AccessibilityWatcher::setScreenLimits(int x, int y) {
> + screenWidth = x;
> + screenHeight = y;
> +}
> +
> +/*
> + * In some cases (or at least in the case of gnome-terminal), the slider
> + * is a child of the object that receives the focus/selection, so we
> + * need to search for it.
> + */
> +AtspiAccessible* AccessibilityWatcher::findChildSlider(AtspiAccessible *accessible) {
> + for (int i = 0; i < atspi_accessible_get_child_count (accessible, NULL); ++i) {
> + AtspiAccessible *child = atspi_accessible_get_child_at_index (accessible, i, NULL);
> + if (atspi_accessible_get_role (child, NULL) == ATSPI_ROLE_SLIDER) {
> + return child;
> + }
> + }
> + return NULL;
> +}
> +
> +const gchar* AccessibilityWatcher::atspiStateGetName (gint state) {
> + auto typeClass = g_type_class_ref (ATSPI_TYPE_STATE_TYPE);
> + g_return_val_if_fail (G_IS_ENUM_CLASS (typeClass), "");
> +
> + auto value = g_enum_get_value (G_ENUM_CLASS (typeClass), state);
> +
> + g_object_unref(typeClass);
> + return value->value_nick;
> +}
> +
> +std::ostream& operator<<(std::ostream& out, const AtspiEvent *event) {
> + if (std::string(atspi_accessible_get_name(event->source, NULL)) == "Paragraph 0") { return out; }
> + out << "=> ";
> + out << event->type;
> + out << event->source;
> + return out;
> +}
> +
> +std::ostream& operator<<(std::ostream& out, AtspiAccessible *access) {
> + out << " | " << atspi_accessible_get_name(atspi_accessible_get_application (access, NULL), NULL);
> + out << " | " << atspi_accessible_get_name(access, NULL);
> + out << " | " << atspi_accessible_get_role_name (access, NULL);
> +
> + AtspiStateSet *stateSet = atspi_accessible_get_state_set (access);
> + if (atspi_state_set_contains(stateSet, ATSPI_STATE_FOCUSED)) {
> + out << " => Focused";
> + }
> + g_object_unref(stateSet);
> + out << std::endl;
> +
> + auto component = atspi_accessible_get_component(access);
> + if (component) {
> + AtspiRect* size = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
> + out << " pos " << size->x << ":" << size->y << " size " << size->width << "x" << size->height << std::endl;
> + g_object_unref(component);
> + }
> + return out;
> +}
> +
> +void AccessibilityWatcher::registerEvent(const AtspiEvent *event, const std::string &type) {
> + FocusInfo res;
> + res.type = type; // registered from filter on calling event
> + res.name = atspi_accessible_get_name (event->source, NULL);
> + res.label = getLabel (event->source);
> + res.role = atspi_accessible_get_role_name (event->source, NULL);
> + res.application = atspi_accessible_get_name(atspi_accessible_get_application (event->source, NULL), NULL);
> +
> + auto child = atspi_accessible_get_parent(event->source, NULL);
> + while (!res.active && child) { // prevents events that are actually from an active child. weird behaviour.
> + auto stateSet = atspi_accessible_get_state_set (child);
> + if (atspi_state_set_contains(stateSet, ATSPI_STATE_ACTIVE)) {
> + res.active = true;
> + }
> + child = atspi_accessible_get_parent(child, NULL);
> + }
> +
> + AtspiComponent* component;
> + if (res.type == "active-descendant-changed") {
> + auto accessible = atspi_accessible_get_child_at_index(event->source, event->detail1, NULL);
> + component = atspi_accessible_get_component(accessible);
> + } else {
> + component = atspi_accessible_get_component(event->source);
> + }
> +
> + if (component) {
> + AtspiRect* size = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
> + res.x = size->x;
> + res.y = size->y;
> + res.w = size->width;
> + res.h = size->height;
> + g_object_unref(component);
> + }
> + // let's get the caret offset, and then its position for a caret event
> + if (type == "caret") {
> + auto text = atspi_accessible_get_text(event->source);
> + if (!text) {
> + return;
> + }
> + auto offset = atspi_text_get_caret_offset(text, NULL);
> + // if we're at the beginning of text, let the widget pos decide
> + if (event->detail1) {
> + AtspiRect *size = atspi_text_get_character_extents(text, offset, ATSPI_COORD_TYPE_SCREEN, NULL);
> + res.x = size->x;
> + res.y = size->y;
> + res.w = size->width;
> + res.h = size->height;
> + }
> + // correcting a missing offset to a caret move event in firefox
> + if (res.x == 0 && res.y == 0 && offset > 0) {
> + AtspiRect *size = atspi_text_get_character_extents(text, offset-1, ATSPI_COORD_TYPE_SCREEN, NULL);
> + res.x = size->x;
> + res.y = size->y;
> + res.w = size->width;
> + res.h = size->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)) {
> + //if (std::string(event->type) == "object:text-caret-moved" && (res.w > A11YWATCHER_MAX_CARET_WIDTH || res.h > A11YWATCHER_MAX_CARET_HEIGHT)) {
> + AtspiRect *size = atspi_text_get_character_extents(text, offset, ATSPI_COORD_TYPE_SCREEN, NULL);
> + res.x = size->x;
> + res.y = size->y;
> + res.w = size->width;
> + res.h = size->height;
> + if (type == "caret" && 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;
> + }
> + }
> + g_object_unref(text);
> +
> + // 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 = atspi_accessible_get_state_set (event->source);
> + if (atspi_state_set_contains(stateSet, ATSPI_STATE_FOCUSED)) {
> + res.focused = true;
> + AccessibilityWatcher::getInstance()->previouslyActiveMenus.clear(); // reset potential menu stack
> + }
> + if (atspi_state_set_contains(stateSet, ATSPI_STATE_SELECTED)) { // never works, GTK Bad implem?
> + res.selected = true;
> + }
> + if (res.type == "state-changed:selected" && event->detail1 == 1) {
> + res.selected = true;
> + AccessibilityWatcher::getInstance()->previouslyActiveMenus.push_back(res); // add to stack of menus
> + }
> +
> + if (appSpecificFilter(res, event)) {
> + return;
> + }
> + if (filterBadEvents(res)) {
> + return;
> + }
> + while (AccessibilityWatcher::getInstance()->focusList.size() >= 5) { // don't keep the whole history
> + AccessibilityWatcher::getInstance()->focusList.erase(AccessibilityWatcher::getInstance()->focusList.begin());
> + }
> + AccessibilityWatcher::getInstance()->focusList.push_back(res);
> +}
> +
> +bool AccessibilityWatcher::appSpecificFilter(FocusInfo& focus, const AtspiEvent* event) {
> + if (focus.type == "state-changed:selected" && // emulates on-change:selected false behaviour. eg: for menus
> + (focus.role == "menu item" ||
> + focus.role == "menu" ||
> + focus.role == "check menu item" ||
> + focus.role == "radio menu item") &&
> + focus.application != "mate-panel")
> + {
> + if (!focus.selected && AccessibilityWatcher::getInstance()->returnToPrevMenu()) {
> + return true;
> + }
> + focus.active = true;
> + }
> + if (focus.application == "soffice" && focus.role == "paragraph") { // LO-calc: avoid spam event from main edit line
> + auto parent = atspi_accessible_get_parent(event->source, NULL);
> + std::string parentLabel = atspi_accessible_get_name(parent, NULL);
> + if (parentLabel == "Input line" ||
> + parentLabel == "Ligne de saisie") {
> + return true;
> + }
> + }
> + if (focus.application == "Icedove") {
> + if (focus.type == "caret") {
> + auto text = 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, NULL);
> + auto string = std::string(atspi_text_get_text_at_offset(text, offset, ATSPI_TEXT_BOUNDARY_CHAR, NULL)->content);
> + auto stringM1 = std::string(atspi_text_get_text_at_offset(text, offset -1, ATSPI_TEXT_BOUNDARY_CHAR, NULL)->content);
> + if (offset == atspi_text_get_character_count(text, NULL) && string == "\0" && (stringM1 == "\n" || int(stringM1.c_str()[0]) == -17)) {
> + 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)
> + AccessibilityWatcher::getInstance()->focusList.push_back(focus);
> + return true;
> + }
> + auto component = atspi_accessible_get_component(event->source);
> + if (component) {
> + AtspiRect* size = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
> + focus.x = size->x;
> + focus.y = size->y;
> + focus.w = 7;
> + focus.h = size->height;
> + AccessibilityWatcher::getInstance()->focusList.push_back(focus);
> + return true;
> + }
> + }
> + }
> + if (focus.application == "Firefox") {
> + if (ignoreLinks && focus.type != "caret" && focus.role == "link") {
> + return true;
> + }
> + if (focus.type == "caret" &&
> + (static_cast<std::string>(event->type) == "object:text-changed:insert:system" ||
> + static_cast<std::string>(event->type) == "object:text-changed:delete:system")) { // prevents status bar focus in firefox
> + return true;
> + }
> + if (focus.type == "focus" && focus.role == "document frame") { // general page parasite event
> + return true;
> + }
> + if (focus.type == "caret" && !(focus.x == 0 && focus.y == 0)) {
> + AccessibilityWatcher::getInstance()->focusList.push_back(focus);
> + return true;
> + }
> + getAlternativeCaret(focus, event);
> + if (focus.type == "caret" && !(focus.xAlt == 0 && focus.yAlt == 0)) {
> + focus.x = focus.xAlt;
> + focus.y = focus.yAlt + focus.hAlt;
> + focus.w = focus.wAlt;
> + focus.h = focus.hAlt;
> + AccessibilityWatcher::getInstance()->focusList.push_back(focus);
> + return true;
> + }
> + }
> + if (focus.application == "evince" && focus.type == "state-changed:selected" && focus.role == "icon") { // LO-calc: avoid spam event from main edit line
> + return true; // ignores the parasite event from evince icon
> + }
> + return false;
> +}
> +
> +bool AccessibilityWatcher::filterBadEvents(const FocusInfo& event) {
> + if (event.type == "caret" && event.x ==0 && event.y == 0) {
> + return true;
> + }
> + if (!event.active) {
> + return true;
> + }
> + if (!event.focused && !event.selected) {
> + return true;
> + }
> + if (AccessibilityWatcher::getInstance()->screenWidth != 0 && AccessibilityWatcher::getInstance()->screenHeight !=0 &&
> + (event.x > AccessibilityWatcher::getInstance()->screenWidth ||
> + event.y > AccessibilityWatcher::getInstance()->screenHeight || // TODO remove when non-singleton
> + event.x < 0 ||
> + event.y < 0 )) {
> + return true;
> + }
> + return false;
> +}
> +
> +/*
> + * this is meant to emulate the missing "selected" argument in menu hierarchy.
> + * no idea whether this is a fail in GTK, or in ATSPI
> + */
> +bool AccessibilityWatcher::returnToPrevMenu() {
> + if (previouslyActiveMenus.size() > 1) {
> + previouslyActiveMenus.pop_back();
> + focusList.push_back(previouslyActiveMenus.back());
> + 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 = atspi_accessible_get_text(event->source);
> + if (!text) { return; }
> + auto offset = atspi_text_get_caret_offset(text, NULL);
> + auto caretChar = std::string(atspi_text_get_string_at_offset(text, offset, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content);
> + bool hasSeenDeviceControl1 = false;
> +
> + // 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, NULL) == offset ? 1 : 0;
> + int charIndex = 1;
> + bool charExtentsFound = false;
> +
> + AtspiRect *size = atspi_text_get_character_extents(text, 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 = atspi_text_get_character_extents(text, offset - charIndex, ATSPI_COORD_TYPE_SCREEN, NULL);
> + caretChar = atspi_text_get_string_at_offset(text, offset - charIndex, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content;
> + // if we found a caret, check we're at beginning of line (or of text) to extrapolate position
> + if ((size->x != 0 || size->y != 0) && int(caretChar[0]) != -17) {
> + if (offset - charIndex -1 >= 0 && std::string(atspi_text_get_string_at_offset(text, offset - charIndex -1, ATSPI_TEXT_GRANULARITY_CHAR, NULL)->content) == "\n") {
> + charExtentsFound = true; // first character of upper line has been found
> + } else if (offset - charIndex -1 == 0) {
> + size = atspi_text_get_character_extents(text, 0, ATSPI_COORD_TYPE_SCREEN, NULL);
> + // first character of upper line has been found
> + charExtentsFound = true;
> + }
> + } else if (caretChar == "\n" || int(caretChar[0]) == -17) {
> + ++lines;
> + }
> + if (!hasSeenDeviceControl1 && int(caretChar[0]) == -17 ) {
> + hasSeenDeviceControl1 = true;
> + }
> + ++charIndex;
> + }
> + if (!hasSeenDeviceControl1) {
> + lines--;
> + }
> + focus.xAlt = size->x;
> + focus.yAlt = size->y + (lines-1) * size->height;
> + focus.wAlt = size->width;
> + focus.hAlt = size->height;
> + }
> +}
> +
> +
> +void AccessibilityWatcher::onCaretMove (const AtspiEvent *event, void *data) {
> + registerEvent(event, "caret");
> +}
> +
> +void AccessibilityWatcher::onSelectedChange (const AtspiEvent *event, void *data) {
> + registerEvent(event, "state-changed:selected");
> +}
> +
> +void AccessibilityWatcher::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;}
> +
> + registerEvent(event, "focus");
> +}
> +
> +void AccessibilityWatcher::onDescendantChanged (const AtspiEvent *event, void *data) {
> + registerEvent(event, "active-descendant-changed");
> +}
> +
> +/* Register to events */
> +void AccessibilityWatcher::addWatches() {
> +
> + //auto callback = boost::bind (&AccessibilityWatcher::onFocus, this, _1, _2);
> + //auto bound_fn = boost::bind<void>( boost::mem_fn(&AccessibilityWatcher::onFocus), this, _1, _2);
> + //auto bound_fn = boost::bind( &AccessibilityWatcher::onFocus, this, _1, _2);
> + //boost::function<void (const AtspiEvent *event, void *data)> callback(functor, this);
> +
> +
> + //std::function<void (AtspiEvent*, void*)> bound_fn = std::bind(&AccessibilityWatcher::onFocus, this, std::placeholders::_1, std::placeholders::_2);
> + //using sig = void (AtspiEvent*, void*);
> + //using tamere = std::function<sig>;
> + //AtspiEventListenerCB bound_fn = std::bind(&AccessibilityWatcher::onFocus, this, std::placeholders::_1, std::placeholders::_2);
> + //focusListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(bound_fn), NULL, NULL);
AFAIK, C/C++ callbacks can be done simply by going through a non-member callback in which you call the C++ member function. I.e. something like this:
void callback_onFocus(AtspiEvent *event, void *data) {
AccessibilityWatcher *watcher = (AccessibilityWatcher *) data;
watcher->registerEvent(event, "focus");
}
void AccessibilityWatcher::addWatches() {
focusListener = atspi_event_listener_new (callback_onFocus, self, NULL);
...
}
> +
> + focusListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onFocus), NULL, NULL);
> + caretMoveListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onCaretMove), NULL, NULL);
> + selectedListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onSelectedChange), NULL, NULL);
> + descendantChangedListener = atspi_event_listener_new (reinterpret_cast<AtspiEventListenerCB>(onDescendantChanged), NULL, NULL);
> +
> + 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);
> +}
> +
> +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);
> +}
> +
> +void AccessibilityWatcher::init() {
> + if (initialized) { return; }
> +
> + atspi_init ();
> + atspi_set_main_context(g_main_context_default());
> + addWatches();
> +
> + initialized = true;
> +}
> +
> +void AccessibilityWatcher::quit() {
> + removeWatches();
> + initialized = false;
> +}
> +
> +std::deque<FocusInfo> AccessibilityWatcher::getFocusQueue() {
> + return focusList;
> +}
> +
> +void AccessibilityWatcher::resetFocusQueue() {
> + focusList.clear();
> +}
> +
--
https://code.launchpad.net/~ksamak/compiz/ezoom_focus_tracking/+merge/318270
Your team Compiz Maintainers is subscribed to branch lp:compiz.
More information about the Ubuntu-reviews
mailing list