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, ¶meter_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