1/* gtkatspiaction.c: ATSPI Action 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 "gtkatspiactionprivate.h"
24
25#include "gtkatspicontextprivate.h"
26#include "gtkatcontextprivate.h"
27
28#include "a11y/atspi/atspi-action.h"
29
30#include "gtkactionable.h"
31#include "gtkactionmuxerprivate.h"
32#include "gtkbutton.h"
33#include "gtkcolorswatchprivate.h"
34#include "gtkentryprivate.h"
35#include "gtkexpander.h"
36#include "gtkmodelbuttonprivate.h"
37#include "gtkpasswordentryprivate.h"
38#include "gtksearchentry.h"
39#include "gtkswitch.h"
40#include "gtkwidgetprivate.h"
41
42#include <glib/gi18n-lib.h>
43
44typedef struct _Action Action;
45
46struct _Action
47{
48 const char *name;
49 const char *localized_name;
50 const char *description;
51 const char *keybinding;
52
53 gboolean (* is_enabled) (GtkAtSpiContext *context);
54 gboolean (* activate) (GtkAtSpiContext *context);
55};
56
57static void
58action_handle_method (GtkAtSpiContext *self,
59 const char *method_name,
60 GVariant *parameters,
61 GDBusMethodInvocation *invocation,
62 const Action *actions,
63 int n_actions)
64{
65 if (g_strcmp0 (str1: method_name, str2: "GetName") == 0)
66 {
67 int idx = -1;
68
69 g_variant_get (value: parameters, format_string: "(i)", &idx);
70
71 if (idx >= 0 && idx < n_actions)
72 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", actions[idx].name));
73 else
74 g_dbus_method_invocation_return_error (invocation,
75 G_IO_ERROR,
76 code: G_IO_ERROR_INVALID_ARGUMENT,
77 format: "Unknown action %d",
78 idx);
79 }
80 else if (g_strcmp0 (str1: method_name, str2: "GetLocalizedName") == 0)
81 {
82 int idx = -1;
83
84 g_variant_get (value: parameters, format_string: "(i)", &idx);
85
86 if (idx >= 0 && idx < n_actions)
87 {
88 const Action *action = &actions[idx];
89 const char *s = g_dpgettext2 (GETTEXT_PACKAGE, context: "accessibility", msgid: action->localized_name);
90
91 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", s));
92 }
93 else
94 {
95 g_dbus_method_invocation_return_error (invocation,
96 G_IO_ERROR,
97 code: G_IO_ERROR_INVALID_ARGUMENT,
98 format: "Unknown action %d",
99 idx);
100 }
101 }
102 else if (g_strcmp0 (str1: method_name, str2: "GetDescription") == 0)
103 {
104 int idx = -1;
105
106 g_variant_get (value: parameters, format_string: "(i)", &idx);
107
108 if (idx >= 0 && idx < n_actions)
109 {
110 const Action *action = &actions[idx];
111 const char *s = g_dpgettext2 (GETTEXT_PACKAGE, context: "accessibility", msgid: action->description);
112
113 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", s));
114 }
115 else
116 {
117 g_dbus_method_invocation_return_error (invocation,
118 G_IO_ERROR,
119 code: G_IO_ERROR_INVALID_ARGUMENT,
120 format: "Unknown action %d",
121 idx);
122 }
123 }
124 else if (g_strcmp0 (str1: method_name, str2: "GetKeyBinding") == 0)
125 {
126 int idx = -1;
127
128 g_variant_get (value: parameters, format_string: "(i)", &idx);
129
130 if (idx >= 0 && idx < n_actions)
131 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", actions[idx].keybinding));
132 else
133 g_dbus_method_invocation_return_error (invocation,
134 G_IO_ERROR,
135 code: G_IO_ERROR_INVALID_ARGUMENT,
136 format: "Unknown action %d",
137 idx);
138 }
139 else if (g_strcmp0 (str1: method_name, str2: "GetActions") == 0)
140 {
141 GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(sss)"));
142
143 for (int i = 0; i < n_actions; i++)
144 {
145 const Action *action = &actions[i];
146
147 if (action->is_enabled != NULL && !action->is_enabled (self))
148 continue;
149
150 g_variant_builder_add (builder: &builder, format_string: "(sss)",
151 g_dpgettext2 (GETTEXT_PACKAGE, context: "accessibility", msgid: action->localized_name),
152 g_dpgettext2 (GETTEXT_PACKAGE, context: "accessibility", msgid: action->description),
153 action->keybinding);
154 }
155
156 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(sss))", &builder));
157 }
158 else if (g_strcmp0 (str1: method_name, str2: "DoAction") == 0)
159 {
160 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
161 GtkWidget *widget = GTK_WIDGET (accessible);
162 int idx = -1;
163
164 if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_visible (widget))
165 {
166 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(b)", FALSE));
167 return;
168 }
169
170 g_variant_get (value: parameters, format_string: "(i)", &idx);
171
172 if (idx >= 0 && idx < n_actions)
173 {
174 const Action *action = &actions[idx];
175
176 if (action->is_enabled == NULL || action->is_enabled (self))
177 {
178 gboolean res = TRUE;
179
180 if (action->activate == NULL)
181 {
182 gtk_widget_activate (widget);
183 }
184 else
185 {
186 res = action->activate (self);
187 }
188
189 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(b)", res));
190 }
191 else
192 {
193 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(b)", FALSE));
194 }
195 }
196 else
197 {
198 g_dbus_method_invocation_return_error (invocation,
199 G_IO_ERROR,
200 code: G_IO_ERROR_INVALID_ARGUMENT,
201 format: "Unknown action %d",
202 idx);
203 }
204 }
205}
206
207static GVariant *
208action_handle_get_property (GtkAtSpiContext *self,
209 const char *property_name,
210 GError **error,
211 const Action *actions,
212 int n_actions)
213{
214 if (g_strcmp0 (str1: property_name, str2: "NActions") == 0)
215 {
216 int n_valid_actions = 0;
217
218 for (int i = 0; i < n_actions; i++)
219 {
220 const Action *action = &actions[i];
221
222 if (action->is_enabled == NULL || action->is_enabled (self))
223 n_valid_actions += 1;
224 }
225
226 return g_variant_new_int32 (value: n_valid_actions);
227 }
228
229 g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
230 format: "Unknown property '%s'", property_name);
231
232 return NULL;
233}
234
235/* {{{ GtkButton */
236static Action button_actions[] = {
237 {
238 .name = "click",
239 .localized_name = NC_("accessibility", "Click"),
240 .description = NC_("accessibility", "Clicks the button"),
241 .keybinding = "<Space>",
242 },
243};
244
245static void
246button_handle_method (GDBusConnection *connection,
247 const gchar *sender,
248 const gchar *object_path,
249 const gchar *interface_name,
250 const gchar *method_name,
251 GVariant *parameters,
252 GDBusMethodInvocation *invocation,
253 gpointer user_data)
254{
255 GtkAtSpiContext *self = user_data;
256
257 action_handle_method (self, method_name, parameters, invocation,
258 actions: button_actions,
259 G_N_ELEMENTS (button_actions));
260}
261
262static GVariant *
263button_handle_get_property (GDBusConnection *connection,
264 const gchar *sender,
265 const gchar *object_path,
266 const gchar *interface_name,
267 const gchar *property_name,
268 GError **error,
269 gpointer user_data)
270{
271 GtkAtSpiContext *self = user_data;
272
273 return action_handle_get_property (self, property_name, error,
274 actions: button_actions,
275 G_N_ELEMENTS (button_actions));
276}
277
278static const GDBusInterfaceVTable button_action_vtable = {
279 button_handle_method,
280 button_handle_get_property,
281 NULL,
282};
283
284/* }}} */
285/* {{{ GtkSwitch */
286
287static const Action switch_actions[] = {
288 {
289 .name = "toggle",
290 .localized_name = NC_("accessibility", "Toggle"),
291 .description = NC_("accessibility", "Toggles the switch"),
292 .keybinding = "<Space>",
293 },
294};
295
296static void
297switch_handle_method (GDBusConnection *connection,
298 const gchar *sender,
299 const gchar *object_path,
300 const gchar *interface_name,
301 const gchar *method_name,
302 GVariant *parameters,
303 GDBusMethodInvocation *invocation,
304 gpointer user_data)
305{
306 GtkAtSpiContext *self = user_data;
307
308 action_handle_method (self, method_name, parameters, invocation,
309 actions: switch_actions,
310 G_N_ELEMENTS (switch_actions));
311}
312
313static GVariant *
314switch_handle_get_property (GDBusConnection *connection,
315 const gchar *sender,
316 const gchar *object_path,
317 const gchar *interface_name,
318 const gchar *property_name,
319 GError **error,
320 gpointer user_data)
321{
322 GtkAtSpiContext *self = user_data;
323
324 return action_handle_get_property (self, property_name, error,
325 actions: switch_actions,
326 G_N_ELEMENTS (switch_actions));
327}
328
329static const GDBusInterfaceVTable switch_action_vtable = {
330 switch_handle_method,
331 switch_handle_get_property,
332 NULL,
333};
334
335/* }}} */
336/* {{{ GtkColorSwatch */
337static gboolean
338color_swatch_select (GtkAtSpiContext *self)
339{
340 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
341 gtk_color_swatch_select (GTK_COLOR_SWATCH (accessible));
342 return TRUE;
343}
344
345static gboolean
346color_swatch_activate (GtkAtSpiContext *self)
347{
348 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
349 gtk_color_swatch_activate (GTK_COLOR_SWATCH (accessible));
350 return TRUE;
351}
352
353static gboolean
354color_swatch_customize (GtkAtSpiContext *self)
355{
356 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
357 gtk_color_swatch_customize (GTK_COLOR_SWATCH (accessible));
358 return TRUE;
359}
360
361static gboolean
362color_swatch_is_enabled (GtkAtSpiContext *self)
363{
364 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
365 return gtk_color_swatch_get_selectable (GTK_COLOR_SWATCH (accessible));
366}
367
368static const Action color_swatch_actions[] = {
369 {
370 .name = "select",
371 .localized_name = NC_("accessibility", "Select"),
372 .description = NC_("accessibility", "Selects the color"),
373 .keybinding = "<Return>",
374 .activate = color_swatch_select,
375 .is_enabled = color_swatch_is_enabled,
376 },
377 {
378 .name = "activate",
379 .localized_name = NC_("accessibility", "Activate"),
380 .description = NC_("accessibility", "Activates the color"),
381 .keybinding = "<VoidSymbol>",
382 .activate = color_swatch_activate,
383 .is_enabled = color_swatch_is_enabled,
384 },
385 {
386 .name = "customize",
387 .localized_name = NC_("accessibility", "Customize"),
388 .description = NC_("accessibility", "Customizes the color"),
389 .keybinding = "<VoidSymbol>",
390 .activate = color_swatch_customize,
391 .is_enabled = color_swatch_is_enabled,
392 },
393};
394
395static void
396color_swatch_handle_method (GDBusConnection *connection,
397 const gchar *sender,
398 const gchar *object_path,
399 const gchar *interface_name,
400 const gchar *method_name,
401 GVariant *parameters,
402 GDBusMethodInvocation *invocation,
403 gpointer user_data)
404{
405 GtkAtSpiContext *self = user_data;
406
407 action_handle_method (self, method_name, parameters, invocation,
408 actions: color_swatch_actions,
409 G_N_ELEMENTS (color_swatch_actions));
410}
411
412static GVariant *
413color_swatch_handle_get_property (GDBusConnection *connection,
414 const gchar *sender,
415 const gchar *object_path,
416 const gchar *interface_name,
417 const gchar *property_name,
418 GError **error,
419 gpointer user_data)
420{
421 GtkAtSpiContext *self = user_data;
422
423 return action_handle_get_property (self, property_name, error,
424 actions: color_swatch_actions,
425 G_N_ELEMENTS (color_swatch_actions));
426}
427
428static const GDBusInterfaceVTable color_swatch_action_vtable = {
429 color_swatch_handle_method,
430 color_swatch_handle_get_property,
431 NULL,
432};
433/* }}} */
434/* {{{ GtkExpander */
435
436static const Action expander_actions[] = {
437 {
438 .name = "activate",
439 .localized_name = NC_("accessibility", "Activate"),
440 .description = NC_("accessibility", "Activates the expander"),
441 .keybinding = "<Space>",
442 },
443};
444
445static void
446expander_handle_method (GDBusConnection *connection,
447 const gchar *sender,
448 const gchar *object_path,
449 const gchar *interface_name,
450 const gchar *method_name,
451 GVariant *parameters,
452 GDBusMethodInvocation *invocation,
453 gpointer user_data)
454{
455 GtkAtSpiContext *self = user_data;
456
457 action_handle_method (self, method_name, parameters, invocation,
458 actions: expander_actions,
459 G_N_ELEMENTS (expander_actions));
460}
461
462static GVariant *
463expander_handle_get_property (GDBusConnection *connection,
464 const gchar *sender,
465 const gchar *object_path,
466 const gchar *interface_name,
467 const gchar *property_name,
468 GError **error,
469 gpointer user_data)
470{
471 GtkAtSpiContext *self = user_data;
472
473 return action_handle_get_property (self, property_name, error,
474 actions: expander_actions,
475 G_N_ELEMENTS (expander_actions));
476}
477
478static const GDBusInterfaceVTable expander_action_vtable = {
479 expander_handle_method,
480 expander_handle_get_property,
481 NULL,
482};
483
484/* }}} */
485/* {{{ GtkEntry */
486
487static gboolean is_primary_icon_enabled (GtkAtSpiContext *self);
488static gboolean is_secondary_icon_enabled (GtkAtSpiContext *self);
489static gboolean activate_primary_icon (GtkAtSpiContext *self);
490static gboolean activate_secondary_icon (GtkAtSpiContext *self);
491
492static const Action entry_actions[] = {
493 {
494 .name = "activate",
495 .localized_name = NC_("accessibility", "Activate"),
496 .description = NC_("accessibility", "Activates the entry"),
497 .keybinding = "<Return>",
498 .is_enabled = NULL,
499 .activate = NULL,
500 },
501 {
502 .name = "activate-primary-icon",
503 .localized_name = NC_("accessibility", "Activate primary icon"),
504 .description = NC_("accessibility", "Activates the primary icon of the entry"),
505 .keybinding = "<VoidSymbol>",
506 .is_enabled = is_primary_icon_enabled,
507 .activate = activate_primary_icon,
508 },
509 {
510 .name = "activate-secondary-icon",
511 .localized_name = NC_("accessibility", "Activate secondary icon"),
512 .description = NC_("accessibility", "Activates the secondary icon of the entry"),
513 .keybinding = "<VoidSymbol>",
514 .is_enabled = is_secondary_icon_enabled,
515 .activate = activate_secondary_icon,
516 },
517};
518
519static gboolean
520is_primary_icon_enabled (GtkAtSpiContext *self)
521{
522 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
523 GtkEntry *entry = GTK_ENTRY (accessible);
524
525 return gtk_entry_get_icon_storage_type (entry, icon_pos: GTK_ENTRY_ICON_PRIMARY) != GTK_IMAGE_EMPTY;
526}
527
528static gboolean
529activate_primary_icon (GtkAtSpiContext *self)
530{
531 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
532 GtkEntry *entry = GTK_ENTRY (accessible);
533
534 return gtk_entry_activate_icon (entry, pos: GTK_ENTRY_ICON_PRIMARY);
535}
536
537static gboolean
538is_secondary_icon_enabled (GtkAtSpiContext *self)
539{
540 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
541 GtkEntry *entry = GTK_ENTRY (accessible);
542
543 return gtk_entry_get_icon_storage_type (entry, icon_pos: GTK_ENTRY_ICON_SECONDARY) != GTK_IMAGE_EMPTY;
544}
545
546static gboolean
547activate_secondary_icon (GtkAtSpiContext *self)
548{
549 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
550 GtkEntry *entry = GTK_ENTRY (accessible);
551
552 return gtk_entry_activate_icon (entry, pos: GTK_ENTRY_ICON_SECONDARY);
553}
554
555static void
556entry_handle_method (GDBusConnection *connection,
557 const gchar *sender,
558 const gchar *object_path,
559 const gchar *interface_name,
560 const gchar *method_name,
561 GVariant *parameters,
562 GDBusMethodInvocation *invocation,
563 gpointer user_data)
564{
565 GtkAtSpiContext *self = user_data;
566
567 action_handle_method (self, method_name, parameters, invocation,
568 actions: entry_actions,
569 G_N_ELEMENTS (entry_actions));
570}
571
572static GVariant *
573entry_handle_get_property (GDBusConnection *connection,
574 const gchar *sender,
575 const gchar *object_path,
576 const gchar *interface_name,
577 const gchar *property_name,
578 GError **error,
579 gpointer user_data)
580{
581 GtkAtSpiContext *self = user_data;
582
583 return action_handle_get_property (self, property_name, error,
584 actions: entry_actions,
585 G_N_ELEMENTS (entry_actions));
586}
587
588static const GDBusInterfaceVTable entry_action_vtable = {
589 entry_handle_method,
590 entry_handle_get_property,
591 NULL,
592};
593
594/* }}} */
595/* {{{ GtkPasswordEntry */
596
597static gboolean is_peek_enabled (GtkAtSpiContext *self);
598static gboolean activate_peek (GtkAtSpiContext *self);
599
600static const Action password_entry_actions[] = {
601 {
602 .name = "activate",
603 .localized_name = NC_("accessibility", "Activate"),
604 .description = NC_("accessibility", "Activates the entry"),
605 .keybinding = "<Return>",
606 .is_enabled = NULL,
607 .activate = NULL,
608 },
609 {
610 .name = "peek",
611 .localized_name = NC_("accessibility", "Peek"),
612 .description = NC_("accessibility", "Shows the contents of the password entry"),
613 .keybinding = "<VoidSymbol>",
614 .is_enabled = is_peek_enabled,
615 .activate = activate_peek,
616 },
617};
618
619static gboolean
620is_peek_enabled (GtkAtSpiContext *self)
621{
622 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
623 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (accessible);
624
625 if (!gtk_password_entry_get_show_peek_icon (entry))
626 return FALSE;
627
628 return TRUE;
629}
630
631static gboolean
632activate_peek (GtkAtSpiContext *self)
633{
634 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
635 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (accessible);
636
637 gtk_password_entry_toggle_peek (entry);
638
639 return TRUE;
640}
641
642static void
643password_entry_handle_method (GDBusConnection *connection,
644 const gchar *sender,
645 const gchar *object_path,
646 const gchar *interface_name,
647 const gchar *method_name,
648 GVariant *parameters,
649 GDBusMethodInvocation *invocation,
650 gpointer user_data)
651{
652 GtkAtSpiContext *self = user_data;
653
654 action_handle_method (self, method_name, parameters, invocation,
655 actions: password_entry_actions,
656 G_N_ELEMENTS (password_entry_actions));
657}
658
659static GVariant *
660password_entry_handle_get_property (GDBusConnection *connection,
661 const gchar *sender,
662 const gchar *object_path,
663 const gchar *interface_name,
664 const gchar *property_name,
665 GError **error,
666 gpointer user_data)
667{
668 GtkAtSpiContext *self = user_data;
669
670 return action_handle_get_property (self, property_name, error,
671 actions: password_entry_actions,
672 G_N_ELEMENTS (password_entry_actions));
673}
674
675static const GDBusInterfaceVTable password_entry_action_vtable = {
676 password_entry_handle_method,
677 password_entry_handle_get_property,
678 NULL,
679};
680
681/* }}} */
682/* {{{ GtkSearchEntry */
683
684static gboolean is_clear_enabled (GtkAtSpiContext *self);
685static gboolean activate_clear (GtkAtSpiContext *self);
686
687static const Action search_entry_actions[] = {
688 {
689 .name = "activate",
690 .localized_name = NC_("accessibility", "Activate"),
691 .description = NC_("accessibility", "Activates the entry"),
692 .keybinding = "<Return>",
693 .is_enabled = NULL,
694 .activate = NULL,
695 },
696 {
697 .name = "clear",
698 .localized_name = NC_("accessibility", "Clear"),
699 .description = NC_("accessibility", "Clears the contents of the entry"),
700 .keybinding = "<VoidSymbol>",
701 .is_enabled = is_clear_enabled,
702 .activate = activate_clear,
703 },
704};
705
706static gboolean
707is_clear_enabled (GtkAtSpiContext *self)
708{
709 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
710 GtkSearchEntry *entry = GTK_SEARCH_ENTRY (accessible);
711
712 const char *str = gtk_editable_get_text (GTK_EDITABLE (entry));
713
714 if (str == NULL || *str == '\0')
715 return FALSE;
716
717 return TRUE;
718}
719
720static gboolean
721activate_clear (GtkAtSpiContext *self)
722{
723 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
724
725 gtk_editable_set_text (GTK_EDITABLE (accessible), text: "");
726
727 return TRUE;
728}
729
730static void
731search_entry_handle_method (GDBusConnection *connection,
732 const gchar *sender,
733 const gchar *object_path,
734 const gchar *interface_name,
735 const gchar *method_name,
736 GVariant *parameters,
737 GDBusMethodInvocation *invocation,
738 gpointer user_data)
739{
740 GtkAtSpiContext *self = user_data;
741
742 action_handle_method (self, method_name, parameters, invocation,
743 actions: search_entry_actions,
744 G_N_ELEMENTS (search_entry_actions));
745}
746
747static GVariant *
748search_entry_handle_get_property (GDBusConnection *connection,
749 const gchar *sender,
750 const gchar *object_path,
751 const gchar *interface_name,
752 const gchar *property_name,
753 GError **error,
754 gpointer user_data)
755{
756 GtkAtSpiContext *self = user_data;
757
758 return action_handle_get_property (self, property_name, error,
759 actions: search_entry_actions,
760 G_N_ELEMENTS (search_entry_actions));
761}
762
763static const GDBusInterfaceVTable search_entry_action_vtable = {
764 search_entry_handle_method,
765 search_entry_handle_get_property,
766 NULL,
767};
768/* }}} */
769
770static gboolean
771is_valid_action (GtkActionMuxer *muxer,
772 const char *action_name)
773{
774 const GVariantType *param_type = NULL;
775 gboolean enabled = FALSE;
776
777 /* Skip disabled or parametrized actions */
778 if (!gtk_action_muxer_query_action (muxer, action_name,
779 enabled: &enabled,
780 parameter_type: &param_type, NULL,
781 NULL, NULL))
782 return FALSE;
783
784 if (!enabled || param_type != NULL)
785 return FALSE;
786
787 return TRUE;
788}
789
790static void
791add_muxer_actions (GtkActionMuxer *muxer,
792 char **actions,
793 int n_actions,
794 GVariantBuilder *builder)
795{
796 for (int i = 0; i < n_actions; i++)
797 {
798 if (!is_valid_action (muxer, action_name: actions[i]))
799 continue;
800
801 g_variant_builder_add (builder, format_string: "(sss)",
802 actions[i],
803 actions[i],
804 "<VoidSymbol>");
805 }
806}
807
808static const char *
809get_action_at_index (GtkActionMuxer *muxer,
810 char **actions,
811 int n_actions,
812 int pos)
813{
814 int real_pos = 0;
815
816 for (int i = 0; i < n_actions; i++)
817 {
818 if (!is_valid_action (muxer, action_name: actions[i]))
819 continue;
820
821 if (real_pos == pos)
822 return actions[i];
823
824 real_pos += 1;
825 }
826
827 return NULL;
828}
829
830static int
831get_valid_actions (GtkActionMuxer *muxer,
832 char **actions,
833 int n_actions)
834{
835 int n_enabled_actions = 0;
836
837 for (int i = 0; i < n_actions; i++)
838 {
839 if (!is_valid_action (muxer, action_name: actions[i]))
840 continue;
841
842 n_enabled_actions += 1;
843 }
844
845 return n_enabled_actions;
846}
847
848static void
849widget_handle_method (GDBusConnection *connection,
850 const gchar *sender,
851 const gchar *object_path,
852 const gchar *interface_name,
853 const gchar *method_name,
854 GVariant *parameters,
855 GDBusMethodInvocation *invocation,
856 gpointer user_data)
857{
858 GtkAtSpiContext *self = user_data;
859 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
860 GtkWidget *widget = GTK_WIDGET (accessible);
861 GtkWidget *parent = gtk_widget_get_parent (widget);
862 GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, FALSE);
863 GtkActionMuxer *parent_muxer = parent ? _gtk_widget_get_action_muxer (widget: parent, FALSE) : NULL;
864
865 if (muxer == NULL)
866 return;
867
868 char **actions = NULL;
869
870 if (muxer != parent_muxer)
871 actions = gtk_action_muxer_list_actions (muxer, TRUE);
872
873 int n_actions = actions != NULL ? g_strv_length (str_array: actions) : 0;
874
875 /* XXX: We need more fields in the action API */
876 if (g_strcmp0 (str1: method_name, str2: "GetName") == 0 ||
877 g_strcmp0 (str1: method_name, str2: "GetLocalizedName") == 0 ||
878 g_strcmp0 (str1: method_name, str2: "GetDescription") == 0)
879 {
880 int action_idx;
881
882 g_variant_get (value: parameters, format_string: "(i)", &action_idx);
883
884 const char *action = get_action_at_index (muxer, actions, n_actions, pos: action_idx);
885
886 if (action != NULL && gtk_widget_is_sensitive (widget))
887 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", action));
888 else
889 g_dbus_method_invocation_return_error (invocation,
890 G_IO_ERROR,
891 code: G_IO_ERROR_INVALID_ARGUMENT,
892 format: "No action with index %d",
893 action_idx);
894 }
895 else if (g_strcmp0 (str1: method_name, str2: "DoAction") == 0)
896 {
897 int action_idx;
898
899 g_variant_get (value: parameters, format_string: "(i)", &action_idx);
900
901 const char *action = get_action_at_index (muxer, actions, n_actions, pos: action_idx);
902
903 if (action != NULL && gtk_widget_is_sensitive (widget))
904 {
905 gboolean res = gtk_widget_activate_action_variant (widget, name: action, NULL);
906
907 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(b)", res));
908 }
909 else
910 {
911 g_dbus_method_invocation_return_error (invocation,
912 G_IO_ERROR,
913 code: G_IO_ERROR_INVALID_ARGUMENT,
914 format: "No action with index %d",
915 action_idx);
916 }
917 }
918 else if (g_strcmp0 (str1: method_name, str2: "GetKeyBinding") == 0)
919 {
920 int action_idx;
921
922 g_variant_get (value: parameters, format_string: "(i)", &action_idx);
923
924 const char *action = get_action_at_index (muxer, actions, n_actions, pos: action_idx);
925
926 if (action != NULL && gtk_widget_is_sensitive (widget))
927 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)", "<VoidSymbol>"));
928 else
929 g_dbus_method_invocation_return_error (invocation,
930 G_IO_ERROR,
931 code: G_IO_ERROR_INVALID_ARGUMENT,
932 format: "No action with index %d",
933 action_idx);
934 }
935 else if (g_strcmp0 (str1: method_name, str2: "GetActions") == 0)
936 {
937 GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(sss)"));
938
939 if (n_actions >= 0 && gtk_widget_is_sensitive (widget))
940 add_muxer_actions (muxer, actions, n_actions, builder: &builder);
941
942 g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(sss))", &builder));
943 }
944
945 g_strfreev (str_array: actions);
946}
947
948static GVariant *
949widget_handle_get_property (GDBusConnection *connection,
950 const gchar *sender,
951 const gchar *object_path,
952 const gchar *interface_name,
953 const gchar *property_name,
954 GError **error,
955 gpointer user_data)
956{
957 GtkAtSpiContext *self = user_data;
958 GtkAccessible *accessible = gtk_at_context_get_accessible (self: GTK_AT_CONTEXT (ptr: self));
959 GtkWidget *widget = GTK_WIDGET (accessible);
960 GtkWidget *parent = gtk_widget_get_parent (widget);
961 GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, FALSE);
962 GtkActionMuxer *parent_muxer = parent ? _gtk_widget_get_action_muxer (widget: parent, FALSE) : NULL;
963 GVariant *res = NULL;
964
965 if (muxer == NULL)
966 return res;
967
968 char **actions = NULL;
969
970 if (muxer != parent_muxer)
971 actions = gtk_action_muxer_list_actions (muxer, TRUE);
972
973 int n_actions = actions != NULL ? g_strv_length (str_array: actions) : 0;
974
975 if (g_strcmp0 (str1: property_name, str2: "NActions") == 0)
976 res = g_variant_new (format_string: "i", get_valid_actions (muxer, actions, n_actions));
977 else
978 g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
979 format: "Unknown property '%s'", property_name);
980
981 return res;
982}
983
984static const GDBusInterfaceVTable widget_action_vtable = {
985 widget_handle_method,
986 widget_handle_get_property,
987 NULL,
988};
989
990const GDBusInterfaceVTable *
991gtk_atspi_get_action_vtable (GtkAccessible *accessible)
992{
993 if (GTK_IS_BUTTON (accessible) ||
994 GTK_IS_MODEL_BUTTON (accessible))
995 return &button_action_vtable;
996 else if (GTK_IS_ENTRY (accessible))
997 return &entry_action_vtable;
998 else if (GTK_IS_EXPANDER (accessible))
999 return &expander_action_vtable;
1000 else if (GTK_IS_PASSWORD_ENTRY (accessible))
1001 return &password_entry_action_vtable;
1002 else if (GTK_IS_SEARCH_ENTRY (accessible))
1003 return &search_entry_action_vtable;
1004 else if (GTK_IS_SWITCH (accessible))
1005 return &switch_action_vtable;
1006 else if (GTK_IS_COLOR_SWATCH (accessible))
1007 return &color_swatch_action_vtable;
1008 else if (GTK_IS_WIDGET (accessible))
1009 return &widget_action_vtable;
1010
1011 return NULL;
1012}
1013
1014/* vim:set foldmethod=marker expandtab: */
1015

source code of gtk/gtk/a11y/gtkatspiaction.c