Accessibility Linux: Make dbus registration async

Change-Id: I74043be04f4ee17089353304fdc007a7f22cdea0
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@digia.com>
This commit is contained in:
Frederik Gladhorn 2012-12-13 18:12:40 +01:00 committed by The Qt Project
parent ffeaff9a26
commit 15a3243556
7 changed files with 142 additions and 111 deletions

View File

@ -69,7 +69,7 @@ static bool isDebugging = false;
#define qAtspiDebug if (!::isDebugging); else qDebug #define qAtspiDebug if (!::isDebugging); else qDebug
AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent) AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
: QDBusVirtualObject(parent), m_dbus(connection), initialized(false) : QDBusVirtualObject(parent), m_dbus(connection)
, sendFocus(0) , sendFocus(0)
, sendObject(0) , sendObject(0)
, sendObject_active_descendant_changed(0) , sendObject_active_descendant_changed(0)
@ -132,6 +132,17 @@ AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
m_applicationAdaptor = new QSpiApplicationAdaptor(m_dbus->connection(), this); m_applicationAdaptor = new QSpiApplicationAdaptor(m_dbus->connection(), this);
connect(m_applicationAdaptor, SIGNAL(windowActivated(QObject*,bool)), this, SLOT(windowActivated(QObject*,bool))); connect(m_applicationAdaptor, SIGNAL(windowActivated(QObject*,bool)), this, SLOT(windowActivated(QObject*,bool)));
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
qAtspiDebug() << "Registered event listener change listener: " << success;
#endif
} }
AtSpiAdaptor::~AtSpiAdaptor() AtSpiAdaptor::~AtSpiAdaptor()
@ -605,30 +616,6 @@ QString AtSpiAdaptor::introspect(const QString &path) const
return xml; 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
qAtspiDebug() << "Registered event listener change listener: " << success;
#endif
}
void AtSpiAdaptor::setBitFlag(const QString &flag) void AtSpiAdaptor::setBitFlag(const QString &flag)
{ {
Q_ASSERT(flag.size()); Q_ASSERT(flag.size());
@ -918,9 +905,6 @@ void AtSpiAdaptor::notifyStateChange(const QAIPointer &interface, const QString
*/ */
void AtSpiAdaptor::notify(QAccessibleEvent *event) void AtSpiAdaptor::notify(QAccessibleEvent *event)
{ {
if (!initialized)
return;
switch (event->type()) { switch (event->type()) {
case QAccessible::ObjectCreated: case QAccessible::ObjectCreated:
if (sendObject || sendObject_children_changed) if (sendObject || sendObject_children_changed)

View File

@ -75,8 +75,8 @@ public:
bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection); bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection);
void notify(QAccessibleEvent *event); void notify(QAccessibleEvent *event);
void setInitialized(bool init); void init();
void checkInitializedAndEnabled();
public Q_SLOTS: public Q_SLOTS:
void eventListenerRegistered(const QString &bus, const QString &path); void eventListenerRegistered(const QString &bus, const QString &path);
void eventListenerDeregistered(const QString &bus, const QString &path); void eventListenerDeregistered(const QString &bus, const QString &path);
@ -140,7 +140,6 @@ private:
/// Assigned from the accessibility registry. /// Assigned from the accessibility registry.
int m_applicationId; int m_applicationId;
bool initialized;
mutable QHash<quintptr, QPointer<QObject> > m_handledObjects; mutable QHash<quintptr, QPointer<QObject> > m_handledObjects;

View File

@ -62,24 +62,16 @@ QT_BEGIN_NAMESPACE
*/ */
QSpiAccessibleBridge::QSpiAccessibleBridge() QSpiAccessibleBridge::QSpiAccessibleBridge()
: cache(0) : cache(0), dec(0), dbusAdaptor(0), m_enabled(false)
{ {
dbusConnection = new DBusConnection(); dbusConnection = new DBusConnection();
if (!dBusConnection().isConnected()) connect(dbusConnection, SIGNAL(enabledChanged(bool)), this, SLOT(enabledChanged(bool)));
qWarning() << "Could not connect to dbus."; }
qSpiInitializeStructTypes(); void QSpiAccessibleBridge::enabledChanged(bool enabled)
initializeConstantMappings(); {
m_enabled = enabled;
/* Create the cache of accessible objects */ updateStatus();
cache = new QSpiDBusCache(dBusConnection(), this);
dec = new DeviceEventControllerAdaptor(this);
dBusConnection().registerObject(QLatin1String(ATSPI_DBUS_PATH_DEC), this, QDBusConnection::ExportAdaptors);
dbusAdaptor = new AtSpiAdaptor(dbusConnection, this);
dBusConnection().registerVirtualObject(QLatin1String(QSPI_OBJECT_PATH_ACCESSIBLE), dbusAdaptor, QDBusConnection::SubPath);
dbusAdaptor->registerApplication();
} }
QSpiAccessibleBridge::~QSpiAccessibleBridge() QSpiAccessibleBridge::~QSpiAccessibleBridge()
@ -92,15 +84,30 @@ QDBusConnection QSpiAccessibleBridge::dBusConnection() const
return dbusConnection->connection(); return dbusConnection->connection();
} }
void QSpiAccessibleBridge::setRootObject(QObject *obj) void QSpiAccessibleBridge::updateStatus()
{ {
Q_UNUSED(obj); // create the adaptor to handle everything if we are in enabled state
dbusAdaptor->setInitialized(true); if (!dbusAdaptor && m_enabled) {
qSpiInitializeStructTypes();
initializeConstantMappings();
cache = new QSpiDBusCache(dbusConnection->connection(), this);
dec = new DeviceEventControllerAdaptor(this);
dbusConnection->connection().registerObject(QLatin1String(ATSPI_DBUS_PATH_DEC), this, QDBusConnection::ExportAdaptors);
dbusAdaptor = new AtSpiAdaptor(dbusConnection, this);
dbusConnection->connection().registerVirtualObject(QLatin1String(QSPI_OBJECT_PATH_ACCESSIBLE), dbusAdaptor, QDBusConnection::SubPath);
dbusAdaptor->registerApplication();
}
} }
void QSpiAccessibleBridge::notifyAccessibilityUpdate(QAccessibleEvent *event) void QSpiAccessibleBridge::notifyAccessibilityUpdate(QAccessibleEvent *event)
{ {
dbusAdaptor->notify(event); if (!dbusAdaptor)
return;
if (m_enabled)
dbusAdaptor->notify(event);
} }
struct RoleMapping { struct RoleMapping {

View File

@ -62,21 +62,22 @@ public:
QSpiAccessibleBridge(); QSpiAccessibleBridge();
virtual ~QSpiAccessibleBridge(); virtual ~QSpiAccessibleBridge();
virtual void setRootObject(QObject *obj);
virtual void notifyAccessibilityUpdate(QAccessibleEvent *event); virtual void notifyAccessibilityUpdate(QAccessibleEvent *event);
QDBusConnection dBusConnection() const; QDBusConnection dBusConnection() const;
public Q_SLOTS:
void enabledChanged(bool enabled);
private: private:
void initializeConstantMappings(); void initializeConstantMappings();
void updateStatus();
QSpiDBusCache *cache; QSpiDBusCache *cache;
DeviceEventControllerAdaptor *dec; DeviceEventControllerAdaptor *dec;
AtSpiAdaptor *dbusAdaptor; AtSpiAdaptor *dbusAdaptor;
DBusConnection* dbusConnection; DBusConnection* dbusConnection;
bool initialized; bool m_enabled;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -44,10 +44,16 @@
#include "dbusconnection_p.h" #include "dbusconnection_p.h"
#include <QtDBus/QDBusMessage> #include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusServiceWatcher>
#include <qdebug.h> #include <qdebug.h>
#include <QDBusConnectionInterface>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QString A11Y_SERVICE = QStringLiteral("org.a11y.Bus");
QString A11Y_PATH = QStringLiteral("/org/a11y/bus");
/*! /*!
\class DBusConnection \class DBusConnection
\internal \internal
@ -55,53 +61,81 @@ QT_BEGIN_NAMESPACE
This is usually a different bus from the session bus. This is usually a different bus from the session bus.
*/ */
DBusConnection::DBusConnection() DBusConnection::DBusConnection(QObject *parent)
: dbusConnection(connectDBus()) : QObject(parent), m_a11yConnection(QString()), m_enabled(false)
{}
QDBusConnection DBusConnection::connectDBus()
{ {
QString address = getAccessibilityBusAddress(); // Start monitoring if "org.a11y.Bus" is registered as DBus service.
if (!address.isEmpty()) {
QDBusConnection c = QDBusConnection::connectToBus(address, QStringLiteral("a11y"));
if (c.isConnected())
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(); QDBusConnection c = QDBusConnection::sessionBus();
if (!c.isConnected()) { dbusWatcher = new QDBusServiceWatcher(A11Y_SERVICE, c, QDBusServiceWatcher::WatchForRegistration, this);
qWarning("Could not connect to DBus."); connect(dbusWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(serviceRegistered()));
}
return QDBusConnection::sessionBus(); // If it is registered already, setup a11y right away
if (c.interface()->isServiceRegistered(A11Y_SERVICE))
serviceRegistered();
} }
QString DBusConnection::getAccessibilityBusAddress() const // We have the a11y registry on the session bus.
// Subscribe to updates about a11y enabled state.
// Find out the bus address
void DBusConnection::serviceRegistered()
{ {
// listen to enabled changes
QDBusConnection c = QDBusConnection::sessionBus(); QDBusConnection c = QDBusConnection::sessionBus();
// FXIME check for changes of enabled state
// if (!c.connect(A11Y_SERVICE, A11Y_PATH, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"), this, SLOT(enabledStateChanged(QDBusVariant))))
// qWarning() << "Could not listen to accessibility enabled state changes.";
QDBusMessage m = QDBusMessage::createMethodCall(QLatin1String("org.a11y.Bus"), // check if it's enabled right away
QLatin1String("/org/a11y/bus"), QDBusMessage enabledMessage = QDBusMessage::createMethodCall(A11Y_SERVICE, A11Y_PATH, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Get"));
QLatin1String("org.a11y.Bus"), QLatin1String("GetAddress")); QList<QVariant> args;
QDBusMessage reply = c.call(m); args << QStringLiteral("org.a11y.Status") << QStringLiteral("IsEnabled");
if (reply.type() == QDBusMessage::ErrorMessage) { enabledMessage.setArguments(args);
qWarning() << "Qt at-spi: error getting the accessibility dbus address: " << reply.errorMessage(); c.callWithCallback(enabledMessage, this, SLOT(enabledStateCallback(QDBusVariant)), SLOT(dbusError(QDBusError)));
return QString(); }
void DBusConnection::dbusError(const QDBusError &error)
{
qWarning() << "Accessibility encountered a DBus error:" << error;
}
void DBusConnection::serviceUnregistered()
{
emit enabledChanged(false);
}
void DBusConnection::enabledStateCallback(const QDBusVariant &enabled)
{
m_enabled = enabled.variant().toBool();
if (m_a11yConnection.isConnected()) {
emit enabledChanged(m_enabled);
} else {
QDBusConnection c = QDBusConnection::sessionBus();
QDBusMessage m = QDBusMessage::createMethodCall(QLatin1String("org.a11y.Bus"),
QLatin1String("/org/a11y/bus"),
QLatin1String("org.a11y.Bus"), QLatin1String("GetAddress"));
c.callWithCallback(m, this, SLOT(connectA11yBus(QString)), SLOT(dbusError(QDBusError)));
} }
}
QString busAddress = reply.arguments().at(0).toString(); void DBusConnection::connectA11yBus(const QString &address)
return busAddress; {
if (address.isEmpty()) {
qWarning("Could not find Accessibility DBus address.");
return;
}
m_a11yConnection = QDBusConnection(QDBusConnection::connectToBus(address, QStringLiteral("a11y")));
if (m_enabled)
emit enabledChanged(true);
} }
/*! /*!
Returns the DBus connection that got established. Returns the DBus connection that got established.
Or an invalid connection if not yet connected.
*/ */
QDBusConnection DBusConnection::connection() const QDBusConnection DBusConnection::connection() const
{ {
return dbusConnection; return m_a11yConnection;
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -45,21 +45,41 @@
#include <QtCore/QString> #include <QtCore/QString>
#include <QtDBus/QDBusConnection> #include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusVariant>
QT_BEGIN_HEADER QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class DBusConnection class QDBusServiceWatcher;
class DBusConnection : public QObject
{ {
Q_OBJECT
public: public:
DBusConnection(); DBusConnection(QObject *parent = 0);
QDBusConnection connection() const; QDBusConnection connection() const;
bool isEnabled() const { return m_enabled; }
Q_SIGNALS:
// Emitted when the global accessibility status changes to enabled
void enabledChanged(bool enabled);
private Q_SLOTS:
void serviceRegistered();
void serviceUnregistered();
void enabledStateCallback(const QDBusVariant &enabled);
// void enabledStateChanged(const QDBusVariant &);
void connectA11yBus(const QString &address);
void dbusError(const QDBusError &error);
private: private:
QString getAccessibilityBusAddress() const; QString getAccessibilityBusAddress() const;
QDBusConnection connectDBus();
QDBusConnection dbusConnection; QDBusServiceWatcher *dbusWatcher;
QDBusConnection m_a11yConnection;
bool m_enabled;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -67,11 +67,8 @@ class AccessibleTestWindow : public QWidget
public: public:
AccessibleTestWindow() AccessibleTestWindow()
{ {
DBusConnection c;
m_address = c.connection().baseService().toLatin1().data();
new QHBoxLayout(this); new QHBoxLayout(this);
} }
QString dbusAddress() const { return m_address; }
void addWidget(QWidget* widget) void addWidget(QWidget* widget)
{ {
@ -85,10 +82,6 @@ public:
qDeleteAll(children()); qDeleteAll(children());
new QHBoxLayout(this); new QHBoxLayout(this);
} }
private:
QString m_address;
QString m_bus;
}; };
@ -116,7 +109,6 @@ private:
AccessibleTestWindow *m_window; AccessibleTestWindow *m_window;
QString bus;
QString address; QString address;
QDBusInterface *root; // the root object on dbus (for the app) QDBusInterface *root; // the root object on dbus (for the app)
QDBusInterface *rootApplication; QDBusInterface *rootApplication;
@ -129,10 +121,12 @@ private:
QStringList tst_QAccessibilityLinux::getChildren(QDBusInterface *interface) QStringList tst_QAccessibilityLinux::getChildren(QDBusInterface *interface)
{ {
QSpiObjectReferenceArray list; QSpiObjectReferenceArray list;
interface->call(QDBus::Block, "GetChildren").arguments().first().value<QDBusArgument>() >> list; const QList<QVariant> args = interface->call(QDBus::Block, "GetChildren").arguments();
Q_ASSERT(args.size() == 1);
Q_ASSERT(args.first().isValid());
args.first().value<QDBusArgument>() >> list;
Q_ASSERT(interface->property("ChildCount").toInt() == list.count()); Q_ASSERT(interface->property("ChildCount").toInt() == list.count());
QStringList children; QStringList children;
Q_FOREACH (const QSpiObjectReference &ref, list) Q_FOREACH (const QSpiObjectReference &ref, list)
children << ref.path.path(); children << ref.path.path();
@ -164,26 +158,18 @@ QDBusInterface *tst_QAccessibilityLinux::getInterface(const QString &path, const
void tst_QAccessibilityLinux::initTestCase() void tst_QAccessibilityLinux::initTestCase()
{ {
// Oxygen style creates many extra items, it's simply unusable here // Oxygen style creates many extra items, it's simply unusable here
qDebug() << "Using fusion style...";
qApp->setStyle("fusion"); qApp->setStyle("fusion");
qApp->setApplicationName("tst_QAccessibilityLinux app"); qApp->setApplicationName("tst_QAccessibilityLinux app");
dbus = DBusConnection();
QTRY_VERIFY(dbus.isEnabled());
QTRY_VERIFY(dbus.connection().isConnected());
address = dbus.connection().baseService().toLatin1().data();
m_window = new AccessibleTestWindow(); m_window = new AccessibleTestWindow();
m_window->show(); m_window->show();
// this has the side-effect of immediately activating accessibility
qDebug() << "Explicitly activating accessibility...";
delete QAccessible::queryAccessibleInterface(m_window);
QTest::qWaitForWindowExposed(m_window); QTest::qWaitForWindowExposed(m_window);
address = m_window->dbusAddress();
registerDbus(); registerDbus();
QStringList appChildren = getChildren(root);
QString window = appChildren.at(0);
mainWindow = getInterface(window, "org.a11y.atspi.Accessible");
} }
void tst_QAccessibilityLinux::cleanupTestCase() void tst_QAccessibilityLinux::cleanupTestCase()