1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | |
5 | |
6 | #include "dbusconnection_p.h" |
7 | |
8 | #include <QtDBus/QDBusMessage> |
9 | #include <QtDBus/QDBusServiceWatcher> |
10 | #include <qdebug.h> |
11 | |
12 | #include <QDBusConnectionInterface> |
13 | #include "bus_interface.h" |
14 | |
15 | #include <QtGui/qguiapplication.h> |
16 | #include <qpa/qplatformnativeinterface.h> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | using namespace Qt::StringLiterals; |
21 | |
22 | /* note: do not change these to QStringLiteral; |
23 | we are unloaded before QtDBus is done using the strings. |
24 | */ |
25 | #define A11Y_SERVICE "org.a11y.Bus"_L1 |
26 | #define A11Y_PATH "/org/a11y/bus"_L1 |
27 | |
28 | /*! |
29 | \class DBusConnection |
30 | \internal |
31 | \brief Connects to the accessibility dbus. |
32 | |
33 | This is usually a different bus from the session bus. |
34 | */ |
35 | DBusConnection::DBusConnection(QObject *parent) |
36 | : QObject(parent), m_a11yConnection(QString()), m_enabled(false) |
37 | { |
38 | // If the bus is explicitly set via env var it overrides everything else. |
39 | QByteArray addressEnv = qgetenv(varName: "AT_SPI_BUS_ADDRESS" ); |
40 | if (!addressEnv.isEmpty()) { |
41 | m_enabled = true; |
42 | connectA11yBus(address: QString::fromLocal8Bit(ba: addressEnv)); |
43 | return; |
44 | } |
45 | |
46 | // Start monitoring if "org.a11y.Bus" is registered as DBus service. |
47 | QDBusConnection c = QDBusConnection::sessionBus(); |
48 | if (!c.isConnected()) { |
49 | return; |
50 | } |
51 | |
52 | dbusWatcher = new QDBusServiceWatcher(A11Y_SERVICE, c, QDBusServiceWatcher::WatchForRegistration, this); |
53 | connect(sender: dbusWatcher, SIGNAL(serviceRegistered(QString)), receiver: this, SLOT(serviceRegistered())); |
54 | |
55 | // If it is registered already, setup a11y right away |
56 | if (c.interface()->isServiceRegistered(A11Y_SERVICE)) |
57 | serviceRegistered(); |
58 | |
59 | // In addition try if there is an xatom exposing the bus address, this allows applications run as root to work |
60 | QString address = getAddressFromXCB(); |
61 | if (!address.isEmpty()) { |
62 | m_enabled = true; |
63 | connectA11yBus(address); |
64 | } |
65 | } |
66 | |
67 | QString DBusConnection::getAddressFromXCB() |
68 | { |
69 | QGuiApplication *app = qobject_cast<QGuiApplication *>(object: QCoreApplication::instance()); |
70 | if (!app) |
71 | return QString(); |
72 | QPlatformNativeInterface *platformNativeInterface = app->platformNativeInterface(); |
73 | QByteArray *addressByteArray = reinterpret_cast<QByteArray*>( |
74 | platformNativeInterface->nativeResourceForIntegration(QByteArrayLiteral("AtspiBus" ))); |
75 | if (addressByteArray) { |
76 | QString address = QString::fromLatin1(ba: *addressByteArray); |
77 | delete addressByteArray; |
78 | return address; |
79 | } |
80 | return QString(); |
81 | } |
82 | |
83 | // We have the a11y registry on the session bus. |
84 | // Subscribe to updates about a11y enabled state. |
85 | // Find out the bus address |
86 | void DBusConnection::serviceRegistered() |
87 | { |
88 | // listen to enabled changes |
89 | QDBusConnection c = QDBusConnection::sessionBus(); |
90 | OrgA11yStatusInterface *a11yStatus = new OrgA11yStatusInterface(A11Y_SERVICE, A11Y_PATH, c, this); |
91 | |
92 | //The variable was introduced because on some embedded platforms there are custom accessibility |
93 | //clients which don't set Status.ScreenReaderEnabled to true. The variable is also useful for |
94 | //debugging. |
95 | static const bool a11yAlwaysOn = qEnvironmentVariableIsSet(varName: "QT_LINUX_ACCESSIBILITY_ALWAYS_ON" ); |
96 | |
97 | bool enabled = a11yAlwaysOn || a11yStatus->screenReaderEnabled() || a11yStatus->isEnabled(); |
98 | |
99 | if (enabled != m_enabled) { |
100 | m_enabled = enabled; |
101 | if (m_a11yConnection.isConnected()) { |
102 | emit enabledChanged(enabled: m_enabled); |
103 | } else { |
104 | QDBusConnection c = QDBusConnection::sessionBus(); |
105 | QDBusMessage m = QDBusMessage::createMethodCall(A11Y_SERVICE, A11Y_PATH, A11Y_SERVICE, |
106 | method: "GetAddress"_L1 ); |
107 | c.callWithCallback(message: m, receiver: this, SLOT(connectA11yBus(QString)), SLOT(dbusError(QDBusError))); |
108 | } |
109 | } |
110 | |
111 | // connect(a11yStatus, ); QtDbus doesn't support notifications for property changes yet |
112 | } |
113 | |
114 | void DBusConnection::serviceUnregistered() |
115 | { |
116 | emit enabledChanged(enabled: false); |
117 | } |
118 | |
119 | void DBusConnection::connectA11yBus(const QString &address) |
120 | { |
121 | if (address.isEmpty()) { |
122 | qWarning(msg: "Could not find Accessibility DBus address." ); |
123 | return; |
124 | } |
125 | m_a11yConnection = QDBusConnection(QDBusConnection::connectToBus(address, name: "a11y"_L1 )); |
126 | |
127 | if (m_enabled) |
128 | emit enabledChanged(enabled: true); |
129 | } |
130 | |
131 | void DBusConnection::dbusError(const QDBusError &error) |
132 | { |
133 | qWarning() << "Accessibility encountered a DBus error:" << error; |
134 | } |
135 | |
136 | /*! |
137 | Returns the DBus connection that got established. |
138 | Or an invalid connection if not yet connected. |
139 | */ |
140 | QDBusConnection DBusConnection::connection() const |
141 | { |
142 | return m_a11yConnection; |
143 | } |
144 | |
145 | QT_END_NAMESPACE |
146 | |
147 | #include "moc_dbusconnection_p.cpp" |
148 | |