diff options
| author | Spacedio <spacedio@thernusen.net> | 2026-02-26 19:20:14 -0500 |
|---|---|---|
| committer | Spacedio <spacedio@thernusen.net> | 2026-02-26 19:20:14 -0500 |
| commit | db657412e6ae93341ed42d59df6aef564a739a1f (patch) | |
| tree | 6f67f55f6dc61fb0afa8829a950f0dbf1a0aeadf /src/config.c | |
| download | lightdm-mini-greeter-db657412e6ae93341ed42d59df6aef564a739a1f.tar.gz | |
Diffstat (limited to 'src/config.c')
| -rw-r--r-- | src/config.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..0d7b21b --- /dev/null +++ b/src/config.c @@ -0,0 +1,435 @@ +/* Functions related to the Configuration */ +#include <stdlib.h> +#include <string.h> + +#include <gdk/gdk.h> +#include <glib.h> + +#include "config.h" +#include "utils.h" + + +static gchar *parse_greeter_string(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gchar *fallback); +static gint parse_greeter_integer(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gint fallback); +static gboolean parse_greeter_boolean(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gboolean fallback); +static GdkRGBA *parse_greeter_color_key(GKeyFile *keyfile, const char *key_name, const char *default_str); +static guint parse_greeter_hotkey_keyval(GKeyFile *keyfile, const char *key_name, const char default_char); +static gunichar *parse_greeter_password_char(GKeyFile *keyfile); +static gfloat parse_greeter_password_alignment(GKeyFile *keyfile); +static gboolean is_rtl_keymap_layout(void); +gboolean input_string_equals(gchar *input_str, const gchar * const fixed_str); + +/* Initialize the configuration, sourcing the greeter's configuration file */ +Config *initialize_config(void) +{ + Config *config = malloc(sizeof(Config)); + if (config == NULL) { + g_error("Could not allocate memory for Config"); + } + + // Load the key-value file + GKeyFile *keyfile = g_key_file_new(); + GError *keyerror = NULL; + gboolean keyfile_loaded = g_key_file_load_from_file( + keyfile, CONFIG_FILE, G_KEY_FILE_NONE, &keyerror); + if (!keyfile_loaded) { + if (keyerror != NULL) { + g_error("Could not load configuration file: %s", keyerror->message); + free(keyerror); + } else { + g_error("Could not load configuration file."); + } + } + + // Parse values from the keyfile into a Config. + config->login_user = + g_strchomp(g_key_file_get_string(keyfile, "greeter", "user", NULL)); + if (strcmp(config->login_user, "CHANGE_ME") == 0) { + g_message("User configuration value is unchanged."); + } + config->show_password_label = + parse_greeter_boolean(keyfile, "greeter", "show-password-label", TRUE); + config->password_label_text = parse_greeter_string( + keyfile, "greeter", "password-label-text", "Password:"); + config->invalid_password_text = parse_greeter_string( + keyfile, "greeter", "invalid-password-text", "Invalid Password"); + config->show_input_cursor = + parse_greeter_boolean(keyfile, "greeter", "show-input-cursor", TRUE); + config->password_alignment = parse_greeter_password_alignment(keyfile); + config->password_input_width = parse_greeter_integer( + keyfile, "greeter", "password-input-width", -1); + config->show_image_on_all_monitors = parse_greeter_boolean( + keyfile, "greeter", "show-image-on-all-monitors", FALSE); + config->show_sys_info = parse_greeter_boolean( + keyfile, "greeter", "show-sys-info", FALSE); + + // Parse Hotkey Settings + config->suspend_key = parse_greeter_hotkey_keyval(keyfile, "suspend-key", 'u'); + config->hibernate_key = parse_greeter_hotkey_keyval(keyfile, "hibernate-key", 'h'); + config->restart_key = parse_greeter_hotkey_keyval(keyfile, "restart-key", 'r'); + config->shutdown_key = parse_greeter_hotkey_keyval(keyfile, "shutdown-key", 's'); + config->session_key = parse_greeter_hotkey_keyval(keyfile, "session-key", 'e'); + gchar *mod_key = + g_key_file_get_string(keyfile, "greeter-hotkeys", "mod-key", NULL); + if (mod_key == NULL) { + config->mod_bit = GDK_SUPER_MASK; + } else { + g_strchomp(mod_key); + if (strcmp(mod_key, "control") == 0) { + config->mod_bit = GDK_CONTROL_MASK; + } else if (strcmp(mod_key, "alt") == 0) { + config->mod_bit = GDK_MOD1_MASK; + } else if (strcmp(mod_key, "meta") == 0) { + config->mod_bit = GDK_SUPER_MASK; + } else { + g_error("Invalid mod-key configuration value: '%s'\n", mod_key); + } + } + + // Parse Theme Settings + // Font + config->font = + parse_greeter_string(keyfile, "greeter-theme", "font", "Sans"); + config->font_size = + parse_greeter_string(keyfile, "greeter-theme", "font-size", "1em"); + config->font_weight = + parse_greeter_string(keyfile, "greeter-theme", "font-weight", "bold"); + config->font_style = + parse_greeter_string(keyfile, "greeter-theme", "font-style", "normal"); + config->text_color = + parse_greeter_color_key(keyfile, "text-color", "#080800"); + config->error_color = + parse_greeter_color_key(keyfile, "error-color", "#F8F8F0"); + // Background + config->background_image = + g_key_file_get_string(keyfile, "greeter-theme", "background-image", NULL); + if (config->background_image == NULL || strcmp(config->background_image, "") == 0) { + config->background_image = (gchar *) "\"\""; + } + config->background_color = + parse_greeter_color_key(keyfile, "background-color", "#1B1D1E"); + config->background_image_size = + parse_greeter_string(keyfile, "greeter-theme", "background-image-size", "auto"); + // Window + config->window_color = + parse_greeter_color_key(keyfile, "window-color", "#F92672"); + config->border_color = + parse_greeter_color_key(keyfile, "border-color", "#080800"); + config->border_width = parse_greeter_string( + keyfile, "greeter-theme", "border-width", "2px"); + // Password + config->password_char = + parse_greeter_password_char(keyfile); + config->password_color = + parse_greeter_color_key(keyfile, "password-color", "#F8F8F0"); + config->password_background_color = + parse_greeter_color_key(keyfile, "password-background-color", "#1B1D1E"); + gchar *temp_password_border_color = g_key_file_get_string( + keyfile, "greeter-theme", "password-border-color", NULL); + if (temp_password_border_color == NULL) { + config->password_border_color = config->border_color; + } else { + free(temp_password_border_color); + config->password_border_color = + parse_greeter_color_key(keyfile, "password-border-color", "#080800"); + } + config->password_border_width = parse_greeter_string( + keyfile, "greeter-theme", "password-border-width", config->border_width); + config->password_border_radius = parse_greeter_string( + keyfile, "greeter-theme", "password-border-radius", "0.341125em"); + // System Info + gchar *temp_sys_info_color = g_key_file_get_string( + keyfile, "greeter-theme", "sys-info-color", NULL); + if (temp_sys_info_color == NULL) { + config->sys_info_color = config->text_color; + } else { + config->sys_info_color = parse_greeter_color_key( + keyfile, "sys-info-color", "#080800"); + } + config->sys_info_font = parse_greeter_string(keyfile, "greeter-theme", "sys-info-font", config->font); + config->sys_info_font_size = + parse_greeter_string(keyfile, "greeter-theme", "sys-info-font-size", config->font_size); + config->sys_info_margin = + parse_greeter_string(keyfile, "greeter-theme", "sys-info-margin", "-5px -5px -5px"); + + + gint layout_spacing = + parse_greeter_integer(keyfile, "greeter-theme", "layout-space", 15); + if (layout_spacing < 0) { + config->layout_spacing = (guint) (-1 * layout_spacing); + } else { + config->layout_spacing = (guint) layout_spacing; + } + + config->x_pos = g_key_file_get_double(keyfile, "greeter-theme", "x-pos", NULL); + config->y_pos = g_key_file_get_double(keyfile, "greeter-theme", "y-pos", NULL); + + g_key_file_free(keyfile); + + return config; +} + + +/* Cleanup any memory allocated for the Config */ +void destroy_config(Config *config) +{ + free(config->login_user); + free(config->font); + free(config->font_size); + free(config->font_weight); + free(config->font_style); + free(config->text_color); + free(config->error_color); + free(config->background_image); + free(config->background_color); + free(config->background_image_size); + free(config->window_color); + free(config->border_color); + free(config->border_width); + free(config->password_label_text); + free(config->invalid_password_text); + free(config->password_char); + free(config->password_color); + free(config->password_background_color); + free(config->password_border_color); + free(config->password_border_width); + free(config->password_border_radius); + free(config->sys_info_color); + free(config->sys_info_font_size); + free(config->sys_info_margin); + free(config); +} + + +/* Parse a string from the config file, returning a copy of the fallback value + * if the key is not present in the group. + */ +static gchar *parse_greeter_string(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gchar *fallback) +{ + gchar *parsed_string = g_key_file_get_string(keyfile, group_name, key_name, NULL); + if (parsed_string == NULL) { + g_warning("Could not find value for %s.%s - falling back to '%s'", + group_name, key_name, fallback); + return g_strdup(fallback); + } else { + return parsed_string; + } +} + +/* Parse an integer from the config file, returning the fallback value if the + * key is not present in the group, or if the value is not an integer. + */ +static gint parse_greeter_integer(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gint fallback) +{ + GError *parse_error = NULL; + gint parse_result = g_key_file_get_integer( + keyfile, group_name, key_name, &parse_error); + if (parse_error != NULL) { + if (parse_error->code == G_KEY_FILE_ERROR_INVALID_VALUE) { + // Read the value as a string so we can log it + gchar *value = g_key_file_get_string( + keyfile, group_name, key_name, NULL); + g_warning("Invalid integer for %s.%s: `%s`", + group_name, key_name, value); + free(value); + } + g_error_free(parse_error); + return fallback; + } + return parse_result; +} + +/* Parse a boolean from the config file, returning the fallback value if the + * key is not present in the group or cannot be parsed as a boolean. + */ +static gboolean parse_greeter_boolean(GKeyFile *keyfile, const char *group_name, + const char *key_name, const gboolean fallback) +{ + GError *parse_error = NULL; + gboolean parse_result = g_key_file_get_boolean( + keyfile, group_name, key_name, &parse_error); + if (!parse_result && parse_error != NULL) { + if (parse_error->code == G_KEY_FILE_ERROR_INVALID_VALUE) { + // Read the value as a string so we can log it + gchar *value = g_key_file_get_string( + keyfile, group_name, key_name, NULL); + g_warning("Invalid boolean for %s.%s: `%s`\n", + group_name, key_name, value); + g_free(value); + } + g_error_free(parse_error); + return fallback; + } + return parse_result; +} + +/* Parse a greeter-colors group key into a newly-allocated GdkRGBA value */ +static GdkRGBA *parse_greeter_color_key(GKeyFile *keyfile, const char *key_name, const char *default_str) +{ + gchar *color_string = g_key_file_get_string( + keyfile, "greeter-theme", key_name, NULL); + if (color_string == NULL) { + g_warning("Could not find value for %s.%s - falling back to '%s'", + "greeter-theme", key_name, default_str); + } + + GdkRGBA *default_color = malloc(sizeof(GdkRGBA)); + gboolean default_was_parsed = gdk_rgba_parse(default_color, default_str); + if (!default_was_parsed) { + g_critical("Could not parse the default '%s' setting: %s", key_name, default_str); + } + + if (color_string != NULL) { + if (strstr(color_string, "#") != NULL) { + // Remove quotations from hex color strings + remove_char(color_string, '"'); + remove_char(color_string, '\''); + } + + GdkRGBA *color = malloc(sizeof(GdkRGBA)); + gboolean color_was_parsed = gdk_rgba_parse(color, color_string); + if (color_was_parsed) { + free(default_color); + return color; + } + g_warning("Could not parse the '%s' setting: %s - falling back to default of %s", + key_name, color_string, default_str); + free(color); + } + return default_color; +} + +/* Parse a greeter-hotkeys key into the GDKkeyval of it's first character */ +static guint parse_greeter_hotkey_keyval(GKeyFile *keyfile, const char *key_name, const char default_char) +{ + gchar *key = g_key_file_get_string( + keyfile, "greeter-hotkeys", key_name, NULL); + + guint32 key_code; + if (key == NULL) { + key_code = (guint32) default_char; + } else if (strcmp(key, "") == 0) { + g_warning("Configuration contains empty key for '%s' - falling back to default of %c\n", key_name, default_char); + key_code = (guint32) default_char; + } else { + key_code = (guint32) key[0]; + } + + return gdk_unicode_to_keyval(key_code); +} + +/* Parse the password masking character that should be displayed when typing + * into the password input. + * + * We first attempt to parse a literal -1 or 0, where -1 means to use the + * default character & 0 means to display no characters when typing a password. + * + * If that parsing fails, we attempt to parse the field as a string & use the + * first character of the string. If the string is empty or parsing fails, we + * fall back to the default characer. + * + * Since the related gtk_entry function takes a unsigned int, we use pointers, + * where NULL means "use the default" & all other values indicate an argument + * to the `gtk_entry_set_invisible_char` function. + * + */ +static gunichar *parse_greeter_password_char(GKeyFile *keyfile) +{ + const char *const group_name = "greeter-theme"; + const char *const key_name = "password-character"; + GError *parse_error = NULL; + + // Attempt the int parsing + + const gint int_result = g_key_file_get_integer( + keyfile, group_name, key_name, &parse_error); + // Matches -1 + if (int_result == -1) { + return NULL; + } + + gunichar *result = malloc(sizeof(gunichar)); + // Matches 0 + if (int_result == 0 && parse_error == NULL) { + *result = 0; + return result; + } else if (parse_error != NULL) { + g_error_free(parse_error); + } + + // Atempt the string parsing + + gchar *str_result = g_key_file_get_string( + keyfile, group_name, key_name, NULL); + + // Invalid or 0-length string + if (str_result == NULL || strlen(str_result) == 0) { + if (str_result != NULL) { + free(str_result); + } + free(result); + return NULL; + } + + // Convert to unicode code points + gunichar *unicode_str = g_utf8_to_ucs4(str_result, -1, NULL, NULL, NULL); + free(str_result); + + if (unicode_str == NULL) { + free(result); + return NULL; + } + + *result = unicode_str[0]; + g_free(unicode_str); + return result; +} + +/* Parse the password input alignment, properly handling RTL layouts. + * + * Note that the gboolean returned by this function is meant to be used with + * the `gtk_entry_set_alignment` function. + */ +static gfloat parse_greeter_password_alignment(GKeyFile *keyfile) +{ + gfloat alignment; + + gchar *password_alignment_text = parse_greeter_string( + keyfile, "greeter", "password-alignment", "right"); + gboolean is_rtl = is_rtl_keymap_layout(); + + if (input_string_equals(password_alignment_text, "left")) { + alignment = is_rtl ? 1 : 0; + } else if (input_string_equals(password_alignment_text, "center")) { + alignment = 0.5; + } else { + alignment = is_rtl ? 0 : 1; + } + free(password_alignment_text); + return alignment; +} + +/* Determine if the default Display's Keymap is in the Right-to-Left direction + */ +static gboolean is_rtl_keymap_layout(void) +{ + GdkDisplay *display = gdk_display_get_default(); + if (display == NULL) { + return FALSE; + } + GdkKeymap *keymap = gdk_keymap_get_for_display(display); + PangoDirection text_direction = gdk_keymap_get_direction(keymap); + return text_direction == PANGO_DIRECTION_RTL; +} + +/* Take a string from the config file, trim any whitespace & check it's + * equality with a fixed string. + */ +gboolean input_string_equals(gchar *input_str, const gchar * const fixed_str) { + return strcmp(g_strchomp(input_str), fixed_str) == 0; +} |
