1 /* vim: set ts=4 sts=4 sw=4 expandtab textwidth=112: */
2 /*
3  * This is free software; you can redistribute it and/or modify it under
4  * the terms of the GNU Library General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU Library General Public
14  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #define _POSIX_SOURCE /* feature test macro for fileno */
18 #define _XOPEN_SOURCE /* feature test macro for fsync */
19 
20 #include <tilda-config.h>
21 #include "debug.h"
22 
23 #include <confuse.h>
24 #include <glib/gi18n.h>
25 #include <stdio.h>
26 #include <stdlib.h> /* atoi */
27 #include <unistd.h> /* fsync */
28 
29 #include "configsys.h"
30 #include <vte/vte.h>
31 
32 static cfg_t *tc;
33 
34 /* CONFIGURATION OPTIONS
35  * In this array we set the default configuration options for the
36  * configuration file.
37  */
38 static cfg_opt_t config_opts[] = {
39 
40     /* strings */
41     CFG_STR("tilda_config_version", PACKAGE_VERSION, CFGF_NONE),
42     CFG_STR("command", "", CFGF_NONE),
43     CFG_STR("font", "Monospace 11", CFGF_NONE),
44     CFG_STR("key", NULL, CFGF_NONE),
45     CFG_STR("addtab_key", "<Shift><Control>t", CFGF_NONE),
46     CFG_STR("fullscreen_key", "F11", CFGF_NONE),
47     CFG_STR("toggle_transparency_key", "F12", CFGF_NONE),
48     CFG_STR("toggle_searchbar_key", "<Shift><Control>f", CFGF_NONE),
49     CFG_STR("closetab_key", "<Shift><Control>w", CFGF_NONE),
50     CFG_STR("nexttab_key", "<Control>Page_Down", CFGF_NONE),
51     CFG_STR("prevtab_key", "<Control>Page_Up", CFGF_NONE),
52     CFG_STR("movetableft_key", "<Shift><Control>Page_Up", CFGF_NONE),
53     CFG_STR("movetabright_key", "<Shift><Control>Page_Down", CFGF_NONE),
54     CFG_STR("gototab_1_key", "<Alt>1", CFGF_NONE),
55     CFG_STR("gototab_2_key", "<Alt>2", CFGF_NONE),
56     CFG_STR("gototab_3_key", "<Alt>3", CFGF_NONE),
57     CFG_STR("gototab_4_key", "<Alt>4", CFGF_NONE),
58     CFG_STR("gototab_5_key", "<Alt>5", CFGF_NONE),
59     CFG_STR("gototab_6_key", "<Alt>6", CFGF_NONE),
60     CFG_STR("gototab_7_key", "<Alt>7", CFGF_NONE),
61     CFG_STR("gototab_8_key", "<Alt>8", CFGF_NONE),
62     CFG_STR("gototab_9_key", "<Alt>9", CFGF_NONE),
63     CFG_STR("gototab_10_key", "<Alt>0", CFGF_NONE),
64     CFG_STR("copy_key", "<Shift><Control>c", CFGF_NONE),
65     CFG_STR("paste_key", "<Shift><Control>v", CFGF_NONE),
66     CFG_STR("quit_key", "<Shift><Control>q", CFGF_NONE),
67     CFG_STR("title", "Tilda", CFGF_NONE),
68     CFG_STR("background_color", "white", CFGF_NONE),
69     CFG_STR("working_dir", NULL, CFGF_NONE),
70     CFG_STR("web_browser", "xdg-open", CFGF_NONE),
71     CFG_STR("increase_font_size_key", "<Control>equal", CFGF_NONE),
72     CFG_STR("decrease_font_size_key", "<Control>minus", CFGF_NONE),
73     CFG_STR("normalize_font_size_key", "<Control>0", CFGF_NONE),
74     CFG_STR("show_on_monitor", "", CFGF_NONE),
75     CFG_STR("word_chars", DEFAULT_WORD_CHARS, CFGF_NONE),
76 
77     /* ints */
78     CFG_INT("lines", 5000, CFGF_NONE),
79     CFG_INT("x_pos", 0, CFGF_NONE),
80     CFG_INT("y_pos", 0, CFGF_NONE),
81     CFG_INT("tab_pos", 0, CFGF_NONE),
82     CFG_BOOL("expand_tabs", FALSE, CFGF_NONE),
83     CFG_BOOL("show_single_tab", FALSE, CFGF_NONE),
84     CFG_INT("backspace_key", 0, CFGF_NONE),
85     CFG_INT("delete_key", 1, CFGF_NONE),
86     CFG_INT("d_set_title", 3, CFGF_NONE),
87     CFG_INT("command_exit", 2, CFGF_NONE),
88     /* Timeout in milliseconds to spawn a shell or command */
89     CFG_INT("command_timeout_ms", 3000, CFGF_NONE),
90     CFG_INT("scheme", 3, CFGF_NONE),
91     CFG_INT("slide_sleep_usec", 20000, CFGF_NONE),
92     CFG_INT("animation_orientation", 0, CFGF_NONE),
93     CFG_INT("timer_resolution", 200, CFGF_NONE),
94     CFG_INT("auto_hide_time", 2000, CFGF_NONE),
95     CFG_INT("on_last_terminal_exit", 0, CFGF_NONE),
96     CFG_BOOL("prompt_on_exit", TRUE, CFGF_NONE),
97     CFG_INT("palette_scheme", 1, CFGF_NONE),
98     CFG_INT("non_focus_pull_up_behaviour", 0, CFGF_NONE),
99     CFG_INT("cursor_shape", 0, CFGF_NONE),
100 
101     /* The length of a tab title */
102     CFG_INT("title_max_length", 25, CFGF_NONE),
103 
104     /* int list */
105     CFG_INT_LIST("palette", "{\
106         0x2e2e, 0x3434, 0x3636,\
107         0xcccc, 0x0000, 0x0000,\
108         0x4e4e, 0x9a9a, 0x0606,\
109         0xc4c4, 0xa0a0, 0x0000,\
110         0x3434, 0x6565, 0xa4a4,\
111         0x7575, 0x5050, 0x7b7b,\
112         0x0606, 0x9820, 0x9a9a,\
113         0xd3d3, 0xd7d7, 0xcfcf,\
114         0x5555, 0x5757, 0x5353,\
115         0xefef, 0x2929, 0x2929,\
116         0x8a8a, 0xe2e2, 0x3434,\
117         0xfcfc, 0xe9e9, 0x4f4f,\
118         0x7272, 0x9f9f, 0xcfcf,\
119         0xadad, 0x7f7f, 0xa8a8,\
120         0x3434, 0xe2e2, 0xe2e2,\
121         0xeeee, 0xeeee, 0xecec}",
122         CFGF_NONE),
123 
124     /* guint16 */
125     CFG_INT("scrollbar_pos", 2, CFGF_NONE),
126     CFG_INT("back_red", 0x0000, CFGF_NONE),
127     CFG_INT("back_green", 0x0000, CFGF_NONE),
128     CFG_INT("back_blue", 0x0000, CFGF_NONE),
129     CFG_INT("text_red", 0xffff, CFGF_NONE),
130     CFG_INT("text_green", 0xffff, CFGF_NONE),
131     CFG_INT("text_blue", 0xffff, CFGF_NONE),
132     CFG_INT("cursor_red", 0xffff, CFGF_NONE),
133     CFG_INT("cursor_green", 0xffff, CFGF_NONE),
134     CFG_INT("cursor_blue", 0xffff, CFGF_NONE),
135 
136     /* floats, libconfuse has a bug with floats on non english systems,
137      * see: https://github.com/martinh/libconfuse/issues/119, so we
138      * need to emulate floats by scaling values to  a long value. */
139     CFG_INT ("width_percentage", G_MAXINT, CFGF_NONE),
140     CFG_INT ("height_percentage", G_MAXINT, CFGF_NONE),
141 
142     /* booleans */
143     CFG_BOOL("scroll_history_infinite", FALSE, CFGF_NONE),
144     CFG_BOOL("scroll_on_output", FALSE, CFGF_NONE),
145     CFG_BOOL("notebook_border", FALSE, CFGF_NONE),
146 
147     CFG_BOOL("scrollbar", FALSE, CFGF_NONE),
148     CFG_BOOL("grab_focus", TRUE, CFGF_NONE),
149     CFG_BOOL("above", TRUE, CFGF_NONE),
150     CFG_BOOL("notaskbar", TRUE, CFGF_NONE),
151     CFG_BOOL("blinks", TRUE, CFGF_NONE),
152     CFG_BOOL("scroll_on_key", TRUE, CFGF_NONE),
153     CFG_BOOL("bell", FALSE, CFGF_NONE),
154     CFG_BOOL("run_command", FALSE, CFGF_NONE),
155     CFG_BOOL("pinned", TRUE, CFGF_NONE),
156     CFG_BOOL("animation", FALSE, CFGF_NONE),
157     CFG_BOOL("hidden", FALSE, CFGF_NONE),
158     CFG_BOOL("set_as_desktop", FALSE, CFGF_NONE),
159     CFG_BOOL("centered_horizontally", FALSE, CFGF_NONE),
160     CFG_BOOL("centered_vertically", FALSE, CFGF_NONE),
161     CFG_BOOL("enable_transparency", FALSE, CFGF_NONE),
162     CFG_BOOL("auto_hide_on_focus_lost", FALSE, CFGF_NONE),
163     CFG_BOOL("auto_hide_on_mouse_leave", FALSE, CFGF_NONE),
164     /* Whether and how we limit the length of a tab title */
165     CFG_INT("title_behaviour", 2, CFGF_NONE),
166     /* Whether to set a new tab's working dir to the current tab's */
167     CFG_BOOL("inherit_working_dir", TRUE, CFGF_NONE),
168     CFG_BOOL("command_login_shell", FALSE, CFGF_NONE),
169     CFG_BOOL("start_fullscreen", FALSE, CFGF_NONE),
170     /* Whether closing a tab shows a confirmation dialog. */
171     CFG_BOOL("confirm_close_tab", TRUE, CFGF_NONE),
172 
173     CFG_INT("back_alpha", 0xffff, CFGF_NONE),
174 
175     /* Whether to show the full tab title as a tooltip */
176     CFG_BOOL("show_title_tooltip", FALSE, CFGF_NONE),
177 
178     /**
179      * Deprecated tilda options. These options be commented out in the
180      * configuration file and will not be initialized with default values
181      * if the option is missing in the config file.
182      **/
183     CFG_INT("max_width", 0, CFGF_NODEFAULT),
184     CFG_INT("max_height", 0, CFGF_NODEFAULT),
185 
186     CFG_STR("image", NULL, CFGF_NODEFAULT),
187 
188     CFG_INT("show_on_monitor_number", 0, CFGF_NODEFAULT),
189     CFG_INT("transparency", 0, CFGF_NODEFAULT),
190 
191     CFG_BOOL("bold", TRUE, CFGF_NODEFAULT),
192     CFG_BOOL("title_max_length_flag", FALSE, CFGF_NODEFAULT),
193     CFG_BOOL("antialias", TRUE, CFGF_NODEFAULT),
194     CFG_BOOL("double_buffer", FALSE, CFGF_NODEFAULT),
195     CFG_BOOL("scroll_background", FALSE, CFGF_NODEFAULT),
196     CFG_BOOL("use_image", FALSE, CFGF_NODEFAULT),
197 
198     CFG_INT("min_width", 0, CFGF_NODEFAULT),
199     CFG_INT("min_height", 0, CFGF_NODEFAULT),
200     /* End deprecated tilda options */
201 
202     CFG_END()
203 };
204 
205 /* Define these here, so that we can enable a non-threadsafe version
206  * without changing the code below. */
207 #ifndef NO_THREADSAFE
208     static GMutex mutex;
209     #define config_mutex_lock() g_mutex_lock (&mutex)
210     #define config_mutex_unlock() g_mutex_unlock (&mutex)
211 #else
212     #define config_mutex_lock()
213     #define config_mutex_unlock()
214 #endif
215 
216 #define CONFIG1_OLDER -1
217 #define CONFIGS_SAME   0
218 #define CONFIG1_NEWER  1
219 
220 static gboolean compare_config_versions (const gchar *config1, const gchar *config2) G_GNUC_UNUSED;
221 
222 static void invoke_deprecation_function(const gchar *const *deprecated_config_options,
223                                         guint size);
224 
225 static void remove_deprecated_config_options(const gchar *const *deprecated_config_options, guint size);
226 
227 /* Note: set config_file to NULL to just free the
228  * data structures, and not write out the state to
229  * a file. */
config_free(const gchar * config_file)230 gint config_free (const gchar *config_file)
231 {
232     gint ret = 0;
233 
234     if (config_file != NULL)
235         ret = config_write (config_file);
236 
237     cfg_free (tc);
238 
239     return ret;
240 }
241 
config_setint(const gchar * key,const glong val)242 gint config_setint (const gchar *key, const glong val)
243 {
244     config_mutex_lock ();
245     cfg_setint (tc, key, val);
246     config_mutex_unlock ();
247 
248     return 0;
249 }
250 
config_setnint(const gchar * key,const glong val,const guint idx)251 gint config_setnint(const gchar *key, const glong val, const guint idx)
252 {
253     config_mutex_lock ();
254     cfg_setnint (tc, key, val, idx);
255     config_mutex_unlock ();
256 
257     return 0;
258 }
259 
config_setdouble(const gchar * key,const gdouble val)260 gint config_setdouble (const gchar *key, const gdouble val) {
261     config_mutex_lock ();
262     cfg_setfloat (tc, key, val);
263     config_mutex_unlock ();
264 
265     return 0;
266 }
267 
config_setndouble(const gchar * key,const gdouble val,const guint idx)268 gint config_setndouble (const gchar *key, const gdouble val, const guint idx) {
269     config_mutex_lock ();
270     cfg_setnfloat (tc, key, val, idx);
271     config_mutex_unlock ();
272 
273     return 0;
274 }
275 
config_setstr(const gchar * key,const gchar * val)276 gint config_setstr (const gchar *key, const gchar *val)
277 {
278     config_mutex_lock ();
279     cfg_setstr (tc, key, val);
280     config_mutex_unlock ();
281 
282     return 0;
283 }
284 
config_setbool(const gchar * key,const gboolean val)285 gint config_setbool(const gchar *key, const gboolean val)
286 {
287     config_mutex_lock ();
288     cfg_setbool (tc, key, val);
289     config_mutex_unlock ();
290 
291     return 0;
292 }
293 
config_getint(const gchar * key)294 glong config_getint (const gchar *key)
295 {
296     glong temp;
297 
298     config_mutex_lock ();
299     temp = cfg_getint (tc, key);
300     config_mutex_unlock ();
301 
302     return temp;
303 }
304 
config_getnint(const gchar * key,const guint idx)305 glong config_getnint(const gchar *key, const guint idx)
306 {
307     glong temp;
308 
309     config_mutex_lock ();
310     temp = cfg_getnint (tc, key, idx);
311     config_mutex_unlock ();
312 
313     return temp;
314 }
315 
config_getdouble(const gchar * key)316 gdouble config_getdouble (const gchar* key) {
317     gdouble temp;
318 
319     config_mutex_lock ();
320     temp = cfg_getfloat (tc, key);
321     config_mutex_unlock ();
322 
323     return temp;
324 }
325 
config_getndouble(const gchar * key,const guint idx)326 gdouble config_getndouble (const gchar* key, const guint idx) {
327     gdouble temp;
328 
329     config_mutex_lock ();
330     temp = cfg_getnfloat (tc, key, idx);
331     config_mutex_unlock ();
332 
333     return temp;
334 }
335 
config_getstr(const gchar * key)336 gchar* config_getstr (const gchar *key)
337 {
338     gchar *temp;
339 
340     config_mutex_lock ();
341     temp = cfg_getstr (tc, key);
342     config_mutex_unlock ();
343 
344     return temp;
345 }
346 
config_getbool(const gchar * key)347 gboolean config_getbool(const gchar *key)
348 {
349     gboolean temp;
350 
351     config_mutex_lock ();
352     temp = cfg_getbool (tc, key);
353     config_mutex_unlock ();
354 
355     return temp;
356 }
357 
358 /* This will write out the current state of the config file to the disk.
359  * It's use is generally discouraged, since config_free() will also write
360  * out the configuration to disk. */
config_write(const gchar * config_file)361 gint config_write (const gchar *config_file)
362 {
363     DEBUG_FUNCTION ("config_write");
364     DEBUG_ASSERT (config_file != NULL);
365 
366     gint ret = 0;
367     FILE *fp;
368 
369     char *temp_config_file = g_strdup_printf ("%s.tmp", config_file);
370     fp = fopen(temp_config_file, "w");
371 
372     if (fp != NULL)
373     {
374         config_mutex_lock ();
375         cfg_print (tc, fp);
376         config_mutex_unlock ();
377 
378         if (fsync (fileno(fp)))
379         {
380             // Error occurred during sync
381             TILDA_PERROR ();
382             DEBUG_ERROR ("Unable to sync file");
383 
384             g_printerr (_("Unable to sync the config file to disk\n"));
385             ret = 2;
386         }
387 
388         if (fclose (fp))
389         {
390             // An error occurred
391             TILDA_PERROR ();
392             DEBUG_ERROR ("Unable to close config file");
393 
394             g_printerr (_("Unable to close the config file\n"));
395             ret = 3;
396         }
397         if (rename(temp_config_file, config_file)) {
398             TILDA_PERROR ();
399             DEBUG_ERROR ("Unable to rename temporary config file to final config file.");
400         }
401     }
402     else
403     {
404         TILDA_PERROR ();
405         DEBUG_ERROR ("Unable to write config file");
406 
407         g_printerr (_("Unable to write the config file to %s\n"), config_file);
408         ret = 4;
409     }
410 
411     return ret;
412 }
413 
414 /**
415  * Start up the configuration system, using the configuration file given
416  * to get the current values. If the configuration file given does not exist,
417  * go ahead and write out the default config to the file.
418  */
config_init(const gchar * config_file)419 gint config_init (const gchar *config_file)
420 {
421     DEBUG_FUNCTION ("config_init");
422 
423     gint ret = 0;
424 
425     // Can we use a more descriptive name than tc?
426     tc = cfg_init (config_opts, 0);
427 
428     if (g_file_test (config_file,
429         G_FILE_TEST_IS_REGULAR))
430     {
431         /* Read in the existing configuration options */
432         ret = cfg_parse (tc, config_file);
433 
434         if (ret == CFG_PARSE_ERROR) {
435             DEBUG_ERROR ("Problem parsing config");
436             return ret;
437         } else if (ret != CFG_SUCCESS) {
438             DEBUG_ERROR ("Problem parsing config.");
439             return ret;
440         }
441     }
442 
443     /* Deprecate old config settings.
444      * This is a lame work around until we get a permanent solution to
445      * libconfuse lacking for this functionality
446      */
447     const gchar *deprecated_tilda_config_options[] = {"show_on_monitor_number",
448                                                       "bold",
449                                                       "title_max_length_flag",
450                                                       "double_buffer",
451                                                       "antialias",
452                                                       "image",
453                                                       "transparency",
454                                                       "scroll_background",
455                                                       "use_image",
456                                                       "min_width",
457                                                       "min_height",
458                                                       "max_width",
459                                                       "max_height"
460     };
461 
462     invoke_deprecation_function (deprecated_tilda_config_options,
463                                  G_N_ELEMENTS(deprecated_tilda_config_options));
464 
465     remove_deprecated_config_options(deprecated_tilda_config_options,
466                                      G_N_ELEMENTS(deprecated_tilda_config_options));
467 
468     #ifndef NO_THREADSAFE
469         g_mutex_init(&mutex);
470     #endif
471 
472     return ret;
473 }
474 
config_get_configured_monitor()475 static GdkMonitor *config_get_configured_monitor ()
476 {
477     gint x_pos = (gint) config_getint ("x_pos");
478     gint y_pos = (gint) config_getint ("y_pos");
479 
480     GdkDisplay *display = gdk_display_get_default ();
481 
482     return gdk_display_get_monitor_at_point (display,
483                                              x_pos,
484                                              y_pos);
485 }
486 
config_get_configured_window_size(GdkRectangle * rectangle)487 void config_get_configured_window_size (GdkRectangle *rectangle)
488 {
489     gdouble relative_width = GLONG_TO_DOUBLE (config_getint ("width_percentage"));
490     gdouble relative_height = GLONG_TO_DOUBLE (config_getint ("height_percentage"));
491 
492     GdkMonitor *monitor = config_get_configured_monitor ();
493 
494     GdkRectangle workarea;
495     gdk_monitor_get_workarea (monitor, &workarea);
496 
497     rectangle->width = pixels_ratio_to_absolute (relative_width, workarea.width);
498     rectangle->height = pixels_ratio_to_absolute (relative_height, workarea.height);
499 }
500 
config_get_configured_percentage(gdouble * width_percentage,gdouble * height_percentage)501 static void config_get_configured_percentage (gdouble *width_percentage,
502                                               gdouble *height_percentage)
503 {
504     glong windowWidth = config_getint ("max_width");
505     glong windowHeight = config_getint ("max_height");
506 
507     GdkMonitor *monitor = config_get_configured_monitor ();
508 
509     GdkRectangle workarea;
510     gdk_monitor_get_workarea (monitor, &workarea);
511 
512     if (width_percentage) {
513         *width_percentage = pixels_absolute_to_ratio (workarea.width, windowWidth);
514     }
515 
516     if (height_percentage) {
517         *height_percentage = pixels_absolute_to_ratio (workarea.height, windowHeight);
518     }
519 }
520 
print_migration_info(const gchar * old_option_name,const gchar * new_option_name)521 static void print_migration_info (const gchar *old_option_name,
522                                   const gchar *new_option_name)
523 {
524     g_print ("Migrated deprecated value in option '%s' to '%s'.\n",
525              old_option_name, new_option_name);
526 }
527 
invoke_deprecation_function(const gchar * const * deprecated_config_options,guint size)528 void invoke_deprecation_function (const gchar *const *deprecated_config_options,
529                                   guint size)
530 {
531     for (guint i = 0; i < size; i++)
532     {
533         const char *const option_name = deprecated_config_options[i];
534 
535         /* This will still return the option even if its
536          * commented out in the config file, so we perform the extra check
537          * using `cfg_opt_size` below to determine if the option has a valid
538          * value. We do this to ensure that we only execute the migration
539          * code once. */
540         cfg_opt_t *option = cfg_getopt (tc, option_name);
541 
542         if (option == NULL || cfg_opt_size (option) == 0) {
543             continue;
544         }
545 
546         gdouble width_percentage;
547         gdouble height_percentage;
548 
549         config_get_configured_percentage (&width_percentage,
550                                           &height_percentage);
551 
552         if (strncmp(option_name, "max_width", sizeof("max_width")) == 0)
553         {
554             print_migration_info (option_name, "width_percentage");
555             config_setint ("width_percentage", GLONG_FROM_DOUBLE (width_percentage));
556         }
557         if (strncmp(option_name, "max_height", sizeof("max_height")) == 0)
558         {
559             print_migration_info (option_name, "height_percentage");
560             config_setint ("height_percentage", GLONG_FROM_DOUBLE (height_percentage));
561         }
562     }
563 }
564 
remove_deprecated_config_options(const gchar * const * deprecated_config_options,guint size)565 void remove_deprecated_config_options(const gchar *const *deprecated_config_options, guint size) {
566     cfg_opt_t *opt;
567     for (guint i =0; i < size; i++) {
568         opt = cfg_getopt(tc, deprecated_config_options[i]);
569         if (opt->nvalues != 0) {
570             g_info("'%s' is no longer a valid config option in the current version of Tilda and has been removed from the config file.", deprecated_config_options[i]);
571             cfg_free_value(opt);
572         }
573     }
574 }
575 
576 /*
577  * Compares two config versions together.
578  *
579  * Returns -1 if config1 is older than config2 (UPDATE REQUIRED)
580  * Returns  0 if config1 is equal to   config2 (NORMAL USAGE)
581  * Returns  1 if config1 is newer than config2 (DISABLE WRITE)
582  */
compare_config_versions(const gchar * config1,const gchar * config2)583 static gboolean compare_config_versions (const gchar *config1, const gchar *config2)
584 {
585     DEBUG_FUNCTION ("compare_config_versions");
586     DEBUG_ASSERT (config1 != NULL);
587     DEBUG_ASSERT (config2 != NULL);
588 
589     /*
590      * 1) Split apart both strings using the .'s
591      * 2) Compare the major-major version
592      * 3) Compare the major version
593      * 4) Compare the minor version
594      */
595 
596     gchar **config1_tokens;
597     gchar **config2_tokens;
598     gint  config1_version[3];
599     gint  config2_version[3];
600     gint  i;
601 
602     config1_tokens = g_strsplit (config1, ".", 3);
603     config2_tokens = g_strsplit (config2, ".", 3);
604 
605     for (i=0; i<3; i++)
606     {
607         config1_version[i] = atoi (config1_tokens[i]);
608         config2_version[i] = atoi (config2_tokens[i]);
609     }
610 
611     g_strfreev (config1_tokens);
612     g_strfreev (config2_tokens);
613 
614     /* We're done splitting things, so compare now */
615     for (i=0; i<3; i++)
616     {
617         if (config1_version[i] > config2_version[i])
618             return CONFIG1_NEWER;
619 
620         if (config1_version[i] < config2_version[i])
621             return CONFIG1_OLDER;
622     }
623 
624     return CONFIGS_SAME;
625 }
626