1// Copyright (C) 2016 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#include "qspiaccessiblebridge_p.h"
6
7#include <atspi/atspi-constants.h>
8#include <private/qguiapplication_p.h>
9#include <qpa/qplatformintegration.h>
10#include <qstring.h>
11
12#include "atspiadaptor_p.h"
13
14#include "qspidbuscache_p.h"
15#include "qspi_constant_mappings_p.h"
16#include "dbusconnection_p.h"
17#include "qspi_struct_marshallers_p.h"
18
19#if QT_CONFIG(accessibility)
20#include "deviceeventcontroller_adaptor.h"
21
22QT_BEGIN_NAMESPACE
23
24using namespace Qt::StringLiterals;
25
26/*!
27 \class QSpiAccessibleBridge
28 \internal
29*/
30
31QSpiAccessibleBridge::QSpiAccessibleBridge()
32 : cache(nullptr), dec(nullptr), dbusAdaptor(nullptr)
33{
34 dbusConnection = new DBusConnection();
35 connect(sender: dbusConnection, SIGNAL(enabledChanged(bool)), receiver: this, SLOT(enabledChanged(bool)));
36 // Now that we have connected the signal, make sure we didn't miss a change,
37 // e.g. when running as root or when AT_SPI_BUS_ADDRESS is set by hand.
38 // But do that only on next loop, once dbus is really settled.
39 QTimer::singleShot(
40 0, this, [this]{
41 if (dbusConnection->isEnabled())
42 enabledChanged(true);
43 });
44}
45
46void QSpiAccessibleBridge::enabledChanged(bool enabled)
47{
48 setActive(enabled);
49 updateStatus();
50}
51
52QSpiAccessibleBridge::~QSpiAccessibleBridge()
53{
54 delete dbusConnection;
55} // Qt currently doesn't delete plugins.
56
57QDBusConnection QSpiAccessibleBridge::dBusConnection() const
58{
59 return dbusConnection->connection();
60}
61
62void QSpiAccessibleBridge::updateStatus()
63{
64 // create the adaptor to handle everything if we are in enabled state
65 if (!dbusAdaptor && isActive()) {
66 qSpiInitializeStructTypes();
67 initializeConstantMappings();
68
69 cache = new QSpiDBusCache(dbusConnection->connection(), this);
70 dec = new DeviceEventControllerAdaptor(this);
71
72 dbusConnection->connection().registerObject(ATSPI_DBUS_PATH_DEC ""_L1, object: this, options: QDBusConnection::ExportAdaptors);
73
74 dbusAdaptor = new AtSpiAdaptor(dbusConnection, this);
75 dbusConnection->connection().registerVirtualObject(QSPI_OBJECT_PATH_ACCESSIBLE ""_L1, object: dbusAdaptor, options: QDBusConnection::SubPath);
76 dbusAdaptor->registerApplication();
77 }
78}
79
80void QSpiAccessibleBridge::notifyAccessibilityUpdate(QAccessibleEvent *event)
81{
82 if (!dbusAdaptor)
83 return;
84 if (isActive() && event->accessibleInterface())
85 dbusAdaptor->notify(event);
86}
87
88struct RoleMapping {
89 QAccessible::Role role;
90 AtspiRole spiRole;
91 const char *name;
92};
93
94static RoleMapping map[] = {
95 //: Role of an accessible object - the object is in an invalid state or could not be constructed
96 { .role: QAccessible::NoRole, .spiRole: ATSPI_ROLE_INVALID, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "invalid role") },
97 //: Role of an accessible object
98 { .role: QAccessible::TitleBar, .spiRole: ATSPI_ROLE_TEXT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "title bar") },
99 //: Role of an accessible object
100 { .role: QAccessible::MenuBar, .spiRole: ATSPI_ROLE_MENU_BAR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "menu bar") },
101 //: Role of an accessible object
102 { .role: QAccessible::ScrollBar, .spiRole: ATSPI_ROLE_SCROLL_BAR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "scroll bar") },
103 //: Role of an accessible object - the grip is usually used for resizing another object
104 { .role: QAccessible::Grip, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "grip") },
105 //: Role of an accessible object
106 { .role: QAccessible::Sound, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "sound") },
107 //: Role of an accessible object
108 { .role: QAccessible::Cursor, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "cursor") },
109 //: Role of an accessible object
110 { .role: QAccessible::Caret, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "text caret") },
111 //: Role of an accessible object
112 { .role: QAccessible::AlertMessage, .spiRole: ATSPI_ROLE_ALERT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "alert message") },
113 //: Role of an accessible object: a window with frame and title
114 { .role: QAccessible::Window, .spiRole: ATSPI_ROLE_FRAME, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "frame") },
115 //: Role of an accessible object
116 { .role: QAccessible::Client, .spiRole: ATSPI_ROLE_FILLER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "filler") },
117 //: Role of an accessible object
118 { .role: QAccessible::PopupMenu, .spiRole: ATSPI_ROLE_POPUP_MENU, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "popup menu") },
119 //: Role of an accessible object
120 { .role: QAccessible::MenuItem, .spiRole: ATSPI_ROLE_MENU_ITEM, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "menu item") },
121 //: Role of an accessible object
122 { .role: QAccessible::ToolTip, .spiRole: ATSPI_ROLE_TOOL_TIP, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "tool tip") },
123 //: Role of an accessible object
124 { .role: QAccessible::Application, .spiRole: ATSPI_ROLE_APPLICATION, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "application") },
125 //: Role of an accessible object
126 { .role: QAccessible::Document, .spiRole: ATSPI_ROLE_DOCUMENT_FRAME, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "document") },
127 //: Role of an accessible object
128 { .role: QAccessible::Pane, .spiRole: ATSPI_ROLE_PANEL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "panel") },
129 //: Role of an accessible object
130 { .role: QAccessible::Chart, .spiRole: ATSPI_ROLE_CHART, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "chart") },
131 //: Role of an accessible object
132 { .role: QAccessible::Dialog, .spiRole: ATSPI_ROLE_DIALOG, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "dialog") },
133 //: Role of an accessible object
134 { .role: QAccessible::Border, .spiRole: ATSPI_ROLE_FRAME, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "frame") },
135 //: Role of an accessible object
136 { .role: QAccessible::Grouping, .spiRole: ATSPI_ROLE_PANEL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "panel") },
137 //: Role of an accessible object
138 { .role: QAccessible::Separator, .spiRole: ATSPI_ROLE_SEPARATOR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "separator") },
139 //: Role of an accessible object
140 { .role: QAccessible::ToolBar, .spiRole: ATSPI_ROLE_TOOL_BAR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "tool bar") },
141 //: Role of an accessible object
142 { .role: QAccessible::StatusBar, .spiRole: ATSPI_ROLE_STATUS_BAR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "status bar") },
143 //: Role of an accessible object
144 { .role: QAccessible::Table, .spiRole: ATSPI_ROLE_TABLE, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "table") },
145 //: Role of an accessible object - part of a table
146 { .role: QAccessible::ColumnHeader, .spiRole: ATSPI_ROLE_TABLE_COLUMN_HEADER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "column header") },
147 //: Role of an accessible object - part of a table
148 { .role: QAccessible::RowHeader, .spiRole: ATSPI_ROLE_TABLE_ROW_HEADER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "row header") },
149 //: Role of an accessible object - part of a table
150 { .role: QAccessible::Column, .spiRole: ATSPI_ROLE_TABLE_CELL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "column") },
151 //: Role of an accessible object - part of a table
152 { .role: QAccessible::Row, .spiRole: ATSPI_ROLE_TABLE_ROW, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "row") },
153 //: Role of an accessible object - part of a table
154 { .role: QAccessible::Cell, .spiRole: ATSPI_ROLE_TABLE_CELL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "cell") },
155 //: Role of an accessible object
156 { .role: QAccessible::Link, .spiRole: ATSPI_ROLE_LINK, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "link") },
157 //: Role of an accessible object
158 { .role: QAccessible::HelpBalloon, .spiRole: ATSPI_ROLE_DIALOG, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "help balloon") },
159 //: Role of an accessible object - a helper dialog
160 { .role: QAccessible::Assistant, .spiRole: ATSPI_ROLE_DIALOG, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "assistant") },
161 //: Role of an accessible object
162 { .role: QAccessible::List, .spiRole: ATSPI_ROLE_LIST, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "list") },
163 //: Role of an accessible object
164 { .role: QAccessible::ListItem, .spiRole: ATSPI_ROLE_LIST_ITEM, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "list item") },
165 //: Role of an accessible object
166 { .role: QAccessible::Tree, .spiRole: ATSPI_ROLE_TREE, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "tree") },
167 //: Role of an accessible object
168 { .role: QAccessible::TreeItem, .spiRole: ATSPI_ROLE_TABLE_CELL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "tree item") },
169 //: Role of an accessible object
170 { .role: QAccessible::PageTab, .spiRole: ATSPI_ROLE_PAGE_TAB, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "page tab") },
171 //: Role of an accessible object
172 { .role: QAccessible::PropertyPage, .spiRole: ATSPI_ROLE_PAGE_TAB, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "property page") },
173 //: Role of an accessible object
174 { .role: QAccessible::Indicator, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "indicator") },
175 //: Role of an accessible object
176 { .role: QAccessible::Graphic, .spiRole: ATSPI_ROLE_IMAGE, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "graphic") },
177 //: Role of an accessible object
178 { .role: QAccessible::StaticText, .spiRole: ATSPI_ROLE_LABEL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "label") },
179 //: Role of an accessible object
180 { .role: QAccessible::EditableText, .spiRole: ATSPI_ROLE_TEXT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "text") },
181 //: Role of an accessible object
182 { .role: QAccessible::PushButton, .spiRole: ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "push button") },
183 //: Role of an accessible object
184 { .role: QAccessible::CheckBox, .spiRole: ATSPI_ROLE_CHECK_BOX, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "check box") },
185 //: Role of an accessible object
186 { .role: QAccessible::RadioButton, .spiRole: ATSPI_ROLE_RADIO_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "radio button") },
187 //: Role of an accessible object
188 { .role: QAccessible::ComboBox, .spiRole: ATSPI_ROLE_COMBO_BOX, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "combo box") },
189 //: Role of an accessible object
190 { .role: QAccessible::ProgressBar, .spiRole: ATSPI_ROLE_PROGRESS_BAR, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "progress bar") },
191 //: Role of an accessible object
192 { .role: QAccessible::Dial, .spiRole: ATSPI_ROLE_DIAL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "dial") },
193 //: Role of an accessible object
194 { .role: QAccessible::HotkeyField, .spiRole: ATSPI_ROLE_TEXT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "hotkey field") },
195 //: Role of an accessible object
196 { .role: QAccessible::Slider, .spiRole: ATSPI_ROLE_SLIDER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "slider") },
197 //: Role of an accessible object
198 { .role: QAccessible::SpinBox, .spiRole: ATSPI_ROLE_SPIN_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "spin box") },
199 //: Role of an accessible object
200 { .role: QAccessible::Canvas, .spiRole: ATSPI_ROLE_CANVAS, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "canvas") },
201 //: Role of an accessible object
202 { .role: QAccessible::Animation, .spiRole: ATSPI_ROLE_ANIMATION, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "animation") },
203 //: Role of an accessible object
204 { .role: QAccessible::Equation, .spiRole: ATSPI_ROLE_TEXT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "equation") },
205 //: Role of an accessible object
206 { .role: QAccessible::ButtonDropDown, .spiRole: ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down") },
207 //: Role of an accessible object
208 { .role: QAccessible::ButtonMenu, .spiRole: ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button menu") },
209 //: Role of an accessible object - a button that expands a grid.
210 { .role: QAccessible::ButtonDropGrid, .spiRole: ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down grid") },
211 //: Role of an accessible object - blank space between other objects.
212 { .role: QAccessible::Whitespace, .spiRole: ATSPI_ROLE_FILLER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "space") },
213 //: Role of an accessible object
214 { .role: QAccessible::PageTabList, .spiRole: ATSPI_ROLE_PAGE_TAB_LIST, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "page tab list") },
215 //: Role of an accessible object
216 { .role: QAccessible::Clock, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "clock") },
217 //: Role of an accessible object
218 { .role: QAccessible::Splitter, .spiRole: ATSPI_ROLE_SPLIT_PANE, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "splitter") },
219 //: Role of an accessible object
220 { .role: QAccessible::LayeredPane, .spiRole: ATSPI_ROLE_LAYERED_PANE, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "layered pane") },
221 //: Role of an accessible object
222 { .role: QAccessible::WebDocument, .spiRole: ATSPI_ROLE_DOCUMENT_WEB, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "web document") },
223 //: Role of an accessible object
224 { .role: QAccessible::Paragraph, .spiRole: ATSPI_ROLE_PARAGRAPH, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "paragraph") },
225 //: Role of an accessible object
226 { .role: QAccessible::Section, .spiRole: ATSPI_ROLE_SECTION, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "section") },
227 //: Role of an accessible object
228 { .role: QAccessible::ColorChooser, .spiRole: ATSPI_ROLE_COLOR_CHOOSER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "color chooser") },
229 //: Role of an accessible object
230 { .role: QAccessible::Footer, .spiRole: ATSPI_ROLE_FOOTER, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "footer") },
231 //: Role of an accessible object
232 { .role: QAccessible::Form, .spiRole: ATSPI_ROLE_FORM, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "form") },
233 //: Role of an accessible object
234 { .role: QAccessible::Heading, .spiRole: ATSPI_ROLE_HEADING, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "heading") },
235 //: Role of an accessible object
236 { .role: QAccessible::Note, .spiRole: ATSPI_ROLE_COMMENT, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "note") },
237 //: Role of an accessible object
238 { .role: QAccessible::ComplementaryContent, .spiRole: ATSPI_ROLE_SECTION, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "complementary content") },
239 //: Role of an accessible object
240 { .role: QAccessible::Terminal, .spiRole: ATSPI_ROLE_TERMINAL, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "terminal") },
241 //: Role of an accessible object
242 { .role: QAccessible::Desktop, .spiRole: ATSPI_ROLE_DESKTOP_FRAME, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "desktop") },
243 //: Role of an accessible object
244 { .role: QAccessible::Notification, .spiRole: ATSPI_ROLE_NOTIFICATION, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "notification") },
245 //: Role of an accessible object
246 { .role: QAccessible::UserRole, .spiRole: ATSPI_ROLE_UNKNOWN, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "unknown") }
247};
248
249void QSpiAccessibleBridge::initializeConstantMappings()
250{
251 for (uint i = 0; i < sizeof(map) / sizeof(RoleMapping); ++i)
252 m_spiRoleMapping.insert(key: map[i].role, value: RoleNames(map[i].spiRole, QLatin1StringView(map[i].name), tr(s: map[i].name)));
253
254 // -1 because we have button duplicated, as PushButton and Button.
255 Q_ASSERT_X(m_spiRoleMapping.size() ==
256 QAccessible::staticMetaObject.enumerator(
257 QAccessible::staticMetaObject.indexOfEnumerator("Role")).keyCount() - 1,
258 "", "Handle all QAccessible::Role members in qSpiRoleMapping");
259}
260
261QSpiAccessibleBridge *QSpiAccessibleBridge::instance()
262{
263 if (auto integration = QGuiApplicationPrivate::platformIntegration()) {
264 if (auto accessibility = integration->accessibility())
265 return static_cast<QSpiAccessibleBridge *>(accessibility);
266 }
267 return nullptr;
268}
269
270RoleNames QSpiAccessibleBridge::namesForRole(QAccessible::Role role)
271{
272 auto brigde = QSpiAccessibleBridge::instance();
273 return brigde ? brigde->spiRoleNames().value(key: role) : RoleNames();
274}
275
276QT_END_NAMESPACE
277
278#include "moc_qspiaccessiblebridge_p.cpp"
279#endif // QT_CONFIG(accessibility)
280

source code of qtbase/src/gui/accessible/linux/qspiaccessiblebridge.cpp