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
44 typedef struct _Action Action;
45
46 struct _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
57 static void
action_handle_method(GtkAtSpiContext * self,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,const Action * actions,int n_actions)58 action_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 (method_name, "GetName") == 0)
66 {
67 int idx = -1;
68
69 g_variant_get (parameters, "(i)", &idx);
70
71 if (idx >= 0 && idx < n_actions)
72 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", actions[idx].name));
73 else
74 g_dbus_method_invocation_return_error (invocation,
75 G_IO_ERROR,
76 G_IO_ERROR_INVALID_ARGUMENT,
77 "Unknown action %d",
78 idx);
79 }
80 else if (g_strcmp0 (method_name, "GetLocalizedName") == 0)
81 {
82 int idx = -1;
83
84 g_variant_get (parameters, "(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, "accessibility", action->localized_name);
90
91 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", s));
92 }
93 else
94 {
95 g_dbus_method_invocation_return_error (invocation,
96 G_IO_ERROR,
97 G_IO_ERROR_INVALID_ARGUMENT,
98 "Unknown action %d",
99 idx);
100 }
101 }
102 else if (g_strcmp0 (method_name, "GetDescription") == 0)
103 {
104 int idx = -1;
105
106 g_variant_get (parameters, "(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, "accessibility", action->description);
112
113 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", s));
114 }
115 else
116 {
117 g_dbus_method_invocation_return_error (invocation,
118 G_IO_ERROR,
119 G_IO_ERROR_INVALID_ARGUMENT,
120 "Unknown action %d",
121 idx);
122 }
123 }
124 else if (g_strcmp0 (method_name, "GetKeyBinding") == 0)
125 {
126 int idx = -1;
127
128 g_variant_get (parameters, "(i)", &idx);
129
130 if (idx >= 0 && idx < n_actions)
131 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", actions[idx].keybinding));
132 else
133 g_dbus_method_invocation_return_error (invocation,
134 G_IO_ERROR,
135 G_IO_ERROR_INVALID_ARGUMENT,
136 "Unknown action %d",
137 idx);
138 }
139 else if (g_strcmp0 (method_name, "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, "(sss)",
151 g_dpgettext2 (GETTEXT_PACKAGE, "accessibility", action->localized_name),
152 g_dpgettext2 (GETTEXT_PACKAGE, "accessibility", action->description),
153 action->keybinding);
154 }
155
156 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(sss))", &builder));
157 }
158 else if (g_strcmp0 (method_name, "DoAction") == 0)
159 {
160 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (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, g_variant_new ("(b)", FALSE));
167 return;
168 }
169
170 g_variant_get (parameters, "(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, g_variant_new ("(b)", res));
190 }
191 else
192 {
193 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
194 }
195 }
196 else
197 {
198 g_dbus_method_invocation_return_error (invocation,
199 G_IO_ERROR,
200 G_IO_ERROR_INVALID_ARGUMENT,
201 "Unknown action %d",
202 idx);
203 }
204 }
205 }
206
207 static GVariant *
action_handle_get_property(GtkAtSpiContext * self,const char * property_name,GError ** error,const Action * actions,int n_actions)208 action_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 (property_name, "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 (n_valid_actions);
227 }
228
229 g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
230 "Unknown property '%s'", property_name);
231
232 return NULL;
233 }
234
235 /* {{{ GtkButton */
236 static 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
245 static void
button_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)246 button_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 button_actions,
259 G_N_ELEMENTS (button_actions));
260 }
261
262 static GVariant *
button_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)263 button_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 button_actions,
275 G_N_ELEMENTS (button_actions));
276 }
277
278 static const GDBusInterfaceVTable button_action_vtable = {
279 button_handle_method,
280 button_handle_get_property,
281 NULL,
282 };
283
284 /* }}} */
285 /* {{{ GtkSwitch */
286
287 static 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
296 static void
switch_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)297 switch_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 switch_actions,
310 G_N_ELEMENTS (switch_actions));
311 }
312
313 static GVariant *
switch_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)314 switch_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 switch_actions,
326 G_N_ELEMENTS (switch_actions));
327 }
328
329 static const GDBusInterfaceVTable switch_action_vtable = {
330 switch_handle_method,
331 switch_handle_get_property,
332 NULL,
333 };
334
335 /* }}} */
336 /* {{{ GtkColorSwatch */
337 static gboolean
color_swatch_select(GtkAtSpiContext * self)338 color_swatch_select (GtkAtSpiContext *self)
339 {
340 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
341 gtk_color_swatch_select (GTK_COLOR_SWATCH (accessible));
342 return TRUE;
343 }
344
345 static gboolean
color_swatch_activate(GtkAtSpiContext * self)346 color_swatch_activate (GtkAtSpiContext *self)
347 {
348 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
349 gtk_color_swatch_activate (GTK_COLOR_SWATCH (accessible));
350 return TRUE;
351 }
352
353 static gboolean
color_swatch_customize(GtkAtSpiContext * self)354 color_swatch_customize (GtkAtSpiContext *self)
355 {
356 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
357 gtk_color_swatch_customize (GTK_COLOR_SWATCH (accessible));
358 return TRUE;
359 }
360
361 static gboolean
color_swatch_is_enabled(GtkAtSpiContext * self)362 color_swatch_is_enabled (GtkAtSpiContext *self)
363 {
364 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
365 return gtk_color_swatch_get_selectable (GTK_COLOR_SWATCH (accessible));
366 }
367
368 static 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
395 static void
color_swatch_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)396 color_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 color_swatch_actions,
409 G_N_ELEMENTS (color_swatch_actions));
410 }
411
412 static GVariant *
color_swatch_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)413 color_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 color_swatch_actions,
425 G_N_ELEMENTS (color_swatch_actions));
426 }
427
428 static const GDBusInterfaceVTable color_swatch_action_vtable = {
429 color_swatch_handle_method,
430 color_swatch_handle_get_property,
431 NULL,
432 };
433 /* }}} */
434 /* {{{ GtkExpander */
435
436 static 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
445 static void
expander_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)446 expander_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 expander_actions,
459 G_N_ELEMENTS (expander_actions));
460 }
461
462 static GVariant *
expander_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)463 expander_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 expander_actions,
475 G_N_ELEMENTS (expander_actions));
476 }
477
478 static const GDBusInterfaceVTable expander_action_vtable = {
479 expander_handle_method,
480 expander_handle_get_property,
481 NULL,
482 };
483
484 /* }}} */
485 /* {{{ GtkEntry */
486
487 static gboolean is_primary_icon_enabled (GtkAtSpiContext *self);
488 static gboolean is_secondary_icon_enabled (GtkAtSpiContext *self);
489 static gboolean activate_primary_icon (GtkAtSpiContext *self);
490 static gboolean activate_secondary_icon (GtkAtSpiContext *self);
491
492 static 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
519 static gboolean
is_primary_icon_enabled(GtkAtSpiContext * self)520 is_primary_icon_enabled (GtkAtSpiContext *self)
521 {
522 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
523 GtkEntry *entry = GTK_ENTRY (accessible);
524
525 return gtk_entry_get_icon_storage_type (entry, GTK_ENTRY_ICON_PRIMARY) != GTK_IMAGE_EMPTY;
526 }
527
528 static gboolean
activate_primary_icon(GtkAtSpiContext * self)529 activate_primary_icon (GtkAtSpiContext *self)
530 {
531 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
532 GtkEntry *entry = GTK_ENTRY (accessible);
533
534 return gtk_entry_activate_icon (entry, GTK_ENTRY_ICON_PRIMARY);
535 }
536
537 static gboolean
is_secondary_icon_enabled(GtkAtSpiContext * self)538 is_secondary_icon_enabled (GtkAtSpiContext *self)
539 {
540 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
541 GtkEntry *entry = GTK_ENTRY (accessible);
542
543 return gtk_entry_get_icon_storage_type (entry, GTK_ENTRY_ICON_SECONDARY) != GTK_IMAGE_EMPTY;
544 }
545
546 static gboolean
activate_secondary_icon(GtkAtSpiContext * self)547 activate_secondary_icon (GtkAtSpiContext *self)
548 {
549 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
550 GtkEntry *entry = GTK_ENTRY (accessible);
551
552 return gtk_entry_activate_icon (entry, GTK_ENTRY_ICON_SECONDARY);
553 }
554
555 static void
entry_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)556 entry_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 entry_actions,
569 G_N_ELEMENTS (entry_actions));
570 }
571
572 static GVariant *
entry_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)573 entry_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 entry_actions,
585 G_N_ELEMENTS (entry_actions));
586 }
587
588 static const GDBusInterfaceVTable entry_action_vtable = {
589 entry_handle_method,
590 entry_handle_get_property,
591 NULL,
592 };
593
594 /* }}} */
595 /* {{{ GtkPasswordEntry */
596
597 static gboolean is_peek_enabled (GtkAtSpiContext *self);
598 static gboolean activate_peek (GtkAtSpiContext *self);
599
600 static 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
619 static gboolean
is_peek_enabled(GtkAtSpiContext * self)620 is_peek_enabled (GtkAtSpiContext *self)
621 {
622 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (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
631 static gboolean
activate_peek(GtkAtSpiContext * self)632 activate_peek (GtkAtSpiContext *self)
633 {
634 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
635 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (accessible);
636
637 gtk_password_entry_toggle_peek (entry);
638
639 return TRUE;
640 }
641
642 static void
password_entry_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)643 password_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 password_entry_actions,
656 G_N_ELEMENTS (password_entry_actions));
657 }
658
659 static GVariant *
password_entry_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)660 password_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 password_entry_actions,
672 G_N_ELEMENTS (password_entry_actions));
673 }
674
675 static 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
684 static gboolean is_clear_enabled (GtkAtSpiContext *self);
685 static gboolean activate_clear (GtkAtSpiContext *self);
686
687 static 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
706 static gboolean
is_clear_enabled(GtkAtSpiContext * self)707 is_clear_enabled (GtkAtSpiContext *self)
708 {
709 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (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
720 static gboolean
activate_clear(GtkAtSpiContext * self)721 activate_clear (GtkAtSpiContext *self)
722 {
723 GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
724
725 gtk_editable_set_text (GTK_EDITABLE (accessible), "");
726
727 return TRUE;
728 }
729
730 static void
search_entry_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)731 search_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 search_entry_actions,
744 G_N_ELEMENTS (search_entry_actions));
745 }
746
747 static GVariant *
search_entry_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)748 search_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 search_entry_actions,
760 G_N_ELEMENTS (search_entry_actions));
761 }
762
763 static const GDBusInterfaceVTable search_entry_action_vtable = {
764 search_entry_handle_method,
765 search_entry_handle_get_property,
766 NULL,
767 };
768 /* }}} */
769
770 static gboolean
is_valid_action(GtkActionMuxer * muxer,const char * action_name)771 is_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,
780 ¶m_type, NULL,
781 NULL, NULL))
782 return FALSE;
783
784 if (!enabled || param_type != NULL)
785 return FALSE;
786
787 return TRUE;
788 }
789
790 static void
add_muxer_actions(GtkActionMuxer * muxer,char ** actions,int n_actions,GVariantBuilder * builder)791 add_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, actions[i]))
799 continue;
800
801 g_variant_builder_add (builder, "(sss)",
802 actions[i],
803 actions[i],
804 "<VoidSymbol>");
805 }
806 }
807
808 static const char *
get_action_at_index(GtkActionMuxer * muxer,char ** actions,int n_actions,int pos)809 get_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, 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
830 static int
get_valid_actions(GtkActionMuxer * muxer,char ** actions,int n_actions)831 get_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, actions[i]))
840 continue;
841
842 n_enabled_actions += 1;
843 }
844
845 return n_enabled_actions;
846 }
847
848 static void
widget_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)849 widget_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 (GTK_AT_CONTEXT (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 (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 (actions) : 0;
874
875 /* XXX: We need more fields in the action API */
876 if (g_strcmp0 (method_name, "GetName") == 0 ||
877 g_strcmp0 (method_name, "GetLocalizedName") == 0 ||
878 g_strcmp0 (method_name, "GetDescription") == 0)
879 {
880 int action_idx;
881
882 g_variant_get (parameters, "(i)", &action_idx);
883
884 const char *action = get_action_at_index (muxer, actions, n_actions, action_idx);
885
886 if (action != NULL && gtk_widget_is_sensitive (widget))
887 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", action));
888 else
889 g_dbus_method_invocation_return_error (invocation,
890 G_IO_ERROR,
891 G_IO_ERROR_INVALID_ARGUMENT,
892 "No action with index %d",
893 action_idx);
894 }
895 else if (g_strcmp0 (method_name, "DoAction") == 0)
896 {
897 int action_idx;
898
899 g_variant_get (parameters, "(i)", &action_idx);
900
901 const char *action = get_action_at_index (muxer, actions, n_actions, action_idx);
902
903 if (action != NULL && gtk_widget_is_sensitive (widget))
904 {
905 gboolean res = gtk_widget_activate_action_variant (widget, action, NULL);
906
907 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", res));
908 }
909 else
910 {
911 g_dbus_method_invocation_return_error (invocation,
912 G_IO_ERROR,
913 G_IO_ERROR_INVALID_ARGUMENT,
914 "No action with index %d",
915 action_idx);
916 }
917 }
918 else if (g_strcmp0 (method_name, "GetKeyBinding") == 0)
919 {
920 int action_idx;
921
922 g_variant_get (parameters, "(i)", &action_idx);
923
924 const char *action = get_action_at_index (muxer, actions, n_actions, action_idx);
925
926 if (action != NULL && gtk_widget_is_sensitive (widget))
927 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "<VoidSymbol>"));
928 else
929 g_dbus_method_invocation_return_error (invocation,
930 G_IO_ERROR,
931 G_IO_ERROR_INVALID_ARGUMENT,
932 "No action with index %d",
933 action_idx);
934 }
935 else if (g_strcmp0 (method_name, "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);
941
942 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(sss))", &builder));
943 }
944
945 g_strfreev (actions);
946 }
947
948 static GVariant *
widget_handle_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)949 widget_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 (GTK_AT_CONTEXT (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 (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 (actions) : 0;
974
975 if (g_strcmp0 (property_name, "NActions") == 0)
976 res = g_variant_new ("i", get_valid_actions (muxer, actions, n_actions));
977 else
978 g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
979 "Unknown property '%s'", property_name);
980
981 return res;
982 }
983
984 static const GDBusInterfaceVTable widget_action_vtable = {
985 widget_handle_method,
986 widget_handle_get_property,
987 NULL,
988 };
989
990 const GDBusInterfaceVTable *
gtk_atspi_get_action_vtable(GtkAccessible * accessible)991 gtk_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