aboutsummaryrefslogtreecommitdiffstats
path: root/src/ui.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/ui.c
downloadlightdm-mini-greeter-master.tar.gz
Initial commit after cloneHEADmaster
Diffstat (limited to 'src/ui.c')
-rw-r--r--src/ui.c430
1 files changed, 430 insertions, 0 deletions
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..6d2d42c
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,430 @@
+/* Functions related to the GUI. */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <lightdm.h>
+
+#include "callbacks.h"
+#include "ui.h"
+#include "utils.h"
+
+
+static UI *new_ui(void);
+static void setup_background_windows(Config *config, UI *ui);
+static GtkWindow *new_background_window(GdkMonitor *monitor);
+static void set_window_to_monitor_size(GdkMonitor *monitor, GtkWindow *window);
+static void hide_mouse_cursor(GtkWidget *window, gpointer user_data);
+static void move_mouse_to_background_window(void);
+static void setup_main_window(Config *config, UI *ui);
+static void place_main_window(GtkWidget *main_window, gpointer user_data);
+static void create_and_attach_layout_container(UI *ui);
+static void create_and_attach_sys_info_label(Config *config, UI *ui);
+static void create_and_attach_password_field(Config *config, UI *ui);
+static void create_and_attach_feedback_label(UI *ui);
+static void attach_config_colors_to_screen(Config *config);
+
+
+/* Initialize the Main Window & it's Children */
+UI *initialize_ui(Config *config)
+{
+ UI *ui = new_ui();
+
+ setup_background_windows(config, ui);
+ move_mouse_to_background_window();
+ setup_main_window(config, ui);
+ create_and_attach_layout_container(ui);
+ create_and_attach_sys_info_label(config, ui);
+ create_and_attach_password_field(config, ui);
+ create_and_attach_feedback_label(ui);
+ attach_config_colors_to_screen(config);
+
+ return ui;
+}
+
+
+/* Create a new UI with all values initialized to NULL */
+static UI *new_ui(void)
+{
+ UI *ui = malloc(sizeof(UI));
+ if (ui == NULL) {
+ g_error("Could not allocate memory for UI");
+ }
+ ui->background_windows = NULL;
+ ui->monitor_count = 0;
+ ui->main_window = NULL;
+ ui->layout_container = NULL;
+ ui->password_label = NULL;
+ ui->password_input = NULL;
+ ui->feedback_label = NULL;
+
+ return ui;
+}
+
+
+/* Create a Background Window for Every Monitor */
+static void setup_background_windows(Config *config, UI *ui)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ ui->monitor_count = gdk_display_get_n_monitors(display);
+ ui->background_windows = malloc((uint) ui->monitor_count * sizeof (GtkWindow *));
+ for (int m = 0; m < ui->monitor_count; m++) {
+ GdkMonitor *monitor = gdk_display_get_monitor(display, m);
+ if (monitor == NULL) {
+ break;
+ }
+
+ GtkWindow *background_window = new_background_window(monitor);
+ ui->background_windows[m] = background_window;
+
+ gboolean show_background_image =
+ (gdk_monitor_is_primary(monitor) || config->show_image_on_all_monitors) &&
+ (strcmp(config->background_image, "\"\"") != 0);
+ if (show_background_image) {
+ GtkStyleContext *style_context =
+ gtk_widget_get_style_context(GTK_WIDGET(background_window));
+ gtk_style_context_add_class(style_context, "with-image");
+ }
+ }
+}
+
+
+/* Create & Configure a Background Window for a Monitor */
+static GtkWindow *new_background_window(GdkMonitor *monitor)
+{
+ GtkWindow *background_window = GTK_WINDOW(gtk_window_new(
+ GTK_WINDOW_TOPLEVEL));
+ gtk_window_set_type_hint(background_window, GDK_WINDOW_TYPE_HINT_DESKTOP);
+ gtk_window_set_keep_below(background_window, TRUE);
+ gtk_widget_set_name(GTK_WIDGET(background_window), "background");
+
+ // Set Window Size to Monitor Size
+ set_window_to_monitor_size(monitor, background_window);
+
+ g_signal_connect(background_window, "realize", G_CALLBACK(hide_mouse_cursor),
+ NULL);
+ // TODO: is this needed?
+ g_signal_connect(background_window, "destroy", G_CALLBACK(gtk_main_quit),
+ NULL);
+
+ return background_window;
+}
+
+
+/* Set the Window's Minimum Size to the Default Screen's Size */
+static void set_window_to_monitor_size(GdkMonitor *monitor, GtkWindow *window)
+{
+ GdkRectangle geometry;
+ gdk_monitor_get_geometry(monitor, &geometry);
+ gtk_widget_set_size_request(
+ GTK_WIDGET(window),
+ geometry.width,
+ geometry.height
+ );
+ gtk_window_move(window, geometry.x, geometry.y);
+ gtk_window_set_resizable(window, FALSE);
+}
+
+
+/* Hide the mouse cursor when it is hovered over the given widget.
+ *
+ * Note: This has no effect when used with a GtkEntry widget.
+ */
+static void hide_mouse_cursor(GtkWidget *widget, gpointer user_data)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ GdkCursor *blank_cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if (window != NULL) {
+ gdk_window_set_cursor(window, blank_cursor);
+ }
+}
+
+
+/* Move the mouse cursor to the upper-left corner of the primary screen.
+ *
+ * This is necessary for hiding the mouse cursor because we cannot hide the
+ * mouse cursor when it is hovered over the GtkEntry password input. Instead,
+ * we hide the cursor when it is over the background windows and then move the
+ * mouse to the corner of the screen where it should hover over the background
+ * window or main window instead.
+ */
+static void move_mouse_to_background_window(void)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ GdkDevice *mouse = gdk_seat_get_pointer(gdk_display_get_default_seat(display));
+ GdkScreen *screen = gdk_display_get_default_screen(display);
+
+ gdk_device_warp(mouse, screen, 0, 0);
+}
+
+
+/* Create & Configure the Main Window */
+static void setup_main_window(Config *config, UI *ui)
+{
+ GtkWindow *main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+
+ gtk_container_set_border_width(GTK_CONTAINER(main_window), config->layout_spacing);
+ gtk_widget_set_name(GTK_WIDGET(main_window), "main");
+
+ g_signal_connect(main_window, "show", G_CALLBACK(place_main_window), config);
+ g_signal_connect(main_window, "realize", G_CALLBACK(hide_mouse_cursor), NULL);
+ g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+
+ ui->main_window = main_window;
+}
+
+
+/* Move the Main Window to the Center of the Primary Monitor
+ *
+ * This is done after the main window is shown(via the "show" signal) so that
+ * the width of the window is properly calculated. Otherwise the returned size
+ * will not include the size of the password label text.
+ */
+static void place_main_window(GtkWidget *main_window, gpointer user_data)
+{
+ // Get the Geometry of the Primary Monitor
+ GdkDisplay *display = gdk_display_get_default();
+ GdkMonitor *primary_monitor = gdk_display_get_primary_monitor(display);
+ GdkRectangle primary_monitor_geometry;
+ gdk_monitor_get_geometry(primary_monitor, &primary_monitor_geometry);
+
+ // Get the Geometry of the Window
+ gint window_width, window_height;
+ gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height);
+
+ Config *config = (Config *) user_data;
+ gint xpos = (gint) (primary_monitor_geometry.width * config->x_pos);
+ gint ypos = (gint) (primary_monitor_geometry.height * config->y_pos);
+
+ gtk_window_move(
+ GTK_WINDOW(main_window),
+ primary_monitor_geometry.x + xpos - window_width / 2,
+ primary_monitor_geometry.y + ypos - window_height / 2);
+}
+
+
+/* Add a Layout Container for All Displayed Widgets */
+static void create_and_attach_layout_container(UI *ui)
+{
+ ui->layout_container = GTK_GRID(gtk_grid_new());
+ gtk_grid_set_column_spacing(ui->layout_container, 5);
+ gtk_grid_set_row_spacing(ui->layout_container, 5);
+
+ gtk_container_add(GTK_CONTAINER(ui->main_window),
+ GTK_WIDGET(ui->layout_container));
+}
+
+/* Create a container for the system information & current time.
+ *
+ * Set the system information text by querying LightDM & the Config, but leave
+ * the time blank & let the timer update it.
+ */
+static void create_and_attach_sys_info_label(Config *config, UI *ui)
+{
+ if (!config->show_sys_info) {
+ return;
+ }
+ // container for system info & time
+ ui->info_container = GTK_GRID(gtk_grid_new());
+ gtk_grid_set_column_spacing(ui->info_container, 0);
+ gtk_grid_set_row_spacing(ui->info_container, 5);
+ gtk_widget_set_name(GTK_WIDGET(ui->info_container), "info");
+
+ // system info: <user>@<hostname>
+ const gchar *hostname = lightdm_get_hostname();
+ gchar *output_string;
+ int output_string_length = asprintf(&output_string, "%s@%s",
+ config->login_user, hostname);
+ if (output_string_length >= 0) {
+ ui->sys_info_label = gtk_label_new(output_string);
+ } else {
+ g_warning("Could not allocate memory for system info string.");
+ ui->sys_info_label = gtk_label_new("");
+ }
+ gtk_label_set_xalign(GTK_LABEL(ui->sys_info_label), 0.0f);
+ gtk_widget_set_name(GTK_WIDGET(ui->sys_info_label), "sys-info");
+
+ // time: filled out by callback
+ ui->time_label = gtk_label_new("");
+ gtk_label_set_xalign(GTK_LABEL(ui->time_label), 1.0f);
+ gtk_widget_set_hexpand(GTK_WIDGET(ui->time_label), TRUE);
+ gtk_widget_set_name(GTK_WIDGET(ui->time_label), "time-info");
+
+ // attach labels to info container, attach info container to layout.
+ gtk_grid_attach(
+ ui->info_container, GTK_WIDGET(ui->sys_info_label), 0, 0, 1, 1);
+ gtk_grid_attach(
+ ui->info_container, GTK_WIDGET(ui->time_label), 1, 0, 1, 1);
+ gtk_grid_attach(
+ ui->layout_container, GTK_WIDGET(ui->info_container), 0, 0, 2, 1);
+}
+
+
+/* Add a label & entry field for the user's password.
+ *
+ * If the `show_password_label` member of `config` is FALSE,
+ * `ui->password_label` is left as NULL.
+ */
+static void create_and_attach_password_field(Config *config, UI *ui)
+{
+ ui->password_input = gtk_entry_new();
+ gtk_entry_set_visibility(GTK_ENTRY(ui->password_input), FALSE);
+ if (config->password_char != NULL) {
+ gtk_entry_set_invisible_char(GTK_ENTRY(ui->password_input), *config->password_char);
+ }
+ gtk_entry_set_alignment(GTK_ENTRY(ui->password_input),
+ config->password_alignment);
+ // TODO: The width is usually a little shorter than we specify. Is there a
+ // way to force this exact character width?
+ // Maybe use 2 GtkBoxes instead of a GtkGrid?
+ gtk_entry_set_width_chars(GTK_ENTRY(ui->password_input),
+ config->password_input_width);
+ gtk_widget_set_name(GTK_WIDGET(ui->password_input), "password");
+ const gint top = config->show_sys_info ? 1 : 0;
+ gtk_grid_attach(ui->layout_container, ui->password_input, 1, top, 1, 1);
+
+ if (config->show_password_label) {
+ ui->password_label = gtk_label_new(config->password_label_text);
+ gtk_label_set_justify(GTK_LABEL(ui->password_label), GTK_JUSTIFY_RIGHT);
+ gtk_grid_attach_next_to(ui->layout_container, ui->password_label,
+ ui->password_input, GTK_POS_LEFT, 1, 1);
+ }
+}
+
+
+/* Add a label for feedback to the user */
+static void create_and_attach_feedback_label(UI *ui)
+{
+ ui->feedback_label = gtk_label_new("");
+ gtk_label_set_justify(GTK_LABEL(ui->feedback_label), GTK_JUSTIFY_CENTER);
+ gtk_widget_set_no_show_all(ui->feedback_label, TRUE);
+ gtk_widget_set_name(GTK_WIDGET(ui->feedback_label), "error");
+
+ GtkWidget *attachment_point;
+ gint width;
+ if (ui->password_label == NULL) {
+ attachment_point = ui->password_input;
+ width = 1;
+ } else {
+ attachment_point = ui->password_label;
+ width = 2;
+ }
+
+ gtk_grid_attach_next_to(ui->layout_container, ui->feedback_label,
+ attachment_point, GTK_POS_BOTTOM, width, 1);
+}
+
+/* Attach a style provider to the screen, using color options from config */
+static void attach_config_colors_to_screen(Config *config)
+{
+ GtkCssProvider* provider = gtk_css_provider_new();
+
+ GdkRGBA *caret_color;
+ if (config->show_input_cursor) {
+ caret_color = config->password_color;
+ } else {
+ caret_color = config->password_background_color;
+ }
+
+ char *css;
+ int css_string_length = asprintf(&css,
+ "* {\n"
+ "font-family: %s;\n"
+ "font-size: %s;\n"
+ "font-weight: %s;\n"
+ "font-style: %s;\n"
+ "}\n"
+ "label {\n"
+ "color: %s;\n"
+ "}\n"
+ "label#error {\n"
+ "color: %s;\n"
+ "}\n"
+ "#background {\n"
+ "background-color: %s;\n"
+ "}\n"
+ "#background.with-image {\n"
+ "background-image: image(url(%s), %s);\n"
+ "background-repeat: no-repeat;\n"
+ "background-size: %s;\n"
+ "background-position: center;\n"
+ "}\n"
+ "#main, #password {\n"
+ "border-width: %s;\n"
+ "border-color: %s;\n"
+ "border-style: solid;\n"
+ "}\n"
+ "#main {\n"
+ "background-color: %s;\n"
+ "}\n"
+ "#password {\n"
+ "color: %s;\n"
+ "caret-color: %s;\n"
+ "background-color: %s;\n"
+ "border-width: %s;\n"
+ "border-color: %s;\n"
+ "border-radius: %s;\n"
+ "background-image: none;\n"
+ "box-shadow: none;\n"
+ "border-image-width: 0;\n"
+ "}\n"
+ "#info {\n"
+ "margin: %s;\n"
+ "}\n"
+ "#info label {\n"
+ "font-family: %s;\n"
+ "font-size: %s;\n"
+ "color: %s;\n"
+ "}\n"
+
+ // *
+ , config->font
+ , config->font_size
+ , config->font_weight
+ , config->font_style
+ // label
+ , gdk_rgba_to_string(config->text_color)
+ // label#error
+ , gdk_rgba_to_string(config->error_color)
+ // #background
+ , gdk_rgba_to_string(config->background_color)
+ // #background.image-background
+ , config->background_image
+ , gdk_rgba_to_string(config->background_color)
+ , config->background_image_size
+ // #main, #password
+ , config->border_width
+ , gdk_rgba_to_string(config->border_color)
+ // #main
+ , gdk_rgba_to_string(config->window_color)
+ // #password
+ , gdk_rgba_to_string(config->password_color)
+ , gdk_rgba_to_string(caret_color)
+ , gdk_rgba_to_string(config->password_background_color)
+ , config->password_border_width
+ , gdk_rgba_to_string(config->password_border_color)
+ , config->password_border_radius
+ // #info
+ , config->sys_info_margin
+ // #info label
+ , config->sys_info_font
+ , config->sys_info_font_size
+ , gdk_rgba_to_string(config->sys_info_color)
+ );
+
+ if (css_string_length >= 0) {
+ gtk_css_provider_load_from_data(provider, css, -1, NULL);
+
+ GdkScreen *screen = gdk_screen_get_default();
+ gtk_style_context_add_provider_for_screen(
+ screen, GTK_STYLE_PROVIDER(provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER + 1);
+ }
+
+
+ g_object_unref(provider);
+}