aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.c
diff options
context:
space:
mode:
authorSpacedio <spacedio@thernusen.net>2026-02-26 19:20:14 -0500
committerSpacedio <spacedio@thernusen.net>2026-02-26 19:20:14 -0500
commitdb657412e6ae93341ed42d59df6aef564a739a1f (patch)
tree6f67f55f6dc61fb0afa8829a950f0dbf1a0aeadf /src/config.c
downloadlightdm-mini-greeter-db657412e6ae93341ed42d59df6aef564a739a1f.tar.gz
Initial commit after cloneHEADmaster
Diffstat (limited to 'src/config.c')
-rw-r--r--src/config.c435
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;
+}