[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