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

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