87327938298684d2ab2beac23a88b6dadfed72e1
[tilda-gobject.git] / tilda-config.c
1 #include "debug.h"
2 #include "translation.h"
3 #include "tilda-config.h"
4
5 #include "tilda.h"
6 #include "tilda-types.h"
7 #include "tilda-window.h"
8 #include "tilda-terminal.h"
9 #include <glib-object.h>
10 #include <stdlib.h>
11
12 GKeyFile *config_defaults = NULL;
13 GKeyFile *config_userprefs = NULL;
14
15 /*
16  * TODO:
17  *
18  * Add some capability to version the config file. Maybe a [meta] section.
19  *
20  * Make it possible to get the version number without firing up the config system
21  * as a whole. Or maybe just add the migration here, in this file, upon startup.
22  *
23  * The whole idea of this is that each of the objects that accesses the config
24  * system implements their own lookup engine.
25  */
26
27 /*
28  * The main idea behind this configuration system is that each of the objects
29  * that accesses the config system implements their own lookup scheme. Since
30  * both TildaWindow and TildaTerminal need to lookup things different ways,
31  * I thought this would be the most logical solution.
32  */
33
34 /* NOTE NOTE NOTE:
35  * This essentially works. You still need to feasibility-test it to make sure that
36  * everything pulls out of the gkeyfile ok. And you need to add the lookup code.
37  *
38  * Other than that, this is a mostly generic version :) !!!
39  */
40
41 static gboolean
42 tilda_config_parse_integer (GKeyFile    *keyfile,
43                                                         const gchar *group_name,
44                                                         const gchar *key,
45                                                         GValue      *value,
46                                                         GError     **user_error)
47 {
48         debug_enter  ();
49         debug_assert (G_IS_VALUE(value));
50
51         GError *error = NULL;
52         gint ret;
53
54         /* Try to get the value */
55         ret = g_key_file_get_integer (keyfile, group_name, key, &error);
56
57         /* Check for error */
58         if (error)
59         {
60                 g_propagate_error (user_error, error);
61                 return FALSE;
62         }
63
64         /* We successfully parsed the int, so set it in the GValue */
65         g_value_set_int (value, ret);
66
67         return TRUE;
68 }
69
70 static gboolean
71 tilda_config_parse_boolean (GKeyFile    *keyfile,
72                                                         const gchar *group_name,
73                                                         const gchar *key,
74                                                         GValue      *value,
75                                                         GError     **user_error)
76 {
77         debug_enter  ();
78         debug_assert (G_IS_VALUE(value));
79
80         GError *error = NULL;
81         gboolean ret;
82
83         /* Try to get the value */
84         ret = g_key_file_get_boolean (keyfile, group_name, key, &error);
85
86         /* Check for error */
87         if (error)
88         {
89                 g_propagate_error (user_error, error);
90                 return FALSE;
91         }
92
93         /* Success */
94         g_value_set_boolean (value, ret);
95
96         return TRUE;
97 }
98
99 static gboolean
100 tilda_config_parse_string (GKeyFile    *keyfile,
101                                                    const gchar *group_name,
102                                                    const gchar *key,
103                                                    GValue      *value,
104                                                    GError **user_error)
105 {
106         debug_enter  ();
107         debug_assert (G_IS_VALUE(value));
108
109         GError *error = NULL;
110         gchar *ret;
111
112         /* Try to get the value */
113         ret = g_key_file_get_string (keyfile, group_name, key, &error);
114
115         /* Check for error */
116         if (error)
117         {
118                 g_propagate_error (user_error, error);
119                 return FALSE;
120         }
121
122         /* Success */
123         g_value_set_string (value, ret);
124
125         return TRUE;
126 }
127
128 #define key_is(STR) (g_ascii_strcasecmp(key,(STR)) == 0)
129
130 static gboolean
131 tilda_config_parse_enum (GKeyFile    *keyfile,
132                                                  const gchar *group_name,
133                                                  const gchar *key,
134                                                  GValue      *value,
135                                                  GError     **user_error)
136 {
137         gchar *ret;
138         GError *error = NULL;
139         GEnumClass *enum_class;
140         GEnumValue *enum_value;
141
142         if (key_is("backspace-binding") || key_is("delete-binding"))
143                 enum_class = g_type_class_peek (vte_terminal_erase_binding_get_type());
144         else if (key_is("dynamic-title"))
145                 enum_class = g_type_class_peek (tilda_dynamic_title_get_type());
146         else if (key_is("exit-action"))
147                 enum_class = g_type_class_peek (tilda_child_exit_action_get_type());
148         else if (key_is("scrollbar-position"))
149                 enum_class = g_type_class_peek (tilda_scrollbar_position_get_type());
150         else if (key_is("animation-orientation") || key_is("tab-position"))
151                 enum_class = g_type_class_peek (gtk_position_type_get_type());
152         else
153         {
154                 g_critical ("FIXME: developer error -- unknown enum key used: %s\n", key);
155                 exit (1);
156                 return FALSE;
157         }
158
159         /* Get the value from the config as a string */
160         ret = g_key_file_get_string (keyfile, group_name, key, &error);
161
162         if (error)
163         {
164                 g_propagate_error (user_error, error);
165                 return FALSE;
166         }
167
168         enum_value = g_enum_get_value_by_nick (enum_class, ret);
169
170         if (!enum_value)
171         {
172                 /* This is exactly the same error that is returned by g_key_file_get_integer()
173                  * when it cannot correctly parse the value. */
174                 g_set_error (user_error,
175                                          G_KEY_FILE_ERROR,
176                                          G_KEY_FILE_ERROR_INVALID_VALUE,
177                                          _("Key file contains key '%s' in group '%s' which has value that cannot be interpreted."), key, group_name);
178                 return FALSE;
179         }
180
181         /* All was successful, let's set it */
182         g_value_set_enum (value, enum_value->value);
183         return TRUE;
184 }
185
186 gboolean
187 tilda_window_set_property_from_config (TildaWindow *self, const gchar *property)
188 {
189         debug_enter  ();
190         debug_assert (TILDA_IS_WINDOW(self));
191
192         gchar *group_name;
193         GError *error = NULL;
194         gboolean ret;
195
196         GParamSpec *pspec;
197         GValue *value = g_malloc0(sizeof(GValue));
198         gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
199
200         /* Get the pspec for this property */
201         pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
202
203         /* Make sure that this property exists */
204         if (pspec == NULL)
205         {
206                 g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
207                 exit (1);
208         }
209
210         /* Initialize the GValue that is going to hold the returned value */
211         g_value_init (value, pspec->value_type);
212
213         /* Set the correct function to do the parsing */
214         if      (g_type_is_a (pspec->value_type, G_TYPE_INT))
215                 parse_func = tilda_config_parse_integer;
216         else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
217                 parse_func = tilda_config_parse_boolean;
218         else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
219                 parse_func = tilda_config_parse_string;
220         else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
221                 parse_func = tilda_config_parse_enum;
222         else
223         {
224                 g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
225                 exit(1);
226         }
227
228         /* Do the [Window] lookup */
229         group_name = g_strdup_printf ("Window%d", self->number);
230         ret = parse_func (config_userprefs, group_name, property, value, &error);
231         g_free (group_name);
232
233         if (error)
234         {
235                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
236                         g_warning (error->message);
237
238                 g_clear_error (&error);
239         }
240         else
241                 goto success;
242
243         /* Do the [Global] lookup */
244         ret = parse_func (config_userprefs, "Global", property, value, &error);
245
246         if (error)
247         {
248                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
249                         g_warning (error->message);
250
251                 g_clear_error (&error);
252         }
253         else
254                 goto success;
255
256         /* Do the [window-defaults] lookup */
257         ret = parse_func (config_defaults, "window-defaults", property, value, &error);
258
259         if (error)
260         {
261                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
262                         g_warning (error->message);
263
264                 g_clear_error (&error);
265
266                 /* This is somewhat of a nasty hack, but I really do want to just set the property to
267                  * NULL if there is no default, but ONLY for strings. */
268                 if (parse_func == tilda_config_parse_string)
269                 {
270                         g_value_set_string (value, NULL);
271                         goto success;
272                 }
273         }
274         else
275                 goto success;
276
277 //failure:
278         g_critical (_("Unable to find a value for window property: %s\n"), property);
279         return FALSE;
280
281 success:
282         g_object_set_property (G_OBJECT(self), property, value);
283         g_value_unset (value);
284         g_free (value);
285         return TRUE;
286 }
287
288 gboolean
289 tilda_terminal_set_property_from_config (TildaTerminal *self, const gchar *property)
290 {
291         debug_enter  ();
292         debug_assert (TILDA_IS_TERMINAL(self));
293
294         TildaWindow *parent_window = TILDA_WINDOW(self->parent_window);
295         gchar *group_name;
296         GError *error = NULL;
297         gboolean ret;
298
299         GParamSpec *pspec;
300         GValue *value = g_malloc0(sizeof(GValue));
301         gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
302
303         /* Get the pspec for this property */
304         pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
305
306         /* Make sure that this property exists */
307         if (pspec == NULL)
308         {
309                 g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
310                 exit (1);
311         }
312
313         /* Initialize the GValue that is going to hold the returned value */
314         g_value_init (value, pspec->value_type);
315
316         /* Set the correct function to do the parsing */
317         if      (g_type_is_a (pspec->value_type, G_TYPE_INT))
318                 parse_func = tilda_config_parse_integer;
319         else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
320                 parse_func = tilda_config_parse_boolean;
321         else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
322                 parse_func = tilda_config_parse_string;
323         else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
324                 parse_func = tilda_config_parse_enum;
325         else
326         {
327                 g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
328                 exit(1);
329         }
330
331         /* Do the [Window/Terminal] lookup */
332         group_name = g_strdup_printf ("Window%d/Terminal%d", parent_window->number, self->number);
333         ret = parse_func (config_userprefs, group_name, property, value, &error);
334         g_free (group_name);
335
336         if (error)
337         {
338                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
339                         g_warning (error->message);
340
341                 g_clear_error (&error);
342         }
343         else
344                 goto success;
345
346         /* Do the [Window] lookup */
347         group_name = g_strdup_printf ("Window%d", parent_window->number);
348         ret = parse_func (config_userprefs, group_name, property, value, &error);
349         g_free (group_name);
350
351         if (error)
352         {
353                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
354                         g_warning (error->message);
355
356                 g_clear_error (&error);
357         }
358         else
359                 goto success;
360
361         /* Do the [Global] lookup */
362         ret = parse_func (config_userprefs, "Global", property, value, &error);
363
364         if (error)
365         {
366                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
367                         g_warning (error->message);
368
369                 g_clear_error (&error);
370         }
371         else
372                 goto success;
373
374         /* Do the [terminal-defaults] lookup */
375         ret = parse_func (config_defaults, "terminal-defaults", property, value, &error);
376
377         if (error)
378         {
379                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
380                         g_warning (error->message);
381
382                 g_clear_error (&error);
383
384                 /* This is somewhat of a nasty hack, but I really do want to just set the property to
385                  * NULL if there is no default, but ONLY for strings. */
386                 if (parse_func == tilda_config_parse_string)
387                 {
388                         g_value_set_string (value, NULL);
389                         goto success;
390                 }
391         }
392         else
393                 goto success;
394
395 //failure:
396         g_critical (_("Unable to find a value for terminal property: %s\n"), property);
397         return FALSE;
398
399 success:
400         g_object_set_property (G_OBJECT(self), property, value);
401         g_value_unset (value);
402         g_free (value);
403         return TRUE;
404 }
405
406 gboolean
407 tilda_config_init (const gchar *userprefs_filename)
408 {
409         gchar *defaults_filename;
410         GError *error = NULL;
411
412         /* Call g_type_class_ref() on all enum types that are being used */
413         g_type_class_ref (vte_terminal_erase_binding_get_type());
414         g_type_class_ref (tilda_dynamic_title_get_type());
415         g_type_class_ref (tilda_child_exit_action_get_type());
416         g_type_class_ref (tilda_scrollbar_position_get_type());
417         g_type_class_ref (gtk_position_type_get_type());
418
419         /* Create the defaults file's path */
420         defaults_filename = g_build_filename ("share-tilda.conf", NULL); // FIXME: use /usr/share/tilda
421
422         /* Create the keyfiles */
423         config_defaults = g_key_file_new ();
424         config_userprefs = g_key_file_new ();
425
426         /* Check if the defaults exist, and load them */
427         if (!g_file_test (defaults_filename, G_FILE_TEST_EXISTS))
428         {
429                 g_critical (_("No configuration defaults file found. Tilda may not work.\n"));
430         }
431         else
432         {
433                 if (!g_key_file_load_from_file (config_defaults, defaults_filename, G_KEY_FILE_NONE, &error))
434                 {
435                         g_critical (_("Error reading configuration defaults: %s\n"), error->message);
436                         g_clear_error (&error);
437                 }
438         }
439
440         /* Check if the user's config exists, and load it */
441         if (!g_file_test (userprefs_filename, G_FILE_TEST_EXISTS))
442         {
443                 g_warning (_("No user configuration file found, using defaults\n"));
444         }
445         else
446         {
447                 if (!g_key_file_load_from_file (config_userprefs, userprefs_filename, G_KEY_FILE_NONE, &error))
448                 {
449                         g_warning (_("Error reading user configuration: %s\n"), error->message);
450                         g_clear_error (&error);
451                 }
452         }
453
454         /* This is just here for the future. Currently, it is acceptable to run without
455          * a configuration, though Tilda may not work very well. */
456         return TRUE;
457 }
458
459 /**
460  * Since we DO NOT allow writing to the configuration file from a Tilda
461  * process (only from the wizard), this does nothing more than free the
462  * defaults hashtable, and free the opened keyfile.
463  */
464 gboolean
465 tilda_config_free ()
466 {
467         /* Since we never write the config file from within Tilda,
468          * we can just close the files. */
469         g_key_file_free (config_defaults);
470         g_key_file_free (config_userprefs);
471
472         config_defaults = NULL;
473         config_userprefs = NULL;
474
475         return TRUE;
476 }
477
478 /* vim: set ts=4 sts=4 sw=4 noet tw=112: */