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 | #include "atspiadaptor_p.h" |
5 | #include "qspiaccessiblebridge_p.h" |
6 | |
7 | #include <QtGui/qwindow.h> |
8 | #include <QtGui/qguiapplication.h> |
9 | #include <qdbusmessage.h> |
10 | #include <qdbusreply.h> |
11 | #include <qclipboard.h> |
12 | |
13 | #include <QtCore/qloggingcategory.h> |
14 | #include <QtCore/qtversion.h> |
15 | |
16 | #if QT_CONFIG(accessibility) |
17 | #include "socket_interface.h" |
18 | #include "qspi_constant_mappings_p.h" |
19 | #include <QtCore/private/qstringiterator_p.h> |
20 | #include <QtGui/private/qaccessiblebridgeutils_p.h> |
21 | |
22 | #include "qspiapplicationadaptor_p.h" |
23 | /*! |
24 | \class AtSpiAdaptor |
25 | \internal |
26 | |
27 | \brief AtSpiAdaptor is the main class to forward between QAccessibleInterface and AT-SPI DBus |
28 | |
29 | AtSpiAdaptor implements the functions specified in all at-spi interfaces. |
30 | It sends notifications coming from Qt via dbus and listens to incoming dbus requests. |
31 | */ |
32 | |
33 | // ATSPI_COORD_TYPE_PARENT was added in at-spi 2.30, define here for older versions |
34 | #if ATSPI_COORD_TYPE_COUNT < 3 |
35 | #define ATSPI_COORD_TYPE_PARENT 2 |
36 | #endif |
37 | |
38 | QT_BEGIN_NAMESPACE |
39 | |
40 | using namespace Qt::StringLiterals; |
41 | |
42 | Q_LOGGING_CATEGORY(lcAccessibilityAtspi, "qt.accessibility.atspi" ) |
43 | Q_LOGGING_CATEGORY(lcAccessibilityAtspiCreation, "qt.accessibility.atspi.creation" ) |
44 | |
45 | AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent) |
46 | : QDBusVirtualObject(parent), m_dbus(connection) |
47 | , sendFocus(0) |
48 | , sendObject(0) |
49 | , sendObject_active_descendant_changed(0) |
50 | , sendObject_attributes_changed(0) |
51 | , sendObject_bounds_changed(0) |
52 | , sendObject_children_changed(0) |
53 | // , sendObject_children_changed_add(0) |
54 | // , sendObject_children_changed_remove(0) |
55 | , sendObject_column_deleted(0) |
56 | , sendObject_column_inserted(0) |
57 | , sendObject_column_reordered(0) |
58 | , sendObject_link_selected(0) |
59 | , sendObject_model_changed(0) |
60 | , sendObject_property_change(0) |
61 | , sendObject_property_change_accessible_description(0) |
62 | , sendObject_property_change_accessible_name(0) |
63 | , sendObject_property_change_accessible_parent(0) |
64 | , sendObject_property_change_accessible_role(0) |
65 | , sendObject_property_change_accessible_table_caption(0) |
66 | , sendObject_property_change_accessible_table_column_description(0) |
67 | , sendObject_property_change_accessible_table_column_header(0) |
68 | , sendObject_property_change_accessible_table_row_description(0) |
69 | , sendObject_property_change_accessible_table_row_header(0) |
70 | , sendObject_property_change_accessible_table_summary(0) |
71 | , sendObject_property_change_accessible_value(0) |
72 | , sendObject_row_deleted(0) |
73 | , sendObject_row_inserted(0) |
74 | , sendObject_row_reordered(0) |
75 | , sendObject_selection_changed(0) |
76 | , sendObject_state_changed(0) |
77 | , sendObject_text_attributes_changed(0) |
78 | , sendObject_text_bounds_changed(0) |
79 | , sendObject_text_caret_moved(0) |
80 | , sendObject_text_changed(0) |
81 | // , sendObject_text_changed_delete(0) |
82 | // , sendObject_text_changed_insert(0) |
83 | , sendObject_text_selection_changed(0) |
84 | , sendObject_value_changed(0) |
85 | , sendObject_visible_data_changed(0) |
86 | , sendWindow(0) |
87 | , sendWindow_activate(0) |
88 | , sendWindow_close(0) |
89 | , sendWindow_create(0) |
90 | , sendWindow_deactivate(0) |
91 | // , sendWindow_desktop_create(0) |
92 | // , sendWindow_desktop_destroy(0) |
93 | , sendWindow_lower(0) |
94 | , sendWindow_maximize(0) |
95 | , sendWindow_minimize(0) |
96 | , sendWindow_move(0) |
97 | , sendWindow_raise(0) |
98 | , sendWindow_reparent(0) |
99 | , sendWindow_resize(0) |
100 | , sendWindow_restore(0) |
101 | , sendWindow_restyle(0) |
102 | , sendWindow_shade(0) |
103 | , sendWindow_unshade(0) |
104 | { |
105 | m_applicationAdaptor = new QSpiApplicationAdaptor(m_dbus->connection(), this); |
106 | connect(sender: m_applicationAdaptor, SIGNAL(windowActivated(QObject*,bool)), receiver: this, SLOT(windowActivated(QObject*,bool))); |
107 | |
108 | updateEventListeners(); |
109 | bool success = m_dbus->connection().connect(service: "org.a11y.atspi.Registry"_L1 , path: "/org/a11y/atspi/registry"_L1 , |
110 | interface: "org.a11y.atspi.Registry"_L1 , name: "EventListenerRegistered"_L1 , receiver: this, |
111 | SLOT(eventListenerRegistered(QString,QString))); |
112 | success = success && m_dbus->connection().connect(service: "org.a11y.atspi.Registry"_L1 , path: "/org/a11y/atspi/registry"_L1 , |
113 | interface: "org.a11y.atspi.Registry"_L1 , name: "EventListenerDeregistered"_L1 , receiver: this, |
114 | SLOT(eventListenerDeregistered(QString,QString))); |
115 | } |
116 | |
117 | AtSpiAdaptor::~AtSpiAdaptor() |
118 | { |
119 | } |
120 | |
121 | /*! |
122 | Provide DBus introspection. |
123 | */ |
124 | QString AtSpiAdaptor::introspect(const QString &path) const |
125 | { |
126 | static const QLatin1StringView accessibleIntrospection( |
127 | " <interface name=\"org.a11y.atspi.Accessible\">\n" |
128 | " <property access=\"read\" type=\"s\" name=\"Name\"/>\n" |
129 | " <property access=\"read\" type=\"s\" name=\"Description\"/>\n" |
130 | " <property access=\"read\" type=\"(so)\" name=\"Parent\">\n" |
131 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n" |
132 | " </property>\n" |
133 | " <property access=\"read\" type=\"i\" name=\"ChildCount\"/>\n" |
134 | " <method name=\"GetChildAtIndex\">\n" |
135 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
136 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
137 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
138 | " </method>\n" |
139 | " <method name=\"GetChildren\">\n" |
140 | " <arg direction=\"out\" type=\"a(so)\"/>\n" |
141 | " <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
142 | " </method>\n" |
143 | " <method name=\"GetIndexInParent\">\n" |
144 | " <arg direction=\"out\" type=\"i\"/>\n" |
145 | " </method>\n" |
146 | " <method name=\"GetRelationSet\">\n" |
147 | " <arg direction=\"out\" type=\"a(ua(so))\"/>\n" |
148 | " <annotation value=\"QSpiRelationArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
149 | " </method>\n" |
150 | " <method name=\"GetRole\">\n" |
151 | " <arg direction=\"out\" type=\"u\"/>\n" |
152 | " </method>\n" |
153 | " <method name=\"GetRoleName\">\n" |
154 | " <arg direction=\"out\" type=\"s\"/>\n" |
155 | " </method>\n" |
156 | " <method name=\"GetLocalizedRoleName\">\n" |
157 | " <arg direction=\"out\" type=\"s\"/>\n" |
158 | " </method>\n" |
159 | " <method name=\"GetState\">\n" |
160 | " <arg direction=\"out\" type=\"au\"/>\n" |
161 | " <annotation value=\"QSpiUIntList\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
162 | " </method>\n" |
163 | " <method name=\"GetAttributes\">\n" |
164 | " <arg direction=\"out\" type=\"a{ss}\"/>\n" |
165 | " <annotation value=\"QSpiAttributeSet\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
166 | " </method>\n" |
167 | " <method name=\"GetApplication\">\n" |
168 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
169 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
170 | " </method>\n" |
171 | " <method name=\"GetAccessibleId\">\n" |
172 | " <arg direction=\"out\" type=\"s\"/>\n" |
173 | " </method>\n" |
174 | " </interface>\n" |
175 | ); |
176 | |
177 | static const QLatin1StringView actionIntrospection( |
178 | " <interface name=\"org.a11y.atspi.Action\">\n" |
179 | " <property access=\"read\" type=\"i\" name=\"NActions\"/>\n" |
180 | " <method name=\"GetDescription\">\n" |
181 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
182 | " <arg direction=\"out\" type=\"s\"/>\n" |
183 | " </method>\n" |
184 | " <method name=\"GetName\">\n" |
185 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
186 | " <arg direction=\"out\" type=\"s\"/>\n" |
187 | " </method>\n" |
188 | " <method name=\"GetKeyBinding\">\n" |
189 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
190 | " <arg direction=\"out\" type=\"s\"/>\n" |
191 | " </method>\n" |
192 | " <method name=\"GetActions\">\n" |
193 | " <arg direction=\"out\" type=\"a(sss)\" name=\"index\"/>\n" |
194 | " <annotation value=\"QSpiActionArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
195 | " </method>\n" |
196 | " <method name=\"DoAction\">\n" |
197 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
198 | " <arg direction=\"out\" type=\"b\"/>\n" |
199 | " </method>\n" |
200 | " </interface>\n" |
201 | ); |
202 | |
203 | static const QLatin1StringView applicationIntrospection( |
204 | " <interface name=\"org.a11y.atspi.Application\">\n" |
205 | " <property access=\"read\" type=\"s\" name=\"ToolkitName\"/>\n" |
206 | " <property access=\"read\" type=\"s\" name=\"Version\"/>\n" |
207 | " <property access=\"readwrite\" type=\"i\" name=\"Id\"/>\n" |
208 | " <method name=\"GetLocale\">\n" |
209 | " <arg direction=\"in\" type=\"u\" name=\"lctype\"/>\n" |
210 | " <arg direction=\"out\" type=\"s\"/>\n" |
211 | " </method>\n" |
212 | " <method name=\"GetApplicationBusAddress\">\n" |
213 | " <arg direction=\"out\" type=\"s\" name=\"address\"/>\n" |
214 | " </method>\n" |
215 | " </interface>\n" |
216 | ); |
217 | |
218 | static const QLatin1StringView componentIntrospection( |
219 | " <interface name=\"org.a11y.atspi.Component\">\n" |
220 | " <method name=\"Contains\">\n" |
221 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
222 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
223 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
224 | " <arg direction=\"out\" type=\"b\"/>\n" |
225 | " </method>\n" |
226 | " <method name=\"GetAccessibleAtPoint\">\n" |
227 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
228 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
229 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
230 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
231 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
232 | " </method>\n" |
233 | " <method name=\"GetExtents\">\n" |
234 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
235 | " <arg direction=\"out\" type=\"(iiii)\"/>\n" |
236 | " <annotation value=\"QSpiRect\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
237 | " </method>\n" |
238 | " <method name=\"GetPosition\">\n" |
239 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
240 | " <arg direction=\"out\" type=\"i\" name=\"x\"/>\n" |
241 | " <arg direction=\"out\" type=\"i\" name=\"y\"/>\n" |
242 | " </method>\n" |
243 | " <method name=\"GetSize\">\n" |
244 | " <arg direction=\"out\" type=\"i\" name=\"width\"/>\n" |
245 | " <arg direction=\"out\" type=\"i\" name=\"height\"/>\n" |
246 | " </method>\n" |
247 | " <method name=\"GetLayer\">\n" |
248 | " <arg direction=\"out\" type=\"u\"/>\n" |
249 | " </method>\n" |
250 | " <method name=\"GetMDIZOrder\">\n" |
251 | " <arg direction=\"out\" type=\"n\"/>\n" |
252 | " </method>\n" |
253 | " <method name=\"GrabFocus\">\n" |
254 | " <arg direction=\"out\" type=\"b\"/>\n" |
255 | " </method>\n" |
256 | " <method name=\"GetAlpha\">\n" |
257 | " <arg direction=\"out\" type=\"d\"/>\n" |
258 | " </method>\n" |
259 | " <method name=\"SetExtents\">\n" |
260 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
261 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
262 | " <arg direction=\"in\" type=\"i\" name=\"width\"/>\n" |
263 | " <arg direction=\"in\" type=\"i\" name=\"height\"/>\n" |
264 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
265 | " <arg direction=\"out\" type=\"b\"/>\n" |
266 | " </method>\n" |
267 | " <method name=\"SetPosition\">\n" |
268 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
269 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
270 | " <arg direction=\"in\" type=\"u\" name=\"coord_type\"/>\n" |
271 | " <arg direction=\"out\" type=\"b\"/>\n" |
272 | " </method>\n" |
273 | " <method name=\"SetSize\">\n" |
274 | " <arg direction=\"in\" type=\"i\" name=\"width\"/>\n" |
275 | " <arg direction=\"in\" type=\"i\" name=\"height\"/>\n" |
276 | " <arg direction=\"out\" type=\"b\"/>\n" |
277 | " </method>\n" |
278 | " </interface>\n" |
279 | ); |
280 | |
281 | static const QLatin1StringView editableTextIntrospection( |
282 | " <interface name=\"org.a11y.atspi.EditableText\">\n" |
283 | " <method name=\"SetTextContents\">\n" |
284 | " <arg direction=\"in\" type=\"s\" name=\"newContents\"/>\n" |
285 | " <arg direction=\"out\" type=\"b\"/>\n" |
286 | " </method>\n" |
287 | " <method name=\"InsertText\">\n" |
288 | " <arg direction=\"in\" type=\"i\" name=\"position\"/>\n" |
289 | " <arg direction=\"in\" type=\"s\" name=\"text\"/>\n" |
290 | " <arg direction=\"in\" type=\"i\" name=\"length\"/>\n" |
291 | " <arg direction=\"out\" type=\"b\"/>\n" |
292 | " </method>\n" |
293 | " <method name=\"CopyText\">\n" |
294 | " <arg direction=\"in\" type=\"i\" name=\"startPos\"/>\n" |
295 | " <arg direction=\"in\" type=\"i\" name=\"endPos\"/>\n" |
296 | " </method>\n" |
297 | " <method name=\"CutText\">\n" |
298 | " <arg direction=\"in\" type=\"i\" name=\"startPos\"/>\n" |
299 | " <arg direction=\"in\" type=\"i\" name=\"endPos\"/>\n" |
300 | " <arg direction=\"out\" type=\"b\"/>\n" |
301 | " </method>\n" |
302 | " <method name=\"DeleteText\">\n" |
303 | " <arg direction=\"in\" type=\"i\" name=\"startPos\"/>\n" |
304 | " <arg direction=\"in\" type=\"i\" name=\"endPos\"/>\n" |
305 | " <arg direction=\"out\" type=\"b\"/>\n" |
306 | " </method>\n" |
307 | " <method name=\"PasteText\">\n" |
308 | " <arg direction=\"in\" type=\"i\" name=\"position\"/>\n" |
309 | " <arg direction=\"out\" type=\"b\"/>\n" |
310 | " </method>\n" |
311 | " </interface>\n" |
312 | ); |
313 | |
314 | static const QLatin1StringView selectionIntrospection( |
315 | " <interface name=\"org.a11y.atspi.Selection\">\n" |
316 | " <property name=\"NSelectedChildren\" type=\"i\" access=\"read\"/>\n" |
317 | " <method name=\"GetSelectedChild\">\n" |
318 | " <arg direction=\"in\" name=\"selectedChildIndex\" type=\"i\"/>\n" |
319 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
320 | " <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QSpiObjectReference\"/>\n" |
321 | " </method>\n" |
322 | " <method name=\"SelectChild\">\n" |
323 | " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" |
324 | " <arg direction=\"out\" type=\"b\"/>\n" |
325 | " </method>\n" |
326 | " <method name=\"DeselectSelectedChild\">\n" |
327 | " <arg direction=\"in\" name=\"selectedChildIndex\" type=\"i\"/>\n" |
328 | " <arg direction=\"out\" type=\"b\"/>\n" |
329 | " </method>\n" |
330 | " <method name=\"IsChildSelected\">\n" |
331 | " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" |
332 | " <arg direction=\"out\" type=\"b\"/>\n" |
333 | " </method>\n" |
334 | " <method name=\"SelectAll\">\n" |
335 | " <arg direction=\"out\" type=\"b\"/>\n" |
336 | " </method>\n" |
337 | " <method name=\"ClearSelection\">\n" |
338 | " <arg direction=\"out\" type=\"b\"/>\n" |
339 | " </method>\n" |
340 | " <method name=\"DeselectChild\">\n" |
341 | " <arg direction=\"in\" name=\"childIndex\" type=\"i\"/>\n" |
342 | " <arg direction=\"out\" type=\"b\"/>\n" |
343 | " </method>\n" |
344 | " </interface>\n" |
345 | ); |
346 | |
347 | static const QLatin1StringView tableIntrospection( |
348 | " <interface name=\"org.a11y.atspi.Table\">\n" |
349 | " <property access=\"read\" type=\"i\" name=\"NRows\"/>\n" |
350 | " <property access=\"read\" type=\"i\" name=\"NColumns\"/>\n" |
351 | " <property access=\"read\" type=\"(so)\" name=\"Caption\">\n" |
352 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n" |
353 | " </property>\n" |
354 | " <property access=\"read\" type=\"(so)\" name=\"Summary\">\n" |
355 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n" |
356 | " </property>\n" |
357 | " <property access=\"read\" type=\"i\" name=\"NSelectedRows\"/>\n" |
358 | " <property access=\"read\" type=\"i\" name=\"NSelectedColumns\"/>\n" |
359 | " <method name=\"GetAccessibleAt\">\n" |
360 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
361 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
362 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
363 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
364 | " </method>\n" |
365 | " <method name=\"GetIndexAt\">\n" |
366 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
367 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
368 | " <arg direction=\"out\" type=\"i\"/>\n" |
369 | " </method>\n" |
370 | " <method name=\"GetRowAtIndex\">\n" |
371 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
372 | " <arg direction=\"out\" type=\"i\"/>\n" |
373 | " </method>\n" |
374 | " <method name=\"GetColumnAtIndex\">\n" |
375 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
376 | " <arg direction=\"out\" type=\"i\"/>\n" |
377 | " </method>\n" |
378 | " <method name=\"GetRowDescription\">\n" |
379 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
380 | " <arg direction=\"out\" type=\"s\"/>\n" |
381 | " </method>\n" |
382 | " <method name=\"GetColumnDescription\">\n" |
383 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
384 | " <arg direction=\"out\" type=\"s\"/>\n" |
385 | " </method>\n" |
386 | " <method name=\"GetRowExtentAt\">\n" |
387 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
388 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
389 | " <arg direction=\"out\" type=\"i\"/>\n" |
390 | " </method>\n" |
391 | " <method name=\"GetColumnExtentAt\">\n" |
392 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
393 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
394 | " <arg direction=\"out\" type=\"i\"/>\n" |
395 | " </method>\n" |
396 | " <method name=\"GetRowHeader\">\n" |
397 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
398 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
399 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
400 | " </method>\n" |
401 | " <method name=\"GetColumnHeader\">\n" |
402 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
403 | " <arg direction=\"out\" type=\"(so)\"/>\n" |
404 | " <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
405 | " </method>\n" |
406 | " <method name=\"GetSelectedRows\">\n" |
407 | " <arg direction=\"out\" type=\"ai\"/>\n" |
408 | " <annotation value=\"QSpiIntList\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
409 | " </method>\n" |
410 | " <method name=\"GetSelectedColumns\">\n" |
411 | " <arg direction=\"out\" type=\"ai\"/>\n" |
412 | " <annotation value=\"QSpiIntList\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
413 | " </method>\n" |
414 | " <method name=\"IsRowSelected\">\n" |
415 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
416 | " <arg direction=\"out\" type=\"b\"/>\n" |
417 | " </method>\n" |
418 | " <method name=\"IsColumnSelected\">\n" |
419 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
420 | " <arg direction=\"out\" type=\"b\"/>\n" |
421 | " </method>\n" |
422 | " <method name=\"IsSelected\">\n" |
423 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
424 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
425 | " <arg direction=\"out\" type=\"b\"/>\n" |
426 | " </method>\n" |
427 | " <method name=\"AddRowSelection\">\n" |
428 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
429 | " <arg direction=\"out\" type=\"b\"/>\n" |
430 | " </method>\n" |
431 | " <method name=\"AddColumnSelection\">\n" |
432 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
433 | " <arg direction=\"out\" type=\"b\"/>\n" |
434 | " </method>\n" |
435 | " <method name=\"RemoveRowSelection\">\n" |
436 | " <arg direction=\"in\" type=\"i\" name=\"row\"/>\n" |
437 | " <arg direction=\"out\" type=\"b\"/>\n" |
438 | " </method>\n" |
439 | " <method name=\"RemoveColumnSelection\">\n" |
440 | " <arg direction=\"in\" type=\"i\" name=\"column\"/>\n" |
441 | " <arg direction=\"out\" type=\"b\"/>\n" |
442 | " </method>\n" |
443 | " <method name=\"GetRowColumnExtentsAtIndex\">\n" |
444 | " <arg direction=\"in\" type=\"i\" name=\"index\"/>\n" |
445 | " <arg direction=\"out\" type=\"b\"/>\n" |
446 | " <arg direction=\"out\" type=\"i\" name=\"row\"/>\n" |
447 | " <arg direction=\"out\" type=\"i\" name=\"col\"/>\n" |
448 | " <arg direction=\"out\" type=\"i\" name=\"row_extents\"/>\n" |
449 | " <arg direction=\"out\" type=\"i\" name=\"col_extents\"/>\n" |
450 | " <arg direction=\"out\" type=\"b\" name=\"is_selected\"/>\n" |
451 | " </method>\n" |
452 | " </interface>\n" |
453 | ); |
454 | |
455 | static const QLatin1StringView tableCellIntrospection( |
456 | " <interface name=\"org.a11y.atspi.TableCell\">\n" |
457 | " <property access=\"read\" name=\"ColumnSpan\" type=\"i\" />\n" |
458 | " <property access=\"read\" name=\"Position\" type=\"(ii)\">\n" |
459 | " <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"QPoint\"/>\n" |
460 | " </property>\n" |
461 | " <property access=\"read\" name=\"RowSpan\" type=\"i\" />\n" |
462 | " <property access=\"read\" name=\"Table\" type=\"(so)\" >\n" |
463 | " <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"QSpiObjectReference\"/>\n" |
464 | " </property>\n" |
465 | " <method name=\"GetRowColumnSpan\">\n" |
466 | " <arg direction=\"out\" type=\"b\" />\n" |
467 | " <arg direction=\"out\" name=\"row\" type=\"i\" />\n" |
468 | " <arg direction=\"out\" name=\"col\" type=\"i\" />\n" |
469 | " <arg direction=\"out\" name=\"row_extents\" type=\"i\" />\n" |
470 | " <arg direction=\"out\" name=\"col_extents\" type=\"i\" />\n" |
471 | " </method>\n" |
472 | " </interface>\n" |
473 | ); |
474 | |
475 | static const QLatin1StringView textIntrospection( |
476 | " <interface name=\"org.a11y.atspi.Text\">\n" |
477 | " <property access=\"read\" type=\"i\" name=\"CharacterCount\"/>\n" |
478 | " <property access=\"read\" type=\"i\" name=\"CaretOffset\"/>\n" |
479 | " <method name=\"GetStringAtOffset\">\n" |
480 | " <arg direction=\"in\" name=\"offset\" type=\"i\"/>\n" |
481 | " <arg direction=\"in\" name=\"granularity\" type=\"u\"/>\n" |
482 | " <arg direction=\"out\" type=\"s\"/>\n" |
483 | " <arg direction=\"out\" name=\"startOffset\" type=\"i\"/>\n" |
484 | " <arg direction=\"out\" name=\"endOffset\" type=\"i\"/>\n" |
485 | " </method>\n" |
486 | " <method name=\"GetText\">\n" |
487 | " <arg direction=\"in\" type=\"i\" name=\"startOffset\"/>\n" |
488 | " <arg direction=\"in\" type=\"i\" name=\"endOffset\"/>\n" |
489 | " <arg direction=\"out\" type=\"s\"/>\n" |
490 | " </method>\n" |
491 | " <method name=\"SetCaretOffset\">\n" |
492 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
493 | " <arg direction=\"out\" type=\"b\"/>\n" |
494 | " </method>\n" |
495 | " <method name=\"GetTextBeforeOffset\">\n" |
496 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
497 | " <arg direction=\"in\" type=\"u\" name=\"type\"/>\n" |
498 | " <arg direction=\"out\" type=\"s\"/>\n" |
499 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
500 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
501 | " </method>\n" |
502 | " <method name=\"GetTextAtOffset\">\n" |
503 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
504 | " <arg direction=\"in\" type=\"u\" name=\"type\"/>\n" |
505 | " <arg direction=\"out\" type=\"s\"/>\n" |
506 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
507 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
508 | " </method>\n" |
509 | " <method name=\"GetTextAfterOffset\">\n" |
510 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
511 | " <arg direction=\"in\" type=\"u\" name=\"type\"/>\n" |
512 | " <arg direction=\"out\" type=\"s\"/>\n" |
513 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
514 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
515 | " </method>\n" |
516 | " <method name=\"GetCharacterAtOffset\">\n" |
517 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
518 | " <arg direction=\"out\" type=\"i\"/>\n" |
519 | " </method>\n" |
520 | " <method name=\"GetAttributeValue\">\n" |
521 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
522 | " <arg direction=\"in\" type=\"s\" name=\"attributeName\"/>\n" |
523 | " <arg direction=\"out\" type=\"s\"/>\n" |
524 | " </method>\n" |
525 | " <method name=\"GetAttributes\">\n" |
526 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
527 | " <arg direction=\"out\" type=\"a{ss}\"/>\n" |
528 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
529 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
530 | " <annotation value=\"QSpiAttributeSet\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
531 | " </method>\n" |
532 | " <method name=\"GetDefaultAttributes\">\n" |
533 | " <arg direction=\"out\" type=\"a{ss}\"/>\n" |
534 | " <annotation value=\"QSpiAttributeSet\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
535 | " </method>\n" |
536 | " <method name=\"GetCharacterExtents\">\n" |
537 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
538 | " <arg direction=\"out\" type=\"i\" name=\"x\"/>\n" |
539 | " <arg direction=\"out\" type=\"i\" name=\"y\"/>\n" |
540 | " <arg direction=\"out\" type=\"i\" name=\"width\"/>\n" |
541 | " <arg direction=\"out\" type=\"i\" name=\"height\"/>\n" |
542 | " <arg direction=\"in\" type=\"u\" name=\"coordType\"/>\n" |
543 | " </method>\n" |
544 | " <method name=\"GetOffsetAtPoint\">\n" |
545 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
546 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
547 | " <arg direction=\"in\" type=\"u\" name=\"coordType\"/>\n" |
548 | " <arg direction=\"out\" type=\"i\"/>\n" |
549 | " </method>\n" |
550 | " <method name=\"GetNSelections\">\n" |
551 | " <arg direction=\"out\" type=\"i\"/>\n" |
552 | " </method>\n" |
553 | " <method name=\"GetSelection\">\n" |
554 | " <arg direction=\"in\" type=\"i\" name=\"selectionNum\"/>\n" |
555 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
556 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
557 | " </method>\n" |
558 | " <method name=\"AddSelection\">\n" |
559 | " <arg direction=\"in\" type=\"i\" name=\"startOffset\"/>\n" |
560 | " <arg direction=\"in\" type=\"i\" name=\"endOffset\"/>\n" |
561 | " <arg direction=\"out\" type=\"b\"/>\n" |
562 | " </method>\n" |
563 | " <method name=\"RemoveSelection\">\n" |
564 | " <arg direction=\"in\" type=\"i\" name=\"selectionNum\"/>\n" |
565 | " <arg direction=\"out\" type=\"b\"/>\n" |
566 | " </method>\n" |
567 | " <method name=\"SetSelection\">\n" |
568 | " <arg direction=\"in\" type=\"i\" name=\"selectionNum\"/>\n" |
569 | " <arg direction=\"in\" type=\"i\" name=\"startOffset\"/>\n" |
570 | " <arg direction=\"in\" type=\"i\" name=\"endOffset\"/>\n" |
571 | " <arg direction=\"out\" type=\"b\"/>\n" |
572 | " </method>\n" |
573 | " <method name=\"GetRangeExtents\">\n" |
574 | " <arg direction=\"in\" type=\"i\" name=\"startOffset\"/>\n" |
575 | " <arg direction=\"in\" type=\"i\" name=\"endOffset\"/>\n" |
576 | " <arg direction=\"out\" type=\"i\" name=\"x\"/>\n" |
577 | " <arg direction=\"out\" type=\"i\" name=\"y\"/>\n" |
578 | " <arg direction=\"out\" type=\"i\" name=\"width\"/>\n" |
579 | " <arg direction=\"out\" type=\"i\" name=\"height\"/>\n" |
580 | " <arg direction=\"in\" type=\"u\" name=\"coordType\"/>\n" |
581 | " </method>\n" |
582 | " <method name=\"GetBoundedRanges\">\n" |
583 | " <arg direction=\"in\" type=\"i\" name=\"x\"/>\n" |
584 | " <arg direction=\"in\" type=\"i\" name=\"y\"/>\n" |
585 | " <arg direction=\"in\" type=\"i\" name=\"width\"/>\n" |
586 | " <arg direction=\"in\" type=\"i\" name=\"height\"/>\n" |
587 | " <arg direction=\"in\" type=\"u\" name=\"coordType\"/>\n" |
588 | " <arg direction=\"in\" type=\"u\" name=\"xClipType\"/>\n" |
589 | " <arg direction=\"in\" type=\"u\" name=\"yClipType\"/>\n" |
590 | " <arg direction=\"out\" type=\"a(iisv)\"/>\n" |
591 | " <annotation value=\"QSpiRangeList\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
592 | " </method>\n" |
593 | " <method name=\"GetAttributeRun\">\n" |
594 | " <arg direction=\"in\" type=\"i\" name=\"offset\"/>\n" |
595 | " <arg direction=\"in\" type=\"b\" name=\"includeDefaults\"/>\n" |
596 | " <arg direction=\"out\" type=\"a{ss}\"/>\n" |
597 | " <arg direction=\"out\" type=\"i\" name=\"startOffset\"/>\n" |
598 | " <arg direction=\"out\" type=\"i\" name=\"endOffset\"/>\n" |
599 | " <annotation value=\"QSpiAttributeSet\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
600 | " </method>\n" |
601 | " <method name=\"GetDefaultAttributeSet\">\n" |
602 | " <arg direction=\"out\" type=\"a{ss}\"/>\n" |
603 | " <annotation value=\"QSpiAttributeSet\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n" |
604 | " </method>\n" |
605 | " <method name=\"ScrollSubstringTo\">\n" |
606 | " <arg direction=\"in\" name=\"startOffset\" type=\"i\"/>\n" |
607 | " <arg direction=\"in\" name=\"endOffset\" type=\"i\"/>\n" |
608 | " <arg direction=\"in\" name=\"type\" type=\"u\"/>\n" |
609 | " <arg direction=\"out\" type=\"b\"/>\n" |
610 | " </method>\n" |
611 | " </interface>\n" |
612 | ); |
613 | |
614 | static const QLatin1StringView valueIntrospection( |
615 | " <interface name=\"org.a11y.atspi.Value\">\n" |
616 | " <property access=\"read\" type=\"d\" name=\"MinimumValue\"/>\n" |
617 | " <property access=\"read\" type=\"d\" name=\"MaximumValue\"/>\n" |
618 | " <property access=\"read\" type=\"d\" name=\"MinimumIncrement\"/>\n" |
619 | " <property access=\"readwrite\" type=\"d\" name=\"CurrentValue\"/>\n" |
620 | " <method name=\"SetCurrentValue\">\n" |
621 | " <arg direction=\"in\" type=\"d\" name=\"value\"/>\n" |
622 | " </method>\n" |
623 | " </interface>\n" |
624 | ); |
625 | |
626 | QAccessibleInterface * interface = interfaceFromPath(dbusPath: path); |
627 | if (!interface) { |
628 | qCWarning(lcAccessibilityAtspi) << "Could not find accessible on path:" << path; |
629 | return QString(); |
630 | } |
631 | |
632 | QStringList interfaces = accessibleInterfaces(interface); |
633 | |
634 | QString xml; |
635 | xml.append(s: accessibleIntrospection); |
636 | |
637 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_COMPONENT ""_L1 )) |
638 | xml.append(s: componentIntrospection); |
639 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_TEXT ""_L1 )) |
640 | xml.append(s: textIntrospection); |
641 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_EDITABLE_TEXT ""_L1 )) |
642 | xml.append(s: editableTextIntrospection); |
643 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_ACTION ""_L1 )) |
644 | xml.append(s: actionIntrospection); |
645 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_SELECTION ""_L1 )) |
646 | xml.append(s: selectionIntrospection); |
647 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_TABLE ""_L1 )) |
648 | xml.append(s: tableIntrospection); |
649 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_TABLE_CELL ""_L1 )) |
650 | xml.append(s: tableCellIntrospection); |
651 | if (interfaces.contains(ATSPI_DBUS_INTERFACE_VALUE ""_L1 )) |
652 | xml.append(s: valueIntrospection); |
653 | if (path == QSPI_OBJECT_PATH_ROOT ""_L1 ) |
654 | xml.append(s: applicationIntrospection); |
655 | |
656 | return xml; |
657 | } |
658 | |
659 | void AtSpiAdaptor::setBitFlag(const QString &flag) |
660 | { |
661 | Q_ASSERT(flag.size()); |
662 | |
663 | // assume we don't get nonsense - look at first letter only |
664 | switch (flag.at(i: 0).toLower().toLatin1()) { |
665 | case 'o': { |
666 | if (flag.size() <= 8) { // Object:: |
667 | sendObject = 1; |
668 | } else { // Object:Foo:Bar |
669 | QString right = flag.mid(position: 7); |
670 | if (false) { |
671 | } else if (right.startsWith(s: "ActiveDescendantChanged"_L1 )) { |
672 | sendObject_active_descendant_changed = 1; |
673 | } else if (right.startsWith(s: "AttributesChanged"_L1 )) { |
674 | sendObject_attributes_changed = 1; |
675 | } else if (right.startsWith(s: "BoundsChanged"_L1 )) { |
676 | sendObject_bounds_changed = 1; |
677 | } else if (right.startsWith(s: "ChildrenChanged"_L1 )) { |
678 | sendObject_children_changed = 1; |
679 | } else if (right.startsWith(s: "ColumnDeleted"_L1 )) { |
680 | sendObject_column_deleted = 1; |
681 | } else if (right.startsWith(s: "ColumnInserted"_L1 )) { |
682 | sendObject_column_inserted = 1; |
683 | } else if (right.startsWith(s: "ColumnReordered"_L1 )) { |
684 | sendObject_column_reordered = 1; |
685 | } else if (right.startsWith(s: "LinkSelected"_L1 )) { |
686 | sendObject_link_selected = 1; |
687 | } else if (right.startsWith(s: "ModelChanged"_L1 )) { |
688 | sendObject_model_changed = 1; |
689 | } else if (right.startsWith(s: "PropertyChange"_L1 )) { |
690 | if (right == "PropertyChange:AccessibleDescription"_L1 ) { |
691 | sendObject_property_change_accessible_description = 1; |
692 | } else if (right == "PropertyChange:AccessibleName"_L1 ) { |
693 | sendObject_property_change_accessible_name = 1; |
694 | } else if (right == "PropertyChange:AccessibleParent"_L1 ) { |
695 | sendObject_property_change_accessible_parent = 1; |
696 | } else if (right == "PropertyChange:AccessibleRole"_L1 ) { |
697 | sendObject_property_change_accessible_role = 1; |
698 | } else if (right == "PropertyChange:TableCaption"_L1 ) { |
699 | sendObject_property_change_accessible_table_caption = 1; |
700 | } else if (right == "PropertyChange:TableColumnDescription"_L1 ) { |
701 | sendObject_property_change_accessible_table_column_description = 1; |
702 | } else if (right == "PropertyChange:TableColumnHeader"_L1 ) { |
703 | sendObject_property_change_accessible_table_column_header = 1; |
704 | } else if (right == "PropertyChange:TableRowDescription"_L1 ) { |
705 | sendObject_property_change_accessible_table_row_description = 1; |
706 | } else if (right == "PropertyChange:TableRowHeader"_L1 ) { |
707 | sendObject_property_change_accessible_table_row_header = 1; |
708 | } else if (right == "PropertyChange:TableSummary"_L1 ) { |
709 | sendObject_property_change_accessible_table_summary = 1; |
710 | } else if (right == "PropertyChange:AccessibleValue"_L1 ) { |
711 | sendObject_property_change_accessible_value = 1; |
712 | } else { |
713 | sendObject_property_change = 1; |
714 | } |
715 | } else if (right.startsWith(s: "RowDeleted"_L1 )) { |
716 | sendObject_row_deleted = 1; |
717 | } else if (right.startsWith(s: "RowInserted"_L1 )) { |
718 | sendObject_row_inserted = 1; |
719 | } else if (right.startsWith(s: "RowReordered"_L1 )) { |
720 | sendObject_row_reordered = 1; |
721 | } else if (right.startsWith(s: "SelectionChanged"_L1 )) { |
722 | sendObject_selection_changed = 1; |
723 | } else if (right.startsWith(s: "StateChanged"_L1 )) { |
724 | sendObject_state_changed = 1; |
725 | } else if (right.startsWith(s: "TextAttributesChanged"_L1 )) { |
726 | sendObject_text_attributes_changed = 1; |
727 | } else if (right.startsWith(s: "TextBoundsChanged"_L1 )) { |
728 | sendObject_text_bounds_changed = 1; |
729 | } else if (right.startsWith(s: "TextCaretMoved"_L1 )) { |
730 | sendObject_text_caret_moved = 1; |
731 | } else if (right.startsWith(s: "TextChanged"_L1 )) { |
732 | sendObject_text_changed = 1; |
733 | } else if (right.startsWith(s: "TextSelectionChanged"_L1 )) { |
734 | sendObject_text_selection_changed = 1; |
735 | } else if (right.startsWith(s: "ValueChanged"_L1 )) { |
736 | sendObject_value_changed = 1; |
737 | } else if (right.startsWith(s: "VisibleDataChanged"_L1 ) |
738 | || right.startsWith(s: "VisibledataChanged"_L1 )) { // typo in libatspi |
739 | sendObject_visible_data_changed = 1; |
740 | } else { |
741 | qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; |
742 | } |
743 | } |
744 | break; |
745 | } |
746 | case 'w': { // window |
747 | if (flag.size() <= 8) { |
748 | sendWindow = 1; |
749 | } else { // object:Foo:Bar |
750 | QString right = flag.mid(position: 7); |
751 | if (false) { |
752 | } else if (right.startsWith(s: "Activate"_L1 )) { |
753 | sendWindow_activate = 1; |
754 | } else if (right.startsWith(s: "Close"_L1 )) { |
755 | sendWindow_close= 1; |
756 | } else if (right.startsWith(s: "Create"_L1 )) { |
757 | sendWindow_create = 1; |
758 | } else if (right.startsWith(s: "Deactivate"_L1 )) { |
759 | sendWindow_deactivate = 1; |
760 | } else if (right.startsWith(s: "Lower"_L1 )) { |
761 | sendWindow_lower = 1; |
762 | } else if (right.startsWith(s: "Maximize"_L1 )) { |
763 | sendWindow_maximize = 1; |
764 | } else if (right.startsWith(s: "Minimize"_L1 )) { |
765 | sendWindow_minimize = 1; |
766 | } else if (right.startsWith(s: "Move"_L1 )) { |
767 | sendWindow_move = 1; |
768 | } else if (right.startsWith(s: "Raise"_L1 )) { |
769 | sendWindow_raise = 1; |
770 | } else if (right.startsWith(s: "Reparent"_L1 )) { |
771 | sendWindow_reparent = 1; |
772 | } else if (right.startsWith(s: "Resize"_L1 )) { |
773 | sendWindow_resize = 1; |
774 | } else if (right.startsWith(s: "Restore"_L1 )) { |
775 | sendWindow_restore = 1; |
776 | } else if (right.startsWith(s: "Restyle"_L1 )) { |
777 | sendWindow_restyle = 1; |
778 | } else if (right.startsWith(s: "Shade"_L1 )) { |
779 | sendWindow_shade = 1; |
780 | } else if (right.startsWith(s: "Unshade"_L1 )) { |
781 | sendWindow_unshade = 1; |
782 | } else if (right.startsWith(s: "DesktopCreate"_L1 )) { |
783 | // ignore this one |
784 | } else if (right.startsWith(s: "DesktopDestroy"_L1 )) { |
785 | // ignore this one |
786 | } else { |
787 | qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; |
788 | } |
789 | } |
790 | break; |
791 | } |
792 | case 'f': { |
793 | sendFocus = 1; |
794 | break; |
795 | } |
796 | case 'd': { // document is not implemented |
797 | break; |
798 | } |
799 | case 't': { // terminal is not implemented |
800 | break; |
801 | } |
802 | case 'm': { // mouse* is handled in a different way by the gnome atspi stack |
803 | break; |
804 | } |
805 | default: |
806 | qCWarning(lcAccessibilityAtspi) << "Subscription string not handled:" << flag; |
807 | } |
808 | } |
809 | |
810 | /*! |
811 | Checks via dbus which events should be sent. |
812 | */ |
813 | void AtSpiAdaptor::updateEventListeners() |
814 | { |
815 | QDBusMessage m = QDBusMessage::createMethodCall(destination: "org.a11y.atspi.Registry"_L1 , |
816 | path: "/org/a11y/atspi/registry"_L1 , |
817 | interface: "org.a11y.atspi.Registry"_L1 , method: "GetRegisteredEvents"_L1 ); |
818 | QDBusReply<QSpiEventListenerArray> listenersReply = m_dbus->connection().call(message: m); |
819 | if (listenersReply.isValid()) { |
820 | const QSpiEventListenerArray evList = listenersReply.value(); |
821 | for (const QSpiEventListener &ev : evList) |
822 | setBitFlag(ev.eventName); |
823 | m_applicationAdaptor->sendEvents(active: !evList.isEmpty()); |
824 | } else { |
825 | qCDebug(lcAccessibilityAtspi) << "Could not query active accessibility event listeners." ; |
826 | } |
827 | } |
828 | |
829 | void AtSpiAdaptor::eventListenerDeregistered(const QString &/*bus*/, const QString &/*path*/) |
830 | { |
831 | // qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::eventListenerDeregistered: " << bus << path; |
832 | updateEventListeners(); |
833 | } |
834 | |
835 | void AtSpiAdaptor::eventListenerRegistered(const QString &/*bus*/, const QString &/*path*/) |
836 | { |
837 | // qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::eventListenerRegistered: " << bus << path; |
838 | updateEventListeners(); |
839 | } |
840 | |
841 | /*! |
842 | This slot needs to get called when a \a window has be activated or deactivated (become focused). |
843 | When \a active is true, the window just received focus, otherwise it lost the focus. |
844 | */ |
845 | void AtSpiAdaptor::windowActivated(QObject* window, bool active) |
846 | { |
847 | if (!(sendWindow || sendWindow_activate)) |
848 | return; |
849 | |
850 | QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(window); |
851 | // If the window has been quickly activated or disabled, it will cause a crash. |
852 | if (iface == nullptr) |
853 | return; |
854 | Q_ASSERT(!active || iface->isValid()); |
855 | |
856 | QString windowTitle; |
857 | // in dtor it may be invalid |
858 | if (iface->isValid()) |
859 | windowTitle = iface->text(t: QAccessible::Name); |
860 | |
861 | QDBusVariant data; |
862 | data.setVariant(windowTitle); |
863 | |
864 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: data)); |
865 | |
866 | QString status = active ? "Activate"_L1 : "Deactivate"_L1 ; |
867 | QString path = pathForObject(object: window); |
868 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_WINDOW ""_L1 , name: status, arguments: args); |
869 | |
870 | QVariantList stateArgs = packDBusSignalArguments(type: "active"_L1 , data1: active ? 1 : 0, data2: 0, variantData: variantForPath(path)); |
871 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , name: "StateChanged"_L1 , arguments: stateArgs); |
872 | } |
873 | |
874 | QVariantList AtSpiAdaptor::packDBusSignalArguments(const QString &type, int data1, int data2, const QVariant &variantData) const |
875 | { |
876 | QVariantList arguments; |
877 | arguments << type << data1 << data2 << variantData |
878 | << QVariant::fromValue(value: QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(QSPI_OBJECT_PATH_ROOT))); |
879 | return arguments; |
880 | } |
881 | |
882 | QVariant AtSpiAdaptor::variantForPath(const QString &path) const |
883 | { |
884 | QDBusVariant data; |
885 | data.setVariant(QVariant::fromValue(value: QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(path)))); |
886 | return QVariant::fromValue(value: data); |
887 | } |
888 | |
889 | bool AtSpiAdaptor::sendDBusSignal(const QString &path, const QString &interface, const QString &signalName, const QVariantList &arguments) const |
890 | { |
891 | QDBusMessage message = QDBusMessage::createSignal(path, interface, name: signalName); |
892 | message.setArguments(arguments); |
893 | return m_dbus->connection().send(message); |
894 | } |
895 | |
896 | QAccessibleInterface *AtSpiAdaptor::interfaceFromPath(const QString& dbusPath) const |
897 | { |
898 | if (dbusPath == QSPI_OBJECT_PATH_ROOT ""_L1 ) |
899 | return QAccessible::queryAccessibleInterface(qApp); |
900 | |
901 | QStringList parts = dbusPath.split(sep: u'/'); |
902 | if (parts.size() != 6) { |
903 | qCDebug(lcAccessibilityAtspi) << "invalid path: " << dbusPath; |
904 | return nullptr; |
905 | } |
906 | |
907 | QString objectString = parts.at(i: 5); |
908 | QAccessible::Id id = objectString.toUInt(); |
909 | |
910 | // The id is always in the range [INT_MAX+1, UINT_MAX] |
911 | if ((int)id >= 0) |
912 | qCWarning(lcAccessibilityAtspi) << "No accessible object found for id: " << id; |
913 | |
914 | return QAccessible::accessibleInterface(uniqueId: id); |
915 | } |
916 | |
917 | void AtSpiAdaptor::notifyStateChange(QAccessibleInterface *interface, const QString &state, int value) |
918 | { |
919 | QString path = pathForInterface(interface); |
920 | QVariantList stateArgs = packDBusSignalArguments(type: state, data1: value, data2: 0, variantData: variantForPath(path)); |
921 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "StateChanged"_L1 , arguments: stateArgs); |
922 | } |
923 | |
924 | |
925 | /*! |
926 | This function gets called when Qt notifies about accessibility updates. |
927 | */ |
928 | void AtSpiAdaptor::notify(QAccessibleEvent *event) |
929 | { |
930 | switch (event->type()) { |
931 | case QAccessible::ObjectCreated: |
932 | if (sendObject || sendObject_children_changed) |
933 | notifyAboutCreation(interface: event->accessibleInterface()); |
934 | break; |
935 | case QAccessible::ObjectShow: { |
936 | if (sendObject || sendObject_state_changed) { |
937 | notifyStateChange(interface: event->accessibleInterface(), state: "showing"_L1 , value: 1); |
938 | } |
939 | break; |
940 | } |
941 | case QAccessible::ObjectHide: { |
942 | if (sendObject || sendObject_state_changed) { |
943 | notifyStateChange(interface: event->accessibleInterface(), state: "showing"_L1 , value: 0); |
944 | } |
945 | break; |
946 | } |
947 | case QAccessible::ObjectDestroyed: { |
948 | if (sendObject || sendObject_state_changed) |
949 | notifyAboutDestruction(interface: event->accessibleInterface()); |
950 | break; |
951 | } |
952 | case QAccessible::ObjectReorder: { |
953 | if (sendObject || sendObject_children_changed) |
954 | childrenChanged(interface: event->accessibleInterface()); |
955 | break; |
956 | } |
957 | case QAccessible::NameChanged: { |
958 | if (sendObject || sendObject_property_change || sendObject_property_change_accessible_name) { |
959 | QAccessibleInterface *iface = event->accessibleInterface(); |
960 | if (!iface) { |
961 | qCDebug(lcAccessibilityAtspi, |
962 | "NameChanged event from invalid accessible." ); |
963 | return; |
964 | } |
965 | |
966 | QString path = pathForInterface(interface: iface); |
967 | QVariantList args = packDBusSignalArguments( |
968 | type: "accessible-name"_L1 , data1: 0, data2: 0, |
969 | variantData: QVariant::fromValue(value: QDBusVariant(iface->text(t: QAccessible::Name)))); |
970 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
971 | signalName: "PropertyChange"_L1 , arguments: args); |
972 | } |
973 | break; |
974 | } |
975 | case QAccessible::DescriptionChanged: { |
976 | if (sendObject || sendObject_property_change || sendObject_property_change_accessible_description) { |
977 | QAccessibleInterface *iface = event->accessibleInterface(); |
978 | if (!iface) { |
979 | qCDebug(lcAccessibilityAtspi, |
980 | "DescriptionChanged event from invalid accessible." ); |
981 | return; |
982 | } |
983 | |
984 | QString path = pathForInterface(interface: iface); |
985 | QVariantList args = packDBusSignalArguments( |
986 | type: "accessible-description"_L1 , data1: 0, data2: 0, |
987 | variantData: QVariant::fromValue(value: QDBusVariant(iface->text(t: QAccessible::Description)))); |
988 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
989 | signalName: "PropertyChange"_L1 , arguments: args); |
990 | } |
991 | break; |
992 | } |
993 | case QAccessible::Focus: { |
994 | if (sendFocus || sendObject || sendObject_state_changed) |
995 | sendFocusChanged(interface: event->accessibleInterface()); |
996 | break; |
997 | } |
998 | case QAccessible::TextInserted: |
999 | case QAccessible::TextRemoved: |
1000 | case QAccessible::TextUpdated: { |
1001 | if (sendObject || sendObject_text_changed) { |
1002 | QAccessibleInterface * iface = event->accessibleInterface(); |
1003 | if (!iface || !iface->textInterface()) { |
1004 | qCWarning(lcAccessibilityAtspi) << "Received text event for invalid interface." ; |
1005 | return; |
1006 | } |
1007 | QString path = pathForInterface(interface: iface); |
1008 | |
1009 | int changePosition = 0; |
1010 | int cursorPosition = 0; |
1011 | QString textRemoved; |
1012 | QString textInserted; |
1013 | |
1014 | if (event->type() == QAccessible::TextInserted) { |
1015 | QAccessibleTextInsertEvent *textEvent = static_cast<QAccessibleTextInsertEvent*>(event); |
1016 | textInserted = textEvent->textInserted(); |
1017 | changePosition = textEvent->changePosition(); |
1018 | cursorPosition = textEvent->cursorPosition(); |
1019 | } else if (event->type() == QAccessible::TextRemoved) { |
1020 | QAccessibleTextRemoveEvent *textEvent = static_cast<QAccessibleTextRemoveEvent*>(event); |
1021 | textRemoved = textEvent->textRemoved(); |
1022 | changePosition = textEvent->changePosition(); |
1023 | cursorPosition = textEvent->cursorPosition(); |
1024 | } else if (event->type() == QAccessible::TextUpdated) { |
1025 | QAccessibleTextUpdateEvent *textEvent = static_cast<QAccessibleTextUpdateEvent*>(event); |
1026 | textInserted = textEvent->textInserted(); |
1027 | textRemoved = textEvent->textRemoved(); |
1028 | changePosition = textEvent->changePosition(); |
1029 | cursorPosition = textEvent->cursorPosition(); |
1030 | } |
1031 | |
1032 | QDBusVariant data; |
1033 | |
1034 | if (!textRemoved.isEmpty()) { |
1035 | data.setVariant(QVariant::fromValue(value: textRemoved)); |
1036 | QVariantList args = packDBusSignalArguments(type: "delete"_L1 , data1: changePosition, data2: textRemoved.size(), variantData: QVariant::fromValue(value: data)); |
1037 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1038 | signalName: "TextChanged"_L1 , arguments: args); |
1039 | } |
1040 | |
1041 | if (!textInserted.isEmpty()) { |
1042 | data.setVariant(QVariant::fromValue(value: textInserted)); |
1043 | QVariantList args = packDBusSignalArguments(type: "insert"_L1 , data1: changePosition, data2: textInserted.size(), variantData: QVariant::fromValue(value: data)); |
1044 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1045 | signalName: "TextChanged"_L1 , arguments: args); |
1046 | } |
1047 | |
1048 | // send a cursor update |
1049 | Q_UNUSED(cursorPosition); |
1050 | // QDBusVariant cursorData; |
1051 | // cursorData.setVariant(QVariant::fromValue(cursorPosition)); |
1052 | // QVariantList args = packDBusSignalArguments(QString(), cursorPosition, 0, QVariant::fromValue(cursorData)); |
1053 | // sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, |
1054 | // "TextCaretMoved"_L1, args); |
1055 | } |
1056 | break; |
1057 | } |
1058 | case QAccessible::TextCaretMoved: { |
1059 | if (sendObject || sendObject_text_caret_moved) { |
1060 | QAccessibleInterface * iface = event->accessibleInterface(); |
1061 | if (!iface || !iface->textInterface()) { |
1062 | qCWarning(lcAccessibilityAtspi) << "Sending TextCaretMoved from object that does not implement text interface: " << iface; |
1063 | return; |
1064 | } |
1065 | |
1066 | QString path = pathForInterface(interface: iface); |
1067 | QDBusVariant cursorData; |
1068 | int pos = iface->textInterface()->cursorPosition(); |
1069 | cursorData.setVariant(QVariant::fromValue(value: pos)); |
1070 | QVariantList args = packDBusSignalArguments(type: QString(), data1: pos, data2: 0, variantData: QVariant::fromValue(value: cursorData)); |
1071 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1072 | signalName: "TextCaretMoved"_L1 , arguments: args); |
1073 | } |
1074 | break; |
1075 | } |
1076 | case QAccessible::TextSelectionChanged: { |
1077 | if (sendObject || sendObject_text_selection_changed) { |
1078 | QAccessibleInterface * iface = event->accessibleInterface(); |
1079 | QString path = pathForInterface(interface: iface); |
1080 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1081 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1082 | signalName: "TextSelectionChanged"_L1 , arguments: args); |
1083 | } |
1084 | break; |
1085 | } |
1086 | case QAccessible::ValueChanged: { |
1087 | if (sendObject || sendObject_value_changed || sendObject_property_change_accessible_value) { |
1088 | QAccessibleInterface * iface = event->accessibleInterface(); |
1089 | if (!iface) { |
1090 | qCWarning(lcAccessibilityAtspi) << "ValueChanged event from invalid accessible." ; |
1091 | return; |
1092 | } |
1093 | if (iface->valueInterface()) { |
1094 | QString path = pathForInterface(interface: iface); |
1095 | QVariantList args = packDBusSignalArguments(type: "accessible-value"_L1 , data1: 0, data2: 0, variantData: variantForPath(path)); |
1096 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1097 | signalName: "PropertyChange"_L1 , arguments: args); |
1098 | } else if (iface->role() == QAccessible::ComboBox) { |
1099 | // Combo Box with AT-SPI likes to be special |
1100 | // It requires a name-change to update caches and then selection-changed |
1101 | QString path = pathForInterface(interface: iface); |
1102 | QVariantList args1 = packDBusSignalArguments( |
1103 | type: "accessible-name"_L1 , data1: 0, data2: 0, |
1104 | variantData: QVariant::fromValue(value: QDBusVariant(iface->text(t: QAccessible::Name)))); |
1105 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1106 | signalName: "PropertyChange"_L1 , arguments: args1); |
1107 | QVariantList args2 = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(0)))); |
1108 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1109 | signalName: "SelectionChanged"_L1 , arguments: args2); |
1110 | } else { |
1111 | qCWarning(lcAccessibilityAtspi) << "ValueChanged event and no ValueInterface or ComboBox: " << iface; |
1112 | } |
1113 | } |
1114 | break; |
1115 | } |
1116 | case QAccessible::SelectionAdd: |
1117 | case QAccessible::SelectionRemove: |
1118 | case QAccessible::Selection: { |
1119 | QAccessibleInterface * iface = event->accessibleInterface(); |
1120 | if (!iface) { |
1121 | qCWarning(lcAccessibilityAtspi) << "Selection event from invalid accessible." ; |
1122 | return; |
1123 | } |
1124 | // send event for change of selected state for the interface itself |
1125 | QString path = pathForInterface(interface: iface); |
1126 | int selected = iface->state().selected ? 1 : 0; |
1127 | QVariantList stateArgs = packDBusSignalArguments(type: "selected"_L1 , data1: selected, data2: 0, variantData: variantForPath(path)); |
1128 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "StateChanged"_L1 , arguments: stateArgs); |
1129 | |
1130 | // send SelectionChanged event for the parent |
1131 | QAccessibleInterface* parent = iface->parent(); |
1132 | if (!parent) { |
1133 | qCDebug(lcAccessibilityAtspi) << "No valid parent in selection event." ; |
1134 | return; |
1135 | } |
1136 | |
1137 | QString parentPath = pathForInterface(interface: parent); |
1138 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: variantForPath(path: parentPath)); |
1139 | sendDBusSignal(path: parentPath, interface: QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), |
1140 | signalName: QLatin1String("SelectionChanged" ), arguments: args); |
1141 | break; |
1142 | } |
1143 | case QAccessible::SelectionWithin: { |
1144 | QAccessibleInterface * iface = event->accessibleInterface(); |
1145 | if (!iface) { |
1146 | qCWarning(lcAccessibilityAtspi) << "SelectionWithin event from invalid accessible." ; |
1147 | return; |
1148 | } |
1149 | |
1150 | QString path = pathForInterface(interface: iface); |
1151 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: variantForPath(path)); |
1152 | sendDBusSignal(path, interface: QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), signalName: QLatin1String("SelectionChanged" ), arguments: args); |
1153 | break; |
1154 | } |
1155 | case QAccessible::StateChanged: { |
1156 | if (sendObject || sendObject_state_changed || sendWindow || sendWindow_activate) { |
1157 | QAccessible::State stateChange = static_cast<QAccessibleStateChangeEvent*>(event)->changedStates(); |
1158 | if (stateChange.checked) { |
1159 | QAccessibleInterface * iface = event->accessibleInterface(); |
1160 | if (!iface) { |
1161 | qCWarning(lcAccessibilityAtspi) << "StateChanged event from invalid accessible." ; |
1162 | return; |
1163 | } |
1164 | int checked = iface->state().checked; |
1165 | notifyStateChange(interface: iface, state: "checked"_L1 , value: checked); |
1166 | } else if (stateChange.active) { |
1167 | QAccessibleInterface * iface = event->accessibleInterface(); |
1168 | if (!iface || !(iface->role() == QAccessible::Window && (sendWindow || sendWindow_activate))) |
1169 | return; |
1170 | int isActive = iface->state().active; |
1171 | QString windowTitle = iface->text(t: QAccessible::Name); |
1172 | QDBusVariant data; |
1173 | data.setVariant(windowTitle); |
1174 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: data)); |
1175 | QString status = isActive ? "Activate"_L1 : "Deactivate"_L1 ; |
1176 | QString path = pathForInterface(interface: iface); |
1177 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_WINDOW ""_L1 , signalName: status, arguments: args); |
1178 | notifyStateChange(interface: iface, state: "active"_L1 , value: isActive); |
1179 | } else if (stateChange.disabled) { |
1180 | QAccessibleInterface *iface = event->accessibleInterface(); |
1181 | QAccessible::State state = iface->state(); |
1182 | bool enabled = !state.disabled; |
1183 | |
1184 | notifyStateChange(interface: iface, state: "enabled"_L1 , value: enabled); |
1185 | notifyStateChange(interface: iface, state: "sensitive"_L1 , value: enabled); |
1186 | } else if (stateChange.focused) { |
1187 | QAccessibleInterface *iface = event->accessibleInterface(); |
1188 | QAccessible::State state = iface->state(); |
1189 | bool focused = state.focused; |
1190 | notifyStateChange(interface: iface, state: "focused"_L1 , value: focused); |
1191 | } |
1192 | } |
1193 | break; |
1194 | } |
1195 | case QAccessible::TableModelChanged: { |
1196 | QAccessibleInterface *interface = event->accessibleInterface(); |
1197 | if (!interface || !interface->isValid()) { |
1198 | qCWarning(lcAccessibilityAtspi) << "TableModelChanged event from invalid accessible." ; |
1199 | return; |
1200 | } |
1201 | |
1202 | const QString path = pathForInterface(interface); |
1203 | QAccessibleTableModelChangeEvent *tableModelEvent = static_cast<QAccessibleTableModelChangeEvent*>(event); |
1204 | switch (tableModelEvent->modelChangeType()) { |
1205 | case QAccessibleTableModelChangeEvent::ColumnsInserted: { |
1206 | if (sendObject || sendObject_column_inserted) { |
1207 | const int firstColumn = tableModelEvent->firstColumn(); |
1208 | const int insertedColumnCount = tableModelEvent->lastColumn() - firstColumn + 1; |
1209 | QVariantList args = packDBusSignalArguments(type: QString(), data1: firstColumn, data2: insertedColumnCount, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1210 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ColumnInserted"_L1 , arguments: args); |
1211 | } |
1212 | break; |
1213 | } |
1214 | case QAccessibleTableModelChangeEvent::ColumnsRemoved: { |
1215 | if (sendObject || sendObject_column_deleted) { |
1216 | const int firstColumn = tableModelEvent->firstColumn(); |
1217 | const int removedColumnCount = tableModelEvent->lastColumn() - firstColumn + 1; |
1218 | QVariantList args = packDBusSignalArguments(type: QString(), data1: firstColumn, data2: removedColumnCount, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1219 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ColumnDeleted"_L1 , arguments: args); |
1220 | } |
1221 | break; |
1222 | } |
1223 | case QAccessibleTableModelChangeEvent::RowsInserted: { |
1224 | if (sendObject || sendObject_row_inserted) { |
1225 | const int firstRow = tableModelEvent->firstRow(); |
1226 | const int insertedRowCount = tableModelEvent->lastRow() - firstRow + 1; |
1227 | QVariantList args = packDBusSignalArguments(type: QString(), data1: firstRow, data2: insertedRowCount, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1228 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "RowInserted"_L1 , arguments: args); |
1229 | } |
1230 | break; |
1231 | } |
1232 | case QAccessibleTableModelChangeEvent::RowsRemoved: { |
1233 | if (sendObject || sendObject_row_deleted) { |
1234 | const int firstRow = tableModelEvent->firstRow(); |
1235 | const int removedRowCount = tableModelEvent->lastRow() - firstRow + 1; |
1236 | QVariantList args = packDBusSignalArguments(type: QString(), data1: firstRow, data2: removedRowCount, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1237 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "RowDeleted"_L1 , arguments: args); |
1238 | } |
1239 | break; |
1240 | } |
1241 | case QAccessibleTableModelChangeEvent::ModelChangeType::ModelReset: { |
1242 | if (sendObject || sendObject_model_changed) { |
1243 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1244 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ModelChanged"_L1 , arguments: args); |
1245 | } |
1246 | break; |
1247 | } |
1248 | case QAccessibleTableModelChangeEvent::DataChanged: { |
1249 | if (sendObject || sendObject_visible_data_changed) { |
1250 | QVariantList args = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: QVariant::fromValue(value: QDBusVariant(QVariant(QString())))); |
1251 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "VisibleDataChanged"_L1 , arguments: args); |
1252 | } |
1253 | break; |
1254 | } |
1255 | } |
1256 | break; |
1257 | } |
1258 | |
1259 | // For now we ignore these events |
1260 | case QAccessible::ParentChanged: |
1261 | case QAccessible::DialogStart: |
1262 | case QAccessible::DialogEnd: |
1263 | case QAccessible::PopupMenuStart: |
1264 | case QAccessible::PopupMenuEnd: |
1265 | case QAccessible::SoundPlayed: |
1266 | case QAccessible::Alert: |
1267 | case QAccessible::ForegroundChanged: |
1268 | case QAccessible::MenuStart: |
1269 | case QAccessible::MenuEnd: |
1270 | case QAccessible::ContextHelpStart: |
1271 | case QAccessible::ContextHelpEnd: |
1272 | case QAccessible::DragDropStart: |
1273 | case QAccessible::DragDropEnd: |
1274 | case QAccessible::ScrollingStart: |
1275 | case QAccessible::ScrollingEnd: |
1276 | case QAccessible::MenuCommand: |
1277 | case QAccessible::ActionChanged: |
1278 | case QAccessible::ActiveDescendantChanged: |
1279 | case QAccessible::AttributeChanged: |
1280 | case QAccessible::DocumentContentChanged: |
1281 | case QAccessible::DocumentLoadComplete: |
1282 | case QAccessible::DocumentLoadStopped: |
1283 | case QAccessible::DocumentReload: |
1284 | case QAccessible::HyperlinkEndIndexChanged: |
1285 | case QAccessible::HyperlinkNumberOfAnchorsChanged: |
1286 | case QAccessible::HyperlinkSelectedLinkChanged: |
1287 | case QAccessible::HypertextLinkActivated: |
1288 | case QAccessible::HypertextLinkSelected: |
1289 | case QAccessible::HyperlinkStartIndexChanged: |
1290 | case QAccessible::HypertextChanged: |
1291 | case QAccessible::HypertextNLinksChanged: |
1292 | case QAccessible::ObjectAttributeChanged: |
1293 | case QAccessible::PageChanged: |
1294 | case QAccessible::SectionChanged: |
1295 | case QAccessible::TableCaptionChanged: |
1296 | case QAccessible::TableColumnDescriptionChanged: |
1297 | case QAccessible::TableColumnHeaderChanged: |
1298 | case QAccessible::TableRowDescriptionChanged: |
1299 | case QAccessible::TableRowHeaderChanged: |
1300 | case QAccessible::TableSummaryChanged: |
1301 | case QAccessible::TextAttributeChanged: |
1302 | case QAccessible::TextColumnChanged: |
1303 | case QAccessible::VisibleDataChanged: |
1304 | case QAccessible::LocationChanged: |
1305 | case QAccessible::HelpChanged: |
1306 | case QAccessible::DefaultActionChanged: |
1307 | case QAccessible::AcceleratorChanged: |
1308 | case QAccessible::InvalidEvent: |
1309 | break; |
1310 | } |
1311 | } |
1312 | |
1313 | void AtSpiAdaptor::sendFocusChanged(QAccessibleInterface *interface) const |
1314 | { |
1315 | static QString lastFocusPath; |
1316 | // "remove" old focus |
1317 | if (!lastFocusPath.isEmpty()) { |
1318 | QVariantList stateArgs = packDBusSignalArguments(type: "focused"_L1 , data1: 0, data2: 0, variantData: variantForPath(path: lastFocusPath)); |
1319 | sendDBusSignal(path: lastFocusPath, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1320 | signalName: "StateChanged"_L1 , arguments: stateArgs); |
1321 | } |
1322 | // send new focus |
1323 | { |
1324 | QString path = pathForInterface(interface); |
1325 | |
1326 | QVariantList stateArgs = packDBusSignalArguments(type: "focused"_L1 , data1: 1, data2: 0, variantData: variantForPath(path)); |
1327 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , |
1328 | signalName: "StateChanged"_L1 , arguments: stateArgs); |
1329 | |
1330 | QVariantList focusArgs = packDBusSignalArguments(type: QString(), data1: 0, data2: 0, variantData: variantForPath(path)); |
1331 | sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_FOCUS ""_L1 , signalName: "Focus"_L1 , arguments: focusArgs); |
1332 | lastFocusPath = path; |
1333 | } |
1334 | } |
1335 | |
1336 | void AtSpiAdaptor::childrenChanged(QAccessibleInterface *interface) const |
1337 | { |
1338 | QString parentPath = pathForInterface(interface); |
1339 | int childCount = interface->childCount(); |
1340 | for (int i = 0; i < interface->childCount(); ++i) { |
1341 | QString childPath = pathForInterface(interface: interface->child(index: i)); |
1342 | QVariantList args = packDBusSignalArguments(type: "add"_L1 , data1: childCount, data2: 0, variantData: childPath); |
1343 | sendDBusSignal(path: parentPath, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ChildrenChanged"_L1 , arguments: args); |
1344 | } |
1345 | } |
1346 | |
1347 | void AtSpiAdaptor::notifyAboutCreation(QAccessibleInterface *interface) const |
1348 | { |
1349 | // notify about the new child of our parent |
1350 | QAccessibleInterface * parent = interface->parent(); |
1351 | if (!parent) { |
1352 | qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::notifyAboutCreation: Could not find parent for " << interface->object(); |
1353 | return; |
1354 | } |
1355 | QString path = pathForInterface(interface); |
1356 | int childCount = parent->childCount(); |
1357 | QString parentPath = pathForInterface(interface: parent); |
1358 | QVariantList args = packDBusSignalArguments(type: "add"_L1 , data1: childCount, data2: 0, variantData: variantForPath(path)); |
1359 | sendDBusSignal(path: parentPath, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ChildrenChanged"_L1 , arguments: args); |
1360 | } |
1361 | |
1362 | void AtSpiAdaptor::notifyAboutDestruction(QAccessibleInterface *interface) const |
1363 | { |
1364 | if (!interface || !interface->isValid()) |
1365 | return; |
1366 | |
1367 | QAccessibleInterface * parent = interface->parent(); |
1368 | if (!parent) { |
1369 | qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::notifyAboutDestruction: Could not find parent for " << interface->object(); |
1370 | return; |
1371 | } |
1372 | QString path = pathForInterface(interface); |
1373 | |
1374 | // this is in the destructor. we have no clue which child we used to be. |
1375 | // FIXME |
1376 | int childIndex = -1; |
1377 | // if (child) { |
1378 | // childIndex = child; |
1379 | // } else { |
1380 | // childIndex = parent->indexOfChild(interface); |
1381 | // } |
1382 | |
1383 | QString parentPath = pathForInterface(interface: parent); |
1384 | QVariantList args = packDBusSignalArguments(type: "remove"_L1 , data1: childIndex, data2: 0, variantData: variantForPath(path)); |
1385 | sendDBusSignal(path: parentPath, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1 , signalName: "ChildrenChanged"_L1 , arguments: args); |
1386 | } |
1387 | |
1388 | /*! |
1389 | Handle incoming DBus message. |
1390 | This function dispatches the dbus message to the right interface handler. |
1391 | */ |
1392 | bool AtSpiAdaptor::handleMessage(const QDBusMessage &message, const QDBusConnection &connection) |
1393 | { |
1394 | // get accessible interface |
1395 | QAccessibleInterface * accessible = interfaceFromPath(dbusPath: message.path()); |
1396 | if (!accessible) { |
1397 | qCWarning(lcAccessibilityAtspi) << "Could not find accessible on path:" << message.path(); |
1398 | return false; |
1399 | } |
1400 | if (!accessible->isValid()) { |
1401 | qCWarning(lcAccessibilityAtspi) << "Accessible invalid:" << accessible << message.path(); |
1402 | return false; |
1403 | } |
1404 | |
1405 | QString interface = message.interface(); |
1406 | QString function = message.member(); |
1407 | |
1408 | // qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::handleMessage: " << interface << function; |
1409 | |
1410 | if (function == "Introspect"_L1 ) { |
1411 | //introspect(message.path()); |
1412 | return false; |
1413 | } |
1414 | |
1415 | // handle properties like regular functions |
1416 | if (interface == "org.freedesktop.DBus.Properties"_L1 ) { |
1417 | interface = message.arguments().at(i: 0).toString(); |
1418 | // Get/Set + Name |
1419 | function = message.member() + message.arguments().at(i: 1).toString(); |
1420 | } |
1421 | |
1422 | // switch interface to call |
1423 | if (interface == ATSPI_DBUS_INTERFACE_ACCESSIBLE ""_L1 ) |
1424 | return accessibleInterface(interface: accessible, function, message, connection); |
1425 | if (interface == ATSPI_DBUS_INTERFACE_APPLICATION ""_L1 ) |
1426 | return applicationInterface(interface: accessible, function, message, connection); |
1427 | if (interface == ATSPI_DBUS_INTERFACE_COMPONENT ""_L1 ) |
1428 | return componentInterface(interface: accessible, function, message, connection); |
1429 | if (interface == ATSPI_DBUS_INTERFACE_ACTION ""_L1 ) |
1430 | return actionInterface(interface: accessible, function, message, connection); |
1431 | if (interface == ATSPI_DBUS_INTERFACE_SELECTION ""_L1 ) |
1432 | return selectionInterface(interface: accessible, function, message, connection); |
1433 | if (interface == ATSPI_DBUS_INTERFACE_TEXT ""_L1 ) |
1434 | return textInterface(interface: accessible, function, message, connection); |
1435 | if (interface == ATSPI_DBUS_INTERFACE_EDITABLE_TEXT ""_L1 ) |
1436 | return editableTextInterface(interface: accessible, function, message, connection); |
1437 | if (interface == ATSPI_DBUS_INTERFACE_VALUE ""_L1 ) |
1438 | return valueInterface(interface: accessible, function, message, connection); |
1439 | if (interface == ATSPI_DBUS_INTERFACE_TABLE ""_L1 ) |
1440 | return tableInterface(interface: accessible, function, message, connection); |
1441 | if (interface == ATSPI_DBUS_INTERFACE_TABLE_CELL ""_L1 ) |
1442 | return tableCellInterface(interface: accessible, function, message, connection); |
1443 | |
1444 | qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::handleMessage with unknown interface: " << message.path() << interface << function; |
1445 | return false; |
1446 | } |
1447 | |
1448 | // Application |
1449 | bool AtSpiAdaptor::applicationInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
1450 | { |
1451 | if (message.path() != ATSPI_DBUS_PATH_ROOT ""_L1 ) { |
1452 | qCWarning(lcAccessibilityAtspi) << "Could not find application interface for:" << message.path() << interface; |
1453 | return false; |
1454 | } |
1455 | |
1456 | if (function == "SetId"_L1 ) { |
1457 | Q_ASSERT(message.signature() == "ssv"_L1 ); |
1458 | QVariant value = qvariant_cast<QDBusVariant>(v: message.arguments().at(i: 2)).variant(); |
1459 | |
1460 | m_applicationId = value.toInt(); |
1461 | return true; |
1462 | } |
1463 | if (function == "GetId"_L1 ) { |
1464 | Q_ASSERT(message.signature() == "ss"_L1 ); |
1465 | QDBusMessage reply = message.createReply(argument: QVariant::fromValue(value: QDBusVariant(m_applicationId))); |
1466 | return connection.send(message: reply); |
1467 | } |
1468 | if (function == "GetToolkitName"_L1 ) { |
1469 | Q_ASSERT(message.signature() == "ss"_L1 ); |
1470 | QDBusMessage reply = message.createReply(argument: QVariant::fromValue(value: QDBusVariant("Qt"_L1 ))); |
1471 | return connection.send(message: reply); |
1472 | } |
1473 | if (function == "GetVersion"_L1 ) { |
1474 | Q_ASSERT(message.signature() == "ss"_L1 ); |
1475 | QDBusMessage reply = message.createReply(argument: QVariant::fromValue(value: QDBusVariant(QLatin1StringView(qVersion())))); |
1476 | return connection.send(message: reply); |
1477 | } |
1478 | if (function == "GetLocale"_L1 ) { |
1479 | Q_ASSERT(message.signature() == "u"_L1 ); |
1480 | QDBusMessage reply = message.createReply(argument: QVariant::fromValue(value: QLocale().name())); |
1481 | return connection.send(message: reply); |
1482 | } |
1483 | qCDebug(lcAccessibilityAtspi) << "AtSpiAdaptor::applicationInterface " << message.path() << interface << function; |
1484 | return false; |
1485 | } |
1486 | |
1487 | /*! |
1488 | Register this application as accessible on the accessibility DBus. |
1489 | */ |
1490 | void AtSpiAdaptor::registerApplication() |
1491 | { |
1492 | OrgA11yAtspiSocketInterface *registry; |
1493 | registry = new OrgA11yAtspiSocketInterface(QSPI_REGISTRY_NAME ""_L1 , |
1494 | QSPI_OBJECT_PATH_ROOT ""_L1 , m_dbus->connection()); |
1495 | |
1496 | QDBusPendingReply<QSpiObjectReference> reply; |
1497 | QSpiObjectReference ref = QSpiObjectReference(m_dbus->connection(), QDBusObjectPath(QSPI_OBJECT_PATH_ROOT)); |
1498 | reply = registry->Embed(ref); |
1499 | reply.waitForFinished(); // TODO: make this async |
1500 | if (reply.isValid ()) { |
1501 | const QSpiObjectReference &socket = reply.value(); |
1502 | accessibilityRegistry = QSpiObjectReference(socket); |
1503 | } else { |
1504 | qCWarning(lcAccessibilityAtspi) << "Error in contacting registry:" |
1505 | << reply.error().name() |
1506 | << reply.error().message(); |
1507 | } |
1508 | delete registry; |
1509 | } |
1510 | |
1511 | namespace { |
1512 | QString accessibleIdForAccessible(QAccessibleInterface *accessible) |
1513 | { |
1514 | QString result; |
1515 | while (accessible) { |
1516 | if (!result.isEmpty()) |
1517 | result.prepend(c: u'.'); |
1518 | if (auto obj = accessible->object()) { |
1519 | const QString name = obj->objectName(); |
1520 | if (!name.isEmpty()) |
1521 | result.prepend(s: name); |
1522 | else |
1523 | result.prepend(s: QString::fromUtf8(utf8: obj->metaObject()->className())); |
1524 | } |
1525 | accessible = accessible->parent(); |
1526 | } |
1527 | return result; |
1528 | } |
1529 | } // namespace |
1530 | |
1531 | // Accessible |
1532 | bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
1533 | { |
1534 | if (function == "GetRole"_L1 ) { |
1535 | sendReply(connection, message, argument: (uint) getRole(interface)); |
1536 | } else if (function == "GetName"_L1 ) { |
1537 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(interface->text(t: QAccessible::Name)))); |
1538 | } else if (function == "GetRoleName"_L1 ) { |
1539 | sendReply(connection, message, argument: QSpiAccessibleBridge::namesForRole(role: interface->role()).name()); |
1540 | } else if (function == "GetLocalizedRoleName"_L1 ) { |
1541 | sendReply(connection, message, argument: QVariant::fromValue(value: QSpiAccessibleBridge::namesForRole(role: interface->role()).localizedName())); |
1542 | } else if (function == "GetChildCount"_L1 ) { |
1543 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(interface->childCount()))); |
1544 | } else if (function == "GetIndexInParent"_L1 ) { |
1545 | int childIndex = -1; |
1546 | QAccessibleInterface * parent = interface->parent(); |
1547 | if (parent) { |
1548 | childIndex = parent->indexOfChild(interface); |
1549 | if (childIndex < 0) { |
1550 | qCDebug(lcAccessibilityAtspi) << "GetIndexInParent get invalid index: " << childIndex << interface; |
1551 | } |
1552 | } |
1553 | sendReply(connection, message, argument: childIndex); |
1554 | } else if (function == "GetParent"_L1 ) { |
1555 | QString path; |
1556 | QAccessibleInterface * parent = interface->parent(); |
1557 | if (!parent) { |
1558 | path = ATSPI_DBUS_PATH_NULL ""_L1 ; |
1559 | } else if (parent->role() == QAccessible::Application) { |
1560 | path = ATSPI_DBUS_PATH_ROOT ""_L1 ; |
1561 | } else { |
1562 | path = pathForInterface(interface: parent); |
1563 | } |
1564 | // Parent is a property, so it needs to be wrapped inside an extra variant. |
1565 | sendReply(connection, message, argument: QVariant::fromValue( |
1566 | value: QDBusVariant(QVariant::fromValue(value: QSpiObjectReference(connection, QDBusObjectPath(path)))))); |
1567 | } else if (function == "GetChildAtIndex"_L1 ) { |
1568 | const int index = message.arguments().at(i: 0).toInt(); |
1569 | if (index < 0) { |
1570 | sendReply(connection, message, argument: QVariant::fromValue( |
1571 | value: QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))); |
1572 | } else { |
1573 | QAccessibleInterface * childInterface = interface->child(index); |
1574 | sendReply(connection, message, argument: QVariant::fromValue( |
1575 | value: QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: childInterface))))); |
1576 | } |
1577 | } else if (function == "GetInterfaces"_L1 ) { |
1578 | sendReply(connection, message, argument: accessibleInterfaces(interface)); |
1579 | } else if (function == "GetDescription"_L1 ) { |
1580 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(interface->text(t: QAccessible::Description)))); |
1581 | } else if (function == "GetState"_L1 ) { |
1582 | quint64 spiState = spiStatesFromQState(state: interface->state()); |
1583 | if (interface->tableInterface()) { |
1584 | // For tables, setting manages_descendants should |
1585 | // indicate to the client that it cannot cache these |
1586 | // interfaces. |
1587 | setSpiStateBit(state: &spiState, spiState: ATSPI_STATE_MANAGES_DESCENDANTS); |
1588 | } |
1589 | QAccessible::Role role = interface->role(); |
1590 | if (role == QAccessible::TreeItem || |
1591 | role == QAccessible::ListItem) { |
1592 | /* Transient means libatspi2 will not cache items. |
1593 | This is important because when adding/removing an item |
1594 | the cache becomes outdated and we don't change the paths of |
1595 | items in lists/trees/tables. */ |
1596 | setSpiStateBit(state: &spiState, spiState: ATSPI_STATE_TRANSIENT); |
1597 | } |
1598 | sendReply(connection, message, |
1599 | argument: QVariant::fromValue(value: spiStateSetFromSpiStates(states: spiState))); |
1600 | } else if (function == "GetAttributes"_L1 ) { |
1601 | sendReply(connection, message, argument: QVariant::fromValue(value: QSpiAttributeSet())); |
1602 | } else if (function == "GetRelationSet"_L1 ) { |
1603 | sendReply(connection, message, argument: QVariant::fromValue(value: relationSet(interface, connection))); |
1604 | } else if (function == "GetApplication"_L1 ) { |
1605 | sendReply(connection, message, argument: QVariant::fromValue( |
1606 | value: QSpiObjectReference(connection, QDBusObjectPath(QSPI_OBJECT_PATH_ROOT)))); |
1607 | } else if (function == "GetChildren"_L1 ) { |
1608 | QSpiObjectReferenceArray children; |
1609 | const int numChildren = interface->childCount(); |
1610 | children.reserve(asize: numChildren); |
1611 | for (int i = 0; i < numChildren; ++i) { |
1612 | QString childPath = pathForInterface(interface: interface->child(index: i)); |
1613 | QSpiObjectReference ref(connection, QDBusObjectPath(childPath)); |
1614 | children << ref; |
1615 | } |
1616 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: children))); |
1617 | } else if (function == "GetAccessibleId"_L1 ) { |
1618 | sendReply(connection, message, |
1619 | argument: QVariant::fromValue(value: QDBusVariant(accessibleIdForAccessible(accessible: interface)))); |
1620 | } else { |
1621 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::accessibleInterface does not implement" << function << message.path(); |
1622 | return false; |
1623 | } |
1624 | return true; |
1625 | } |
1626 | |
1627 | AtspiRole AtSpiAdaptor::getRole(QAccessibleInterface *interface) const |
1628 | { |
1629 | if ((interface->role() == QAccessible::EditableText) && interface->state().passwordEdit) |
1630 | return ATSPI_ROLE_PASSWORD_TEXT; |
1631 | return QSpiAccessibleBridge::namesForRole(role: interface->role()).spiRole(); |
1632 | } |
1633 | |
1634 | QStringList AtSpiAdaptor::accessibleInterfaces(QAccessibleInterface *interface) const |
1635 | { |
1636 | QStringList ifaces; |
1637 | qCDebug(lcAccessibilityAtspiCreation) << "AtSpiAdaptor::accessibleInterfaces create: " << interface->object(); |
1638 | ifaces << u"" ATSPI_DBUS_INTERFACE_ACCESSIBLE ""_s ; |
1639 | |
1640 | if ( (!interface->rect().isEmpty()) || |
1641 | (interface->object() && interface->object()->isWidgetType()) || |
1642 | (interface->role() == QAccessible::ListItem) || |
1643 | (interface->role() == QAccessible::Cell) || |
1644 | (interface->role() == QAccessible::TreeItem) || |
1645 | (interface->role() == QAccessible::Row) || |
1646 | (interface->object() && interface->object()->inherits(classname: "QSGItem" )) |
1647 | ) { |
1648 | ifaces << u"" ATSPI_DBUS_INTERFACE_COMPONENT ""_s ; |
1649 | } else { |
1650 | qCDebug(lcAccessibilityAtspiCreation) << " IS NOT a component" ; |
1651 | } |
1652 | if (interface->role() == QAccessible::Application) |
1653 | ifaces << u"" ATSPI_DBUS_INTERFACE_APPLICATION ""_s ; |
1654 | |
1655 | if (interface->actionInterface() || interface->valueInterface()) |
1656 | ifaces << u"" ATSPI_DBUS_INTERFACE_ACTION ""_s ; |
1657 | |
1658 | if (interface->selectionInterface()) |
1659 | ifaces << ATSPI_DBUS_INTERFACE_SELECTION ""_L1 ; |
1660 | |
1661 | if (interface->textInterface()) |
1662 | ifaces << u"" ATSPI_DBUS_INTERFACE_TEXT ""_s ; |
1663 | |
1664 | if (interface->editableTextInterface()) |
1665 | ifaces << u"" ATSPI_DBUS_INTERFACE_EDITABLE_TEXT ""_s ; |
1666 | |
1667 | if (interface->valueInterface()) |
1668 | ifaces << u"" ATSPI_DBUS_INTERFACE_VALUE ""_s ; |
1669 | |
1670 | if (interface->tableInterface()) |
1671 | ifaces << u"" ATSPI_DBUS_INTERFACE_TABLE ""_s ; |
1672 | |
1673 | if (interface->tableCellInterface()) |
1674 | ifaces << u"" ATSPI_DBUS_INTERFACE_TABLE_CELL ""_s ; |
1675 | |
1676 | return ifaces; |
1677 | } |
1678 | |
1679 | QSpiRelationArray AtSpiAdaptor::relationSet(QAccessibleInterface *interface, const QDBusConnection &connection) const |
1680 | { |
1681 | typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair; |
1682 | const QList<RelationPair> relationInterfaces = interface->relations(); |
1683 | |
1684 | QSpiRelationArray relations; |
1685 | for (const RelationPair &pair : relationInterfaces) { |
1686 | // FIXME: this loop seems a bit strange... "related" always have one item when we check. |
1687 | //And why is it a list, when it always have one item? And it seems to assume that the QAccessible::Relation enum maps directly to AtSpi |
1688 | QSpiObjectReferenceArray related; |
1689 | |
1690 | QDBusObjectPath path = QDBusObjectPath(pathForInterface(interface: pair.first)); |
1691 | related.append(t: QSpiObjectReference(connection, path)); |
1692 | |
1693 | if (!related.isEmpty()) |
1694 | relations.append(t: QSpiRelationArrayEntry(qAccessibleRelationToAtSpiRelation(relation: pair.second), related)); |
1695 | } |
1696 | return relations; |
1697 | } |
1698 | |
1699 | void AtSpiAdaptor::sendReply(const QDBusConnection &connection, const QDBusMessage &message, const QVariant &argument) const |
1700 | { |
1701 | QDBusMessage reply = message.createReply(argument); |
1702 | connection.send(message: reply); |
1703 | } |
1704 | |
1705 | |
1706 | QString AtSpiAdaptor::pathForObject(QObject *object) const |
1707 | { |
1708 | Q_ASSERT(object); |
1709 | |
1710 | if (inheritsQAction(object)) { |
1711 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::pathForObject: Creating path with QAction as object." ; |
1712 | } |
1713 | |
1714 | QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(object); |
1715 | return pathForInterface(interface: iface); |
1716 | } |
1717 | |
1718 | QString AtSpiAdaptor::pathForInterface(QAccessibleInterface *interface) const |
1719 | { |
1720 | if (!interface || !interface->isValid()) |
1721 | return u"" ATSPI_DBUS_PATH_NULL ""_s ; |
1722 | if (interface->role() == QAccessible::Application) |
1723 | return u"" QSPI_OBJECT_PATH_ROOT ""_s ; |
1724 | |
1725 | QAccessible::Id id = QAccessible::uniqueId(iface: interface); |
1726 | Q_ASSERT((int)id < 0); |
1727 | return QSPI_OBJECT_PATH_PREFIX ""_L1 + QString::number(id); |
1728 | } |
1729 | |
1730 | bool AtSpiAdaptor::inheritsQAction(QObject *object) |
1731 | { |
1732 | const QMetaObject *mo = object->metaObject(); |
1733 | while (mo) { |
1734 | const QLatin1StringView cn(mo->className()); |
1735 | if (cn == "QAction"_L1 ) |
1736 | return true; |
1737 | mo = mo->superClass(); |
1738 | } |
1739 | return false; |
1740 | } |
1741 | |
1742 | // Component |
1743 | static QAccessibleInterface * getWindow(QAccessibleInterface * interface) |
1744 | { |
1745 | if (interface->role() == QAccessible::Dialog || interface->role() == QAccessible::Window) |
1746 | return interface; |
1747 | |
1748 | QAccessibleInterface * parent = interface->parent(); |
1749 | while (parent && parent->role() != QAccessible::Dialog |
1750 | && parent->role() != QAccessible::Window) |
1751 | parent = parent->parent(); |
1752 | |
1753 | return parent; |
1754 | } |
1755 | |
1756 | bool AtSpiAdaptor::componentInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
1757 | { |
1758 | if (function == "Contains"_L1 ) { |
1759 | bool ret = false; |
1760 | int x = message.arguments().at(i: 0).toInt(); |
1761 | int y = message.arguments().at(i: 1).toInt(); |
1762 | uint coordType = message.arguments().at(i: 2).toUInt(); |
1763 | if (!isValidCoordType(coordType)) |
1764 | return false; |
1765 | ret = getExtents(interface, coordType).contains(ax: x, ay: y); |
1766 | sendReply(connection, message, argument: ret); |
1767 | } else if (function == "GetAccessibleAtPoint"_L1 ) { |
1768 | QPoint point(message.arguments().at(i: 0).toInt(), message.arguments().at(i: 1).toInt()); |
1769 | uint coordType = message.arguments().at(i: 2).toUInt(); |
1770 | if (!isValidCoordType(coordType)) |
1771 | return false; |
1772 | QPoint screenPos = translateToScreenCoordinates(interface, pos: point, fromCoordType: coordType); |
1773 | |
1774 | QAccessibleInterface * childInterface(interface->childAt(x: screenPos.x(), y: screenPos.y())); |
1775 | QAccessibleInterface * iface = nullptr; |
1776 | while (childInterface) { |
1777 | iface = childInterface; |
1778 | childInterface = iface->childAt(x: screenPos.x(), y: screenPos.y()); |
1779 | } |
1780 | if (iface) { |
1781 | QString path = pathForInterface(interface: iface); |
1782 | sendReply(connection, message, argument: QVariant::fromValue( |
1783 | value: QSpiObjectReference(connection, QDBusObjectPath(path)))); |
1784 | } else { |
1785 | sendReply(connection, message, argument: QVariant::fromValue( |
1786 | value: QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))); |
1787 | } |
1788 | } else if (function == "GetAlpha"_L1 ) { |
1789 | sendReply(connection, message, argument: (double) 1.0); |
1790 | } else if (function == "GetExtents"_L1 ) { |
1791 | uint coordType = message.arguments().at(i: 0).toUInt(); |
1792 | if (!isValidCoordType(coordType)) |
1793 | return false; |
1794 | sendReply(connection, message, argument: QVariant::fromValue(value: getExtents(interface, coordType))); |
1795 | } else if (function == "GetLayer"_L1 ) { |
1796 | sendReply(connection, message, argument: QVariant::fromValue(value: (uint)1)); |
1797 | } else if (function == "GetMDIZOrder"_L1 ) { |
1798 | sendReply(connection, message, argument: QVariant::fromValue(value: (short)0)); |
1799 | } else if (function == "GetPosition"_L1 ) { |
1800 | uint coordType = message.arguments().at(i: 0).toUInt(); |
1801 | if (!isValidCoordType(coordType)) |
1802 | return false; |
1803 | QRect rect = getExtents(interface, coordType); |
1804 | QVariantList pos; |
1805 | pos << rect.x() << rect.y(); |
1806 | connection.send(message: message.createReply(arguments: pos)); |
1807 | } else if (function == "GetSize"_L1 ) { |
1808 | QRect rect = interface->rect(); |
1809 | QVariantList size; |
1810 | size << rect.width() << rect.height(); |
1811 | connection.send(message: message.createReply(arguments: size)); |
1812 | } else if (function == "GrabFocus"_L1 ) { |
1813 | QAccessibleActionInterface *actionIface = interface->actionInterface(); |
1814 | if (actionIface && actionIface->actionNames().contains(str: QAccessibleActionInterface::setFocusAction())) { |
1815 | actionIface->doAction(actionName: QAccessibleActionInterface::setFocusAction()); |
1816 | sendReply(connection, message, argument: true); |
1817 | } else { |
1818 | sendReply(connection, message, argument: false); |
1819 | } |
1820 | } else if (function == "SetExtents"_L1 ) { |
1821 | // int x = message.arguments().at(0).toInt(); |
1822 | // int y = message.arguments().at(1).toInt(); |
1823 | // int width = message.arguments().at(2).toInt(); |
1824 | // int height = message.arguments().at(3).toInt(); |
1825 | // uint coordinateType = message.arguments().at(4).toUInt(); |
1826 | qCDebug(lcAccessibilityAtspi) << "SetExtents is not implemented." ; |
1827 | sendReply(connection, message, argument: false); |
1828 | } else if (function == "SetPosition"_L1 ) { |
1829 | // int x = message.arguments().at(0).toInt(); |
1830 | // int y = message.arguments().at(1).toInt(); |
1831 | // uint coordinateType = message.arguments().at(2).toUInt(); |
1832 | qCDebug(lcAccessibilityAtspi) << "SetPosition is not implemented." ; |
1833 | sendReply(connection, message, argument: false); |
1834 | } else if (function == "SetSize"_L1 ) { |
1835 | // int width = message.arguments().at(0).toInt(); |
1836 | // int height = message.arguments().at(1).toInt(); |
1837 | qCDebug(lcAccessibilityAtspi) << "SetSize is not implemented." ; |
1838 | sendReply(connection, message, argument: false); |
1839 | } else { |
1840 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::componentInterface does not implement" << function << message.path(); |
1841 | return false; |
1842 | } |
1843 | return true; |
1844 | } |
1845 | |
1846 | QRect AtSpiAdaptor::getExtents(QAccessibleInterface *interface, uint coordType) |
1847 | { |
1848 | return translateFromScreenCoordinates(interface, rect: interface->rect(), targetCoordType: coordType); |
1849 | } |
1850 | |
1851 | // Action interface |
1852 | bool AtSpiAdaptor::actionInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
1853 | { |
1854 | if (function == "GetNActions"_L1 ) { |
1855 | int count = QAccessibleBridgeUtils::effectiveActionNames(iface: interface).size(); |
1856 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: count)))); |
1857 | } else if (function == "DoAction"_L1 ) { |
1858 | int index = message.arguments().at(i: 0).toInt(); |
1859 | const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface: interface); |
1860 | if (index < 0 || index >= actionNames.size()) |
1861 | return false; |
1862 | const QString actionName = actionNames.at(i: index); |
1863 | bool success = QAccessibleBridgeUtils::performEffectiveAction(iface: interface, actionName); |
1864 | sendReply(connection, message, argument: success); |
1865 | } else if (function == "GetActions"_L1 ) { |
1866 | sendReply(connection, message, argument: QVariant::fromValue(value: getActions(interface))); |
1867 | } else if (function == "GetName"_L1 ) { |
1868 | int index = message.arguments().at(i: 0).toInt(); |
1869 | const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface: interface); |
1870 | if (index < 0 || index >= actionNames.size()) |
1871 | return false; |
1872 | sendReply(connection, message, argument: actionNames.at(i: index)); |
1873 | } else if (function == "GetDescription"_L1 ) { |
1874 | int index = message.arguments().at(i: 0).toInt(); |
1875 | const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface: interface); |
1876 | if (index < 0 || index >= actionNames.size()) |
1877 | return false; |
1878 | QString description; |
1879 | if (QAccessibleActionInterface *actionIface = interface->actionInterface()) |
1880 | description = actionIface->localizedActionDescription(name: actionNames.at(i: index)); |
1881 | else |
1882 | description = qAccessibleLocalizedActionDescription(actionName: actionNames.at(i: index)); |
1883 | sendReply(connection, message, argument: description); |
1884 | } else if (function == "GetKeyBinding"_L1 ) { |
1885 | int index = message.arguments().at(i: 0).toInt(); |
1886 | const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface: interface); |
1887 | if (index < 0 || index >= actionNames.size()) |
1888 | return false; |
1889 | QStringList keyBindings; |
1890 | if (QAccessibleActionInterface *actionIface = interface->actionInterface()) |
1891 | keyBindings = actionIface->keyBindingsForAction(actionName: actionNames.at(i: index)); |
1892 | if (keyBindings.isEmpty()) { |
1893 | QString acc = interface->text(t: QAccessible::Accelerator); |
1894 | if (!acc.isEmpty()) |
1895 | keyBindings.append(t: acc); |
1896 | } |
1897 | if (keyBindings.size() > 0) |
1898 | sendReply(connection, message, argument: keyBindings.join(sep: u';')); |
1899 | else |
1900 | sendReply(connection, message, argument: QString()); |
1901 | } else { |
1902 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::actionInterface does not implement" << function << message.path(); |
1903 | return false; |
1904 | } |
1905 | return true; |
1906 | } |
1907 | |
1908 | QSpiActionArray AtSpiAdaptor::getActions(QAccessibleInterface *interface) const |
1909 | { |
1910 | QAccessibleActionInterface *actionInterface = interface->actionInterface(); |
1911 | QSpiActionArray actions; |
1912 | const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface: interface); |
1913 | actions.reserve(asize: actionNames.size()); |
1914 | for (const QString &actionName : actionNames) { |
1915 | QSpiAction action; |
1916 | |
1917 | action.name = actionName; |
1918 | if (actionInterface) { |
1919 | action.description = actionInterface->localizedActionDescription(name: actionName); |
1920 | const QStringList keyBindings = actionInterface->keyBindingsForAction(actionName); |
1921 | if (!keyBindings.isEmpty()) |
1922 | action.keyBinding = keyBindings.front(); |
1923 | } else { |
1924 | action.description = qAccessibleLocalizedActionDescription(actionName); |
1925 | } |
1926 | |
1927 | actions.append(t: std::move(action)); |
1928 | } |
1929 | return actions; |
1930 | } |
1931 | |
1932 | // Text interface |
1933 | bool AtSpiAdaptor::textInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
1934 | { |
1935 | if (!interface->textInterface()) |
1936 | return false; |
1937 | |
1938 | // properties |
1939 | if (function == "GetCaretOffset"_L1 ) { |
1940 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: interface->textInterface()->cursorPosition())))); |
1941 | } else if (function == "GetCharacterCount"_L1 ) { |
1942 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: interface->textInterface()->characterCount())))); |
1943 | |
1944 | // functions |
1945 | } else if (function == "AddSelection"_L1 ) { |
1946 | int startOffset = message.arguments().at(i: 0).toInt(); |
1947 | int endOffset = message.arguments().at(i: 1).toInt(); |
1948 | int lastSelection = interface->textInterface()->selectionCount(); |
1949 | interface->textInterface()->setSelection(selectionIndex: lastSelection, startOffset, endOffset); |
1950 | sendReply(connection, message, argument: (interface->textInterface()->selectionCount() > lastSelection)); |
1951 | } else if (function == "GetAttributeRun"_L1 ) { |
1952 | int offset = message.arguments().at(i: 0).toInt(); |
1953 | bool includeDefaults = message.arguments().at(i: 1).toBool(); |
1954 | Q_UNUSED(includeDefaults); |
1955 | connection.send(message: message.createReply(arguments: getAttributes(interface, offset, includeDefaults))); |
1956 | } else if (function == "GetAttributeValue"_L1 ) { |
1957 | int offset = message.arguments().at(i: 0).toInt(); |
1958 | QString attributeName = message.arguments().at(i: 1).toString(); |
1959 | connection.send(message: message.createReply(argument: QVariant(getAttributeValue(interface, offset, attributeName)))); |
1960 | } else if (function == "GetAttributes"_L1 ) { |
1961 | int offset = message.arguments().at(i: 0).toInt(); |
1962 | connection.send(message: message.createReply(arguments: getAttributes(interface, offset, includeDefaults: true))); |
1963 | } else if (function == "GetBoundedRanges"_L1 ) { |
1964 | int x = message.arguments().at(i: 0).toInt(); |
1965 | int y = message.arguments().at(i: 1).toInt(); |
1966 | int width = message.arguments().at(i: 2).toInt(); |
1967 | int height = message.arguments().at(i: 3).toInt(); |
1968 | uint coordType = message.arguments().at(i: 4).toUInt(); |
1969 | uint xClipType = message.arguments().at(i: 5).toUInt(); |
1970 | uint yClipType = message.arguments().at(i: 6).toUInt(); |
1971 | Q_UNUSED(x); |
1972 | Q_UNUSED(y); |
1973 | Q_UNUSED(width); |
1974 | Q_UNUSED(height); |
1975 | Q_UNUSED(coordType); |
1976 | Q_UNUSED(xClipType); |
1977 | Q_UNUSED(yClipType); |
1978 | qCDebug(lcAccessibilityAtspi) << "Not implemented: QSpiAdaptor::GetBoundedRanges" ; |
1979 | sendReply(connection, message, argument: QVariant::fromValue(value: QSpiTextRangeList())); |
1980 | } else if (function == "GetCharacterAtOffset"_L1 ) { |
1981 | int offset = message.arguments().at(i: 0).toInt(); |
1982 | int start; |
1983 | int end; |
1984 | const QString charString = interface->textInterface() |
1985 | ->textAtOffset(offset, boundaryType: QAccessible::CharBoundary, startOffset: &start, endOffset: &end); |
1986 | int codePoint = 0; |
1987 | QStringIterator stringIt(charString); |
1988 | if (stringIt.hasNext()) |
1989 | codePoint = static_cast<int>(stringIt.peekNext()); |
1990 | sendReply(connection, message, argument: codePoint); |
1991 | } else if (function == "GetCharacterExtents"_L1 ) { |
1992 | int offset = message.arguments().at(i: 0).toInt(); |
1993 | int coordType = message.arguments().at(i: 1).toUInt(); |
1994 | connection.send(message: message.createReply(arguments: getCharacterExtents(interface, offset, coordType))); |
1995 | } else if (function == "GetDefaultAttributeSet"_L1 || function == "GetDefaultAttributes"_L1 ) { |
1996 | // GetDefaultAttributes is deprecated in favour of GetDefaultAttributeSet. |
1997 | // Empty set seems reasonable. There is no default attribute set. |
1998 | sendReply(connection, message, argument: QVariant::fromValue(value: QSpiAttributeSet())); |
1999 | } else if (function == "GetNSelections"_L1 ) { |
2000 | sendReply(connection, message, argument: interface->textInterface()->selectionCount()); |
2001 | } else if (function == "GetOffsetAtPoint"_L1 ) { |
2002 | qCDebug(lcAccessibilityAtspi) << message.signature(); |
2003 | Q_ASSERT(!message.signature().isEmpty()); |
2004 | QPoint point(message.arguments().at(i: 0).toInt(), message.arguments().at(i: 1).toInt()); |
2005 | uint coordType = message.arguments().at(i: 2).toUInt(); |
2006 | if (!isValidCoordType(coordType)) |
2007 | return false; |
2008 | QPoint screenPos = translateToScreenCoordinates(interface, pos: point, fromCoordType: coordType); |
2009 | int offset = interface->textInterface()->offsetAtPoint(point: screenPos); |
2010 | sendReply(connection, message, argument: offset); |
2011 | } else if (function == "GetRangeExtents"_L1 ) { |
2012 | int startOffset = message.arguments().at(i: 0).toInt(); |
2013 | int endOffset = message.arguments().at(i: 1).toInt(); |
2014 | uint coordType = message.arguments().at(i: 2).toUInt(); |
2015 | connection.send(message: message.createReply(arguments: getRangeExtents(interface, startOffset, endOffset, coordType))); |
2016 | } else if (function == "GetSelection"_L1 ) { |
2017 | int selectionNum = message.arguments().at(i: 0).toInt(); |
2018 | int start, end; |
2019 | interface->textInterface()->selection(selectionIndex: selectionNum, startOffset: &start, endOffset: &end); |
2020 | if (start < 0) |
2021 | start = end = interface->textInterface()->cursorPosition(); |
2022 | QVariantList sel; |
2023 | sel << start << end; |
2024 | connection.send(message: message.createReply(arguments: sel)); |
2025 | } else if (function == "GetStringAtOffset"_L1 ) { |
2026 | int offset = message.arguments().at(i: 0).toInt(); |
2027 | uint granularity = message.arguments().at(i: 1).toUInt(); |
2028 | if (!isValidAtspiTextGranularity(coordType: granularity)) |
2029 | return false; |
2030 | int startOffset, endOffset; |
2031 | QString text = interface->textInterface()->textAtOffset(offset, boundaryType: qAccessibleBoundaryTypeFromAtspiTextGranularity(atspiTextGranularity: granularity), startOffset: &startOffset, endOffset: &endOffset); |
2032 | QVariantList ret; |
2033 | ret << text << startOffset << endOffset; |
2034 | connection.send(message: message.createReply(arguments: ret)); |
2035 | } else if (function == "GetText"_L1 ) { |
2036 | int startOffset = message.arguments().at(i: 0).toInt(); |
2037 | int endOffset = message.arguments().at(i: 1).toInt(); |
2038 | if (endOffset == -1) // AT-SPI uses -1 to signal all characters |
2039 | endOffset = interface->textInterface()->characterCount(); |
2040 | sendReply(connection, message, argument: interface->textInterface()->text(startOffset, endOffset)); |
2041 | } else if (function == "GetTextAfterOffset"_L1 ) { |
2042 | int offset = message.arguments().at(i: 0).toInt(); |
2043 | int type = message.arguments().at(i: 1).toUInt(); |
2044 | int startOffset, endOffset; |
2045 | QString text = interface->textInterface()->textAfterOffset(offset, boundaryType: qAccessibleBoundaryTypeFromAtspiBoundaryType(atspiTextBoundaryType: type), startOffset: &startOffset, endOffset: &endOffset); |
2046 | QVariantList ret; |
2047 | ret << text << startOffset << endOffset; |
2048 | connection.send(message: message.createReply(arguments: ret)); |
2049 | } else if (function == "GetTextAtOffset"_L1 ) { |
2050 | int offset = message.arguments().at(i: 0).toInt(); |
2051 | int type = message.arguments().at(i: 1).toUInt(); |
2052 | int startOffset, endOffset; |
2053 | QString text = interface->textInterface()->textAtOffset(offset, boundaryType: qAccessibleBoundaryTypeFromAtspiBoundaryType(atspiTextBoundaryType: type), startOffset: &startOffset, endOffset: &endOffset); |
2054 | QVariantList ret; |
2055 | ret << text << startOffset << endOffset; |
2056 | connection.send(message: message.createReply(arguments: ret)); |
2057 | } else if (function == "GetTextBeforeOffset"_L1 ) { |
2058 | int offset = message.arguments().at(i: 0).toInt(); |
2059 | int type = message.arguments().at(i: 1).toUInt(); |
2060 | int startOffset, endOffset; |
2061 | QString text = interface->textInterface()->textBeforeOffset(offset, boundaryType: qAccessibleBoundaryTypeFromAtspiBoundaryType(atspiTextBoundaryType: type), startOffset: &startOffset, endOffset: &endOffset); |
2062 | QVariantList ret; |
2063 | ret << text << startOffset << endOffset; |
2064 | connection.send(message: message.createReply(arguments: ret)); |
2065 | } else if (function == "RemoveSelection"_L1 ) { |
2066 | int selectionNum = message.arguments().at(i: 0).toInt(); |
2067 | interface->textInterface()->removeSelection(selectionIndex: selectionNum); |
2068 | sendReply(connection, message, argument: true); |
2069 | } else if (function == "SetCaretOffset"_L1 ) { |
2070 | int offset = message.arguments().at(i: 0).toInt(); |
2071 | interface->textInterface()->setCursorPosition(offset); |
2072 | sendReply(connection, message, argument: true); |
2073 | } else if (function == "ScrollSubstringTo"_L1 ) { |
2074 | int startOffset = message.arguments().at(i: 0).toInt(); |
2075 | int endOffset = message.arguments().at(i: 1).toInt(); |
2076 | // ignore third parameter (scroll type), since QAccessibleTextInterface::scrollToSubstring doesn't have that |
2077 | qCInfo(lcAccessibilityAtspi) << "AtSpiAdaptor::ScrollSubstringTo doesn'take take scroll type into account." ; |
2078 | interface->textInterface()->scrollToSubstring(startIndex: startOffset, endIndex: endOffset); |
2079 | sendReply(connection, message, argument: true); |
2080 | } else if (function == "SetSelection"_L1 ) { |
2081 | int selectionNum = message.arguments().at(i: 0).toInt(); |
2082 | int startOffset = message.arguments().at(i: 1).toInt(); |
2083 | int endOffset = message.arguments().at(i: 2).toInt(); |
2084 | interface->textInterface()->setSelection(selectionIndex: selectionNum, startOffset, endOffset); |
2085 | sendReply(connection, message, argument: true); |
2086 | } else { |
2087 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::textInterface does not implement" << function << message.path(); |
2088 | return false; |
2089 | } |
2090 | return true; |
2091 | } |
2092 | |
2093 | QAccessible::TextBoundaryType AtSpiAdaptor::qAccessibleBoundaryTypeFromAtspiBoundaryType(int atspiTextBoundaryType) |
2094 | { |
2095 | switch (atspiTextBoundaryType) { |
2096 | case ATSPI_TEXT_BOUNDARY_CHAR: |
2097 | return QAccessible::CharBoundary; |
2098 | case ATSPI_TEXT_BOUNDARY_WORD_START: |
2099 | case ATSPI_TEXT_BOUNDARY_WORD_END: |
2100 | return QAccessible::WordBoundary; |
2101 | case ATSPI_TEXT_BOUNDARY_SENTENCE_START: |
2102 | case ATSPI_TEXT_BOUNDARY_SENTENCE_END: |
2103 | return QAccessible::SentenceBoundary; |
2104 | case ATSPI_TEXT_BOUNDARY_LINE_START: |
2105 | case ATSPI_TEXT_BOUNDARY_LINE_END: |
2106 | return QAccessible::LineBoundary; |
2107 | } |
2108 | Q_ASSERT_X(0, "" , "Requested invalid boundary type." ); |
2109 | return QAccessible::CharBoundary; |
2110 | } |
2111 | |
2112 | bool AtSpiAdaptor::isValidAtspiTextGranularity(uint atspiTextGranularity) |
2113 | { |
2114 | if (atspiTextGranularity == ATSPI_TEXT_GRANULARITY_CHAR |
2115 | || atspiTextGranularity == ATSPI_TEXT_GRANULARITY_WORD |
2116 | || atspiTextGranularity == ATSPI_TEXT_GRANULARITY_SENTENCE |
2117 | || atspiTextGranularity == ATSPI_TEXT_GRANULARITY_LINE |
2118 | || atspiTextGranularity == ATSPI_TEXT_GRANULARITY_PARAGRAPH) |
2119 | return true; |
2120 | |
2121 | qCWarning(lcAccessibilityAtspi) << "Unknown value" << atspiTextGranularity << "for AT-SPI text granularity type" ; |
2122 | return false; |
2123 | } |
2124 | |
2125 | QAccessible::TextBoundaryType AtSpiAdaptor::qAccessibleBoundaryTypeFromAtspiTextGranularity(uint atspiTextGranularity) |
2126 | { |
2127 | Q_ASSERT(isValidAtspiTextGranularity(atspiTextGranularity)); |
2128 | |
2129 | switch (atspiTextGranularity) { |
2130 | case ATSPI_TEXT_GRANULARITY_CHAR: |
2131 | return QAccessible::CharBoundary; |
2132 | case ATSPI_TEXT_GRANULARITY_WORD: |
2133 | return QAccessible::WordBoundary; |
2134 | case ATSPI_TEXT_GRANULARITY_SENTENCE: |
2135 | return QAccessible::SentenceBoundary; |
2136 | case ATSPI_TEXT_GRANULARITY_LINE: |
2137 | return QAccessible::LineBoundary; |
2138 | case ATSPI_TEXT_GRANULARITY_PARAGRAPH: |
2139 | return QAccessible::ParagraphBoundary; |
2140 | } |
2141 | return QAccessible::CharBoundary; |
2142 | } |
2143 | |
2144 | namespace |
2145 | { |
2146 | struct AtSpiAttribute { |
2147 | QString name; |
2148 | QString value; |
2149 | AtSpiAttribute(const QString &aName, const QString &aValue) : name(aName), value(aValue) {} |
2150 | bool isNull() const { return name.isNull() || value.isNull(); } |
2151 | }; |
2152 | |
2153 | QString atspiColor(const QString &ia2Color) |
2154 | { |
2155 | // "rgb(%u,%u,%u)" -> "%u,%u,%u" |
2156 | return ia2Color.mid(position: 4, n: ia2Color.size() - (4+1)); |
2157 | } |
2158 | |
2159 | QString atspiSize(const QString &ia2Size) |
2160 | { |
2161 | // "%fpt" -> "%f" |
2162 | return ia2Size.left(n: ia2Size.size() - 2); |
2163 | } |
2164 | |
2165 | AtSpiAttribute atspiTextAttribute(const QString &ia2Name, const QString &ia2Value) |
2166 | { |
2167 | QString name = ia2Name; |
2168 | QString value = ia2Value; |
2169 | |
2170 | // IAccessible2: http://www.linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes |
2171 | // ATK attribute names: https://git.gnome.org/browse/orca/tree/src/orca/text_attribute_names.py |
2172 | // ATK attribute values: https://developer.gnome.org/atk/unstable/AtkText.html#AtkTextAttribute |
2173 | |
2174 | // https://bugzilla.gnome.org/show_bug.cgi?id=744553 "ATK docs provide no guidance for allowed values of some text attributes" |
2175 | // specifically for "weight", "invalid", "language" and value range for colors |
2176 | |
2177 | if (ia2Name == "background-color"_L1 ) { |
2178 | name = QStringLiteral("bg-color" ); |
2179 | value = atspiColor(ia2Color: value); |
2180 | } else if (ia2Name == "font-family"_L1 ) { |
2181 | name = QStringLiteral("family-name" ); |
2182 | } else if (ia2Name == "color"_L1 ) { |
2183 | name = QStringLiteral("fg-color" ); |
2184 | value = atspiColor(ia2Color: value); |
2185 | } else if (ia2Name == "text-align"_L1 ) { |
2186 | name = QStringLiteral("justification" ); |
2187 | if (value == "justify"_L1 ) { |
2188 | value = QStringLiteral("fill" ); |
2189 | } else if (value != "left"_L1 && value != "right"_L1 && value != "center"_L1 ) { |
2190 | qCDebug(lcAccessibilityAtspi) << "Unknown text-align attribute value \"" |
2191 | << value << "\" cannot be translated to AT-SPI." ; |
2192 | value = QString(); |
2193 | } |
2194 | } else if (ia2Name == "font-size"_L1 ) { |
2195 | name = QStringLiteral("size" ); |
2196 | value = atspiSize(ia2Size: value); |
2197 | } else if (ia2Name == "font-style"_L1 ) { |
2198 | name = QStringLiteral("style" ); |
2199 | if (value != "normal"_L1 && value != "italic"_L1 && value != "oblique"_L1 ) { |
2200 | qCDebug(lcAccessibilityAtspi) << "Unknown font-style attribute value \"" << value |
2201 | << "\" cannot be translated to AT-SPI." ; |
2202 | value = QString(); |
2203 | } |
2204 | } else if (ia2Name == "text-underline-type"_L1 ) { |
2205 | name = QStringLiteral("underline" ); |
2206 | if (value != "none"_L1 && value != "single"_L1 && value != "double"_L1 ) { |
2207 | qCDebug(lcAccessibilityAtspi) << "Unknown text-underline-type attribute value \"" |
2208 | << value << "\" cannot be translated to AT-SPI." ; |
2209 | value = QString(); |
2210 | } |
2211 | } else if (ia2Name == "font-weight"_L1 ) { |
2212 | name = QStringLiteral("weight" ); |
2213 | if (value == "normal"_L1 ) |
2214 | // Orca seems to accept all IAccessible2 values except for "normal" |
2215 | // (on which it produces traceback and fails to read any following text attributes), |
2216 | // but that is the default value, so omit it anyway |
2217 | value = QString(); |
2218 | } else if (ia2Name == "text-position"_L1 ) { |
2219 | name = QStringLiteral("vertical-align" ); |
2220 | if (value != "baseline"_L1 && value != "super"_L1 && value != "sub"_L1 ) { |
2221 | qCDebug(lcAccessibilityAtspi) << "Unknown text-position attribute value \"" << value |
2222 | << "\" cannot be translated to AT-SPI." ; |
2223 | value = QString(); |
2224 | } |
2225 | } else if (ia2Name == "writing-mode"_L1 ) { |
2226 | name = QStringLiteral("direction" ); |
2227 | if (value == "lr"_L1 ) |
2228 | value = QStringLiteral("ltr" ); |
2229 | else if (value == "rl"_L1 ) |
2230 | value = QStringLiteral("rtl" ); |
2231 | else if (value == "tb"_L1 ) { |
2232 | // IAccessible2 docs refer to XSL, which specifies "tb" is shorthand for "tb-rl"; so at least give a hint about the horizontal direction (ATK does not support vertical direction in this attribute (yet)) |
2233 | value = QStringLiteral("rtl" ); |
2234 | qCDebug(lcAccessibilityAtspi) << "writing-mode attribute value \"tb\" translated only w.r.t. horizontal direction; vertical direction ignored" ; |
2235 | } else { |
2236 | qCDebug(lcAccessibilityAtspi) << "Unknown writing-mode attribute value \"" << value |
2237 | << "\" cannot be translated to AT-SPI." ; |
2238 | value = QString(); |
2239 | } |
2240 | } else if (ia2Name == "language"_L1 ) { |
2241 | // OK - ATK has no docs on the format of the value, IAccessible2 has reasonable format - leave it at that now |
2242 | } else if (ia2Name == "invalid"_L1 ) { |
2243 | // OK - ATK docs are vague but suggest they support the same range of values as IAccessible2 |
2244 | } else { |
2245 | // attribute we know nothing about |
2246 | name = QString(); |
2247 | value = QString(); |
2248 | } |
2249 | return AtSpiAttribute(name, value); |
2250 | } |
2251 | } |
2252 | |
2253 | // FIXME all attribute methods below should share code |
2254 | QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int offset, bool includeDefaults) const |
2255 | { |
2256 | Q_UNUSED(includeDefaults); |
2257 | |
2258 | QSpiAttributeSet set; |
2259 | int startOffset; |
2260 | int endOffset; |
2261 | |
2262 | QString joined = interface->textInterface()->attributes(offset, startOffset: &startOffset, endOffset: &endOffset); |
2263 | const QStringList attributes = joined.split(sep: u';', behavior: Qt::SkipEmptyParts, cs: Qt::CaseSensitive); |
2264 | for (const QString &attr : attributes) { |
2265 | QStringList items; |
2266 | items = attr.split(sep: u':', behavior: Qt::SkipEmptyParts, cs: Qt::CaseSensitive); |
2267 | AtSpiAttribute attribute = atspiTextAttribute(ia2Name: items[0], ia2Value: items[1]); |
2268 | if (!attribute.isNull()) |
2269 | set[attribute.name] = attribute.value; |
2270 | } |
2271 | |
2272 | QVariantList list; |
2273 | list << QVariant::fromValue(value: set) << startOffset << endOffset; |
2274 | |
2275 | return list; |
2276 | } |
2277 | |
2278 | QString AtSpiAdaptor::getAttributeValue(QAccessibleInterface *interface, int offset, const QString &attributeName) const |
2279 | { |
2280 | QString joined; |
2281 | QSpiAttributeSet map; |
2282 | int startOffset; |
2283 | int endOffset; |
2284 | |
2285 | joined = interface->textInterface()->attributes(offset, startOffset: &startOffset, endOffset: &endOffset); |
2286 | const QStringList attributes = joined.split (sep: u';', behavior: Qt::SkipEmptyParts, cs: Qt::CaseSensitive); |
2287 | for (const QString& attr : attributes) { |
2288 | QStringList items; |
2289 | items = attr.split(sep: u':', behavior: Qt::SkipEmptyParts, cs: Qt::CaseSensitive); |
2290 | AtSpiAttribute attribute = atspiTextAttribute(ia2Name: items[0], ia2Value: items[1]); |
2291 | if (!attribute.isNull()) |
2292 | map[attribute.name] = attribute.value; |
2293 | } |
2294 | return map[attributeName]; |
2295 | } |
2296 | |
2297 | QList<QVariant> AtSpiAdaptor::getCharacterExtents(QAccessibleInterface *interface, int offset, uint coordType) const |
2298 | { |
2299 | QRect rect = interface->textInterface()->characterRect(offset); |
2300 | rect = translateFromScreenCoordinates(interface, rect, targetCoordType: coordType); |
2301 | return QList<QVariant>() << rect.x() << rect.y() << rect.width() << rect.height(); |
2302 | } |
2303 | |
2304 | QList<QVariant> AtSpiAdaptor::getRangeExtents(QAccessibleInterface *interface, |
2305 | int startOffset, int endOffset, uint coordType) const |
2306 | { |
2307 | if (endOffset == -1) |
2308 | endOffset = interface->textInterface()->characterCount(); |
2309 | |
2310 | QAccessibleTextInterface *textInterface = interface->textInterface(); |
2311 | if (endOffset <= startOffset || !textInterface) |
2312 | return QList<QVariant>() << -1 << -1 << 0 << 0; |
2313 | |
2314 | QRect rect = textInterface->characterRect(offset: startOffset); |
2315 | for (int i=startOffset + 1; i <= endOffset; i++) |
2316 | rect = rect | textInterface->characterRect(offset: i); |
2317 | |
2318 | rect = translateFromScreenCoordinates(interface, rect, targetCoordType: coordType); |
2319 | return QList<QVariant>() << rect.x() << rect.y() << rect.width() << rect.height(); |
2320 | } |
2321 | |
2322 | bool AtSpiAdaptor::isValidCoordType(uint coordType) |
2323 | { |
2324 | if (coordType == ATSPI_COORD_TYPE_SCREEN || coordType == ATSPI_COORD_TYPE_WINDOW || coordType == ATSPI_COORD_TYPE_PARENT) |
2325 | return true; |
2326 | |
2327 | qCWarning(lcAccessibilityAtspi) << "Unknown value" << coordType << "for AT-SPI coord type" ; |
2328 | return false; |
2329 | } |
2330 | |
2331 | QRect AtSpiAdaptor::translateFromScreenCoordinates(QAccessibleInterface *interface, const QRect &screenRect, uint targetCoordType) |
2332 | { |
2333 | Q_ASSERT(isValidCoordType(targetCoordType)); |
2334 | |
2335 | QAccessibleInterface *upper = nullptr; |
2336 | if (targetCoordType == ATSPI_COORD_TYPE_WINDOW) |
2337 | upper = getWindow(interface); |
2338 | else if (targetCoordType == ATSPI_COORD_TYPE_PARENT) |
2339 | upper = interface->parent(); |
2340 | |
2341 | QRect rect = screenRect; |
2342 | if (upper) |
2343 | rect.translate(dx: -upper->rect().x(), dy: -upper->rect().y()); |
2344 | |
2345 | return rect; |
2346 | } |
2347 | |
2348 | QPoint AtSpiAdaptor::translateToScreenCoordinates(QAccessibleInterface *interface, const QPoint &pos, uint fromCoordType) |
2349 | { |
2350 | Q_ASSERT(isValidCoordType(fromCoordType)); |
2351 | |
2352 | QAccessibleInterface *upper = nullptr; |
2353 | if (fromCoordType == ATSPI_COORD_TYPE_WINDOW) |
2354 | upper = getWindow(interface); |
2355 | else if (fromCoordType == ATSPI_COORD_TYPE_PARENT) |
2356 | upper = interface->parent(); |
2357 | |
2358 | QPoint screenPos = pos; |
2359 | if (upper) |
2360 | screenPos += upper->rect().topLeft(); |
2361 | |
2362 | return screenPos; |
2363 | } |
2364 | |
2365 | // Editable Text interface |
2366 | static QString textForRange(QAccessibleInterface *accessible, int startOffset, int endOffset) |
2367 | { |
2368 | if (QAccessibleTextInterface *textIface = accessible->textInterface()) { |
2369 | if (endOffset == -1) |
2370 | endOffset = textIface->characterCount(); |
2371 | return textIface->text(startOffset, endOffset); |
2372 | } |
2373 | QString txt = accessible->text(t: QAccessible::Value); |
2374 | if (endOffset == -1) |
2375 | endOffset = txt.size(); |
2376 | return txt.mid(position: startOffset, n: endOffset - startOffset); |
2377 | } |
2378 | |
2379 | static void replaceTextFallback(QAccessibleInterface *accessible, long startOffset, long endOffset, const QString &txt) |
2380 | { |
2381 | QString t = textForRange(accessible, startOffset: 0, endOffset: -1); |
2382 | if (endOffset == -1) |
2383 | endOffset = t.size(); |
2384 | if (endOffset - startOffset == 0) |
2385 | t.insert(i: startOffset, s: txt); |
2386 | else |
2387 | t.replace(i: startOffset, len: endOffset - startOffset, after: txt); |
2388 | accessible->setText(t: QAccessible::Value, text: t); |
2389 | } |
2390 | |
2391 | bool AtSpiAdaptor::editableTextInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
2392 | { |
2393 | if (function == "CopyText"_L1 ) { |
2394 | #ifndef QT_NO_CLIPBOARD |
2395 | int startOffset = message.arguments().at(i: 0).toInt(); |
2396 | int endOffset = message.arguments().at(i: 1).toInt(); |
2397 | const QString t = textForRange(accessible: interface, startOffset, endOffset); |
2398 | QGuiApplication::clipboard()->setText(t); |
2399 | #endif |
2400 | connection.send(message: message.createReply(argument: true)); |
2401 | } else if (function == "CutText"_L1 ) { |
2402 | #ifndef QT_NO_CLIPBOARD |
2403 | int startOffset = message.arguments().at(i: 0).toInt(); |
2404 | int endOffset = message.arguments().at(i: 1).toInt(); |
2405 | const QString t = textForRange(accessible: interface, startOffset, endOffset); |
2406 | if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) |
2407 | editableTextIface->deleteText(startOffset, endOffset); |
2408 | else |
2409 | replaceTextFallback(accessible: interface, startOffset, endOffset, txt: QString()); |
2410 | QGuiApplication::clipboard()->setText(t); |
2411 | #endif |
2412 | connection.send(message: message.createReply(argument: true)); |
2413 | } else if (function == "DeleteText"_L1 ) { |
2414 | int startOffset = message.arguments().at(i: 0).toInt(); |
2415 | int endOffset = message.arguments().at(i: 1).toInt(); |
2416 | if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) |
2417 | editableTextIface->deleteText(startOffset, endOffset); |
2418 | else |
2419 | replaceTextFallback(accessible: interface, startOffset, endOffset, txt: QString()); |
2420 | connection.send(message: message.createReply(argument: true)); |
2421 | } else if (function == "InsertText"_L1 ) { |
2422 | int position = message.arguments().at(i: 0).toInt(); |
2423 | QString text = message.arguments().at(i: 1).toString(); |
2424 | int length = message.arguments().at(i: 2).toInt(); |
2425 | text.resize(size: length); |
2426 | if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) |
2427 | editableTextIface->insertText(offset: position, text); |
2428 | else |
2429 | replaceTextFallback(accessible: interface, startOffset: position, endOffset: position, txt: text); |
2430 | connection.send(message: message.createReply(argument: true)); |
2431 | } else if (function == "PasteText"_L1 ) { |
2432 | #ifndef QT_NO_CLIPBOARD |
2433 | int position = message.arguments().at(i: 0).toInt(); |
2434 | const QString txt = QGuiApplication::clipboard()->text(); |
2435 | if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) |
2436 | editableTextIface->insertText(offset: position, text: txt); |
2437 | else |
2438 | replaceTextFallback(accessible: interface, startOffset: position, endOffset: position, txt); |
2439 | #endif |
2440 | connection.send(message: message.createReply(argument: true)); |
2441 | } else if (function == "SetTextContents"_L1 ) { |
2442 | QString newContents = message.arguments().at(i: 0).toString(); |
2443 | if (QAccessibleEditableTextInterface *editableTextIface = interface->editableTextInterface()) |
2444 | editableTextIface->replaceText(startOffset: 0, endOffset: interface->textInterface()->characterCount(), text: newContents); |
2445 | else |
2446 | replaceTextFallback(accessible: interface, startOffset: 0, endOffset: -1, txt: newContents); |
2447 | connection.send(message: message.createReply(argument: true)); |
2448 | } else if (function.isEmpty()) { |
2449 | connection.send(message: message.createReply()); |
2450 | } else { |
2451 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::editableTextInterface does not implement" << function << message.path(); |
2452 | return false; |
2453 | } |
2454 | return true; |
2455 | } |
2456 | |
2457 | // Value interface |
2458 | bool AtSpiAdaptor::valueInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
2459 | { |
2460 | QAccessibleValueInterface *valueIface = interface->valueInterface(); |
2461 | if (!valueIface) |
2462 | return false; |
2463 | |
2464 | if (function == "SetCurrentValue"_L1 ) { |
2465 | QDBusVariant v = qvariant_cast<QDBusVariant>(v: message.arguments().at(i: 2)); |
2466 | double value = v.variant().toDouble(); |
2467 | //Temporary fix |
2468 | //See https://bugzilla.gnome.org/show_bug.cgi?id=652596 |
2469 | valueIface->setCurrentValue(value); |
2470 | connection.send(message: message.createReply()); |
2471 | } else { |
2472 | QVariant value; |
2473 | if (function == "GetCurrentValue"_L1 ) |
2474 | value = valueIface->currentValue(); |
2475 | else if (function == "GetMaximumValue"_L1 ) |
2476 | value = valueIface->maximumValue(); |
2477 | else if (function == "GetMinimumIncrement"_L1 ) |
2478 | value = valueIface->minimumStepSize(); |
2479 | else if (function == "GetMinimumValue"_L1 ) |
2480 | value = valueIface->minimumValue(); |
2481 | else { |
2482 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::valueInterface does not implement" << function << message.path(); |
2483 | return false; |
2484 | } |
2485 | if (!value.canConvert<double>()) { |
2486 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::valueInterface: Could not convert to double:" << function; |
2487 | } |
2488 | |
2489 | // explicitly convert to dbus-variant containing one double since atspi expects that |
2490 | // everything else might fail to convert back on the other end |
2491 | connection.send(message: message.createReply( |
2492 | argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: value.toDouble()))))); |
2493 | } |
2494 | return true; |
2495 | } |
2496 | |
2497 | // Selection interface |
2498 | bool AtSpiAdaptor::selectionInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
2499 | { |
2500 | QAccessibleSelectionInterface* selectionInterface = interface->selectionInterface(); |
2501 | if (!selectionInterface) { |
2502 | qCWarning(lcAccessibilityAtspi) << "Could not find selection interface for: " << message.path() << interface; |
2503 | return false; |
2504 | } |
2505 | |
2506 | if (function == "ClearSelection"_L1 ) { |
2507 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: (selectionInterface->clear())))); |
2508 | } else if (function == "DeselectChild"_L1 ) { |
2509 | int childIndex = message.arguments().at(i: 0).toInt(); |
2510 | bool ret = false; |
2511 | QAccessibleInterface *child = interface->child(index: childIndex); |
2512 | if (child) |
2513 | ret = selectionInterface->unselect(childItem: child); |
2514 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ret))); |
2515 | } else if (function == "DeselectSelectedChild"_L1 ) { |
2516 | int selectionIndex = message.arguments().at(i: 0).toInt(); |
2517 | bool ret = false; |
2518 | QAccessibleInterface *selectedChild = selectionInterface->selectedItem(selectionIndex); |
2519 | if (selectedChild) |
2520 | ret = selectionInterface->unselect(childItem: selectedChild); |
2521 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ret))); |
2522 | } else if (function == "GetNSelectedChildren"_L1 ) { |
2523 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2524 | QVariant::fromValue(value: selectionInterface->selectedItemCount()))))); |
2525 | } else if (function == "GetSelectedChild"_L1 ) { |
2526 | int selectionIndex = message.arguments().at(i: 0).toInt(); |
2527 | QSpiObjectReference ref(connection, QDBusObjectPath(pathForInterface(interface: selectionInterface->selectedItem(selectionIndex)))); |
2528 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ref))); |
2529 | } else if (function == "IsChildSelected"_L1 ) { |
2530 | int childIndex = message.arguments().at(i: 0).toInt(); |
2531 | bool ret = false; |
2532 | QAccessibleInterface *child = interface->child(index: childIndex); |
2533 | if (child) |
2534 | ret = selectionInterface->isSelected(childItem: child); |
2535 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ret))); |
2536 | } else if (function == "SelectAll"_L1 ) { |
2537 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: selectionInterface->selectAll()))); |
2538 | } else if (function == "SelectChild"_L1 ) { |
2539 | int childIndex = message.arguments().at(i: 0).toInt(); |
2540 | bool ret = false; |
2541 | QAccessibleInterface *child = interface->child(index: childIndex); |
2542 | if (child) |
2543 | ret = selectionInterface->select(childItem: child); |
2544 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ret))); |
2545 | } else { |
2546 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::selectionInterface does not implement " << function << message.path(); |
2547 | return false; |
2548 | } |
2549 | |
2550 | return true; |
2551 | } |
2552 | |
2553 | |
2554 | // Table interface |
2555 | bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
2556 | { |
2557 | if (!(interface->tableInterface() || interface->tableCellInterface())) { |
2558 | qCWarning(lcAccessibilityAtspi) << "Qt AtSpiAdaptor: Could not find table interface for:" << message.path() << interface; |
2559 | return false; |
2560 | } |
2561 | |
2562 | if (function == "GetCaption"_L1 ) { |
2563 | QAccessibleInterface * captionInterface= interface->tableInterface()->caption(); |
2564 | if (captionInterface) { |
2565 | QSpiObjectReference ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: captionInterface))); |
2566 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: ref)))); |
2567 | } else { |
2568 | sendReply(connection, message, argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue( |
2569 | value: QSpiObjectReference(connection, QDBusObjectPath(ATSPI_DBUS_PATH_NULL)))))); |
2570 | } |
2571 | } else if (function == "GetNColumns"_L1 ) { |
2572 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2573 | QVariant::fromValue(value: interface->tableInterface()->columnCount()))))); |
2574 | } else if (function == "GetNRows"_L1 ) { |
2575 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2576 | QVariant::fromValue(value: interface->tableInterface()->rowCount()))))); |
2577 | } else if (function == "GetNSelectedColumns"_L1 ) { |
2578 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2579 | QVariant::fromValue(value: interface->tableInterface()->selectedColumnCount()))))); |
2580 | } else if (function == "GetNSelectedRows"_L1 ) { |
2581 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2582 | QVariant::fromValue(value: interface->tableInterface()->selectedRowCount()))))); |
2583 | } else if (function == "GetSummary"_L1 ) { |
2584 | QAccessibleInterface *summary = interface->tableInterface() ? interface->tableInterface()->summary() : nullptr; |
2585 | QSpiObjectReference ref(connection, QDBusObjectPath(pathForInterface(interface: summary))); |
2586 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: ref))))); |
2587 | } else if (function == "GetAccessibleAt"_L1 ) { |
2588 | int row = message.arguments().at(i: 0).toInt(); |
2589 | int column = message.arguments().at(i: 1).toInt(); |
2590 | if ((row < 0) || |
2591 | (column < 0) || |
2592 | (row >= interface->tableInterface()->rowCount()) || |
2593 | (column >= interface->tableInterface()->columnCount())) { |
2594 | qCWarning(lcAccessibilityAtspi) << "Invalid index for tableInterface GetAccessibleAt (" << row << "," << column << ')'; |
2595 | return false; |
2596 | } |
2597 | |
2598 | QSpiObjectReference ref; |
2599 | QAccessibleInterface * cell(interface->tableInterface()->cellAt(row, column)); |
2600 | if (cell) { |
2601 | ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: cell))); |
2602 | } else { |
2603 | qCWarning(lcAccessibilityAtspi) << "No cell interface returned for" << interface->object() << row << column; |
2604 | ref = QSpiObjectReference(); |
2605 | } |
2606 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ref))); |
2607 | |
2608 | } else if (function == "GetIndexAt"_L1 ) { |
2609 | int row = message.arguments().at(i: 0).toInt(); |
2610 | int column = message.arguments().at(i: 1).toInt(); |
2611 | QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column); |
2612 | if (!cell) { |
2613 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::GetIndexAt(" << row << ',' << column << ") did not find a cell." << interface; |
2614 | return false; |
2615 | } |
2616 | int index = interface->indexOfChild(cell); |
2617 | qCDebug(lcAccessibilityAtspi) << "QSpiAdaptor::GetIndexAt row:" << row << " col:" << column << " logical index:" << index; |
2618 | Q_ASSERT(index > 0); |
2619 | connection.send(message: message.createReply(argument: index)); |
2620 | } else if ((function == "GetColumnAtIndex"_L1 ) || (function == "GetRowAtIndex"_L1 )) { |
2621 | int index = message.arguments().at(i: 0).toInt(); |
2622 | int ret = -1; |
2623 | if (index >= 0) { |
2624 | QAccessibleInterface * cell = interface->child(index); |
2625 | if (cell) { |
2626 | if (function == "GetColumnAtIndex"_L1 ) { |
2627 | if (cell->role() == QAccessible::ColumnHeader) { |
2628 | ret = index; |
2629 | } else if (cell->role() == QAccessible::RowHeader) { |
2630 | ret = -1; |
2631 | } else { |
2632 | if (!cell->tableCellInterface()) { |
2633 | qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No table cell interface: " << cell; |
2634 | return false; |
2635 | } |
2636 | ret = cell->tableCellInterface()->columnIndex(); |
2637 | } |
2638 | } else { |
2639 | if (cell->role() == QAccessible::ColumnHeader) { |
2640 | ret = -1; |
2641 | } else if (cell->role() == QAccessible::RowHeader) { |
2642 | ret = index % interface->tableInterface()->columnCount(); |
2643 | } else { |
2644 | if (!cell->tableCellInterface()) { |
2645 | qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No table cell interface: " << cell; |
2646 | return false; |
2647 | } |
2648 | ret = cell->tableCellInterface()->rowIndex(); |
2649 | } |
2650 | } |
2651 | } else { |
2652 | qCWarning(lcAccessibilityAtspi).nospace() << "AtSpiAdaptor::" << function << " No cell at index: " << index << " " << interface; |
2653 | return false; |
2654 | } |
2655 | } |
2656 | connection.send(message: message.createReply(argument: ret)); |
2657 | |
2658 | } else if (function == "GetColumnDescription"_L1 ) { |
2659 | int column = message.arguments().at(i: 0).toInt(); |
2660 | connection.send(message: message.createReply(argument: interface->tableInterface()->columnDescription(column))); |
2661 | } else if (function == "GetRowDescription"_L1 ) { |
2662 | int row = message.arguments().at(i: 0).toInt(); |
2663 | connection.send(message: message.createReply(argument: interface->tableInterface()->rowDescription(row))); |
2664 | |
2665 | |
2666 | |
2667 | } else if (function == "GetRowColumnExtentsAtIndex"_L1 ) { |
2668 | int index = message.arguments().at(i: 0).toInt(); |
2669 | bool success = false; |
2670 | |
2671 | int row = -1; |
2672 | int col = -1; |
2673 | int rowExtents = -1; |
2674 | int colExtents = -1; |
2675 | bool isSelected = false; |
2676 | |
2677 | int cols = interface->tableInterface()->columnCount(); |
2678 | if (cols > 0) { |
2679 | row = index / cols; |
2680 | col = index % cols; |
2681 | QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, column: col)->tableCellInterface(); |
2682 | if (cell) { |
2683 | row = cell->rowIndex(); |
2684 | col = cell->columnIndex(); |
2685 | rowExtents = cell->rowExtent(); |
2686 | colExtents = cell->columnExtent(); |
2687 | isSelected = cell->isSelected(); |
2688 | success = true; |
2689 | } |
2690 | } |
2691 | QVariantList list; |
2692 | list << success << row << col << rowExtents << colExtents << isSelected; |
2693 | connection.send(message: message.createReply(arguments: list)); |
2694 | |
2695 | } else if (function == "GetColumnExtentAt"_L1 ) { |
2696 | int row = message.arguments().at(i: 0).toInt(); |
2697 | int column = message.arguments().at(i: 1).toInt(); |
2698 | connection.send(message: message.createReply(argument: interface->tableInterface()->cellAt(row, column)->tableCellInterface()->columnExtent())); |
2699 | |
2700 | } else if (function == "GetRowExtentAt"_L1 ) { |
2701 | int row = message.arguments().at(i: 0).toInt(); |
2702 | int column = message.arguments().at(i: 1).toInt(); |
2703 | connection.send(message: message.createReply(argument: interface->tableInterface()->cellAt(row, column)->tableCellInterface()->rowExtent())); |
2704 | |
2705 | } else if (function == "GetColumnHeader"_L1 ) { |
2706 | int column = message.arguments().at(i: 0).toInt(); |
2707 | QSpiObjectReference ref; |
2708 | |
2709 | QAccessibleInterface * cell(interface->tableInterface()->cellAt(row: 0, column)); |
2710 | if (cell && cell->tableCellInterface()) { |
2711 | QList<QAccessibleInterface*> = cell->tableCellInterface()->columnHeaderCells(); |
2712 | if (header.size() > 0) { |
2713 | ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: header.takeAt(i: 0)))); |
2714 | } |
2715 | } |
2716 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ref))); |
2717 | |
2718 | } else if (function == "GetRowHeader"_L1 ) { |
2719 | int row = message.arguments().at(i: 0).toInt(); |
2720 | QSpiObjectReference ref; |
2721 | QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column: 0); |
2722 | if (cell && cell->tableCellInterface()) { |
2723 | QList<QAccessibleInterface*> = cell->tableCellInterface()->rowHeaderCells(); |
2724 | if (header.size() > 0) { |
2725 | ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: header.takeAt(i: 0)))); |
2726 | } |
2727 | } |
2728 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: ref))); |
2729 | |
2730 | } else if (function == "GetSelectedColumns"_L1 ) { |
2731 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: interface->tableInterface()->selectedColumns()))); |
2732 | } else if (function == "GetSelectedRows"_L1 ) { |
2733 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: interface->tableInterface()->selectedRows()))); |
2734 | } else if (function == "IsColumnSelected"_L1 ) { |
2735 | int column = message.arguments().at(i: 0).toInt(); |
2736 | connection.send(message: message.createReply(argument: interface->tableInterface()->isColumnSelected(column))); |
2737 | } else if (function == "IsRowSelected"_L1 ) { |
2738 | int row = message.arguments().at(i: 0).toInt(); |
2739 | connection.send(message: message.createReply(argument: interface->tableInterface()->isRowSelected(row))); |
2740 | } else if (function == "IsSelected"_L1 ) { |
2741 | int row = message.arguments().at(i: 0).toInt(); |
2742 | int column = message.arguments().at(i: 1).toInt(); |
2743 | QAccessibleTableCellInterface* cell = interface->tableInterface()->cellAt(row, column)->tableCellInterface(); |
2744 | connection.send(message: message.createReply(argument: cell->isSelected())); |
2745 | } else if (function == "AddColumnSelection"_L1 ) { |
2746 | int column = message.arguments().at(i: 0).toInt(); |
2747 | connection.send(message: message.createReply(argument: interface->tableInterface()->selectColumn(column))); |
2748 | } else if (function == "AddRowSelection"_L1 ) { |
2749 | int row = message.arguments().at(i: 0).toInt(); |
2750 | connection.send(message: message.createReply(argument: interface->tableInterface()->selectRow(row))); |
2751 | } else if (function == "RemoveColumnSelection"_L1 ) { |
2752 | int column = message.arguments().at(i: 0).toInt(); |
2753 | connection.send(message: message.createReply(argument: interface->tableInterface()->unselectColumn(column))); |
2754 | } else if (function == "RemoveRowSelection"_L1 ) { |
2755 | int row = message.arguments().at(i: 0).toInt(); |
2756 | connection.send(message: message.createReply(argument: interface->tableInterface()->unselectRow(row))); |
2757 | } else { |
2758 | qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::tableInterface does not implement" << function << message.path(); |
2759 | return false; |
2760 | } |
2761 | return true; |
2762 | } |
2763 | |
2764 | // Table cell interface |
2765 | bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) |
2766 | { |
2767 | QAccessibleTableCellInterface* cellInterface = interface->tableCellInterface(); |
2768 | if (!cellInterface) { |
2769 | qCWarning(lcAccessibilityAtspi) << "Could not find table cell interface for: " << message.path() << interface; |
2770 | return false; |
2771 | } |
2772 | |
2773 | if (function == "GetColumnSpan"_L1 ) { |
2774 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2775 | QVariant::fromValue(value: cellInterface->columnExtent()))))); |
2776 | } else if (function == "GetPosition"_L1 ) { |
2777 | const int row = cellInterface->rowIndex(); |
2778 | const int column = cellInterface->columnIndex(); |
2779 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2780 | QVariant::fromValue(value: QPoint(row, column)))))); |
2781 | } else if (function == "GetRowSpan"_L1 ) { |
2782 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant( |
2783 | QVariant::fromValue(value: cellInterface->rowExtent()))))); |
2784 | } else if (function == "GetRowColumnSpan"_L1 ) { |
2785 | QVariantList list; |
2786 | list << cellInterface->rowIndex() << cellInterface->columnIndex() << cellInterface->rowExtent() << cellInterface->columnExtent(); |
2787 | connection.send(message: message.createReply(arguments: list)); |
2788 | } else if (function == "GetTable"_L1 ) { |
2789 | QSpiObjectReference ref; |
2790 | QAccessibleInterface* table = cellInterface->table(); |
2791 | if (table && table->tableInterface()) |
2792 | ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(interface: table))); |
2793 | connection.send(message: message.createReply(argument: QVariant::fromValue(value: QDBusVariant(QVariant::fromValue(value: ref))))); |
2794 | } |
2795 | |
2796 | return true; |
2797 | } |
2798 | |
2799 | QT_END_NAMESPACE |
2800 | |
2801 | #include "moc_atspiadaptor_p.cpp" |
2802 | #endif // QT_CONFIG(accessibility) |
2803 | |