From 8e1ff45e74f529c7f49688242ea0fd25ce2913f3 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Thu, 10 May 2012 23:52:05 +0200 Subject: Add Linux Accessibility Bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a plugin that bridges the QAccessible world to AT-SPI 2 on Linux. Change-Id: I7af22621ee6a3cefc723b137b7f227a611cf6641 Reviewed-by: Jan-Arve Sæther --- .../linuxaccessibility/application.cpp | 219 ++ .../linuxaccessibility/application_p.h | 86 + .../linuxaccessibility/atspiadaptor.cpp | 2316 ++++++++++++++++++++ .../linuxaccessibility/atspiadaptor_p.h | 220 ++ src/platformsupport/linuxaccessibility/bridge.cpp | 192 ++ src/platformsupport/linuxaccessibility/bridge_p.h | 85 + src/platformsupport/linuxaccessibility/cache.cpp | 92 + src/platformsupport/linuxaccessibility/cache_p.h | 72 + .../linuxaccessibility/constant_mappings.cpp | 159 ++ .../linuxaccessibility/constant_mappings_p.h | 138 ++ .../linuxaccessibility/dbusconnection.cpp | 116 + .../linuxaccessibility/dbusconnection_p.h | 68 + .../linuxaccessibility/linuxaccessibility.pri | 27 + src/platformsupport/linuxaccessibility/main.cpp | 94 + .../linuxaccessibility/struct_marshallers.cpp | 234 ++ .../linuxaccessibility/struct_marshallers_p.h | 177 ++ 16 files changed, 4295 insertions(+) create mode 100644 src/platformsupport/linuxaccessibility/application.cpp create mode 100644 src/platformsupport/linuxaccessibility/application_p.h create mode 100644 src/platformsupport/linuxaccessibility/atspiadaptor.cpp create mode 100644 src/platformsupport/linuxaccessibility/atspiadaptor_p.h create mode 100644 src/platformsupport/linuxaccessibility/bridge.cpp create mode 100644 src/platformsupport/linuxaccessibility/bridge_p.h create mode 100644 src/platformsupport/linuxaccessibility/cache.cpp create mode 100644 src/platformsupport/linuxaccessibility/cache_p.h create mode 100644 src/platformsupport/linuxaccessibility/constant_mappings.cpp create mode 100644 src/platformsupport/linuxaccessibility/constant_mappings_p.h create mode 100644 src/platformsupport/linuxaccessibility/dbusconnection.cpp create mode 100644 src/platformsupport/linuxaccessibility/dbusconnection_p.h create mode 100644 src/platformsupport/linuxaccessibility/linuxaccessibility.pri create mode 100644 src/platformsupport/linuxaccessibility/main.cpp create mode 100644 src/platformsupport/linuxaccessibility/struct_marshallers.cpp create mode 100644 src/platformsupport/linuxaccessibility/struct_marshallers_p.h (limited to 'src/platformsupport/linuxaccessibility') diff --git a/src/platformsupport/linuxaccessibility/application.cpp b/src/platformsupport/linuxaccessibility/application.cpp new file mode 100644 index 0000000000..84f5cef625 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/application.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "application_p.h" + +#include +#include +#include + +#include "deviceeventcontroller_adaptor.h" + +//#define KEYBOARD_DEBUG + +QT_BEGIN_NAMESPACE + +/*! + \class QSpiApplicationAdaptor + + \brief QSpiApplicationAdaptor + + QSpiApplicationAdaptor +*/ + +QSpiApplicationAdaptor::QSpiApplicationAdaptor(const QDBusConnection &connection, QObject *parent) + : QObject(parent), dbusConnection(connection) +{ +} + +enum QSpiKeyEventType { + QSPI_KEY_EVENT_PRESS, + QSPI_KEY_EVENT_RELEASE, + QSPI_KEY_EVENT_LAST_DEFINED +}; + +void QSpiApplicationAdaptor::sendEvents(bool active) +{ + if (active) { + qApp->installEventFilter(this); + } else { + qApp->removeEventFilter(this); + } +} + + +bool QSpiApplicationAdaptor::eventFilter(QObject *target, QEvent *event) +{ + if (!event->spontaneous()) + return false; + + switch (event->type()) { + case QEvent::WindowActivate: + emit windowActivated(target, true); + break; + case QEvent::WindowDeactivate: + emit windowActivated(target, false); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: { + QKeyEvent *keyEvent = static_cast (event); + QSpiDeviceEvent de; + + if (event->type() == QEvent::KeyPress) + de.type = QSPI_KEY_EVENT_PRESS; + else + de.type = QSPI_KEY_EVENT_RELEASE; + + de.id = keyEvent->nativeVirtualKey(); + de.hardwareCode = keyEvent->nativeScanCode(); + + de.modifiers = keyEvent->nativeModifiers(); + de.timestamp = QDateTime::currentMSecsSinceEpoch(); + + if (keyEvent->key() == Qt::Key_Tab) + de.text = QStringLiteral("Tab"); + else if (keyEvent->key() == Qt::Key_Backtab) + de.text = QStringLiteral("Backtab"); + else if (keyEvent->key() == Qt::Key_Left) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Left") : QStringLiteral("Left"); + else if (keyEvent->key() == Qt::Key_Right) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Right") : QStringLiteral("Right"); + else if (keyEvent->key() == Qt::Key_Up) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Up") : QStringLiteral("Up"); + else if (keyEvent->key() == Qt::Key_Down) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Down") : QStringLiteral("Down"); + else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) + de.text = QStringLiteral("Return"); + else if (keyEvent->key() == Qt::Key_Backspace) + de.text = QStringLiteral("BackSpace"); + else if (keyEvent->key() == Qt::Key_Delete) + de.text = QStringLiteral("Delete"); + else if (keyEvent->key() == Qt::Key_PageUp) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Page_Up") : QStringLiteral("Page_Up"); + else if (keyEvent->key() == Qt::Key_PageDown) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Page_Up") : QStringLiteral("Page_Down"); + else if (keyEvent->key() == Qt::Key_Home) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_Home") : QStringLiteral("Home"); + else if (keyEvent->key() == Qt::Key_End) + de.text = (keyEvent->modifiers() & Qt::KeypadModifier) ? QStringLiteral("KP_End") : QStringLiteral("End"); + else if (keyEvent->key() == Qt::Key_Clear && (keyEvent->modifiers() & Qt::KeypadModifier)) + de.text = QStringLiteral("KP_Begin"); // Key pad 5 + else if (keyEvent->key() == Qt::Key_Escape) + de.text = QStringLiteral("Escape"); + else if (keyEvent->key() == Qt::Key_Space) + de.text = QStringLiteral("space"); + else if (keyEvent->key() == Qt::Key_CapsLock) + de.text = QStringLiteral("Caps_Lock"); + else if (keyEvent->key() == Qt::Key_NumLock) + de.text = QStringLiteral("Num_Lock"); + else if (keyEvent->key() == Qt::Key_Insert) + de.text = QStringLiteral("Insert"); + else + de.text = keyEvent->text(); + + // This is a bit dubious, Gnome uses some gtk function here. + // Long term the spec will hopefully change to just use keycodes. + de.isText = !de.text.isEmpty(); + +#ifdef KEYBOARD_DEBUG + qDebug() << QStringLiteral("Key event text: ") << event->type() << de.isText << QStringLiteral(" ") << de.text + << QStringLiteral(" hardware code: ") << de.hardwareCode + << QStringLiteral(" native sc: ") << keyEvent->nativeScanCode() + << QStringLiteral(" native mod: ") << keyEvent->nativeModifiers() + << QStringLiteral("native virt: ") << keyEvent->nativeVirtualKey(); +#endif + + QDBusMessage m = QDBusMessage::createMethodCall(QStringLiteral("org.a11y.atspi.Registry"), + QStringLiteral("/org/a11y/atspi/registry/deviceeventcontroller"), + QStringLiteral("org.a11y.atspi.DeviceEventController"), QStringLiteral("NotifyListenersSync")); + m.setArguments(QVariantList() < (target, copyKeyEvent(keyEvent))); +#ifdef KEYBOARD_DEBUG + qDebug() << QStringLiteral("Sent key: ") << de.text; +#endif + return true; + } + } + default: + break; + } + return false; +} + +QKeyEvent* QSpiApplicationAdaptor::copyKeyEvent(QKeyEvent* old) +{ + return new QKeyEvent(old->type(), old->key(), old->modifiers(), old->text(), old->isAutoRepeat(), old->count()); +} + +void QSpiApplicationAdaptor::notifyKeyboardListenerCallback(const QDBusMessage& message) +{ + if (!keyEvents.length()) { + qWarning() << QStringLiteral("QSpiApplication::notifyKeyboardListenerCallback with no queued key called"); + return; + } + Q_ASSERT(message.arguments().length() == 1); + if (message.arguments().at(0).toBool() == true) { + QPair event = keyEvents.dequeue(); + delete event.second; + } else { + QPair event = keyEvents.dequeue(); + QApplication::postEvent(event.first, event.second); + } +} + +void QSpiApplicationAdaptor::notifyKeyboardListenerError(const QDBusError& error, const QDBusMessage& /*message*/) +{ + qWarning() << QStringLiteral("QSpiApplication::keyEventError ") << error.name() << error.message(); + while (!keyEvents.isEmpty()) { + QPair event = keyEvents.dequeue(); + QApplication::postEvent(event.first, event.second); + } +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/application_p.h b/src/platformsupport/linuxaccessibility/application_p.h new file mode 100644 index 0000000000..01ebc16ca7 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/application_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q_SPI_APPLICATION_H +#define Q_SPI_APPLICATION_H + +#include +#include +#include + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +/* + * Used for the root object. + * + * Uses the root object reference and reports its parent as the desktop object. + */ +class QSpiApplicationAdaptor :public QObject +{ + Q_OBJECT + +public: + QSpiApplicationAdaptor(const QDBusConnection &connection, QObject *parent); + virtual ~QSpiApplicationAdaptor() {} + void sendEvents(bool active); + +Q_SIGNALS: + void windowActivated(QObject* window, bool active); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private Q_SLOTS: + void notifyKeyboardListenerCallback(const QDBusMessage& message); + void notifyKeyboardListenerError(const QDBusError& error, const QDBusMessage& message); + +private: + static QKeyEvent* copyKeyEvent(QKeyEvent*); + + QQueue > keyEvents; + QDBusConnection dbusConnection; +}; + +QT_END_NAMESPACE +QT_END_HEADER + +#endif diff --git a/src/platformsupport/linuxaccessibility/atspiadaptor.cpp b/src/platformsupport/linuxaccessibility/atspiadaptor.cpp new file mode 100644 index 0000000000..f9aedb4d61 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/atspiadaptor.cpp @@ -0,0 +1,2316 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "atspiadaptor_p.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "socket_interface.h" +#include "constant_mappings_p.h" + +#include "application_p.h" +/*! + \class AtSpiAdaptor + + \brief AtSpiAdaptor is the main class to forward between QAccessibleInterface and AT-SPI DBus + + AtSpiAdaptor implements the functions specified in all at-spi interfaces. + It sends notifications comming from Qt via dbus and listens to incoming dbus requests. +*/ + +QT_BEGIN_NAMESPACE + +AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent) + : QDBusVirtualObject(parent), m_dbus(connection), initialized(false) + , sendFocus(0) + , sendObject(0) + , sendObject_active_descendant_changed(0) + , sendObject_attributes_changed(0) + , sendObject_bounds_changed(0) + , sendObject_children_changed(0) +// , sendObject_children_changed_add(0) +// , sendObject_children_changed_remove(0) + , sendObject_column_deleted(0) + , sendObject_column_inserted(0) + , sendObject_column_reordered(0) + , sendObject_link_selected(0) + , sendObject_model_changed(0) + , sendObject_property_change(0) + , sendObject_property_change_accessible_description(0) + , sendObject_property_change_accessible_name(0) + , sendObject_property_change_accessible_parent(0) + , sendObject_property_change_accessible_role(0) + , sendObject_property_change_accessible_table_caption(0) + , sendObject_property_change_accessible_table_column_description(0) + , sendObject_property_change_accessible_table_column_header(0) + , sendObject_property_change_accessible_table_row_description(0) + , sendObject_property_change_accessible_table_row_header(0) + , sendObject_property_change_accessible_table_summary(0) + , sendObject_property_change_accessible_value(0) + , sendObject_row_deleted(0) + , sendObject_row_inserted(0) + , sendObject_row_reordered(0) + , sendObject_selection_changed(0) + , sendObject_text_attributes_changed(0) + , sendObject_text_bounds_changed(0) + , sendObject_text_caret_moved(0) + , sendObject_text_changed(0) +// , sendObject_text_changed_delete(0) +// , sendObject_text_changed_insert(0) + , sendObject_text_selection_changed(0) + , sendObject_value_changed(0) + , sendObject_visible_data_changed(0) + , sendWindow(0) + , sendWindow_activate(0) + , sendWindow_close(0) + , sendWindow_create(0) + , sendWindow_deactivate(0) +// , sendWindow_desktop_create(0) +// , sendWindow_desktop_destroy(0) + , sendWindow_lower(0) + , sendWindow_maximize(0) + , sendWindow_minimize(0) + , sendWindow_move(0) + , sendWindow_raise(0) + , sendWindow_reparent(0) + , sendWindow_resize(0) + , sendWindow_restore(0) + , sendWindow_restyle(0) + , sendWindow_shade(0) + , sendWindow_unshade(0) +{ + m_applicationAdaptor = new QSpiApplicationAdaptor(m_dbus->connection(), this); + connect(m_applicationAdaptor, SIGNAL(windowActivated(QObject*,bool)), this, SLOT(windowActivated(QObject*,bool))); +} + +AtSpiAdaptor::~AtSpiAdaptor() +{ +} + +/*! + Provide DBus introspection. + */ +QString AtSpiAdaptor::introspect(const QString &path) const +{ + static const QLatin1String accessibleIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String actionIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String applicationIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String componentIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String editableTextIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String tableIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String textIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + static const QLatin1String valueIntrospection( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + QAIPointer interface = interfaceFromPath(path); + if (!interface) { + qWarning() << "WARNING Qt AtSpiAdaptor: Could not find accessible on path: " << path; + return QString(); + } + + QStringList interfaces = accessibleInterfaces(interface); + + QString xml; + xml.append(accessibleIntrospection); + + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_COMPONENT))) + xml.append(componentIntrospection); + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_TEXT))) + xml.append(textIntrospection); + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_EDITABLE_TEXT))) + xml.append(editableTextIntrospection); + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_ACTION))) + xml.append(actionIntrospection); + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_TABLE))) + xml.append(tableIntrospection); + if (interfaces.contains(QLatin1String(ATSPI_DBUS_INTERFACE_VALUE))) + xml.append(valueIntrospection); + if (path == QLatin1String(QSPI_OBJECT_PATH_ROOT)) + xml.append(applicationIntrospection); + + return xml; +} + +/*! + When initialized we will send updates, not before this. + + This function also checks which event listeners are registered in the at-spi registry. + */ +void AtSpiAdaptor::setInitialized(bool init) +{ + initialized = init; + + if (!initialized) + return; + + updateEventListeners(); + bool success = m_dbus->connection().connect(QLatin1String("org.a11y.atspi.Registry"), QLatin1String("/org/a11y/atspi/registry"), + QLatin1String("org.a11y.atspi.Registry"), QLatin1String("EventListenerRegistered"), this, + SLOT(eventListenerRegistered(QString,QString))); + success = success && m_dbus->connection().connect(QLatin1String("org.a11y.atspi.Registry"), QLatin1String("/org/a11y/atspi/registry"), + QLatin1String("org.a11y.atspi.Registry"), QLatin1String("EventListenerDeregistered"), this, + SLOT(eventListenerDeregistered(QString,QString))); +#ifdef QT_ATSPI_DEBUG + qDebug() << "Registered event listener change listener: " << success; +#endif +} + +void AtSpiAdaptor::setBitFlag(const QString &flag) +{ + Q_ASSERT(flag.size()); + + // assume we don't get nonsense - look at first letter only + switch (flag.at(0).toLower().toLatin1()) { + case 'o': { + if (flag.size() <= 8) { // Object:: + sendObject = 1; + } else { // Object:Foo:Bar + QString right = flag.mid(7); + if (false) { + } else if (right.startsWith(QLatin1String("ActiveDescendantChanged"))) { + sendObject_active_descendant_changed = 1; + } else if (right.startsWith(QLatin1String("AttributesChanged"))) { + sendObject_attributes_changed = 1; + } else if (right.startsWith(QLatin1String("BoundsChanged"))) { + sendObject_bounds_changed = 1; + } else if (right.startsWith(QLatin1String("ChildrenChanged"))) { + sendObject_children_changed = 1; + } else if (right.startsWith(QLatin1String("ColumnDeleted"))) { + sendObject_column_deleted = 1; + } else if (right.startsWith(QLatin1String("ColumnInserted"))) { + sendObject_column_inserted = 1; + } else if (right.startsWith(QLatin1String("ColumnReordered"))) { + sendObject_column_reordered = 1; + } else if (right.startsWith(QLatin1String("LinkSelected"))) { + sendObject_link_selected = 1; + } else if (right.startsWith(QLatin1String("ModelChanged"))) { + sendObject_model_changed = 1; + } else if (right.startsWith(QLatin1String("PropertyChange"))) { + if (right == QLatin1String("PropertyChange:AccessibleDescription")) { + sendObject_property_change_accessible_description = 1; + } else if (right == QLatin1String("PropertyChange:AccessibleName")) { + sendObject_property_change_accessible_name = 1; + } else if (right == QLatin1String("PropertyChange:AccessibleParent")) { + sendObject_property_change_accessible_parent = 1; + } else if (right == QLatin1String("PropertyChange:AccessibleRole")) { + sendObject_property_change_accessible_role = 1; + } else if (right == QLatin1String("PropertyChange:TableCaption")) { + sendObject_property_change_accessible_table_caption = 1; + } else if (right == QLatin1String("PropertyChange:TableColumnDescription")) { + sendObject_property_change_accessible_table_column_description = 1; + } else if (right == QLatin1String("PropertyChange:TableColumnHeader")) { + sendObject_property_change_accessible_table_column_header = 1; + } else if (right == QLatin1String("PropertyChange:TableRowDescription")) { + sendObject_property_change_accessible_table_row_description = 1; + } else if (right == QLatin1String("PropertyChange:TableRowHeader")) { + sendObject_property_change_accessible_table_row_header = 1; + } else if (right == QLatin1String("PropertyChange:TableSummary")) { + sendObject_property_change_accessible_table_summary = 1; + } else if (right == QLatin1String("PropertyChange:AccessibleValue")) { + sendObject_property_change_accessible_value = 1; + } else { + sendObject_property_change = 1; + } + } else if (right.startsWith(QLatin1String("RowDeleted"))) { + sendObject_row_deleted = 1; + } else if (right.startsWith(QLatin1String("RowInserted"))) { + sendObject_row_inserted = 1; + } else if (right.startsWith(QLatin1String("RowReordered"))) { + sendObject_row_reordered = 1; + } else if (right.startsWith(QLatin1String("SelectionChanged"))) { + sendObject_selection_changed = 1; + } else if (right.startsWith(QLatin1String("StateChanged"))) { + sendObject_state_changed = 1; + } else if (right.startsWith(QLatin1String("TextAttributesChanged"))) { + sendObject_text_attributes_changed = 1; + } else if (right.startsWith(QLatin1String("TextBoundsChanged"))) { + sendObject_text_bounds_changed = 1; + } else if (right.startsWith(QLatin1String("TextCaretMoved"))) { + sendObject_text_caret_moved = 1; + } else if (right.startsWith(QLatin1String("TextChanged"))) { + sendObject_text_changed = 1; + } else if (right.startsWith(QLatin1String("TextSelectionChanged"))) { + sendObject_text_selection_changed = 1; + } else if (right.startsWith(QLatin1String("ValueChanged"))) { + sendObject_value_changed = 1; + } else if (right.startsWith(QLatin1String("VisibleDataChanged"))) { + sendObject_visible_data_changed = 1; + } else { + qWarning() << "WARNING: subscription string not handled:" << flag; + } + } + break; + } + case 'w': { // window + if (flag.size() <= 8) { + sendWindow = 1; + } else { // object:Foo:Bar + QString right = flag.mid(7); + if (false) { + } else if (right.startsWith(QLatin1String("Activate"))) { + sendWindow_activate = 1; + } else if (right.startsWith(QLatin1String("Close"))) { + sendWindow_close= 1; + } else if (right.startsWith(QLatin1String("Create"))) { + sendWindow_create = 1; + } else if (right.startsWith(QLatin1String("Deactivate"))) { + sendWindow_deactivate = 1; + } else if (right.startsWith(QLatin1String("Lower"))) { + sendWindow_lower = 1; + } else if (right.startsWith(QLatin1String("Maximize"))) { + sendWindow_maximize = 1; + } else if (right.startsWith(QLatin1String("Minimize"))) { + sendWindow_minimize = 1; + } else if (right.startsWith(QLatin1String("Move"))) { + sendWindow_move = 1; + } else if (right.startsWith(QLatin1String("Raise"))) { + sendWindow_raise = 1; + } else if (right.startsWith(QLatin1String("Reparent"))) { + sendWindow_reparent = 1; + } else if (right.startsWith(QLatin1String("Resize"))) { + sendWindow_resize = 1; + } else if (right.startsWith(QLatin1String("Restore"))) { + sendWindow_restore = 1; + } else if (right.startsWith(QLatin1String("Restyle"))) { + sendWindow_restyle = 1; + } else if (right.startsWith(QLatin1String("Shade"))) { + sendWindow_shade = 1; + } else if (right.startsWith(QLatin1String("Unshade"))) { + sendWindow_unshade = 1; + } else if (right.startsWith(QLatin1String("DesktopCreate"))) { + // ignore this one + } else if (right.startsWith(QLatin1String("DesktopDestroy"))) { + // ignore this one + } else { + qWarning() << "WARNING: subscription string not handled:" << flag; + } + } + break; + } + case 'f': { + sendFocus = 1; + break; + } + case 'd': { // document is not implemented + break; + } + case 't': { // terminal is not implemented + break; + } + case 'm': { // mouse* is handled in a different way by the gnome atspi stack + break; + } + default: + qWarning() << "WARNING: subscription string not handled:" << flag; + } +} + +/*! + Checks via dbus which events should be sent. + */ +void AtSpiAdaptor::updateEventListeners() +{ + QDBusMessage m = QDBusMessage::createMethodCall(QLatin1String("org.a11y.atspi.Registry"), + QLatin1String("/org/a11y/atspi/registry"), + QLatin1String("org.a11y.atspi.Registry"), QLatin1String("GetRegisteredEvents")); + QDBusReply listenersReply = m_dbus->connection().call(m); + if (listenersReply.isValid()) { + const QSpiEventListenerArray evList = listenersReply.value(); + Q_FOREACH (const QSpiEventListener &ev, evList) { + setBitFlag(ev.eventName); + } + m_applicationAdaptor->sendEvents(!evList.isEmpty()); + } else { + qWarning() << "Could not query active accessibility event listeners."; + } +} + +void AtSpiAdaptor::eventListenerDeregistered(const QString &/*bus*/, const QString &/*path*/) +{ +// qDebug() << "AtSpiAdaptor::eventListenerDeregistered: " << bus << path; + updateEventListeners(); +} + +void AtSpiAdaptor::eventListenerRegistered(const QString &/*bus*/, const QString &/*path*/) +{ +// qDebug() << "AtSpiAdaptor::eventListenerRegistered: " << bus << path; + updateEventListeners(); +} + +/*! + This slot needs to get called when a \a window has be activated or deactivated (become focused). + When \a active is true, the window just received focus, otherwise it lost the focus. + */ +void AtSpiAdaptor::windowActivated(QObject* window, bool active) +{ + if (!(sendWindow || sendWindow_activate)) + return; + + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(window); + Q_ASSERT(iface && iface->isValid()); + + QString windowTitle = iface->text(QAccessible::Name); + delete iface; + + QDBusVariant data; + data.setVariant(windowTitle); + + QVariantList args = packDBusSignalArguments(QString(), 0, 0, QVariant::fromValue(data)); + + QString status = active ? QLatin1String("Activate") : QLatin1String("Deactivate"); + QString path = pathForObject(window); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_WINDOW), status, args); + + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("active"), active ? 1 : 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); +} + +QVariantList AtSpiAdaptor::packDBusSignalArguments(const QString &type, int data1, int data2, const QVariant &variantData) const +{ + QVariantList arguments; + arguments << type << data1 << data2 << variantData + << QVariant::fromValue(QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(QSPI_OBJECT_PATH_ROOT))); + return arguments; +} + +QVariant AtSpiAdaptor::variantForPath(const QString &path) const +{ + QDBusVariant data; + data.setVariant(QVariant::fromValue(QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(path)))); + return QVariant::fromValue(data); +} + +bool AtSpiAdaptor::sendDBusSignal(const QString &path, const QString &interface, const QString &signalName, const QVariantList &arguments) const +{ + QDBusMessage message = QDBusMessage::createSignal(path, interface, signalName); + message.setArguments(arguments); + return m_dbus->connection().send(message); +} + +QAIPointer AtSpiAdaptor::interfaceFromPath(const QString& dbusPath) const +{ + if (dbusPath == QLatin1String(QSPI_OBJECT_PATH_ROOT)) + return QAIPointer(QAccessible::queryAccessibleInterface(qApp)); + + QStringList parts = dbusPath.split(QLatin1Char('/')); + if (parts.size() <= 5) { + qWarning() << "invalid path: " << dbusPath; + return QAIPointer(); + } + + QString objectString = parts.at(5); + quintptr uintptr = objectString.toULongLong(); + + if (uintptr && m_handledObjects.contains(uintptr)) { + // We found the pointer, check if it's still valid: + if (m_handledObjects[uintptr]) { + QObject* object = reinterpret_cast(uintptr); + + QAIPointer interface = QAIPointer(QAccessible::queryAccessibleInterface(object)); + if (!interface) + return QAIPointer(); + + for (int i = 6; i < parts.size(); ++i) { + int childIndex = parts.at(i).toInt(); + if (childIndex < 0) { + qWarning() << "Invalid child index"; + return QAIPointer(); + } + QAIPointer childInterface(interface->child(childIndex)); + if (childInterface) + interface = childInterface; + } + return interface; + } else { + m_handledObjects.remove(uintptr); + } + } + return QAIPointer(); +} + + +/*! + This function gets called when Qt notifies about accessibility updates. +*/ +void AtSpiAdaptor::notify(QAccessibleEvent *event) +{ + if (!initialized) + return; + + switch (event->type()) { + case QAccessible::ObjectCreated: + if (sendObject || sendObject_children_changed) + notifyAboutCreation(QAIPointer(event->accessibleInterface())); + break; + case QAccessible::ObjectShow: { + if (sendObject || sendObject_state_changed) { + QString path = pathForInterface(QAIPointer(event->accessibleInterface())); + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("showing"), 1, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + } + break; + } + case QAccessible::ObjectHide: { + if (sendObject || sendObject_state_changed) { + QString path = pathForInterface(QAIPointer(event->accessibleInterface())); + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("showing"), 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + } + break; + } + case QAccessible::ObjectDestroyed: { + if (sendObject || sendObject_state_changed) + notifyAboutDestruction(QAIPointer(event->accessibleInterface())); + break; + } + case QAccessible::NameChanged: { + if (sendObject || sendObject_property_change || sendObject_property_change_accessible_name) { + QString path = pathForInterface(QAIPointer(event->accessibleInterface())); + QVariantList args = packDBusSignalArguments(QLatin1String("accessible-name"), 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("PropertyChange"), args); + } + break; + } + case QAccessible::DescriptionChanged: { + if (sendObject || sendObject_property_change || sendObject_property_change_accessible_description) { + QString path = pathForInterface(QAIPointer(event->accessibleInterface())); + QVariantList args = packDBusSignalArguments(QLatin1String("accessible-description"), 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("PropertyChange"), args); + } + break; + } + case QAccessible::Focus: { + if (sendFocus || sendObject || sendObject_state_changed) + sendFocusChanged(QAIPointer(event->accessibleInterface())); + break; + } + case QAccessible::TextInserted: + case QAccessible::TextRemoved: + case QAccessible::TextUpdated: { + if (sendObject || sendObject_text_changed) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + Q_ASSERT(iface->textInterface()); + QString path = pathForInterface(iface); + + int changePosition = 0; + int cursorPosition = 0; + QString textRemoved; + QString textInserted; + + if (event->type() == QAccessible::TextInserted) { + QAccessibleTextInsertEvent *textEvent = static_cast(event); + textInserted = textEvent->textInserted(); + changePosition = textEvent->changePosition(); + cursorPosition = textEvent->cursorPosition(); + } else if (event->type() == QAccessible::TextRemoved) { + QAccessibleTextRemoveEvent *textEvent = static_cast(event); + textRemoved = textEvent->textRemoved(); + changePosition = textEvent->changePosition(); + cursorPosition = textEvent->cursorPosition(); + } else if (event->type() == QAccessible::TextInserted) { + QAccessibleTextUpdateEvent *textEvent = static_cast(event); + textInserted = textEvent->textInserted(); + textRemoved = textEvent->textRemoved(); + changePosition = textEvent->changePosition(); + cursorPosition = textEvent->cursorPosition(); + } + + QDBusVariant data; + + if (!textRemoved.isEmpty()) { + data.setVariant(QVariant::fromValue(textRemoved)); + QVariantList args = packDBusSignalArguments(QLatin1String("delete"), changePosition, textRemoved.length(), QVariant::fromValue(data)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("TextChanged"), args); + } + + if (!textInserted.isEmpty()) { + data.setVariant(QVariant::fromValue(textInserted)); + QVariantList args = packDBusSignalArguments(QLatin1String("insert"), changePosition, textInserted.length(), QVariant::fromValue(data)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("TextChanged"), args); + } + + // send a cursor update + Q_UNUSED(cursorPosition) +// QDBusVariant cursorData; +// cursorData.setVariant(QVariant::fromValue(cursorPosition)); +// QVariantList args = packDBusSignalArguments(QString(), cursorPosition, 0, QVariant::fromValue(cursorData)); +// sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), +// QLatin1String("TextCaretMoved"), args); + } + break; + } + case QAccessible::TextCaretMoved: { + if (sendObject || sendObject_text_caret_moved) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + Q_ASSERT(iface->textInterface()); + QString path = pathForInterface(iface); + QDBusVariant cursorData; + int pos = iface->textInterface()->cursorPosition(); + cursorData.setVariant(QVariant::fromValue(pos)); + QVariantList args = packDBusSignalArguments(QString(), pos, 0, QVariant::fromValue(cursorData)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("TextCaretMoved"), args); + } + break; + } + case QAccessible::TextSelectionChanged: { + if (sendObject || sendObject_text_selection_changed) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments(QString(), 0, 0, QVariant::fromValue(QDBusVariant(QVariant(QString())))); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("TextSelectionChanged"), args); + } + break; + } + case QAccessible::ValueChanged: { + if (sendObject || sendObject_value_changed) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + Q_ASSERT(iface->valueInterface()); + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments(QLatin1String("accessible-value"), 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("PropertyChange"), args); + } + break; + } + case QAccessible::Selection: { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + QString path = pathForInterface(iface); + int selected = iface->state().selected ? 1 : 0; + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("selected"), selected, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + break; + } + + case QAccessible::StateChanged: { + if (sendObject || sendObject_state_changed || sendWindow || sendWindow_activate) { + QAccessible::State stateChange = static_cast(event)->changedStates(); + if (stateChange.checked) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + int checked = iface->state().checked; + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments(QLatin1String("checked"), checked, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), args); + } else if (stateChange.active) { + QAIPointer iface = QAIPointer(event->accessibleInterface()); + if (!(iface->role() == QAccessible::Window && (sendWindow || sendWindow_activate))) + return; + QString windowTitle = iface->text(QAccessible::Name); + QDBusVariant data; + data.setVariant(windowTitle); + QVariantList args = packDBusSignalArguments(QString(), 0, 0, QVariant::fromValue(data)); + + QString status = iface->state().active ? QLatin1String("Activate") : QLatin1String("Deactivate"); + QString path = pathForInterface(iface); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_WINDOW), status, args); + + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("active"), iface->state().active ? 1 : 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + } + } + break; + } +// case QAccessible::TableModelChanged: { +// // This is rather evil. We don't send data and hope that at-spi fetches the right child. +// // This hack fails when a row gets removed and a different one added in its place. +// QDBusVariant data; +// emit ChildrenChanged("add", 0, 0, data, spiBridge->getRootReference()); +// break; +// } + // case QAccessible::TableModelChanged: + // QAccessible2::TableModelChange change = interface->tableInterface()->modelChange(); + // // assume we should reset if everything is 0 + // if (change.firstColumn == 0 && change.firstRow == 0 && change.lastColumn == 0 && change.lastRow == 0) { + // notifyAboutDestruction(accessible); + // notifyAboutCreation(accessible); + // } + // break; + + case QAccessible::ParentChanged: + break; + case QAccessible::DialogStart: + break; + case QAccessible::DialogEnd: + break; + case QAccessible::SelectionRemove: + break; + default: + QAIPointer iface = QAIPointer(event->accessibleInterface()); + qWarning() << "QSpiAccessible::accessibleEvent not handled: " << QString::number(event->type(), 16) + << " obj: " << iface->object() + << ((iface->isValid() && iface->object()) ? iface->object()->objectName() : QLatin1String(" invalid interface!")); + break; + } +} + +void AtSpiAdaptor::sendFocusChanged(const QAIPointer &interface) const +{ + static QString lastFocusPath; + // "remove" old focus + if (!lastFocusPath.isEmpty()) { + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("focused"), 0, 0, variantForPath(lastFocusPath)); + sendDBusSignal(lastFocusPath, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + } + // send new focus + { + QString path = pathForInterface(interface); + + QVariantList stateArgs = packDBusSignalArguments(QLatin1String("focused"), 1, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), + QLatin1String("StateChanged"), stateArgs); + + QVariantList focusArgs = packDBusSignalArguments(QString(), 0, 0, variantForPath(path)); + sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_FOCUS), + QLatin1String("Focus"), focusArgs); + lastFocusPath = path; + } +} + +void AtSpiAdaptor::notifyAboutCreation(const QAIPointer &interface) const +{ +// // say hello to d-bus +// cache->emitAddAccessible(accessible->getCacheItem()); + + // notify about the new child of our parent + QAIPointer parent(interface->parent()); + if (!parent) { + qWarning() << "AtSpiAdaptor::notifyAboutCreation: Could not find parent for " << interface->object(); + return; + } + QString path = pathForInterface(interface); + int childCount = parent->childCount(); + QString parentPath = pathForInterface(parent); + QVariantList args = packDBusSignalArguments(QLatin1String("add"), childCount, 0, variantForPath(path)); + sendDBusSignal(parentPath, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), QLatin1String("ChildrenChanged"), args); +} + +void AtSpiAdaptor::notifyAboutDestruction(const QAIPointer &interface) const +{ + if (!interface->isValid()) + return; + + QAIPointer parent(interface->parent()); + if (!parent) { + qWarning() << "AtSpiAdaptor::notifyAboutDestruction: Could not find parent for " << interface->object(); + return; + } + QString path = pathForInterface(interface); + + // this is in the destructor. we have no clue which child we used to be. + // FIXME + int childIndex = -1; + // if (child) { + // childIndex = child; + // } else { + // childIndex = parent->indexOfChild(interface); + // } + + QString parentPath = pathForInterface(parent, true); + QVariantList args = packDBusSignalArguments(QLatin1String("remove"), childIndex, 0, variantForPath(path)); + sendDBusSignal(parentPath, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), QLatin1String("ChildrenChanged"), args); +} + +/*! + Handle incoming DBus message. + This function dispatches the dbus message to the right interface handler. + */ +bool AtSpiAdaptor::handleMessage(const QDBusMessage &message, const QDBusConnection &connection) +{ + // get accessible interface + QAIPointer accessible = interfaceFromPath(message.path()); + if (!accessible) { + qWarning() << "WARNING Qt AtSpiAdaptor: Could not find accessible on path: " << message.path(); + return false; + } + + QString interface = message.interface(); + QString function = message.member(); + + // qDebug() << "AtSpiAdaptor::handleMessage: " << interface << function; + + if (function == QLatin1String("Introspect")) { + //introspect(message.path()); + return false; + } + + // handle properties like regular functions + if (interface == QLatin1String("org.freedesktop.DBus.Properties")) { + interface = message.arguments().at(0).toString(); + // Get/Set + Name + function = message.member() + message.arguments().at(1).toString(); + } + + // switch interface to call + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_ACCESSIBLE)) + return accessibleInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_APPLICATION)) + return applicationInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_COMPONENT)) + return componentInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_ACTION)) + return actionInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_TEXT)) + return textInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_EDITABLE_TEXT)) + return editableTextInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_VALUE)) + return valueInterface(accessible, function, message, connection); + if (interface == QLatin1String(ATSPI_DBUS_INTERFACE_TABLE)) + return tableInterface(accessible, function, message, connection); + + qWarning() << "AtSpiAdaptor::handleMessage with unknown interface: " << message.path() << interface << function; + return false; +} + +// Application +bool AtSpiAdaptor::applicationInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (message.path() != QLatin1String(ATSPI_DBUS_PATH_ROOT)) { + qWarning() << "WARNING Qt AtSpiAdaptor: Could not find application interface for: " << message.path() << interface; + return false; + } + + if (function == QLatin1String("SetId")) { + Q_ASSERT(message.signature() == QLatin1String("ssv")); + QVariant value = qvariant_cast(message.arguments().at(2)).variant(); + + m_applicationId = value.toInt(); + return true; + } + if (function == QLatin1String("GetId")) { + Q_ASSERT(message.signature() == QLatin1String("ss")); + QDBusMessage reply = message.createReply(QVariant::fromValue(QDBusVariant(m_applicationId))); + return connection.send(reply); + } + if (function == QLatin1String("GetToolkitName")) { + Q_ASSERT(message.signature() == QLatin1String("ss")); + QDBusMessage reply = message.createReply(QVariant::fromValue(QDBusVariant(QLatin1String("Qt")))); + return connection.send(reply); + } + + qDebug() << "AtSpiAdaptor::applicationInterface " << message.path() << interface << function; + return false; +} + +/*! + Register this application as accessible on the accessibility DBus. + */ +void AtSpiAdaptor::registerApplication() +{ + OrgA11yAtspiSocketInterface *registry; + registry = new OrgA11yAtspiSocketInterface(QLatin1String(QSPI_REGISTRY_NAME), + QLatin1String(QSPI_OBJECT_PATH_ROOT), m_dbus->connection()); + + QDBusPendingReply reply; + QSpiObjectReference ref = QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(QSPI_OBJECT_PATH_ROOT)); + reply = registry->Embed(ref); + reply.waitForFinished(); // TODO: make this async + if (reply.isValid ()) { + const QSpiObjectReference &socket = reply.value(); + accessibilityRegistry = QSpiObjectReference(socket); + } else { + qWarning() << "Error in contacting registry"; + qWarning() << reply.error().name(); + qWarning() << reply.error().message(); + } + delete registry; +} + +// Accessible +bool AtSpiAdaptor::accessibleInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (function == QLatin1String("GetRole")) { + sendReply(connection, message, (uint) getRole(interface)); + } else if (function == QLatin1String("GetName")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Name)))); + } else if (function == QLatin1String("GetRoleName")) { + sendReply(connection, message, qSpiRoleMapping[interface->role()].name()); + } else if (function == QLatin1String("GetLocalizedRoleName")) { + sendReply(connection, message, QVariant::fromValue(qSpiRoleMapping[interface->role()].localizedName())); + } else if (function == QLatin1String("GetChildCount")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->childCount()))); + } else if (function == QLatin1String("GetIndexInParent")) { + int childIndex = -1; + QAIPointer parent(interface->parent()); + if (parent) { + childIndex = parent->indexOfChild(interface.data()); + if (childIndex < 0) + qWarning() << "GetIndexInParent get invalid index: " << childIndex << interface; + } + sendReply(connection, message, childIndex); + } else if (function == QLatin1String("GetParent")) { + QString path; + QAIPointer parent(interface->parent()); + if (!parent) { + path = QLatin1String(ATSPI_DBUS_PATH_NULL); + } else if (parent->role() == QAccessible::Application) { + path = QLatin1String(ATSPI_DBUS_PATH_ROOT); + } else { + path = pathForInterface(parent); + } + // Parent is a property, so it needs to be wrapped inside an extra variant. + sendReply(connection, message, QVariant::fromValue( + QDBusVariant(QVariant::fromValue(QSpiObjectReference(connection, QDBusObjectPath(path)))))); + } else if (function == QLatin1String("GetChildAtIndex")) { + int index = message.arguments().first().toInt(); + if (index < 0) { + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))); + } else { + QAIPointer childInterface = QAIPointer(interface->child(index)); + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(childInterface))))); + } + } else if (function == QLatin1String("GetInterfaces")) { + sendReply(connection, message, accessibleInterfaces(interface)); + } else if (function == QLatin1String("GetDescription")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Description)))); + } else if (function == QLatin1String("GetState")) { + quint64 spiState = spiStatesFromQState(interface->state()); + if (interface->tableInterface()) { + setSpiStateBit(&spiState, ATSPI_STATE_MANAGES_DESCENDANTS); + } +// FIXME: figure out if this is a top level window and set its active state accordingly +// if (interface->object() && interface->object()->isWidgetType()) { +// QWidget *w = qobject_cast(interface->object()); +// if (w->topLevelWidget() && w->isActiveWindow()) { +// setSpiStateBit(&spiState, ATSPI_STATE_ACTIVE); +// } +// } + QAccessible::Role role = interface->role(); + if (role == QAccessible::TreeItem || + role == QAccessible::ListItem) { + /* Transient means libatspi2 will not cache items. + This is important because when adding/removing an item + the cache becomes outdated and we don't change the paths of + items in lists/trees/tables. */ + setSpiStateBit(&spiState, ATSPI_STATE_TRANSIENT); + } + sendReply(connection, message, + QVariant::fromValue(spiStateSetFromSpiStates(spiState))); + } else if (function == QLatin1String("GetAttributes")) { + sendReply(connection, message, QVariant::fromValue(QSpiAttributeSet())); + } else if (function == QLatin1String("GetRelationSet")) { + sendReply(connection, message, QVariant::fromValue(relationSet(interface, connection))); + } else if (function == QLatin1String("GetApplication")) { + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(QSPI_OBJECT_PATH_ROOT)))); + } else if (function == QLatin1String("GetChildren")) { + QSpiObjectReferenceArray children; + for (int i = 0; i < interface->childCount(); ++i) { + QString childPath = pathForInterface(QAIPointer(interface->child(i))); + QSpiObjectReference ref(connection, QDBusObjectPath(childPath)); + children << ref; + } + connection.send(message.createReply(QVariant::fromValue(children))); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +AtspiRole AtSpiAdaptor::getRole(const QAIPointer &interface) const +{ + if ((interface->role() == QAccessible::EditableText) && interface->state().passwordEdit) + return ATSPI_ROLE_PASSWORD_TEXT; + return qSpiRoleMapping[interface->role()].spiRole(); +} + +//#define ACCESSIBLE_CREATION_DEBUG + +QStringList AtSpiAdaptor::accessibleInterfaces(const QAIPointer &interface) const +{ + QStringList ifaces; +#ifdef ACCESSIBLE_CREATION_DEBUG + qDebug() << "AtSpiAdaptor::accessibleInterfaces create: " << interface->object(); +#endif + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_ACCESSIBLE); + + if ( (!interface->rect().isEmpty()) || + (interface->object() && interface->object()->isWidgetType()) || + (interface->role() == QAccessible::ListItem) || + (interface->role() == QAccessible::Cell) || + (interface->role() == QAccessible::TreeItem) || + (interface->role() == QAccessible::Row) || + (interface->object() && interface->object()->inherits("QSGItem")) + ) { + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_COMPONENT); + } +#ifdef ACCESSIBLE_CREATION_DEBUG + else { + qDebug() << " IS NOT a component"; + } +#endif + + if (interface->actionInterface()) + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_ACTION); + + if (interface->textInterface()) + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_TEXT); + + if (interface->editableTextInterface()) + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_EDITABLE_TEXT); + + if (interface->valueInterface()) + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_VALUE); + + if (interface->tableInterface()) + ifaces << QLatin1String(ATSPI_DBUS_INTERFACE_TABLE); + + return ifaces; +} + +QSpiRelationArray AtSpiAdaptor::relationSet(const QAIPointer &interface, const QDBusConnection &connection) const +{ + typedef QPair RelationPair; + QVector relationInterfaces; + relationInterfaces = interface->relations(); + + QSpiRelationArray relations; + Q_FOREACH (const RelationPair &pair, relationInterfaces) { +// FIXME: this loop seems a bit strange... "related" always have one item when we check. +//And why is it a list, when it always have one item? And it seems to assume that the QAccessible::Relation enum maps directly to AtSpi + QList related; + + QDBusObjectPath path = QDBusObjectPath(pathForInterface(QAIPointer(pair.first))); + related.append(QSpiObjectReference(connection, path)); + + if (!related.isEmpty()) + relations.append(QSpiRelationArrayEntry(qAccessibleRelationToAtSpiRelation(pair.second), related)); + } + return relations; +} + +void AtSpiAdaptor::sendReply(const QDBusConnection &connection, const QDBusMessage &message, const QVariant &argument) const +{ + QDBusMessage reply = message.createReply(argument); + connection.send(reply); +} + + +QString AtSpiAdaptor::pathForObject(QObject *object) const +{ + Q_ASSERT(object); + + if (object == qApp) { + return QLatin1String(QSPI_OBJECT_PATH_ROOT); + } + + if (qstrcmp(object->metaObject()->className(), "QAction") == 0) { + qDebug() << "AtSpiAdaptor::pathForObject: warning: creating path with QAction as object."; + } + quintptr uintptr = reinterpret_cast(object); + if (!m_handledObjects.contains(uintptr)) + m_handledObjects[uintptr] = QPointer(object); + return QLatin1String(QSPI_OBJECT_PATH_PREFIX) + QString::number(uintptr); +} + +QString AtSpiAdaptor::pathForInterface(const QAIPointer &interface, bool inDestructor) const +{ + if (!interface || !interface->isValid()) + return QLatin1String(ATSPI_DBUS_PATH_NULL); + if (interface->role() == QAccessible::Application) + return QLatin1String(QSPI_OBJECT_PATH_ROOT); + + QAIPointer interfaceWithObject = interface; + QString path; + + if (interface->role() == QAccessible::MenuItem && interface->object() && + inheritsQAction(interface->object())) { + interfaceWithObject = QAIPointer(interface->parent()); + int childIndex = interfaceWithObject->indexOfChild(interface.data()); + path.append(QString::fromLatin1("/%1").arg(childIndex)); + } + + while (!interfaceWithObject->object()) { + QAIPointer parentInterface(interfaceWithObject->parent()); + + Q_ASSERT(parentInterface->isValid()); + int index = parentInterface->indexOfChild(interfaceWithObject.data()); + if (index < 0) { + qWarning() << "Object claims to have child that we cannot navigate to. FIX IT!" << parentInterface->object(); + return QLatin1String(ATSPI_DBUS_PATH_NULL); + } + path.prepend(QLatin1Char('/') + QString::number(index)); + interfaceWithObject = parentInterface; + } + quintptr uintptr = reinterpret_cast(interfaceWithObject->object()); + path.prepend(QLatin1String(QSPI_OBJECT_PATH_PREFIX) + QString::number(uintptr)); + + if (!inDestructor && !m_handledObjects.contains(uintptr)) + m_handledObjects[uintptr] = QPointer(interfaceWithObject->object()); + + return path; +} + +bool AtSpiAdaptor::inheritsQAction(QObject *object) +{ + const QMetaObject *mo = object->metaObject(); + while (mo) { + const QLatin1String cn(mo->className()); + if (cn == QLatin1String("QAction")) + return true; + mo = mo->superClass(); + } + return false; +} + +// Component +static QAIPointer getWindow(QAIPointer interface) +{ + if (interface->role() == QAccessible::Window) + return interface; + + QAIPointer parent(interface->parent()); + while (parent && parent->role() != QAccessible::Window) + parent = QAIPointer(parent->parent()); + + return parent; +} + +static QRect getRelativeRect(const QAIPointer &interface) +{ + QAIPointer window; + QRect wr, cr; + + cr = interface->rect(); + + window = getWindow(interface); + if (window) { + wr = window->rect(); + + cr.setX(cr.x() - wr.x()); + cr.setY(cr.x() - wr.y()); + } + return cr; +} + +bool AtSpiAdaptor::componentInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (function == QLatin1String("Contains")) { + bool ret = false; + int x = message.arguments().at(0).toInt(); + int y = message.arguments().at(1).toInt(); + uint coordType = message.arguments().at(2).toUInt(); + if (coordType == ATSPI_COORD_TYPE_SCREEN) + ret = interface->rect().contains(x, y); + else + ret = getRelativeRect(interface).contains(x, y); + sendReply(connection, message, ret); + } else if (function == QLatin1String("GetAccessibleAtPoint")) { + int x = message.arguments().at(0).toInt(); + int y = message.arguments().at(1).toInt(); + uint coordType = message.arguments().at(2).toUInt(); + Q_UNUSED (coordType) // FIXME + + QAIPointer childInterface(interface->childAt(x, y)); + QAIPointer iface; + while (childInterface) { + iface = childInterface; + childInterface = QAIPointer(iface->childAt(x, y)); + } + if (iface) { + QString path = pathForInterface(iface); + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(path)))); + } else { + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))); + } + } else if (function == QLatin1String("GetAlpha")) { + sendReply(connection, message, (double) 1.0); + } else if (function == QLatin1String("GetExtents")) { + uint coordType = message.arguments().at(0).toUInt(); + sendReply(connection, message, QVariant::fromValue(getExtents(interface, coordType))); + } else if (function == QLatin1String("GetLayer")) { + sendReply(connection, message, QVariant::fromValue((uint)1)); + } else if (function == QLatin1String("GetMDIZOrder")) { + sendReply(connection, message, QVariant::fromValue((short)0)); + } else if (function == QLatin1String("GetPosition")) { + uint coordType = message.arguments().at(0).toUInt(); + QRect rect; + if (coordType == ATSPI_COORD_TYPE_SCREEN) + rect = interface->rect(); + else + rect = getRelativeRect(interface); + QVariantList pos; + pos << rect.x() << rect.y(); + connection.send(message.createReply(pos)); + } else if (function == QLatin1String("GetSize")) { + QRect rect = interface->rect(); + QVariantList size; + size << rect.width() << rect.height(); + connection.send(message.createReply(size)); + } else if (function == QLatin1String("GrabFocus")) { +// FIXME: implement focus grabbing +// if (interface->object() && interface->object()->isWidgetType()) { +// QWidget* w = static_cast(interface->object()); +// w->setFocus(Qt::OtherFocusReason); +// sendReply(connection, message, true); +// } + sendReply(connection, message, false); + } else if (function == QLatin1String("SetExtents")) { +// int x = message.arguments().at(0).toInt(); +// int y = message.arguments().at(1).toInt(); +// int width = message.arguments().at(2).toInt(); +// int height = message.arguments().at(3).toInt(); +// uint coordinateType = message.arguments().at(4).toUInt(); + qWarning() << "SetExtents is not implemented."; + sendReply(connection, message, false); + } else if (function == QLatin1String("SetPosition")) { +// int x = message.arguments().at(0).toInt(); +// int y = message.arguments().at(1).toInt(); +// uint coordinateType = message.arguments().at(2).toUInt(); + qWarning() << "SetPosition is not implemented."; + sendReply(connection, message, false); + } else if (function == QLatin1String("SetSize")) { +// int width = message.arguments().at(0).toInt(); +// int height = message.arguments().at(1).toInt(); + qWarning() << "SetSize is not implemented."; + sendReply(connection, message, false); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +QRect AtSpiAdaptor::getExtents(const QAIPointer &interface, uint coordType) +{ + return (coordType == ATSPI_COORD_TYPE_SCREEN) ? interface->rect() : getRelativeRect(interface); +} + +// Action interface +bool AtSpiAdaptor::actionInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + QAccessibleActionInterface *actionIface = interface->actionInterface(); + if (!actionIface) + return false; + + if (function == QLatin1String("GetNActions")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(QVariant::fromValue(actionIface->actionNames().count())))); + } else if (function == QLatin1String("DoAction")) { + int index = message.arguments().at(0).toInt(); + if (index < 0 || index >= actionIface->actionNames().count()) + return false; + interface->actionInterface()->doAction(actionIface->actionNames().at(index)); + sendReply(connection, message, true); + } else if (function == QLatin1String("GetActions")) { + sendReply(connection, message, QVariant::fromValue(getActions(actionIface))); + } else if (function == QLatin1String("GetName")) { + int index = message.arguments().at(0).toInt(); + if (index < 0 || index >= actionIface->actionNames().count()) + return false; + sendReply(connection, message, actionIface->actionNames().at(index)); + } else if (function == QLatin1String("GetDescription")) { + int index = message.arguments().at(0).toInt(); + if (index < 0 || index >= actionIface->actionNames().count()) + return false; + sendReply(connection, message, actionIface->localizedActionDescription(actionIface->actionNames().at(index))); + } else if (function == QLatin1String("GetKeyBinding")) { + int index = message.arguments().at(0).toInt(); + if (index < 0 || index >= actionIface->actionNames().count()) + return false; + QStringList keyBindings; + keyBindings = actionIface->keyBindingsForAction(actionIface->actionNames().value(index)); + if (keyBindings.isEmpty()) { + QString acc = interface->text(QAccessible::Accelerator); + if (!acc.isEmpty()) + keyBindings.append(acc); + } + if (keyBindings.length() > 0) + sendReply(connection, message, keyBindings.join(QLatin1String(";"))); + else + sendReply(connection, message, QString()); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +QSpiActionArray AtSpiAdaptor::getActions(QAccessibleActionInterface *actionInterface) const +{ + QSpiActionArray actions; + Q_FOREACH (const QString &actionName, actionInterface->actionNames()) { + QSpiAction action; + QStringList keyBindings; + + action.description = actionInterface->localizedActionDescription(actionName); + + keyBindings = actionInterface->keyBindingsForAction(actionName); + + if (keyBindings.length() > 0) + action.keyBinding = keyBindings[0]; + else + action.keyBinding = QString(); + + actions << action; + } + return actions; +} + +// Text interface +bool AtSpiAdaptor::textInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (!interface->textInterface()) + return false; + + // properties + if (function == QLatin1String("GetCaretOffset")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(QVariant::fromValue(interface->textInterface()->cursorPosition())))); + } else if (function == QLatin1String("GetCharacterCount")) { + sendReply(connection, message, QVariant::fromValue(QDBusVariant(QVariant::fromValue(interface->textInterface()->characterCount())))); + + // functions + } else if (function == QLatin1String("AddSelection")) { + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + int lastSelection = interface->textInterface()->selectionCount(); + interface->textInterface()->setSelection(lastSelection, startOffset, endOffset); + sendReply(connection, message, (interface->textInterface()->selectionCount() > lastSelection)); + } else if (function == QLatin1String("GetAttributeRun")) { + int offset = message.arguments().at(0).toInt(); + bool includeDefaults = message.arguments().at(1).toBool(); + Q_UNUSED(includeDefaults) + connection.send(message.createReply(getAttributes(interface, offset, includeDefaults))); + } else if (function == QLatin1String("GetAttributeValue")) { + int offset = message.arguments().at(0).toInt(); + QString attributeName = message.arguments().at(1).toString(); + connection.send(message.createReply(getAttributeValue(interface, offset, attributeName))); + } else if (function == QLatin1String("GetAttributes")) { + int offset = message.arguments().at(0).toInt(); + connection.send(message.createReply(getAttributes(interface, offset, true))); + } else if (function == QLatin1String("GetBoundedRanges")) { + int x = message.arguments().at(0).toInt(); + int y = message.arguments().at(1).toInt(); + int width = message.arguments().at(2).toInt(); + int height = message.arguments().at(3).toInt(); + uint coordType = message.arguments().at(4).toUInt(); + uint xClipType = message.arguments().at(5).toUInt(); + uint yClipType = message.arguments().at(6).toUInt(); + Q_UNUSED(x) Q_UNUSED (y) Q_UNUSED(width) + Q_UNUSED(height) Q_UNUSED(coordType) + Q_UNUSED(xClipType) Q_UNUSED(yClipType) + qWarning("Not implemented: QSpiAdaptor::GetBoundedRanges"); + sendReply(connection, message, QVariant::fromValue(QSpiTextRangeList())); + } else if (function == QLatin1String("GetCharacterAtOffset")) { + int offset = message.arguments().at(0).toInt(); + int start; + int end; + QString result = interface->textInterface()->textAtOffset(offset, QAccessible2::CharBoundary, &start, &end); + sendReply(connection, message, (int) *(qPrintable (result))); + } else if (function == QLatin1String("GetCharacterExtents")) { + int offset = message.arguments().at(0).toInt(); + int coordType = message.arguments().at(1).toUInt(); + connection.send(message.createReply(getCharacterExtents(interface, offset, coordType))); + } else if (function == QLatin1String("GetDefaultAttributeSet") || function == QLatin1String("GetDefaultAttributes")) { + // GetDefaultAttributes is deprecated in favour of GetDefaultAttributeSet. + // Empty set seems reasonable. There is no default attribute set. + sendReply(connection, message, QVariant::fromValue(QSpiAttributeSet())); + } else if (function == QLatin1String("GetNSelections")) { + sendReply(connection, message, interface->textInterface()->selectionCount()); + } else if (function == QLatin1String("GetOffsetAtPoint")) { + qDebug() << message.signature(); + Q_ASSERT(!message.signature().isEmpty()); + QPoint point(message.arguments().at(0).toInt(), message.arguments().at(1).toInt()); + uint coordType = message.arguments().at(2).toUInt(); + if (coordType == ATSPI_COORD_TYPE_WINDOW) { + QWindow *win = interface->window(); + point -= QPoint(win->x(), win->y()); + } + int offset = interface->textInterface()->offsetAtPoint(point); + sendReply(connection, message, offset); + } else if (function == QLatin1String("GetRangeExtents")) { + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + uint coordType = message.arguments().at(2).toUInt(); + connection.send(message.createReply(getRangeExtents(interface, startOffset, endOffset, coordType))); + } else if (function == QLatin1String("GetSelection")) { + int selectionNum = message.arguments().at(0).toInt(); + int start, end; + interface->textInterface()->selection(selectionNum, &start, &end); + if (start < 0) + start = end = interface->textInterface()->cursorPosition(); + QVariantList sel; + sel << start << end; + connection.send(message.createReply(sel)); + } else if (function == QLatin1String("GetText")) { + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + if (endOffset == -1) // AT-SPI uses -1 to signal all characters + endOffset = interface->textInterface()->characterCount(); + sendReply(connection, message, interface->textInterface()->text(startOffset, endOffset)); + } else if (function == QLatin1String("GetTextAfterOffset")) { + int offset = message.arguments().at(0).toInt(); + int type = message.arguments().at(1).toUInt(); + int startOffset, endOffset; + QString text = interface->textInterface()->textAfterOffset(offset, qAccessibleBoundaryType(type), &startOffset, &endOffset); + QVariantList ret; + ret << text << startOffset << endOffset; + connection.send(message.createReply(ret)); + } else if (function == QLatin1String("GetTextAtOffset")) { + int offset = message.arguments().at(0).toInt(); + int type = message.arguments().at(1).toUInt(); + int startOffset, endOffset; + QString text = interface->textInterface()->textAtOffset(offset, qAccessibleBoundaryType(type), &startOffset, &endOffset); + QVariantList ret; + ret << text << startOffset << endOffset; + connection.send(message.createReply(ret)); + } else if (function == QLatin1String("GetTextBeforeOffset")) { + int offset = message.arguments().at(0).toInt(); + int type = message.arguments().at(1).toUInt(); + int startOffset, endOffset; + QString text = interface->textInterface()->textBeforeOffset(offset, qAccessibleBoundaryType(type), &startOffset, &endOffset); + QVariantList ret; + ret << text << startOffset << endOffset; + connection.send(message.createReply(ret)); + } else if (function == QLatin1String("RemoveSelection")) { + int selectionNum = message.arguments().at(0).toInt(); + interface->textInterface()->removeSelection(selectionNum); + sendReply(connection, message, true); + } else if (function == QLatin1String("SetCaretOffset")) { + int offset = message.arguments().at(0).toInt(); + interface->textInterface()->setCursorPosition(offset); + sendReply(connection, message, true); + } else if (function == QLatin1String("SetSelection")) { + int selectionNum = message.arguments().at(0).toInt(); + int startOffset = message.arguments().at(1).toInt(); + int endOffset = message.arguments().at(2).toInt(); + interface->textInterface()->setSelection(selectionNum, startOffset, endOffset); + sendReply(connection, message, true); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +QAccessible2::BoundaryType AtSpiAdaptor::qAccessibleBoundaryType(int atspiTextBoundaryType) const +{ + switch (atspiTextBoundaryType) { + case ATSPI_TEXT_BOUNDARY_CHAR: + return QAccessible2::CharBoundary; + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + return QAccessible2::WordBoundary; + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + return QAccessible2::SentenceBoundary; + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + return QAccessible2::LineBoundary; + } + Q_ASSERT_X(0, "", "Requested invalid boundary type."); + return QAccessible2::CharBoundary; +} + +// FIXME all attribute methods below should share code +QVariantList AtSpiAdaptor::getAttributes(const QAIPointer &interface, int offset, bool includeDefaults) const +{ + Q_UNUSED(includeDefaults); + + QSpiAttributeSet set; + int startOffset; + int endOffset; + + QString joined = interface->textInterface()->attributes(offset, &startOffset, &endOffset); + QStringList attributes = joined.split (QLatin1Char(';'), QString::SkipEmptyParts, Qt::CaseSensitive); + foreach (const QString &attr, attributes) { + QStringList items; + items = attr.split(QLatin1Char(':'), QString::SkipEmptyParts, Qt::CaseSensitive); + set[items[0]] = items[1]; + } + + QVariantList list; + list << QVariant::fromValue(set) << startOffset << endOffset; + + return list; +} + +QVariantList AtSpiAdaptor::getAttributeValue(const QAIPointer &interface, int offset, const QString &attributeName) const +{ + QString mapped; + QString joined; + QStringList attributes; + QSpiAttributeSet map; + int startOffset; + int endOffset; + bool defined; + + joined = interface->textInterface()->attributes(offset, &startOffset, &endOffset); + attributes = joined.split (QLatin1Char(';'), QString::SkipEmptyParts, Qt::CaseSensitive); + foreach (const QString& attr, attributes) { + QStringList items; + items = attr.split(QLatin1Char(':'), QString::SkipEmptyParts, Qt::CaseSensitive); + map[items[0]] = items[1]; + } + mapped = map[attributeName]; + defined = mapped.isEmpty(); + QVariantList list; + list << mapped << startOffset << endOffset << defined; + return list; +} + +QRect AtSpiAdaptor::getCharacterExtents(const QAIPointer &interface, int offset, uint coordType) const +{ + QRect rect = interface->textInterface()->characterRect(offset); + + if (coordType == ATSPI_COORD_TYPE_WINDOW) + rect = translateRectToWindowCoordinates(interface, rect); + + return rect; +} + +QRect AtSpiAdaptor::getRangeExtents(const QAIPointer &interface, + int startOffset, int endOffset, uint coordType) const +{ + if (endOffset == -1) + endOffset = interface->textInterface()->characterCount(); + + QAccessibleTextInterface *textInterface = interface->textInterface(); + if (endOffset <= startOffset || !textInterface) + return QRect(); + + QRect rect = textInterface->characterRect(startOffset); + for (int i=startOffset + 1; i <= endOffset; i++) + rect = rect | textInterface->characterRect(i); + + // relative to window + if (coordType == ATSPI_COORD_TYPE_WINDOW) + rect = translateRectToWindowCoordinates(interface, rect); + + return rect; +} + +QRect AtSpiAdaptor::translateRectToWindowCoordinates(const QAIPointer &interface, const QRect &rect) +{ + QAIPointer window = getWindow(interface); + if (window) + return rect.translated(-window->rect().x(), -window->rect().y()); + + return rect; +} + + +// Editable Text interface +static QString textForRange(QAccessibleInterface *accessible, int startOffset, int endOffset) +{ + if (QAccessibleTextInterface *textIface = accessible->textInterface()) { + if (endOffset == -1) + endOffset = textIface->characterCount(); + return textIface->text(startOffset, endOffset); + } + QString txt = accessible->text(QAccessible::Value); + if (endOffset == -1) + endOffset = txt.length(); + return txt.mid(startOffset, endOffset - startOffset); +} + +static void replaceTextFallback(QAccessibleInterface *accessible, long startOffset, long endOffset, const QString &txt) +{ + QString t = textForRange(accessible, 0, -1); + if (endOffset == -1) + endOffset = t.length(); + if (endOffset - startOffset == 0) + t.insert(startOffset, txt); + else + t.replace(startOffset, endOffset - startOffset, txt); + accessible->setText(QAccessible::Value, t); +} + +bool AtSpiAdaptor::editableTextInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (function == QLatin1String("CopyText")) { +#ifndef QT_NO_CLIPBOARD + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + const QString t = textForRange(interface.data(), startOffset, endOffset); + QGuiApplication::clipboard()->setText(t); +#endif + connection.send(message.createReply(true)); + } else if (function == QLatin1String("CutText")) { +#ifndef QT_NO_CLIPBOARD + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + const QString t = textForRange(interface.data(), startOffset, endOffset); + if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) + editableTextIface->deleteText(startOffset, endOffset); + else + replaceTextFallback(interface.data(), startOffset, endOffset, QString()); + QGuiApplication::clipboard()->setText(t); +#endif + connection.send(message.createReply(true)); + } else if (function == QLatin1String("DeleteText")) { + int startOffset = message.arguments().at(0).toInt(); + int endOffset = message.arguments().at(1).toInt(); + if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) + editableTextIface->deleteText(startOffset, endOffset); + else + replaceTextFallback(interface.data(), startOffset, endOffset, QString()); + connection.send(message.createReply(true)); + } else if (function == QLatin1String("InsertText")) { + int position = message.arguments().at(0).toInt(); + QString text = message.arguments().at(1).toString(); + int length = message.arguments().at(2).toInt(); + text.resize(length); + if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) + editableTextIface->insertText(position, text); + else + replaceTextFallback(interface.data(), position, position, text); + connection.send(message.createReply(true)); + } else if (function == QLatin1String("PasteText")) { +#ifndef QT_NO_CLIPBOARD + int position = message.arguments().at(0).toInt(); + const QString txt = QGuiApplication::clipboard()->text(); + if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) + editableTextIface->insertText(position, txt); + else + replaceTextFallback(interface.data(), position, position, txt); +#endif + connection.send(message.createReply(true)); + } else if (function == QLatin1String("SetTextContents")) { + QString newContents = message.arguments().at(0).toString(); + interface->editableTextInterface()->replaceText(0, interface->textInterface()->characterCount(), newContents); + connection.send(message.createReply(true)); + } else if (function == QLatin1String("")) { + connection.send(message.createReply()); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +// Value interface +bool AtSpiAdaptor::valueInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (0) { + } else if (function == QLatin1String("SetCurrentValue")) { + QDBusVariant v = message.arguments().at(2).value(); + double value = v.variant().toDouble(); + //Temporary fix + //See https://bugzilla.gnome.org/show_bug.cgi?id=652596 + interface->valueInterface()->setCurrentValue(value); + connection.send(message.createReply()); // FIXME is the reply needed? + } else if (function == QLatin1String("GetCurrentValue")) { + bool success; + double val = interface->valueInterface()->currentValue().toDouble(&success); + if (!success) + qWarning ("AtSpiAdaptor::valueInterface: Could not convert current value to double."); + connection.send(message.createReply( + QVariant::fromValue(QDBusVariant(QVariant::fromValue(val))))); + } else if (function == QLatin1String("GetMaximumValue")) { + bool success; + double val = interface->valueInterface()->maximumValue().toDouble(&success); + if (!success) + qWarning ("AtSpiAdaptor::valueInterface: Could not convert current value to double."); + connection.send(message.createReply( + QVariant::fromValue(QDBusVariant(QVariant::fromValue(val))))); + } else if (function == QLatin1String("GetMinimumIncrement")) { + connection.send(message.createReply( + QVariant::fromValue(QDBusVariant(QVariant::fromValue(0.0))))); + } else if (function == QLatin1String("GetMinimumValue")) { + bool success; + double val = interface->valueInterface()->minimumValue().toDouble(&success); + if (!success) + qWarning ("AtSpiAdaptor::valueInterface: Could not convert current value to double."); + connection.send(message.createReply( + QVariant::fromValue(QDBusVariant(QVariant::fromValue(val))))); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +// Table interface +bool AtSpiAdaptor::tableInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) +{ + if (!(interface->tableInterface() || interface->tableCellInterface())) { + qWarning() << "WARNING Qt AtSpiAdaptor: Could not find table interface for: " << message.path() << interface; + return false; + } + + if (0) { + // properties + } else if (function == QLatin1String("GetCaption")) { + QAIPointer captionInterface= QAIPointer(interface->tableInterface()->caption()); + if (captionInterface) { + QSpiObjectReference ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(captionInterface))); + sendReply(connection, message, QVariant::fromValue(ref)); + } else { + sendReply(connection, message, QVariant::fromValue( + QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))); + } + } else if (function == QLatin1String("GetNColumns")) { + connection.send(message.createReply(QVariant::fromValue(QDBusVariant( + QVariant::fromValue(interface->tableInterface()->columnCount()))))); + } else if (function == QLatin1String("GetNRows")) { + connection.send(message.createReply(QVariant::fromValue(QDBusVariant( + QVariant::fromValue(interface->tableInterface()->rowCount()))))); + } else if (function == QLatin1String("GetNSelectedColumns")) { + connection.send(message.createReply(QVariant::fromValue(QDBusVariant( + QVariant::fromValue(interface->tableInterface()->selectedColumnCount()))))); + } else if (function == QLatin1String("GetNSelectedRows")) { + connection.send(message.createReply(QVariant::fromValue(QDBusVariant( + QVariant::fromValue(interface->tableInterface()->selectedRowCount()))))); + } else if (function == QLatin1String("GetSummary")) { + QAIPointer summary = interface->tableInterface() ? QAIPointer(interface->tableInterface()->summary()) : QAIPointer(0); + QSpiObjectReference ref(connection, QDBusObjectPath(pathForInterface(summary))); + connection.send(message.createReply(QVariant::fromValue(QDBusVariant(QVariant::fromValue(ref))))); + } else if (function == QLatin1String("GetAccessibleAt")) { + int row = message.arguments().at(0).toInt(); + int column = message.arguments().at(1).toInt(); + Q_ASSERT(interface->tableInterface()); + Q_ASSERT(row >= 0); + Q_ASSERT(column >= 0); + Q_ASSERT(row < interface->tableInterface()->rowCount()); + Q_ASSERT(column < interface->tableInterface()->columnCount()); + + QSpiObjectReference ref; + QAIPointer cell(interface->tableInterface()->cellAt(row, column)); + if (cell) { + ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(cell))); + } else { + qWarning() << "WARNING: no cell interface returned for " << interface->object() << row << column; + ref = QSpiObjectReference(); + } + connection.send(message.createReply(QVariant::fromValue(ref))); + + } else if (function == QLatin1String("GetIndexAt")) { + int row = message.arguments().at(0).toInt(); + int column = message.arguments().at(1).toInt(); + QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column); + if (!cell) { + qWarning() << "WARNING: AtSpiAdaptor::GetIndexAt(" << row << "," << column << ") did not find a cell. " << interface; + return false; + } + int index = interface->indexOfChild(cell); + qDebug() << "QSpiAdaptor::GetIndexAt row:" << row << " col:" << column << " logical index:" << index; + Q_ASSERT(index > 0); + delete cell; + connection.send(message.createReply(index)); + } else if ((function == QLatin1String("GetColumnAtIndex")) || (function == QLatin1String("GetRowAtIndex"))) { + int index = message.arguments().at(0).toInt(); + int ret = -1; + if (index >= 0) { + QAIPointer cell = QAIPointer(interface->child(index)); + if (cell) { + if (function == QLatin1String("GetColumnAtIndex")) { + if (cell->role() == QAccessible::ColumnHeader) { + ret = index; + } else if (cell->role() == QAccessible::RowHeader) { + ret = -1; + } else { + if (!cell->tableCellInterface()) { + qWarning() << "WARNING: AtSpiAdaptor::" << function << " No table cell interface: " << cell; + return false; + } + ret = cell->tableCellInterface()->columnIndex(); + } + } else { + if (cell->role() == QAccessible::ColumnHeader) { + ret = -1; + } else if (cell->role() == QAccessible::RowHeader) { + ret = index % interface->tableInterface()->columnCount(); + } else { + if (!cell->tableCellInterface()) { + qWarning() << "WARNING: AtSpiAdaptor::" << function << " No table cell interface: " << cell; + return false; + } + ret = cell->tableCellInterface()->rowIndex(); + } + } + } else { + qWarning() << "WARNING: AtSpiAdaptor::" << function << " No cell at index: " << index << interface; + return false; + } + } + connection.send(message.createReply(ret)); + + } else if (function == QLatin1String("GetColumnDescription")) { + int column = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->columnDescription(column))); + } else if (function == QLatin1String("GetRowDescription")) { + int row = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->rowDescription(row))); + + + + } else if (function == QLatin1String("GetRowColumnExtentsAtIndex")) { + int index = message.arguments().at(0).toInt(); + bool success = false; + + int row, col, rowExtents, colExtents; + bool isSelected; + + int cols = interface->tableInterface()->columnCount(); + row = index/cols; + col = index%cols; + QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, col)->tableCellInterface(); + if (cell) { + cell->rowColumnExtents(&row, &col, &rowExtents, &colExtents, &isSelected); + success = true; + delete cell; + } + + QVariantList list; + list << success << row << col << rowExtents << colExtents << isSelected; + connection.send(message.createReply(list)); + + } else if (function == QLatin1String("GetColumnExtentAt")) { + 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())); + + } else if (function == QLatin1String("GetRowExtentAt")) { + 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())); + + } else if (function == QLatin1String("GetColumnHeader")) { + int column = message.arguments().at(0).toInt(); + QSpiObjectReference ref; + + QAIPointer cell(interface->tableInterface()->cellAt(0, column)); + if (cell && cell->tableCellInterface()) { + QList header = cell->tableCellInterface()->columnHeaderCells(); + if (header.size() > 0) { + ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(QAIPointer(header.takeAt(0))))); + qDeleteAll(header); + } + } + connection.send(message.createReply(QVariant::fromValue(ref))); + + } else if (function == QLatin1String("GetRowHeader")) { + int row = message.arguments().at(0).toInt(); + QSpiObjectReference ref; + QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, 0)->tableCellInterface(); + if (cell) { + QList header = cell->rowHeaderCells(); + delete cell; + if (header.size() > 0) { + ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(QAIPointer(header.takeAt(0))))); + qDeleteAll(header); + } + } + connection.send(message.createReply(QVariant::fromValue(ref))); + + } else if (function == QLatin1String("GetSelectedColumns")) { + connection.send(message.createReply(QVariant::fromValue(interface->tableInterface()->selectedColumns()))); + } else if (function == QLatin1String("GetSelectedRows")) { + connection.send(message.createReply(QVariant::fromValue(interface->tableInterface()->selectedRows()))); + } else if (function == QLatin1String("IsColumnSelected")) { + int column = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->isColumnSelected(column))); + } else if (function == QLatin1String("IsRowSelected")) { + int row = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->isRowSelected(row))); + } else if (function == QLatin1String("IsSelected")) { + 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())); + delete cell; + } else if (function == QLatin1String("AddColumnSelection")) { + int column = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->selectColumn(column))); + } else if (function == QLatin1String("AddRowSelection")) { + int row = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->selectRow(row))); + } else if (function == QLatin1String("RemoveColumnSelection")) { + int column = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->unselectColumn(column))); + } else if (function == QLatin1String("RemoveRowSelection")) { + int row = message.arguments().at(0).toInt(); + connection.send(message.createReply(interface->tableInterface()->unselectRow(row))); + } else { + qWarning() << "WARNING: AtSpiAdaptor::handleMessage does not implement " << function << message.path(); + return false; + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/atspiadaptor_p.h b/src/platformsupport/linuxaccessibility/atspiadaptor_p.h new file mode 100644 index 0000000000..a649784909 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/atspiadaptor_p.h @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef ATSPIADAPTOR_H +#define ATSPIADAPTOR_H + +#include + +#include +#include +#include +#include + +#include "dbusconnection_p.h" +#include "struct_marshallers_p.h" + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class QAccessibleInterface; +class QSpiAccessibleInterface; +class QSpiApplicationAdaptor; + +typedef QSharedPointer QAIPointer; + +class AtSpiAdaptor :public QDBusVirtualObject +{ + Q_OBJECT + +public: + explicit AtSpiAdaptor(DBusConnection *connection, QObject *parent = 0); + ~AtSpiAdaptor(); + + void registerApplication(); + QString introspect(const QString &path) const; + bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection); + void notify(QAccessibleEvent *event); + + void setInitialized(bool init); + +public Q_SLOTS: + void eventListenerRegistered(const QString &bus, const QString &path); + void eventListenerDeregistered(const QString &bus, const QString &path); + void windowActivated(QObject* window, bool active); + +private: + void updateEventListeners(); + void setBitFlag(const QString &flag); + + // sending messages + QVariantList packDBusSignalArguments(const QString &type, int data1, int data2, const QVariant &variantData) const; + bool sendDBusSignal(const QString &path, const QString &interface, const QString &name, const QVariantList &arguments) const; + QVariant variantForPath(const QString &path) const; + + void sendFocusChanged(const QAIPointer &interface) const; + void notifyAboutCreation(const QAIPointer &interface) const; + void notifyAboutDestruction(const QAIPointer &interface) const; + + // handlers for the different accessible interfaces + bool applicationInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool accessibleInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool componentInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool actionInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool textInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool editableTextInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool valueInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + bool tableInterface(const QAIPointer &interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection); + + void sendReply(const QDBusConnection &connection, const QDBusMessage &message, const QVariant &argument) const; + + QAIPointer interfaceFromPath(const QString& dbusPath) const; + QString pathForInterface(const QAIPointer &interface, bool inDestructor = false) const; + QString pathForObject(QObject *object) const; + + // accessible helper functions + AtspiRole getRole(const QAIPointer &interface) const; + QSpiRelationArray relationSet(const QAIPointer &interface, const QDBusConnection &connection) const; + QStringList accessibleInterfaces(const QAIPointer &interface) const; + + // component helper functions + static QRect getExtents(const QAIPointer &interface, uint coordType); + static QRect translateRectToWindowCoordinates(const QAIPointer &interface, const QRect &rect); + + // action helper functions + QSpiActionArray getActions(QAccessibleActionInterface* interface) const; + + // text helper functions + QVariantList getAttributes(const QAIPointer &, int offset, bool includeDefaults) const; + QVariantList getAttributeValue(const QAIPointer &, int offset, const QString &attributeName) const; + QRect getCharacterExtents(const QAIPointer &, int offset, uint coordType) const; + QRect getRangeExtents(const QAIPointer &, int startOffset, int endOffset, uint coordType) const; + QAccessible2::BoundaryType qAccessibleBoundaryType(int atspiTextBoundaryType) const; + static bool inheritsQAction(QObject *object); + + // private vars + QSpiObjectReference accessibilityRegistry; + DBusConnection *m_dbus; + QSpiApplicationAdaptor *m_applicationAdaptor; + + /// Assigned from the accessibility registry. + int m_applicationId; + bool initialized; + + mutable QHash > m_handledObjects; + + // Bit fields - which updates to send + + // AT-SPI has some events that we do not care about: + // document + // document-load-complete + // document-load-stopped + // document-reload + uint sendFocus : 1; + // mouse abs/rel/button + + // all of object + uint sendObject : 1; + uint sendObject_active_descendant_changed : 1; + uint sendObject_attributes_changed : 1; + uint sendObject_bounds_changed : 1; + uint sendObject_children_changed : 1; +// uint sendObject_children_changed_add : 1; +// uint sendObject_children_changed_remove : 1; + uint sendObject_column_deleted : 1; + uint sendObject_column_inserted : 1; + uint sendObject_column_reordered : 1; + uint sendObject_link_selected : 1; + uint sendObject_model_changed : 1; + uint sendObject_property_change : 1; + uint sendObject_property_change_accessible_description : 1; + uint sendObject_property_change_accessible_name : 1; + uint sendObject_property_change_accessible_parent : 1; + uint sendObject_property_change_accessible_role : 1; + uint sendObject_property_change_accessible_table_caption : 1; + uint sendObject_property_change_accessible_table_column_description : 1; + uint sendObject_property_change_accessible_table_column_header : 1; + uint sendObject_property_change_accessible_table_row_description : 1; + uint sendObject_property_change_accessible_table_row_header : 1; + uint sendObject_property_change_accessible_table_summary : 1; + uint sendObject_property_change_accessible_value : 1; + uint sendObject_row_deleted : 1; + uint sendObject_row_inserted : 1; + uint sendObject_row_reordered : 1; + uint sendObject_selection_changed : 1; + uint sendObject_state_changed : 1; + uint sendObject_text_attributes_changed : 1; + uint sendObject_text_bounds_changed : 1; + uint sendObject_text_caret_moved : 1; + uint sendObject_text_changed : 1; +// uint sendObject_text_changed_delete : 1; +// uint sendObject_text_changed_insert : 1; + uint sendObject_text_selection_changed : 1; + uint sendObject_value_changed : 1; + uint sendObject_visible_data_changed : 1; + + // we don't implement terminal + // terminal-application_changed/charwidth_changed/columncount_changed/line_changed/linecount_changed + uint sendWindow : 1; + uint sendWindow_activate : 1; + uint sendWindow_close: 1; + uint sendWindow_create : 1; + uint sendWindow_deactivate : 1; +// uint sendWindow_desktop_create : 1; +// uint sendWindow_desktop_destroy : 1; + uint sendWindow_lower : 1; + uint sendWindow_maximize : 1; + uint sendWindow_minimize : 1; + uint sendWindow_move : 1; + uint sendWindow_raise : 1; + uint sendWindow_reparent : 1; + uint sendWindow_resize : 1; + uint sendWindow_restore : 1; + uint sendWindow_restyle : 1; + uint sendWindow_shade : 1; + uint sendWindow_unshade : 1; +}; + +QT_END_NAMESPACE +QT_END_HEADER + +#endif diff --git a/src/platformsupport/linuxaccessibility/bridge.cpp b/src/platformsupport/linuxaccessibility/bridge.cpp new file mode 100644 index 0000000000..c36f5a58c8 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/bridge.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "bridge_p.h" + +#include +#include + +#include "atspiadaptor_p.h" + +#include "cache_p.h" +#include "constant_mappings_p.h" +#include "dbusconnection_p.h" +#include "struct_marshallers_p.h" + +#include "deviceeventcontroller_adaptor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSpiAccessibleBridge + + \brief QSpiAccessibleBridge + + QSpiAccessibleBridge +*/ + +QSpiAccessibleBridge::QSpiAccessibleBridge() + : cache(0) +{ + dbusConnection = new DBusConnection(); + if (!dBusConnection().isConnected()) + qWarning() << "Could not connect to dbus."; + + qSpiInitializeStructTypes(); + initializeConstantMappings(); + + /* Create the cache of accessible objects */ + cache = new QSpiDBusCache(dBusConnection(), this); + dec = new DeviceEventControllerAdaptor(this); + + bool reg = dBusConnection().registerObject(QLatin1String(ATSPI_DBUS_PATH_DEC), this, QDBusConnection::ExportAdaptors); + qDebug() << "Registered DEC: " << reg; + + dbusAdaptor = new AtSpiAdaptor(dbusConnection, this); + dBusConnection().registerVirtualObject(QLatin1String(QSPI_OBJECT_PATH_ACCESSIBLE), dbusAdaptor, QDBusConnection::SubPath); + dbusAdaptor->registerApplication(); +} + +QSpiAccessibleBridge::~QSpiAccessibleBridge() +{ + delete dbusConnection; +} // Qt currently doesn't delete plugins. + +QDBusConnection QSpiAccessibleBridge::dBusConnection() const +{ + return dbusConnection->connection(); +} + +void QSpiAccessibleBridge::setRootObject(QAccessibleInterface *interface) +{ + // the interface we get will be for the QApplication object. + // we already cache it in the constructor. + Q_ASSERT(interface->object() == qApp); + dbusAdaptor->setInitialized(true); +} + +void QSpiAccessibleBridge::notifyAccessibilityUpdate(QAccessibleEvent *event) +{ + dbusAdaptor->notify(event); +} + +struct RoleMapping { + QAccessible::Role role; + AtspiRole spiRole; + const char *name; + const char *localizedName; +}; + +static RoleMapping map[] = { + { QAccessible::NoRole, ATSPI_ROLE_INVALID, "invalid", QT_TR_NOOP("invalid role") }, + { QAccessible::TitleBar, ATSPI_ROLE_TEXT, "text", QT_TR_NOOP("title bar") }, + { QAccessible::MenuBar, ATSPI_ROLE_MENU_BAR, "menu bar", QT_TR_NOOP("menu bar") }, + { QAccessible::ScrollBar, ATSPI_ROLE_SCROLL_BAR, "scroll bar", QT_TR_NOOP("scroll bar") }, + { QAccessible::Grip, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("grip") }, + { QAccessible::Sound, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("sound") }, + { QAccessible::Cursor, ATSPI_ROLE_ARROW, "arrow", QT_TR_NOOP("cursor") }, + { QAccessible::Caret, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("caret") }, + { QAccessible::AlertMessage, ATSPI_ROLE_ALERT, "alert", QT_TR_NOOP("alert message") }, + { QAccessible::Window, ATSPI_ROLE_WINDOW, "window", QT_TR_NOOP("window") }, + { QAccessible::Client, ATSPI_ROLE_FILLER, "filler", QT_TR_NOOP("filler") }, + { QAccessible::PopupMenu, ATSPI_ROLE_POPUP_MENU, "popup menu", QT_TR_NOOP("popup menu") }, + { QAccessible::MenuItem, ATSPI_ROLE_MENU_ITEM, "menu item", QT_TR_NOOP("menu item") }, + { QAccessible::ToolTip, ATSPI_ROLE_TOOL_TIP, "tool tip", QT_TR_NOOP("tool tip") }, + { QAccessible::Application, ATSPI_ROLE_APPLICATION, "application", QT_TR_NOOP("application") }, + { QAccessible::Document, ATSPI_ROLE_DOCUMENT_FRAME, "document frame", QT_TR_NOOP("document") }, + { QAccessible::Pane, ATSPI_ROLE_PANEL, "panel", QT_TR_NOOP("pane") }, + { QAccessible::Chart, ATSPI_ROLE_CHART, "chart", QT_TR_NOOP("chart") }, + { QAccessible::Dialog, ATSPI_ROLE_DIALOG, "dialog", QT_TR_NOOP("dialog") }, + { QAccessible::Border, ATSPI_ROLE_FRAME, "frame", QT_TR_NOOP("border") }, + { QAccessible::Grouping, ATSPI_ROLE_PANEL, "panel", QT_TR_NOOP("grouping") }, + { QAccessible::Separator, ATSPI_ROLE_SEPARATOR, "separator", QT_TR_NOOP("separator") }, + { QAccessible::ToolBar, ATSPI_ROLE_TOOL_BAR, "tool bar", QT_TR_NOOP("tool bar") }, + { QAccessible::StatusBar, ATSPI_ROLE_STATUS_BAR, "statusbar", QT_TR_NOOP("status bar") }, + { QAccessible::Table, ATSPI_ROLE_TABLE, "table", QT_TR_NOOP("table") }, + { QAccessible::ColumnHeader, ATSPI_ROLE_TABLE_COLUMN_HEADER, "column header", QT_TR_NOOP("column header") }, + { QAccessible::RowHeader, ATSPI_ROLE_TABLE_ROW_HEADER, "row header", QT_TR_NOOP("row header") }, + { QAccessible::Column, ATSPI_ROLE_TABLE_CELL, "table cell", QT_TR_NOOP("column") }, + { QAccessible::Row, ATSPI_ROLE_TABLE_CELL, "table cell", QT_TR_NOOP("row") }, + { QAccessible::Cell, ATSPI_ROLE_TABLE_CELL, "table cell", QT_TR_NOOP("cell") }, + { QAccessible::Link, ATSPI_ROLE_LINK, "link", QT_TR_NOOP("link") }, + { QAccessible::HelpBalloon, ATSPI_ROLE_DIALOG, "dialog", QT_TR_NOOP("help balloon") }, + { QAccessible::Assistant, ATSPI_ROLE_DIALOG, "dialog", QT_TR_NOOP("assistant") }, + { QAccessible::List, ATSPI_ROLE_LIST, "list", QT_TR_NOOP("list") }, + { QAccessible::ListItem, ATSPI_ROLE_LIST_ITEM, "list item", QT_TR_NOOP("list item") }, + { QAccessible::Tree, ATSPI_ROLE_TREE, "tree", QT_TR_NOOP("tree") }, + { QAccessible::TreeItem, ATSPI_ROLE_TABLE_CELL, "tree item", QT_TR_NOOP("tree item") }, + { QAccessible::PageTab, ATSPI_ROLE_PAGE_TAB, "page tab", QT_TR_NOOP("page tab") }, + { QAccessible::PropertyPage, ATSPI_ROLE_PAGE_TAB, "page tab", QT_TR_NOOP("property page") }, + { QAccessible::Indicator, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("indicator") }, + { QAccessible::Graphic, ATSPI_ROLE_IMAGE, "image", QT_TR_NOOP("graphic") }, + { QAccessible::StaticText, ATSPI_ROLE_LABEL, "label", QT_TR_NOOP("label") }, + { QAccessible::EditableText, ATSPI_ROLE_TEXT, "text", QT_TR_NOOP("text") }, + { QAccessible::PushButton, ATSPI_ROLE_PUSH_BUTTON, "push button", QT_TR_NOOP("push button") }, + { QAccessible::CheckBox, ATSPI_ROLE_CHECK_BOX, "check box", QT_TR_NOOP("check box") }, + { QAccessible::RadioButton, ATSPI_ROLE_RADIO_BUTTON, "radio button", QT_TR_NOOP("radio box") }, + { QAccessible::ComboBox, ATSPI_ROLE_COMBO_BOX, "combo box", QT_TR_NOOP("combo box") }, + { QAccessible::ProgressBar, ATSPI_ROLE_PROGRESS_BAR, "progress bar", QT_TR_NOOP("progress bar") }, + { QAccessible::Dial, ATSPI_ROLE_DIAL, "accelerator label", QT_TR_NOOP("dial") }, + { QAccessible::HotkeyField, ATSPI_ROLE_TEXT, "text", QT_TR_NOOP("hotkey field") }, //FIXME text? + { QAccessible::Slider, ATSPI_ROLE_SLIDER, "slider", QT_TR_NOOP("slider") }, + { QAccessible::SpinBox, ATSPI_ROLE_SPIN_BUTTON, "spin button", QT_TR_NOOP("spin box") }, + { QAccessible::Canvas, ATSPI_ROLE_CANVAS, "canvas", QT_TR_NOOP("canvas") }, + { QAccessible::Animation, ATSPI_ROLE_ANIMATION, "animation", QT_TR_NOOP("animation") }, + { QAccessible::Equation, ATSPI_ROLE_TEXT, "text", QT_TR_NOOP("equation") }, + { QAccessible::ButtonDropDown, ATSPI_ROLE_PUSH_BUTTON, "push button", QT_TR_NOOP("button drop down") }, + { QAccessible::ButtonMenu, ATSPI_ROLE_PUSH_BUTTON, "push button", QT_TR_NOOP("button menu") }, + { QAccessible::ButtonDropGrid, ATSPI_ROLE_PUSH_BUTTON, "push button", QT_TR_NOOP("button drop grid") }, + { QAccessible::Whitespace, ATSPI_ROLE_FILLER, "filler", QT_TR_NOOP("whitespace") }, + { QAccessible::PageTabList, ATSPI_ROLE_PAGE_TAB_LIST, "page tab list", QT_TR_NOOP("page tab list") }, + { QAccessible::Clock, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("clock") }, + { QAccessible::Splitter, ATSPI_ROLE_SPLIT_PANE, "split pane", QT_TR_NOOP("splitter") }, + { QAccessible::LayeredPane, ATSPI_ROLE_LAYERED_PANE, "layered pane", QT_TR_NOOP("layered pane") }, + { QAccessible::UserRole, ATSPI_ROLE_UNKNOWN, "unknown", QT_TR_NOOP("user role") } +}; + +void QSpiAccessibleBridge::initializeConstantMappings() +{ + for (uint i = 0; i < sizeof(map) / sizeof(RoleMapping); ++i) + qSpiRoleMapping.insert(map[i].role, RoleNames(map[i].spiRole, QLatin1String(map[i].name), tr(map[i].localizedName))); +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/bridge_p.h b/src/platformsupport/linuxaccessibility/bridge_p.h new file mode 100644 index 0000000000..4a77fa7984 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/bridge_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QSPIACCESSIBLEBRIDGE_H +#define QSPIACCESSIBLEBRIDGE_H + +#include +#include + +class DeviceEventControllerAdaptor; + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class DBusConnection; +class QSpiDBusCache; +class AtSpiAdaptor; + +class QSpiAccessibleBridge: public QObject, public QPlatformAccessibility +{ + Q_OBJECT +public: + QSpiAccessibleBridge(); + + virtual ~QSpiAccessibleBridge(); + virtual void setRootObject(QAccessibleInterface *obj); + + virtual void notifyAccessibilityUpdate(QAccessibleEvent *event); + QDBusConnection dBusConnection() const; + +private: + void initializeConstantMappings(); + + QSpiDBusCache *cache; + DeviceEventControllerAdaptor *dec; + + AtSpiAdaptor *dbusAdaptor; + + DBusConnection* dbusConnection; + bool initialized; +}; + +QT_END_NAMESPACE +QT_END_HEADER + +#endif diff --git a/src/platformsupport/linuxaccessibility/cache.cpp b/src/platformsupport/linuxaccessibility/cache.cpp new file mode 100644 index 0000000000..1fa325720a --- /dev/null +++ b/src/platformsupport/linuxaccessibility/cache.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "cache_p.h" +#include "cache_adaptor.h" + +#include "bridge_p.h" + +#define QSPI_OBJECT_PATH_CACHE "/org/a11y/atspi/cache" + +QT_BEGIN_NAMESPACE + +/*! + \class QSpiDBusCache + + \brief This class is responsible for the AT-SPI cache interface. + + The idea behind the cache is that starting an application would + result in many dbus calls. The way GTK/Gail/ATK work is that + they create accessibles for all objects on startup. + In order to avoid querying all the objects individually via DBus + they get sent by using the GetItems call of the cache. + + Additionally the AddAccessible and RemoveAccessible signals + are responsible for adding/removing objects from the cache. + + Currently the Qt bridge chooses to ignore these. +*/ + +QSpiDBusCache::QSpiDBusCache(QDBusConnection c, QObject* parent) + : QObject(parent) +{ + new CacheAdaptor(this); + c.registerObject(QLatin1String(QSPI_OBJECT_PATH_CACHE), this, QDBusConnection::ExportAdaptors); +} + +void QSpiDBusCache::emitAddAccessible(const QSpiAccessibleCacheItem& item) +{ + emit AddAccessible(item); +} + +void QSpiDBusCache::emitRemoveAccessible(const QSpiObjectReference& item) +{ + emit RemoveAccessible(item); +} + +QSpiAccessibleCacheArray QSpiDBusCache::GetItems() +{ + QList cacheArray; + return cacheArray; +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/cache_p.h b/src/platformsupport/linuxaccessibility/cache_p.h new file mode 100644 index 0000000000..0e3d8913f6 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/cache_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef Q_SPI_CACHE_H +#define Q_SPI_CACHE_H + +#include +#include "struct_marshallers_p.h" + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class QSpiDBusCache : public QObject +{ + Q_OBJECT + +public: + QSpiDBusCache(QDBusConnection c, QObject* parent = 0); + void emitAddAccessible(const QSpiAccessibleCacheItem& item); + void emitRemoveAccessible(const QSpiObjectReference& item); + +Q_SIGNALS: + void AddAccessible(const QSpiAccessibleCacheItem &nodeAdded); + void RemoveAccessible(const QSpiObjectReference &nodeRemoved); + +public Q_SLOTS: + QSpiAccessibleCacheArray GetItems(); +}; + +QT_END_NAMESPACE +QT_END_HEADER + +#endif /* Q_SPI_CACHE_H */ diff --git a/src/platformsupport/linuxaccessibility/constant_mappings.cpp b/src/platformsupport/linuxaccessibility/constant_mappings.cpp new file mode 100644 index 0000000000..d7587237c0 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/constant_mappings.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "constant_mappings_p.h" + +#include +#include + +// FIXME the assignment of roles is quite arbitrary, at some point go through this list and sort and check that it makes sense +// "calendar" "check menu item" "color chooser" "column header" "dateeditor" "desktop icon" "desktop frame" +// "directory pane" "drawing area" "file chooser" "fontchooser" "frame" "glass pane" "html container" "icon" +// "internal frame" "option pane" "password text" "radio menu item" "root pane" "row header" "scroll pane" +// "tear off menu item" "terminal" "toggle button" "tree table" "unknown" "viewport" "header" "footer" "paragraph" +// "ruler" "autocomplete" "edit bar" "embedded component" "entry" "caption" +// "heading" "page" "section" "redundant object" "form" "input method window" "menu" + +QT_BEGIN_NAMESPACE + +QHash qSpiRoleMapping; + +quint64 spiStatesFromQState(QAccessible::State state) +{ + quint64 spiState = 0; + + setSpiStateBit(&spiState, ATSPI_STATE_EDITABLE); + setSpiStateBit(&spiState, ATSPI_STATE_ENABLED); + setSpiStateBit(&spiState, ATSPI_STATE_SHOWING); + setSpiStateBit(&spiState, ATSPI_STATE_VISIBLE); + setSpiStateBit(&spiState, ATSPI_STATE_SENSITIVE); + + if (state.disabled) { + unsetSpiStateBit(&spiState, ATSPI_STATE_ENABLED); + unsetSpiStateBit(&spiState, ATSPI_STATE_SHOWING); + unsetSpiStateBit(&spiState, ATSPI_STATE_VISIBLE); + unsetSpiStateBit(&spiState, ATSPI_STATE_SENSITIVE); + } + + if (state.selected) + setSpiStateBit(&spiState, ATSPI_STATE_SELECTED); + if (state.focused) + setSpiStateBit(&spiState, ATSPI_STATE_FOCUSED); + if (state.pressed) + setSpiStateBit(&spiState, ATSPI_STATE_PRESSED); + if (state.checked) + setSpiStateBit(&spiState, ATSPI_STATE_CHECKED); + if (state.checkStateMixed) + setSpiStateBit(&spiState, ATSPI_STATE_INDETERMINATE); + if (state.readOnly) + unsetSpiStateBit(&spiState, ATSPI_STATE_EDITABLE); + // if (state.HotTracked) + if (state.defaultButton) + setSpiStateBit(&spiState, ATSPI_STATE_IS_DEFAULT); + if (state.expanded) + setSpiStateBit(&spiState, ATSPI_STATE_EXPANDED); + if (state.collapsed) + setSpiStateBit(&spiState, ATSPI_STATE_COLLAPSED); + if (state.busy) + setSpiStateBit(&spiState, ATSPI_STATE_BUSY); + if (state.marqueed || state.animated) + setSpiStateBit(&spiState, ATSPI_STATE_ANIMATED); + if (state.invisible || state.offscreen) { + unsetSpiStateBit(&spiState, ATSPI_STATE_SHOWING); + unsetSpiStateBit(&spiState, ATSPI_STATE_VISIBLE); + } + if (state.sizeable) + setSpiStateBit(&spiState, ATSPI_STATE_RESIZABLE); + // if (state.Movable) + // if (state.SelfVoicing) + if (state.focusable) + setSpiStateBit(&spiState, ATSPI_STATE_FOCUSABLE); + if (state.selectable) + setSpiStateBit(&spiState, ATSPI_STATE_SELECTABLE); + // if (state.Linked) + if (state.traversed) + setSpiStateBit(&spiState, ATSPI_STATE_VISITED); + if (state.multiSelectable) + setSpiStateBit(&spiState, ATSPI_STATE_MULTISELECTABLE); + if (state.extSelectable) + setSpiStateBit(&spiState, ATSPI_STATE_SELECTABLE); + // if (state.Protected) + // if (state.HasPopup) + if (state.modal) + setSpiStateBit(&spiState, ATSPI_STATE_MODAL); + + // Not implemented in Qt + // if (state.singleLine) + // setSpiStateBit(&spiState, ATSPI_STATE_SINGLE_LINE); + + return spiState; +} + +QSpiUIntList spiStateSetFromSpiStates(quint64 states) +{ + uint low = states & 0xFFFFFFFF; + uint high = (states >> 32) & 0xFFFFFFFF; + + QSpiUIntList stateList; + stateList.append(low); + stateList.append(high); + return stateList; +} + +AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relation) +{ + switch (relation) { + case QAccessible::Label: + return ATSPI_RELATION_LABELLED_BY; + case QAccessible::Labelled: + return ATSPI_RELATION_LABEL_FOR; + case QAccessible::Controller: + return ATSPI_RELATION_CONTROLLED_BY; + case QAccessible::Controlled: + return ATSPI_RELATION_CONTROLLER_FOR; + default: + qWarning() << "Cannot return AT-SPI relation for:" << relation; + } + return ATSPI_RELATION_NULL; +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/constant_mappings_p.h b/src/platformsupport/linuxaccessibility/constant_mappings_p.h new file mode 100644 index 0000000000..e0892336a4 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/constant_mappings_p.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/* + * This file contains AT-SPI constants and mappings between QAccessible + * and AT-SPI constants such as 'role' and 'state' enumerations. + */ + +#ifndef Q_SPI_CONSTANT_MAPPINGS_H +#define Q_SPI_CONSTANT_MAPPINGS_H + +#include "struct_marshallers_p.h" + +#include +#include + + +// interface names from at-spi2-core/atspi/atspi-misc-private.h +#define ATSPI_DBUS_NAME_REGISTRY "org.a11y.atspi.Registry" +#define ATSPI_DBUS_PATH_REGISTRY "/org/a11y/atspi/registry" +#define ATSPI_DBUS_INTERFACE_REGISTRY "org.a11y.atspi.Registry" + +#define ATSPI_DBUS_PATH_ROOT "/org/a11y/atspi/accessible/root" + +#define ATSPI_DBUS_PATH_DEC "/org/a11y/atspi/registry/deviceeventcontroller" +#define ATSPI_DBUS_INTERFACE_DEC "org.a11y.atspi.DeviceEventController" +#define ATSPI_DBUS_INTERFACE_DEVICE_EVENT_LISTENER "org.a11y.atspi.DeviceEventListener" + +#define ATSPI_DBUS_INTERFACE_CACHE "org.a11y.atspi.Cache" +#define ATSPI_DBUS_INTERFACE_ACCESSIBLE "org.a11y.atspi.Accessible" +#define ATSPI_DBUS_INTERFACE_ACTION "org.a11y.atspi.Action" +#define ATSPI_DBUS_INTERFACE_APPLICATION "org.a11y.atspi.Application" +#define ATSPI_DBUS_INTERFACE_COLLECTION "org.a11y.atspi.Collection" +#define ATSPI_DBUS_INTERFACE_COMPONENT "org.a11y.atspi.Component" +#define ATSPI_DBUS_INTERFACE_DOCUMENT "org.a11y.atspi.Document" +#define ATSPI_DBUS_INTERFACE_EDITABLE_TEXT "org.a11y.atspi.EditableText" +#define ATSPI_DBUS_INTERFACE_EVENT_KEYBOARD "org.a11y.atspi.Event.Keyboard" +#define ATSPI_DBUS_INTERFACE_EVENT_MOUSE "org.a11y.atspi.Event.Mouse" +#define ATSPI_DBUS_INTERFACE_EVENT_OBJECT "org.a11y.atspi.Event.Object" +#define ATSPI_DBUS_INTERFACE_HYPERLINK "org.a11y.atspi.Hyperlink" +#define ATSPI_DBUS_INTERFACE_HYPERTEXT "org.a11y.atspi.Hypertext" +#define ATSPI_DBUS_INTERFACE_IMAGE "org.a11y.atspi.Image" +#define ATSPI_DBUS_INTERFACE_SELECTION "org.a11y.atspi.Selection" +#define ATSPI_DBUS_INTERFACE_TABLE "org.a11y.atspi.Table" +#define ATSPI_DBUS_INTERFACE_TEXT "org.a11y.atspi.Text" +#define ATSPI_DBUS_INTERFACE_VALUE "org.a11y.atspi.Value" +#define ATSPI_DBUS_INTERFACE_SOCKET "org.a11y.atspi.Socket" + +// missing from at-spi2-core: +#define ATSPI_DBUS_INTERFACE_EVENT_WINDOW "org.a11y.atspi.Event.Window" +#define ATSPI_DBUS_INTERFACE_EVENT_FOCUS "org.a11y.atspi.Event.Focus" + +#define QSPI_OBJECT_PATH_ACCESSIBLE "/org/a11y/atspi/accessible" +#define QSPI_OBJECT_PATH_PREFIX "/org/a11y/atspi/accessible/" +#define QSPI_OBJECT_PATH_ROOT QSPI_OBJECT_PATH_PREFIX "root" + +#define QSPI_REGISTRY_NAME "org.a11y.atspi.Registry" + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +struct RoleNames { + RoleNames() {} + RoleNames(AtspiRole r, const QString& n, const QString& ln) + :m_spiRole(r), m_name(n), m_localizedName(ln) + {} + + AtspiRole spiRole() const {return m_spiRole;} + QString name() const {return m_name;} + QString localizedName() const {return m_localizedName;} + +private: + AtspiRole m_spiRole; + QString m_name; + QString m_localizedName; +}; + +extern QHash qSpiRoleMapping; +extern QHash qSpiStateMapping; + +inline void setSpiStateBit(quint64* state, AtspiStateType spiState) +{ + *state |= quint64(1) << spiState; +} + +inline void unsetSpiStateBit(quint64* state, AtspiStateType spiState) +{ + *state &= ~(quint64(1) << spiState); +} + +quint64 spiStatesFromQState(QAccessible::State state); +QSpiUIntList spiStateSetFromSpiStates(quint64 states); + +AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relation); + +QT_END_NAMESPACE +QT_END_HEADER + +#endif /* Q_SPI_CONSTANT_MAPPINGS_H */ diff --git a/src/platformsupport/linuxaccessibility/dbusconnection.cpp b/src/platformsupport/linuxaccessibility/dbusconnection.cpp new file mode 100644 index 0000000000..4d9f315952 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/dbusconnection.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#include "dbusconnection_p.h" + +#include +#include + +/*! + \class DBusConnection + + \brief DBusConnection + + DBusConnection +*/ + +QT_BEGIN_NAMESPACE + +/*! + Connects to the accessibility dbus. + + This is usually a different bus from the session bus. +*/ +DBusConnection::DBusConnection() + : dbusConnection(connectDBus()) +{} + +QDBusConnection DBusConnection::connectDBus() +{ + QString address = getAccessibilityBusAddress(); + + if (!address.isEmpty()) { + QDBusConnection c = QDBusConnection::connectToBus(address, QStringLiteral("a11y")); + if (c.isConnected()) { + qDebug() << "Connected to accessibility bus at: " << address; + return c; + } + qWarning("Found Accessibility DBus address but cannot connect. Falling back to session bus."); + } else { + qWarning("Accessibility DBus not found. Falling back to session bus."); + } + + QDBusConnection c = QDBusConnection::sessionBus(); + if (!c.isConnected()) { + qWarning("Could not connect to DBus."); + } + return QDBusConnection::sessionBus(); +} + +QString DBusConnection::getAccessibilityBusAddress() const +{ + QDBusConnection c = QDBusConnection::sessionBus(); + + QDBusMessage m = QDBusMessage::createMethodCall(QLatin1String("org.a11y.Bus"), + QLatin1String("/org/a11y/bus"), + QLatin1String("org.a11y.Bus"), QLatin1String("GetAddress")); + QDBusMessage reply = c.call(m); + if (reply.type() == QDBusMessage::ErrorMessage) { + qWarning() << "Qt at-spi: error getting the accessibility dbus address: " << reply.errorMessage(); + return QString(); + } + + QString busAddress = reply.arguments().at(0).toString(); + qDebug() << "Got bus address: " << busAddress; + return busAddress; +} + +/*! + Returns the DBus connection that got established. +*/ +QDBusConnection DBusConnection::connection() const +{ + return dbusConnection; +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/dbusconnection_p.h b/src/platformsupport/linuxaccessibility/dbusconnection_p.h new file mode 100644 index 0000000000..387c0e09b8 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/dbusconnection_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef DBUSCONNECTION_H +#define DBUSCONNECTION_H + +#include +#include + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class DBusConnection +{ +public: + DBusConnection(); + QDBusConnection connection() const; + +private: + QString getAccessibilityBusAddress() const; + QDBusConnection connectDBus(); + + QDBusConnection dbusConnection; +}; + +QT_END_NAMESPACE +QT_END_HEADER + +#endif // DBUSCONNECTION_H diff --git a/src/platformsupport/linuxaccessibility/linuxaccessibility.pri b/src/platformsupport/linuxaccessibility/linuxaccessibility.pri new file mode 100644 index 0000000000..66d69add1f --- /dev/null +++ b/src/platformsupport/linuxaccessibility/linuxaccessibility.pri @@ -0,0 +1,27 @@ +contains(QT_CONFIG, dbus):contains(QT_CONFIG, xcb):contains(QT_CONFIG, accessibility) { + + PKGCONFIG += atspi-2 + CONFIG += link_pkgconfig + QT += dbus + include(../../3rdparty/atspi2/atspi2.pri) + + INCLUDEPATH += $$PWD + + HEADERS += \ + $$PWD/application_p.h \ + $$PWD/bridge_p.h \ + $$PWD/cache_p.h \ + $$PWD/struct_marshallers_p.h \ + $$PWD/constant_mappings_p.h \ + $$PWD/dbusconnection_p.h \ + $$PWD/atspiadaptor_p.h + + SOURCES += \ + $$PWD/application.cpp \ + $$PWD/bridge.cpp \ + $$PWD/cache.cpp \ + $$PWD/struct_marshallers.cpp \ + $$PWD/constant_mappings.cpp \ + $$PWD/dbusconnection.cpp \ + $$PWD/atspiadaptor.cpp +} diff --git a/src/platformsupport/linuxaccessibility/main.cpp b/src/platformsupport/linuxaccessibility/main.cpp new file mode 100644 index 0000000000..9087286e5e --- /dev/null +++ b/src/platformsupport/linuxaccessibility/main.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#include "bridge.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSpiAccessibleBridgePlugin + + \brief QSpiAccessibleBridgePlugin + + QSpiAccessibleBridgePlugin +*/ + +class QSpiAccessibleBridgePlugin: public QAccessibleBridgePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QAccessibleBridgeFactoryInterface" FILE "linuxaccessibility.json"); +public: + QSpiAccessibleBridgePlugin(QObject *parent = 0); + virtual ~QSpiAccessibleBridgePlugin() {} + + virtual QAccessibleBridge* create(const QString &key); + virtual QStringList keys() const; +}; + +/*! + The contructor of the plugin. + */ +QSpiAccessibleBridgePlugin::QSpiAccessibleBridgePlugin(QObject *parent) +: QAccessibleBridgePlugin(parent) +{ +} + +/*! + Creates a new instance of the QAccessibleBridge plugin. + */ +QAccessibleBridge* QSpiAccessibleBridgePlugin::create(const QString &key) +{ + if (key == "QSPIACCESSIBLEBRIDGE") + return new QSpiAccessibleBridge(); + return 0; +} + +/*! + + */ +QStringList QSpiAccessibleBridgePlugin::keys() const +{ + return QStringList() << "QSPIACCESSIBLEBRIDGE"; +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/struct_marshallers.cpp b/src/platformsupport/linuxaccessibility/struct_marshallers.cpp new file mode 100644 index 0000000000..61e1a763d0 --- /dev/null +++ b/src/platformsupport/linuxaccessibility/struct_marshallers.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "struct_marshallers_p.h" + +#include +#include +#include + +#include "bridge_p.h" + +QT_BEGIN_NAMESPACE + +QSpiObjectReference::QSpiObjectReference() + : path(QDBusObjectPath(ATSPI_DBUS_PATH_NULL)) +{} + +/* QSpiAccessibleCacheArray */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAccessibleCacheItem &item) +{ + argument.beginStructure(); + argument << item.path; + argument << item.application; + argument << item.parent; + argument << item.children; + argument << item.supportedInterfaces; + argument << item.name; + argument << item.role; + argument << item.description; + argument << item.state; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAccessibleCacheItem &item) +{ + argument.beginStructure(); + argument >> item.path; + argument >> item.application; + argument >> item.parent; + argument >> item.children; + argument >> item.supportedInterfaces; + argument >> item.name; + argument >> item.role; + argument >> item.description; + argument >> item.state; + argument.endStructure(); + return argument; +} + +/* QSpiObjectReference */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiObjectReference &address) +{ + argument.beginStructure(); + argument << address.service; + argument << address.path; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiObjectReference &address) +{ + argument.beginStructure(); + argument >> address.service; + argument >> address.path; + argument.endStructure(); + return argument; +} + +/* QSpiAction */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAction &action) +{ + argument.beginStructure(); + argument << action.name; + argument << action.description; + argument << action.keyBinding; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAction &action) +{ + Q_UNUSED(action) + qWarning() << "QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAction &action): implement me!"; + return argument; +} + + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiEventListener &ev) +{ + argument.beginStructure(); + argument << ev.listenerAddress; + argument << ev.eventName; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiEventListener &ev) +{ + argument.beginStructure(); + argument >> ev.listenerAddress; + argument >> ev.eventName; + argument.endStructure(); + return argument; +} + +/* QSpiAppUpdate */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update) { + argument.beginStructure(); + argument << update.type << update.address; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update) { + argument.beginStructure(); + argument >> update.type >> update.address; + argument.endStructure(); + return argument; +} + +/* QSpiRelationArrayEntry */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiRelationArrayEntry &entry) { + argument.beginStructure(); + argument << entry.first << entry.second; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiRelationArrayEntry &entry) { + argument.beginStructure(); + argument >> entry.first >> entry.second; + argument.endStructure(); + return argument; +} + +/* QSpiDeviceEvent */ +/*---------------------------------------------------------------------------*/ + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiDeviceEvent &event) { + argument.beginStructure(); + argument << event.type + << event.id + << event.hardwareCode + << event.modifiers + << event.timestamp + << event.text + << event.isText; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiDeviceEvent &event) { + argument.beginStructure(); + argument >> event.type + >> event.id + >> event.hardwareCode + >> event.modifiers + >> event.timestamp + >> event.text + >> event.isText; + argument.endStructure(); + return argument; +} + +void qSpiInitializeStructTypes() +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/linuxaccessibility/struct_marshallers_p.h b/src/platformsupport/linuxaccessibility/struct_marshallers_p.h new file mode 100644 index 0000000000..f161b4623d --- /dev/null +++ b/src/platformsupport/linuxaccessibility/struct_marshallers_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef Q_SPI_STRUCT_MARSHALLERS_H +#define Q_SPI_STRUCT_MARSHALLERS_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +typedef QList QSpiIntList; +typedef QList QSpiUIntList; + +// FIXME: make this copy on write +struct QSpiObjectReference +{ + QString service; + QDBusObjectPath path; + + QSpiObjectReference(); + QSpiObjectReference(const QDBusConnection& connection, const QDBusObjectPath& path) + : service(connection.baseService()), path(path) {} +}; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiObjectReference &address); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiObjectReference &address); + +typedef QList QSpiObjectReferenceArray; + +struct QSpiAccessibleCacheItem +{ + QSpiObjectReference path; + QSpiObjectReference application; + QSpiObjectReference parent; + QList children; + QStringList supportedInterfaces; + QString name; + uint role; + QString description; + QSpiUIntList state; +}; + +typedef QList QSpiAccessibleCacheArray; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAccessibleCacheItem &item); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAccessibleCacheItem &item); + +struct QSpiAction +{ + QString name; + QString description; + QString keyBinding; +}; + +typedef QList QSpiActionArray; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAction &action); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAction &action); + +struct QSpiEventListener +{ + QString listenerAddress; + QString eventName; +}; + +typedef QList QSpiEventListenerArray; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiEventListener &action); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiEventListener &action); + +typedef QPair < unsigned int, QList < QSpiObjectReference > > QSpiRelationArrayEntry; +typedef QList< QSpiRelationArrayEntry > QSpiRelationArray; + +//a(iisv) +struct QSpiTextRange { + int startOffset; + int endOffset; + QString contents; + QVariant v; +}; +typedef QList QSpiTextRangeList; +typedef QMap QSpiAttributeSet; + +enum QSpiAppUpdateType { + QSPI_APP_UPDATE_ADDED = 0, + QSPI_APP_UPDATE_REMOVED = 1 +}; + +struct QSpiAppUpdate { + int type; /* Is an application added or removed */ + QString address; /* D-Bus address of application added or removed */ +}; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update); + +struct QSpiDeviceEvent { + unsigned int type; + int id; + int hardwareCode; + int modifiers; + int timestamp; + QString text; + bool isText; +}; + +QDBusArgument &operator<<(QDBusArgument &argument, const QSpiDeviceEvent &event); +const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiDeviceEvent &event); + +void qSpiInitializeStructTypes(); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSpiIntList) +Q_DECLARE_METATYPE(QSpiUIntList) +Q_DECLARE_METATYPE(QSpiObjectReference) +Q_DECLARE_METATYPE(QSpiObjectReferenceArray) +Q_DECLARE_METATYPE(QSpiAccessibleCacheItem) +Q_DECLARE_METATYPE(QSpiAccessibleCacheArray) +Q_DECLARE_METATYPE(QSpiAction) +Q_DECLARE_METATYPE(QSpiActionArray) +Q_DECLARE_METATYPE(QSpiEventListener) +Q_DECLARE_METATYPE(QSpiEventListenerArray) +Q_DECLARE_METATYPE(QSpiRelationArrayEntry) +Q_DECLARE_METATYPE(QSpiRelationArray) +Q_DECLARE_METATYPE(QSpiTextRange) +Q_DECLARE_METATYPE(QSpiTextRangeList) +Q_DECLARE_METATYPE(QSpiAttributeSet) +Q_DECLARE_METATYPE(QSpiAppUpdate) +Q_DECLARE_METATYPE(QSpiDeviceEvent) + +QT_END_HEADER +#endif /* Q_SPI_STRUCT_MARSHALLERS_H */ -- cgit v1.2.3