/* vim: set ts=4 sts=4 sw=4 expandtab textwidth=112: */ /* * This is free software; you can redistribute it and/or modify it under * the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #define _POSIX_SOURCE /* feature test macro for fileno */ #define _XOPEN_SOURCE /* feature test macro for fsync */ #include "config.h" #include "debug.h" #include #include #include #include /* atoi */ #include /* fsync */ #include "configsys.h" #include static cfg_t *tc; /* CONFIGURATION OPTIONS * In this array we set the default configuration options for the * configuration file. */ static cfg_opt_t config_opts[] = { /* strings */ CFG_STR("tilda_config_version", PACKAGE_VERSION, CFGF_NONE), CFG_STR("command", "", CFGF_NONE), CFG_STR("font", "Monospace 11", CFGF_NONE), CFG_STR("key", NULL, CFGF_NONE), CFG_STR("addtab_key", "t", CFGF_NONE), CFG_STR("fullscreen_key", "F11", CFGF_NONE), CFG_STR("toggle_transparency_key", "F12", CFGF_NONE), CFG_STR("toggle_searchbar_key", "f", CFGF_NONE), CFG_STR("closetab_key", "w", CFGF_NONE), CFG_STR("nexttab_key", "Page_Down", CFGF_NONE), CFG_STR("prevtab_key", "Page_Up", CFGF_NONE), CFG_STR("movetableft_key", "Page_Up", CFGF_NONE), CFG_STR("movetabright_key", "Page_Down", CFGF_NONE), CFG_STR("gototab_1_key", "1", CFGF_NONE), CFG_STR("gototab_2_key", "2", CFGF_NONE), CFG_STR("gototab_3_key", "3", CFGF_NONE), CFG_STR("gototab_4_key", "4", CFGF_NONE), CFG_STR("gototab_5_key", "5", CFGF_NONE), CFG_STR("gototab_6_key", "6", CFGF_NONE), CFG_STR("gototab_7_key", "7", CFGF_NONE), CFG_STR("gototab_8_key", "8", CFGF_NONE), CFG_STR("gototab_9_key", "9", CFGF_NONE), CFG_STR("gototab_10_key", "0", CFGF_NONE), CFG_STR("copy_key", "c", CFGF_NONE), CFG_STR("paste_key", "v", CFGF_NONE), CFG_STR("quit_key", "q", CFGF_NONE), CFG_STR("title", "Tilda", CFGF_NONE), CFG_STR("background_color", "white", CFGF_NONE), CFG_STR("working_dir", NULL, CFGF_NONE), CFG_STR("web_browser", "xdg-open", CFGF_NONE), CFG_STR("increase_font_size_key", "equal", CFGF_NONE), CFG_STR("decrease_font_size_key", "minus", CFGF_NONE), CFG_STR("normalize_font_size_key", "0", CFGF_NONE), CFG_STR("show_on_monitor", "", CFGF_NONE), CFG_STR("word_chars", DEFAULT_WORD_CHARS, CFGF_NONE), /* ints */ CFG_INT("lines", 5000, CFGF_NONE), CFG_INT("x_pos", 0, CFGF_NONE), CFG_INT("y_pos", 0, CFGF_NONE), CFG_INT("tab_pos", 0, CFGF_NONE), CFG_BOOL("expand_tabs", FALSE, CFGF_NONE), CFG_BOOL("show_single_tab", FALSE, CFGF_NONE), CFG_INT("backspace_key", 0, CFGF_NONE), CFG_INT("delete_key", 1, CFGF_NONE), CFG_INT("d_set_title", 3, CFGF_NONE), CFG_INT("command_exit", 2, CFGF_NONE), /* Timeout in milliseconds to spawn a shell or command */ CFG_INT("command_timeout_ms", 3000, CFGF_NONE), CFG_INT("scheme", 3, CFGF_NONE), CFG_INT("slide_sleep_usec", 20000, CFGF_NONE), CFG_INT("animation_orientation", 0, CFGF_NONE), CFG_INT("timer_resolution", 200, CFGF_NONE), CFG_INT("auto_hide_time", 2000, CFGF_NONE), CFG_INT("on_last_terminal_exit", 0, CFGF_NONE), CFG_BOOL("prompt_on_exit", TRUE, CFGF_NONE), CFG_INT("palette_scheme", 1, CFGF_NONE), CFG_INT("non_focus_pull_up_behaviour", 0, CFGF_NONE), CFG_INT("cursor_shape", 0, CFGF_NONE), /* The length of a tab title */ CFG_INT("title_max_length", 25, CFGF_NONE), /* int list */ CFG_INT_LIST("palette", "{\ 0x2e2e, 0x3434, 0x3636,\ 0xcccc, 0x0000, 0x0000,\ 0x4e4e, 0x9a9a, 0x0606,\ 0xc4c4, 0xa0a0, 0x0000,\ 0x3434, 0x6565, 0xa4a4,\ 0x7575, 0x5050, 0x7b7b,\ 0x0606, 0x9820, 0x9a9a,\ 0xd3d3, 0xd7d7, 0xcfcf,\ 0x5555, 0x5757, 0x5353,\ 0xefef, 0x2929, 0x2929,\ 0x8a8a, 0xe2e2, 0x3434,\ 0xfcfc, 0xe9e9, 0x4f4f,\ 0x7272, 0x9f9f, 0xcfcf,\ 0xadad, 0x7f7f, 0xa8a8,\ 0x3434, 0xe2e2, 0xe2e2,\ 0xeeee, 0xeeee, 0xecec}", CFGF_NONE), /* guint16 */ CFG_INT("scrollbar_pos", 2, CFGF_NONE), CFG_INT("back_red", 0x0000, CFGF_NONE), CFG_INT("back_green", 0x0000, CFGF_NONE), CFG_INT("back_blue", 0x0000, CFGF_NONE), CFG_INT("text_red", 0xffff, CFGF_NONE), CFG_INT("text_green", 0xffff, CFGF_NONE), CFG_INT("text_blue", 0xffff, CFGF_NONE), CFG_INT("cursor_red", 0xffff, CFGF_NONE), CFG_INT("cursor_green", 0xffff, CFGF_NONE), CFG_INT("cursor_blue", 0xffff, CFGF_NONE), /* floats, libconfuse has a bug with floats on non english systems, * see: https://github.com/martinh/libconfuse/issues/119, so we * need to emulate floats by scaling values to a long value. */ CFG_INT ("width_percentage", G_MAXINT, CFGF_NONE), CFG_INT ("height_percentage", G_MAXINT, CFGF_NONE), /* booleans */ CFG_BOOL("scroll_history_infinite", FALSE, CFGF_NONE), CFG_BOOL("scroll_on_output", FALSE, CFGF_NONE), CFG_BOOL("notebook_border", FALSE, CFGF_NONE), CFG_BOOL("scrollbar", FALSE, CFGF_NONE), CFG_BOOL("grab_focus", TRUE, CFGF_NONE), CFG_BOOL("above", TRUE, CFGF_NONE), CFG_BOOL("notaskbar", TRUE, CFGF_NONE), CFG_BOOL("blinks", TRUE, CFGF_NONE), CFG_BOOL("scroll_on_key", TRUE, CFGF_NONE), CFG_BOOL("bell", FALSE, CFGF_NONE), CFG_BOOL("run_command", FALSE, CFGF_NONE), CFG_BOOL("pinned", TRUE, CFGF_NONE), CFG_BOOL("animation", FALSE, CFGF_NONE), CFG_BOOL("hidden", FALSE, CFGF_NONE), CFG_BOOL("set_as_desktop", FALSE, CFGF_NONE), CFG_BOOL("centered_horizontally", FALSE, CFGF_NONE), CFG_BOOL("centered_vertically", FALSE, CFGF_NONE), CFG_BOOL("enable_transparency", FALSE, CFGF_NONE), CFG_BOOL("auto_hide_on_focus_lost", FALSE, CFGF_NONE), CFG_BOOL("auto_hide_on_mouse_leave", FALSE, CFGF_NONE), /* Whether and how we limit the length of a tab title */ CFG_INT("title_behaviour", 2, CFGF_NONE), /* Whether to set a new tab's working dir to the current tab's */ CFG_BOOL("inherit_working_dir", TRUE, CFGF_NONE), CFG_BOOL("command_login_shell", FALSE, CFGF_NONE), CFG_BOOL("start_fullscreen", FALSE, CFGF_NONE), /* Whether closing a tab shows a confirmation dialog. */ CFG_BOOL("confirm_close_tab", TRUE, CFGF_NONE), CFG_INT("back_alpha", 0xffff, CFGF_NONE), /* Whether bold text is shown as bright colors */ CFG_BOOL("bold_is_bright", FALSE, CFGF_NONE), /* Whether to show the full tab title as a tooltip */ CFG_BOOL("show_title_tooltip", FALSE, CFGF_NONE), /* Whether match activation with mouse click requires CTRL to be pressed */ CFG_BOOL("control_activates_match", TRUE, CFGF_NONE), /* Whether to enable regular expressions to match * certain types of tokens: */ CFG_BOOL("match_web_uris", TRUE, CFGF_NONE), CFG_BOOL("match_file_uris", TRUE, CFGF_NONE), CFG_BOOL("match_email_addresses", TRUE, CFGF_NONE), CFG_BOOL("match_numbers", TRUE, CFGF_NONE), /* if set to TRUE, tilda will fall back to open * URIs with the 'web_browser' option. */ CFG_BOOL("use_custom_web_browser", FALSE, CFGF_NONE), /** * Deprecated tilda options. These options be commented out in the * configuration file and will not be initialized with default values * if the option is missing in the config file. **/ CFG_INT("max_width", 0, CFGF_NODEFAULT), CFG_INT("max_height", 0, CFGF_NODEFAULT), CFG_STR("image", NULL, CFGF_NODEFAULT), CFG_INT("show_on_monitor_number", 0, CFGF_NODEFAULT), CFG_INT("transparency", 0, CFGF_NODEFAULT), CFG_BOOL("bold", TRUE, CFGF_NODEFAULT), CFG_BOOL("title_max_length_flag", FALSE, CFGF_NODEFAULT), CFG_BOOL("antialias", TRUE, CFGF_NODEFAULT), CFG_BOOL("double_buffer", FALSE, CFGF_NODEFAULT), CFG_BOOL("scroll_background", FALSE, CFGF_NODEFAULT), CFG_BOOL("use_image", FALSE, CFGF_NODEFAULT), CFG_INT("min_width", 0, CFGF_NODEFAULT), CFG_INT("min_height", 0, CFGF_NODEFAULT), /* End deprecated tilda options */ CFG_END() }; /* Define these here, so that we can enable a non-threadsafe version * without changing the code below. */ #ifndef NO_THREADSAFE static GMutex mutex; #define config_mutex_lock() g_mutex_lock (&mutex) #define config_mutex_unlock() g_mutex_unlock (&mutex) #else #define config_mutex_lock() #define config_mutex_unlock() #endif #define CONFIG1_OLDER -1 #define CONFIGS_SAME 0 #define CONFIG1_NEWER 1 static gboolean compare_config_versions (const gchar *config1, const gchar *config2) G_GNUC_UNUSED; static void invoke_deprecation_function(const gchar *const *deprecated_config_options, guint size); static void remove_deprecated_config_options(const gchar *const *deprecated_config_options, guint size); /* Note: set config_file to NULL to just free the * data structures, and not write out the state to * a file. */ gint config_free (const gchar *config_file) { gint ret = 0; if (config_file != NULL) ret = config_write (config_file); cfg_free (tc); return ret; } gint config_setint (const gchar *key, const glong val) { config_mutex_lock (); cfg_setint (tc, key, val); config_mutex_unlock (); return 0; } gint config_setnint(const gchar *key, const glong val, const guint idx) { config_mutex_lock (); cfg_setnint (tc, key, val, idx); config_mutex_unlock (); return 0; } gint config_setstr (const gchar *key, const gchar *val) { config_mutex_lock (); cfg_setstr (tc, key, val); config_mutex_unlock (); return 0; } gint config_setbool(const gchar *key, const gboolean val) { config_mutex_lock (); cfg_setbool (tc, key, val); config_mutex_unlock (); return 0; } glong config_getint (const gchar *key) { glong temp; config_mutex_lock (); temp = cfg_getint (tc, key); config_mutex_unlock (); return temp; } glong config_getnint(const gchar *key, const guint idx) { glong temp; config_mutex_lock (); temp = cfg_getnint (tc, key, idx); config_mutex_unlock (); return temp; } gchar* config_getstr (const gchar *key) { gchar *temp; config_mutex_lock (); temp = cfg_getstr (tc, key); config_mutex_unlock (); return temp; } gboolean config_getbool(const gchar *key) { gboolean temp; config_mutex_lock (); temp = cfg_getbool (tc, key); config_mutex_unlock (); return temp; } /* This will write out the current state of the config file to the disk. * It's use is generally discouraged, since config_free() will also write * out the configuration to disk. */ gint config_write (const gchar *config_file) { DEBUG_FUNCTION ("config_write"); DEBUG_ASSERT (config_file != NULL); gint ret = 0; FILE *fp; char *temp_config_file = g_strdup_printf ("%s.tmp", config_file); fp = fopen(temp_config_file, "w"); if (fp != NULL) { config_mutex_lock (); cfg_print (tc, fp); config_mutex_unlock (); if (fsync (fileno(fp))) { // Error occurred during sync TILDA_PERROR (); DEBUG_ERROR ("Unable to sync file"); g_printerr (_("Unable to sync the config file to disk\n")); ret = 2; } if (fclose (fp)) { // An error occurred TILDA_PERROR (); DEBUG_ERROR ("Unable to close config file"); g_printerr (_("Unable to close the config file\n")); ret = 3; } if (rename(temp_config_file, config_file)) { TILDA_PERROR (); DEBUG_ERROR ("Unable to rename temporary config file to final config file."); } } else { TILDA_PERROR (); DEBUG_ERROR ("Unable to write config file"); g_printerr (_("Unable to write the config file to %s\n"), config_file); ret = 4; } return ret; } /** * Start up the configuration system, using the configuration file given * to get the current values. If the configuration file given does not exist, * go ahead and write out the default config to the file. */ gint config_init (const gchar *config_file) { DEBUG_FUNCTION ("config_init"); gint ret = 0; // Can we use a more descriptive name than tc? tc = cfg_init (config_opts, 0); if (g_file_test (config_file, G_FILE_TEST_IS_REGULAR)) { /* Read in the existing configuration options */ ret = cfg_parse (tc, config_file); if (ret == CFG_PARSE_ERROR) { DEBUG_ERROR ("Problem parsing config"); return ret; } else if (ret != CFG_SUCCESS) { DEBUG_ERROR ("Problem parsing config."); return ret; } } /* Deprecate old config settings. * This is a lame work around until we get a permanent solution to * libconfuse lacking for this functionality */ const gchar *deprecated_tilda_config_options[] = {"show_on_monitor_number", "bold", "title_max_length_flag", "double_buffer", "antialias", "image", "transparency", "scroll_background", "use_image", "min_width", "min_height", "max_width", "max_height" }; invoke_deprecation_function (deprecated_tilda_config_options, G_N_ELEMENTS(deprecated_tilda_config_options)); remove_deprecated_config_options(deprecated_tilda_config_options, G_N_ELEMENTS(deprecated_tilda_config_options)); #ifndef NO_THREADSAFE g_mutex_init(&mutex); #endif return ret; } static GdkMonitor *config_get_configured_monitor () { gint x_pos = (gint) config_getint ("x_pos"); gint y_pos = (gint) config_getint ("y_pos"); GdkDisplay *display = gdk_display_get_default (); return gdk_display_get_monitor_at_point (display, x_pos, y_pos); } void config_get_configured_window_size (GdkRectangle *rectangle) { gdouble relative_width = GLONG_TO_DOUBLE (config_getint ("width_percentage")); gdouble relative_height = GLONG_TO_DOUBLE (config_getint ("height_percentage")); GdkMonitor *monitor = config_get_configured_monitor (); GdkRectangle workarea; gdk_monitor_get_workarea (monitor, &workarea); rectangle->width = pixels_ratio_to_absolute (relative_width, workarea.width); rectangle->height = pixels_ratio_to_absolute (relative_height, workarea.height); } static void config_get_configured_percentage (gdouble *width_percentage, gdouble *height_percentage) { glong windowWidth = config_getint ("max_width"); glong windowHeight = config_getint ("max_height"); GdkMonitor *monitor = config_get_configured_monitor (); GdkRectangle workarea; gdk_monitor_get_workarea (monitor, &workarea); if (width_percentage) { *width_percentage = pixels_absolute_to_ratio (workarea.width, windowWidth); } if (height_percentage) { *height_percentage = pixels_absolute_to_ratio (workarea.height, windowHeight); } } static void print_migration_info (const gchar *old_option_name, const gchar *new_option_name) { g_print ("Migrated deprecated value in option '%s' to '%s'.\n", old_option_name, new_option_name); } void invoke_deprecation_function (const gchar *const *deprecated_config_options, guint size) { for (guint i = 0; i < size; i++) { const char *const option_name = deprecated_config_options[i]; /* This will still return the option even if its * commented out in the config file, so we perform the extra check * using `cfg_opt_size` below to determine if the option has a valid * value. We do this to ensure that we only execute the migration * code once. */ cfg_opt_t *option = cfg_getopt (tc, option_name); if (option == NULL || cfg_opt_size (option) == 0) { continue; } gdouble width_percentage; gdouble height_percentage; config_get_configured_percentage (&width_percentage, &height_percentage); if (strncmp(option_name, "max_width", sizeof("max_width")) == 0) { print_migration_info (option_name, "width_percentage"); config_setint ("width_percentage", GLONG_FROM_DOUBLE (width_percentage)); } if (strncmp(option_name, "max_height", sizeof("max_height")) == 0) { print_migration_info (option_name, "height_percentage"); config_setint ("height_percentage", GLONG_FROM_DOUBLE (height_percentage)); } } } void remove_deprecated_config_options(const gchar *const *deprecated_config_options, guint size) { cfg_opt_t *opt; for (guint i =0; i < size; i++) { opt = cfg_getopt(tc, deprecated_config_options[i]); if (opt->nvalues != 0) { 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]); cfg_free_value(opt); } } } /* * Compares two config versions together. * * Returns -1 if config1 is older than config2 (UPDATE REQUIRED) * Returns 0 if config1 is equal to config2 (NORMAL USAGE) * Returns 1 if config1 is newer than config2 (DISABLE WRITE) */ static gboolean compare_config_versions (const gchar *config1, const gchar *config2) { DEBUG_FUNCTION ("compare_config_versions"); DEBUG_ASSERT (config1 != NULL); DEBUG_ASSERT (config2 != NULL); /* * 1) Split apart both strings using the .'s * 2) Compare the major-major version * 3) Compare the major version * 4) Compare the minor version */ gchar **config1_tokens; gchar **config2_tokens; gint config1_version[3]; gint config2_version[3]; gint i; config1_tokens = g_strsplit (config1, ".", 3); config2_tokens = g_strsplit (config2, ".", 3); for (i=0; i<3; i++) { config1_version[i] = atoi (config1_tokens[i]); config2_version[i] = atoi (config2_tokens[i]); } g_strfreev (config1_tokens); g_strfreev (config2_tokens); /* We're done splitting things, so compare now */ for (i=0; i<3; i++) { if (config1_version[i] > config2_version[i]) return CONFIG1_NEWER; if (config1_version[i] < config2_version[i]) return CONFIG1_OLDER; } return CONFIGS_SAME; }