2 * This file is part of GtkHotkey.
3 * Copyright Mikkel Kamstrup Erlandsen, March, 2008
5 * GtkHotkey is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * GtkHotkey 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
13 * GNU Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with GtkHotkey. If not, see <http://www.gnu.org/licenses/>.
19 #include "gtk-hotkey-key-file-registry.h"
20 #include "gtk-hotkey-utils.h"
23 #include <gio/gdesktopappinfo.h>
26 GTK_HOTKEY_KEY_FILE_REGISTRY_DUMMY_PROPERTY
29 /* Beware - the lengths of these strings are hardcoded throughout
30 * this file (sorry) */
31 #define HOTKEY_HOME "~/.config/hotkeys"
32 #define HOTKEY_FILE_EXT ".hotkeys"
33 #define HOTKEY_GROUP "hotkey:"
35 static GtkHotkeyInfo* gtk_hotkey_key_file_registry_real_get_hotkey (GtkHotkeyRegistry *base,
40 static GList* gtk_hotkey_key_file_registry_real_get_application_hotkeys
41 (GtkHotkeyRegistry *base,
45 static GList* gtk_hotkey_key_file_registry_real_get_all_hotkeys
46 (GtkHotkeyRegistry *base);
48 static gboolean gtk_hotkey_key_file_registry_real_store_hotkey(GtkHotkeyRegistry *base,
52 static gboolean gtk_hotkey_key_file_registry_real_delete_hotkey
53 (GtkHotkeyRegistry *base,
58 static gboolean gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry *base,
62 static GFile* get_hotkey_home (void);
64 static GFile* get_hotkey_file (const gchar *app_id);
66 static GKeyFile* get_hotkey_key_file (const gchar *app_id,
69 static GtkHotkeyInfo* get_hotkey_info_from_key_file (GKeyFile *keyfile,
74 static GList* get_all_hotkey_infos_from_key_file (GKeyFile *keyfile,
77 static gpointer gtk_hotkey_key_file_registry_parent_class = NULL;
82 gtk_hotkey_key_file_registry_real_get_hotkey (GtkHotkeyRegistry *base,
87 GKeyFile *keyfile = NULL;
88 GtkHotkeyInfo *info = NULL;
90 g_return_val_if_fail (GTK_HOTKEY_IS_KEY_FILE_REGISTRY(base), NULL);
91 g_return_val_if_fail (app_id != NULL, NULL);
92 g_return_val_if_fail (key_id != NULL, NULL);
94 keyfile = get_hotkey_key_file (app_id, error);
98 info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
101 if (keyfile) g_key_file_free (keyfile);
109 gtk_hotkey_key_file_registry_real_get_application_hotkeys (GtkHotkeyRegistry *base,
115 g_return_val_if_fail (app_id != NULL, NULL);
117 keyfile = get_hotkey_key_file (app_id, error);
120 return NULL; /* error is set by get_hotkey_key_file() */
122 return get_all_hotkey_infos_from_key_file (keyfile, app_id);
127 gtk_hotkey_key_file_registry_real_get_all_hotkeys (GtkHotkeyRegistry *base)
130 GFileEnumerator *dir;
131 GFileInfo *file_info;
133 GList *result = NULL;
135 home = get_hotkey_home ();
138 dir = g_file_enumerate_children (home, G_FILE_ATTRIBUTE_STANDARD_NAME,
141 gchar *path = g_file_get_path (home);
142 g_critical ("Failed to read hotkey home directory '%s': %s",
143 path, error->message);
145 g_error_free (error);
150 while ((file_info = g_file_enumerator_next_file (dir, NULL, &error)) != NULL) {
151 const gchar *filename;
156 filename = g_file_info_get_name(file_info);
158 if (g_str_has_suffix (filename, HOTKEY_FILE_EXT)) {
159 file = g_file_get_child (home, filename);
161 /* Extract app_id from file name */
162 app_id = g_string_new (filename);
163 g_string_erase (app_id, app_id->len - 8, 8);
165 /* Load all hotkeys from the file, and append it to
166 * the total result */
167 app_hotkeys = gtk_hotkey_registry_get_application_hotkeys (base,
171 g_warning ("Failed to read hotkeys for application '%s': %s",
172 app_id->str, error->message);
173 g_error_free (error);
176 result = g_list_concat (result, app_hotkeys);
179 g_string_free (app_id, TRUE);
180 g_object_unref (file);
183 g_object_unref (file_info);
187 gchar *path = g_file_get_path (home);
188 g_warning ("Failed to read hotkey home directory '%s': %s",
189 path, error->message);
191 g_error_free (error);
195 g_object_unref (dir);
196 g_object_unref (home);
203 gtk_hotkey_key_file_registry_real_store_hotkey (GtkHotkeyRegistry *base,
210 gchar *file_path, *group;
213 g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
214 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
216 /* Make sure we have our root dir */
217 home = get_hotkey_home ();
218 if (!g_file_query_exists(home, NULL)) {
220 if (!g_file_make_directory (home, NULL, &tmp_error)) {
221 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
222 GTK_HOTKEY_REGISTRY_ERROR_IO,
223 "Failed to create hotkey configuration dir "
224 HOTKEY_HOME": %s", tmp_error->message);
225 g_error_free (tmp_error);
226 g_object_unref (home);
231 /* Now load any old contents of the keyfile */
232 file = get_hotkey_file (gtk_hotkey_info_get_application_id (info));
233 file_path = g_file_get_path (file);
234 keyfile = g_key_file_new ();
237 if (!g_key_file_load_from_file (keyfile, file_path, 0, &tmp_error)) {
238 if (tmp_error->code == G_KEY_FILE_ERROR_PARSE) {
239 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
240 GTK_HOTKEY_REGISTRY_ERROR_MALFORMED_MEDIUM,
241 "The file %s is not in a valid key-file format: %s",
242 file_path, tmp_error->message);
245 /* Ignore other errors */
246 g_error_free (tmp_error);
249 /* Prepare keyfile data */
250 group = g_strconcat (HOTKEY_GROUP, gtk_hotkey_info_get_key_id (info), NULL);
252 g_key_file_set_string (keyfile, group, "Owner",
253 gtk_hotkey_info_get_application_id (info));
254 g_key_file_set_string (keyfile, group, "Signature",
255 gtk_hotkey_info_get_signature (info));
257 if (gtk_hotkey_info_get_description (info))
258 g_key_file_set_string (keyfile, group, "Description",
259 gtk_hotkey_info_get_description (info));
261 if (gtk_hotkey_info_get_app_info (info)) {
262 GAppInfo *ai = gtk_hotkey_info_get_app_info (info);
263 g_key_file_set_string (keyfile, group, "AppInfo",
264 g_app_info_get_id (ai));
270 contents = g_key_file_to_data (keyfile, &size, &tmp_error);
272 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
273 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
274 "Failed to generate keyfile contents: %s",
279 /* Write the actual data */
280 g_file_set_contents (file_path, contents, size, &tmp_error);
282 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
283 GTK_HOTKEY_REGISTRY_ERROR_IO,
284 "Failed to write keyfile '%s': %s",
285 file_path, tmp_error->message);
290 if (tmp_error) g_error_free (tmp_error);
292 if (group) g_free (group);
293 g_key_file_free (keyfile);
294 g_object_unref (file);
295 g_object_unref (home);
300 g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
301 gtk_hotkey_registry_hotkey_stored (base, info);
306 gtk_hotkey_key_file_registry_real_delete_hotkey (GtkHotkeyRegistry *base,
311 GtkHotkeyInfo *info = NULL;
315 gboolean is_error = FALSE;
318 g_return_val_if_fail (app_id != NULL, FALSE);
319 g_return_val_if_fail (key_id != NULL, FALSE);
320 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
324 file = get_hotkey_file (app_id);
325 g_return_val_if_fail (G_IS_FILE(file), FALSE);
327 path = g_file_get_path (file);
328 keyfile = g_key_file_new ();
330 /* Load the old keyfile */
332 g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
334 if ((tmp_error->domain == G_FILE_ERROR &&
335 tmp_error->code == G_FILE_ERROR_NOENT) ||
336 (tmp_error->domain == G_KEY_FILE_ERROR &&
337 tmp_error->code == G_KEY_FILE_ERROR_NOT_FOUND))
338 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
339 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
340 "No such keyfile '%s'. Application '%s' has not "
341 "registered any hotkeys",
344 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
345 GTK_HOTKEY_REGISTRY_ERROR_IO,
346 "Failed to load keyfile '%s': %s",
347 app_id, tmp_error->message);
352 /* Get a ref to the GtkHotkeyInfo so that we can emit it with the
353 * hotkey-deleted signal */
355 info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
361 /* Remove the group for key_id */
362 group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
364 g_key_file_remove_group (keyfile, group, &tmp_error);
366 if (tmp_error->domain == G_KEY_FILE_ERROR &&
367 tmp_error->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
368 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
369 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
370 "Application '%s' has not registered a hotkey with"
371 "id '%s'", app_id, key_id);
373 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
374 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
375 "Failed to delete hotkey '%s' from application %s: %s",
376 key_id, app_id, tmp_error->message);
381 /* Check if the keyfile is empty. If it is we delete it */
384 groups = g_key_file_get_groups (keyfile, &count);
388 g_file_delete (file, NULL, &tmp_error);
391 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
392 GTK_HOTKEY_REGISTRY_ERROR_IO,
393 "Failed to delete empty keyfile '%s': %s",
394 path, tmp_error->message);
397 /* File deleted, we should just clean up and exit */
401 /* Write new keyfile */
405 contents = g_key_file_to_data (keyfile, &size, &tmp_error);
407 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
408 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
409 "Failed to generate keyfile contents: %s",
416 g_file_set_contents (path, contents, size, &tmp_error);
418 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
419 GTK_HOTKEY_REGISTRY_ERROR_IO,
420 "Failed to write keyfile '%s': %s",
421 path, tmp_error->message);
427 if (tmp_error) g_error_free (tmp_error);
428 g_object_unref (file);
430 if (group) g_free (group);
431 g_key_file_free (keyfile);
436 gtk_hotkey_registry_hotkey_deleted (base, info);
437 g_object_unref (info);
442 gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry *base,
449 g_return_val_if_fail (app_id != NULL, FALSE);
450 g_return_val_if_fail (key_id != NULL, FALSE);
452 file = get_hotkey_file (app_id);
453 g_return_val_if_fail (G_IS_FILE(file), FALSE);
455 if (g_file_query_exists (file, NULL))
460 g_object_unref (file);
465 gtk_hotkey_key_file_registry_class_init (GtkHotkeyKeyFileRegistryClass *klass)
467 gtk_hotkey_key_file_registry_parent_class = g_type_class_peek_parent (klass);
468 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_hotkey = gtk_hotkey_key_file_registry_real_get_hotkey;
469 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_application_hotkeys = gtk_hotkey_key_file_registry_real_get_application_hotkeys;
470 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_all_hotkeys = gtk_hotkey_key_file_registry_real_get_all_hotkeys;
471 GTK_HOTKEY_REGISTRY_CLASS (klass)->store_hotkey = gtk_hotkey_key_file_registry_real_store_hotkey;
472 GTK_HOTKEY_REGISTRY_CLASS (klass)->delete_hotkey = gtk_hotkey_key_file_registry_real_delete_hotkey;
473 GTK_HOTKEY_REGISTRY_CLASS (klass)->has_hotkey = gtk_hotkey_key_file_registry_real_has_hotkey;
478 gtk_hotkey_key_file_registry_init (GtkHotkeyKeyFileRegistry *self)
484 gtk_hotkey_key_file_registry_finalize (GtkHotkeyKeyFileRegistry *self)
490 gtk_hotkey_key_file_registry_get_type (void)
492 static GType gtk_hotkey_key_file_registry_type_id = 0;
494 if (G_UNLIKELY (gtk_hotkey_key_file_registry_type_id == 0)) {
495 static const GTypeInfo g_define_type_info = {
496 sizeof (GtkHotkeyKeyFileRegistryClass),
497 (GBaseInitFunc) gtk_hotkey_key_file_registry_init,
498 (GBaseFinalizeFunc) gtk_hotkey_key_file_registry_finalize,
499 (GClassInitFunc) gtk_hotkey_key_file_registry_class_init,
500 (GClassFinalizeFunc) NULL,
502 sizeof (GtkHotkeyKeyFileRegistry),
504 (GInstanceInitFunc) gtk_hotkey_key_file_registry_init
507 gtk_hotkey_key_file_registry_type_id = g_type_register_static (GTK_HOTKEY_TYPE_STORAGE, "GtkHotkeyKeyFileRegistry", &g_define_type_info, 0);
509 return gtk_hotkey_key_file_registry_type_id;
513 get_hotkey_home (void)
517 home = g_file_parse_name (HOTKEY_HOME);
519 if (g_file_query_exists(home, NULL) &&
520 !gtk_hotkey_g_file_is_directory(home)) {
521 g_critical (HOTKEY_HOME" exists but is not a directory");
522 g_object_unref (home);
529 /* It is not guaranteed that the keyfile exists */
531 get_hotkey_file (const gchar *app_id)
536 g_return_val_if_fail (app_id != NULL, NULL);
538 home = get_hotkey_home();
539 g_return_val_if_fail (home != NULL, NULL);
541 filename = g_strconcat (app_id, HOTKEY_FILE_EXT, NULL);
542 file = g_file_get_child (home, filename);
544 g_object_unref (home);
550 get_hotkey_key_file (const gchar *app_id, GError **error)
554 GKeyFile *keyfile = NULL;
557 g_return_val_if_fail (app_id != NULL, NULL);
558 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
560 file = get_hotkey_file (app_id);
561 if (!g_file_query_exists (file, NULL)) {
562 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
563 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
564 "Application '%s' has not registered any hotkeys", app_id);
565 g_object_unref (file);
569 path = g_file_get_path (file);
570 keyfile = g_key_file_new ();
573 g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
575 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
576 GTK_HOTKEY_REGISTRY_ERROR_IO,
577 "Failed to load keyfile '%s': %s", path, tmp_error->message);
583 g_object_unref (file);
584 if (tmp_error) g_error_free (tmp_error);
587 g_key_file_free (keyfile);
594 static GtkHotkeyInfo*
595 get_hotkey_info_from_key_file (GKeyFile *keyfile,
600 GtkHotkeyInfo *info = NULL;
601 gchar *group, *description, *app_info_id, *signature;
602 GAppInfo *app_info = NULL;
604 g_return_val_if_fail (keyfile != NULL, NULL);
605 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
606 g_return_val_if_fail (app_id != NULL, NULL);
607 g_return_val_if_fail (key_id != NULL, NULL);
609 group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
610 description = g_key_file_get_string (keyfile, group, "Description", NULL);
611 app_info_id = g_key_file_get_string (keyfile, group, "AppInfo", NULL);
612 signature = g_key_file_get_string (keyfile, group, "Signature", NULL);
614 if (!g_key_file_has_group (keyfile, group)) {
615 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
616 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_KEY,
617 "Keyfile has no group "HOTKEY_GROUP"%s", key_id);
622 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
623 GTK_HOTKEY_REGISTRY_ERROR_BAD_SIGNATURE,
624 "No 'Signature' defined for hotkey '%s' for application '%s'",
630 app_info = G_APP_INFO(g_desktop_app_info_new (app_info_id));
631 if (!G_IS_APP_INFO(app_info)) {
632 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
633 GTK_HOTKEY_REGISTRY_ERROR_MISSING_APP,
634 "Keyfile refering to 'AppInfo = %s', but no application"
635 "by that id is registered on the system", app_info_id);
640 info = gtk_hotkey_info_new (app_id, key_id, signature, app_info);
642 gtk_hotkey_info_set_description (info, description);
646 if (signature) g_free (signature);
647 if (description) g_free (description);
648 if (app_info_id) g_free (app_info_id);
649 if (app_info) g_object_unref (app_info);
655 get_all_hotkey_infos_from_key_file (GKeyFile *keyfile,
659 GtkHotkeyInfo *hotkey;
666 g_return_val_if_fail (keyfile != NULL, NULL);
667 g_return_val_if_fail (app_id != NULL, NULL);
670 groups = g_key_file_get_groups (keyfile, &count);
673 for (i = 0; i < count; i++) {
675 key_id = g_string_new (group);
677 /* Ignore non hotkey groups */
678 if (!g_str_has_prefix (key_id->str, HOTKEY_GROUP)) {
679 g_warning ("Hotkey file for %s contains non 'hotkey:' group '%s'",
681 g_string_free (key_id, TRUE);
685 /* Strip 'hotkey:' prefix from key_id */
686 g_string_erase (key_id, 0, 7);
689 hotkey = get_hotkey_info_from_key_file (keyfile, app_id, key_id->str, &error);
691 g_warning ("Failed to read hotkey '%s' for application '%s': %s",
692 key_id->str, app_id, error->message);
693 g_error_free (error);
694 g_string_free (key_id, TRUE);
698 hotkeys = g_list_prepend (hotkeys, hotkey);
700 g_string_free (key_id, TRUE);