#include "Atspi.hpp"
#include "Singleton.hpp"
+#include "dbusLocators.hpp"
#include <memory>
#include <functional>
#include <limits>
ADD_INTROSPECTION_FUNCTIONS(Action, ACTION);
ADD_INTROSPECTION_FUNCTIONS(Selection, SELECTION);
ADD_INTROSPECTION_FUNCTIONS(Collection, COLLECTION);
+ADD_INTROSPECTION_FUNCTIONS(Component, COMPONENT);
ADD_INTROSPECTION_FUNCTIONS(Value, VALUE);
ADD_INTROSPECTION_FUNCTIONS(EditableText, EDITABLE_TEXT);
{
ConnectAtClient();
- DBusClient proxy{"org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus"};
- auto addr = proxy.method<std::string()>("GetAddress").call();
+ DBusClient proxy{ dbusLocators::atspi::BUS, dbusLocators::atspi::OBJ_PATH, dbusLocators::atspi::BUS_INTERFACE };
+ auto addr = proxy.method<std::string()>(dbusLocators::atspi::GET_ADDRESS).call();
if (!addr) {
ERROR("failed");
} else {
- eldbusConnection = DBus::EldbusConnectionCallbackHandle{ eldbus_address_connection_get(std::get<0>(addr).c_str()), eldbus_connection_unref };
+ eldbusConnection = DBus::EldbusConnectionCallbackHandle{
+ eldbus_address_connection_get(std::get<0>(addr).c_str()),
+ eldbus_connection_unref };
}
+
+ eventListener = atspi_event_listener_new(onAtspiEventCb, this, nullptr);
+ ASSERT(eventListener);
+ atspi_event_listener_register(eventListener, "window:activate", NULL);
+ atspi_event_listener_register(eventListener, "object:state-changed:visible", NULL);
+ atspi_event_listener_register(eventListener, "object:state-changed:showing", NULL);
+ atspi_event_listener_register(eventListener, "object:bounds-changed", NULL);
+ atspi_event_listener_register(eventListener, "object:state-changed:defunct", NULL);
}
Atspi::~Atspi()
{
+ atspi_event_listener_deregister(eventListener, "window:activate", NULL);
+ atspi_event_listener_deregister(eventListener, "object:state-changed:visible", NULL);
+ atspi_event_listener_deregister(eventListener, "object:state-changed:showing", NULL);
+ atspi_event_listener_deregister(eventListener, "object:bounds-changed", NULL);
+ atspi_event_listener_deregister(eventListener, "object:state-changed:defunct", NULL);
+ g_object_unref(eventListener);
+
DisconnectAtClient();
}
+void Atspi::onAtspiEventCb(AtspiEvent *event, void *data)
+{
+ static_cast<Atspi *>(data)->onAtspiEvent(event);
+}
+
+Atspi::WatchHandler::~WatchHandler()
+{
+ atspi->removeWatcher(this);
+}
+
+void Atspi::removeWatcher(WatchHandler *wh)
+{
+ if (wh->atspi) {
+ ASSERT(wh->atspi == this);
+ watchedCallbacks[wh->index] = {};
+ ASSERT(!watchedCallbacks[wh->index]);
+ wh->atspi = nullptr;
+
+ while (!watchedCallbacks.empty() && !watchedCallbacks.back())
+ watchedCallbacks.pop_back();
+ }
+}
+
+std::unique_ptr<Atspi::WatchHandler> Atspi::registerWatcherForAllObjects(WatchCallback callback)
+{
+ std::unique_ptr<Atspi::WatchHandler> wh { new WatchHandler };
+ wh->atspi = this;
+ wh->index = watchedCallbacks.size();
+ watchedCallbacks.push_back(std::move(callback));
+ return std::move(wh);
+}
+
+void Atspi::onAtspiEvent(AtspiEvent *event)
+{
+ using namespace std::literals::string_literals;
+
+ auto source = make(event->source, true);
+ auto id = getUniqueId(source);
+ DEBUG("Event: detail1 %d detail2 %d source %20s event %s", event->detail1, event->detail2, id.c_str(), event->type);
+ Optional<WatchEvent> watchEventType;
+
+ if (event->type == "object:state-changed:showing"s || event->type == "object:state-changed:visible"s)
+ watchEventType = event->detail1 ? WatchEvent::shown : WatchEvent::hidden;
+ else if (event->type == "window:activate"s) watchEventType = WatchEvent::activated;
+ else if (event->type == "object:bounds-changed"s) watchEventType = WatchEvent::moved;
+ else if (event->type == "object:state-changed:defunct"s) watchEventType = WatchEvent::defunct;
+
+ if (watchEventType) {
+ for (size_t i = 0; i < watchedCallbacks.size(); ++i) {
+ if (watchedCallbacks[i]) watchedCallbacks[i](source, *watchEventType);
+ }
+ }
+}
+
bool Atspi::ConnectAtClient()
{
auto error = atspi_init();
INFO("Atspi has been already initialized");
}
- auto dbusStatus = setPropertyBool("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Status", "IsEnabled", true);
+ auto dbusStatus = setPropertyBool(dbusLocators::atspi::BUS, dbusLocators::atspi::OBJ_PATH, dbusLocators::atspi::STATUS_INTERFACE,
+ dbusLocators::atspi::IS_ENABLED, true);
if (!dbusStatus) {
ERROR("IsEnabled flag set to true procedure failed");
return false;
bool Atspi::DisconnectAtClient()
{
DEBUG("shutting down");
- auto dbusStatus = setPropertyBool("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Status", "IsEnabled", false);
+ auto dbusStatus = setPropertyBool(dbusLocators::atspi::BUS, dbusLocators::atspi::OBJ_PATH, dbusLocators::atspi::STATUS_INTERFACE,
+ dbusLocators::atspi::IS_ENABLED, false);
if (!dbusStatus)
ERROR("IsEnabled flag set to false procedure failed");
ERROR("Failed to create eldbus object");
return false;
}
- std::unique_ptr<Eldbus_Proxy, void (*)(Eldbus_Proxy *)> proxy{eldbus_proxy_get(dobj.get(), "org.freedesktop.DBus.Properties"), eldbus_proxy_unref};
+ std::unique_ptr<Eldbus_Proxy, void (*)(Eldbus_Proxy *)> proxy{
+ eldbus_proxy_get(dobj.get(), dbusLocators::freeDesktop::PROPERTIES_INTERFACE), eldbus_proxy_unref};
if (!proxy) {
- ERROR("Failed to create proxy object for 'org.freedesktop.DBus.Properties'");
+ ERROR("Failed to create proxy object for '%s'", dbusLocators::freeDesktop::PROPERTIES_INTERFACE);
return false;
}
- std::unique_ptr<Eldbus_Message, void (*)(Eldbus_Message *)> req{eldbus_proxy_method_call_new(proxy.get(), "Set"), eldbus_message_unref};
+ std::unique_ptr<Eldbus_Message, void (*)(Eldbus_Message *)> req{
+ eldbus_proxy_method_call_new(proxy.get(), dbusLocators::freeDesktop::SET), eldbus_message_unref};
if (!req) {
- ERROR("Failed to create method call on org.freedesktop.DBus.Properties.Set");
+ ERROR("Failed to create method call on %s.%s", dbusLocators::freeDesktop::PROPERTIES_INTERFACE, dbusLocators::freeDesktop::SET);
return false;
}
eldbus_message_ref(req.get());
)
{
callFunction<typename PropertyCallback<CallType>::CallType>(
- eldbusConnection, DBUS_INTERFACE_PROPERTIES, obj, "Get",
+ eldbusConnection, dbusLocators::freeDesktop::PROPERTIES_INTERFACE, obj, dbusLocators::freeDesktop::GET,
[callback](typename PropertyCallback<CallType>::VariantReturnType val) {
if (!val) {
callback(Error { val.getError() });
{
typename PropertySetterCallback<CallType>::VariantSetterType variantValue { std::move(value) };
callFunction<typename PropertySetterCallback<CallType>::CallType>(
- eldbusConnection, DBUS_INTERFACE_PROPERTIES, obj, "Set",
+ eldbusConnection, dbusLocators::freeDesktop::PROPERTIES_INTERFACE, obj, dbusLocators::freeDesktop::SET,
std::move(callback), interface, func_name, std::move(variantValue));
}
callFunction<std::unordered_map<std::string, std::string>()>(
eldbusConnection,
accessibleObj,
- "GetAttributes",
+ dbusLocators::atspi::GET_ATTRIBUTES,
std::move(callback)
);
}
void Atspi::getProcessId(const AtspiAccessiblePtr &accessibleObj, AsyncCallback<unsigned int> callback) const
{
- DBus::DBusClient dbus { "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", eldbusConnection };
- dbus.method<unsigned int(std::string)>("GetConnectionUnixProcessID").asyncCall(std::move(callback),
+ DBus::DBusClient dbus {
+ dbusLocators::freeDesktop::BUS, dbusLocators::freeDesktop::OBJ_PATH, dbusLocators::freeDesktop::INTERFACE,
+ eldbusConnection };
+ dbus.method<unsigned int(std::string)>(dbusLocators::freeDesktop::GET_CONNECTION_UNIX_PROCESS_ID).asyncCall(std::move(callback),
getBusName(accessibleObj));
}
callFunction<bool(std::string)>(
eldbusConnection,
accessibleObj,
- "DoActionName",
+ dbusLocators::atspi::DO_ACTION_NAME,
std::move(callback),
action
);
getProperty<AtspiAccessiblePtr()>(
eldbusConnection,
accessibleObj,
- "Parent",
+ dbusLocators::atspi::PARENT,
std::move(callback)
);
}
callFunction<int()>(
eldbusConnection,
accessibleObj,
- "GetIndexInParent",
+ dbusLocators::atspi::GET_INDEX_IN_PARENT,
std::move(callback)
);
}
callFunction<void(int)>(
eldbusConnection,
accessibleObj,
- "SelectChild",
+ dbusLocators::atspi::SELECT_CHILD,
std::move(callback),
index
);
getProperty<std::string()>(
eldbusConnection,
accessibleObj,
- "Name",
+ dbusLocators::atspi::NAME,
std::move(callback)
);
}
callFunction<uint32_t()>(
eldbusConnection,
accessibleObj,
- "GetRole",
+ dbusLocators::atspi::GET_ROLE,
[callback](ValueOrError<uint32_t> val) {
if (!val) callback(Error{ val.getError() });
else callback((AtspiRole)std::get<0>(val));
getProperty<int()>(
eldbusConnection,
accessibleObj,
- "ChildCount",
+ dbusLocators::atspi::CHILD_COUNT,
[callback](ValueOrError<int> val) {
if (!val) callback(Error{ val.getError() });
else callback((size_t)std::get<0>(val));
callFunction<AtspiAccessiblePtr(int)>(
eldbusConnection,
accessibleObj,
- "GetChildAtIndex",
+ dbusLocators::atspi::GET_CHILD_AT_INDEX,
std::move(callback),
(int)index
);
callFunction<std::array<uint32_t, 2>()>(
eldbusConnection,
accessibleObj,
- "GetState",
+ dbusLocators::atspi::GET_STATE,
[callback](ValueOrError<std::array<uint32_t, 2>> data) {
if (!data) {
callback(Error{ data.getError() });
});
}
-void Atspi::getRelationSet(const std::shared_ptr<AtspiAccessible> &accessibleObj,
+void Atspi::getRelationSet(const AtspiAccessiblePtr &accessibleObj,
AsyncCallback<std::vector<AccessibilityRelation>> callback) const
{
- using RelationSet = std::vector<std::tuple<uint32_t, std::vector<std::shared_ptr<AtspiAccessible>>>>;
+ using RelationSet = std::vector<std::tuple<uint32_t, std::vector<AtspiAccessiblePtr>>>;
callFunction<RelationSet()>(
eldbusConnection,
accessibleObj,
- "GetRelationSet",
+ dbusLocators::atspi::GET_RELATION_SET,
[callback](ValueOrError<RelationSet> relationSet) {
if (!relationSet) {
ERROR("dbus getRelationSet failed");
});
}
-void Atspi::getObjectInRelation(const std::shared_ptr<AtspiAccessible> &accessibleObj, AtspiRelationType searchType,
- AsyncCallback<std::shared_ptr<AtspiAccessible>> callback) const
+void Atspi::getScreenPosition(const AtspiComponentPtr &accessibleObj, AsyncCallback<Rectangle> callback) const
+{
+ callFunction<DBus::ValueOrError<std::tuple<int32_t, int32_t, int32_t, int32_t>>(uint32_t)>(
+ eldbusConnection,
+ accessibleObj,
+ dbusLocators::atspi::GET_EXTENTS,
+ [ = ](DBus::ValueOrError<std::tuple<int32_t, int32_t, int32_t, int32_t>> val) {
+ if (!val) {
+ callback(val.getError());
+ } else {
+ auto &p = std::get<0>(val);
+ callback(Rectangle{ { std::get<0>(p), std::get<1>(p) }, { std::get<2>(p), std::get<3>(p) } });
+ }
+ }, (uint32_t)ATSPI_COORD_TYPE_SCREEN);
+
+}
+
+void Atspi::getObjectInRelation(const AtspiAccessiblePtr &accessibleObj, AtspiRelationType searchType,
+ AsyncCallback<AtspiAccessiblePtr> callback) const
{
auto obj = accessibleObj;
getRelationSet(accessibleObj,
return;
}
- DEBUG("Relations found: %d", std::get<0>(relations).size());
for (auto r : std::get<0>(relations)) {
- DEBUG("Relation type: %d", r. type);
if (r.type == searchType) {
- DEBUG("Searched relation found");
auto objInRelation = r.targets[0];
callback(std::move(objInRelation));
return;
namespace
{
- void getValueTemplateFunction(const EldbusConnectionCallbackHandle &eldbusConnection, const std::shared_ptr<AtspiValue> &valueInterface, const std::string paramName,
+ void getValueTemplateFunction(const EldbusConnectionCallbackHandle &eldbusConnection, const AtspiValuePtr &valueInterface, const std::string paramName,
AsyncCallback<double> callback)
{
getProperty<double()>(
}
}
-void Atspi::getCurrentValue(const std::shared_ptr<AtspiValue> &valueInterface, AsyncCallback<double> callback) const
+void Atspi::getCurrentValue(const AtspiValuePtr &valueInterface, AsyncCallback<double> callback) const
{
- getValueTemplateFunction(eldbusConnection, valueInterface, "CurrentValue", std::move(callback));
+ getValueTemplateFunction(eldbusConnection, valueInterface, dbusLocators::atspi::CURRENT_VALUE, std::move(callback));
}
-void Atspi::setCurrentValue(const std::shared_ptr<AtspiValue> &valueInterface, double newValue, AsyncCallback<void> callback) const
+void Atspi::setCurrentValue(const AtspiValuePtr &valueInterface, double newValue, AsyncCallback<void> callback) const
{
setProperty<void(double)>(
eldbusConnection,
valueInterface,
- "CurrentValue",
+ dbusLocators::atspi::CURRENT_VALUE,
std::move(callback),
newValue
);
}
-void Atspi::getMaximumValue(const std::shared_ptr<AtspiValue> &valueInterface, AsyncCallback<double> callback) const
+void Atspi::getMaximumValue(const AtspiValuePtr &valueInterface, AsyncCallback<double> callback) const
{
- getValueTemplateFunction(eldbusConnection, valueInterface, "MaximumValue", std::move(callback));
+ getValueTemplateFunction(eldbusConnection, valueInterface, dbusLocators::atspi::MAXIMUM_VALUE, std::move(callback));
}
-void Atspi::getMinimumValue(const std::shared_ptr<AtspiValue> &valueInterface, AsyncCallback<double> callback) const
+void Atspi::getMinimumValue(const AtspiValuePtr &valueInterface, AsyncCallback<double> callback) const
{
- getValueTemplateFunction(eldbusConnection, valueInterface, "MinimumValue", std::move(callback));
+ getValueTemplateFunction(eldbusConnection, valueInterface, dbusLocators::atspi::MINIMUM_VALUE, std::move(callback));
}
template <typename T> void Atspi::getInterface(const AtspiAccessiblePtr &accessibleObj, AsyncCallback<std::shared_ptr<T>> callback) const
callFunction<std::vector<std::string>()>(
eldbusConnection,
accessibleObj,
- "GetInterfaces",
+ dbusLocators::atspi::GET_INTERFACES,
[this, callback, accessibleObj](ValueOrError<std::vector<std::string>> val) {
const auto interfaceName = InterfaceNameFromType<T>::interfaceName;
if (!val) {
auto pr = DBusClient { bus, path, ATSPI_DBUS_INTERFACE_COLLECTION, eldbusConnection };
std::get<8>(m.value) = reverse;
- auto mm = pr.method<std::vector<AtspiAccessiblePtr>(decltype(Matcher::value), uint32_t, int32_t, bool)>("GetMatches");
+ auto mm = pr.method<std::vector<AtspiAccessiblePtr>(decltype(Matcher::value), uint32_t, int32_t, bool)>(dbusLocators::atspi::GET_MATCHES);
mm.asyncCall(std::move(callback),
std::move(m.value),
(uint32_t)sortOrder,
callFunction<ReturnType(int32_t, int32_t, uint32_t)>(
self->atspi->eldbusConnection,
ptr,
- "GetNavigableAtPoint",
+ dbusLocators::atspi::GET_NAVIGABLE_AT_POINT,
[self](ReturnType value) {
process(std::move(self), std::move(value));
},
callFunction<ReturnType(int32_t, int32_t, uint32_t)>(
eldbusConnection,
root,
- "GetNavigableAtPoint",
+ dbusLocators::atspi::GET_NAVIGABLE_AT_POINT,
[h](ReturnType value) {
handler::process(std::move(h), std::move(value));
},
pt.x, pt.y, (uint32_t)ctype);
}
-void Atspi::getAllAcceptedObjects(const AtspiAccessiblePtr &root, AsyncCallback<std::tuple<std::vector<AcceptedObjectInfo>, Rectangle>> callback) const
-{
- using ReturnType = ValueOrError<int32_t, int32_t, int32_t, int32_t,
- std::vector<std::tuple<AtspiAccessiblePtr, int32_t, int32_t, int32_t, int32_t>>>;
-
- callFunction<ReturnType()>(
- eldbusConnection,
- root,
- "GetAllAcceptedObjects",
- [ = ](ReturnType value) {
- if (!value) {
- callback(Error { value.getError() });
- } else {
- std::tuple<std::vector<AcceptedObjectInfo>, Rectangle> res;
- auto &ois = std::get<0>(res);
- std::get<1>(res) = {
- { std::get<0>(value), std::get<1>(value) },
- { std::get<2>(value), std::get<3>(value) }
- };
- auto v = std::get<4>(value);
- ois.reserve(v.size());
- for (auto &a : v) {
- ois.push_back({});
- auto &o = ois.back();
- o.object = std::move(std::get<0>(a));
- o.pos = { { std::get<1>(a), std::get<2>(a)}, { std::get<3>(a), std::get<4>(a) } };
- }
- callback(std::move(res));
- }
- }
- );
-}
-
-std::vector<std::pair<std::string, std::string>> Atspi::getAttributes(const std::shared_ptr<AtspiAccessible> &accessibleObj) const
+std::vector<std::pair<std::string, std::string>> Atspi::getAttributes(const AtspiAccessiblePtr &accessibleObj) const
{
EXIT_IF_NULLPTR(accessibleObj);
GError *error = nullptr;
void Atspi::getVisibleWindowInfos(AsyncCallback<std::vector<VisibleWindowInfo>> callback)
{
- DBus::DBusClient dbus{ "org.enlightenment.wm", "/org/enlightenment/wm", "org.enlightenment.wm.proc", DBus::ConnectionType::SYSTEM };
- dbus.method<std::vector<VisibleWindowsReplyType>()>("GetVisibleWinInfo").asyncCall(
+ DBus::DBusClient dbus{
+ dbusLocators::windowManager::BUS, dbusLocators::windowManager::OBJ_PATH, dbusLocators::windowManager::INTERFACE,
+ DBus::ConnectionType::SYSTEM };
+ dbus.method<std::vector<VisibleWindowsReplyType>()>(dbusLocators::windowManager::GET_VISIBLE_WIN_INFO).asyncCall(
[ = ](DBus::ValueOrError<std::vector<VisibleWindowsReplyType>> wins) {
if (wins) {
std::vector<VisibleWindowInfo> res;
#include "UniversalSwitchLog.hpp"
#include "UniversalSwitch.hpp"
#include "Atspi.hpp"
+#include "dbusLocators.hpp"
#include "UIElement.hpp"
#include "DBus.hpp"
#include "utils.hpp"
+#include "Window.hpp"
+#include "UniversalSwitchLog.hpp"
#include <glib.h>
#include <glib-object.h>
#include <tuple>
#include <memory>
#include <functional>
+#include <algorithm>
+#include <unordered_set>
+#include <limits>
using AtspiAccessiblePtr = std::shared_ptr<AtspiAccessible>;
-static unsigned int contextChangeIdent = 0;
+static constexpr int MaximumAllowedElements = 1000;
+
+static std::initializer_list<AtspiRole> ignoredAtspiRoles = {
+ ATSPI_ROLE_APPLICATION,
+ ATSPI_ROLE_FILLER,
+ ATSPI_ROLE_SCROLL_PANE,
+ ATSPI_ROLE_SPLIT_PANE,
+ ATSPI_ROLE_WINDOW,
+ ATSPI_ROLE_IMAGE,
+ ATSPI_ROLE_LIST,
+ ATSPI_ROLE_ICON,
+ ATSPI_ROLE_TOOL_BAR,
+ ATSPI_ROLE_REDUNDANT_OBJECT,
+ ATSPI_ROLE_COLOR_CHOOSER,
+ ATSPI_ROLE_TREE_TABLE,
+ ATSPI_ROLE_PAGE_TAB_LIST,
+ ATSPI_ROLE_PAGE_TAB,
+ ATSPI_ROLE_SPIN_BUTTON,
+ ATSPI_ROLE_INPUT_METHOD_WINDOW,
+ ATSPI_ROLE_EMBEDDED,
+ ATSPI_ROLE_INVALID,
+ ATSPI_ROLE_NOTIFICATION,
+ ATSPI_ROLE_DATE_EDITOR,
+ ATSPI_ROLE_CALENDAR,
+};
NavigationInterface::~NavigationInterface()
{
public:
NavigationImpl()
{
- initializeContextSwitchTracking();
+#ifdef DEBUG_DEBUGS_WANTED
+ DBus::setDebugPrinter([](auto str, auto strLen) {
+ DEBUG("%s", std::string(str, strLen).c_str());
+ });
+#endif
+ watchHandle = Singleton<UniversalSwitch>::instance().getAtspi()->registerWatcherForAllObjects(
+ [this](const auto & src, auto eventType) {
+ this->onAtspiEvent(src, eventType);
+ });
+ initializeRebuildingContext(true);
}
~NavigationImpl() = default;
}
void getElementAtPoint(Point pt, std::function<void(DBus::ValueOrError<std::shared_ptr<AtspiAccessible>>)> callback) override
{
- if (rootObject) {
+ if (rootVisibleObject) {
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
if (!atspi)
callback(DBus::Error("no atspi"));
else
- atspi->getAtPoint(pt, Atspi::CoordType::Screen, rootObject, std::move(callback));
+ atspi->getAtPoint(pt, Atspi::CoordType::Screen, rootVisibleObject, std::move(callback));
} else {
callback(DBus::Error("no root object"));
}
}
private:
- std::shared_ptr<AtspiAccessible> lastActiveWindow, topWindow, rootObject;
+ std::shared_ptr<AtspiAccessible> rootObject, rootVisibleObject;
std::vector<std::shared_ptr<AtspiAccessible>> modalWindows;
+ std::unique_ptr<Atspi::WatchHandler> watchHandle;
struct GObjectUnref {
template <typename T> void operator()(T *t)
static Optional<unsigned int> getFocusProcessPid()
{
- auto ed = DBus::DBusClient{ "org.enlightenment.wm", "/org/enlightenment/wm", "org.enlightenment.wm.proc", DBus::ConnectionType::SYSTEM };
- auto m = ed.method<int()>("GetFocusProc");
+ auto ed = DBus::DBusClient{
+ dbusLocators::windowManager::BUS, dbusLocators::windowManager::OBJ_PATH, dbusLocators::windowManager::INTERFACE,
+ DBus::ConnectionType::SYSTEM };
+
+ auto m = ed.method<int()>(dbusLocators::windowManager::GET_FOCUS_PROC);
auto val = m.call();
if (!val) {
ERROR("failed to get pid");
return std::get<0>(val);
}
- struct row_info {
+ struct RowInfo {
unsigned int first, last;
Rectangle pos;
};
- struct object_info {
+ struct ObjectInfo {
std::shared_ptr<AtspiAccessible> obj;
Rectangle pos;
};
- enum class row_box_mode {
- ROW_BOX_NONE = 0,
- ROW_BOX_NORMAL,
- ROW_BOX_SPECIAL,
+ struct AcceptedObjectInfo {
+ AtspiAccessiblePtr object;
+ Rectangle pos;
};
- struct navigation_rows {
- std::vector<object_info> object_infos;
- std::vector<row_info> row_infos;
- Rectangle root_pos;
+ class NavigationRows
+ {
+ std::vector<ObjectInfo> object_infos;
+ std::vector<RowInfo> row_infos;
+ std::unordered_set<AtspiAccessiblePtr> knownObjects;
+ Rectangle root_pos = { { 0, 0 }, {1, 1 } };
+ public:
+ NavigationRows() = default;
+ NavigationRows(const std::vector<AcceptedObjectInfo> &objects, Rectangle root_pos) : root_pos(root_pos)
+ {
+ object_infos.reserve(objects.size());
+ row_infos.clear();
+
+ RowInfo rowInfo;
+ unsigned char new_line = 1;
+ int prev_obj_x = 0;
- unsigned int getRowCount()
+ for (unsigned int i = 0; i < objects.size(); ++i) {
+ auto &a = objects[i];
+ knownObjects.insert(a.object);
+ object_infos.push_back({});
+ auto &objInfo = object_infos.back();
+ objInfo.obj = std::move(a.object);
+ objInfo.pos = a.pos;
+
+ if (!new_line && (objInfo.pos.position.x < prev_obj_x || rowInfo.pos.position.y + rowInfo.pos.size.height <= objInfo.pos.position.y)) {
+ // new row
+ rowInfo.last = i - 1;
+ row_infos.push_back(rowInfo);
+ new_line = 1;
+ }
+ prev_obj_x = objInfo.pos.position.x;
+ if (new_line) {
+ new_line = 0;
+ rowInfo.first = i;
+ rowInfo.pos = objInfo.pos;
+ } else {
+ rowInfo.pos = Rectangle::sum(rowInfo.pos, objInfo.pos);
+ }
+ }
+ if (!new_line) {
+ rowInfo.last = (unsigned int)objects.size() - 1;
+ row_infos.push_back(rowInfo);
+ }
+ debugDump();
+ }
+ bool hasObject(const AtspiAccessiblePtr &p) const
+ {
+ return knownObjects.find(p) != knownObjects.end();
+ }
+ unsigned int getRowCount() const
{
return (unsigned int)row_infos.size();
}
- Optional<unsigned int> getColumnCountInRow(unsigned int row)
+ Optional<unsigned int> getColumnCountInRow(unsigned int row) const
{
if (row == 0 || row > getRowCount()) {
return {};
}
- row_info &ri = row_infos[row - 1];
+ auto &ri = row_infos[row - 1];
return ri.last - ri.first + 1;
}
- Optional<object_info> getElement(unsigned int row, unsigned int col)
+ Optional<ObjectInfo> getElement(unsigned int row, unsigned int col) const
{
if (row == 0 || col == 0 || row > getRowCount()) return {};
auto &ri = row_infos[row - 1];
auto &oi = object_infos[ri.first + col - 1];
return oi;
}
- Optional<Rectangle> getRowSize(unsigned int row)
+ Optional<Rectangle> getRowSize(unsigned int row) const
{
if (row == 0) {
DEBUG("root pos is %s", root_pos.toString().c_str());
}
return {};
}
- bool findElement(unsigned int &row, unsigned int &col, const std::shared_ptr<AtspiAccessible> &obj)
+ bool findElement(unsigned int &row, unsigned int &col, const std::shared_ptr<AtspiAccessible> &obj) const
{
for (unsigned int r = 0; r < row_infos.size(); ++r) {
- row_info &ri = row_infos[r];
+ auto &ri = row_infos[r];
for (unsigned int c = ri.first; c <= ri.last; ++c) {
- object_info &e = object_infos[c];
+ auto &e = object_infos[c];
if (e.obj == obj) {
row = r + 1;
col = c - ri.first + 1;
}
return false;
}
+ friend bool operator == (const NavigationRows &l, const NavigationRows &r)
+ {
+ if (l.getRowCount() != r.getRowCount()) return false;
+ for (size_t y = 1; y <= l.getRowCount(); ++y) {
+ if (*l.getColumnCountInRow(y) != *r.getColumnCountInRow(y)) return false;
+ auto s = *l.getColumnCountInRow(y);
+ for (size_t x = 1; x <= s; ++x) {
+ auto a = *l.getElement(y, x);
+ auto b = *r.getElement(y, x);
+ if (a.obj != b.obj) return false;
+ if (a.pos != b.pos) return false;
+ }
+ }
+ return true;
+ }
+ friend bool operator != (const NavigationRows &l, const NavigationRows &r)
+ {
+ return !(l == r);
+ }
+ void debugDump(const char *prefix = "")
+ {
+ DEBUG("%sobjects %d", prefix, (unsigned int)object_infos.size());
+ for (unsigned int r = 0; r < row_infos.size(); ++r) {
+ auto &ri = row_infos[r];
+ DEBUG("%srow %3d of %3d (size %s)", prefix, r + 1, row_infos.size(), ri.pos.toString().c_str());
+ for (unsigned int c = ri.first; c <= ri.last; ++c) {
+ auto &e = object_infos[c];
+ DEBUG("%s element %3d %3d = %s %s", prefix, c, c - ri.first + 1, e.pos.toString().c_str(),
+ Atspi::getUniqueId(e.obj).c_str());
+ }
+ }
+ }
};
- navigation_rows current_rows;
+ static void filterRows(std::vector<AcceptedObjectInfo> &objects)
+ {
+ auto mainWindow = Singleton<UniversalSwitch>::instance().getMainWindow();
+ auto mainWindowDims = mainWindow->getDimensions();
+
+ size_t src = 0, dst = 0;
+ for (; src < objects.size(); ++src) {
+ auto d = Rectangle::intersect(mainWindowDims, objects[src].pos);
+ if (!d.hasPositiveSize()) continue;
+ if (src + 1 < objects.size() && objects[src].pos == objects[src + 1].pos) continue;
+ objects[dst++] = std::move(objects[src]);
+ }
+ objects.resize(dst);
+ }
+
+ static void sortWidgets(std::vector<AcceptedObjectInfo> &objects)
+ {
+ auto sort_vertically = [](const AcceptedObjectInfo & l, const AcceptedObjectInfo & r) {
+ return l.pos.position.y < r.pos.position.y;
+ };
+ auto sort_horizontally = [](const AcceptedObjectInfo & l, const AcceptedObjectInfo & r) {
+ return l.pos.position.x < r.pos.position.x;
+ };
+ std::sort(objects.begin(), objects.end(), sort_vertically);
+ for (size_t b = 0; b < objects.size();) {
+ size_t e = b + 1;
+ const auto yl = objects[b].pos.position.y;
+ const auto hl = objects[b].pos.size.height;
+ const auto maxPositionYForObject = yl + (hl + 3) / 4;
+ while (e < objects.size() && objects[e].pos.position.y <= maxPositionYForObject) ++e;
+ std::sort(objects.data() + b, objects.data() + e, sort_horizontally);
+ b = e;
+ }
+ }
+
+ NavigationRows reconstructNavigationRows(std::vector<AcceptedObjectInfo> objects, Rectangle root_pos)
+ {
+ filterRows(objects);
+ sortWidgets(objects);
+ return NavigationRows { objects, root_pos };
+ }
+
+ NavigationRows current_rows;
unsigned int current_row_index = 0, current_column_index = 0, current_rows_count = 0, current_row_column_count = 0;
- void navigation_rows_debug_dump(const navigation_rows &nr)
+ static bool checkIfStillRunning()
{
- DEBUG("objects %d", (unsigned int)nr.object_infos.size());
- for (unsigned int r = 0; r < nr.row_infos.size(); ++r) {
- auto &ri = nr.row_infos[r];
- DEBUG("row %3d of %3d (size %s)", r + 1, nr.row_infos.size(), ri.pos.toString().c_str());
- for (unsigned int c = ri.first; c <= ri.last; ++c) {
- auto &e = nr.object_infos[c];
- DEBUG(" element %3d %3d = %s %s", c, c - ri.first + 1, e.pos.toString().c_str(),
- Atspi::getUniqueId(e.obj).c_str());
- }
+ auto a = Singleton<UniversalSwitch>::instance().getAtspi();
+ if (!a) {
+ ERROR("no atspi");
+ return false;
}
+ auto n = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ if (!n) {
+ ERROR("no navigation");
+ return false;
+ }
+ return true;
}
- void createNavigationRows(const std::shared_ptr<AtspiAccessible> &root, std::function<void(Optional<navigation_rows>)> callback)
+ template <typename Callback> static bool checkIfStillRunningOtherwiseCallCallback(const std::function<Callback> &callback)
{
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) {
+ if (!checkIfStillRunning()) {
callback({});
- return;
+ return false;
+ }
+ return true;
+ }
+
+ template <typename Callback, typename ... Elem> static bool checkIfStillRunningOtherwiseCallCallback(const std::function<Callback> &callback, const DBus::ValueOrError<Elem...> &val)
+ {
+ if (!val) {
+ ERROR("failed %s", val.getError().message.c_str());
+ callback({});
+ return false;
+ }
+ return checkIfStillRunningOtherwiseCallCallback(callback);
+ }
+
+ struct Empty { };
+ template <typename Elem, typename ALL = Empty> struct ParallerExecHelper {
+ public:
+ using CallbackType = std::function<void(ParallerExecHelper<Elem, ALL> &)>;
+
+ std::vector<Elem> elems;
+ ALL all;
+
+ ParallerExecHelper() = delete;
+ ParallerExecHelper(CallbackType callback, size_t size = 0) : elems(size), callback(std::move(callback)) { }
+ ~ParallerExecHelper()
+ {
+ callback(*this);
}
- atspi->getAllAcceptedObjects(root,
- [ = ](DBus::ValueOrError<std::tuple<std::vector<Atspi::AcceptedObjectInfo>, Rectangle>> value_) {
- if (value_) {
- navigation_rows navState;
- auto value = std::get<0>(value_);
- auto &objects = std::get<0>(value);
- navState.root_pos = std::get<1>(value);
- navState.object_infos.reserve(objects.size());
- navState.row_infos.clear();
-
- row_info rowInfo;
- unsigned char new_line = 1;
- int prev_obj_x = 0;
-
- for (unsigned int i = 0; i < objects.size(); ++i) {
- auto &a = objects[objects.size() - 1 - i];
- navState.object_infos.push_back({});
- auto &objInfo = navState.object_infos.back();
- objInfo.obj = std::move(a.object);
- objInfo.pos = a.pos;
-
- if (!new_line && (objInfo.pos.position.x < prev_obj_x || rowInfo.pos.position.y + rowInfo.pos.size.height <= objInfo.pos.position.y)) {
- // new row
- rowInfo.last = i - 1;
- navState.row_infos.push_back(rowInfo);
- new_line = 1;
+ private:
+ CallbackType callback;
+ };
+
+ template <typename ... ARGS> bool continueProcessingImpl(const char *filename, int fileline, const DBus::ValueOrError<ARGS...> &args)
+ {
+ if (!args) {
+ ERROR("in %s:%d: %s", filename, fileline, args.getError().message.c_str());
+ return false;
+ }
+ return checkIfStillRunning();
+ }
+#define continueProcessing(args) continueProcessingImpl(__FILE__, __LINE__, args)
+
+ void createNavigationRowsFromElements(
+ const AtspiAccessiblePtr &root,
+ const AtspiAccessiblePtr &visibleRoot,
+ AtspiCollectionPtr collection,
+ std::vector<AtspiAccessiblePtr> &elements,
+ std::function<void(Optional<NavigationRows>)> callback)
+ {
+ DEBUG("my root is %s", Atspi::getUniqueId(root).c_str());
+ DEBUG("got %d elements", elements.size());
+ for (size_t i = 0; i < elements.size(); ++i)
+ DEBUG("got element %2d %s", (unsigned int)i, Atspi::getUniqueId(elements[i]).c_str());
+ struct Elem {
+ AtspiAccessiblePtr obj, parent;
+ Rectangle pos;
+ bool hasRelation = false, isItem = false, isCollapsed = false, isDisabled = false;
+ };
+ struct All {
+ std::vector<AtspiAccessiblePtr> expandableElems, expandableAndExpandedElems;
+ std::unordered_map<AtspiAccessiblePtr, size_t> ptrToIndex;
+ Optional<Rectangle> rootPos, rootPos2;
+
+ bool hasError = false;
+ };
+ using ExecHelper = ParallerExecHelper<Elem, All>;
+
+ auto updateIsCollapsed = [](ExecHelper & result) {
+ // parent is not collapsed, when it's expandbable, but not expanded, so it must be in
+ // expandableElems (e1), but not in expandableAndExpandedElems (e2)
+ auto &e1 = result.all.expandableElems;
+ auto &e2 = result.all.expandableAndExpandedElems;
+
+ std::unordered_map<AtspiAccessiblePtr, size_t> parentPtrToIndex;
+ for (size_t i = 0; i < result.elems.size(); ++i) {
+ auto &e = result.elems[i];
+ if (e.parent) parentPtrToIndex[e.parent] = i;
+ }
+ std::sort(result.all.expandableElems.begin(), result.all.expandableElems.end());
+ std::sort(result.all.expandableAndExpandedElems.begin(), result.all.expandableAndExpandedElems.end());
+ for (size_t i1 = 0, i2 = 0; i1 < e1.size(); ++i1) {
+ while (i2 < e2.size() && e1[i1] > e2[i2]) ++i2;
+ if (i2 == e2.size() || e1[i1] != e2[i2]) {
+ auto it = parentPtrToIndex.find(e1[i1]);
+ if (it != parentPtrToIndex.end())
+ result.elems[it->second].isCollapsed = true;
+ }
+ }
+ };
+ auto clb = [callback, updateIsCollapsed](ExecHelper & result) {
+ auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ if (nav) {
+ DEBUG("finalizing navigation");
+ updateIsCollapsed(result);
+ for (auto &inf : result.elems) {
+ DEBUG("elem %30s pos %s relation %d item %d collapsed %d", Atspi::getUniqueId(inf.obj).c_str(),
+ inf.pos.toString().c_str(), inf.hasRelation ? 1 : 0, inf.isItem ? 1 : 0, inf.isCollapsed ? 1 : 0);
+ }
+ std::vector<AcceptedObjectInfo> objects;
+ objects.reserve(result.elems.size());
+ for (size_t i = 0; i < result.elems.size(); ++i) {
+ if (result.elems[i].obj && result.elems[i].pos.size.width > 0 && result.elems[i].pos.size.height > 0)
+ objects.push_back({ result.elems[i].obj, result.elems[i].pos });
+ }
+ auto &r = result.all.rootPos ? *result.all.rootPos : *result.all.rootPos2;
+ callback(nav->reconstructNavigationRows(objects, r));
+ } else {
+ DEBUG("no navigation");
+ callback({});
+ }
+ };
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+
+ auto sizes = std::make_shared<ExecHelper>(clb, elements.size());
+ auto queryChildWithGivenRoleAndChildrenCount = [ = ](const AtspiAccessiblePtr & parent, size_t index, size_t expectedCount,
+ AtspiRole expectedRole, std::function<void(AtspiAccessiblePtr elem)> callback) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ if (atspi) {
+ atspi->getChildrenCount(parent, [ = ](DBus::ValueOrError<size_t> count) {
+ if (continueProcessing(count)) {
+ if (std::get<0>(count) != expectedCount) {
+ callback(nullptr);
+ } else {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getChildAtIndex(parent, index, [ = ](DBus::ValueOrError<AtspiAccessiblePtr> child) {
+ if (continueProcessing(child)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getRole(std::get<0>(child), [ = ](DBus::ValueOrError<AtspiRole> role) {
+ if (continueProcessing(role)) {
+ if (std::get<0>(role) != expectedRole)
+ callback(nullptr);
+ else
+ callback(std::get<0>(child));
+ } else {
+ sizes->all.hasError = true;
+ }
+ });
+ } else {
+ sizes->all.hasError = true;
+ }
+ });
+ }
+ } else {
+ sizes->all.hasError = true;
}
- prev_obj_x = objInfo.pos.position.x;
- if (new_line) {
- new_line = 0;
- rowInfo.first = i;
- rowInfo.pos = objInfo.pos;
+ });
+ }
+ };
+ auto updateTotalSize = [ = ](AtspiAccessiblePtr elem, bool first) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ if (atspi) {
+ atspi->getComponentInterface(elem, [ = ](DBus::ValueOrError<AtspiComponentPtr> col) {
+ if (continueProcessing(col)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getScreenPosition(std::get<0>(col), [ = ](DBus::ValueOrError<Rectangle> pos) {
+ if (continueProcessing(pos)) {
+ if (first)
+ sizes->all.rootPos = std::get<0>(pos);
+ else
+ sizes->all.rootPos2 = std::get<0>(pos);
+ } else {
+ sizes->all.hasError = true;
+ }
+ });
} else {
- rowInfo.pos = Rectangle::sum(rowInfo.pos, objInfo.pos);
+ sizes->all.hasError = true;
+ }
+ });
+ } else {
+ DEBUG("no atspi");
+ }
+ };
+ atspi->getRole(visibleRoot, [ = ](DBus::ValueOrError<AtspiRole> role) {
+ if (continueProcessing(role)) {
+ auto callback = [ = ](AtspiAccessiblePtr c1) {
+ if (c1) {
+ queryChildWithGivenRoleAndChildrenCount(c1, 0, 1, ATSPI_ROLE_NOTIFICATION, [ = ](AtspiAccessiblePtr c2) {
+ if (c2) {
+ queryChildWithGivenRoleAndChildrenCount(c2, 1, 2, ATSPI_ROLE_FILLER, [ = ](AtspiAccessiblePtr c3) {
+ if (c3) {
+ updateTotalSize(c3, true);
+ }
+ });
+ }
+ });
}
+ };
+ if (std::get<0>(role) == ATSPI_ROLE_WINDOW) {
+ queryChildWithGivenRoleAndChildrenCount(visibleRoot, 0, 1, ATSPI_ROLE_DIALOG, std::move(callback));
+ } else {
+ callback(visibleRoot);
}
- if (!new_line) {
- rowInfo.last = (unsigned int)objects.size() - 1;
- navState.row_infos.push_back(rowInfo);
+ } else {
+ sizes->all.hasError = true;
+ }
+ });
+ updateTotalSize(visibleRoot, false);
+
+ for (size_t i = 0; i < elements.size(); ++i) {
+ auto &e = elements[i];
+ sizes->elems[i].obj = e;
+ sizes->all.ptrToIndex[e] = i;
+ }
+
+ atspi->getMatchedElements(collection, ATSPI_Collection_SORT_ORDER_CANONICAL, MaximumAllowedElements,
+ Atspi::Matcher().states({ ATSPI_STATE_VISIBLE, ATSPI_STATE_SHOWING, ATSPI_STATE_HIGHLIGHTABLE, ATSPI_STATE_ENABLED, ATSPI_STATE_EXPANDABLE }, ATSPI_Collection_MATCH_ALL).
+ roles(ignoredAtspiRoles, ATSPI_Collection_MATCH_NONE),
+ false, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
+ if (continueProcessing(elems))
+ sizes->all.expandableElems = std::move(std::get<0>(elems));
+ else
+ sizes->all.hasError = true;
+ });
+ atspi->getMatchedElements(collection, ATSPI_Collection_SORT_ORDER_CANONICAL, MaximumAllowedElements,
+ Atspi::Matcher().states({ ATSPI_STATE_VISIBLE, ATSPI_STATE_SHOWING, ATSPI_STATE_HIGHLIGHTABLE, ATSPI_STATE_ENABLED, ATSPI_STATE_EXPANDABLE, ATSPI_STATE_EXPANDED }, ATSPI_Collection_MATCH_ALL).
+ roles(ignoredAtspiRoles, ATSPI_Collection_MATCH_NONE),
+ false, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
+ if (continueProcessing(elems))
+ sizes->all.expandableAndExpandedElems = std::move(std::get<0>(elems));
+ else
+ sizes->all.hasError = true;
+ });
+ atspi->getMatchedElements(collection, ATSPI_Collection_SORT_ORDER_CANONICAL, MaximumAllowedElements,
+ Atspi::Matcher().states({ ATSPI_STATE_VISIBLE, ATSPI_STATE_SHOWING, ATSPI_STATE_HIGHLIGHTABLE, ATSPI_STATE_ENABLED }, ATSPI_Collection_MATCH_ALL).
+ roles({ ATSPI_ROLE_LIST_ITEM, ATSPI_ROLE_MENU_ITEM }, ATSPI_Collection_MATCH_ANY),
+ false, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
+ if (continueProcessing(elems)) {
+ for (auto &e : std::get<0>(elems)) {
+ auto indexIt = sizes->all.ptrToIndex.find(e);
+ if (indexIt != sizes->all.ptrToIndex.end())
+ sizes->elems[indexIt->second].isItem = true;
}
- navigation_rows_debug_dump(navState);
- callback(std::move(navState));
} else {
- ERROR("failed %s", value_.getError().message.c_str());
- callback({});
+ sizes->all.hasError = true;
+ }
+ });
+
+ for (size_t i = 0; i < elements.size(); ++i) {
+ auto &e = elements[i];
+ atspi->getComponentInterface(e, [ = ](DBus::ValueOrError<AtspiComponentPtr> com) {
+ if (continueProcessing(com)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getScreenPosition(std::get<0>(com), [ = ](DBus::ValueOrError<Rectangle> val) {
+ if (continueProcessing(val))
+ sizes->elems[i].pos = std::get<0>(val);
+ else
+ sizes->all.hasError = true;
+ });
+ } else {
+ sizes->all.hasError = true;
+ }
+ });
+ atspi->getObjectInRelation(e, ATSPI_RELATION_CONTROLLED_BY, [ = ](DBus::ValueOrError<AtspiAccessiblePtr> obj) {
+ if (continueProcessing(obj))
+ sizes->elems[i].hasRelation = std::get<0>(obj) != nullptr;
+ else
+ sizes->all.hasError = true;
+ });
+ atspi->getParent(e, [ = ](DBus::ValueOrError<AtspiAccessiblePtr> parent) {
+ if (continueProcessing(parent))
+ sizes->elems[i].parent = std::move(std::get<0>(parent));
+ else
+ sizes->all.hasError = true;
+ });
+ }
+ }
+
+ void createNavigationRowsNew(const std::shared_ptr<AtspiAccessible> &root, const std::shared_ptr<AtspiAccessible> &visibleRoot, std::function<void(Optional<NavigationRows>)> callback)
+ {
+ if (!checkIfStillRunningOtherwiseCallCallback(callback)) return;
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getCollectionInterface(root, [ = ](DBus::ValueOrError<AtspiCollectionPtr> col) {
+ if (checkIfStillRunningOtherwiseCallCallback(callback, col)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getMatchedElements(std::get<0>(col), ATSPI_Collection_SORT_ORDER_CANONICAL, MaximumAllowedElements,
+ Atspi::Matcher()
+ .states({ ATSPI_STATE_VISIBLE, ATSPI_STATE_SHOWING, ATSPI_STATE_HIGHLIGHTABLE, ATSPI_STATE_ENABLED }, ATSPI_Collection_MATCH_ALL)
+ .roles(ignoredAtspiRoles, ATSPI_Collection_MATCH_NONE)
+ ,
+ false, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
+ if (!checkIfStillRunningOtherwiseCallCallback(callback, elems)) return;
+ auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ nav->createNavigationRowsFromElements(root, visibleRoot, std::get<0>(col), std::get<0>(elems), callback);
+ });
+ }
+ });
+ }
+
+ void createNavigationRows(const std::shared_ptr<AtspiAccessible> &root, const std::shared_ptr<AtspiAccessible> &visibleRoot, std::function<void(Optional<NavigationRows>)> callback)
+ {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ if (!atspi) {
+ callback({});
+ return;
+ }
+ createNavigationRowsNew(root, visibleRoot,
+ [ = ](Optional<NavigationRows> nr) {
+ if (!nr) {
+ ERROR("failed");
+ } else {
+ callback(std::move(nr));
}
});
}
current_row_index = current_column_index = 0;
}
- void screenNavigationContextChanged(unsigned int currentContextChangeIdent, const std::shared_ptr<AtspiAccessible> &root)
+ void screenNavigationContextChanged(const AtspiAccessiblePtr &root, const AtspiAccessiblePtr &visibleRoot)
{
DEBUG("changing context");
current_rows_count = current_row_column_count = 0;
resetIndexes();
- createNavigationRows(root,
- [ = ](Optional<navigation_rows> rows) {
- if (currentContextChangeIdent != contextChangeIdent) {
- DEBUG("ignoring, as something else took over - rootObject has changed");
- return;
- }
+ createNavigationRows(root, visibleRoot,
+ [ = ](Optional<NavigationRows> rows) {
if (rows) {
current_rows = std::move(*rows);
current_rows_count = current_rows.getRowCount();
} else {
ERROR("failed to get navigation rows");
- current_rows.root_pos = { { 0, 0 }, {1, 1 } };
}
auto ui = std::make_shared<UIElement>(root);
- emitCallback<NavigationCallbackType::ContextChanged>(ui, current_rows.root_pos);
+ emitCallback<NavigationCallbackType::ContextChanged>(ui, *current_rows.getRowSize(0));
emitCallback<NavigationCallbackType::BoxMoved>(Rectangle{}, BoxPositionMode::NONE);
DEBUG("context has %d rows", current_rows_count);
ecore::Timer restartTimer;
- void rebuildContextFromZero()
+ void rebuildContextFromZero(bool force)
{
- auto rebuildContextCallback = [](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
- ASSERT(elems);
- auto &el = std::get<0>(elems);
- ASSERT(!el.empty());
- DEBUG("got %d elements", (unsigned int)el.size());
- for (auto i = 0u; i < el.size(); ++i) {
- auto n = atspi->getName(el[i]);
- DEBUG("got element %u = %s %s", i,
- Atspi::getUniqueId(el[i]).c_str(),
- (n ? (*n).c_str() : "")
- );
- }
- auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
- if (nav) {
- nav->buildContextForRoot(el[el.size() - 1]);
- }
- };
-
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
-
- auto currentPid = getpid();
- auto focusedPid = getFocusProcessPid();
- auto desktop = atspi->getDesktop();
- ASSERT(desktop);
+ if (!atspi) {
+ DEBUG("no atspi");
+ return;
+ }
auto checkIfHasChildren = [ = ](AtspiAccessiblePtr root, std::function<void(bool)> callback) {
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
atspi->getChildrenCount(root, [ = ](DBus::ValueOrError<size_t> count) {
- if (!count) {
- ERROR("failed %s", count.getError().message.c_str());
- return;
- }
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
- DEBUG("elem %s has %d children", Atspi::getUniqueId(root).c_str(), count ? (int)std::get<0>(count) : -1);
- if (std::get<0>(count) == 0) {
- callback(false);
- } else {
- atspi->getChildAtIndex(root, 0, [ = ](DBus::ValueOrError<AtspiAccessiblePtr> child) {
- if (!child) {
- ERROR("failed %s", count.getError().message.c_str());
- return;
- }
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
-
- atspi->getChildrenCount(std::get<0>(child), [ = ](DBus::ValueOrError<size_t> count) {
- if (!count) {
- ERROR("failed %s", count.getError().message.c_str());
- return;
+ if (continueProcessing(count)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ DEBUG("elem %s has %d children", Atspi::getUniqueId(root).c_str(), count ? (int)std::get<0>(count) : -1);
+ if (std::get<0>(count) == 0) {
+ callback(false);
+ } else {
+ atspi->getChildAtIndex(root, 0, [ = ](DBus::ValueOrError<AtspiAccessiblePtr> child) {
+ if (continueProcessing(child)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getChildrenCount(std::get<0>(child), [ = ](DBus::ValueOrError<size_t> count) {
+ if (continueProcessing(count)) {
+ DEBUG("elem %s has %d children", Atspi::getUniqueId(root).c_str(), count ? (int)std::get<0>(count) : -1);
+ callback(std::get<0>(count) > 0);
+ } else {
+ callback(false);
+ }
+ });
+ } else {
+ callback(false);
}
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
- DEBUG("elem %s has %d children", Atspi::getUniqueId(root).c_str(), count ? (int)std::get<0>(count) : -1);
- callback(std::get<0>(count) > 0);
});
- });
+ }
+ } else {
+ callback(false);
}
});
};
auto processProcess = [ = ](AtspiAccessiblePtr app) {
- DEBUG("process %s", Atspi::getUniqueId(app).c_str());
- if (!app) return;
-
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
-
+ ASSERT(atspi);
atspi->getCollectionInterface(app, [ = ](DBus::ValueOrError<AtspiCollectionPtr> col) {
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
- if (!col) {
- ERROR("failed %s", col.getError().message.c_str());
- } else {
+ if (continueProcessing(col)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
atspi->getMatchedElements(
std::get<0>(col),
- ATSPI_Collection_SORT_ORDER_CANONICAL, 5,
+ ATSPI_Collection_SORT_ORDER_CANONICAL, MaximumAllowedElements,
Atspi::Matcher().states({ATSPI_STATE_MODAL, ATSPI_STATE_SHOWING, ATSPI_STATE_VISIBLE}, ATSPI_Collection_MATCH_ALL),
false,
[ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
- if (!elems) {
- ERROR("error %s", elems.getError().message.c_str());
- return;
- }
- if (std::get<0>(elems).empty()) {
- DEBUG("no modal window found");
- atspi->getMatchedElements(
- std::get<0>(col),
- ATSPI_Collection_SORT_ORDER_CANONICAL, 5,
- Atspi::Matcher().states({ATSPI_STATE_SHOWING, ATSPI_STATE_VISIBLE}, ATSPI_Collection_MATCH_ALL).
- roles({ ATSPI_ROLE_DIALOG, ATSPI_ROLE_FRAME, ATSPI_ROLE_WINDOW, /*, ATSPI_ROLE_POPUP_MENU ? */ }, ATSPI_Collection_MATCH_ANY),
- false,
- rebuildContextCallback);
- } else {
- DEBUG("modal window found");
- rebuildContextCallback(std::move(elems));
+ if (continueProcessing(elems)) {
+ auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ ASSERT(nav);
+ if (std::get<0>(elems).empty()) {
+ DEBUG("no modal window found");
+
+ atspi->getMatchedElements(
+ std::get<0>(col),
+ ATSPI_Collection_SORT_ORDER_CANONICAL, 5,
+ Atspi::Matcher().states({ATSPI_STATE_SHOWING, ATSPI_STATE_VISIBLE}, ATSPI_Collection_MATCH_ALL).
+ roles({ ATSPI_ROLE_DIALOG, ATSPI_ROLE_FRAME, ATSPI_ROLE_WINDOW, ATSPI_ROLE_PAGE_TAB, /*, ATSPI_ROLE_POPUP_MENU ? */ }, ATSPI_Collection_MATCH_ANY),
+ false,
+ [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
+ if (continueProcessing(elems)) {
+ auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ ASSERT(nav);
+ ASSERT(!std::get<0>(elems).empty());
+ nav->buildContextForRoot(app, std::get<0>(elems)[0], force);
+ }
+ });
+ } else {
+ auto &el = std::get<0>(elems);
+ auto modal = el[el.size() - 1];
+ DEBUG("modal window %s selected", Atspi::getUniqueId(modal).c_str());
+
+ struct ElementNames {
+ std::vector<AtspiAccessiblePtr> elems;
+ std::vector<std::string> names;
+
+ ~ElementNames()
+ {
+ DEBUG("got %d modal windows", (unsigned int)elems.size());
+ for (unsigned int i = 0; i < elems.size(); ++i)
+ DEBUG("got modal window %u = %s %s", i, Atspi::getUniqueId(elems[i]).c_str(), names[i].c_str());
+ }
+ };
+ auto t = std::make_shared<ElementNames>();
+ t->elems = std::move(std::get<0>(elems));
+ t->names.resize(t->elems.size());
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ for (auto i = 0u; i < t->elems.size(); ++i) {
+ atspi->getName(t->elems[i], [ = ](DBus::ValueOrError<std::string> name) {
+ if (name) t->names[i] = std::move(std::get<0>(name));
+ });
+ }
+
+ nav->buildContextForRoot(modal, modal, force);
+ }
}
});
}
});
};
- atspi->getChildren(desktop,
- [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> desktopChildren) {
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
-
- if (!desktopChildren) {
- DEBUG("failed to get children, trying again soon");
+ struct All {
+ std::unordered_map<int32_t, AtspiAccessiblePtr> pidToAtspi;
+ std::unordered_map<AtspiAccessiblePtr, int32_t> atspiToPid;
+ std::unordered_map<AtspiAccessiblePtr, std::string> names;
+ int32_t ourPid;
+ std::vector<int32_t> pids;
+ bool weHaveChildren = false;
+ };
+ using ExecHelper = ParallerExecHelper<Empty, All>;
+ auto windowsParser = [ = ](ExecHelper & wins) {
+ for (auto it : wins.all.names) {
+ auto id = Atspi::getUniqueId(it.first);
+ auto pid = wins.all.atspiToPid[it.first];
+ DEBUG("got %6d %s %s", pid, id.c_str(), it.second.c_str());
+ }
+ for (auto pid : wins.all.pids) {
+ DEBUG("got pid %d", pid);
+ }
+ AtspiAccessiblePtr root;
+ for (auto pid : wins.all.pids) {
+ DEBUG("checking pid %d", pid);
+ auto ptr = wins.all.pidToAtspi[pid];
+ auto name = wins.all.names[ptr];
+ if ((pid == wins.all.ourPid && wins.all.weHaveChildren) || name == "quickpanel" || name == "volume" || pid == wins.all.pids.back()) {
+ root = std::move(ptr);
+ break;
+ }
+ }
+ if (root) {
+ processProcess(root);
+ } else {
auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
if (nav) {
- nav->restartTimer.reset(5.0f, [] {
- auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
- if (nav)
- {
- nav->rebuildContextFromZero();
- }
- return ecore::TimerRepetitionPolicy::cancel;
- });
+ DEBUG("restarting context navigation, as selected window was lost");
+ nav->initializeRebuildingContext(force, 0.2f);
+ } else {
+ DEBUG("no navigation");
}
- return;
}
- struct Data {
- Optional<AtspiAccessiblePtr> us, focused;
- size_t todo;
- };
- auto data = std::make_shared<Data>();
- auto processData = [ = ]() {
- if (data->us) {
- checkIfHasChildren(*data->us, [ = ](bool weHaveChildren) {
- processProcess(weHaveChildren ? *data->us : (data->focused ? *data->focused : nullptr));
- });
- } else if (data->focused)
- processProcess(*data->focused);
- };
- data->todo = std::get<0>(desktopChildren).size();
- for (auto app : std::get<0>(desktopChildren)) {
- if (!app) {
- --data->todo;
- if (data->todo == 0)
- processData();
- continue;
+ };
+ atspi->getVisibleWindowInfos([ = ](DBus::ValueOrError<std::vector<Atspi::VisibleWindowInfo>> wins_) {
+ if (continueProcessing(wins_)) {
+ auto &wins = std::get<0>(wins_);
+ std::vector<int> pids;
+ {
+ bool foundFocused = false;
+ for (auto &w : wins) {
+ bool visibility =
+ w.visibility == Atspi::WindowVisibility::unobscured ||
+ w.visibility == Atspi::WindowVisibility::partially_obscured ||
+ w.focused;
+ DEBUG("got pid %6d visibility %d (%2d %d)", w.pid, visibility ? 1 : 0, (int)w.visibility, (int)w.focused);
+ if (visibility) {
+ if (!foundFocused)
+ pids.push_back(w.pid);
+ if (w.focused) {
+ foundFocused = true;
+ }
+ }
+ }
}
- atspi->getProcessId(app,
- [ = ](DBus::ValueOrError<unsigned int> pid) {
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (!atspi) return;
- if (!pid) {
- DEBUG("error %s with root %s", pid.getError().message.c_str(), Atspi::getUniqueId(app).c_str());
- return;
+
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ auto desktop = atspi->getDesktop();
+ ASSERT(desktop);
+ atspi->getChildren(desktop,
+ [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> desktopChildren) {
+ if (continueProcessing(desktopChildren)) {
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+
+ auto ptr = std::make_shared<ExecHelper>(std::move(windowsParser), pids.size());
+ ptr->all.pids = std::move(pids);
+ ptr->all.ourPid = getpid();
+
+ for (auto app : std::get<0>(desktopChildren)) {
+ if (!app) continue;
+ auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
+ atspi->getProcessId(app,
+ [ = ](DBus::ValueOrError<unsigned int> pid) {
+ if (continueProcessing(pid)) {
+ auto p = (int32_t)std::get<0>(pid);
+ ptr->all.atspiToPid[app] = p;
+ ptr->all.pidToAtspi[p] = app;
+ if (p == ptr->all.ourPid) {
+ checkIfHasChildren(app, [ = ](bool hasChildren) {
+ ptr->all.weHaveChildren = hasChildren;
+ });
+ }
+ }
+ });
+ atspi->getName(app, [ = ](DBus::ValueOrError<std::string> name) {
+ if (continueProcessing(name))
+ ptr->all.names[app] = std::move(std::get<0>(name));
+ });
+ }
+ } else {
+ auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
+ ASSERT(nav);
+ nav->initializeRebuildingContext(force, 5.0f);
}
- if (focusedPid && std::get<0>(pid) == *focusedPid) data->focused = app;
- else if (std::get<0>(pid) == currentPid) data->us = app;
- --data->todo;
- if (data->todo == 0)
- processData();
});
}
});
}
- void buildContextForRoot(const std::shared_ptr<AtspiAccessible> &root)
+ void buildContextForRoot(const std::shared_ptr<AtspiAccessible> &root, const std::shared_ptr<AtspiAccessible> &visibleRoot, bool force)
{
if (root == rootObject) {
- DEBUG("no context switch, as object %s is already the root", Atspi::getUniqueId(root).c_str());
- return;
+ if (!force) {
+ DEBUG("no context switch, as object %s is already the root", Atspi::getUniqueId(root).c_str());
+ return;
+ }
+ DEBUG("object %s is already the root, but context switch is forced", Atspi::getUniqueId(root).c_str());
}
rootObject = root;
- screenNavigationContextChanged(contextChangeIdent, root);
+ rootVisibleObject = visibleRoot;
+ screenNavigationContextChanged(root, visibleRoot);
auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
if (atspi) {
atspi->getName(root,
[ = ](DBus::ValueOrError<std::string> name) {
DEBUG("new CONTEXT is %s %s", Atspi::getUniqueId(root).c_str(), name ? std::get<0>(name).c_str() : "");
});
+ } else {
+ DEBUG("no atspi");
}
}
ecore::Timer rebuildContextTimer;
- void initializeRebuildingContext()
+ void initializeRebuildingContext(bool force, float delay = 0.2f)
{
- rebuildContextTimer.reset(0.2f,
+ DEBUG("requesting context rebuild");
+ rebuildContextTimer.reset(delay,
[ = ]() {
auto nav = std::dynamic_pointer_cast<NavigationImpl>(Singleton<UniversalSwitch>::instance().getNavigationInterface());
if (nav)
- nav->rebuildContextFromZero();
+ nav->rebuildContextFromZero(force);
return ecore::TimerRepetitionPolicy::cancel;
});
}
- void onAtspiWindow(const AtspiEvent *event)
+ void onAtspiEvent(const AtspiAccessiblePtr &src, Atspi::WatchEvent event)
{
using namespace std::literals::string_literals;
+ bool rebuild = false;
- auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
- if (atspi) {
- auto source = atspi->make(event->source, true);
- auto id = atspi->getUniqueId(source);
- bool update = false;
- DEBUG("Event: %s: %s", event->type, id.c_str());
-
- if (event->type == "window:activate"s ||
- event->type == "object:state-changed:visible"s ||
- event->type == "object:state-changed:showing"s ||
- event->type == "object:bounds-changed"s ||
- event->type == "object:state-changed:defunct"s ||
- event->type == "object:children-changed"s) {
- initializeRebuildingContext();
- }
+ switch (event) {
+ case Atspi::WatchEvent::activated:
+ case Atspi::WatchEvent::shown:
+ rebuild = true;
+ break;
+ case Atspi::WatchEvent::defunct:
+ case Atspi::WatchEvent::hidden:
+ case Atspi::WatchEvent::moved:
+ if (current_rows.hasObject(src)) rebuild = true;
+ break;
+ default:
+ DEBUG("unknown event %d", (int)event);
+ ASSERT(0);
}
+ if (rebuild)
+ initializeRebuildingContext(true);
}
- static void onAtspiWindowCb(AtspiEvent *event, void *data)
- {
- ((NavigationImpl *)data)->onAtspiWindow(event);
- }
-
- void initializeContextSwitchTracking()
- {
- eventListener.reset(atspi_event_listener_new(onAtspiWindowCb, this, nullptr));
- atspi_event_listener_register(eventListener.get(), "window:activate", NULL);
- atspi_event_listener_register(eventListener.get(), "object:state-changed:visible", NULL);
- atspi_event_listener_register(eventListener.get(), "object:state-changed:showing", NULL);
- atspi_event_listener_register(eventListener.get(), "object:bounds-changed", NULL);
- atspi_event_listener_register(eventListener.get(), "object:state-changed:defunct", NULL);
- atspi_event_listener_register(eventListener.get(), "object:children-changed", NULL);
-
- initializeRebuildingContext();
- }
- void terminateContextSwitchTracking()
- {
- if (eventListener) {
- atspi_event_listener_deregister(eventListener.get(), "window:activate", NULL);
- atspi_event_listener_deregister(eventListener.get(), "object:state-changed:visible", NULL);
- atspi_event_listener_deregister(eventListener.get(), "object:state-changed:showing", NULL);
- atspi_event_listener_register(eventListener.get(), "object:bounds-changed", NULL);
- atspi_event_listener_register(eventListener.get(), "object:state-changed:defunct", NULL);
- atspi_event_listener_register(eventListener.get(), "object:children-changed", NULL);
- eventListener = {};
- }
- }
};
std::shared_ptr<NavigationInterface> createNavigationImpl()