-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
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);
}