diff options
Diffstat (limited to 'src/gui/accessible/linux')
-rw-r--r-- | src/gui/accessible/linux/atspiadaptor.cpp | 399 | ||||
-rw-r--r-- | src/gui/accessible/linux/atspiadaptor_p.h | 12 | ||||
-rw-r--r-- | src/gui/accessible/linux/dbusconnection.cpp | 39 | ||||
-rw-r--r-- | src/gui/accessible/linux/dbusconnection_p.h | 4 | ||||
-rw-r--r-- | src/gui/accessible/linux/dbusxml/Socket.xml | 2 | ||||
-rw-r--r-- | src/gui/accessible/linux/qspi_constant_mappings.cpp | 14 | ||||
-rw-r--r-- | src/gui/accessible/linux/qspiaccessiblebridge.cpp | 14 | ||||
-rw-r--r-- | src/gui/accessible/linux/qspiaccessiblebridge_p.h | 4 | ||||
-rw-r--r-- | src/gui/accessible/linux/qspiapplicationadaptor.cpp | 14 |
9 files changed, 394 insertions, 108 deletions
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp index 2e6fb720c5..722723207a 100644 --- a/src/gui/accessible/linux/atspiadaptor.cpp +++ b/src/gui/accessible/linux/atspiadaptor.cpp @@ -11,11 +11,12 @@ #include <qclipboard.h> #include <QtCore/qloggingcategory.h> -#include <QtCore/qlibraryinfo.h> +#include <QtCore/qtversion.h> #if QT_CONFIG(accessibility) #include "socket_interface.h" #include "qspi_constant_mappings_p.h" +#include <QtCore/private/qstringiterator_p.h> #include <QtGui/private/qaccessiblebridgeutils_p.h> #include "qspiapplicationadaptor_p.h" @@ -34,6 +35,13 @@ #define ATSPI_COORD_TYPE_PARENT 2 #endif +// ATSPI_*_VERSION defines were added in libatspi 2.50, +// as was the AtspiLive enum; define values here for older versions +#if !defined(ATSPI_MAJOR_VERSION) || !defined(ATSPI_MINOR_VERSION) || ATSPI_MAJOR_VERSION < 2 || ATSPI_MINOR_VERSION < 50 +#define ATSPI_LIVE_POLITE 1 +#define ATSPI_LIVE_ASSERTIVE 2 +#endif + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -41,11 +49,12 @@ using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(lcAccessibilityAtspi, "qt.accessibility.atspi") Q_LOGGING_CATEGORY(lcAccessibilityAtspiCreation, "qt.accessibility.atspi.creation") -AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent) +AtSpiAdaptor::AtSpiAdaptor(QAtSpiDBusConnection *connection, QObject *parent) : QDBusVirtualObject(parent), m_dbus(connection) , sendFocus(0) , sendObject(0) , sendObject_active_descendant_changed(0) + , sendObject_announcement(0) , sendObject_attributes_changed(0) , sendObject_bounds_changed(0) , sendObject_children_changed(0) @@ -126,6 +135,7 @@ QString AtSpiAdaptor::introspect(const QString &path) const " <interface name=\"org.a11y.atspi.Accessible\">\n" " <property access=\"read\" type=\"s\" name=\"Name\"/>\n" " <property access=\"read\" type=\"s\" name=\"Description\"/>\n" + " <property access=\"read\" type=\"s\" name=\"HelpText\"/>\n" " <property access=\"read\" type=\"(so)\" name=\"Parent\">\n" " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n" " </property>\n" @@ -167,6 +177,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const " <arg direction=\"out\" type=\"(so)\"/>\n" " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" " </method>\n" + " <method name=\"GetAccessibleId\">\n" + " <arg direction=\"out\" type=\"s\"/>\n" + " </method>\n" " </interface>\n" ); @@ -307,6 +320,39 @@ QString AtSpiAdaptor::introspect(const QString &path) const " </interface>\n" ); + static const QLatin1StringView selectionIntrospection( + " <interface name=\"org.a11y.atspi.Selection\">\n" + " <property name=\"NSelectedChildren\" type=\"i\" access=\"read\"/>\n" + " <method name=\"GetSelectedChild\">\n" + " <arg direction=\"in\" name=\"selectedChildIndex\" type=\"i\"/>\n" + " <arg direction=\"out\" type=\"(so)\"/>\n" + " <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QSpiObjectReference\"/>\n" + " </method>\n" + " <method name=\"SelectChild\">\n" + " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " <method name=\"DeselectSelectedChild\">\n" + " <arg direction=\"in\" name=\"selectedChildIndex\" type=\"i\"/>\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " <method name=\"IsChildSelected\">\n" + " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " <method name=\"SelectAll\">\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " <method name=\"ClearSelection\">\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " <method name=\"DeselectChild\">\n" + " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" + " <arg direction=\"out\" type=\"b\"/>\n" + " </method>\n" + " </interface>\n" + ); + static const QLatin1StringView tableIntrospection( " <interface name=\"org.a11y.atspi.Table\">\n" " <property access=\"read\" type=\"i\" name=\"NRows\"/>\n" @@ -432,6 +478,14 @@ QString AtSpiAdaptor::introspect(const QString &path) const " <arg direction=\"out\" name=\"row_extents\" type=\"i\" />\n" " <arg direction=\"out\" name=\"col_extents\" type=\"i\" />\n" " </method>\n" + " <method name=\"GetColumnHeaderCells\">\n" + " <arg direction=\"out\" type=\"a(so)\"/>\n" + " <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" + " </method>\n" + " <method name=\"GetRowHeaderCells\">\n" + " <arg direction=\"out\" type=\"a(so)\"/>\n" + " <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" + " </method>\n" " </interface>\n" ); @@ -588,7 +642,7 @@ QString AtSpiAdaptor::introspect(const QString &path) const QAccessibleInterface * interface = interfaceFromPath(path); if (!interface) { - qCDebug(lcAccessibilityAtspi) << "WARNING Qt AtSpiAdaptor: Could not find accessible on path: " << path; + qCWarning(lcAccessibilityAtspi) << "Could not find accessible on path:" << path; return QString(); } @@ -605,6 +659,8 @@ QString AtSpiAdaptor::introspect(const QString &path) const xml.append(editableTextIntrospection); if (interfaces.contains(ATSPI_DBUS_INTERFACE_ACTION ""_L1)) xml.append(actionIntrospection); + if (interfaces.contains(ATSPI_DBUS_INTERFACE_SELECTION ""_L1)) + xml.append(selectionIntrospection); if (interfaces.contains(ATSPI_DBUS_INTERFACE_TABLE ""_L1)) xml.append(tableIntrospection); if (interfaces.contains(ATSPI_DBUS_INTERFACE_TABLE_CELL ""_L1)) @@ -631,6 +687,8 @@ void AtSpiAdaptor::setBitFlag(const QString &flag) if (false) { } else if (right.startsWith("ActiveDescendantChanged"_L1)) { sendObject_active_descendant_changed = 1; + } else if (right.startsWith("Announcement"_L1)) { + sendObject_announcement = 1; } else if (right.startsWith("AttributesChanged"_L1)) { sendObject_attributes_changed = 1; } else if (right.startsWith("BoundsChanged"_L1)) { @@ -699,7 +757,7 @@ void AtSpiAdaptor::setBitFlag(const QString &flag) || right.startsWith("VisibledataChanged"_L1)) { // typo in libatspi sendObject_visible_data_changed = 1; } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: subscription string not handled:" << flag; + qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; } } break; @@ -745,7 +803,7 @@ void AtSpiAdaptor::setBitFlag(const QString &flag) } else if (right.startsWith("DesktopDestroy"_L1)) { // ignore this one } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: subscription string not handled:" << flag; + qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; } } break; @@ -764,7 +822,7 @@ void AtSpiAdaptor::setBitFlag(const QString &flag) break; } default: - qCDebug(lcAccessibilityAtspi) << "WARNING: subscription string not handled:" << flag; + qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; } } @@ -882,6 +940,26 @@ void AtSpiAdaptor::notifyStateChange(QAccessibleInterface *interface, const QStr sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "StateChanged"_L1, stateArgs); } +void AtSpiAdaptor::sendAnnouncement(QAccessibleAnnouncementEvent *event) +{ + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qCWarning(lcAccessibilityAtspi, "Announcement event has no accessible set."); + return; + } + if (!iface->isValid()) { + qCWarning(lcAccessibilityAtspi) << "Announcement event with invalid accessible: " << iface; + return; + } + + const QString path = pathForInterface(iface); + const QString message = event->message(); + const QAccessible::AnnouncementPriority prio = event->priority(); + const int politeness = (prio == QAccessible::AnnouncementPriority::Assertive) ? ATSPI_LIVE_ASSERTIVE : ATSPI_LIVE_POLITE; + + const QVariantList args = packDBusSignalArguments(QString(), politeness, 0, QVariant::fromValue(QDBusVariant(message))); + sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "Announcement"_L1, args); +} /*! This function gets called when Qt notifies about accessibility updates. @@ -917,8 +995,17 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) } case QAccessible::NameChanged: { if (sendObject || sendObject_property_change || sendObject_property_change_accessible_name) { - QString path = pathForInterface(event->accessibleInterface()); - QVariantList args = packDBusSignalArguments("accessible-name"_L1, 0, 0, variantForPath(path)); + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qCDebug(lcAccessibilityAtspi, + "NameChanged event from invalid accessible."); + return; + } + + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments( + "accessible-name"_L1, 0, 0, + QVariant::fromValue(QDBusVariant(iface->text(QAccessible::Name)))); sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "PropertyChange"_L1, args); } @@ -926,8 +1013,17 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) } case QAccessible::DescriptionChanged: { if (sendObject || sendObject_property_change || sendObject_property_change_accessible_description) { - QString path = pathForInterface(event->accessibleInterface()); - QVariantList args = packDBusSignalArguments("accessible-description"_L1, 0, 0, variantForPath(path)); + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qCDebug(lcAccessibilityAtspi, + "DescriptionChanged event from invalid accessible."); + return; + } + + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments( + "accessible-description"_L1, 0, 0, + QVariant::fromValue(QDBusVariant(iface->text(QAccessible::Description)))); sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "PropertyChange"_L1, args); } @@ -938,13 +1034,21 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) sendFocusChanged(event->accessibleInterface()); break; } + + case QAccessible::Announcement: { + if (sendObject || sendObject_announcement) { + QAccessibleAnnouncementEvent *announcementEvent = static_cast<QAccessibleAnnouncementEvent*>(event); + sendAnnouncement(announcementEvent); + } + break; + } case QAccessible::TextInserted: case QAccessible::TextRemoved: case QAccessible::TextUpdated: { if (sendObject || sendObject_text_changed) { QAccessibleInterface * iface = event->accessibleInterface(); if (!iface || !iface->textInterface()) { - qCDebug(lcAccessibilityAtspi) << "Received text event for invalid interface."; + qCWarning(lcAccessibilityAtspi) << "Received text event for invalid interface."; return; } QString path = pathForInterface(iface); @@ -1248,6 +1352,7 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) case QAccessible::HelpChanged: case QAccessible::DefaultActionChanged: case QAccessible::AcceleratorChanged: + case QAccessible::IdentifierChanged: case QAccessible::InvalidEvent: break; } @@ -1337,11 +1442,11 @@ bool AtSpiAdaptor::handleMessage(const QDBusMessage &message, const QDBusConnect // get accessible interface QAccessibleInterface * accessible = interfaceFromPath(message.path()); if (!accessible) { - qCDebug(lcAccessibilityAtspi) << "WARNING Qt AtSpiAdaptor: Could not find accessible on path: " << message.path(); + qCWarning(lcAccessibilityAtspi) << "Could not find accessible on path:" << message.path(); return false; } if (!accessible->isValid()) { - qCWarning(lcAccessibilityAtspi) << "WARNING Qt AtSpiAdaptor: Accessible invalid: " << accessible << message.path(); + qCWarning(lcAccessibilityAtspi) << "Accessible invalid:" << accessible << message.path(); return false; } @@ -1371,6 +1476,8 @@ bool AtSpiAdaptor::handleMessage(const QDBusMessage &message, const QDBusConnect return componentInterface(accessible, function, message, connection); if (interface == ATSPI_DBUS_INTERFACE_ACTION ""_L1) return actionInterface(accessible, function, message, connection); + if (interface == ATSPI_DBUS_INTERFACE_SELECTION ""_L1) + return selectionInterface(accessible, function, message, connection); if (interface == ATSPI_DBUS_INTERFACE_TEXT ""_L1) return textInterface(accessible, function, message, connection); if (interface == ATSPI_DBUS_INTERFACE_EDITABLE_TEXT ""_L1) @@ -1390,7 +1497,7 @@ bool AtSpiAdaptor::handleMessage(const QDBusMessage &message, const QDBusConnect bool AtSpiAdaptor::applicationInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) { if (message.path() != ATSPI_DBUS_PATH_ROOT ""_L1) { - qCDebug(lcAccessibilityAtspi) << "WARNING Qt AtSpiAdaptor: Could not find application interface for: " << message.path() << interface; + qCWarning(lcAccessibilityAtspi) << "Could not find application interface for:" << message.path() << interface; return false; } @@ -1442,7 +1549,7 @@ void AtSpiAdaptor::registerApplication() const QSpiObjectReference &socket = reply.value(); accessibilityRegistry = QSpiObjectReference(socket); } else { - qCDebug(lcAccessibilityAtspi) << "Error in contacting registry: " + qCWarning(lcAccessibilityAtspi) << "Error in contacting registry:" << reply.error().name() << reply.error().message(); } @@ -1499,6 +1606,8 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS sendReply(connection, message, accessibleInterfaces(interface)); } else if (function == "GetDescription"_L1) { sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Description)))); + } else if (function == "GetHelpText"_L1) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Help)))); } else if (function == "GetState"_L1) { quint64 spiState = spiStatesFromQState(interface->state()); if (interface->tableInterface()) { @@ -1519,7 +1628,7 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS sendReply(connection, message, QVariant::fromValue(spiStateSetFromSpiStates(spiState))); } else if (function == "GetAttributes"_L1) { - sendReply(connection, message, QVariant::fromValue(QSpiAttributeSet())); + sendReply(connection, message, QVariant::fromValue(getAttributes(interface))); } else if (function == "GetRelationSet"_L1) { sendReply(connection, message, QVariant::fromValue(relationSet(interface, connection))); } else if (function == "GetApplication"_L1) { @@ -1535,8 +1644,11 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS children << ref; } connection.send(message.createReply(QVariant::fromValue(children))); + } else if (function == "GetAccessibleId"_L1) { + sendReply(connection, message, + QVariant::fromValue(QDBusVariant(QAccessibleBridgeUtils::accessibleId(interface)))); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::accessibleInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::accessibleInterface does not implement" << function << message.path(); return false; } return true; @@ -1573,6 +1685,9 @@ QStringList AtSpiAdaptor::accessibleInterfaces(QAccessibleInterface *interface) if (interface->actionInterface() || interface->valueInterface()) ifaces << u"" ATSPI_DBUS_INTERFACE_ACTION ""_s; + if (interface->selectionInterface()) + ifaces << ATSPI_DBUS_INTERFACE_SELECTION ""_L1; + if (interface->textInterface()) ifaces << u"" ATSPI_DBUS_INTERFACE_TEXT ""_s; @@ -1623,7 +1738,7 @@ QString AtSpiAdaptor::pathForObject(QObject *object) const Q_ASSERT(object); if (inheritsQAction(object)) { - qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::pathForObject: warning: creating path with QAction as object."; + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::pathForObject: Creating path with QAction as object."; } QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(object); @@ -1657,15 +1772,14 @@ bool AtSpiAdaptor::inheritsQAction(QObject *object) // Component static QAccessibleInterface * getWindow(QAccessibleInterface * interface) { - if (interface->role() == QAccessible::Dialog || interface->role() == QAccessible::Window) - return interface; - - QAccessibleInterface * parent = interface->parent(); - while (parent && parent->role() != QAccessible::Dialog - && parent->role() != QAccessible::Window) - parent = parent->parent(); - - return parent; + // find top-level window in a11y hierarchy (either has a + // corresponding role or is a direct child of the application object) + QAccessibleInterface* app = QAccessible::queryAccessibleInterface(qApp); + while (interface && interface->role() != QAccessible::Dialog + && interface->role() != QAccessible::Window && interface->parent() != app) + interface = interface->parent(); + + return interface; } bool AtSpiAdaptor::componentInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) @@ -1752,7 +1866,7 @@ bool AtSpiAdaptor::componentInterface(QAccessibleInterface *interface, const QSt qCDebug(lcAccessibilityAtspi) << "SetSize is not implemented."; sendReply(connection, message, false); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::componentInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::componentInterface does not implement" << function << message.path(); return false; } return true; @@ -1767,12 +1881,12 @@ QRect AtSpiAdaptor::getExtents(QAccessibleInterface *interface, uint coordType) bool AtSpiAdaptor::actionInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) { if (function == "GetNActions"_L1) { - int count = QAccessibleBridgeUtils::effectiveActionNames(interface).count(); + int count = QAccessibleBridgeUtils::effectiveActionNames(interface).size(); sendReply(connection, message, QVariant::fromValue(QDBusVariant(QVariant::fromValue(count)))); } else if (function == "DoAction"_L1) { int index = message.arguments().at(0).toInt(); const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(interface); - if (index < 0 || index >= actionNames.count()) + if (index < 0 || index >= actionNames.size()) return false; const QString actionName = actionNames.at(index); bool success = QAccessibleBridgeUtils::performEffectiveAction(interface, actionName); @@ -1782,13 +1896,13 @@ bool AtSpiAdaptor::actionInterface(QAccessibleInterface *interface, const QStrin } else if (function == "GetName"_L1) { int index = message.arguments().at(0).toInt(); const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(interface); - if (index < 0 || index >= actionNames.count()) + if (index < 0 || index >= actionNames.size()) return false; sendReply(connection, message, actionNames.at(index)); } else if (function == "GetDescription"_L1) { int index = message.arguments().at(0).toInt(); const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(interface); - if (index < 0 || index >= actionNames.count()) + if (index < 0 || index >= actionNames.size()) return false; QString description; if (QAccessibleActionInterface *actionIface = interface->actionInterface()) @@ -1799,7 +1913,7 @@ bool AtSpiAdaptor::actionInterface(QAccessibleInterface *interface, const QStrin } else if (function == "GetKeyBinding"_L1) { int index = message.arguments().at(0).toInt(); const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(interface); - if (index < 0 || index >= actionNames.count()) + if (index < 0 || index >= actionNames.size()) return false; QStringList keyBindings; if (QAccessibleActionInterface *actionIface = interface->actionInterface()) @@ -1809,12 +1923,12 @@ bool AtSpiAdaptor::actionInterface(QAccessibleInterface *interface, const QStrin if (!acc.isEmpty()) keyBindings.append(acc); } - if (keyBindings.length() > 0) + if (keyBindings.size() > 0) sendReply(connection, message, keyBindings.join(u';')); else sendReply(connection, message, QString()); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::actionInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::actionInterface does not implement" << function << message.path(); return false; } return true; @@ -1896,8 +2010,13 @@ bool AtSpiAdaptor::textInterface(QAccessibleInterface *interface, const QString int offset = message.arguments().at(0).toInt(); int start; int end; - QString result = interface->textInterface()->textAtOffset(offset, QAccessible::CharBoundary, &start, &end); - sendReply(connection, message, (int) *(qPrintable (result))); + const QString charString = interface->textInterface() + ->textAtOffset(offset, QAccessible::CharBoundary, &start, &end); + int codePoint = 0; + QStringIterator stringIt(charString); + if (stringIt.hasNext()) + codePoint = static_cast<int>(stringIt.peekNext()); + sendReply(connection, message, codePoint); } else if (function == "GetCharacterExtents"_L1) { int offset = message.arguments().at(0).toInt(); int coordType = message.arguments().at(1).toUInt(); @@ -1994,7 +2113,7 @@ bool AtSpiAdaptor::textInterface(QAccessibleInterface *interface, const QString interface->textInterface()->setSelection(selectionNum, startOffset, endOffset); sendReply(connection, message, true); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::textInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::textInterface does not implement" << function << message.path(); return false; } return true; @@ -2063,7 +2182,7 @@ namespace QString atspiColor(const QString &ia2Color) { // "rgb(%u,%u,%u)" -> "%u,%u,%u" - return ia2Color.mid(4, ia2Color.size() - (4+1)); + return ia2Color.mid(4, ia2Color.size() - (4+1)).replace(u"\\,"_s, u","_s); } QString atspiSize(const QString &ia2Size) @@ -2077,9 +2196,9 @@ namespace QString name = ia2Name; QString value = ia2Value; - // IAccessible2: http://www.linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes - // ATK attribute names: https://git.gnome.org/browse/orca/tree/src/orca/text_attribute_names.py - // ATK attribute values: https://developer.gnome.org/atk/unstable/AtkText.html#AtkTextAttribute + // IAccessible2: https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes + // ATK attribute names: https://gitlab.gnome.org/GNOME/orca/-/blob/master/src/orca/text_attribute_names.py + // ATK attribute values: https://gnome.pages.gitlab.gnome.org/atk/AtkText.html#AtkTextAttribute // https://bugzilla.gnome.org/show_bug.cgi?id=744553 "ATK docs provide no guidance for allowed values of some text attributes" // specifically for "weight", "invalid", "language" and value range for colors @@ -2125,6 +2244,13 @@ namespace // (on which it produces traceback and fails to read any following text attributes), // but that is the default value, so omit it anyway value = QString(); + } else if (((ia2Name == "text-line-through-style"_L1 || ia2Name == "text-line-through-type"_L1) && (ia2Value != "none"_L1)) + || (ia2Name == "text-line-through-text"_L1 && !ia2Value.isEmpty())) { + // if any of the above is set, set "strikethrough" to true, but don't explicitly set + // to false otherwise, since any of the others might still be set to indicate strikethrough + // and no strikethrough is assumed anyway when nothing is explicitly set + name = QStringLiteral("strikethrough"); + value = QStringLiteral("true"); } else if (ia2Name == "text-position"_L1) { name = QStringLiteral("vertical-align"); if (value != "baseline"_L1 && value != "super"_L1 && value != "sub"_L1) { @@ -2160,6 +2286,38 @@ namespace } } +QSpiAttributeSet AtSpiAdaptor::getAttributes(QAccessibleInterface *interface) const +{ + QSpiAttributeSet set; + QAccessibleAttributesInterface *attributesIface = interface->attributesInterface(); + if (!attributesIface) + return set; + + const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys(); + for (QAccessible::Attribute key : attrKeys) { + const QVariant value = attributesIface->attributeValue(key); + // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/ + switch (key) { + case QAccessible::Attribute::Custom: + { + // forward custom attributes to AT-SPI as-is + Q_ASSERT((value.canConvert<QHash<QString, QString>>())); + const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>(); + for (auto [name, val] : attrMap.asKeyValueRange()) + set.insert(name, val); + break; + } + case QAccessible::Attribute::Level: + Q_ASSERT(value.canConvert<int>()); + set.insert(QStringLiteral("level"), QString::number(value.toInt())); + break; + default: + break; + } + } + return set; +} + // FIXME all attribute methods below should share code QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int offset, bool includeDefaults) const { @@ -2172,11 +2330,13 @@ QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int of QString joined = interface->textInterface()->attributes(offset, &startOffset, &endOffset); const QStringList attributes = joined.split(u';', Qt::SkipEmptyParts, Qt::CaseSensitive); for (const QString &attr : attributes) { - QStringList items; - items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive); - AtSpiAttribute attribute = atspiTextAttribute(items[0], items[1]); - if (!attribute.isNull()) - set[attribute.name] = attribute.value; + QStringList items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive); + if (items.count() == 2) + { + AtSpiAttribute attribute = atspiTextAttribute(items[0], items[1]); + if (!attribute.isNull()) + set[attribute.name] = attribute.value; + } } QVariantList list; @@ -2234,7 +2394,7 @@ bool AtSpiAdaptor::isValidCoordType(uint coordType) if (coordType == ATSPI_COORD_TYPE_SCREEN || coordType == ATSPI_COORD_TYPE_WINDOW || coordType == ATSPI_COORD_TYPE_PARENT) return true; - qCWarning(lcAccessibilityAtspi) << "unknown value" << coordType << "for AT-SPI coord type"; + qCWarning(lcAccessibilityAtspi) << "Unknown value" << coordType << "for AT-SPI coord type"; return false; } @@ -2358,7 +2518,7 @@ bool AtSpiAdaptor::editableTextInterface(QAccessibleInterface *interface, const } else if (function.isEmpty()) { connection.send(message.createReply()); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::editableTextInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::editableTextInterface does not implement" << function << message.path(); return false; } return true; @@ -2389,11 +2549,11 @@ bool AtSpiAdaptor::valueInterface(QAccessibleInterface *interface, const QString else if (function == "GetMinimumValue"_L1) value = valueIface->minimumValue(); else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::valueInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::valueInterface does not implement" << function << message.path(); return false; } if (!value.canConvert<double>()) { - qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::valueInterface: Could not convert to double: " << function; + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::valueInterface: Could not convert to double:" << function; } // explicitly convert to dbus-variant containing one double since atspi expects that @@ -2404,11 +2564,68 @@ bool AtSpiAdaptor::valueInterface(QAccessibleInterface *interface, const QString return true; } +// Selection interface +bool AtSpiAdaptor::selectionInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + QAccessibleSelectionInterface* selectionInterface = interface->selectionInterface(); + if (!selectionInterface) { + qCWarning(lcAccessibilityAtspi) << "Could not find selection interface for: " << message.path() << interface; + return false; + } + + if (function == "ClearSelection"_L1 ) { + connection.send(message.createReply(QVariant::fromValue((selectionInterface->clear())))); + } else if (function == "DeselectChild"_L1 ) { + int childIndex = message.arguments().at(0).toInt(); + bool ret = false; + QAccessibleInterface *child = interface->child(childIndex); + if (child) + ret = selectionInterface->unselect(child); + connection.send(message.createReply(QVariant::fromValue(ret))); + } else if (function == "DeselectSelectedChild"_L1 ) { + int selectionIndex = message.arguments().at(0).toInt(); + bool ret = false; + QAccessibleInterface *selectedChild = selectionInterface->selectedItem(selectionIndex); + if (selectedChild) + ret = selectionInterface->unselect(selectedChild); + connection.send(message.createReply(QVariant::fromValue(ret))); + } else if (function == "GetNSelectedChildren"_L1) { + connection.send(message.createReply(QVariant::fromValue(QDBusVariant( + QVariant::fromValue(selectionInterface->selectedItemCount()))))); + } else if (function == "GetSelectedChild"_L1) { + int selectionIndex = message.arguments().at(0).toInt(); + QSpiObjectReference ref(connection, QDBusObjectPath(pathForInterface(selectionInterface->selectedItem(selectionIndex)))); + connection.send(message.createReply(QVariant::fromValue(ref))); + } else if (function == "IsChildSelected"_L1 ) { + int childIndex = message.arguments().at(0).toInt(); + bool ret = false; + QAccessibleInterface *child = interface->child(childIndex); + if (child) + ret = selectionInterface->isSelected(child); + connection.send(message.createReply(QVariant::fromValue(ret))); + } else if (function == "SelectAll"_L1 ) { + connection.send(message.createReply(QVariant::fromValue(selectionInterface->selectAll()))); + } else if (function == "SelectChild"_L1 ) { + int childIndex = message.arguments().at(0).toInt(); + bool ret = false; + QAccessibleInterface *child = interface->child(childIndex); + if (child) + ret = selectionInterface->select(child); + connection.send(message.createReply(QVariant::fromValue(ret))); + } else { + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::selectionInterface does not implement " << function << message.path(); + return false; + } + + return true; +} + + // Table interface bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) { if (!(interface->tableInterface() || interface->tableCellInterface())) { - qCDebug(lcAccessibilityAtspi) << "WARNING Qt AtSpiAdaptor: Could not find table interface for: " << message.path() << interface; + qCWarning(lcAccessibilityAtspi) << "Qt AtSpiAdaptor: Could not find table interface for:" << message.path() << interface; return false; } @@ -2444,7 +2661,7 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString (column < 0) || (row >= interface->tableInterface()->rowCount()) || (column >= interface->tableInterface()->columnCount())) { - qCDebug(lcAccessibilityAtspi) << "WARNING: invalid index for tableInterface GetAccessibleAt (" << row << ", " << column << ')'; + qCWarning(lcAccessibilityAtspi) << "Invalid index for tableInterface GetAccessibleAt (" << row << "," << column << ')'; return false; } @@ -2453,7 +2670,7 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString if (cell) { ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(cell))); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: no cell interface returned for " << interface->object() << row << column; + qCWarning(lcAccessibilityAtspi) << "No cell interface returned for" << interface->object() << row << column; ref = QSpiObjectReference(); } connection.send(message.createReply(QVariant::fromValue(ref))); @@ -2463,7 +2680,7 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString int column = message.arguments().at(1).toInt(); QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column); if (!cell) { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::GetIndexAt(" << row << ',' << column << ") did not find a cell. " << interface; + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::GetIndexAt(" << row << ',' << column << ") did not find a cell." << interface; return false; } int index = interface->indexOfChild(cell); @@ -2483,7 +2700,7 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString ret = -1; } else { if (!cell->tableCellInterface()) { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::" << function << " No table cell interface: " << cell; + qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No table cell interface: " << cell; return false; } ret = cell->tableCellInterface()->columnIndex(); @@ -2495,14 +2712,14 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString ret = index % interface->tableInterface()->columnCount(); } else { if (!cell->tableCellInterface()) { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::" << function << " No table cell interface: " << cell; + qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No table cell interface: " << cell; return false; } ret = cell->tableCellInterface()->rowIndex(); } } } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::" << function << " No cell at index: " << index << interface; + qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No cell at index: " << index << " " << interface; return false; } } @@ -2531,14 +2748,15 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString if (cols > 0) { row = index / cols; col = index % cols; - QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, col)->tableCellInterface(); - if (cell) { - row = cell->rowIndex(); - col = cell->columnIndex(); - rowExtents = cell->rowExtent(); - colExtents = cell->columnExtent(); - isSelected = cell->isSelected(); - success = true; + if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, col)) { + if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface()) { + row = cellIface->rowIndex(); + col = cellIface->columnIndex(); + rowExtents = cellIface->rowExtent(); + colExtents = cellIface->columnExtent(); + isSelected = cellIface->isSelected(); + success = true; + } } } QVariantList list; @@ -2548,12 +2766,22 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString } else if (function == "GetColumnExtentAt"_L1) { int row = message.arguments().at(0).toInt(); int column = message.arguments().at(1).toInt(); - connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->columnExtent())); + int columnExtent = 0; + if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column)) { + if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface()) + columnExtent = cellIface->columnExtent(); + } + connection.send(message.createReply(columnExtent)); } else if (function == "GetRowExtentAt"_L1) { int row = message.arguments().at(0).toInt(); int column = message.arguments().at(1).toInt(); - connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->rowExtent())); + int rowExtent = 0; + if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column)) { + if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface()) + rowExtent = cellIface->rowExtent(); + } + connection.send(message.createReply(rowExtent)); } else if (function == "GetColumnHeader"_L1) { int column = message.arguments().at(0).toInt(); @@ -2593,8 +2821,12 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString } else if (function == "IsSelected"_L1) { int row = message.arguments().at(0).toInt(); int column = message.arguments().at(1).toInt(); - QAccessibleTableCellInterface* cell = interface->tableInterface()->cellAt(row, column)->tableCellInterface(); - connection.send(message.createReply(cell->isSelected())); + bool selected = false; + if (QAccessibleInterface* cell = interface->tableInterface()->cellAt(row, column)) { + if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface()) + selected = cellIface->isSelected(); + } + connection.send(message.createReply(selected)); } else if (function == "AddColumnSelection"_L1) { int column = message.arguments().at(0).toInt(); connection.send(message.createReply(interface->tableInterface()->selectColumn(column))); @@ -2608,7 +2840,7 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString int row = message.arguments().at(0).toInt(); connection.send(message.createReply(interface->tableInterface()->unselectRow(row))); } else { - qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::tableInterface does not implement " << function << message.path(); + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::tableInterface does not implement" << function << message.path(); return false; } return true; @@ -2623,7 +2855,17 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt return false; } - if (function == "GetColumnSpan"_L1) { + if (function == "GetColumnHeaderCells"_L1) { + QSpiObjectReferenceArray headerCells; + const auto headerCellInterfaces = cellInterface->columnHeaderCells(); + headerCells.reserve(headerCellInterfaces.size()); + for (QAccessibleInterface *cell : headerCellInterfaces) { + const QString childPath = pathForInterface(cell); + const QSpiObjectReference ref(connection, QDBusObjectPath(childPath)); + headerCells << ref; + } + connection.send(message.createReply(QVariant::fromValue(headerCells))); + } else if (function == "GetColumnSpan"_L1) { connection.send(message.createReply(QVariant::fromValue(QDBusVariant( QVariant::fromValue(cellInterface->columnExtent()))))); } else if (function == "GetPosition"_L1) { @@ -2631,6 +2873,16 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt const int column = cellInterface->columnIndex(); connection.send(message.createReply(QVariant::fromValue(QDBusVariant( QVariant::fromValue(QPoint(row, column)))))); + } else if (function == "GetRowHeaderCells"_L1) { + QSpiObjectReferenceArray headerCells; + const auto headerCellInterfaces = cellInterface->rowHeaderCells(); + headerCells.reserve(headerCellInterfaces.size()); + for (QAccessibleInterface *cell : headerCellInterfaces) { + const QString childPath = pathForInterface(cell); + const QSpiObjectReference ref(connection, QDBusObjectPath(childPath)); + headerCells << ref; + } + connection.send(message.createReply(QVariant::fromValue(headerCells))); } else if (function == "GetRowSpan"_L1) { connection.send(message.createReply(QVariant::fromValue(QDBusVariant( QVariant::fromValue(cellInterface->rowExtent()))))); @@ -2644,6 +2896,9 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt if (table && table->tableInterface()) ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(table))); connection.send(message.createReply(QVariant::fromValue(QDBusVariant(QVariant::fromValue(ref))))); + } else { + qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::tableCellInterface does not implement" << function << message.path(); + return false; } return true; diff --git a/src/gui/accessible/linux/atspiadaptor_p.h b/src/gui/accessible/linux/atspiadaptor_p.h index 3e39f58fbd..fe7f6c477c 100644 --- a/src/gui/accessible/linux/atspiadaptor_p.h +++ b/src/gui/accessible/linux/atspiadaptor_p.h @@ -16,10 +16,9 @@ // We mean it. // -#include <atspi/atspi-constants.h> +#include <atspi/atspi.h> #include <QtGui/private/qtguiglobal_p.h> -#include <QtCore/qsharedpointer.h> #include <QtDBus/qdbusvirtualobject.h> #include <QtGui/qaccessible.h> @@ -39,7 +38,7 @@ class AtSpiAdaptor :public QDBusVirtualObject Q_OBJECT public: - explicit AtSpiAdaptor(DBusConnection *connection, QObject *parent = nullptr); + explicit AtSpiAdaptor(QAtSpiDBusConnection *connection, QObject *parent = nullptr); ~AtSpiAdaptor(); void registerApplication(); @@ -74,6 +73,7 @@ private: bool textInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); bool editableTextInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); bool valueInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool selectionInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); bool tableInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); bool tableCellInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); @@ -85,8 +85,11 @@ private: void notifyStateChange(QAccessibleInterface *interface, const QString& state, int value); + void sendAnnouncement(QAccessibleAnnouncementEvent *event); + // accessible helper functions AtspiRole getRole(QAccessibleInterface *interface) const; + QSpiAttributeSet getAttributes(QAccessibleInterface *) const; QSpiRelationArray relationSet(QAccessibleInterface *interface, const QDBusConnection &connection) const; QStringList accessibleInterfaces(QAccessibleInterface *interface) const; @@ -111,7 +114,7 @@ private: // private vars QSpiObjectReference accessibilityRegistry; - DBusConnection *m_dbus; + QAtSpiDBusConnection *m_dbus; QSpiApplicationAdaptor *m_applicationAdaptor; /// Assigned from the accessibility registry. @@ -130,6 +133,7 @@ private: // all of object uint sendObject : 1; uint sendObject_active_descendant_changed : 1; + uint sendObject_announcement : 1; uint sendObject_attributes_changed : 1; uint sendObject_bounds_changed : 1; uint sendObject_children_changed : 1; diff --git a/src/gui/accessible/linux/dbusconnection.cpp b/src/gui/accessible/linux/dbusconnection.cpp index 9105768fcf..620575cc98 100644 --- a/src/gui/accessible/linux/dbusconnection.cpp +++ b/src/gui/accessible/linux/dbusconnection.cpp @@ -26,27 +26,20 @@ using namespace Qt::StringLiterals; #define A11Y_PATH "/org/a11y/bus"_L1 /*! - \class DBusConnection + \class QAtSpiDBusConnection \internal \brief Connects to the accessibility dbus. This is usually a different bus from the session bus. */ -DBusConnection::DBusConnection(QObject *parent) +QAtSpiDBusConnection::QAtSpiDBusConnection(QObject *parent) : QObject(parent), m_a11yConnection(QString()), m_enabled(false) { // If the bus is explicitly set via env var it overrides everything else. QByteArray addressEnv = qgetenv("AT_SPI_BUS_ADDRESS"); if (!addressEnv.isEmpty()) { - // Only connect on next loop run, connections to our enabled signal are - // only established after the ctor returns. - QMetaObject::invokeMethod( - this, - [this, addressEnv] { - m_enabled = true; - connectA11yBus(QString::fromLocal8Bit(addressEnv)); - }, - Qt::QueuedConnection); + m_enabled = true; + connectA11yBus(QString::fromLocal8Bit(addressEnv)); return; } @@ -63,15 +56,17 @@ DBusConnection::DBusConnection(QObject *parent) if (c.interface()->isServiceRegistered(A11Y_SERVICE)) serviceRegistered(); - // In addition try if there is an xatom exposing the bus address, this allows applications run as root to work - QString address = getAddressFromXCB(); - if (!address.isEmpty()) { - m_enabled = true; - connectA11yBus(address); + if (QGuiApplication::platformName().startsWith("xcb"_L1)) { + // In addition try if there is an xatom exposing the bus address, this allows applications run as root to work + QString address = getAddressFromXCB(); + if (!address.isEmpty()) { + m_enabled = true; + connectA11yBus(address); + } } } -QString DBusConnection::getAddressFromXCB() +QString QAtSpiDBusConnection::getAddressFromXCB() { QGuiApplication *app = qobject_cast<QGuiApplication *>(QCoreApplication::instance()); if (!app) @@ -90,7 +85,7 @@ QString DBusConnection::getAddressFromXCB() // We have the a11y registry on the session bus. // Subscribe to updates about a11y enabled state. // Find out the bus address -void DBusConnection::serviceRegistered() +void QAtSpiDBusConnection::serviceRegistered() { // listen to enabled changes QDBusConnection c = QDBusConnection::sessionBus(); @@ -118,12 +113,12 @@ void DBusConnection::serviceRegistered() // connect(a11yStatus, ); QtDbus doesn't support notifications for property changes yet } -void DBusConnection::serviceUnregistered() +void QAtSpiDBusConnection::serviceUnregistered() { emit enabledChanged(false); } -void DBusConnection::connectA11yBus(const QString &address) +void QAtSpiDBusConnection::connectA11yBus(const QString &address) { if (address.isEmpty()) { qWarning("Could not find Accessibility DBus address."); @@ -135,7 +130,7 @@ void DBusConnection::connectA11yBus(const QString &address) emit enabledChanged(true); } -void DBusConnection::dbusError(const QDBusError &error) +void QAtSpiDBusConnection::dbusError(const QDBusError &error) { qWarning() << "Accessibility encountered a DBus error:" << error; } @@ -144,7 +139,7 @@ void DBusConnection::dbusError(const QDBusError &error) Returns the DBus connection that got established. Or an invalid connection if not yet connected. */ -QDBusConnection DBusConnection::connection() const +QDBusConnection QAtSpiDBusConnection::connection() const { return m_a11yConnection; } diff --git a/src/gui/accessible/linux/dbusconnection_p.h b/src/gui/accessible/linux/dbusconnection_p.h index 8b231c5ad0..98bd02741f 100644 --- a/src/gui/accessible/linux/dbusconnection_p.h +++ b/src/gui/accessible/linux/dbusconnection_p.h @@ -27,12 +27,12 @@ QT_BEGIN_NAMESPACE class QDBusServiceWatcher; -class DBusConnection : public QObject +class QAtSpiDBusConnection : public QObject { Q_OBJECT public: - DBusConnection(QObject *parent = nullptr); + QAtSpiDBusConnection(QObject *parent = nullptr); QDBusConnection connection() const; bool isEnabled() const { return m_enabled; } diff --git a/src/gui/accessible/linux/dbusxml/Socket.xml b/src/gui/accessible/linux/dbusxml/Socket.xml index 75ec99f994..f9ac76d2c8 100644 --- a/src/gui/accessible/linux/dbusxml/Socket.xml +++ b/src/gui/accessible/linux/dbusxml/Socket.xml @@ -17,7 +17,7 @@ <signal name="Available"> <arg direction="in" name="socket" type="(so)"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> - </method> + </signal> </interface> </node> diff --git a/src/gui/accessible/linux/qspi_constant_mappings.cpp b/src/gui/accessible/linux/qspi_constant_mappings.cpp index b3e8816df5..e5b6e3f634 100644 --- a/src/gui/accessible/linux/qspi_constant_mappings.cpp +++ b/src/gui/accessible/linux/qspi_constant_mappings.cpp @@ -36,6 +36,8 @@ quint64 spiStatesFromQState(QAccessible::State state) setSpiStateBit(&spiState, ATSPI_STATE_FOCUSED); if (state.pressed) setSpiStateBit(&spiState, ATSPI_STATE_PRESSED); + if (state.checkable) + setSpiStateBit(&spiState, ATSPI_STATE_CHECKABLE); if (state.checked) setSpiStateBit(&spiState, ATSPI_STATE_CHECKED); if (state.checkStateMixed) @@ -75,7 +77,8 @@ quint64 spiStatesFromQState(QAccessible::State state) if (state.extSelectable) setSpiStateBit(&spiState, ATSPI_STATE_SELECTABLE); // if (state.Protected) - // if (state.HasPopup) + if (state.hasPopup) + setSpiStateBit(&spiState, ATSPI_STATE_HAS_POPUP); if (state.modal) setSpiStateBit(&spiState, ATSPI_STATE_MODAL); if (state.multiLine) @@ -97,6 +100,7 @@ QSpiUIntList spiStateSetFromSpiStates(quint64 states) AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relation) { + // direction of the relation is "inversed" in Qt and AT-SPI switch (relation) { case QAccessible::Label: return ATSPI_RELATION_LABELLED_BY; @@ -106,6 +110,14 @@ AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relat return ATSPI_RELATION_CONTROLLED_BY; case QAccessible::Controlled: return ATSPI_RELATION_CONTROLLER_FOR; + case QAccessible::DescriptionFor: + return ATSPI_RELATION_DESCRIBED_BY; + case QAccessible::Described: + return ATSPI_RELATION_DESCRIPTION_FOR; + case QAccessible::FlowsFrom: + return ATSPI_RELATION_FLOWS_TO; + case QAccessible::FlowsTo: + return ATSPI_RELATION_FLOWS_FROM; default: qWarning() << "Cannot return AT-SPI relation for:" << relation; } diff --git a/src/gui/accessible/linux/qspiaccessiblebridge.cpp b/src/gui/accessible/linux/qspiaccessiblebridge.cpp index 8961055f1b..0d18589ac5 100644 --- a/src/gui/accessible/linux/qspiaccessiblebridge.cpp +++ b/src/gui/accessible/linux/qspiaccessiblebridge.cpp @@ -31,8 +31,16 @@ using namespace Qt::StringLiterals; QSpiAccessibleBridge::QSpiAccessibleBridge() : cache(nullptr), dec(nullptr), dbusAdaptor(nullptr) { - dbusConnection = new DBusConnection(); + dbusConnection = new QAtSpiDBusConnection(); connect(dbusConnection, SIGNAL(enabledChanged(bool)), this, SLOT(enabledChanged(bool))); + // Now that we have connected the signal, make sure we didn't miss a change, + // e.g. when running as root or when AT_SPI_BUS_ADDRESS is set by hand. + // But do that only on next loop, once dbus is really settled. + QTimer::singleShot( + 0, this, [this]{ + if (dbusConnection->isEnabled() && dbusConnection->connection().isConnected()) + enabledChanged(true); + }); } void QSpiAccessibleBridge::enabledChanged(bool enabled) @@ -197,7 +205,11 @@ static RoleMapping map[] = { //: Role of an accessible object { QAccessible::ButtonDropDown, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down") }, //: Role of an accessible object +#if ATSPI_ROLE_COUNT > 130 + { QAccessible::ButtonMenu, ATSPI_ROLE_PUSH_BUTTON_MENU, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button menu") }, +#else { QAccessible::ButtonMenu, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button menu") }, +#endif //: Role of an accessible object - a button that expands a grid. { QAccessible::ButtonDropGrid, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down grid") }, //: Role of an accessible object - blank space between other objects. diff --git a/src/gui/accessible/linux/qspiaccessiblebridge_p.h b/src/gui/accessible/linux/qspiaccessiblebridge_p.h index 5ad48c5cc4..69c4feb7bf 100644 --- a/src/gui/accessible/linux/qspiaccessiblebridge_p.h +++ b/src/gui/accessible/linux/qspiaccessiblebridge_p.h @@ -27,7 +27,7 @@ QT_REQUIRE_CONFIG(accessibility); QT_BEGIN_NAMESPACE -class DBusConnection; +class QAtSpiDBusConnection; class QSpiDBusCache; class AtSpiAdaptor; struct RoleNames; @@ -60,7 +60,7 @@ private: QSpiDBusCache *cache; DeviceEventControllerAdaptor *dec; AtSpiAdaptor *dbusAdaptor; - DBusConnection* dbusConnection; + QAtSpiDBusConnection* dbusConnection; SpiRoleMapping m_spiRoleMapping; }; diff --git a/src/gui/accessible/linux/qspiapplicationadaptor.cpp b/src/gui/accessible/linux/qspiapplicationadaptor.cpp index 963490d056..37d7648984 100644 --- a/src/gui/accessible/linux/qspiapplicationadaptor.cpp +++ b/src/gui/accessible/linux/qspiapplicationadaptor.cpp @@ -12,12 +12,16 @@ #include "deviceeventcontroller_adaptor.h" #include "atspi/atspi-constants.h" +#if __has_include(<xcb/xproto.h>) #include <xcb/xproto.h> +#endif //#define KEYBOARD_DEBUG QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + /*! \class QSpiApplicationAdaptor \internal @@ -125,9 +129,13 @@ bool QSpiApplicationAdaptor::eventFilter(QObject *target, QEvent *event) de.modifiers = 0; if ((keyEvent->modifiers() & Qt::ShiftModifier) && (keyEvent->key() != Qt::Key_Shift)) de.modifiers |= 1 << ATSPI_MODIFIER_SHIFT; - // TODO rather introduce Qt::CapslockModifier into KeyboardModifier - if (keyEvent->nativeModifiers() & XCB_MOD_MASK_LOCK ) - de.modifiers |= 1 << ATSPI_MODIFIER_SHIFTLOCK; +#ifdef XCB_MOD_MASK_LOCK + if (QGuiApplication::platformName().startsWith("xcb"_L1)) { + // TODO rather introduce Qt::CapslockModifier into KeyboardModifier + if (keyEvent->nativeModifiers() & XCB_MOD_MASK_LOCK ) + de.modifiers |= 1 << ATSPI_MODIFIER_SHIFTLOCK; + } +#endif if ((keyEvent->modifiers() & Qt::ControlModifier) && (keyEvent->key() != Qt::Key_Control)) de.modifiers |= 1 << ATSPI_MODIFIER_CONTROL; if ((keyEvent->modifiers() & Qt::AltModifier) && (keyEvent->key() != Qt::Key_Alt)) |