1 /*
2  * Copyright © 2013 Canonical Limited
3  * Copyright © 2016 Sébastien Wilmet
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the licence, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Ryan Lortie <desrt@desrt.ca>
19  *          Sébastien Wilmet <swilmet@gnome.org>
20  */
21 
22 #include "config.h"
23 #include "gtkapplicationaccelsprivate.h"
24 #include <string.h>
25 #include "gtkactionmuxer.h"
26 
27 typedef struct
28 {
29   guint           key;
30   GdkModifierType modifier;
31 } AccelKey;
32 
33 struct _GtkApplicationAccels
34 {
35   GObject parent;
36 
37   GHashTable *action_to_accels;
38   GHashTable *accel_to_actions;
39 };
40 
G_DEFINE_TYPE(GtkApplicationAccels,gtk_application_accels,G_TYPE_OBJECT)41 G_DEFINE_TYPE (GtkApplicationAccels, gtk_application_accels, G_TYPE_OBJECT)
42 
43 static AccelKey *
44 accel_key_copy (const AccelKey *source)
45 {
46   AccelKey *dest;
47 
48   dest = g_slice_new (AccelKey);
49   dest->key = source->key;
50   dest->modifier = source->modifier;
51 
52   return dest;
53 }
54 
55 static void
accel_key_free(gpointer data)56 accel_key_free (gpointer data)
57 {
58   AccelKey *key = data;
59 
60   g_slice_free (AccelKey, key);
61 }
62 
63 static guint
accel_key_hash(gconstpointer data)64 accel_key_hash (gconstpointer data)
65 {
66   const AccelKey *key = data;
67 
68   return key->key + (key->modifier << 16);
69 }
70 
71 static gboolean
accel_key_equal(gconstpointer a,gconstpointer b)72 accel_key_equal (gconstpointer a,
73                  gconstpointer b)
74 {
75   const AccelKey *ak = a;
76   const AccelKey *bk = b;
77 
78   return ak->key == bk->key && ak->modifier == bk->modifier;
79 }
80 
81 static void
add_entry(GtkApplicationAccels * accels,AccelKey * key,const gchar * action_and_target)82 add_entry (GtkApplicationAccels *accels,
83            AccelKey             *key,
84            const gchar          *action_and_target)
85 {
86   const gchar **old;
87   const gchar **new;
88   gint n;
89 
90   old = g_hash_table_lookup (accels->accel_to_actions, key);
91   if (old != NULL)
92     for (n = 0; old[n]; n++)  /* find the length */
93       ;
94   else
95     n = 0;
96 
97   new = g_new (const gchar *, n + 1 + 1);
98   memcpy (new, old, n * sizeof (const gchar *));
99   new[n] = action_and_target;
100   new[n + 1] = NULL;
101 
102   g_hash_table_insert (accels->accel_to_actions, accel_key_copy (key), new);
103 }
104 
105 static void
remove_entry(GtkApplicationAccels * accels,AccelKey * key,const gchar * action_and_target)106 remove_entry (GtkApplicationAccels *accels,
107               AccelKey             *key,
108               const gchar          *action_and_target)
109 {
110   const gchar **old;
111   const gchar **new;
112   gint n, i;
113 
114   /* if we can't find the entry then something has gone very wrong... */
115   old = g_hash_table_lookup (accels->accel_to_actions, key);
116   g_assert (old != NULL);
117 
118   for (n = 0; old[n]; n++)  /* find the length */
119     ;
120   g_assert_cmpint (n, >, 0);
121 
122   if (n == 1)
123     {
124       /* The simple case of removing the last action for an accel. */
125       g_assert_cmpstr (old[0], ==, action_and_target);
126       g_hash_table_remove (accels->accel_to_actions, key);
127       return;
128     }
129 
130   for (i = 0; i < n; i++)
131     if (g_str_equal (old[i], action_and_target))
132       break;
133 
134   /* We must have found it... */
135   g_assert_cmpint (i, <, n);
136 
137   new = g_new (const gchar *, n - 1 + 1);
138   memcpy (new, old, i * sizeof (const gchar *));
139   memcpy (new + i, old + i + 1, (n - (i + 1)) * sizeof (const gchar *));
140   new[n - 1] = NULL;
141 
142   g_hash_table_insert (accels->accel_to_actions, accel_key_copy (key), new);
143 }
144 
145 static void
gtk_application_accels_finalize(GObject * object)146 gtk_application_accels_finalize (GObject *object)
147 {
148   GtkApplicationAccels *accels = GTK_APPLICATION_ACCELS (object);
149 
150   g_hash_table_unref (accels->accel_to_actions);
151   g_hash_table_unref (accels->action_to_accels);
152 
153   G_OBJECT_CLASS (gtk_application_accels_parent_class)->finalize (object);
154 }
155 
156 static void
gtk_application_accels_class_init(GtkApplicationAccelsClass * klass)157 gtk_application_accels_class_init (GtkApplicationAccelsClass *klass)
158 {
159   GObjectClass *object_class = G_OBJECT_CLASS (klass);
160 
161   object_class->finalize = gtk_application_accels_finalize;
162 }
163 
164 static void
gtk_application_accels_init(GtkApplicationAccels * accels)165 gtk_application_accels_init (GtkApplicationAccels *accels)
166 {
167   accels->accel_to_actions = g_hash_table_new_full (accel_key_hash, accel_key_equal,
168                                                     accel_key_free, g_free);
169   accels->action_to_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
170 }
171 
172 GtkApplicationAccels *
gtk_application_accels_new(void)173 gtk_application_accels_new (void)
174 {
175   return g_object_new (GTK_TYPE_APPLICATION_ACCELS, NULL);
176 }
177 
178 void
gtk_application_accels_set_accels_for_action(GtkApplicationAccels * accels,const gchar * detailed_action_name,const gchar * const * accelerators)179 gtk_application_accels_set_accels_for_action (GtkApplicationAccels *accels,
180                                               const gchar          *detailed_action_name,
181                                               const gchar * const  *accelerators)
182 {
183   gchar *action_and_target;
184   AccelKey *keys, *old_keys;
185   gint i, n;
186 
187   action_and_target = gtk_normalise_detailed_action_name (detailed_action_name);
188 
189   n = accelerators ? g_strv_length ((gchar **) accelerators) : 0;
190 
191   if (n > 0)
192     {
193       keys = g_new0 (AccelKey, n + 1);
194 
195       for (i = 0; i < n; i++)
196         {
197           gtk_accelerator_parse (accelerators[i], &keys[i].key, &keys[i].modifier);
198 
199           if (keys[i].key == 0)
200             {
201               g_warning ("Unable to parse accelerator '%s': ignored request to install %d accelerators",
202                          accelerators[i], n);
203               g_free (action_and_target);
204               g_free (keys);
205               return;
206             }
207         }
208     }
209   else
210     keys = NULL;
211 
212   old_keys = g_hash_table_lookup (accels->action_to_accels, action_and_target);
213   if (old_keys)
214     {
215       /* We need to remove accel entries from existing keys */
216       for (i = 0; old_keys[i].key; i++)
217         remove_entry (accels, &old_keys[i], action_and_target);
218     }
219 
220   if (keys)
221     {
222       g_hash_table_replace (accels->action_to_accels, action_and_target, keys);
223 
224       for (i = 0; i < n; i++)
225         add_entry (accels, &keys[i], action_and_target);
226     }
227   else
228     {
229       g_hash_table_remove (accels->action_to_accels, action_and_target);
230       g_free (action_and_target);
231     }
232 }
233 
234 gchar **
gtk_application_accels_get_accels_for_action(GtkApplicationAccels * accels,const gchar * detailed_action_name)235 gtk_application_accels_get_accels_for_action (GtkApplicationAccels *accels,
236                                               const gchar          *detailed_action_name)
237 {
238   gchar *action_and_target;
239   AccelKey *keys;
240   gchar **result;
241   gint n, i = 0;
242 
243   action_and_target = gtk_normalise_detailed_action_name (detailed_action_name);
244 
245   keys = g_hash_table_lookup (accels->action_to_accels, action_and_target);
246   if (!keys)
247     {
248       g_free (action_and_target);
249       return g_new0 (gchar *, 0 + 1);
250     }
251 
252   for (n = 0; keys[n].key; n++)
253     ;
254 
255   result = g_new0 (gchar *, n + 1);
256 
257   for (i = 0; i < n; i++)
258     result[i] = gtk_accelerator_name (keys[i].key, keys[i].modifier);
259 
260   g_free (action_and_target);
261   return result;
262 }
263 
264 gchar **
gtk_application_accels_get_actions_for_accel(GtkApplicationAccels * accels,const gchar * accel)265 gtk_application_accels_get_actions_for_accel (GtkApplicationAccels *accels,
266                                               const gchar          *accel)
267 {
268   const gchar * const *actions_and_targets;
269   gchar **detailed_actions;
270   AccelKey accel_key;
271   guint i, n;
272 
273   gtk_accelerator_parse (accel, &accel_key.key, &accel_key.modifier);
274 
275   if (accel_key.key == 0)
276     {
277       g_critical ("invalid accelerator string '%s'", accel);
278       g_return_val_if_fail (accel_key.key != 0, NULL);
279     }
280 
281   actions_and_targets = g_hash_table_lookup (accels->accel_to_actions, &accel_key);
282   n = actions_and_targets ? g_strv_length ((gchar **) actions_and_targets) : 0;
283 
284   detailed_actions = g_new0 (gchar *, n + 1);
285 
286   for (i = 0; i < n; i++)
287     {
288       const gchar *action_and_target = actions_and_targets[i];
289       const gchar *sep;
290       GVariant *target;
291 
292       sep = strrchr (action_and_target, '|');
293       target = g_variant_parse (NULL, action_and_target, sep, NULL, NULL);
294       detailed_actions[i] = g_action_print_detailed_name (sep + 1, target);
295       if (target)
296         g_variant_unref (target);
297     }
298 
299   detailed_actions[n] = NULL;
300 
301   return detailed_actions;
302 }
303 
304 gchar **
gtk_application_accels_list_action_descriptions(GtkApplicationAccels * accels)305 gtk_application_accels_list_action_descriptions (GtkApplicationAccels *accels)
306 {
307   GHashTableIter iter;
308   gchar **result;
309   gint n, i = 0;
310   gpointer key;
311 
312   n = g_hash_table_size (accels->action_to_accels);
313   result = g_new (gchar *, n + 1);
314 
315   g_hash_table_iter_init (&iter, accels->action_to_accels);
316   while (g_hash_table_iter_next (&iter, &key, NULL))
317     {
318       const gchar *action_and_target = key;
319       const gchar *sep;
320       GVariant *target;
321 
322       sep = strrchr (action_and_target, '|');
323       target = g_variant_parse (NULL, action_and_target, sep, NULL, NULL);
324       result[i++] = g_action_print_detailed_name (sep + 1, target);
325       if (target)
326         g_variant_unref (target);
327     }
328   g_assert_cmpint (i, ==, n);
329   result[i] = NULL;
330 
331   return result;
332 }
333 
334 void
gtk_application_accels_foreach_key(GtkApplicationAccels * accels,GtkWindow * window,GtkWindowKeysForeachFunc callback,gpointer user_data)335 gtk_application_accels_foreach_key (GtkApplicationAccels     *accels,
336                                     GtkWindow                *window,
337                                     GtkWindowKeysForeachFunc  callback,
338                                     gpointer                  user_data)
339 {
340   GHashTableIter iter;
341   gpointer key;
342 
343   g_hash_table_iter_init (&iter, accels->accel_to_actions);
344   while (g_hash_table_iter_next (&iter, &key, NULL))
345     {
346       AccelKey *accel_key = key;
347 
348       (* callback) (window, accel_key->key, accel_key->modifier, FALSE, user_data);
349     }
350 }
351 
352 gboolean
gtk_application_accels_activate(GtkApplicationAccels * accels,GActionGroup * action_group,guint key,GdkModifierType modifier)353 gtk_application_accels_activate (GtkApplicationAccels *accels,
354                                  GActionGroup         *action_group,
355                                  guint                 key,
356                                  GdkModifierType       modifier)
357 {
358   AccelKey accel_key = { key, modifier };
359   const gchar **actions;
360   gint i;
361 
362   actions = g_hash_table_lookup (accels->accel_to_actions, &accel_key);
363 
364   if (actions == NULL)
365     return FALSE;
366 
367   /* We may have more than one action on a given accel.  This could be
368    * the case if we have different types of windows with different
369    * actions in each.
370    *
371    * Find the first one that will successfully activate and use it.
372    */
373   for (i = 0; actions[i]; i++)
374     {
375       const GVariantType *parameter_type;
376       const gchar *action_name;
377       const gchar *sep;
378       gboolean enabled;
379       GVariant *target;
380 
381       sep = strrchr (actions[i], '|');
382       action_name = sep + 1;
383 
384       if (!g_action_group_query_action (action_group, action_name, &enabled, &parameter_type, NULL, NULL, NULL))
385         continue;
386 
387       if (!enabled)
388         continue;
389 
390       /* We found an action with the correct name and it's enabled.
391        * This is the action that we are going to try to invoke.
392        *
393        * There is still the possibility that the target value doesn't
394        * match the expected parameter type.  In that case, we will print
395        * a warning.
396        *
397        * Note: we want to hold a ref on the target while we're invoking
398        * the action to prevent trouble if someone uninstalls the accel
399        * from the handler.  That's not a problem since we're parsing it.
400        */
401       if (actions[i] != sep) /* if it has a target... */
402         {
403           GError *error = NULL;
404 
405           if (parameter_type == NULL)
406             {
407               gchar *accel_str = gtk_accelerator_name (key, modifier);
408               g_warning ("Accelerator '%s' tries to invoke action '%s' with target, but action has no parameter",
409                          accel_str, action_name);
410               g_free (accel_str);
411               return TRUE;
412             }
413 
414           target = g_variant_parse (NULL, actions[i], sep, NULL, &error);
415           g_assert_no_error (error);
416           g_assert (target);
417 
418           if (!g_variant_is_of_type (target, parameter_type))
419             {
420               gchar *accel_str = gtk_accelerator_name (key, modifier);
421               gchar *typestr = g_variant_type_dup_string (parameter_type);
422               gchar *targetstr = g_variant_print (target, TRUE);
423               g_warning ("Accelerator '%s' tries to invoke action '%s' with target '%s',"
424                          " but action expects parameter with type '%s'", accel_str, action_name, targetstr, typestr);
425               g_variant_unref (target);
426               g_free (targetstr);
427               g_free (accel_str);
428               g_free (typestr);
429               return TRUE;
430             }
431         }
432       else
433         {
434           if (parameter_type != NULL)
435             {
436               gchar *accel_str = gtk_accelerator_name (key, modifier);
437               gchar *typestr = g_variant_type_dup_string (parameter_type);
438               g_warning ("Accelerator '%s' tries to invoke action '%s' without target,"
439                          " but action expects parameter with type '%s'", accel_str, action_name, typestr);
440               g_free (accel_str);
441               g_free (typestr);
442               return TRUE;
443             }
444 
445           target = NULL;
446         }
447 
448       g_action_group_activate_action (action_group, action_name, target);
449 
450       if (target)
451         g_variant_unref (target);
452 
453       return TRUE;
454     }
455 
456   return FALSE;
457 }
458