1 /*
2  * This file is part of GtkHotkey.
3  * Copyright Mikkel Kamstrup Erlandsen, March, 2008
4  *
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.
9  *
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.
14  *
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/>.
17  */
18 
19 #include "gtk-hotkey-key-file-registry.h"
20 #include "gtk-hotkey-utils.h"
21 
22 #include <gio/gio.h>
23 #include <gio/gdesktopappinfo.h>
24 
25 enum  {
26 	GTK_HOTKEY_KEY_FILE_REGISTRY_DUMMY_PROPERTY
27 };
28 
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:"
34 
35 static GtkHotkeyInfo*	gtk_hotkey_key_file_registry_real_get_hotkey	(GtkHotkeyRegistry	*base,
36 																	 const char			*app_id,
37 																	 const char			*key_id,
38 																	 GError				**error);
39 
40 static GList*			gtk_hotkey_key_file_registry_real_get_application_hotkeys
41 																	(GtkHotkeyRegistry	*base,
42 																	 const char			*app_id,
43 																	 GError				**error);
44 
45 static GList*			gtk_hotkey_key_file_registry_real_get_all_hotkeys
46 																	(GtkHotkeyRegistry	*base);
47 
48 static gboolean			gtk_hotkey_key_file_registry_real_store_hotkey(GtkHotkeyRegistry	*base,
49 																	 GtkHotkeyInfo		*info,
50 																	 GError				**error);
51 
52 static gboolean			gtk_hotkey_key_file_registry_real_delete_hotkey
53 																	(GtkHotkeyRegistry	*base,
54 																	 const gchar		*app_id,
55 																	 const gchar		*key_id,
56 																	 GError				**error);
57 
58 static gboolean			gtk_hotkey_key_file_registry_real_has_hotkey  (GtkHotkeyRegistry	*base,
59 																	 const gchar		*app_id,
60 																	 const gchar		*key_id);
61 
62 static GFile*			get_hotkey_home								(void);
63 
64 static GFile*			get_hotkey_file								(const gchar		*app_id);
65 
66 static GKeyFile*		get_hotkey_key_file							(const gchar		*app_id,
67 																	 GError				**error);
68 
69 static GtkHotkeyInfo*   get_hotkey_info_from_key_file				(GKeyFile			*keyfile,
70 																	 const gchar		*app_id,
71 																	 const gchar		*key_id,
72 																	 GError				**error);
73 
74 static GList*			get_all_hotkey_infos_from_key_file			(GKeyFile			*keyfile,
75 																	 const gchar		*app_id);
76 
77 static gpointer gtk_hotkey_key_file_registry_parent_class = NULL;
78 
79 
80 static
81 GtkHotkeyInfo*
gtk_hotkey_key_file_registry_real_get_hotkey(GtkHotkeyRegistry * base,const char * app_id,const char * key_id,GError ** error)82 gtk_hotkey_key_file_registry_real_get_hotkey (GtkHotkeyRegistry	*base,
83 											const char			*app_id,
84 											const char			*key_id,
85 											GError				**error)
86 {
87 	GKeyFile					*keyfile = NULL;
88 	GtkHotkeyInfo				*info = NULL;
89 
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);
93 
94 	keyfile = get_hotkey_key_file (app_id, error);
95 	if (keyfile == NULL)
96 		goto clean_up;
97 
98 	info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
99 
100 	clean_up:
101 		if (keyfile) g_key_file_free (keyfile);
102 
103 	return info;
104 
105 }
106 
107 
108 static GList*
gtk_hotkey_key_file_registry_real_get_application_hotkeys(GtkHotkeyRegistry * base,const char * app_id,GError ** error)109 gtk_hotkey_key_file_registry_real_get_application_hotkeys (GtkHotkeyRegistry	*base,
110 														 const char			*app_id,
111 														 GError				**error)
112 {
113 	GKeyFile					*keyfile;
114 
115 	g_return_val_if_fail (app_id != NULL, NULL);
116 
117 	keyfile = get_hotkey_key_file (app_id, error);
118 
119 	if (keyfile == NULL)
120 		return NULL; /* error is set by get_hotkey_key_file() */
121 
122 	return get_all_hotkey_infos_from_key_file (keyfile, app_id);
123 }
124 
125 
126 static GList*
gtk_hotkey_key_file_registry_real_get_all_hotkeys(GtkHotkeyRegistry * base)127 gtk_hotkey_key_file_registry_real_get_all_hotkeys (GtkHotkeyRegistry *base)
128 {
129 	GFile					*home;
130 	GFileEnumerator			*dir;
131 	GFileInfo				*file_info;
132 	GError					*error;
133 	GList					*result = NULL;
134 
135 	home = get_hotkey_home ();
136 
137 	error = NULL;
138 	dir = g_file_enumerate_children (home, G_FILE_ATTRIBUTE_STANDARD_NAME,
139 									 0, NULL, &error);
140 	if (error) {
141 		gchar *path = g_file_get_path (home);
142 		g_critical ("Failed to read hotkey home directory '%s': %s",
143 					path, error->message);
144 		g_free (path);
145 		g_error_free (error);
146 		return NULL;
147 	}
148 
149 	error = NULL;
150 	while ((file_info = g_file_enumerator_next_file (dir, NULL, &error)) != NULL) {
151 		const gchar *filename;
152 		GFile		*file;
153 		GString		*app_id;
154 		GList		*app_hotkeys;
155 
156 		filename = g_file_info_get_name(file_info);
157 
158 		if (g_str_has_suffix (filename, HOTKEY_FILE_EXT)) {
159 			file = g_file_get_child (home, filename);
160 
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);
164 
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,
168 																	  app_id->str,
169 																	  &error);
170 			if (error) {
171 				g_warning ("Failed to read hotkeys for application '%s': %s",
172 						   app_id->str, error->message);
173 				g_error_free (error);
174 				error = NULL;
175 			} else {
176 				result = g_list_concat (result, app_hotkeys);
177 			}
178 
179 			g_string_free (app_id, TRUE);
180 			g_object_unref (file);
181 		}
182 
183 		g_object_unref (file_info);
184 	}
185 
186 	if (error) {
187 		gchar *path = g_file_get_path (home);
188 		g_warning ("Failed to read hotkey home directory '%s': %s",
189 				   path, error->message);
190 		g_free (path);
191 		g_error_free (error);
192 	}
193 
194 
195 	g_object_unref (dir);
196 	g_object_unref (home);
197 
198 	return result;
199 }
200 
201 
202 static gboolean
gtk_hotkey_key_file_registry_real_store_hotkey(GtkHotkeyRegistry * base,GtkHotkeyInfo * info,GError ** error)203 gtk_hotkey_key_file_registry_real_store_hotkey (GtkHotkeyRegistry	*base,
204 											  GtkHotkeyInfo		*info,
205 											  GError			**error)
206 {
207 	GKeyFile					*keyfile;
208 	GFile						*file, *home;
209 	GError						*tmp_error;
210 	gchar						*file_path, *group = NULL;
211 
212 
213 	g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
214 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
215 
216 	/* Make sure we have our root dir */
217 	home = get_hotkey_home ();
218 	if (!g_file_query_exists(home, NULL)) {
219 		tmp_error = 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);
227 			return FALSE;
228 		}
229 	}
230 
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 ();
235 
236 	tmp_error = NULL;
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);
243 			goto clean_up;
244 		}
245 		/* Ignore other errors */
246 		g_error_free (tmp_error);
247 	}
248 
249 	/* Prepare keyfile data */
250 	group = g_strconcat (HOTKEY_GROUP, gtk_hotkey_info_get_key_id (info), NULL);
251 
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));
256 
257 	if (gtk_hotkey_info_get_description (info))
258 		g_key_file_set_string (keyfile, group, "Description",
259 							   gtk_hotkey_info_get_description (info));
260 
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));
265 	}
266 
267 	gsize size;
268 	gchar *contents;
269 	tmp_error = NULL;
270 	contents = g_key_file_to_data (keyfile, &size, &tmp_error);
271 	if (tmp_error) {
272 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
273 					 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
274 					 "Failed to generate keyfile contents: %s",
275 					 tmp_error->message);
276 		goto clean_up;
277 	}
278 
279 	/* Write the actual data */
280 	g_file_set_contents (file_path, contents, size, &tmp_error);
281 	if (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);
286 		goto clean_up;
287 	}
288 
289 	clean_up:
290 		if (tmp_error) g_error_free (tmp_error);
291 		g_free (file_path);
292 		if (group) g_free (group);
293 		g_key_file_free (keyfile);
294 		g_object_unref (file);
295 		g_object_unref (home);
296 
297 	if (*error)
298 		return FALSE;
299 
300 	g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
301 	gtk_hotkey_registry_hotkey_stored (base, info);
302 	return TRUE;
303 }
304 
305 static gboolean
gtk_hotkey_key_file_registry_real_delete_hotkey(GtkHotkeyRegistry * base,const gchar * app_id,const gchar * key_id,GError ** error)306 gtk_hotkey_key_file_registry_real_delete_hotkey (GtkHotkeyRegistry	*base,
307 											   const gchar		*app_id,
308 											   const gchar		*key_id,
309 											   GError			**error)
310 {
311 	GtkHotkeyInfo			*info = NULL;
312 	GFile					*file;
313 	GKeyFile				*keyfile;
314 	GError					*tmp_error;
315 	gboolean				is_error = FALSE;
316 	gchar					*path, *group;
317 
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);
321 
322 	group = NULL;
323 
324 	file = get_hotkey_file (app_id);
325 	g_return_val_if_fail (G_IS_FILE(file), FALSE);
326 
327 	path = g_file_get_path (file);
328 	keyfile = g_key_file_new ();
329 
330 	/* Load the old keyfile */
331 	tmp_error = NULL;
332 	g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
333 	if (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",
342 						 path, app_id);
343 		else
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);
348 		is_error = TRUE;
349 		goto clean_up;
350 	}
351 
352 	/* Get a ref to the GtkHotkeyInfo so that we can emit it with the
353 	 * hotkey-deleted signal */
354 	tmp_error = NULL;
355 	info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
356 	if (info == NULL) {
357 		is_error = TRUE;
358 		goto clean_up;
359 	}
360 
361 	/* Remove the group for key_id */
362 	group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
363 	tmp_error = NULL;
364 	g_key_file_remove_group (keyfile, group, &tmp_error);
365 	if (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);
372 		else
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);
377 		is_error = TRUE;
378 		goto clean_up;
379 	}
380 
381 	/* Check if the keyfile is empty. If it is we delete it */
382 	gsize count;
383 	GStrv groups;
384 	groups = g_key_file_get_groups (keyfile, &count);
385 	g_strfreev (groups);
386 	if (count == 0) {
387 		tmp_error = NULL;
388 		g_file_delete (file, NULL, &tmp_error);
389 
390 		if (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);
395 			is_error = TRUE;
396 		}
397 		/* File deleted, we should just clean up and exit */
398 		goto clean_up;
399 	}
400 
401 	/* Write new keyfile */
402 	gsize size;
403 	gchar *contents;
404 	tmp_error = NULL;
405 	contents = g_key_file_to_data (keyfile, &size, &tmp_error);
406 	if (tmp_error) {
407 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
408 					 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
409 					 "Failed to generate keyfile contents: %s",
410 					 tmp_error->message);
411 		is_error = TRUE;
412 		goto clean_up;
413 	}
414 
415 	tmp_error = NULL;
416 	g_file_set_contents (path, contents, size, &tmp_error);
417 	if (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);
422 		is_error = TRUE;
423 		goto clean_up;
424 	}
425 
426 	clean_up:
427 		if (tmp_error) g_error_free (tmp_error);
428 		g_object_unref (file);
429 		g_free (path);
430 		if (group) g_free (group);
431 		g_key_file_free (keyfile);
432 
433 	if (is_error)
434 		return FALSE;
435 
436 	gtk_hotkey_registry_hotkey_deleted (base, info);
437 	g_object_unref (info);
438 	return TRUE;
439 }
440 
441 static gboolean
gtk_hotkey_key_file_registry_real_has_hotkey(GtkHotkeyRegistry * base,const gchar * app_id,const gchar * key_id)442 gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry	*base,
443 											const gchar			*app_id,
444 											const gchar			*key_id)
445 {
446 	GFile					*file;
447 	gboolean				exists;
448 
449 	g_return_val_if_fail (app_id != NULL, FALSE);
450 	g_return_val_if_fail (key_id != NULL, FALSE);
451 
452 	file = get_hotkey_file (app_id);
453 	g_return_val_if_fail (G_IS_FILE(file), FALSE);
454 
455 	if (g_file_query_exists (file, NULL))
456 		exists = TRUE;
457 	else
458 		exists = FALSE;
459 
460 	g_object_unref (file);
461 	return exists;
462 }
463 
464 static void
gtk_hotkey_key_file_registry_class_init(GtkHotkeyKeyFileRegistryClass * klass)465 gtk_hotkey_key_file_registry_class_init (GtkHotkeyKeyFileRegistryClass *klass)
466 {
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;
474 }
475 
476 
477 static void
gtk_hotkey_key_file_registry_init(GtkHotkeyKeyFileRegistry * self)478 gtk_hotkey_key_file_registry_init (GtkHotkeyKeyFileRegistry *self)
479 {
480 
481 }
482 
483 static void
gtk_hotkey_key_file_registry_finalize(GtkHotkeyKeyFileRegistry * self)484 gtk_hotkey_key_file_registry_finalize (GtkHotkeyKeyFileRegistry *self)
485 {
486 
487 }
488 
489 GType
gtk_hotkey_key_file_registry_get_type(void)490 gtk_hotkey_key_file_registry_get_type (void)
491 {
492 	static GType gtk_hotkey_key_file_registry_type_id = 0;
493 
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,
501 			NULL,
502 			sizeof (GtkHotkeyKeyFileRegistry),
503 			0,
504 			(GInstanceInitFunc) gtk_hotkey_key_file_registry_init,
505 			(const GTypeValueTable *) NULL	/* value table */
506 		};
507 
508 		gtk_hotkey_key_file_registry_type_id = g_type_register_static (GTK_HOTKEY_TYPE_STORAGE, "GtkHotkeyKeyFileRegistry", &g_define_type_info, 0);
509 	}
510 	return gtk_hotkey_key_file_registry_type_id;
511 }
512 
513 static GFile*
get_hotkey_home(void)514 get_hotkey_home (void)
515 {
516 	GFile   *home;
517 
518 	home = g_file_parse_name (HOTKEY_HOME);
519 
520 	if (g_file_query_exists(home, NULL) &&
521 		!gtk_hotkey_g_file_is_directory(home)) {
522 		g_critical (HOTKEY_HOME" exists but is not a directory");
523 		g_object_unref (home);
524 		return NULL;
525 	}
526 
527 	return home;
528 }
529 
530 /* It is not guaranteed that the keyfile exists */
531 static GFile*
get_hotkey_file(const gchar * app_id)532 get_hotkey_file (const gchar *app_id)
533 {
534 	GFile   *home, *file;
535 	gchar   *filename;
536 
537 	g_return_val_if_fail (app_id != NULL, NULL);
538 
539 	home = get_hotkey_home();
540 	g_return_val_if_fail (home != NULL, NULL);
541 
542 	filename = g_strconcat (app_id, HOTKEY_FILE_EXT, NULL);
543 	file = g_file_get_child (home, filename);
544 
545 	g_object_unref (home);
546 	g_free (filename);
547 	return file;
548 }
549 
550 static GKeyFile*
get_hotkey_key_file(const gchar * app_id,GError ** error)551 get_hotkey_key_file (const gchar *app_id, GError **error)
552 {
553 	gchar		*path;
554 	GFile		*file;
555 	GKeyFile	*keyfile = NULL;
556 	GError		*tmp_error;
557 
558 	g_return_val_if_fail (app_id != NULL, NULL);
559 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
560 
561 	file = get_hotkey_file (app_id);
562 	if (!g_file_query_exists (file, NULL)) {
563 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
564 					 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
565 					 "Application '%s' has not registered any hotkeys", app_id);
566 		g_object_unref (file);
567 		return NULL;
568 	}
569 
570 	path = g_file_get_path (file);
571 	keyfile = g_key_file_new ();
572 
573 	tmp_error = NULL;
574 	g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
575 	if (tmp_error) {
576 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
577 					 GTK_HOTKEY_REGISTRY_ERROR_IO,
578 					 "Failed to load keyfile '%s': %s", path, tmp_error->message);
579 		goto clean_up;
580 	}
581 
582 	clean_up:
583 		g_free (path);
584 		g_object_unref (file);
585 		if (tmp_error) g_error_free (tmp_error);
586 
587 	if (*error) {
588 		g_key_file_free (keyfile);
589 		return NULL;
590 	}
591 
592 	return keyfile;
593 }
594 
595 static GtkHotkeyInfo*
get_hotkey_info_from_key_file(GKeyFile * keyfile,const gchar * app_id,const gchar * key_id,GError ** error)596 get_hotkey_info_from_key_file (GKeyFile	*keyfile,
597 							   const gchar *app_id,
598 							   const gchar *key_id,
599 							   GError **error)
600 {
601 	GtkHotkeyInfo   *info = NULL;
602 	gchar			*group, *description, *app_info_id, *signature;
603 	GAppInfo		*app_info = NULL;
604 
605 	g_return_val_if_fail (keyfile != NULL, NULL);
606 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
607 	g_return_val_if_fail (app_id != NULL, NULL);
608 	g_return_val_if_fail (key_id != NULL, NULL);
609 
610 	group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
611 	description = g_key_file_get_string (keyfile, group, "Description", NULL);
612 	app_info_id = g_key_file_get_string (keyfile, group, "AppInfo", NULL);
613 	signature = g_key_file_get_string (keyfile, group, "Signature", NULL);
614 
615 	if (!g_key_file_has_group (keyfile, group)) {
616 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
617 					 GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_KEY,
618 					 "Keyfile has no group "HOTKEY_GROUP"%s", key_id);
619 		goto clean_up;
620 	}
621 
622 	if (!signature) {
623 		g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
624 					 GTK_HOTKEY_REGISTRY_ERROR_BAD_SIGNATURE,
625 					 "No 'Signature' defined for hotkey '%s' for application '%s'",
626 					 key_id, app_id);
627 		goto clean_up;
628 	}
629 
630 	if (app_info_id) {
631 		app_info = G_APP_INFO(g_desktop_app_info_new (app_info_id));
632 		if (!G_IS_APP_INFO(app_info)) {
633 			g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
634 						 GTK_HOTKEY_REGISTRY_ERROR_MISSING_APP,
635 						 "Keyfile referring to 'AppInfo = %s', but no application"
636 						 "by that id is registered on the system", app_info_id);
637 			goto clean_up;
638 		}
639 	}
640 
641 	info = gtk_hotkey_info_new (app_id, key_id, signature, app_info);
642 	if (description)
643 		gtk_hotkey_info_set_description (info, description);
644 
645 	clean_up:
646 		g_free (group);
647 		if (signature) g_free (signature);
648 		if (description) g_free (description);
649 		if (app_info_id) g_free (app_info_id);
650 		if (app_info) g_object_unref (app_info);
651 
652 	return info;
653 }
654 
655 static GList*
get_all_hotkey_infos_from_key_file(GKeyFile * keyfile,const gchar * app_id)656 get_all_hotkey_infos_from_key_file (GKeyFile	*keyfile,
657 									const gchar	*app_id)
658 {
659 	GList			*hotkeys;
660 	GtkHotkeyInfo	*hotkey;
661 	GStrv			groups;
662 	gsize			count;
663 	gchar			*group;
664 	GString			*key_id;
665 	GError			*error;
666 
667 	g_return_val_if_fail (keyfile != NULL, NULL);
668 	g_return_val_if_fail (app_id != NULL, NULL);
669 
670 	hotkeys = NULL;
671 	groups = g_key_file_get_groups (keyfile, &count);
672 
673 	int i;
674 	for (i = 0; i < count; i++) {
675 		group = groups[i];
676 		key_id = g_string_new (group);
677 
678 		/* Ignore non hotkey groups */
679 		if (!g_str_has_prefix (key_id->str, HOTKEY_GROUP)) {
680 			g_warning ("Hotkey file for %s contains non 'hotkey:' group '%s'",
681 					   app_id, group);
682 			g_string_free (key_id, TRUE);
683 			continue;
684 		}
685 
686 		/* Strip 'hotkey:' prefix from key_id */
687 		g_string_erase (key_id, 0, 7);
688 
689 		error = NULL;
690 		hotkey = get_hotkey_info_from_key_file (keyfile, app_id, key_id->str, &error);
691 		if (error) {
692 			g_warning ("Failed to read hotkey '%s' for application '%s': %s",
693 					   key_id->str, app_id, error->message);
694 			g_error_free (error);
695 			g_string_free (key_id, TRUE);
696 			continue;
697 		}
698 
699 		hotkeys = g_list_prepend (hotkeys, hotkey);
700 
701 		g_string_free (key_id, TRUE);
702 	}
703 
704 	g_strfreev (groups);
705 	return hotkeys;
706 }
707