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 GtkHotkeyKeyFileRegistry *self;
88 GKeyFile *keyfile = NULL;
89 GtkHotkeyInfo *info = NULL;
91 g_return_val_if_fail (GTK_HOTKEY_IS_KEY_FILE_REGISTRY(base), NULL);
92 g_return_val_if_fail (app_id != NULL, NULL);
93 g_return_val_if_fail (key_id != NULL, NULL);
95 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
97 keyfile = get_hotkey_key_file (app_id, error);
101 info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
104 if (keyfile) g_key_file_free (keyfile);
112 gtk_hotkey_key_file_registry_real_get_application_hotkeys (GtkHotkeyRegistry *base,
116 GtkHotkeyKeyFileRegistry *self;
119 g_return_val_if_fail (app_id != NULL, NULL);
121 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
122 keyfile = get_hotkey_key_file (app_id, error);
125 return NULL; /* error is set by get_hotkey_key_file() */
127 return get_all_hotkey_infos_from_key_file (keyfile, app_id);
132 gtk_hotkey_key_file_registry_real_get_all_hotkeys (GtkHotkeyRegistry *base)
134 GtkHotkeyKeyFileRegistry *self;
136 GFileEnumerator *dir;
137 GFileInfo *file_info;
139 GList *result = NULL;
141 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
142 home = get_hotkey_home ();
145 dir = g_file_enumerate_children (home, G_FILE_ATTRIBUTE_STANDARD_NAME,
148 gchar *path = g_file_get_path (home);
149 g_critical ("Failed to read hotkey home directory '%s': %s",
150 path, error->message);
152 g_error_free (error);
157 while ((file_info = g_file_enumerator_next_file (dir, NULL, &error)) != NULL) {
158 const gchar *filename;
163 filename = g_file_info_get_name(file_info);
165 if (g_str_has_suffix (filename, HOTKEY_FILE_EXT)) {
166 file = g_file_get_child (home, filename);
168 /* Extract app_id from file name */
169 app_id = g_string_new (filename);
170 g_string_erase (app_id, app_id->len - 8, 8);
172 /* Load all hotkeys from the file, and append it to
173 * the total result */
174 app_hotkeys = gtk_hotkey_registry_get_application_hotkeys (base,
178 g_warning ("Failed to read hotkeys for application '%s': %s",
179 app_id->str, error->message);
180 g_error_free (error);
183 result = g_list_concat (result, app_hotkeys);
186 g_string_free (app_id, TRUE);
187 g_object_unref (file);
190 g_object_unref (file_info);
194 gchar *path = g_file_get_path (home);
195 g_warning ("Failed to read hotkey home directory '%s': %s",
196 path, error->message);
198 g_error_free (error);
202 g_object_unref (dir);
203 g_object_unref (home);
210 gtk_hotkey_key_file_registry_real_store_hotkey (GtkHotkeyRegistry *base,
214 GtkHotkeyKeyFileRegistry *self;
218 gchar *file_path, *group;
221 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
222 g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
223 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
225 /* Make sure we have our root dir */
226 home = get_hotkey_home ();
227 if (!g_file_query_exists(home, NULL)) {
229 if (!g_file_make_directory (home, NULL, &tmp_error)) {
230 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
231 GTK_HOTKEY_REGISTRY_ERROR_IO,
232 "Failed to create hotkey configuration dir "
233 HOTKEY_HOME": %s", tmp_error->message);
234 g_error_free (tmp_error);
235 g_object_unref (home);
240 /* Now load any old contents of the keyfile */
241 file = get_hotkey_file (gtk_hotkey_info_get_application_id (info));
242 file_path = g_file_get_path (file);
243 keyfile = g_key_file_new ();
246 if (!g_key_file_load_from_file (keyfile, file_path, 0, &tmp_error)) {
247 if (tmp_error->code == G_KEY_FILE_ERROR_PARSE) {
248 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
249 GTK_HOTKEY_REGISTRY_ERROR_MALFORMED_MEDIUM,
250 "The file %s is not in a valid key-file format: %s",
251 file_path, tmp_error->message);
254 /* Ignore other errors */
255 g_error_free (tmp_error);
258 /* Prepare keyfile data */
259 group = g_strconcat (HOTKEY_GROUP, gtk_hotkey_info_get_key_id (info), NULL);
261 g_key_file_set_string (keyfile, group, "Owner",
262 gtk_hotkey_info_get_application_id (info));
263 g_key_file_set_string (keyfile, group, "Signature",
264 gtk_hotkey_info_get_signature (info));
266 if (gtk_hotkey_info_get_description (info))
267 g_key_file_set_string (keyfile, group, "Description",
268 gtk_hotkey_info_get_description (info));
270 if (gtk_hotkey_info_get_app_info (info)) {
271 GAppInfo *ai = gtk_hotkey_info_get_app_info (info);
272 g_key_file_set_string (keyfile, group, "AppInfo",
273 g_app_info_get_id (ai));
279 contents = g_key_file_to_data (keyfile, &size, &tmp_error);
281 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
282 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
283 "Failed to generate keyfile contents: %s",
288 /* Write the actual data */
289 g_file_set_contents (file_path, contents, size, &tmp_error);
291 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
292 GTK_HOTKEY_REGISTRY_ERROR_IO,
293 "Failed to write keyfile '%s': %s",
294 file_path, tmp_error->message);
299 if (tmp_error) g_error_free (tmp_error);
301 if (group) g_free (group);
302 g_key_file_free (keyfile);
303 g_object_unref (file);
304 g_object_unref (home);
309 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
310 g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
311 gtk_hotkey_registry_hotkey_stored (base, info);
316 gtk_hotkey_key_file_registry_real_delete_hotkey (GtkHotkeyRegistry *base,
321 GtkHotkeyKeyFileRegistry *self;
322 GtkHotkeyInfo *info = NULL;
326 gboolean is_error = FALSE;
329 g_return_val_if_fail (app_id != NULL, FALSE);
330 g_return_val_if_fail (key_id != NULL, FALSE);
331 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
333 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
336 file = get_hotkey_file (app_id);
337 g_return_val_if_fail (G_IS_FILE(file), FALSE);
339 path = g_file_get_path (file);
340 keyfile = g_key_file_new ();
342 /* Load the old keyfile */
344 g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
346 if ((tmp_error->domain == G_FILE_ERROR &&
347 tmp_error->code == G_FILE_ERROR_NOENT) ||
348 (tmp_error->domain == G_KEY_FILE_ERROR &&
349 tmp_error->code == G_KEY_FILE_ERROR_NOT_FOUND))
350 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
351 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
352 "No such keyfile '%s'. Application '%s' has not "
353 "registered any hotkeys",
356 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
357 GTK_HOTKEY_REGISTRY_ERROR_IO,
358 "Failed to load keyfile '%s': %s",
359 app_id, tmp_error->message);
364 /* Get a ref to the GtkHotkeyInfo so that we can emit it with the
365 * hotkey-deleted signal */
367 info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
373 /* Remove the group for key_id */
374 group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
376 g_key_file_remove_group (keyfile, group, &tmp_error);
378 if (tmp_error->domain == G_KEY_FILE_ERROR &&
379 tmp_error->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
380 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
381 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
382 "Application '%s' has not registered a hotkey with"
383 "id '%s'", app_id, key_id);
385 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
386 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
387 "Failed to delete hotkey '%s' from application %s: %s",
388 key_id, app_id, tmp_error->message);
393 /* Check if the keyfile is empty. If it is we delete it */
396 groups = g_key_file_get_groups (keyfile, &count);
400 g_file_delete (file, NULL, &tmp_error);
403 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
404 GTK_HOTKEY_REGISTRY_ERROR_IO,
405 "Failed to delete empty keyfile '%s': %s",
406 path, tmp_error->message);
409 /* File deleted, we should just clean up and exit */
413 /* Write new keyfile */
417 contents = g_key_file_to_data (keyfile, &size, &tmp_error);
419 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
420 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
421 "Failed to generate keyfile contents: %s",
428 g_file_set_contents (path, contents, size, &tmp_error);
430 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
431 GTK_HOTKEY_REGISTRY_ERROR_IO,
432 "Failed to write keyfile '%s': %s",
433 path, tmp_error->message);
439 if (tmp_error) g_error_free (tmp_error);
440 g_object_unref (file);
442 if (group) g_free (group);
443 g_key_file_free (keyfile);
448 gtk_hotkey_registry_hotkey_deleted (base, info);
449 g_object_unref (info);
454 gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry *base,
458 GtkHotkeyKeyFileRegistry *self;
462 g_return_val_if_fail (app_id != NULL, FALSE);
463 g_return_val_if_fail (key_id != NULL, FALSE);
465 self = GTK_HOTKEY_KEY_FILE_REGISTRY (base);
467 file = get_hotkey_file (app_id);
468 g_return_val_if_fail (G_IS_FILE(file), FALSE);
470 if (g_file_query_exists (file, NULL))
475 g_object_unref (file);
480 gtk_hotkey_key_file_registry_class_init (GtkHotkeyKeyFileRegistryClass *klass)
482 gtk_hotkey_key_file_registry_parent_class = g_type_class_peek_parent (klass);
483 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_hotkey = gtk_hotkey_key_file_registry_real_get_hotkey;
484 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_application_hotkeys = gtk_hotkey_key_file_registry_real_get_application_hotkeys;
485 GTK_HOTKEY_REGISTRY_CLASS (klass)->get_all_hotkeys = gtk_hotkey_key_file_registry_real_get_all_hotkeys;
486 GTK_HOTKEY_REGISTRY_CLASS (klass)->store_hotkey = gtk_hotkey_key_file_registry_real_store_hotkey;
487 GTK_HOTKEY_REGISTRY_CLASS (klass)->delete_hotkey = gtk_hotkey_key_file_registry_real_delete_hotkey;
488 GTK_HOTKEY_REGISTRY_CLASS (klass)->has_hotkey = gtk_hotkey_key_file_registry_real_has_hotkey;
493 gtk_hotkey_key_file_registry_init (GtkHotkeyKeyFileRegistry *self)
499 gtk_hotkey_key_file_registry_finalize (GtkHotkeyKeyFileRegistry *self)
505 gtk_hotkey_key_file_registry_get_type (void)
507 static GType gtk_hotkey_key_file_registry_type_id = 0;
509 if (G_UNLIKELY (gtk_hotkey_key_file_registry_type_id == 0)) {
510 static const GTypeInfo g_define_type_info = {
511 sizeof (GtkHotkeyKeyFileRegistryClass),
512 (GBaseInitFunc) gtk_hotkey_key_file_registry_init,
513 (GBaseFinalizeFunc) gtk_hotkey_key_file_registry_finalize,
514 (GClassInitFunc) gtk_hotkey_key_file_registry_class_init,
515 (GClassFinalizeFunc) NULL,
517 sizeof (GtkHotkeyKeyFileRegistry),
519 (GInstanceInitFunc) gtk_hotkey_key_file_registry_init
522 gtk_hotkey_key_file_registry_type_id = g_type_register_static (GTK_HOTKEY_TYPE_STORAGE, "GtkHotkeyKeyFileRegistry", &g_define_type_info, 0);
524 return gtk_hotkey_key_file_registry_type_id;
528 get_hotkey_home (void)
532 home = g_file_parse_name (HOTKEY_HOME);
534 if (g_file_query_exists(home, NULL) &&
535 !gtk_hotkey_g_file_is_directory(home)) {
536 g_critical (HOTKEY_HOME" exists but is not a directory");
537 g_object_unref (home);
544 /* It is not guaranteed that the keyfile exists */
546 get_hotkey_file (const gchar *app_id)
551 g_return_val_if_fail (app_id != NULL, NULL);
553 home = get_hotkey_home();
554 g_return_val_if_fail (home != NULL, NULL);
556 filename = g_strconcat (app_id, HOTKEY_FILE_EXT, NULL);
557 file = g_file_get_child (home, filename);
559 g_object_unref (home);
565 get_hotkey_key_file (const gchar *app_id, GError **error)
569 GKeyFile *keyfile = NULL;
572 g_return_val_if_fail (app_id != NULL, NULL);
573 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
575 file = get_hotkey_file (app_id);
576 if (!g_file_query_exists (file, NULL)) {
577 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
578 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
579 "Application '%s' has not registered any hotkeys", app_id);
580 g_object_unref (file);
584 path = g_file_get_path (file);
585 keyfile = g_key_file_new ();
588 g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
590 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
591 GTK_HOTKEY_REGISTRY_ERROR_IO,
592 "Failed to load keyfile '%s': %s", path, tmp_error->message);
598 g_object_unref (file);
599 if (tmp_error) g_error_free (tmp_error);
602 g_key_file_free (keyfile);
609 static GtkHotkeyInfo*
610 get_hotkey_info_from_key_file (GKeyFile *keyfile,
615 GtkHotkeyInfo *info = NULL;
616 gchar *group, *description, *app_info_id, *signature;
617 GAppInfo *app_info = NULL;
619 g_return_val_if_fail (keyfile != NULL, NULL);
620 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
621 g_return_val_if_fail (app_id != NULL, NULL);
622 g_return_val_if_fail (key_id != NULL, NULL);
624 group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
625 description = g_key_file_get_string (keyfile, group, "Description", NULL);
626 app_info_id = g_key_file_get_string (keyfile, group, "AppInfo", NULL);
627 signature = g_key_file_get_string (keyfile, group, "Signature", NULL);
629 if (!g_key_file_has_group (keyfile, group)) {
630 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
631 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_KEY,
632 "Keyfile has no group "HOTKEY_GROUP"%s", key_id);
637 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
638 GTK_HOTKEY_REGISTRY_ERROR_BAD_SIGNATURE,
639 "No 'Signature' defined for hotkey '%s' for application '%s'",
645 app_info = G_APP_INFO(g_desktop_app_info_new (app_info_id));
646 if (!G_IS_APP_INFO(app_info)) {
647 g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
648 GTK_HOTKEY_REGISTRY_ERROR_MISSING_APP,
649 "Keyfile refering to 'AppInfo = %s', but no application"
650 "by that id is registered on the system", app_info_id);
655 info = gtk_hotkey_info_new (app_id, key_id, signature, app_info);
657 gtk_hotkey_info_set_description (info, description);
661 if (signature) g_free (signature);
662 if (description) g_free (description);
663 if (app_info_id) g_free (app_info_id);
664 if (app_info) g_object_unref (app_info);
670 get_all_hotkey_infos_from_key_file (GKeyFile *keyfile,
674 GtkHotkeyInfo *hotkey;
681 g_return_val_if_fail (keyfile != NULL, NULL);
682 g_return_val_if_fail (app_id != NULL, NULL);
685 groups = g_key_file_get_groups (keyfile, &count);
688 for (i = 0; i < count; i++) {
690 key_id = g_string_new (group);
692 /* Ignore non hotkey groups */
693 if (!g_str_has_prefix (key_id->str, HOTKEY_GROUP)) {
694 g_warning ("Hotkey file for %s contains non 'hotkey:' group '%s'",
696 g_string_free (key_id, TRUE);
700 /* Strip 'hotkey:' prefix from key_id */
701 g_string_erase (key_id, 0, 7);
704 hotkey = get_hotkey_info_from_key_file (keyfile, app_id, key_id->str, &error);
706 g_warning ("Failed to read hotkey '%s' for application '%s': %s",
707 key_id->str, app_id, error->message);
708 g_error_free (error);
709 g_string_free (key_id, TRUE);
713 hotkeys = g_list_prepend (hotkeys, hotkey);
715 g_string_free (key_id, TRUE);