gtkatspicontext.c 57.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* gtkatspicontext.c: AT-SPI GtkATContext implementation
 *
 * Copyright 2020  GNOME Foundation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "gtkatspicontextprivate.h"

25
26
#include "gtkaccessibleprivate.h"

27
28
#include "gtkatspiactionprivate.h"
#include "gtkatspieditabletextprivate.h"
29
#include "gtkatspiprivate.h"
30
31
#include "gtkatspirootprivate.h"
#include "gtkatspiselectionprivate.h"
32
#include "gtkatspitextprivate.h"
33
#include "gtkatspiutilsprivate.h"
34
#include "gtkatspivalueprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
35
#include "gtkatspicomponentprivate.h"
36
#include "a11y/atspi/atspi-accessible.h"
37
#include "a11y/atspi/atspi-action.h"
Matthias Clasen's avatar
Matthias Clasen committed
38
#include "a11y/atspi/atspi-editabletext.h"
39
#include "a11y/atspi/atspi-text.h"
40
#include "a11y/atspi/atspi-value.h"
41
#include "a11y/atspi/atspi-selection.h"
Matthias Clasen's avatar
Matthias Clasen committed
42
#include "a11y/atspi/atspi-component.h"
43

44
#include "gtkdebug.h"
45
#include "gtkprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
46
#include "gtkeditable.h"
47
#include "gtkentryprivate.h"
48
#include "gtkroot.h"
49
#include "gtkstack.h"
Matthias Clasen's avatar
Matthias Clasen committed
50
#include "gtktextview.h"
51
#include "gtktypebuiltins.h"
52
#include "gtkwindow.h"
53
54
55

#include <gio/gio.h>

56
57
#include <locale.h>

58
59
60
61
62
#if defined(GDK_WINDOWING_WAYLAND)
# include <gdk/wayland/gdkwaylanddisplay.h>
#endif
#if defined(GDK_WINDOWING_X11)
# include <gdk/x11/gdkx11display.h>
63
# include <gdk/x11/gdkx11property.h>
64
65
#endif

Matthias Clasen's avatar
Matthias Clasen committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* We create a GtkAtSpiContext object for (almost) every widget.
 *
 * Each context implements a number of Atspi interfaces on a D-Bus
 * object. The context objects are connected into a tree by the
 * Parent property and GetChildAtIndex method of the Accessible
 * interface.
 *
 * The tree is an almost perfect mirror image of the widget tree,
 * with a few notable exceptions:
 *
 * - We don't create contexts for the GtkText widgets inside
 *   entry wrappers, since the Text functionality is represented
 *   on the entry contexts.
 *
 * - We insert non-widget backed context objects for each page
 *   of a stack. The main purpose of these extra context is to
 *   hold the TAB_PANEL role and be the target of the CONTROLS
 *   relation with their corresponding tabs (in the stack
 *   switcher or notebook).
85
 *
Lukáš Tyrychtr's avatar
Lukáš Tyrychtr committed
86
87
 * These are the exceptions implemented by GTK itself, but note that application
 * developers can customize the accessibility tree by implementing the
Lukáš Tyrychtr's avatar
Lukáš Tyrychtr committed
88
 * [iface@Gtk.Accessible] interface in any way they choose.
Matthias Clasen's avatar
Matthias Clasen committed
89
90
 */

91
92
93
struct _GtkAtSpiContext
{
  GtkATContext parent_instance;
94
95
96
97
98
99
100
101
102
103
104

  /* The root object, used as a entry point */
  GtkAtSpiRoot *root;

  /* The object path of the ATContext on the bus */
  char *context_path;

  /* Just a pointer; the connection is owned by the GtkAtSpiRoot
   * associated to the GtkATContext
   */
  GDBusConnection *connection;
105
106
107
108
109
110

  /* Accerciser refuses to work unless we implement a GetInterface
   * call that returns a list of all implemented interfaces. We
   * collect the answer here.
   */
  GVariant *interfaces;
111
112
113

  guint registration_ids[20];
  guint n_registered_objects;
114
115
};

116
117
G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT)

Matthias Clasen's avatar
Matthias Clasen committed
118
/* {{{ State handling */
Matthias Clasen's avatar
Matthias Clasen committed
119
static void
Matthias Clasen's avatar
Matthias Clasen committed
120
121
set_atspi_state (guint64        *states,
                 AtspiStateType  state)
Matthias Clasen's avatar
Matthias Clasen committed
122
{
Matthias Clasen's avatar
Matthias Clasen committed
123
  *states |= (G_GUINT64_CONSTANT (1) << state);
Matthias Clasen's avatar
Matthias Clasen committed
124
125
126
}

static void
Matthias Clasen's avatar
Matthias Clasen committed
127
128
unset_atspi_state (guint64        *states,
                   AtspiStateType  state)
Matthias Clasen's avatar
Matthias Clasen committed
129
{
Matthias Clasen's avatar
Matthias Clasen committed
130
  *states &= ~(G_GUINT64_CONSTANT (1) << state);
Matthias Clasen's avatar
Matthias Clasen committed
131
132
}

133
134
135
136
137
138
static void
collect_states (GtkAtSpiContext    *self,
                GVariantBuilder *builder)
{
  GtkATContext *ctx = GTK_AT_CONTEXT (self);
  GtkAccessibleValue *value;
139
  GtkAccessible *accessible;
Matthias Clasen's avatar
Matthias Clasen committed
140
141
  guint64 states = 0;

142
  accessible = gtk_at_context_get_accessible (ctx);
143

Matthias Clasen's avatar
Matthias Clasen committed
144
  set_atspi_state (&states, ATSPI_STATE_VISIBLE);
145

146
147
148
149
150
151
152
  if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_WINDOW)
    {
      set_atspi_state (&states, ATSPI_STATE_SHOWING);
      if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE))
        set_atspi_state (&states, ATSPI_STATE_ACTIVE);
    }

Matthias Clasen's avatar
Matthias Clasen committed
153
  if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX ||
154
155
      ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SEARCH_BOX ||
      ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SPIN_BUTTON)
Matthias Clasen's avatar
Matthias Clasen committed
156
    set_atspi_state (&states, ATSPI_STATE_EDITABLE);
Matthias Clasen's avatar
Matthias Clasen committed
157

158
159
160
161
162
  if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY))
    {
      value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY);
      if (gtk_boolean_accessible_value_get (value))
        {
Matthias Clasen's avatar
Matthias Clasen committed
163
164
          set_atspi_state (&states, ATSPI_STATE_READ_ONLY);
          unset_atspi_state (&states, ATSPI_STATE_EDITABLE);
165
166
        }
    }
Matthias Clasen's avatar
Matthias Clasen committed
167

168
  if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE))
Matthias Clasen's avatar
Matthias Clasen committed
169
    set_atspi_state (&states, ATSPI_STATE_FOCUSABLE);
170

171
  if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED))
Matthias Clasen's avatar
Matthias Clasen committed
172
    set_atspi_state (&states, ATSPI_STATE_FOCUSED);
173

174
175
176
177
  if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION))
    {
      value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION);
      if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL)
Matthias Clasen's avatar
Matthias Clasen committed
178
        set_atspi_state (&states, ATSPI_STATE_HORIZONTAL);
179
      else
Matthias Clasen's avatar
Matthias Clasen committed
180
        set_atspi_state (&states, ATSPI_STATE_VERTICAL);
181
182
183
184
185
186
    }

  if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL))
    {
      value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL);
      if (gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
187
        set_atspi_state (&states, ATSPI_STATE_MODAL);
188
189
190
191
192
193
    }

  if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE))
    {
      value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE);
      if (gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
194
        set_atspi_state (&states, ATSPI_STATE_MULTI_LINE);
195
196
    }

197
198
199
200
  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY);
      if (gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
201
        set_atspi_state (&states, ATSPI_STATE_BUSY);
202
203
204
205
206
207
208
209
    }

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED);
      switch (gtk_tristate_accessible_value_get (value))
        {
        case GTK_ACCESSIBLE_TRISTATE_TRUE:
Matthias Clasen's avatar
Matthias Clasen committed
210
          set_atspi_state (&states, ATSPI_STATE_CHECKED);
211
212
          break;
        case GTK_ACCESSIBLE_TRISTATE_MIXED:
Matthias Clasen's avatar
Matthias Clasen committed
213
          set_atspi_state (&states, ATSPI_STATE_INDETERMINATE);
214
215
216
217
218
219
220
221
222
223
224
          break;
        case GTK_ACCESSIBLE_TRISTATE_FALSE:
        default:
          break;
        }
    }

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED);
      if (!gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
225
        set_atspi_state (&states, ATSPI_STATE_SENSITIVE);
226
227
    }
  else
Matthias Clasen's avatar
Matthias Clasen committed
228
    set_atspi_state (&states, ATSPI_STATE_SENSITIVE);
229
230
231
232
233
234

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED);
      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
        {
Matthias Clasen's avatar
Matthias Clasen committed
235
          set_atspi_state (&states, ATSPI_STATE_EXPANDABLE);
236
          if (gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
237
            set_atspi_state (&states, ATSPI_STATE_EXPANDED);
238
239
240
241
242
243
244
245
246
247
248
        }
    }

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID);
      switch (gtk_invalid_accessible_value_get (value))
        {
        case GTK_ACCESSIBLE_INVALID_TRUE:
        case GTK_ACCESSIBLE_INVALID_GRAMMAR:
        case GTK_ACCESSIBLE_INVALID_SPELLING:
Matthias Clasen's avatar
Matthias Clasen committed
249
          set_atspi_state (&states, ATSPI_STATE_INVALID);
250
251
252
253
254
255
256
257
258
259
260
261
262
          break;
        case GTK_ACCESSIBLE_INVALID_FALSE:
        default:
          break;
        }
    }

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED);
      switch (gtk_tristate_accessible_value_get (value))
        {
        case GTK_ACCESSIBLE_TRISTATE_TRUE:
Matthias Clasen's avatar
Matthias Clasen committed
263
          set_atspi_state (&states, ATSPI_STATE_PRESSED);
264
265
          break;
        case GTK_ACCESSIBLE_TRISTATE_MIXED:
Matthias Clasen's avatar
Matthias Clasen committed
266
          set_atspi_state (&states, ATSPI_STATE_INDETERMINATE);
267
268
269
270
271
272
273
274
275
276
277
278
          break;
        case GTK_ACCESSIBLE_TRISTATE_FALSE:
        default:
          break;
        }
    }

  if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED))
    {
      value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED);
      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
        {
Matthias Clasen's avatar
Matthias Clasen committed
279
          set_atspi_state (&states, ATSPI_STATE_SELECTABLE);
280
          if (gtk_boolean_accessible_value_get (value))
Matthias Clasen's avatar
Matthias Clasen committed
281
            set_atspi_state (&states, ATSPI_STATE_SELECTED);
282
283
284
        }
    }

Matthias Clasen's avatar
Matthias Clasen committed
285
286
  g_variant_builder_add (builder, "u", (guint32) (states & 0xffffffff));
  g_variant_builder_add (builder, "u", (guint32) (states >> 32));
287
}
Matthias Clasen's avatar
Matthias Clasen committed
288
289
/* }}} */
/* {{{ Relation handling */
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
static void
collect_relations (GtkAtSpiContext *self,
                   GVariantBuilder *builder)
{
  GtkATContext *ctx = GTK_AT_CONTEXT (self);
  struct {
    GtkAccessibleRelation r;
    AtspiRelationType s;
  } map[] = {
    { GTK_ACCESSIBLE_RELATION_LABELLED_BY, ATSPI_RELATION_LABELLED_BY },
    { GTK_ACCESSIBLE_RELATION_CONTROLS, ATSPI_RELATION_CONTROLLER_FOR },
    { GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, ATSPI_RELATION_DESCRIBED_BY },
    { GTK_ACCESSIBLE_RELATION_FLOW_TO, ATSPI_RELATION_FLOWS_TO},
  };
  GtkAccessibleValue *value;
  GList *list, *l;
  GtkATContext *target_ctx;
  int i;

  for (i = 0; i < G_N_ELEMENTS (map); i++)
    {
      if (!gtk_at_context_has_accessible_relation (ctx, map[i].r))
        continue;

      GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));

      value = gtk_at_context_get_accessible_relation (ctx, map[i].r);
      list = gtk_reference_list_accessible_value_get (value);

      for (l = list; l; l = l->next)
        {
          target_ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (l->data));
322
323
324
325
326
327

          /* Realize the ATContext of the target, so we can ask for its ref */
          gtk_at_context_realize (target_ctx);

          g_variant_builder_add (&b, "@(so)",
                                 gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (target_ctx)));
328
329
330
331
332
        }

      g_variant_builder_add (builder, "(ua(so))", map[i].s, &b);
    }
}
Matthias Clasen's avatar
Matthias Clasen committed
333
334
/* }}} */
/* {{{ Accessible implementation */
335
static int
Lukáš Tyrychtr's avatar
Lukáš Tyrychtr committed
336
337
get_index_in (GtkAccessible *parent,
              GtkAccessible *child)
338
{
339
  GtkAccessible *candidate;
340
  guint res;
341

342
343
344
  if (parent == NULL)
    return -1;

345
  res = 0;
346
  for (candidate = gtk_accessible_get_first_accessible_child (parent);
347
348
       candidate != NULL;
       candidate = gtk_accessible_get_next_accessible_sibling (candidate))
349
    {
350
      if (candidate == child)
351
        return res;
352

353
      if (!gtk_accessible_should_present (candidate))
354
355
        continue;

356
      res++;
357
358
    }

359
  return -1;
360
361
}

362
363
364
static int
get_index_in_parent (GtkAccessible *accessible)
{
365
  GtkAccessible *parent = gtk_accessible_get_accessible_parent (accessible);
366
367
368
369
370

  if (parent != NULL)
    return get_index_in (parent, accessible);

  return -1;
371
372
}

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
static int
get_index_in_toplevels (GtkWidget *widget)
{
  GListModel *toplevels = gtk_window_get_toplevels ();
  guint n_toplevels = g_list_model_get_n_items (toplevels);
  GtkWidget *window;
  int idx;

  idx = 0;
  for (guint i = 0; i < n_toplevels; i++)
    {
      window = g_list_model_get_item (toplevels, i);

      g_object_unref (window);

      if (window == widget)
389
        return idx;
390

391
392
393
      if (!gtk_accessible_should_present (GTK_ACCESSIBLE (window)))
        continue;

394
395
396
      idx += 1;
    }

397
  return -1;
398
399
}

400
401
402
403
404
static GVariant *
get_parent_context_ref (GtkAccessible *accessible)
{
  GVariant *res = NULL;

405
  GtkAccessible *parent = gtk_accessible_get_accessible_parent (accessible);
406

407
408
409
410
  if (parent == NULL)
    {
      GtkATContext *context = gtk_accessible_get_at_context (accessible);
      GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context);
411

412
      res = gtk_at_spi_root_to_ref (self->root);
413
    }
414
  else
415
    {
416
417
      GtkATContext *parent_context = gtk_accessible_get_at_context (parent);
      gtk_at_context_realize (parent_context);
418

419
      res = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (parent_context));
420
421
422
423
424
425
426
427
    }

  if (res == NULL)
    res = gtk_at_spi_null_ref ();

  return res;
}

428
429
430
431
432
433
434
435
436
437
438
439
static void
handle_accessible_method (GDBusConnection       *connection,
                          const gchar           *sender,
                          const gchar           *object_path,
                          const gchar           *interface_name,
                          const gchar           *method_name,
                          GVariant              *parameters,
                          GDBusMethodInvocation *invocation,
                          gpointer               user_data)
{
  GtkAtSpiContext *self = user_data;

440
  GTK_DEBUG (A11Y, "handling %s on %s", method_name, object_path);
Matthias Clasen's avatar
Matthias Clasen committed
441

442
443
  if (g_strcmp0 (method_name, "GetRole") == 0)
    {
444
445
      guint atspi_role = gtk_atspi_role_for_context (GTK_AT_CONTEXT (self));

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", atspi_role));
    }
  else if (g_strcmp0 (method_name, "GetRoleName") == 0)
    {
      GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self));
      const char *name = gtk_accessible_role_to_name (role, NULL);
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name));
    }
  else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0)
    {
      GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self));
      const char *name = gtk_accessible_role_to_name (role, GETTEXT_PACKAGE);
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name));
    }
  else if (g_strcmp0 (method_name, "GetState") == 0)
    {
      GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)"));

      g_variant_builder_open (&builder, G_VARIANT_TYPE ("au"));
465
      collect_states (self, &builder);
466
467
468
469
470
471
472
473
474
475
      g_variant_builder_close (&builder);

      g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
    }
  else if (g_strcmp0 (method_name, "GetAttributes") == 0)
    {
      GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})"));

      g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}"));
      g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK");
476
477
478
479
480
481
482
483
484
485
486

      if (gtk_at_context_has_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER))
        {
          GtkAccessibleValue *value;

          value = gtk_at_context_get_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER);

          g_variant_builder_add (&builder, "{ss}",
                                 "placeholder-text", gtk_string_accessible_value_get (value));
        }

487
488
489
490
491
492
      g_variant_builder_close (&builder);

      g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
    }
  else if (g_strcmp0 (method_name, "GetApplication") == 0)
    {
493
      g_dbus_method_invocation_return_value (invocation,
494
                                             g_variant_new ("(@(so))", gtk_at_spi_root_to_ref (self->root)));
495
496
497
    }
  else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0)
    {
498
499
      GtkATContext *context = NULL;
      GtkAccessible *accessible;
500
      int idx, presentable_idx;
501
502
503

      g_variant_get (parameters, "(i)", &idx);

504
505
      accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));

506
      GtkAccessible *child;
507

508
      presentable_idx = 0;
509
510
511
      for (child = gtk_accessible_get_first_accessible_child (accessible);
           child != NULL;
           child = gtk_accessible_get_next_accessible_sibling (child))
512
        {
513
514
          if (!gtk_accessible_should_present (child))
              continue;
515

516
          if (presentable_idx == idx)
517
            break;
518
          presentable_idx++;
519

520
521
522
523
        }
      if (child)
        {
          context = gtk_accessible_get_at_context (child);
524
525
        }

526
      if (context == NULL)
527
528
529
530
531
532
533
534
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 G_IO_ERROR,
                                                 G_IO_ERROR_INVALID_ARGUMENT,
                                                 "No child with index %d", idx);
          return;
        }

535
536
537
538
      /* Realize the child ATContext in order to get its ref */
      gtk_at_context_realize (context);

      GVariant *ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context));
539

540
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", ref));
541
542
543
544
545
546
547
    }
  else if (g_strcmp0 (method_name, "GetChildren") == 0)
    {
      GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));

      GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));

548
      GtkAccessible *child;
549
550
551
552
      for (child = gtk_accessible_get_first_accessible_child (accessible);
           child != NULL;
           child = gtk_accessible_get_next_accessible_sibling (child))
      {
553
554
          if (!gtk_accessible_should_present (child))
            continue;
555

556
          GtkATContext *context = gtk_accessible_get_at_context (child);
557

558
559
          /* Realize the child ATContext in order to get its ref */
          gtk_at_context_realize (context);
560

561
          GVariant *ref = gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (context));
562

563
564
          if (ref != NULL)
            g_variant_builder_add (&builder, "@(so)", ref);
565
566
567
        }

      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder));
568
    }
569
570
  else if (g_strcmp0 (method_name, "GetIndexInParent") == 0)
    {
Emmanuele Bassi's avatar
Emmanuele Bassi committed
571
      int idx = gtk_at_spi_context_get_index_in_parent (self);
572

573
574
575
576
      if (idx == -1)
        g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not found");
      else
        g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", idx));
577
    }
578
579
580
  else if (g_strcmp0 (method_name, "GetRelationSet") == 0)
    {
      GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))"));
581
      collect_relations (self, &builder);
582
583
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder));
    }
Matthias Clasen's avatar
Matthias Clasen committed
584
585
  else if (g_strcmp0 (method_name, "GetInterfaces") == 0)
    {
586
      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@as)", self->interfaces));
Matthias Clasen's avatar
Matthias Clasen committed
587
588
    }

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
}

static GVariant *
handle_accessible_get_property (GDBusConnection       *connection,
                                const gchar           *sender,
                                const gchar           *object_path,
                                const gchar           *interface_name,
                                const gchar           *property_name,
                                GError               **error,
                                gpointer               user_data)
{
  GtkAtSpiContext *self = user_data;
  GVariant *res = NULL;

  GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));

605
  GTK_DEBUG (A11Y, "handling GetProperty %s on %s", property_name, object_path);
Matthias Clasen's avatar
Matthias Clasen committed
606

607
  if (g_strcmp0 (property_name, "Name") == 0)
608
    {
609
610
611
      char *label = gtk_at_context_get_name (GTK_AT_CONTEXT (self));
      res = g_variant_new_string (label ? label : "");
      g_free (label);
612
    }
613
614
  else if (g_strcmp0 (property_name, "Description") == 0)
    {
615
      char *label = gtk_at_context_get_description (GTK_AT_CONTEXT (self));
616
      res = g_variant_new_string (label ? label : "");
617
618
619
620
621
622
623
      g_free (label);
    }
  else if (g_strcmp0 (property_name, "Locale") == 0)
    res = g_variant_new_string (setlocale (LC_MESSAGES, NULL));
  else if (g_strcmp0 (property_name, "AccessibleId") == 0)
    res = g_variant_new_string ("");
  else if (g_strcmp0 (property_name, "Parent") == 0)
624
    res = get_parent_context_ref (accessible);
625
  else if (g_strcmp0 (property_name, "ChildCount") == 0)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
626
    res = g_variant_new_int32 (gtk_at_spi_context_get_child_count (self));
627
628
629
630
631
632
633
634
635
636
637
638
  else
    g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                 "Unknown property '%s'", property_name);

  return res;
}

static const GDBusInterfaceVTable accessible_vtable = {
  handle_accessible_method,
  handle_accessible_get_property,
  NULL,
};
Matthias Clasen's avatar
Matthias Clasen committed
639
640
/* }}} */
/* {{{ Change notification */
641
642
643
644
645
646
647
static void
emit_text_changed (GtkAtSpiContext *self,
                   const char      *kind,
                   int              start,
                   int              end,
                   const char      *text)
{
648
649
650
  if (self->connection == NULL)
    return;

651
652
653
654
655
656
657
658
659
660
661
  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "TextChanged",
                                 g_variant_new ("(siiva{sv})",
                                                kind, start, end, g_variant_new_string (text), NULL),
                                 NULL);
}

static void
662
663
664
emit_text_selection_changed (GtkAtSpiContext *self,
                             const char      *kind,
                             int              cursor_position)
665
{
666
667
668
  if (self->connection == NULL)
    return;

669
670
671
672
673
674
675
676
  if (strcmp (kind, "text-caret-moved") == 0)
    g_dbus_connection_emit_signal (self->connection,
                                   NULL,
                                   self->context_path,
                                   "org.a11y.atspi.Event.Object",
                                   "TextCaretMoved",
                                   g_variant_new ("(siiva{sv})",
                                                  "", cursor_position, 0, g_variant_new_string (""), NULL),
677
                                 NULL);
678
679
680
681
682
683
684
685
686
  else
    g_dbus_connection_emit_signal (self->connection,
                                   NULL,
                                   self->context_path,
                                   "org.a11y.atspi.Event.Object",
                                   "TextSelectionChanged",
                                   g_variant_new ("(siiva{sv})",
                                                  "", 0, 0, g_variant_new_string (""), NULL),
                                   NULL);
687
688
}

689
690
691
692
static void
emit_selection_changed (GtkAtSpiContext *self,
                        const char      *kind)
{
693
694
695
  if (self->connection == NULL)
    return;

696
697
698
699
700
701
702
703
704
705
  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "SelectionChanged",
                                 g_variant_new ("(siiva{sv})",
                                                "", 0, 0, g_variant_new_string (""), NULL),
                                 NULL);
}

706
static void
707
708
709
710
emit_state_changed (GtkAtSpiContext *self,
                    const char      *name,
                    gboolean         enabled)
{
711
712
713
  if (self->connection == NULL)
    return;

714
715
716
717
718
719
720
721
722
723
  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "StateChanged",
                                 g_variant_new ("(siiva{sv})",
                                                name, enabled, 0, g_variant_new_string ("0"), NULL),
                                 NULL);
}

724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
static void
emit_defunct (GtkAtSpiContext *self)
{
  if (self->connection == NULL)
    return;

  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "StateChanged",
                                 g_variant_new ("(siiva{sv})", "defunct", TRUE, 0, g_variant_new_string ("0"), NULL),
                                 NULL);
}

739
740
741
742
743
static void
emit_property_changed (GtkAtSpiContext *self,
                       const char      *name,
                       GVariant        *value)
{
744
745
746
  if (self->connection == NULL)
    return;

747
748
749
750
751
752
753
754
755
756
  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "PropertyChange",
                                 g_variant_new ("(siiva{sv})",
                                                name, 0, 0, value, NULL),
                                 NULL);
}

Matthias Clasen's avatar
Matthias Clasen committed
757
758
759
760
761
762
763
static void
emit_bounds_changed (GtkAtSpiContext *self,
                     int              x,
                     int              y,
                     int              width,
                     int              height)
{
764
765
766
  if (self->connection == NULL)
    return;

Matthias Clasen's avatar
Matthias Clasen committed
767
768
769
770
771
772
773
774
775
776
  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Object",
                                 "BoundsChanged",
                                 g_variant_new ("(siiva{sv})",
                                                "", 0, 0, g_variant_new ("(iiii)", x, y, width, height), NULL),
                                 NULL);
}

777
778
779
780
781
782
static void
emit_children_changed (GtkAtSpiContext         *self,
                       GtkAtSpiContext         *child_context,
                       int                      idx,
                       GtkAccessibleChildState  state)
{
783
784
785
786
  /* If we don't have a connection on either contexts, we cannot emit a signal */
  if (self->connection == NULL || child_context->connection == NULL)
    return;

787
  GVariant *context_ref = gtk_at_spi_context_to_ref (self);
788
  GVariant *child_ref = gtk_at_spi_context_to_ref (child_context);
789

790
791
792
793
794
795
  gtk_at_spi_emit_children_changed (self->connection,
                                    self->context_path,
                                    state,
                                    idx,
                                    child_ref,
                                    context_ref);
796
797
}

Matthias Clasen's avatar
Matthias Clasen committed
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
static void
emit_focus (GtkAtSpiContext *self,
            gboolean         focus_in)
{
  if (self->connection == NULL)
    return;

  if (focus_in)
    g_dbus_connection_emit_signal (self->connection,
                                   NULL,
                                   self->context_path,
                                   "org.a11y.atspi.Event.Focus",
                                   "Focus",
                                   g_variant_new ("(siiva{sv})",
                                                  "", 0, 0, g_variant_new_string ("0"), NULL),
                                   NULL);
}

816
static void
817
818
emit_window_event (GtkAtSpiContext *self,
                   const char      *event_type)
819
820
821
822
823
824
825
826
{
  if (self->connection == NULL)
    return;

  g_dbus_connection_emit_signal (self->connection,
                                 NULL,
                                 self->context_path,
                                 "org.a11y.atspi.Event.Window",
827
                                 event_type,
828
                                 g_variant_new ("(siiva{sv})",
829
830
831
                                                "", 0, 0,
                                                g_variant_new_string("0"),
                                                NULL),
832
833
834
                                 NULL);
}

835
836
static void
gtk_at_spi_context_state_change (GtkATContext                *ctx,
837
838
839
840
841
842
843
                                 GtkAccessibleStateChange     changed_states,
                                 GtkAccessiblePropertyChange  changed_properties,
                                 GtkAccessibleRelationChange  changed_relations,
                                 GtkAccessibleAttributeSet   *states,
                                 GtkAccessibleAttributeSet   *properties,
                                 GtkAccessibleAttributeSet   *relations)
{
844
  GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx);
Matthias Clasen's avatar
Matthias Clasen committed
845
  GtkAccessible *accessible = gtk_at_context_get_accessible (ctx);
846
847
  GtkAccessibleValue *value;

848
  if (GTK_IS_WIDGET (accessible) && !gtk_widget_get_realized (GTK_WIDGET (accessible)))
849
850
    return;

851
852
  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_HIDDEN)
    {
853
      GtkAccessible *parent;
854
855
      GtkATContext *context;
      GtkAccessibleChildChange change;
856
857

      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_HIDDEN);
858
859
860
861
      if (gtk_boolean_accessible_value_get (value))
        change = GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED;
      else
        change = GTK_ACCESSIBLE_CHILD_CHANGE_ADDED;
862

863
      if (GTK_IS_ROOT (accessible))
864
        {
865
          gtk_at_spi_root_child_changed (self->root, change, accessible);
866
          emit_state_changed (self, "showing", gtk_boolean_accessible_value_get (value));
867
868
        }
      else
869
        {
870
          parent = gtk_accessible_get_accessible_parent (accessible);
871

872
          context = gtk_accessible_get_at_context (parent);
873
          gtk_at_context_child_changed (context, change, accessible);
874
        }
875
876
    }

877
878
879
880
881
882
883
884
885
886
  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_BUSY)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_BUSY);
      emit_state_changed (self, "busy", gtk_boolean_accessible_value_get (value));
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_CHECKED)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_CHECKED);

887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_TRISTATE)
        {
          switch (gtk_tristate_accessible_value_get (value))
            {
            case GTK_ACCESSIBLE_TRISTATE_TRUE:
              emit_state_changed (self, "checked", TRUE);
              emit_state_changed (self, "indeterminate", FALSE);
              break;
            case GTK_ACCESSIBLE_TRISTATE_MIXED:
              emit_state_changed (self, "checked", FALSE);
              emit_state_changed (self, "indeterminate", TRUE);
              break;
            case GTK_ACCESSIBLE_TRISTATE_FALSE:
              emit_state_changed (self, "checked", FALSE);
              emit_state_changed (self, "indeterminate", FALSE);
              break;
            default:
              break;
            }
        }
      else
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
        {
          emit_state_changed (self, "checked", FALSE);
          emit_state_changed (self, "indeterminate", TRUE);
        }
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_DISABLED)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_DISABLED);
      emit_state_changed (self, "sensitive", !gtk_boolean_accessible_value_get (value));
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_EXPANDED);
      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
        {
          emit_state_changed (self, "expandable", TRUE);
          emit_state_changed (self, "expanded",gtk_boolean_accessible_value_get (value));
        }
      else
        emit_state_changed (self, "expandable", FALSE);
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_INVALID)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_INVALID);
      switch (gtk_invalid_accessible_value_get (value))
        {
        case GTK_ACCESSIBLE_INVALID_TRUE:
        case GTK_ACCESSIBLE_INVALID_GRAMMAR:
        case GTK_ACCESSIBLE_INVALID_SPELLING:
          emit_state_changed (self, "invalid", TRUE);
          break;
        case GTK_ACCESSIBLE_INVALID_FALSE:
          emit_state_changed (self, "invalid", FALSE);
944
          break;
945
946
947
948
949
950
951
952
        default:
          break;
        }
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_PRESSED)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_PRESSED);
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974

      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_TRISTATE)
        {
          switch (gtk_tristate_accessible_value_get (value))
            {
            case GTK_ACCESSIBLE_TRISTATE_TRUE:
              emit_state_changed (self, "pressed", TRUE);
              emit_state_changed (self, "indeterminate", FALSE);
              break;
            case GTK_ACCESSIBLE_TRISTATE_MIXED:
              emit_state_changed (self, "pressed", FALSE);
              emit_state_changed (self, "indeterminate", TRUE);
              break;
            case GTK_ACCESSIBLE_TRISTATE_FALSE:
              emit_state_changed (self, "pressed", FALSE);
              emit_state_changed (self, "indeterminate", FALSE);
              break;
            default:
              break;
            }
        }
      else
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
        {
          emit_state_changed (self, "pressed", FALSE);
          emit_state_changed (self, "indeterminate", TRUE);
        }
    }

  if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_SELECTED)
    {
      value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_SELECTED);
      if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
        {
          emit_state_changed (self, "selectable", TRUE);
          emit_state_changed (self, "selected",gtk_boolean_accessible_value_get (value));
        }
      else
        emit_state_changed (self, "selectable", FALSE);
    }

  if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_READ_ONLY)
    {
      gboolean readonly;

      value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_READ_ONLY);
      readonly = gtk_boolean_accessible_value_get (value);

      emit_state_changed (self, "read-only", readonly);
For faster browsing, not all history is shown. View entire blame