1 | /* gtkatspicontext.c: AT-SPI GtkATContext implementation |
2 | * |
3 | * Copyright 2020 GNOME Foundation |
4 | * |
5 | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gtkatspicontextprivate.h" |
24 | |
25 | #include "gtkaccessibleprivate.h" |
26 | |
27 | #include "gtkatspiactionprivate.h" |
28 | #include "gtkatspieditabletextprivate.h" |
29 | #include "gtkatspiprivate.h" |
30 | #include "gtkatspirootprivate.h" |
31 | #include "gtkatspiselectionprivate.h" |
32 | #include "gtkatspitextprivate.h" |
33 | #include "gtkatspiutilsprivate.h" |
34 | #include "gtkatspivalueprivate.h" |
35 | #include "gtkatspicomponentprivate.h" |
36 | #include "a11y/atspi/atspi-accessible.h" |
37 | #include "a11y/atspi/atspi-action.h" |
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" |
42 | #include "a11y/atspi/atspi-component.h" |
43 | |
44 | #include "gtkdebug.h" |
45 | #include "gtkeditable.h" |
46 | #include "gtkentryprivate.h" |
47 | #include "gtkroot.h" |
48 | #include "gtkstack.h" |
49 | #include "gtktextview.h" |
50 | #include "gtktypebuiltins.h" |
51 | #include "gtkwindow.h" |
52 | |
53 | #include <gio/gio.h> |
54 | |
55 | #include <locale.h> |
56 | |
57 | #if defined(GDK_WINDOWING_WAYLAND) |
58 | # include <gdk/wayland/gdkwaylanddisplay.h> |
59 | #endif |
60 | #if defined(GDK_WINDOWING_X11) |
61 | # include <gdk/x11/gdkx11display.h> |
62 | # include <gdk/x11/gdkx11property.h> |
63 | #endif |
64 | |
65 | /* We create a GtkAtSpiContext object for (almost) every widget. |
66 | * |
67 | * Each context implements a number of Atspi interfaces on a D-Bus |
68 | * object. The context objects are connected into a tree by the |
69 | * Parent property and GetChildAtIndex method of the Accessible |
70 | * interface. |
71 | * |
72 | * The tree is an almost perfect mirror image of the widget tree, |
73 | * with a few notable exceptions: |
74 | * |
75 | * - We don't create contexts for the GtkText widgets inside |
76 | * entry wrappers, since the Text functionality is represented |
77 | * on the entry contexts. |
78 | * |
79 | * - We insert non-widget backed context objects for each page |
80 | * of a stack. The main purpose of these extra context is to |
81 | * hold the TAB_PANEL role and be the target of the CONTROLS |
82 | * relation with their corresponding tabs (in the stack |
83 | * switcher or notebook). |
84 | */ |
85 | |
86 | struct _GtkAtSpiContext |
87 | { |
88 | GtkATContext parent_instance; |
89 | |
90 | /* The root object, used as a entry point */ |
91 | GtkAtSpiRoot *root; |
92 | |
93 | /* The object path of the ATContext on the bus */ |
94 | char *context_path; |
95 | |
96 | /* Just a pointer; the connection is owned by the GtkAtSpiRoot |
97 | * associated to the GtkATContext |
98 | */ |
99 | GDBusConnection *connection; |
100 | |
101 | /* Accerciser refuses to work unless we implement a GetInterface |
102 | * call that returns a list of all implemented interfaces. We |
103 | * collect the answer here. |
104 | */ |
105 | GVariant *interfaces; |
106 | |
107 | guint registration_ids[20]; |
108 | guint n_registered_objects; |
109 | }; |
110 | |
111 | G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT) |
112 | |
113 | /* {{{ State handling */ |
114 | static void |
115 | set_atspi_state (guint64 *states, |
116 | AtspiStateType state) |
117 | { |
118 | *states |= (G_GUINT64_CONSTANT (1) << state); |
119 | } |
120 | |
121 | static void |
122 | unset_atspi_state (guint64 *states, |
123 | AtspiStateType state) |
124 | { |
125 | *states &= ~(G_GUINT64_CONSTANT (1) << state); |
126 | } |
127 | |
128 | static void |
129 | collect_states (GtkAtSpiContext *self, |
130 | GVariantBuilder *builder) |
131 | { |
132 | GtkATContext *ctx = GTK_AT_CONTEXT (ptr: self); |
133 | GtkAccessibleValue *value; |
134 | GtkAccessible *accessible; |
135 | guint64 states = 0; |
136 | |
137 | accessible = gtk_at_context_get_accessible (self: ctx); |
138 | |
139 | set_atspi_state (states: &states, state: ATSPI_STATE_VISIBLE); |
140 | |
141 | if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_WINDOW) |
142 | { |
143 | set_atspi_state (states: &states, state: ATSPI_STATE_SHOWING); |
144 | if (gtk_accessible_get_platform_state (self: accessible, state: GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE)) |
145 | set_atspi_state (states: &states, state: ATSPI_STATE_ACTIVE); |
146 | } |
147 | |
148 | if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX || |
149 | ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SEARCH_BOX || |
150 | ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SPIN_BUTTON) |
151 | set_atspi_state (states: &states, state: ATSPI_STATE_EDITABLE); |
152 | |
153 | if (gtk_at_context_has_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_READ_ONLY)) |
154 | { |
155 | value = gtk_at_context_get_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_READ_ONLY); |
156 | if (gtk_boolean_accessible_value_get (value)) |
157 | { |
158 | set_atspi_state (states: &states, state: ATSPI_STATE_READ_ONLY); |
159 | unset_atspi_state (states: &states, state: ATSPI_STATE_EDITABLE); |
160 | } |
161 | } |
162 | |
163 | if (gtk_accessible_get_platform_state (self: accessible, state: GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE)) |
164 | set_atspi_state (states: &states, state: ATSPI_STATE_FOCUSABLE); |
165 | |
166 | if (gtk_accessible_get_platform_state (self: accessible, state: GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED)) |
167 | set_atspi_state (states: &states, state: ATSPI_STATE_FOCUSED); |
168 | |
169 | if (gtk_at_context_has_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_ORIENTATION)) |
170 | { |
171 | value = gtk_at_context_get_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_ORIENTATION); |
172 | if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) |
173 | set_atspi_state (states: &states, state: ATSPI_STATE_HORIZONTAL); |
174 | else |
175 | set_atspi_state (states: &states, state: ATSPI_STATE_VERTICAL); |
176 | } |
177 | |
178 | if (gtk_at_context_has_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_MODAL)) |
179 | { |
180 | value = gtk_at_context_get_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_MODAL); |
181 | if (gtk_boolean_accessible_value_get (value)) |
182 | set_atspi_state (states: &states, state: ATSPI_STATE_MODAL); |
183 | } |
184 | |
185 | if (gtk_at_context_has_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_MULTI_LINE)) |
186 | { |
187 | value = gtk_at_context_get_accessible_property (self: ctx, property: GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); |
188 | if (gtk_boolean_accessible_value_get (value)) |
189 | set_atspi_state (states: &states, state: ATSPI_STATE_MULTI_LINE); |
190 | } |
191 | |
192 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_BUSY)) |
193 | { |
194 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_BUSY); |
195 | if (gtk_boolean_accessible_value_get (value)) |
196 | set_atspi_state (states: &states, state: ATSPI_STATE_BUSY); |
197 | } |
198 | |
199 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_CHECKED)) |
200 | { |
201 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_CHECKED); |
202 | switch (gtk_tristate_accessible_value_get (value)) |
203 | { |
204 | case GTK_ACCESSIBLE_TRISTATE_TRUE: |
205 | set_atspi_state (states: &states, state: ATSPI_STATE_CHECKED); |
206 | break; |
207 | case GTK_ACCESSIBLE_TRISTATE_MIXED: |
208 | set_atspi_state (states: &states, state: ATSPI_STATE_INDETERMINATE); |
209 | break; |
210 | case GTK_ACCESSIBLE_TRISTATE_FALSE: |
211 | default: |
212 | break; |
213 | } |
214 | } |
215 | |
216 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_DISABLED)) |
217 | { |
218 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_DISABLED); |
219 | if (!gtk_boolean_accessible_value_get (value)) |
220 | set_atspi_state (states: &states, state: ATSPI_STATE_SENSITIVE); |
221 | } |
222 | else |
223 | set_atspi_state (states: &states, state: ATSPI_STATE_SENSITIVE); |
224 | |
225 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_EXPANDED)) |
226 | { |
227 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_EXPANDED); |
228 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) |
229 | { |
230 | set_atspi_state (states: &states, state: ATSPI_STATE_EXPANDABLE); |
231 | if (gtk_boolean_accessible_value_get (value)) |
232 | set_atspi_state (states: &states, state: ATSPI_STATE_EXPANDED); |
233 | } |
234 | } |
235 | |
236 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_INVALID)) |
237 | { |
238 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_INVALID); |
239 | switch (gtk_invalid_accessible_value_get (value)) |
240 | { |
241 | case GTK_ACCESSIBLE_INVALID_TRUE: |
242 | case GTK_ACCESSIBLE_INVALID_GRAMMAR: |
243 | case GTK_ACCESSIBLE_INVALID_SPELLING: |
244 | set_atspi_state (states: &states, state: ATSPI_STATE_INVALID); |
245 | break; |
246 | case GTK_ACCESSIBLE_INVALID_FALSE: |
247 | default: |
248 | break; |
249 | } |
250 | } |
251 | |
252 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_PRESSED)) |
253 | { |
254 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_PRESSED); |
255 | switch (gtk_tristate_accessible_value_get (value)) |
256 | { |
257 | case GTK_ACCESSIBLE_TRISTATE_TRUE: |
258 | set_atspi_state (states: &states, state: ATSPI_STATE_PRESSED); |
259 | break; |
260 | case GTK_ACCESSIBLE_TRISTATE_MIXED: |
261 | set_atspi_state (states: &states, state: ATSPI_STATE_INDETERMINATE); |
262 | break; |
263 | case GTK_ACCESSIBLE_TRISTATE_FALSE: |
264 | default: |
265 | break; |
266 | } |
267 | } |
268 | |
269 | if (gtk_at_context_has_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_SELECTED)) |
270 | { |
271 | value = gtk_at_context_get_accessible_state (self: ctx, state: GTK_ACCESSIBLE_STATE_SELECTED); |
272 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) |
273 | { |
274 | set_atspi_state (states: &states, state: ATSPI_STATE_SELECTABLE); |
275 | if (gtk_boolean_accessible_value_get (value)) |
276 | set_atspi_state (states: &states, state: ATSPI_STATE_SELECTED); |
277 | } |
278 | } |
279 | |
280 | g_variant_builder_add (builder, format_string: "u" , (guint32) (states & 0xffffffff)); |
281 | g_variant_builder_add (builder, format_string: "u" , (guint32) (states >> 32)); |
282 | } |
283 | /* }}} */ |
284 | /* {{{ Relation handling */ |
285 | static void |
286 | collect_relations (GtkAtSpiContext *self, |
287 | GVariantBuilder *builder) |
288 | { |
289 | GtkATContext *ctx = GTK_AT_CONTEXT (ptr: self); |
290 | struct { |
291 | GtkAccessibleRelation r; |
292 | AtspiRelationType s; |
293 | } map[] = { |
294 | { GTK_ACCESSIBLE_RELATION_LABELLED_BY, ATSPI_RELATION_LABELLED_BY }, |
295 | { GTK_ACCESSIBLE_RELATION_CONTROLS, ATSPI_RELATION_CONTROLLER_FOR }, |
296 | { GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, ATSPI_RELATION_DESCRIBED_BY }, |
297 | { GTK_ACCESSIBLE_RELATION_FLOW_TO, ATSPI_RELATION_FLOWS_TO}, |
298 | }; |
299 | GtkAccessibleValue *value; |
300 | GList *list, *l; |
301 | GtkATContext *target_ctx; |
302 | int i; |
303 | |
304 | for (i = 0; i < G_N_ELEMENTS (map); i++) |
305 | { |
306 | if (!gtk_at_context_has_accessible_relation (self: ctx, relation: map[i].r)) |
307 | continue; |
308 | |
309 | GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)" )); |
310 | |
311 | value = gtk_at_context_get_accessible_relation (self: ctx, relation: map[i].r); |
312 | list = gtk_reference_list_accessible_value_get (value); |
313 | |
314 | for (l = list; l; l = l->next) |
315 | { |
316 | target_ctx = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: l->data)); |
317 | |
318 | /* Realize the ATContext of the target, so we can ask for its ref */ |
319 | gtk_at_context_realize (self: target_ctx); |
320 | |
321 | g_variant_builder_add (builder: &b, format_string: "@(so)" , |
322 | gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: target_ctx))); |
323 | } |
324 | |
325 | g_variant_builder_add (builder, format_string: "(ua(so))" , map[i].s, &b); |
326 | } |
327 | } |
328 | /* }}} */ |
329 | /* {{{ Accessible implementation */ |
330 | static int |
331 | get_index_in_parent (GtkWidget *widget) |
332 | { |
333 | GtkWidget *parent = gtk_widget_get_parent (widget); |
334 | GtkWidget *child; |
335 | int idx; |
336 | |
337 | if (parent == NULL) |
338 | return -1; |
339 | |
340 | idx = 0; |
341 | for (child = gtk_widget_get_first_child (widget: parent); |
342 | child; |
343 | child = gtk_widget_get_next_sibling (widget: child)) |
344 | { |
345 | if (child == widget) |
346 | return idx; |
347 | |
348 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
349 | continue; |
350 | |
351 | idx++; |
352 | } |
353 | |
354 | return -1; |
355 | } |
356 | |
357 | static int |
358 | get_index_in_toplevels (GtkWidget *widget) |
359 | { |
360 | GListModel *toplevels = gtk_window_get_toplevels (); |
361 | guint n_toplevels = g_list_model_get_n_items (list: toplevels); |
362 | GtkWidget *window; |
363 | int idx; |
364 | |
365 | idx = 0; |
366 | for (guint i = 0; i < n_toplevels; i++) |
367 | { |
368 | window = g_list_model_get_item (list: toplevels, position: i); |
369 | |
370 | g_object_unref (object: window); |
371 | |
372 | if (window == widget) |
373 | return idx; |
374 | |
375 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: window))) |
376 | continue; |
377 | |
378 | idx += 1; |
379 | } |
380 | |
381 | return -1; |
382 | } |
383 | |
384 | static GVariant * |
385 | get_parent_context_ref (GtkAccessible *accessible) |
386 | { |
387 | GVariant *res = NULL; |
388 | |
389 | if (GTK_IS_WIDGET (accessible)) |
390 | { |
391 | GtkWidget *widget = GTK_WIDGET (accessible); |
392 | GtkWidget *parent = gtk_widget_get_parent (widget); |
393 | |
394 | if (parent == NULL) |
395 | { |
396 | GtkATContext *context = gtk_accessible_get_at_context (self: accessible); |
397 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: context); |
398 | |
399 | res = gtk_at_spi_root_to_ref (self: self->root); |
400 | } |
401 | else if (GTK_IS_STACK (parent)) |
402 | { |
403 | GtkStackPage *page = |
404 | gtk_stack_get_page (GTK_STACK (parent), child: widget); |
405 | GtkATContext *parent_context = |
406 | gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: page)); |
407 | |
408 | if (parent_context != NULL) |
409 | { |
410 | gtk_at_context_realize (self: parent_context); |
411 | |
412 | res = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: parent_context)); |
413 | } |
414 | } |
415 | else |
416 | { |
417 | GtkATContext *parent_context = |
418 | gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: parent)); |
419 | |
420 | if (parent_context != NULL) |
421 | { |
422 | /* XXX: This realize() is needed otherwise opening a GtkPopover will |
423 | * emit a warning when getting the context's reference |
424 | */ |
425 | gtk_at_context_realize (self: parent_context); |
426 | |
427 | res = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: parent_context)); |
428 | } |
429 | } |
430 | } |
431 | else if (GTK_IS_STACK_PAGE (accessible)) |
432 | { |
433 | GtkWidget *parent = |
434 | gtk_widget_get_parent (widget: gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); |
435 | GtkATContext *parent_context = |
436 | gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: parent)); |
437 | |
438 | if (parent_context != NULL) |
439 | { |
440 | gtk_at_context_realize (self: parent_context); |
441 | |
442 | res = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: parent_context)); |
443 | } |
444 | } |
445 | |
446 | if (res == NULL) |
447 | res = gtk_at_spi_null_ref (); |
448 | |
449 | return res; |
450 | } |
451 | |
452 | static void |
453 | handle_accessible_method (GDBusConnection *connection, |
454 | const gchar *sender, |
455 | const gchar *object_path, |
456 | const gchar *interface_name, |
457 | const gchar *method_name, |
458 | GVariant *parameters, |
459 | GDBusMethodInvocation *invocation, |
460 | gpointer user_data) |
461 | { |
462 | GtkAtSpiContext *self = user_data; |
463 | |
464 | GTK_NOTE (A11Y, g_message ("handling %s on %s" , method_name, object_path)); |
465 | |
466 | if (g_strcmp0 (str1: method_name, str2: "GetRole" ) == 0) |
467 | { |
468 | guint atspi_role = gtk_atspi_role_for_context (context: GTK_AT_CONTEXT (ptr: self)); |
469 | |
470 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(u)" , atspi_role)); |
471 | } |
472 | else if (g_strcmp0 (str1: method_name, str2: "GetRoleName" ) == 0) |
473 | { |
474 | GtkAccessibleRole role = gtk_at_context_get_accessible_role (self: GTK_AT_CONTEXT (ptr: self)); |
475 | const char *name = gtk_accessible_role_to_name (role, NULL); |
476 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)" , name)); |
477 | } |
478 | else if (g_strcmp0 (str1: method_name, str2: "GetLocalizedRoleName" ) == 0) |
479 | { |
480 | GtkAccessibleRole role = gtk_at_context_get_accessible_role (self: GTK_AT_CONTEXT (ptr: self)); |
481 | const char *name = gtk_accessible_role_to_name (role, GETTEXT_PACKAGE); |
482 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)" , name)); |
483 | } |
484 | else if (g_strcmp0 (str1: method_name, str2: "GetState" ) == 0) |
485 | { |
486 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)" )); |
487 | |
488 | g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("au" )); |
489 | collect_states (self, builder: &builder); |
490 | g_variant_builder_close (builder: &builder); |
491 | |
492 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_builder_end (builder: &builder)); |
493 | } |
494 | else if (g_strcmp0 (str1: method_name, str2: "GetAttributes" ) == 0) |
495 | { |
496 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})" )); |
497 | |
498 | g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("a{ss}" )); |
499 | g_variant_builder_add (builder: &builder, format_string: "{ss}" , "toolkit" , "GTK" ); |
500 | |
501 | if (gtk_at_context_has_accessible_property (self: GTK_AT_CONTEXT (ptr: self), property: GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER)) |
502 | { |
503 | GtkAccessibleValue *value; |
504 | |
505 | value = gtk_at_context_get_accessible_property (self: GTK_AT_CONTEXT (ptr: self), property: GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER); |
506 | |
507 | g_variant_builder_add (builder: &builder, format_string: "{ss}" , |
508 | "placeholder-text" , gtk_string_accessible_value_get (value)); |
509 | } |
510 | |
511 | g_variant_builder_close (builder: &builder); |
512 | |
513 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_builder_end (builder: &builder)); |
514 | } |
515 | else if (g_strcmp0 (str1: method_name, str2: "GetApplication" ) == 0) |
516 | { |
517 | g_dbus_method_invocation_return_value (invocation, |
518 | parameters: g_variant_new (format_string: "(@(so))" , gtk_at_spi_root_to_ref (self: self->root))); |
519 | } |
520 | else if (g_strcmp0 (str1: method_name, str2: "GetChildAtIndex" ) == 0) |
521 | { |
522 | GtkATContext *context = NULL; |
523 | GtkAccessible *accessible; |
524 | int idx, real_idx = 0; |
525 | |
526 | g_variant_get (value: parameters, format_string: "(i)" , &idx); |
527 | |
528 | accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
529 | |
530 | if (GTK_IS_STACK_PAGE (accessible)) |
531 | { |
532 | if (idx == 0) |
533 | { |
534 | GtkWidget *child; |
535 | |
536 | child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); |
537 | if (gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
538 | context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: child)); |
539 | } |
540 | } |
541 | else if (GTK_IS_WIDGET (accessible)) |
542 | { |
543 | GtkWidget *widget = GTK_WIDGET (accessible); |
544 | GtkWidget *child; |
545 | |
546 | real_idx = 0; |
547 | for (child = gtk_widget_get_first_child (widget); |
548 | child; |
549 | child = gtk_widget_get_next_sibling (widget: child)) |
550 | { |
551 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
552 | continue; |
553 | |
554 | if (real_idx == idx) |
555 | break; |
556 | |
557 | real_idx += 1; |
558 | } |
559 | |
560 | if (child) |
561 | { |
562 | if (GTK_IS_STACK (accessible)) |
563 | context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: gtk_stack_get_page (GTK_STACK (accessible), child))); |
564 | else |
565 | context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: child)); |
566 | } |
567 | } |
568 | |
569 | if (context == NULL) |
570 | { |
571 | g_dbus_method_invocation_return_error (invocation, |
572 | G_IO_ERROR, |
573 | code: G_IO_ERROR_INVALID_ARGUMENT, |
574 | format: "No child with index %d" , idx); |
575 | return; |
576 | } |
577 | |
578 | /* Realize the child ATContext in order to get its ref */ |
579 | gtk_at_context_realize (self: context); |
580 | |
581 | GVariant *ref = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
582 | |
583 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(@(so))" , ref)); |
584 | } |
585 | else if (g_strcmp0 (str1: method_name, str2: "GetChildren" ) == 0) |
586 | { |
587 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)" )); |
588 | |
589 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
590 | if (GTK_IS_WIDGET (accessible)) |
591 | { |
592 | GtkWidget *widget = GTK_WIDGET (accessible); |
593 | GtkWidget *child; |
594 | |
595 | for (child = gtk_widget_get_first_child (widget); |
596 | child; |
597 | child = gtk_widget_get_next_sibling (widget: child)) |
598 | { |
599 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
600 | continue; |
601 | |
602 | GtkATContext *context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: child)); |
603 | |
604 | /* Realize the child ATContext in order to get its ref */ |
605 | gtk_at_context_realize (self: context); |
606 | |
607 | GVariant *ref = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
608 | |
609 | if (ref != NULL) |
610 | g_variant_builder_add (builder: &builder, format_string: "@(so)" , ref); |
611 | } |
612 | } |
613 | else if (GTK_IS_STACK_PAGE (accessible)) |
614 | { |
615 | GtkWidget *child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); |
616 | |
617 | if (gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
618 | { |
619 | GtkATContext *context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: child)); |
620 | |
621 | /* Realize the child ATContext in order to get its ref */ |
622 | gtk_at_context_realize (self: context); |
623 | |
624 | GVariant *ref = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
625 | |
626 | if (ref != NULL) |
627 | g_variant_builder_add (builder: &builder, format_string: "@(so)" , ref); |
628 | } |
629 | } |
630 | |
631 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(so))" , &builder)); |
632 | } |
633 | else if (g_strcmp0 (str1: method_name, str2: "GetIndexInParent" ) == 0) |
634 | { |
635 | int idx = gtk_at_spi_context_get_index_in_parent (self); |
636 | |
637 | if (idx == -1) |
638 | g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code: G_DBUS_ERROR_FAILED, format: "Not found" ); |
639 | else |
640 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(i)" , idx)); |
641 | } |
642 | else if (g_strcmp0 (str1: method_name, str2: "GetRelationSet" ) == 0) |
643 | { |
644 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))" )); |
645 | collect_relations (self, builder: &builder); |
646 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(ua(so)))" , &builder)); |
647 | } |
648 | else if (g_strcmp0 (str1: method_name, str2: "GetInterfaces" ) == 0) |
649 | { |
650 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(@as)" , self->interfaces)); |
651 | } |
652 | |
653 | } |
654 | |
655 | static GVariant * |
656 | handle_accessible_get_property (GDBusConnection *connection, |
657 | const gchar *sender, |
658 | const gchar *object_path, |
659 | const gchar *interface_name, |
660 | const gchar *property_name, |
661 | GError **error, |
662 | gpointer user_data) |
663 | { |
664 | GtkAtSpiContext *self = user_data; |
665 | GVariant *res = NULL; |
666 | |
667 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
668 | |
669 | GTK_NOTE (A11Y, g_message ("handling GetProperty %s on %s" , property_name, object_path)); |
670 | |
671 | if (g_strcmp0 (str1: property_name, str2: "Name" ) == 0) |
672 | { |
673 | char *label = gtk_at_context_get_name (self: GTK_AT_CONTEXT (ptr: self)); |
674 | res = g_variant_new_string (string: label ? label : "" ); |
675 | g_free (mem: label); |
676 | } |
677 | else if (g_strcmp0 (str1: property_name, str2: "Description" ) == 0) |
678 | { |
679 | char *label = gtk_at_context_get_description (self: GTK_AT_CONTEXT (ptr: self)); |
680 | res = g_variant_new_string (string: label ? label : "" ); |
681 | g_free (mem: label); |
682 | } |
683 | else if (g_strcmp0 (str1: property_name, str2: "Locale" ) == 0) |
684 | res = g_variant_new_string (string: setlocale (LC_MESSAGES, NULL)); |
685 | else if (g_strcmp0 (str1: property_name, str2: "AccessibleId" ) == 0) |
686 | res = g_variant_new_string (string: "" ); |
687 | else if (g_strcmp0 (str1: property_name, str2: "Parent" ) == 0) |
688 | res = get_parent_context_ref (accessible); |
689 | else if (g_strcmp0 (str1: property_name, str2: "ChildCount" ) == 0) |
690 | res = g_variant_new_int32 (value: gtk_at_spi_context_get_child_count (self)); |
691 | else |
692 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
693 | format: "Unknown property '%s'" , property_name); |
694 | |
695 | return res; |
696 | } |
697 | |
698 | static const GDBusInterfaceVTable accessible_vtable = { |
699 | handle_accessible_method, |
700 | handle_accessible_get_property, |
701 | NULL, |
702 | }; |
703 | /* }}} */ |
704 | /* {{{ Change notification */ |
705 | static void |
706 | emit_text_changed (GtkAtSpiContext *self, |
707 | const char *kind, |
708 | int start, |
709 | int end, |
710 | const char *text) |
711 | { |
712 | if (self->connection == NULL) |
713 | return; |
714 | |
715 | g_dbus_connection_emit_signal (connection: self->connection, |
716 | NULL, |
717 | object_path: self->context_path, |
718 | interface_name: "org.a11y.atspi.Event.Object" , |
719 | signal_name: "TextChanged" , |
720 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
721 | kind, start, end, g_variant_new_string (string: text), NULL), |
722 | NULL); |
723 | } |
724 | |
725 | static void |
726 | emit_text_selection_changed (GtkAtSpiContext *self, |
727 | const char *kind, |
728 | int cursor_position) |
729 | { |
730 | if (self->connection == NULL) |
731 | return; |
732 | |
733 | if (strcmp (s1: kind, s2: "text-caret-moved" ) == 0) |
734 | g_dbus_connection_emit_signal (connection: self->connection, |
735 | NULL, |
736 | object_path: self->context_path, |
737 | interface_name: "org.a11y.atspi.Event.Object" , |
738 | signal_name: "TextCaretMoved" , |
739 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
740 | "" , cursor_position, 0, g_variant_new_string (string: "" ), NULL), |
741 | NULL); |
742 | else |
743 | g_dbus_connection_emit_signal (connection: self->connection, |
744 | NULL, |
745 | object_path: self->context_path, |
746 | interface_name: "org.a11y.atspi.Event.Object" , |
747 | signal_name: "TextSelectionChanged" , |
748 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
749 | "" , 0, 0, g_variant_new_string (string: "" ), NULL), |
750 | NULL); |
751 | } |
752 | |
753 | static void |
754 | emit_selection_changed (GtkAtSpiContext *self, |
755 | const char *kind) |
756 | { |
757 | if (self->connection == NULL) |
758 | return; |
759 | |
760 | g_dbus_connection_emit_signal (connection: self->connection, |
761 | NULL, |
762 | object_path: self->context_path, |
763 | interface_name: "org.a11y.atspi.Event.Object" , |
764 | signal_name: "SelectionChanged" , |
765 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
766 | "" , 0, 0, g_variant_new_string (string: "" ), NULL), |
767 | NULL); |
768 | } |
769 | |
770 | static void |
771 | emit_state_changed (GtkAtSpiContext *self, |
772 | const char *name, |
773 | gboolean enabled) |
774 | { |
775 | if (self->connection == NULL) |
776 | return; |
777 | |
778 | g_dbus_connection_emit_signal (connection: self->connection, |
779 | NULL, |
780 | object_path: self->context_path, |
781 | interface_name: "org.a11y.atspi.Event.Object" , |
782 | signal_name: "StateChanged" , |
783 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
784 | name, enabled, 0, g_variant_new_string (string: "0" ), NULL), |
785 | NULL); |
786 | } |
787 | |
788 | static void |
789 | emit_defunct (GtkAtSpiContext *self) |
790 | { |
791 | if (self->connection == NULL) |
792 | return; |
793 | |
794 | g_dbus_connection_emit_signal (connection: self->connection, |
795 | NULL, |
796 | object_path: self->context_path, |
797 | interface_name: "org.a11y.atspi.Event.Object" , |
798 | signal_name: "StateChanged" , |
799 | parameters: g_variant_new (format_string: "(siiva{sv})" , "defunct" , TRUE, 0, g_variant_new_string (string: "0" ), NULL), |
800 | NULL); |
801 | } |
802 | |
803 | static void |
804 | emit_property_changed (GtkAtSpiContext *self, |
805 | const char *name, |
806 | GVariant *value) |
807 | { |
808 | if (self->connection == NULL) |
809 | return; |
810 | |
811 | g_dbus_connection_emit_signal (connection: self->connection, |
812 | NULL, |
813 | object_path: self->context_path, |
814 | interface_name: "org.a11y.atspi.Event.Object" , |
815 | signal_name: "PropertyChange" , |
816 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
817 | name, 0, 0, value, NULL), |
818 | NULL); |
819 | } |
820 | |
821 | static void |
822 | emit_bounds_changed (GtkAtSpiContext *self, |
823 | int x, |
824 | int y, |
825 | int width, |
826 | int height) |
827 | { |
828 | if (self->connection == NULL) |
829 | return; |
830 | |
831 | g_dbus_connection_emit_signal (connection: self->connection, |
832 | NULL, |
833 | object_path: self->context_path, |
834 | interface_name: "org.a11y.atspi.Event.Object" , |
835 | signal_name: "BoundsChanged" , |
836 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
837 | "" , 0, 0, g_variant_new (format_string: "(iiii)" , x, y, width, height), NULL), |
838 | NULL); |
839 | } |
840 | |
841 | static void |
842 | emit_children_changed (GtkAtSpiContext *self, |
843 | GtkAtSpiContext *child_context, |
844 | int idx, |
845 | GtkAccessibleChildState state) |
846 | { |
847 | /* If we don't have a connection on either contexts, we cannot emit a signal */ |
848 | if (self->connection == NULL || child_context->connection == NULL) |
849 | return; |
850 | |
851 | GVariant *context_ref = gtk_at_spi_context_to_ref (self); |
852 | GVariant *child_ref = gtk_at_spi_context_to_ref (self: child_context); |
853 | |
854 | gtk_at_spi_emit_children_changed (connection: self->connection, |
855 | path: self->context_path, |
856 | state, |
857 | idx, |
858 | child_ref, |
859 | sender_ref: context_ref); |
860 | } |
861 | |
862 | static void |
863 | emit_focus (GtkAtSpiContext *self, |
864 | gboolean focus_in) |
865 | { |
866 | if (self->connection == NULL) |
867 | return; |
868 | |
869 | if (focus_in) |
870 | g_dbus_connection_emit_signal (connection: self->connection, |
871 | NULL, |
872 | object_path: self->context_path, |
873 | interface_name: "org.a11y.atspi.Event.Focus" , |
874 | signal_name: "Focus" , |
875 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
876 | "" , 0, 0, g_variant_new_string (string: "0" ), NULL), |
877 | NULL); |
878 | } |
879 | |
880 | static void |
881 | emit_window_event (GtkAtSpiContext *self, |
882 | const char *event_type) |
883 | { |
884 | if (self->connection == NULL) |
885 | return; |
886 | |
887 | g_dbus_connection_emit_signal (connection: self->connection, |
888 | NULL, |
889 | object_path: self->context_path, |
890 | interface_name: "org.a11y.atspi.Event.Window" , |
891 | signal_name: event_type, |
892 | parameters: g_variant_new (format_string: "(siiva{sv})" , |
893 | "" , 0, 0, |
894 | g_variant_new_string(string: "0" ), |
895 | NULL), |
896 | NULL); |
897 | } |
898 | |
899 | static void |
900 | gtk_at_spi_context_state_change (GtkATContext *ctx, |
901 | GtkAccessibleStateChange changed_states, |
902 | GtkAccessiblePropertyChange changed_properties, |
903 | GtkAccessibleRelationChange changed_relations, |
904 | GtkAccessibleAttributeSet *states, |
905 | GtkAccessibleAttributeSet *properties, |
906 | GtkAccessibleAttributeSet *relations) |
907 | { |
908 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: ctx); |
909 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: ctx); |
910 | GtkAccessibleValue *value; |
911 | |
912 | if (GTK_IS_WIDGET (accessible) && !gtk_widget_get_realized (GTK_WIDGET (accessible))) |
913 | return; |
914 | |
915 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_HIDDEN) |
916 | { |
917 | GtkWidget *parent; |
918 | GtkATContext *context; |
919 | GtkAccessibleChildChange change; |
920 | |
921 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_HIDDEN); |
922 | if (gtk_boolean_accessible_value_get (value)) |
923 | change = GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED; |
924 | else |
925 | change = GTK_ACCESSIBLE_CHILD_CHANGE_ADDED; |
926 | |
927 | if (GTK_IS_ROOT (ptr: accessible)) |
928 | { |
929 | gtk_at_spi_root_child_changed (self: self->root, change, child: accessible); |
930 | emit_state_changed (self, name: "showing" , enabled: gtk_boolean_accessible_value_get (value)); |
931 | } |
932 | else |
933 | { |
934 | if (GTK_IS_WIDGET (accessible)) |
935 | parent = gtk_widget_get_parent (GTK_WIDGET (accessible)); |
936 | else if (GTK_IS_STACK_PAGE (accessible)) |
937 | parent = gtk_widget_get_parent (widget: gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); |
938 | else |
939 | g_assert_not_reached (); |
940 | |
941 | context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: parent)); |
942 | gtk_at_context_child_changed (self: context, change, child: accessible); |
943 | } |
944 | } |
945 | |
946 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_BUSY) |
947 | { |
948 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_BUSY); |
949 | emit_state_changed (self, name: "busy" , enabled: gtk_boolean_accessible_value_get (value)); |
950 | } |
951 | |
952 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_CHECKED) |
953 | { |
954 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_CHECKED); |
955 | |
956 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_TRISTATE) |
957 | { |
958 | switch (gtk_tristate_accessible_value_get (value)) |
959 | { |
960 | case GTK_ACCESSIBLE_TRISTATE_TRUE: |
961 | emit_state_changed (self, name: "checked" , TRUE); |
962 | emit_state_changed (self, name: "indeterminate" , FALSE); |
963 | break; |
964 | case GTK_ACCESSIBLE_TRISTATE_MIXED: |
965 | emit_state_changed (self, name: "checked" , FALSE); |
966 | emit_state_changed (self, name: "indeterminate" , TRUE); |
967 | break; |
968 | case GTK_ACCESSIBLE_TRISTATE_FALSE: |
969 | emit_state_changed (self, name: "checked" , FALSE); |
970 | emit_state_changed (self, name: "indeterminate" , FALSE); |
971 | break; |
972 | default: |
973 | break; |
974 | } |
975 | } |
976 | else |
977 | { |
978 | emit_state_changed (self, name: "checked" , FALSE); |
979 | emit_state_changed (self, name: "indeterminate" , TRUE); |
980 | } |
981 | } |
982 | |
983 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_DISABLED) |
984 | { |
985 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_DISABLED); |
986 | emit_state_changed (self, name: "sensitive" , enabled: !gtk_boolean_accessible_value_get (value)); |
987 | } |
988 | |
989 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED) |
990 | { |
991 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_EXPANDED); |
992 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) |
993 | { |
994 | emit_state_changed (self, name: "expandable" , TRUE); |
995 | emit_state_changed (self, name: "expanded" ,enabled: gtk_boolean_accessible_value_get (value)); |
996 | } |
997 | else |
998 | emit_state_changed (self, name: "expandable" , FALSE); |
999 | } |
1000 | |
1001 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_INVALID) |
1002 | { |
1003 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_INVALID); |
1004 | switch (gtk_invalid_accessible_value_get (value)) |
1005 | { |
1006 | case GTK_ACCESSIBLE_INVALID_TRUE: |
1007 | case GTK_ACCESSIBLE_INVALID_GRAMMAR: |
1008 | case GTK_ACCESSIBLE_INVALID_SPELLING: |
1009 | emit_state_changed (self, name: "invalid" , TRUE); |
1010 | break; |
1011 | case GTK_ACCESSIBLE_INVALID_FALSE: |
1012 | emit_state_changed (self, name: "invalid" , FALSE); |
1013 | break; |
1014 | default: |
1015 | break; |
1016 | } |
1017 | } |
1018 | |
1019 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_PRESSED) |
1020 | { |
1021 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_PRESSED); |
1022 | |
1023 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_TRISTATE) |
1024 | { |
1025 | switch (gtk_tristate_accessible_value_get (value)) |
1026 | { |
1027 | case GTK_ACCESSIBLE_TRISTATE_TRUE: |
1028 | emit_state_changed (self, name: "pressed" , TRUE); |
1029 | emit_state_changed (self, name: "indeterminate" , FALSE); |
1030 | break; |
1031 | case GTK_ACCESSIBLE_TRISTATE_MIXED: |
1032 | emit_state_changed (self, name: "pressed" , FALSE); |
1033 | emit_state_changed (self, name: "indeterminate" , TRUE); |
1034 | break; |
1035 | case GTK_ACCESSIBLE_TRISTATE_FALSE: |
1036 | emit_state_changed (self, name: "pressed" , FALSE); |
1037 | emit_state_changed (self, name: "indeterminate" , FALSE); |
1038 | break; |
1039 | default: |
1040 | break; |
1041 | } |
1042 | } |
1043 | else |
1044 | { |
1045 | emit_state_changed (self, name: "pressed" , FALSE); |
1046 | emit_state_changed (self, name: "indeterminate" , TRUE); |
1047 | } |
1048 | } |
1049 | |
1050 | if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_SELECTED) |
1051 | { |
1052 | value = gtk_accessible_attribute_set_get_value (self: states, state: GTK_ACCESSIBLE_STATE_SELECTED); |
1053 | if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) |
1054 | { |
1055 | emit_state_changed (self, name: "selectable" , TRUE); |
1056 | emit_state_changed (self, name: "selected" ,enabled: gtk_boolean_accessible_value_get (value)); |
1057 | } |
1058 | else |
1059 | emit_state_changed (self, name: "selectable" , FALSE); |
1060 | } |
1061 | |
1062 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_READ_ONLY) |
1063 | { |
1064 | gboolean readonly; |
1065 | |
1066 | value = gtk_accessible_attribute_set_get_value (self: properties, state: GTK_ACCESSIBLE_PROPERTY_READ_ONLY); |
1067 | readonly = gtk_boolean_accessible_value_get (value); |
1068 | |
1069 | emit_state_changed (self, name: "read-only" , enabled: readonly); |
1070 | if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX) |
1071 | emit_state_changed (self, name: "editable" , enabled: !readonly); |
1072 | } |
1073 | |
1074 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_ORIENTATION) |
1075 | { |
1076 | value = gtk_accessible_attribute_set_get_value (self: properties, state: GTK_ACCESSIBLE_PROPERTY_ORIENTATION); |
1077 | if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) |
1078 | { |
1079 | emit_state_changed (self, name: "horizontal" , TRUE); |
1080 | emit_state_changed (self, name: "vertical" , FALSE); |
1081 | } |
1082 | else |
1083 | { |
1084 | emit_state_changed (self, name: "horizontal" , FALSE); |
1085 | emit_state_changed (self, name: "vertical" , TRUE); |
1086 | } |
1087 | } |
1088 | |
1089 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MODAL) |
1090 | { |
1091 | value = gtk_accessible_attribute_set_get_value (self: properties, state: GTK_ACCESSIBLE_PROPERTY_MODAL); |
1092 | emit_state_changed (self, name: "modal" , enabled: gtk_boolean_accessible_value_get (value)); |
1093 | } |
1094 | |
1095 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MULTI_LINE) |
1096 | { |
1097 | value = gtk_accessible_attribute_set_get_value (self: properties, state: GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); |
1098 | emit_state_changed (self, name: "multi-line" , enabled: gtk_boolean_accessible_value_get (value)); |
1099 | } |
1100 | |
1101 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_LABEL) |
1102 | { |
1103 | char *label = gtk_at_context_get_name (self: GTK_AT_CONTEXT (ptr: self)); |
1104 | GVariant *v = g_variant_new_take_string (string: label); |
1105 | emit_property_changed (self, name: "accessible-name" , value: v); |
1106 | } |
1107 | |
1108 | if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_DESCRIPTION) |
1109 | { |
1110 | char *label = gtk_at_context_get_description (self: GTK_AT_CONTEXT (ptr: self)); |
1111 | GVariant *v = g_variant_new_take_string (string: label); |
1112 | emit_property_changed (self, name: "accessible-description" , value: v); |
1113 | } |
1114 | } |
1115 | |
1116 | static void |
1117 | gtk_at_spi_context_platform_change (GtkATContext *ctx, |
1118 | GtkAccessiblePlatformChange changed_platform) |
1119 | { |
1120 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: ctx); |
1121 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: ctx); |
1122 | GtkWidget *widget; |
1123 | |
1124 | if (!GTK_IS_WIDGET (accessible)) |
1125 | return; |
1126 | |
1127 | widget = GTK_WIDGET (accessible); |
1128 | if (!gtk_widget_get_realized (widget)) |
1129 | return; |
1130 | |
1131 | if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE) |
1132 | { |
1133 | gboolean state = gtk_accessible_get_platform_state (self: GTK_ACCESSIBLE (ptr: widget), |
1134 | state: GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE); |
1135 | emit_state_changed (self, name: "focusable" , enabled: state); |
1136 | } |
1137 | |
1138 | if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED) |
1139 | { |
1140 | gboolean state = gtk_accessible_get_platform_state (self: GTK_ACCESSIBLE (ptr: widget), |
1141 | state: GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED); |
1142 | emit_state_changed (self, name: "focused" , enabled: state); |
1143 | emit_focus (self, focus_in: state); |
1144 | } |
1145 | |
1146 | if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_ACTIVE) |
1147 | { |
1148 | gboolean state = gtk_accessible_get_platform_state (self: GTK_ACCESSIBLE (ptr: widget), |
1149 | state: GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE); |
1150 | emit_state_changed (self, name: "active" , enabled: state); |
1151 | |
1152 | /* Orca tracks the window:activate and window:deactivate events on top |
1153 | * levels to decide whether to track other AT-SPI events |
1154 | */ |
1155 | if (gtk_accessible_get_accessible_role (self: accessible) == GTK_ACCESSIBLE_ROLE_WINDOW) |
1156 | { |
1157 | if (state) |
1158 | emit_window_event (self, event_type: "activate" ); |
1159 | else |
1160 | emit_window_event (self, event_type: "deactivate" ); |
1161 | } |
1162 | } |
1163 | } |
1164 | |
1165 | static void |
1166 | gtk_at_spi_context_bounds_change (GtkATContext *ctx) |
1167 | { |
1168 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: ctx); |
1169 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: ctx); |
1170 | GtkWidget *widget; |
1171 | GtkWidget *parent; |
1172 | double x, y; |
1173 | int width, height; |
1174 | |
1175 | if (!GTK_IS_WIDGET (accessible)) |
1176 | return; |
1177 | |
1178 | widget = GTK_WIDGET (accessible); |
1179 | if (!gtk_widget_get_realized (widget)) |
1180 | return; |
1181 | |
1182 | parent = gtk_widget_get_parent (widget); |
1183 | |
1184 | if (parent) |
1185 | gtk_widget_translate_coordinates (src_widget: widget, dest_widget: parent, src_x: 0., src_y: 0., dest_x: &x, dest_y: &y); |
1186 | else |
1187 | x = y = 0.; |
1188 | |
1189 | width = gtk_widget_get_width (widget); |
1190 | height = gtk_widget_get_height (widget); |
1191 | |
1192 | emit_bounds_changed (self, x: (int)x, y: (int)y, width, height); |
1193 | } |
1194 | |
1195 | static void |
1196 | gtk_at_spi_context_child_change (GtkATContext *ctx, |
1197 | GtkAccessibleChildChange change, |
1198 | GtkAccessible *child) |
1199 | { |
1200 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: ctx); |
1201 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: ctx); |
1202 | GtkATContext *child_context = gtk_accessible_get_at_context (self: child); |
1203 | GtkWidget *parent_widget; |
1204 | GtkWidget *child_widget; |
1205 | int idx = 0; |
1206 | |
1207 | if (!GTK_IS_WIDGET (accessible)) |
1208 | return; |
1209 | |
1210 | if (child_context == NULL) |
1211 | return; |
1212 | |
1213 | /* handle the stack page special case */ |
1214 | if (GTK_IS_WIDGET (child) && |
1215 | GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (child)))) |
1216 | { |
1217 | GtkWidget *stack; |
1218 | GtkStackPage *page; |
1219 | GListModel *pages; |
1220 | |
1221 | stack = gtk_widget_get_parent (GTK_WIDGET (child)); |
1222 | page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (child)); |
1223 | pages = G_LIST_MODEL (ptr: gtk_stack_get_pages (GTK_STACK (stack))); |
1224 | idx = 0; |
1225 | for (guint i = 0; i < g_list_model_get_n_items (list: pages); i++) |
1226 | { |
1227 | GtkStackPage *item = g_list_model_get_item (list: pages, position: i); |
1228 | |
1229 | g_object_unref (object: item); |
1230 | |
1231 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: item))) |
1232 | continue; |
1233 | |
1234 | if (item == page) |
1235 | break; |
1236 | |
1237 | idx++; |
1238 | } |
1239 | g_object_unref (object: pages); |
1240 | |
1241 | if (change & GTK_ACCESSIBLE_CHILD_CHANGE_ADDED) |
1242 | { |
1243 | emit_children_changed (self: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: stack))), |
1244 | child_context: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: page))), |
1245 | idx, |
1246 | state: GTK_ACCESSIBLE_CHILD_STATE_ADDED); |
1247 | |
1248 | emit_children_changed (self: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: page))), |
1249 | child_context: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: child)), |
1250 | idx: 0, |
1251 | state: GTK_ACCESSIBLE_CHILD_STATE_ADDED); |
1252 | } |
1253 | |
1254 | if (change & GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED) |
1255 | { |
1256 | emit_children_changed (self: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: page))), |
1257 | child_context: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: child)), |
1258 | idx: 0, |
1259 | state: GTK_ACCESSIBLE_CHILD_STATE_REMOVED); |
1260 | emit_children_changed (self: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: stack))), |
1261 | child_context: GTK_AT_SPI_CONTEXT (ptr: gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: page))), |
1262 | idx, |
1263 | state: GTK_ACCESSIBLE_CHILD_STATE_REMOVED); |
1264 | |
1265 | } |
1266 | |
1267 | return; |
1268 | } |
1269 | |
1270 | parent_widget = GTK_WIDGET (accessible); |
1271 | |
1272 | if (GTK_IS_STACK_PAGE (child)) |
1273 | child_widget = gtk_stack_page_get_child (GTK_STACK_PAGE (child)); |
1274 | else |
1275 | child_widget = GTK_WIDGET (child); |
1276 | |
1277 | if (gtk_widget_get_parent (widget: child_widget) != parent_widget) |
1278 | { |
1279 | idx = 0; |
1280 | } |
1281 | else |
1282 | { |
1283 | for (GtkWidget *iter = gtk_widget_get_first_child (widget: parent_widget); |
1284 | iter != NULL; |
1285 | iter = gtk_widget_get_next_sibling (widget: iter)) |
1286 | { |
1287 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: iter))) |
1288 | continue; |
1289 | |
1290 | if (iter == child_widget) |
1291 | break; |
1292 | |
1293 | idx += 1; |
1294 | } |
1295 | } |
1296 | |
1297 | if (change & GTK_ACCESSIBLE_CHILD_CHANGE_ADDED) |
1298 | emit_children_changed (self, |
1299 | child_context: GTK_AT_SPI_CONTEXT (ptr: child_context), |
1300 | idx, |
1301 | state: GTK_ACCESSIBLE_CHILD_STATE_ADDED); |
1302 | else if (change & GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED) |
1303 | emit_children_changed (self, |
1304 | child_context: GTK_AT_SPI_CONTEXT (ptr: child_context), |
1305 | idx, |
1306 | state: GTK_ACCESSIBLE_CHILD_STATE_REMOVED); |
1307 | } |
1308 | /* }}} */ |
1309 | /* {{{ D-Bus Registration */ |
1310 | static void |
1311 | gtk_at_spi_context_register_object (GtkAtSpiContext *self) |
1312 | { |
1313 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
1314 | GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY); |
1315 | const GDBusInterfaceVTable *vtable; |
1316 | |
1317 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_accessible_interface.name); |
1318 | self->registration_ids[self->n_registered_objects] = |
1319 | g_dbus_connection_register_object (self->connection, |
1320 | self->context_path, |
1321 | (GDBusInterfaceInfo *) &atspi_accessible_interface, |
1322 | &accessible_vtable, |
1323 | self, |
1324 | NULL, |
1325 | NULL); |
1326 | self->n_registered_objects++; |
1327 | |
1328 | vtable = gtk_atspi_get_component_vtable (accessible); |
1329 | if (vtable) |
1330 | { |
1331 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_component_interface.name); |
1332 | self->registration_ids[self->n_registered_objects] = |
1333 | g_dbus_connection_register_object (self->connection, |
1334 | self->context_path, |
1335 | (GDBusInterfaceInfo *) &atspi_component_interface, |
1336 | vtable, |
1337 | self, |
1338 | NULL, |
1339 | NULL); |
1340 | self->n_registered_objects++; |
1341 | } |
1342 | |
1343 | vtable = gtk_atspi_get_text_vtable (accessible); |
1344 | if (vtable) |
1345 | { |
1346 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_text_interface.name); |
1347 | self->registration_ids[self->n_registered_objects] = |
1348 | g_dbus_connection_register_object (self->connection, |
1349 | self->context_path, |
1350 | (GDBusInterfaceInfo *) &atspi_text_interface, |
1351 | vtable, |
1352 | self, |
1353 | NULL, |
1354 | NULL); |
1355 | self->n_registered_objects++; |
1356 | } |
1357 | |
1358 | vtable = gtk_atspi_get_editable_text_vtable (accessible); |
1359 | if (vtable) |
1360 | { |
1361 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_editable_text_interface.name); |
1362 | self->registration_ids[self->n_registered_objects] = |
1363 | g_dbus_connection_register_object (self->connection, |
1364 | self->context_path, |
1365 | (GDBusInterfaceInfo *) &atspi_editable_text_interface, |
1366 | vtable, |
1367 | self, |
1368 | NULL, |
1369 | NULL); |
1370 | self->n_registered_objects++; |
1371 | } |
1372 | vtable = gtk_atspi_get_value_vtable (accessible); |
1373 | if (vtable) |
1374 | { |
1375 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_value_interface.name); |
1376 | self->registration_ids[self->n_registered_objects] = |
1377 | g_dbus_connection_register_object (self->connection, |
1378 | self->context_path, |
1379 | (GDBusInterfaceInfo *) &atspi_value_interface, |
1380 | vtable, |
1381 | self, |
1382 | NULL, |
1383 | NULL); |
1384 | self->n_registered_objects++; |
1385 | } |
1386 | |
1387 | /* Calling gtk_accessible_get_accessible_role() in here will recurse, |
1388 | * so pass the role in explicitly. |
1389 | */ |
1390 | vtable = gtk_atspi_get_selection_vtable (accessible, |
1391 | role: GTK_AT_CONTEXT (ptr: self)->accessible_role); |
1392 | if (vtable) |
1393 | { |
1394 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_selection_interface.name); |
1395 | self->registration_ids[self->n_registered_objects] = |
1396 | g_dbus_connection_register_object (self->connection, |
1397 | self->context_path, |
1398 | (GDBusInterfaceInfo *) &atspi_selection_interface, |
1399 | vtable, |
1400 | self, |
1401 | NULL, |
1402 | NULL); |
1403 | self->n_registered_objects++; |
1404 | } |
1405 | |
1406 | vtable = gtk_atspi_get_action_vtable (accessible); |
1407 | if (vtable) |
1408 | { |
1409 | g_variant_builder_add (builder: &interfaces, format_string: "s" , atspi_action_interface.name); |
1410 | self->registration_ids[self->n_registered_objects] = |
1411 | g_dbus_connection_register_object (self->connection, |
1412 | self->context_path, |
1413 | (GDBusInterfaceInfo *) &atspi_action_interface, |
1414 | vtable, |
1415 | self, |
1416 | NULL, |
1417 | NULL); |
1418 | self->n_registered_objects++; |
1419 | } |
1420 | |
1421 | self->interfaces = g_variant_ref_sink (value: g_variant_builder_end (builder: &interfaces)); |
1422 | |
1423 | GTK_NOTE (A11Y, g_message ("Registered %d interfaces on object path '%s'" , |
1424 | self->n_registered_objects, |
1425 | self->context_path)); |
1426 | } |
1427 | |
1428 | static void |
1429 | gtk_at_spi_context_unregister_object (GtkAtSpiContext *self) |
1430 | { |
1431 | while (self->n_registered_objects > 0) |
1432 | { |
1433 | self->n_registered_objects--; |
1434 | g_dbus_connection_unregister_object (connection: self->connection, |
1435 | registration_id: self->registration_ids[self->n_registered_objects]); |
1436 | self->registration_ids[self->n_registered_objects] = 0; |
1437 | } |
1438 | |
1439 | g_clear_pointer (&self->interfaces, g_variant_unref); |
1440 | } |
1441 | /* }}} */ |
1442 | /* {{{ GObject boilerplate */ |
1443 | static void |
1444 | gtk_at_spi_context_finalize (GObject *gobject) |
1445 | { |
1446 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: gobject); |
1447 | |
1448 | gtk_at_spi_context_unregister_object (self); |
1449 | |
1450 | g_clear_object (&self->root); |
1451 | |
1452 | g_free (mem: self->context_path); |
1453 | |
1454 | G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject); |
1455 | } |
1456 | |
1457 | static void |
1458 | gtk_at_spi_context_constructed (GObject *gobject) |
1459 | { |
1460 | GtkAtSpiContext *self G_GNUC_UNUSED = GTK_AT_SPI_CONTEXT (ptr: gobject); |
1461 | |
1462 | G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject); |
1463 | } |
1464 | |
1465 | static const char *get_bus_address (GdkDisplay *display); |
1466 | |
1467 | static void |
1468 | register_object (GtkAtSpiRoot *root, |
1469 | GtkAtSpiContext *context) |
1470 | { |
1471 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: context)); |
1472 | |
1473 | gtk_atspi_connect_text_signals (accessible, |
1474 | text_changed: (GtkAtspiTextChangedCallback *)emit_text_changed, |
1475 | selection_changed: (GtkAtspiTextSelectionCallback *)emit_text_selection_changed, |
1476 | data: context); |
1477 | gtk_atspi_connect_selection_signals (accessible, |
1478 | selection_changed: (GtkAtspiSelectionCallback *)emit_selection_changed, |
1479 | data: context); |
1480 | gtk_at_spi_context_register_object (self: context); |
1481 | } |
1482 | |
1483 | static void |
1484 | gtk_at_spi_context_realize (GtkATContext *context) |
1485 | { |
1486 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: context); |
1487 | GdkDisplay *display = gtk_at_context_get_display (self: context); |
1488 | |
1489 | /* Every GTK application has a single root AT-SPI object, which |
1490 | * handles all the global state, including the cache of accessible |
1491 | * objects. We use the GdkDisplay to store it, so it's guaranteed |
1492 | * to be a unique per-display connection |
1493 | */ |
1494 | self->root = |
1495 | g_object_get_data (G_OBJECT (display), key: "-gtk-atspi-root" ); |
1496 | |
1497 | if (self->root == NULL) |
1498 | { |
1499 | self->root = gtk_at_spi_root_new (bus_address: get_bus_address (display)); |
1500 | g_object_set_data_full (G_OBJECT (display), key: "-gtk-atspi-root" , |
1501 | g_object_ref (self->root), |
1502 | destroy: g_object_unref); |
1503 | } |
1504 | else |
1505 | { |
1506 | g_object_ref (self->root); |
1507 | } |
1508 | |
1509 | /* UUIDs use '-' as the separator, but that's not a valid character |
1510 | * for a DBus object path |
1511 | */ |
1512 | char *uuid = g_uuid_string_random (); |
1513 | size_t len = strlen (s: uuid); |
1514 | for (size_t i = 0; i < len; i++) |
1515 | { |
1516 | if (uuid[i] == '-') |
1517 | uuid[i] = '_'; |
1518 | } |
1519 | |
1520 | self->context_path = |
1521 | g_strconcat (string1: gtk_at_spi_root_get_base_path (self: self->root), "/" , uuid, NULL); |
1522 | |
1523 | g_free (mem: uuid); |
1524 | |
1525 | self->connection = gtk_at_spi_root_get_connection (self: self->root); |
1526 | if (self->connection == NULL) |
1527 | return; |
1528 | |
1529 | #ifdef G_ENABLE_DEBUG |
1530 | if (GTK_DEBUG_CHECK (A11Y)) |
1531 | { |
1532 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: context); |
1533 | GtkAccessibleRole role = gtk_at_context_get_accessible_role (self: context); |
1534 | char *role_name = g_enum_to_string (g_enum_type: GTK_TYPE_ACCESSIBLE_ROLE, value: role); |
1535 | g_message ("Realizing ATSPI context “%s” for accessible “%s”, with role: “%s”" , |
1536 | self->context_path, |
1537 | G_OBJECT_TYPE_NAME (accessible), |
1538 | role_name); |
1539 | g_free (mem: role_name); |
1540 | } |
1541 | #endif |
1542 | |
1543 | gtk_at_spi_root_queue_register (self: self->root, context: self, func: register_object); |
1544 | } |
1545 | |
1546 | static void |
1547 | gtk_at_spi_context_unrealize (GtkATContext *context) |
1548 | { |
1549 | GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ptr: context); |
1550 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: context); |
1551 | |
1552 | GTK_NOTE (A11Y, g_message ("Unrealizing ATSPI context at '%s' for accessible '%s'" , |
1553 | self->context_path, |
1554 | G_OBJECT_TYPE_NAME (accessible))); |
1555 | |
1556 | /* Notify ATs that the accessible object is going away */ |
1557 | emit_defunct (self); |
1558 | gtk_at_spi_root_unregister (self: self->root, context: self); |
1559 | |
1560 | gtk_atspi_disconnect_text_signals (accessible); |
1561 | gtk_atspi_disconnect_selection_signals (accessible); |
1562 | gtk_at_spi_context_unregister_object (self); |
1563 | |
1564 | g_clear_pointer (&self->context_path, g_free); |
1565 | g_clear_object (&self->root); |
1566 | } |
1567 | |
1568 | static void |
1569 | gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass) |
1570 | { |
1571 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
1572 | GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (ptr: klass); |
1573 | |
1574 | gobject_class->constructed = gtk_at_spi_context_constructed; |
1575 | gobject_class->finalize = gtk_at_spi_context_finalize; |
1576 | |
1577 | context_class->realize = gtk_at_spi_context_realize; |
1578 | context_class->unrealize = gtk_at_spi_context_unrealize; |
1579 | context_class->state_change = gtk_at_spi_context_state_change; |
1580 | context_class->platform_change = gtk_at_spi_context_platform_change; |
1581 | context_class->bounds_change = gtk_at_spi_context_bounds_change; |
1582 | context_class->child_change = gtk_at_spi_context_child_change; |
1583 | } |
1584 | |
1585 | static void |
1586 | gtk_at_spi_context_init (GtkAtSpiContext *self) |
1587 | { |
1588 | } |
1589 | /* }}} */ |
1590 | /* {{{ Bus address discovery */ |
1591 | #ifdef GDK_WINDOWING_X11 |
1592 | static char * |
1593 | get_bus_address_x11 (GdkDisplay *display) |
1594 | { |
1595 | GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via X11..." )); |
1596 | |
1597 | Display *xdisplay = gdk_x11_display_get_xdisplay (display); |
1598 | Atom type_return; |
1599 | int format_return; |
1600 | gulong nitems_return; |
1601 | gulong bytes_after_return; |
1602 | guchar *data = NULL; |
1603 | char *address = NULL; |
1604 | |
1605 | gdk_x11_display_error_trap_push (display); |
1606 | XGetWindowProperty (xdisplay, DefaultRootWindow (xdisplay), |
1607 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "AT_SPI_BUS" ), |
1608 | 0L, BUFSIZ, False, |
1609 | (Atom) 31, |
1610 | &type_return, &format_return, &nitems_return, |
1611 | &bytes_after_return, &data); |
1612 | gdk_x11_display_error_trap_pop_ignored (display); |
1613 | |
1614 | address = g_strdup (str: (char *) data); |
1615 | |
1616 | XFree (data); |
1617 | |
1618 | return address; |
1619 | } |
1620 | #endif |
1621 | |
1622 | #if defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_X11) |
1623 | static char * |
1624 | get_bus_address_dbus (GdkDisplay *display) |
1625 | { |
1626 | GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via DBus..." )); |
1627 | |
1628 | GError *error = NULL; |
1629 | GDBusConnection *connection = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, error: &error); |
1630 | |
1631 | if (error != NULL) |
1632 | { |
1633 | GTK_NOTE (A11Y, g_message ("Unable to acquire session bus: %s" , error->message)); |
1634 | g_error_free (error); |
1635 | return NULL; |
1636 | } |
1637 | |
1638 | GVariant *res = |
1639 | g_dbus_connection_call_sync (connection, bus_name: "org.a11y.Bus" , |
1640 | object_path: "/org/a11y/bus" , |
1641 | interface_name: "org.a11y.Bus" , |
1642 | method_name: "GetAddress" , |
1643 | NULL, NULL, |
1644 | flags: G_DBUS_CALL_FLAGS_NONE, |
1645 | timeout_msec: -1, |
1646 | NULL, |
1647 | error: &error); |
1648 | if (error != NULL) |
1649 | { |
1650 | GTK_NOTE (A11Y, g_message ("Unable to acquire the address of the accessibility bus: %s" , |
1651 | error->message)); |
1652 | g_error_free (error); |
1653 | } |
1654 | |
1655 | char *address = NULL; |
1656 | if (res != NULL) |
1657 | { |
1658 | g_variant_get (value: res, format_string: "(s)" , &address); |
1659 | g_variant_unref (value: res); |
1660 | } |
1661 | |
1662 | g_object_unref (object: connection); |
1663 | |
1664 | return address; |
1665 | } |
1666 | #endif |
1667 | |
1668 | static const char * |
1669 | get_bus_address (GdkDisplay *display) |
1670 | { |
1671 | const char *bus_address; |
1672 | |
1673 | bus_address = g_object_get_data (G_OBJECT (display), key: "-gtk-atspi-bus-address" ); |
1674 | if (bus_address != NULL) |
1675 | return bus_address; |
1676 | |
1677 | /* The bus address environment variable takes precedence; this is the |
1678 | * mechanism used by Flatpak to handle the accessibility bus portal |
1679 | * between the sandbox and the outside world |
1680 | */ |
1681 | bus_address = g_getenv (variable: "AT_SPI_BUS_ADDRESS" ); |
1682 | if (bus_address != NULL && *bus_address != '\0') |
1683 | { |
1684 | GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from environment: %s" , bus_address)); |
1685 | g_object_set_data_full (G_OBJECT (display), key: "-gtk-atspi-bus-address" , |
1686 | data: g_strdup (str: bus_address), |
1687 | destroy: g_free); |
1688 | goto out; |
1689 | } |
1690 | |
1691 | #if defined(GDK_WINDOWING_WAYLAND) |
1692 | if (bus_address == NULL) |
1693 | { |
1694 | if (GDK_IS_WAYLAND_DISPLAY (display)) |
1695 | { |
1696 | char *addr = get_bus_address_dbus (display); |
1697 | |
1698 | GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s" , addr)); |
1699 | g_object_set_data_full (G_OBJECT (display), key: "-gtk-atspi-bus-address" , |
1700 | data: addr, |
1701 | destroy: g_free); |
1702 | |
1703 | bus_address = addr; |
1704 | } |
1705 | } |
1706 | #endif |
1707 | #if defined(GDK_WINDOWING_X11) |
1708 | if (bus_address == NULL) |
1709 | { |
1710 | if (GDK_IS_X11_DISPLAY (display)) |
1711 | { |
1712 | char *addr = get_bus_address_dbus (display); |
1713 | |
1714 | if (addr == NULL) |
1715 | { |
1716 | addr = get_bus_address_x11 (display); |
1717 | GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from X11: %s" , addr)); |
1718 | } |
1719 | else |
1720 | { |
1721 | GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s" , addr)); |
1722 | } |
1723 | |
1724 | g_object_set_data_full (G_OBJECT (display), key: "-gtk-atspi-bus-address" , |
1725 | data: addr, |
1726 | destroy: g_free); |
1727 | |
1728 | bus_address = addr; |
1729 | } |
1730 | } |
1731 | #endif |
1732 | |
1733 | out: |
1734 | return bus_address; |
1735 | } |
1736 | |
1737 | /* }}} */ |
1738 | /* {{{ API */ |
1739 | GtkATContext * |
1740 | gtk_at_spi_create_context (GtkAccessibleRole accessible_role, |
1741 | GtkAccessible *accessible, |
1742 | GdkDisplay *display) |
1743 | { |
1744 | const char *bus_address; |
1745 | |
1746 | g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL); |
1747 | g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); |
1748 | |
1749 | bus_address = get_bus_address (display); |
1750 | |
1751 | if (bus_address == NULL) |
1752 | bus_address = "" ; |
1753 | |
1754 | if (*bus_address == '\0') |
1755 | return NULL; |
1756 | |
1757 | #if defined(GDK_WINDOWING_WAYLAND) |
1758 | if (GDK_IS_WAYLAND_DISPLAY (display)) |
1759 | return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, |
1760 | first_property_name: "accessible-role" , accessible_role, |
1761 | "accessible" , accessible, |
1762 | "display" , display, |
1763 | NULL); |
1764 | #endif |
1765 | #if defined(GDK_WINDOWING_X11) |
1766 | if (GDK_IS_X11_DISPLAY (display)) |
1767 | return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, |
1768 | first_property_name: "accessible-role" , accessible_role, |
1769 | "accessible" , accessible, |
1770 | "display" , display, |
1771 | NULL); |
1772 | #endif |
1773 | |
1774 | return NULL; |
1775 | } |
1776 | |
1777 | const char * |
1778 | gtk_at_spi_context_get_context_path (GtkAtSpiContext *self) |
1779 | { |
1780 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); |
1781 | |
1782 | return self->context_path; |
1783 | } |
1784 | |
1785 | /*< private > |
1786 | * gtk_at_spi_context_to_ref: |
1787 | * @self: a `GtkAtSpiContext` |
1788 | * |
1789 | * Returns an ATSPI object reference for the `GtkAtSpiContext`. |
1790 | * |
1791 | * Returns: (transfer floating): a `GVariant` with the reference |
1792 | */ |
1793 | GVariant * |
1794 | gtk_at_spi_context_to_ref (GtkAtSpiContext *self) |
1795 | { |
1796 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); |
1797 | |
1798 | if (self->context_path == NULL) |
1799 | return gtk_at_spi_null_ref (); |
1800 | |
1801 | const char *name = g_dbus_connection_get_unique_name (connection: self->connection); |
1802 | |
1803 | return g_variant_new (format_string: "(so)" , name, self->context_path); |
1804 | } |
1805 | |
1806 | GVariant * |
1807 | gtk_at_spi_context_get_interfaces (GtkAtSpiContext *self) |
1808 | { |
1809 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); |
1810 | |
1811 | return self->interfaces; |
1812 | } |
1813 | |
1814 | GVariant * |
1815 | gtk_at_spi_context_get_states (GtkAtSpiContext *self) |
1816 | { |
1817 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au" )); |
1818 | |
1819 | collect_states (self, builder: &builder); |
1820 | |
1821 | return g_variant_builder_end (builder: &builder); |
1822 | } |
1823 | |
1824 | GVariant * |
1825 | gtk_at_spi_context_get_parent_ref (GtkAtSpiContext *self) |
1826 | { |
1827 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); |
1828 | |
1829 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
1830 | |
1831 | return get_parent_context_ref (accessible); |
1832 | } |
1833 | |
1834 | GtkAtSpiRoot * |
1835 | gtk_at_spi_context_get_root (GtkAtSpiContext *self) |
1836 | { |
1837 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); |
1838 | |
1839 | return self->root; |
1840 | } |
1841 | |
1842 | int |
1843 | gtk_at_spi_context_get_index_in_parent (GtkAtSpiContext *self) |
1844 | { |
1845 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), -1); |
1846 | |
1847 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
1848 | int idx; |
1849 | |
1850 | if (GTK_IS_ROOT (ptr: accessible)) |
1851 | idx = get_index_in_toplevels (GTK_WIDGET (accessible)); |
1852 | else if (GTK_IS_STACK_PAGE (accessible)) |
1853 | idx = get_index_in_parent (widget: gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); |
1854 | else if (GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (accessible)))) |
1855 | idx = 1; |
1856 | else |
1857 | idx = get_index_in_parent (GTK_WIDGET (accessible)); |
1858 | |
1859 | return idx; |
1860 | } |
1861 | |
1862 | int |
1863 | gtk_at_spi_context_get_child_count (GtkAtSpiContext *self) |
1864 | { |
1865 | g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), -1); |
1866 | |
1867 | GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self)); |
1868 | int n_children = -1; |
1869 | |
1870 | if (GTK_IS_WIDGET (accessible)) |
1871 | { |
1872 | GtkWidget *child; |
1873 | |
1874 | n_children = 0; |
1875 | for (child = gtk_widget_get_first_child (GTK_WIDGET (accessible)); |
1876 | child; |
1877 | child = gtk_widget_get_next_sibling (widget: child)) |
1878 | { |
1879 | if (!gtk_accessible_should_present (self: GTK_ACCESSIBLE (ptr: child))) |
1880 | continue; |
1881 | |
1882 | n_children++; |
1883 | } |
1884 | } |
1885 | else if (GTK_IS_STACK_PAGE (accessible)) |
1886 | { |
1887 | n_children = 1; |
1888 | } |
1889 | |
1890 | return n_children; |
1891 | } |
1892 | /* }}} */ |
1893 | |
1894 | /* vim:set foldmethod=marker expandtab: */ |
1895 | |