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 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | using namespace Qt::StringLiterals; |
25 | |
26 | /*! |
27 | \class QSpiAccessibleBridge |
28 | \internal |
29 | */ |
30 | |
31 | QSpiAccessibleBridge::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 | |
46 | void QSpiAccessibleBridge::enabledChanged(bool enabled) |
47 | { |
48 | setActive(enabled); |
49 | updateStatus(); |
50 | } |
51 | |
52 | QSpiAccessibleBridge::~QSpiAccessibleBridge() |
53 | { |
54 | delete dbusConnection; |
55 | } // Qt currently doesn't delete plugins. |
56 | |
57 | QDBusConnection QSpiAccessibleBridge::dBusConnection() const |
58 | { |
59 | return dbusConnection->connection(); |
60 | } |
61 | |
62 | void 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 | |
80 | void QSpiAccessibleBridge::notifyAccessibilityUpdate(QAccessibleEvent *event) |
81 | { |
82 | if (!dbusAdaptor) |
83 | return; |
84 | if (isActive() && event->accessibleInterface()) |
85 | dbusAdaptor->notify(event); |
86 | } |
87 | |
88 | struct RoleMapping { |
89 | QAccessible::Role role; |
90 | AtspiRole spiRole; |
91 | const char *name; |
92 | }; |
93 | |
94 | static 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 | |
249 | void 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 | |
261 | QSpiAccessibleBridge *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 | |
270 | RoleNames QSpiAccessibleBridge::namesForRole(QAccessible::Role role) |
271 | { |
272 | auto brigde = QSpiAccessibleBridge::instance(); |
273 | return brigde ? brigde->spiRoleNames().value(key: role) : RoleNames(); |
274 | } |
275 | |
276 | QT_END_NAMESPACE |
277 | |
278 | #include "moc_qspiaccessiblebridge_p.cpp" |
279 | #endif // QT_CONFIG(accessibility) |
280 | |