+#include "tilda-config.h"
+
+#include "tilda.h"
+#include "tilda-types.h"
#include "debug.h"
#include "translation.h"
-#include "tilda-config.h"
-GHashTable *config_defaults = NULL;
-GKeyFile *config_userprefs = NULL;
+#include <glib-object.h> /* for GType */
+#include <stdlib.h> /* for exit() */
+
+GKeyFile *config_defaults = NULL;
+GKeyFile *config_userprefs = NULL;
/*
* TODO:
- *
- * Add some capability to version the config file. Maybe a [meta] section.
- *
- * Make it possible to get the version number without firing up the config system
- * as a whole. Or maybe just add the migration here, in this file, upon startup.
- *
- * The whole idea of this is that each of the objects that accesses the config
- * system implements their own lookup engine.
+ * 1) Configuration forward-migration, automatically, on startup.
*/
-/*
- * The main idea behind this configuration system is that each of the objects
- * that accesses the config system implements their own lookup scheme. Since
- * both TildaWindow and TildaTerminal need to lookup things different ways,
- * I thought this would be the most logical solution.
+/* The main idea behind this configuration system is that each type of GObject
+ * that needs to use the system implements its own lookup scheme. I tried to
+ * do this as generically as possible, by using just the GObject property names,
+ * and then looking up their type and using that to use the correct parsing
+ * function.
+ *
+ * It is basically function overloading (for example, from C++) but done in C.
+ * Rather complicated, if you ask me. Also a complete pain. But the result
+ * sure is nice.
*/
-/**
- * This function adds all of the defaults to the defaults hashtable.
- * It just uses their names, since there are currently no conflicts
- * in the properties of TildaWindow and TildaTerminal. If conflicts
- * become a problem in the future, prefix them with "w-" and "t-",
- * for example. Then fix up all callers. :) */
-static void
-tilda_config_setup_defaults ()
+static gchar *
+tilda_config_lookup_version (GKeyFile *keyfile, GError **error)
+{
+ debug_enter ();
+
+ return g_key_file_get_string (keyfile, "Meta", "version", error);
+}
+
+static gboolean
+tilda_config_parse_integer (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *value,
+ GError **user_error)
+{
+ debug_enter ();
+ debug_assert (G_IS_VALUE(value));
+
+ GError *error = NULL;
+ gint ret;
+
+ /* Try to get the value */
+ ret = g_key_file_get_integer (keyfile, group_name, key, &error);
+
+ /* Check for error */
+ if (error)
+ {
+ g_propagate_error (user_error, error);
+ return FALSE;
+ }
+
+ /* We successfully parsed the int, so set it in the GValue */
+ g_value_set_int (value, ret);
+
+ return TRUE;
+}
+
+static gboolean
+tilda_config_parse_boolean (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *value,
+ GError **user_error)
+{
+ debug_enter ();
+ debug_assert (G_IS_VALUE(value));
+
+ GError *error = NULL;
+ gboolean ret;
+
+ /* Try to get the value */
+ ret = g_key_file_get_boolean (keyfile, group_name, key, &error);
+
+ /* Check for error */
+ if (error)
+ {
+ g_propagate_error (user_error, error);
+ return FALSE;
+ }
+
+ /* Success */
+ g_value_set_boolean (value, ret);
+
+ return TRUE;
+}
+
+static gboolean
+tilda_config_parse_string (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *value,
+ GError **user_error)
+{
+ debug_enter ();
+ debug_assert (G_IS_VALUE(value));
+
+ GError *error = NULL;
+ gchar *ret;
+
+ /* Try to get the value */
+ ret = g_key_file_get_string (keyfile, group_name, key, &error);
+
+ /* Check for error */
+ if (error)
+ {
+ g_propagate_error (user_error, error);
+ return FALSE;
+ }
+
+ /* Success */
+ g_value_set_string (value, ret);
+
+ return TRUE;
+}
+
+#define key_is(STR) (g_ascii_strcasecmp(key,(STR)) == 0)
+
+static gboolean
+tilda_config_parse_enum (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *value,
+ GError **user_error)
+{
+ gchar *ret;
+ GError *error = NULL;
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ if (key_is("backspace-binding") || key_is("delete-binding"))
+ enum_class = g_type_class_peek (vte_terminal_erase_binding_get_type());
+ else if (key_is("dynamic-title"))
+ enum_class = g_type_class_peek (tilda_dynamic_title_get_type());
+ else if (key_is("exit-action"))
+ enum_class = g_type_class_peek (tilda_child_exit_action_get_type());
+ else if (key_is("scrollbar-position"))
+ enum_class = g_type_class_peek (tilda_scrollbar_position_get_type());
+ else if (key_is("animation-orientation") || key_is("tab-position"))
+ enum_class = g_type_class_peek (gtk_position_type_get_type());
+ else
+ {
+ g_critical ("FIXME: developer error -- unknown enum key used: %s\n", key);
+ exit (1);
+ return FALSE;
+ }
+
+ /* Get the value from the config as a string */
+ ret = g_key_file_get_string (keyfile, group_name, key, &error);
+
+ if (error)
+ {
+ g_propagate_error (user_error, error);
+ return FALSE;
+ }
+
+ enum_value = g_enum_get_value_by_nick (enum_class, ret);
+
+ if (!enum_value)
+ {
+ /* This is exactly the same error that is returned by g_key_file_get_integer()
+ * when it cannot correctly parse the value. */
+ g_set_error (user_error,
+ G_KEY_FILE_ERROR,
+ G_KEY_FILE_ERROR_INVALID_VALUE,
+ _("Key file contains key '%s' in group '%s' which has value that cannot be interpreted."), key, group_name);
+ return FALSE;
+ }
+
+ /* All was successful, let's set it */
+ g_value_set_enum (value, enum_value->value);
+ return TRUE;
+}
+
+gboolean
+tilda_controller_set_property_from_config (TildaController *self, const gchar *property)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_CONTROLLER(self));
+
+ GError *error = NULL;
+ gboolean ret;
+
+ GParamSpec *pspec;
+ GValue *value = g_malloc0(sizeof(GValue));
+ gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
+
+ /* Get the pspec for this property */
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
+
+ /* Make sure that this property exists */
+ if (pspec == NULL)
+ {
+ g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
+ exit (1);
+ }
+
+ /* Initialize the GValue that is going to hold the returned value */
+ g_value_init (value, pspec->value_type);
+
+ /* Set the correct function to do the parsing */
+ if (g_type_is_a (pspec->value_type, G_TYPE_INT))
+ parse_func = tilda_config_parse_integer;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
+ parse_func = tilda_config_parse_boolean;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
+ parse_func = tilda_config_parse_string;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
+ parse_func = tilda_config_parse_enum;
+ else
+ {
+ g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
+ exit(1);
+ }
+
+ /* Do the [Controller] lookup */
+ ret = parse_func (config_userprefs, "Controller", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [Global] lookup */
+ ret = parse_func (config_userprefs, "Global", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [controller-defaults] lookup */
+ ret = parse_func (config_defaults, "controller-defaults", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+
+ /* This is somewhat of a nasty hack, but I really do want to just set the property to
+ * NULL if there is no default, but ONLY for strings. */
+ if (parse_func == tilda_config_parse_string)
+ {
+ g_value_set_string (value, NULL);
+ goto success;
+ }
+ }
+ else
+ goto success;
+
+//failure:
+ g_critical (_("Unable to find a value for controller property: %s\n"), property);
+ return FALSE;
+
+success:
+ g_object_set_property (G_OBJECT(self), property, value);
+ g_value_unset (value);
+ g_free (value);
+ return TRUE;
+}
+
+gboolean
+tilda_window_set_property_from_config (TildaWindow *self, const gchar *property)
{
- /* NOTE: All properties must be specified as strings. This sucks, but it really
- * is easiest to keep it this way. We can then implement custom functions to
- * change the values as we need them. */
-
- /* Window properties */
- g_hash_table_insert (config_defaults, "key", NULL);
- g_hash_table_insert (config_defaults, "height", "300");
- g_hash_table_insert (config_defaults, "width", "600");
- g_hash_table_insert (config_defaults, "x-position", "0");
- g_hash_table_insert (config_defaults, "y-position", "0");
- g_hash_table_insert (config_defaults, "initial-terminals", "1");
- g_hash_table_insert (config_defaults, "tab-position", "top");
- g_hash_table_insert (config_defaults, "animation-orientation", "top");
- g_hash_table_insert (config_defaults, "animation-delay", "15000");
- g_hash_table_insert (config_defaults, "keep-above", "true");
- g_hash_table_insert (config_defaults, "skip-taskbar-hint", "true");
- g_hash_table_insert (config_defaults, "stick", "true");
- g_hash_table_insert (config_defaults, "hidden-at-start", "false");
- g_hash_table_insert (config_defaults, "centered-horizontally", "false");
- g_hash_table_insert (config_defaults, "centered-vertically", "false");
-
- /* Terminal Properties */
- g_hash_table_insert (config_defaults, "background-image", NULL);
- g_hash_table_insert (config_defaults, "shell", NULL);
- g_hash_table_insert (config_defaults, "font", "Bitstream Vera Sans Mono 12");
- g_hash_table_insert (config_defaults, "title", "Tilda");
- g_hash_table_insert (config_defaults, "working-directory", NULL);
- g_hash_table_insert (config_defaults, "web-browser", "firefox");
- g_hash_table_insert (config_defaults, "scrollback-lines", "1000");
- g_hash_table_insert (config_defaults, "transparency-percent", "0");
- g_hash_table_insert (config_defaults, "backspace-binding", "vte-erase-auto");
- g_hash_table_insert (config_defaults, "delete-binding", "vte-erase-auto");
- g_hash_table_insert (config_defaults, "dynamic-title", "after-initial");
- g_hash_table_insert (config_defaults, "exit-action", "exit");
- g_hash_table_insert (config_defaults, "scrollbar-position", "right");
-#if 0
- /* These don't have properties yet! */
- g_hash_table_insert (config_defaults, "background-color", "0;0;0");
- g_hash_table_insert (config_defaults, "foreground-color", "255;255;255");
-#endif
- g_hash_table_insert (config_defaults, "scroll-background", "false");
- g_hash_table_insert (config_defaults, "scroll-on-output", "false");
- g_hash_table_insert (config_defaults, "scroll-on-keystroke", "true");
- g_hash_table_insert (config_defaults, "antialiased", "true");
- g_hash_table_insert (config_defaults, "allow-bold-text", "true");
- g_hash_table_insert (config_defaults, "cursor-blinks", "true");
- g_hash_table_insert (config_defaults, "audible-bell", "false");
- g_hash_table_insert (config_defaults, "visible-bell", "true");
- g_hash_table_insert (config_defaults, "double-buffered", "true");
- g_hash_table_insert (config_defaults, "mouse-autohide", "true");
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gchar *group_name;
+ GError *error = NULL;
+ gboolean ret;
+
+ GParamSpec *pspec;
+ GValue *value = g_malloc0(sizeof(GValue));
+ gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
+
+ /* Get the pspec for this property */
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
+
+ /* Make sure that this property exists */
+ if (pspec == NULL)
+ {
+ g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
+ exit (1);
+ }
+
+ /* Initialize the GValue that is going to hold the returned value */
+ g_value_init (value, pspec->value_type);
+
+ /* Set the correct function to do the parsing */
+ if (g_type_is_a (pspec->value_type, G_TYPE_INT))
+ parse_func = tilda_config_parse_integer;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
+ parse_func = tilda_config_parse_boolean;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
+ parse_func = tilda_config_parse_string;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
+ parse_func = tilda_config_parse_enum;
+ else
+ {
+ g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
+ exit(1);
+ }
+
+ /* Do the [Window] lookup */
+ group_name = g_strdup_printf ("Window%d", self->number);
+ ret = parse_func (config_userprefs, group_name, property, value, &error);
+ g_free (group_name);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [Global] lookup */
+ ret = parse_func (config_userprefs, "Global", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [window-defaults] lookup */
+ ret = parse_func (config_defaults, "window-defaults", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+
+ /* This is somewhat of a nasty hack, but I really do want to just set the property to
+ * NULL if there is no default, but ONLY for strings. */
+ if (parse_func == tilda_config_parse_string)
+ {
+ g_value_set_string (value, NULL);
+ goto success;
+ }
+ }
+ else
+ goto success;
+
+//failure:
+ g_critical (_("Unable to find a value for window property: %s\n"), property);
+ return FALSE;
+
+success:
+ g_object_set_property (G_OBJECT(self), property, value);
+ g_value_unset (value);
+ g_free (value);
+ return TRUE;
}
gboolean
-tilda_config_init (const gchar *filename)
+tilda_terminal_set_property_from_config (TildaTerminal *self, const gchar *property)
{
+ debug_enter ();
+ debug_assert (TILDA_IS_TERMINAL(self));
+
+ TildaWindow *parent_window = TILDA_WINDOW(self->parent_window);
+ gchar *group_name;
GError *error = NULL;
+ gboolean ret;
+
+ GParamSpec *pspec;
+ GValue *value = g_malloc0(sizeof(GValue));
+ gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
- /* Create the hashtable */
- config_defaults = g_hash_table_new (g_str_hash, g_str_equal);
+ /* Get the pspec for this property */
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
- /* Fill it with the default values */
- tilda_config_setup_defaults ();
+ /* Make sure that this property exists */
+ if (pspec == NULL)
+ {
+ g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
+ exit (1);
+ }
+
+ /* Initialize the GValue that is going to hold the returned value */
+ g_value_init (value, pspec->value_type);
- /* If we have no file to read from, then we are just using the defaults,
- * and are done. */
- if (filename == NULL)
+ /* Set the correct function to do the parsing */
+ if (g_type_is_a (pspec->value_type, G_TYPE_INT))
+ parse_func = tilda_config_parse_integer;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
+ parse_func = tilda_config_parse_boolean;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
+ parse_func = tilda_config_parse_string;
+ else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
+ parse_func = tilda_config_parse_enum;
+ else
{
- config_userprefs = NULL;
- return TRUE;
+ g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
+ exit(1);
}
- /* Create the key file */
+ /* Do the [Window/Terminal] lookup */
+ group_name = g_strdup_printf ("Window%d/Terminal%d", parent_window->number, self->number);
+ ret = parse_func (config_userprefs, group_name, property, value, &error);
+ g_free (group_name);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [Window] lookup */
+ group_name = g_strdup_printf ("Window%d", parent_window->number);
+ ret = parse_func (config_userprefs, group_name, property, value, &error);
+ g_free (group_name);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [Global] lookup */
+ ret = parse_func (config_userprefs, "Global", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+ }
+ else
+ goto success;
+
+ /* Do the [terminal-defaults] lookup */
+ ret = parse_func (config_defaults, "terminal-defaults", property, value, &error);
+
+ if (error)
+ {
+ if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
+ g_warning (error->message);
+
+ g_clear_error (&error);
+
+ /* This is somewhat of a nasty hack, but I really do want to just set the property to
+ * NULL if there is no default, but ONLY for strings. */
+ if (parse_func == tilda_config_parse_string)
+ {
+ g_value_set_string (value, NULL);
+ goto success;
+ }
+ }
+ else
+ goto success;
+
+//failure:
+ g_critical (_("Unable to find a value for terminal property: %s\n"), property);
+ return FALSE;
+
+success:
+ g_object_set_property (G_OBJECT(self), property, value);
+ g_value_unset (value);
+ g_free (value);
+ return TRUE;
+}
+
+gboolean
+tilda_config_init (const gchar *userprefs_filename)
+{
+ gchar *defaults_filename;
+ GError *error = NULL;
+
+ /* Call g_type_class_ref() on all enum types that are being used */
+ g_type_class_ref (vte_terminal_erase_binding_get_type());
+ g_type_class_ref (tilda_dynamic_title_get_type());
+ g_type_class_ref (tilda_child_exit_action_get_type());
+ g_type_class_ref (tilda_scrollbar_position_get_type());
+ g_type_class_ref (gtk_position_type_get_type());
+
+ /* Create the defaults file's path */
+ defaults_filename = g_build_filename ("share-tilda.conf", NULL); // FIXME: use /usr/share/tilda
+
+ /* Create the keyfiles */
+ config_defaults = g_key_file_new ();
config_userprefs = g_key_file_new ();
- if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ /* Check if the defaults exist, and load them */
+ if (!g_file_test (defaults_filename, G_FILE_TEST_EXISTS))
{
- g_warning (_("No config file found, using defaults\n"));
- return TRUE;
+ g_critical (_("No configuration defaults file found. Tilda may not work.\n"));
+ }
+ else
+ {
+ if (!g_key_file_load_from_file (config_defaults, defaults_filename, G_KEY_FILE_NONE, &error))
+ {
+ g_critical (_("Error reading configuration defaults: %s\n"), error->message);
+ g_clear_error (&error);
+ }
}
- if (!g_key_file_load_from_file (config_userprefs, filename, G_KEY_FILE_NONE, &error))
+ /* Check if the user's config exists, and load it */
+ if (!g_file_test (userprefs_filename, G_FILE_TEST_EXISTS))
{
- g_warning (_("Error reading from config file: %s\n"), error->message);
- g_error_free (error);
- return FALSE;
+ g_warning (_("No user configuration file found, using defaults\n"));
+ }
+ else
+ {
+ if (!g_key_file_load_from_file (config_userprefs, userprefs_filename, G_KEY_FILE_NONE, &error))
+ {
+ g_warning (_("Error reading user configuration: %s\n"), error->message);
+ g_clear_error (&error);
+ }
}
- /* Everything went ok */
+ /* This is just here for the future. Currently, it is acceptable to run without
+ * a configuration, though Tilda may not work very well. */
return TRUE;
}
gboolean
tilda_config_free ()
{
- g_hash_table_destroy (config_defaults);
+ /* Since we never write the config file from within Tilda,
+ * we can just close the files. */
+ g_key_file_free (config_defaults);
g_key_file_free (config_userprefs);
config_defaults = NULL;