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
AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
: QDBusVirtualObject(parent), m_dbus(connection), initialized(false)
: QDBusVirtualObject(parent), m_dbus(connection)
, sendFocus(0)
, sendObject(0)
, sendObject_active_descendant_changed(0)
@ -132,6 +132,17 @@ AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
m_applicationAdaptor = new QSpiApplicationAdaptor(m_dbus->connection(), this);
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()
@ -605,30 +616,6 @@ QString AtSpiAdaptor::introspect(const QString &path) const
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)
{
Q_ASSERT(flag.size());
@ -918,9 +905,6 @@ void AtSpiAdaptor::notifyStateChange(const QAIPointer &interface, const QString
*/
void AtSpiAdaptor::notify(QAccessibleEvent *event)
{
if (!initialized)
return;
switch (event->type()) {
case QAccessible::ObjectCreated:
if (sendObject || sendObject_children_changed)

View File

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

View File

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

View File

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

View File

@ -44,10 +44,16 @@
#include "dbusconnection_p.h"
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusServiceWatcher>
#include <qdebug.h>
#include <QDBusConnectionInterface>
QT_BEGIN_NAMESPACE
QString A11Y_SERVICE = QStringLiteral("org.a11y.Bus");
QString A11Y_PATH = QStringLiteral("/org/a11y/bus");
/*!
\class DBusConnection
\internal
@ -55,53 +61,81 @@ QT_BEGIN_NAMESPACE
This is usually a different bus from the session bus.
*/
DBusConnection::DBusConnection()
: dbusConnection(connectDBus())
{}
QDBusConnection DBusConnection::connectDBus()
DBusConnection::DBusConnection(QObject *parent)
: QObject(parent), m_a11yConnection(QString()), m_enabled(false)
{
QString address = getAccessibilityBusAddress();
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.");
}
// Start monitoring if "org.a11y.Bus" is registered as DBus service.
QDBusConnection c = QDBusConnection::sessionBus();
if (!c.isConnected()) {
qWarning("Could not connect to DBus.");
}
return QDBusConnection::sessionBus();
dbusWatcher = new QDBusServiceWatcher(A11Y_SERVICE, c, QDBusServiceWatcher::WatchForRegistration, this);
connect(dbusWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(serviceRegistered()));
// 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();
// 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"),
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();
// check if it's enabled right away
QDBusMessage enabledMessage = QDBusMessage::createMethodCall(A11Y_SERVICE, A11Y_PATH, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Get"));
QList<QVariant> args;
args << QStringLiteral("org.a11y.Status") << QStringLiteral("IsEnabled");
enabledMessage.setArguments(args);
c.callWithCallback(enabledMessage, this, SLOT(enabledStateCallback(QDBusVariant)), SLOT(dbusError(QDBusError)));
}
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();
return busAddress;
void DBusConnection::connectA11yBus(const QString &address)
{
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.
Or an invalid connection if not yet connected.
*/
QDBusConnection DBusConnection::connection() const
{
return dbusConnection;
return m_a11yConnection;
}
QT_END_NAMESPACE

View File

@ -45,21 +45,41 @@
#include <QtCore/QString>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusVariant>
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
class DBusConnection
class QDBusServiceWatcher;
class DBusConnection : public QObject
{
Q_OBJECT
public:
DBusConnection();
DBusConnection(QObject *parent = 0);
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:
QString getAccessibilityBusAddress() const;
QDBusConnection connectDBus();
QDBusConnection dbusConnection;
QDBusServiceWatcher *dbusWatcher;
QDBusConnection m_a11yConnection;
bool m_enabled;
};
QT_END_NAMESPACE

View File

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