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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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