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                                       &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 
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