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