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