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

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