1 /*
2 * Copyright (C) 2014 - 2020, Sean Davis <sean@bluesabre.org>
3 * Copyright (C) 2014, Andrew P. <pan.pav.7c5@gmail.com>
4 * Copyright (C) 2015, Robert Ancell <robert.ancell@canonical.com>
5 * Copyright (C) 2015, Simon Steinbeiß <ochosi@shimmerproject.org>
6 *
7 * This program is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later
10 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
11 * license.
12 */
13
14 #include <math.h>
15 #include <cairo-xlib.h>
16 #include <gtk/gtk.h>
17 #include <gdk/gdkx.h>
18 #include <gdk-pixbuf/gdk-pixbuf.h>
19 #include <string.h>
20 #include <X11/Xatom.h>
21
22 #include "greeterbackground.h"
23 #include "greeterdeprecated.h"
24
25 typedef enum
26 {
27 /* Broken/uninitialized configuration */
28 BACKGROUND_TYPE_INVALID,
29 /* Do not use this monitor */
30 BACKGROUND_TYPE_SKIP,
31 /* Do not override window background */
32 BACKGROUND_TYPE_DEFAULT,
33 /* Solid color */
34 BACKGROUND_TYPE_COLOR,
35 /* Path to image and scaling mode */
36 BACKGROUND_TYPE_IMAGE
37 /* Maybe other types (e.g. gradient) */
38 } BackgroundType;
39
40 static const gchar* BACKGROUND_TYPE_SKIP_VALUE = "#skip";
41 static const gchar* BACKGROUND_TYPE_DEFAULT_VALUE = "#default";
42
43 typedef enum
44 {
45 /* It is not really useful, used for debugging */
46 SCALING_MODE_SOURCE,
47 /* Default mode for values without mode prefix */
48 SCALING_MODE_ZOOMED,
49 SCALING_MODE_STRETCHED
50 } ScalingMode;
51
52 static const gchar* SCALING_MODE_PREFIXES[] = {"#source:", "#zoomed:", "#stretched:", NULL};
53
54 typedef gdouble (*TransitionFunction)(gdouble x);
55 typedef void (*TransitionDraw)(gconstpointer monitor, cairo_t* cr);
56
57 /* Background configuration (parsed from background=... option).
58 Used to fill <Background> */
59 typedef struct
60 {
61 BackgroundType type;
62 union
63 {
64 GdkRGBA color;
65 struct
66 {
67 gchar *path;
68 ScalingMode mode;
69 } image;
70 } options;
71 } BackgroundConfig;
72
73 /* Transition configuration
74 Used as part of <MonitorConfig> and <Monitor> */
75 typedef struct
76 {
77 /* Transition duration, in ms */
78 glong duration;
79 TransitionFunction func;
80 /* Function to draw monitor background */
81 TransitionDraw draw;
82 } TransitionConfig;
83
84 /* Store monitor configuration */
85 typedef struct
86 {
87 BackgroundConfig bg;
88 gboolean user_bg;
89 gboolean laptop;
90
91 TransitionConfig transition;
92 } MonitorConfig;
93
94 /* Actual drawing information attached to monitor.
95 * Used to separate configured monitor background and user background. */
96 typedef struct
97 {
98 gint ref_count;
99 BackgroundType type;
100 union
101 {
102 GdkPixbuf* image;
103 GdkRGBA color;
104 } options;
105 } Background;
106
107 typedef struct
108 {
109 GreeterBackground* object;
110 gint number;
111 gchar* name;
112 GdkRectangle geometry;
113 GtkWindow* window;
114 gulong window_draw_handler_id;
115
116 /* Configured background */
117 Background* background_configured;
118 /* Current monitor background: &background_configured or &background_custom
119 * Monitors with type = BACKGROUND_TYPE_SKIP have background = NULL */
120 Background* background;
121
122 struct
123 {
124 TransitionConfig config;
125
126 /* Old background, stage == 0.0 */
127 Background* from;
128 /* New background, stage == 1.0 */
129 Background* to;
130
131 guint timer_id;
132 gint64 started;
133 /* Current stage */
134 gdouble stage;
135 } transition;
136 } Monitor;
137
138 static const Monitor INVALID_MONITOR_STRUCT = {0};
139
140 struct _GreeterBackground
141 {
142 GObject parent_instance;
143 struct _GreeterBackgroundPrivate* priv;
144 };
145
146 struct _GreeterBackgroundClass
147 {
148 GObjectClass parent_class;
149 };
150
151 typedef struct _GreeterBackgroundPrivate GreeterBackgroundPrivate;
152
153 struct _GreeterBackgroundPrivate
154 {
155 GdkScreen* screen;
156 gulong screen_monitors_changed_handler_id;
157
158 /* Widget to display on active monitor */
159 GtkWidget* child;
160 /* List of groups <GtkAccelGroup*> for greeter screens windows */
161 GSList* accel_groups;
162
163 /* Mapping monitor name <gchar*> to its config <MonitorConfig*> */
164 GHashTable* configs;
165
166 /* Default config for unlisted monitors */
167 MonitorConfig* default_config;
168
169 /* Array of configured monitors for current screen */
170 Monitor* monitors;
171 gsize monitors_size;
172
173 /* Name => <Monitor*>, "Number" => <Monitor*> */
174 GHashTable* monitors_map;
175
176 GList* active_monitors_config;
177 const Monitor* active_monitor;
178 gboolean active_monitor_change_in_progress;
179 gint64 active_monitor_change_last_timestamp;
180
181 /* List of monitors <Monitor*> with user-background=true*/
182 GSList* customized_monitors;
183 /* Initialized by set_custom_background() */
184 BackgroundConfig customized_background;
185
186 /* List of monitors <Monitor*> with laptop=true */
187 GSList* laptop_monitors;
188 /* DBus proxy to catch lid state changing */
189 GDBusProxy* laptop_upower_proxy;
190 /* Cached lid state */
191 gboolean laptop_lid_closed;
192
193 /* Use cursor position to determinate current active monitor (dynamic) */
194 gboolean follow_cursor;
195 /* Use cursor position to determinate initial active monitor */
196 gboolean follow_cursor_to_init;
197 };
198
199 enum
200 {
201 BACKGROUND_SIGNAL_ACTIVE_MONITOR_CHANGED,
202 BACKGROUND_SIGNAL_LAST
203 };
204
205 static guint background_signals[BACKGROUND_SIGNAL_LAST] = {0};
206
207 static const gchar* DBUS_UPOWER_NAME = "org.freedesktop.UPower";
208 static const gchar* DBUS_UPOWER_PATH = "/org/freedesktop/UPower";
209 static const gchar* DBUS_UPOWER_INTERFACE = "org.freedesktop.UPower";
210 static const gchar* DBUS_UPOWER_PROP_LID_IS_PRESENT = "LidIsPresent";
211 static const gchar* DBUS_UPOWER_PROP_LID_IS_CLOSED = "LidIsClosed";
212
213 static const gchar* ACTIVE_MONITOR_CURSOR_TAG = "#cursor";
214
215 G_DEFINE_TYPE_WITH_PRIVATE(GreeterBackground, greeter_background, G_TYPE_OBJECT);
216
217 void greeter_background_disconnect (GreeterBackground* background);
218 static gboolean greeter_background_find_monitor_data(GreeterBackground* background,
219 GHashTable* table,
220 const Monitor* monitor,
221 gpointer* data);
222 static void greeter_background_set_active_monitor (GreeterBackground* background,
223 const Monitor* active);
224 static void greeter_background_get_cursor_position (GreeterBackground* background,
225 gint* x, gint* y);
226 static void greeter_background_set_cursor_position (GreeterBackground* background,
227 gint x, gint y);
228 static void greeter_background_try_init_dbus (GreeterBackground* background);
229 static void greeter_background_stop_dbus (GreeterBackground* background);
230 static gboolean greeter_background_monitor_enabled (GreeterBackground* background,
231 const Monitor* monitor);
232 static void greeter_background_dbus_changed_cb (GDBusProxy* proxy,
233 GVariant* changed_properties,
234 const gchar* const* invalidated_properties,
235 GreeterBackground* background);
236 static void greeter_background_monitors_changed_cb (GdkScreen* screen,
237 GreeterBackground* background);
238 static void greeter_background_child_destroyed_cb (GtkWidget* child,
239 GreeterBackground* background);
240
241 /* struct BackgroundConfig */
242 static gboolean background_config_initialize (BackgroundConfig* config,
243 const gchar* value);
244 static void background_config_finalize (BackgroundConfig* config);
245 static void background_config_copy (const BackgroundConfig* source,
246 BackgroundConfig* dest);
247
248 /* struct MonitorConfig */
249 static void monitor_config_free (MonitorConfig* config);
250 /* Copy source config to dest, return dest. Allocate memory if dest == NULL. */
251 static MonitorConfig* monitor_config_copy (const MonitorConfig* source,
252 MonitorConfig* dest);
253
254 /* struct Background */
255 static Background* background_new (const BackgroundConfig* config,
256 const Monitor* monitor,
257 GHashTable* images_cache);
258 static Background* background_ref (Background* bg);
259 static void background_unref (Background** bg);
260 static void background_finalize (Background* bg);
261
262 /* struct Monitor */
263 static void monitor_finalize (Monitor* info);
264 static void monitor_set_background (Monitor* monitor,
265 Background* background);
266 static void monitor_start_transition (Monitor* monitor,
267 Background* from,
268 Background* to);
269 static void monitor_stop_transition (Monitor* monitor);
270 static gboolean monitor_transition_cb (GtkWidget *widget,
271 GdkFrameClock* frame_clock,
272 Monitor* monitor);
273 static void monitor_transition_draw_alpha (const Monitor* monitor,
274 cairo_t* cr);
275 static void monitor_draw_background (const Monitor* monitor,
276 const Background* background,
277 cairo_t* cr);
278 static gboolean monitor_window_draw_cb (GtkWidget* widget,
279 cairo_t* cr,
280 const Monitor* monitor);
281 static gboolean monitor_window_enter_notify_cb (GtkWidget* widget,
282 GdkEventCrossing* event,
283 const Monitor* monitor);
284
285 static GdkPixbuf* scale_image_file (const gchar* path,
286 ScalingMode mode,
287 gint width, gint height,
288 GHashTable* cache);
289 static GdkPixbuf* scale_image (GdkPixbuf* source,
290 ScalingMode mode,
291 gint width, gint height);
292 static cairo_surface_t* create_root_surface (GdkScreen* screen);
293 static void set_root_pixmap_id (GdkScreen* screen,
294 Display* display,
295 Pixmap xpixmap);
296 static void set_surface_as_root (GdkScreen* screen,
297 cairo_surface_t* surface);
298 static gdouble transition_func_linear (gdouble x);
299 static gdouble transition_func_ease_in_out (gdouble x);
300
301 /* Implemented in lightdm-gtk-greeter.c */
302 gpointer greeter_save_focus(GtkWidget* widget);
303 void greeter_restore_focus(const gpointer saved_data);
304
305 static const MonitorConfig DEFAULT_MONITOR_CONFIG =
306 {
307 .bg =
308 {
309 .type = BACKGROUND_TYPE_COLOR,
310 .options =
311 {
312 .color = {.red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}
313 }
314 },
315 .user_bg = TRUE,
316 .laptop = FALSE,
317 .transition =
318 {
319 .duration = 500,
320 .func = transition_func_ease_in_out,
321 .draw = (TransitionDraw)monitor_transition_draw_alpha
322 }
323 };
324
325 /* Implementation */
326
327 static void
greeter_background_class_init(GreeterBackgroundClass * klass)328 greeter_background_class_init(GreeterBackgroundClass* klass)
329 {
330 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
331
332 background_signals[BACKGROUND_SIGNAL_ACTIVE_MONITOR_CHANGED] =
333 g_signal_new("active-monitor-changed",
334 G_TYPE_FROM_CLASS(gobject_class),
335 G_SIGNAL_RUN_FIRST,
336 0, /* class_offset */
337 NULL /* accumulator */, NULL /* accu_data */,
338 g_cclosure_marshal_VOID__VOID,
339 G_TYPE_NONE, 0);
340 }
341
342 static void
greeter_background_init(GreeterBackground * self)343 greeter_background_init(GreeterBackground* self)
344 {
345 GreeterBackgroundPrivate* priv = greeter_background_get_instance_private (self);
346 self->priv = priv;
347
348 priv->screen = NULL;
349 priv->screen_monitors_changed_handler_id = 0;
350 priv->accel_groups = NULL;
351
352 priv->configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)monitor_config_free);
353 priv->default_config = monitor_config_copy(&DEFAULT_MONITOR_CONFIG, NULL);
354
355 priv->monitors = NULL;
356 priv->monitors_size = 0;
357 priv->monitors_map = NULL;
358
359 priv->customized_monitors = NULL;
360 priv->customized_background.type = BACKGROUND_TYPE_INVALID;
361 priv->active_monitors_config = NULL;
362 priv->active_monitor = NULL;
363
364 priv->laptop_monitors = NULL;
365 priv->laptop_upower_proxy = NULL;
366 priv->laptop_lid_closed = FALSE;
367 }
368
369 GreeterBackground*
greeter_background_new(GtkWidget * child)370 greeter_background_new(GtkWidget* child)
371 {
372 GreeterBackground *background;
373
374 g_return_val_if_fail(child != NULL, NULL);
375
376 background = GREETER_BACKGROUND(g_object_new(greeter_background_get_type(), NULL));
377 background->priv->child = child;
378 g_signal_connect(background->priv->child, "destroy", G_CALLBACK(greeter_background_child_destroyed_cb), background);
379 return background;
380 }
381
382 void
greeter_background_set_active_monitor_config(GreeterBackground * background,const gchar * value)383 greeter_background_set_active_monitor_config(GreeterBackground* background,
384 const gchar* value)
385 {
386 GreeterBackgroundPrivate *priv;
387 gchar **iter;
388 gchar **values;
389
390 g_return_if_fail(GREETER_IS_BACKGROUND(background));
391
392 priv = background->priv;
393
394 g_list_free_full(priv->active_monitors_config, g_free);
395 priv->active_monitors_config = NULL;
396 priv->active_monitor_change_in_progress = FALSE;
397 priv->active_monitor_change_last_timestamp = 0;
398
399 priv->follow_cursor = FALSE;
400 priv->follow_cursor_to_init = FALSE;
401
402 if (!value || !*value)
403 return;
404
405 values = g_strsplit(value, ";", -1);
406
407 for(iter = values; *iter; ++iter)
408 {
409 const gchar* tag = *iter;
410 if (g_strcmp0(tag, ACTIVE_MONITOR_CURSOR_TAG) == 0)
411 {
412 priv->follow_cursor = TRUE;
413 priv->follow_cursor_to_init = (priv->active_monitors_config == NULL);
414 }
415 else
416 priv->active_monitors_config = g_list_prepend(priv->active_monitors_config, g_strdup(tag));
417 }
418 g_strfreev(values);
419
420 priv->active_monitors_config = g_list_reverse(priv->active_monitors_config);
421 }
422
423 void
greeter_background_set_monitor_config(GreeterBackground * background,const gchar * name,const gchar * bg,gint user_bg,gint laptop,gint transition_duration,TransitionType transition_type)424 greeter_background_set_monitor_config(GreeterBackground* background,
425 const gchar* name,
426 const gchar* bg,
427 gint user_bg,
428 gint laptop,
429 gint transition_duration,
430 TransitionType transition_type)
431 {
432 GreeterBackgroundPrivate *priv;
433 MonitorConfig *config;
434 const MonitorConfig *FALLBACK;
435
436 g_return_if_fail(GREETER_IS_BACKGROUND(background));
437
438 priv = background->priv;
439
440 config = g_new0(MonitorConfig, 1);
441
442 FALLBACK = (g_strcmp0(name, GREETER_BACKGROUND_DEFAULT) == 0) ? &DEFAULT_MONITOR_CONFIG : priv->default_config;
443
444 if(!background_config_initialize(&config->bg, bg))
445 background_config_copy(&FALLBACK->bg, &config->bg);
446 config->user_bg = user_bg >= 0 ? user_bg : FALLBACK->user_bg;
447 config->laptop = laptop >= 0 ? laptop : FALLBACK->laptop;
448 config->transition.duration = transition_duration >= 0 ? transition_duration : FALLBACK->transition.duration;
449 config->transition.draw = FALLBACK->transition.draw;
450
451 switch(transition_type)
452 {
453 case TRANSITION_TYPE_NONE:
454 config->transition.func = NULL; break;
455 case TRANSITION_TYPE_LINEAR:
456 config->transition.func = transition_func_linear; break;
457 case TRANSITION_TYPE_EASE_IN_OUT:
458 config->transition.func = transition_func_ease_in_out; break;
459 case TRANSITION_TYPE_FALLBACK:
460 default:
461 config->transition.func = FALLBACK->transition.func;
462 }
463
464 if(FALLBACK == priv->default_config)
465 g_hash_table_insert(priv->configs, g_strdup(name), config);
466 else
467 {
468 if(priv->default_config)
469 monitor_config_free(priv->default_config);
470 priv->default_config = config;
471 }
472 }
473
474 void
greeter_background_remove_monitor_config(GreeterBackground * background,const gchar * name)475 greeter_background_remove_monitor_config(GreeterBackground* background,
476 const gchar* name)
477 {
478 g_return_if_fail(GREETER_IS_BACKGROUND(background));
479 g_hash_table_remove(background->priv->configs, name);
480 }
481
482 gchar**
greeter_background_get_configured_monitors(GreeterBackground * background)483 greeter_background_get_configured_monitors(GreeterBackground* background)
484 {
485 GreeterBackgroundPrivate *priv;
486 GHashTableIter iter;
487 gpointer key;
488 gchar **names;
489 gint n;
490
491 g_return_val_if_fail(GREETER_IS_BACKGROUND(background), NULL);
492
493 priv = background->priv;
494
495 n = g_hash_table_size(priv->configs);
496 names = g_new(gchar*, n + 1);
497 names[n--] = NULL;
498
499 g_hash_table_iter_init(&iter, priv->configs);
500 while(g_hash_table_iter_next(&iter, &key, NULL))
501 names[n--] = g_strdup(key);
502
503 return names;
504 }
505
506 void
greeter_background_connect(GreeterBackground * background,GdkScreen * screen)507 greeter_background_connect(GreeterBackground* background,
508 GdkScreen* screen)
509 {
510 GreeterBackgroundPrivate *priv;
511 GHashTable *images_cache;
512 Monitor *first_not_skipped_monitor = NULL;
513 cairo_region_t *screen_region;
514 gpointer saved_focus = NULL;
515 guint i;
516
517 g_return_if_fail(GREETER_IS_BACKGROUND(background));
518 g_return_if_fail(GDK_IS_SCREEN(screen));
519
520 g_debug("[Background] Connecting to screen: %p (%dx%dpx, %dx%dmm)", screen,
521 greeter_screen_get_width(screen), greeter_screen_get_height(screen),
522 greeter_screen_get_width_mm(screen), greeter_screen_get_height_mm(screen));
523
524 priv = background->priv;
525 saved_focus = NULL;
526 if(priv->screen)
527 {
528 if (priv->active_monitor)
529 saved_focus = greeter_save_focus(priv->child);
530 greeter_background_disconnect(background);
531 }
532
533 priv->screen = screen;
534 priv->monitors_size = greeter_screen_get_n_monitors(screen);
535 priv->monitors = g_new0(Monitor, priv->monitors_size);
536 priv->monitors_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
537
538 g_debug("[Background] Monitors found: %" G_GSIZE_FORMAT, priv->monitors_size);
539
540 /* Used to track situation when all monitors marked as "#skip" */
541 first_not_skipped_monitor = NULL;
542
543 images_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
544 screen_region = cairo_region_create();
545
546 for(i = 0; i < priv->monitors_size; ++i)
547 {
548 const MonitorConfig* config;
549 Monitor* monitor = &priv->monitors[i];
550 const gchar* printable_name;
551 gchar* window_name;
552 GSList* item;
553 Background* bg = NULL;
554
555 monitor->object = background;
556 monitor->name = g_strdup(greeter_screen_get_monitor_plug_name(screen, i));
557 monitor->number = i;
558
559 printable_name = monitor->name ? monitor->name : "<unknown>";
560
561 greeter_screen_get_monitor_geometry(screen, i, &monitor->geometry);
562
563 g_debug("[Background] Monitor: %s #%d (%dx%d at %dx%d)%s", printable_name, i,
564 monitor->geometry.width, monitor->geometry.height,
565 monitor->geometry.x, monitor->geometry.y,
566 ((gint)i == greeter_screen_get_primary_monitor(screen)) ? " primary" : "");
567
568 if(!greeter_background_find_monitor_data(background, priv->configs, monitor, (gpointer*)&config))
569 {
570 g_debug("[Background] No configuration options for monitor %s #%d, using default", printable_name, i);
571 config = priv->default_config;
572 }
573
574 /* Force last skipped monitor to be active monitor, if there is no other choice */
575 if(config->bg.type == BACKGROUND_TYPE_SKIP)
576 {
577 if(i < priv->monitors_size - 1 || first_not_skipped_monitor)
578 {
579 g_debug("[Background] Skipping monitor %s #%d", printable_name, i);
580 continue;
581 }
582 g_debug("[Background] Monitor %s #%d can not be skipped, using default configuration for it", printable_name, i);
583 if(priv->default_config->bg.type != BACKGROUND_TYPE_SKIP)
584 config = priv->default_config;
585 else
586 config = &DEFAULT_MONITOR_CONFIG;
587 }
588
589 /* Simple check to skip fully overlapped monitors.
590 Actually, it's can track only monitors in "mirrors" mode. Nothing more. */
591 if(cairo_region_contains_rectangle(screen_region, &monitor->geometry) == CAIRO_REGION_OVERLAP_IN)
592 {
593 g_debug("[Background] Skipping monitor %s #%d, its area is already used by other monitors", printable_name, i);
594 continue;
595 }
596 cairo_region_union_rectangle(screen_region, &monitor->geometry);
597
598 if(!first_not_skipped_monitor)
599 first_not_skipped_monitor = monitor;
600
601 monitor->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
602 gtk_window_set_type_hint(monitor->window, GDK_WINDOW_TYPE_HINT_DESKTOP);
603 gtk_window_set_keep_below(monitor->window, TRUE);
604 gtk_window_set_resizable(monitor->window, FALSE);
605 gtk_widget_set_app_paintable(GTK_WIDGET(monitor->window), TRUE);
606 gtk_window_set_screen(monitor->window, screen);
607 gtk_widget_set_size_request(GTK_WIDGET(monitor->window), monitor->geometry.width, monitor->geometry.height);
608 gtk_window_move(monitor->window, monitor->geometry.x, monitor->geometry.y);
609 gtk_widget_show(GTK_WIDGET(monitor->window));
610 monitor->window_draw_handler_id = g_signal_connect(G_OBJECT(monitor->window), "draw",
611 G_CALLBACK(monitor_window_draw_cb),
612 monitor);
613
614 window_name = monitor->name ? g_strdup_printf("monitor-%s", monitor->name) : g_strdup_printf("monitor-%d", i);
615 gtk_widget_set_name(GTK_WIDGET(monitor->window), window_name);
616 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(monitor->window)), "lightdm-gtk-greeter");
617 g_free(window_name);
618
619 for(item = priv->accel_groups; item != NULL; item = g_slist_next(item))
620 gtk_window_add_accel_group(monitor->window, item->data);
621
622 g_signal_connect(G_OBJECT(monitor->window), "enter-notify-event",
623 G_CALLBACK(monitor_window_enter_notify_cb), monitor);
624
625 if(config->user_bg)
626 priv->customized_monitors = g_slist_prepend(priv->customized_monitors, monitor);
627
628 if(config->laptop)
629 priv->laptop_monitors = g_slist_prepend(priv->laptop_monitors, monitor);
630
631 if(config->transition.duration && config->transition.func)
632 monitor->transition.config = config->transition;
633
634 monitor->background_configured = background_new(&config->bg, monitor, images_cache);
635 if(!monitor->background_configured)
636 monitor->background_configured = background_new(&DEFAULT_MONITOR_CONFIG.bg, monitor, images_cache);
637
638 if(config->user_bg && priv->customized_background.type != BACKGROUND_TYPE_INVALID)
639 bg = background_new(&priv->customized_background, monitor, images_cache);
640
641 if(bg)
642 {
643 monitor_set_background(monitor, bg);
644 background_unref(&bg);
645 }
646 else
647 monitor_set_background(monitor, monitor->background_configured);
648
649 if(monitor->name)
650 g_hash_table_insert(priv->monitors_map, g_strdup(monitor->name), monitor);
651 g_hash_table_insert(priv->monitors_map, g_strdup_printf("%d", i), monitor);
652 }
653 g_hash_table_unref(images_cache);
654
655 if(priv->laptop_monitors && !priv->laptop_upower_proxy)
656 greeter_background_try_init_dbus(background);
657 else if(!priv->laptop_monitors)
658 greeter_background_stop_dbus(background);
659
660 if(priv->follow_cursor_to_init)
661 {
662 gint x, y;
663 greeter_background_get_cursor_position(background, &x, &y);
664 for(i = 0; i < priv->monitors_size && !priv->active_monitor; ++i)
665 {
666 const Monitor* monitor = &priv->monitors[i];
667 if(greeter_background_monitor_enabled(background, monitor) &&
668 x >= monitor->geometry.x && x < monitor->geometry.x + monitor->geometry.width &&
669 y >= monitor->geometry.y && y < monitor->geometry.y + monitor->geometry.height)
670 {
671 g_debug("[Background] Pointer position will be used to set active monitor: %dx%d", x, y);
672 greeter_background_set_active_monitor(background, monitor);
673 break;
674 }
675 }
676 }
677
678 if(!priv->active_monitor)
679 greeter_background_set_active_monitor(background, NULL);
680
681 if(saved_focus)
682 {
683 greeter_restore_focus(saved_focus);
684 g_free(saved_focus);
685 }
686
687 priv->screen_monitors_changed_handler_id = g_signal_connect(G_OBJECT(screen), "monitors-changed",
688 G_CALLBACK(greeter_background_monitors_changed_cb),
689 background);
690 }
691
692 void
greeter_background_disconnect(GreeterBackground * background)693 greeter_background_disconnect(GreeterBackground* background)
694 {
695 GreeterBackgroundPrivate *priv;
696 guint i;
697
698 g_return_if_fail(GREETER_IS_BACKGROUND(background));
699
700 priv = background->priv;
701
702 if(priv->screen_monitors_changed_handler_id)
703 g_signal_handler_disconnect(priv->screen, priv->screen_monitors_changed_handler_id);
704 priv->screen_monitors_changed_handler_id = 0;
705 priv->screen = NULL;
706 priv->active_monitor = NULL;
707
708 for(i = 0; i < priv->monitors_size; ++i)
709 monitor_finalize(&priv->monitors[i]);
710 g_free(priv->monitors);
711 priv->monitors = NULL;
712 priv->monitors_size = 0;
713
714 g_hash_table_unref(priv->monitors_map);
715 priv->monitors_map = NULL;
716 g_slist_free(priv->customized_monitors);
717 priv->customized_monitors = NULL;
718 g_slist_free(priv->laptop_monitors);
719 priv->laptop_monitors = NULL;
720 }
721
722 /* Moved to separate function to simplify needless and unnecessary syntax expansion in future (regex) */
723 static gboolean
greeter_background_find_monitor_data(GreeterBackground * background,GHashTable * table,const Monitor * monitor,gpointer * data)724 greeter_background_find_monitor_data(GreeterBackground* background,
725 GHashTable* table,
726 const Monitor* monitor,
727 gpointer* data)
728 {
729 if(!monitor->name || !g_hash_table_lookup_extended(table, monitor->name, NULL, data))
730 {
731 gchar* num_str = g_strdup_printf("%d", monitor->number);
732 gboolean result = g_hash_table_lookup_extended(table, num_str, NULL, data);
733 g_free(num_str);
734 return result;
735 }
736 return TRUE;
737 }
738
739 static void
greeter_background_set_active_monitor(GreeterBackground * background,const Monitor * active)740 greeter_background_set_active_monitor(GreeterBackground* background,
741 const Monitor* active)
742 {
743 GreeterBackgroundPrivate* priv = background->priv;
744 gint x, y;
745 gint64 timestamp;
746
747 if (priv->active_monitor_change_in_progress)
748 return;
749
750 /* Prevents infinite signal emmission between two monitors (LP: #1410406, #1509780)
751 * There are some rare scenarios when using multiple monitors that cause the greeter
752 * to switch back and forth between the monitors indefinitely. By comparing the
753 * timestamp at this precision (1/10th of a second), this should no longer be
754 * possible.
755 */
756 if (active != NULL)
757 {
758 timestamp = floor(g_get_monotonic_time () * 0.00001);
759 if (timestamp == priv->active_monitor_change_last_timestamp)
760 {
761 g_debug("[Background] Preventing infinite monitor loop");
762 return;
763 }
764 }
765
766 priv->active_monitor_change_in_progress = TRUE;
767 priv->active_monitor_change_last_timestamp = floor(g_get_monotonic_time () * 0.00001);
768
769 if(active && !active->background)
770 {
771 if(priv->active_monitor)
772 goto active_monitor_change_complete;
773 active = NULL;
774 }
775
776 /* Auto */
777 if(!active)
778 {
779 /* Normal way: at least one configured active monitor is not disabled */
780 GList* iter;
781 for(iter = priv->active_monitors_config; iter && !active; iter = g_list_next(iter))
782 {
783 const Monitor* monitor = g_hash_table_lookup(priv->monitors_map, iter->data);
784 if(monitor && monitor->background && greeter_background_monitor_enabled(background, monitor))
785 active = monitor;
786 if(active)
787 g_debug("[Background] Active monitor is not specified, using first enabled monitor from 'active-monitor' list");
788 }
789
790 /* All monitors listed in active-monitor-config are disabled (or option is empty) */
791
792 /* Using primary monitor */
793 if(!active)
794 {
795 gint num = greeter_screen_get_primary_monitor(priv->screen);
796 if ((guint)num >= priv->monitors_size)
797 goto active_monitor_change_complete;
798
799 active = &priv->monitors[num];
800 if(!active->background || !greeter_background_monitor_enabled(background, active))
801 active = NULL;
802 if(active)
803 g_debug("[Background] Active monitor is not specified, using primary monitor");
804 }
805
806 /* Fallback: first enabled and/or not skipped monitor (screen always have one) */
807 if(!active)
808 {
809 guint i;
810 const Monitor* first_not_skipped = NULL;
811 for(i = 0; i < priv->monitors_size && !active; ++i)
812 {
813 const Monitor* monitor = &priv->monitors[i];
814 if(!monitor->background)
815 continue;
816 if(greeter_background_monitor_enabled(background, monitor))
817 active = monitor;
818 if(!first_not_skipped)
819 first_not_skipped = active;
820 }
821 if(!active)
822 active = first_not_skipped;
823 if(active)
824 g_debug("[Background] Active monitor is not specified, using first enabled monitor");
825 }
826
827 if(!active && priv->laptop_monitors)
828 {
829 active = priv->laptop_monitors->data;
830 g_debug("[Background] Active monitor is not specified, using laptop monitor");
831 }
832 }
833
834 if(!active)
835 {
836 if(priv->active_monitor)
837 g_warning("[Background] Active monitor is not specified, failed to identify. Active monitor stays the same: %s #%d",
838 priv->active_monitor->name, priv->active_monitor->number);
839 else
840 g_warning("[Background] Active monitor is not specified, failed to identify. Active monitor stays the same: <not defined>");
841 goto active_monitor_change_complete;
842 }
843
844 if(active == priv->active_monitor)
845 goto active_monitor_change_complete;
846
847 priv->active_monitor = active;
848
849 if (priv->active_monitor == NULL)
850 goto active_monitor_change_complete;
851
852 if(priv->child)
853 {
854 GtkWidget* old_parent = gtk_widget_get_parent(priv->child);
855 gpointer focus = greeter_save_focus(priv->child);
856
857 if(old_parent)
858 greeter_widget_reparent(priv->child, GTK_WIDGET(active->window));
859 else
860 gtk_container_add(GTK_CONTAINER(active->window), priv->child);
861
862 gtk_window_present(active->window);
863 greeter_restore_focus(focus);
864 g_free(focus);
865 }
866 else
867 g_warning("[Background] Child widget is destroyed or not defined");
868
869 g_debug("[Background] Active monitor changed to: %s #%d", active->name, active->number);
870 g_signal_emit(background, background_signals[BACKGROUND_SIGNAL_ACTIVE_MONITOR_CHANGED], 0);
871
872 greeter_background_get_cursor_position(background, &x, &y);
873 /* Do not center cursor if it is already inside active monitor */
874 if(x < active->geometry.x || x >= active->geometry.x + active->geometry.width ||
875 y < active->geometry.y || y >= active->geometry.y + active->geometry.height)
876 greeter_background_set_cursor_position(background,
877 active->geometry.x + active->geometry.width/2,
878 active->geometry.y + active->geometry.height/2);
879
880 active_monitor_change_complete:
881 priv->active_monitor_change_in_progress = FALSE;
882 return;
883 }
884
885 static void
greeter_background_get_cursor_position(GreeterBackground * background,gint * x,gint * y)886 greeter_background_get_cursor_position(GreeterBackground* background,
887 gint* x, gint* y)
888 {
889 GreeterBackgroundPrivate* priv = background->priv;
890
891 GdkDisplay* display = gdk_screen_get_display(priv->screen);
892 GdkDeviceManager* device_manager = greeter_display_get_device_manager(display);
893 GdkDevice* device = greeter_device_manager_get_client_pointer(device_manager);
894 gdk_device_get_position(device, NULL, x, y);
895 }
896
897 static void
greeter_background_set_cursor_position(GreeterBackground * background,gint x,gint y)898 greeter_background_set_cursor_position(GreeterBackground* background,
899 gint x, gint y)
900 {
901 GreeterBackgroundPrivate* priv = background->priv;
902
903 GdkDisplay* display = gdk_screen_get_display(priv->screen);
904 GdkDeviceManager* device_manager = greeter_display_get_device_manager(display);
905 gdk_device_warp(greeter_device_manager_get_client_pointer(device_manager), priv->screen, x, y);
906 }
907
908 static void
greeter_background_try_init_dbus(GreeterBackground * background)909 greeter_background_try_init_dbus(GreeterBackground* background)
910 {
911 GreeterBackgroundPrivate *priv;
912 GError *error = NULL;
913 GVariant *variant;
914 gboolean lid_present;
915
916 g_debug("[Background] Creating DBus proxy");
917
918 priv = background->priv;
919
920 if(priv->laptop_upower_proxy)
921 greeter_background_stop_dbus(background);
922
923 priv->laptop_upower_proxy = g_dbus_proxy_new_for_bus_sync(
924 G_BUS_TYPE_SYSTEM,
925 G_DBUS_PROXY_FLAGS_NONE,
926 NULL, /* interface info */
927 DBUS_UPOWER_NAME,
928 DBUS_UPOWER_PATH,
929 DBUS_UPOWER_INTERFACE,
930 NULL, /* cancellable */
931 &error);
932 if(!priv->laptop_upower_proxy)
933 {
934 if(error)
935 g_warning("[Background] Failed to create dbus proxy: %s", error->message);
936 g_clear_error(&error);
937 return;
938 }
939
940 variant = g_dbus_proxy_get_cached_property(priv->laptop_upower_proxy, DBUS_UPOWER_PROP_LID_IS_PRESENT);
941 lid_present = g_variant_get_boolean(variant);
942 g_variant_unref(variant);
943
944 g_debug("[Background] UPower.%s property value: %d", DBUS_UPOWER_PROP_LID_IS_PRESENT, lid_present);
945
946 if(!lid_present)
947 greeter_background_stop_dbus(background);
948 else
949 {
950 variant = g_dbus_proxy_get_cached_property(priv->laptop_upower_proxy, DBUS_UPOWER_PROP_LID_IS_CLOSED);
951 priv->laptop_lid_closed = g_variant_get_boolean(variant);
952 g_variant_unref(variant);
953
954 g_signal_connect(priv->laptop_upower_proxy, "g-properties-changed",
955 G_CALLBACK(greeter_background_dbus_changed_cb), background);
956 }
957 }
958
959 static void
greeter_background_stop_dbus(GreeterBackground * background)960 greeter_background_stop_dbus(GreeterBackground* background)
961 {
962 if(!background->priv->laptop_upower_proxy)
963 return;
964 g_clear_object(&background->priv->laptop_upower_proxy);
965 }
966
967 static gboolean
greeter_background_monitor_enabled(GreeterBackground * background,const Monitor * monitor)968 greeter_background_monitor_enabled(GreeterBackground* background,
969 const Monitor* monitor)
970 {
971 GreeterBackgroundPrivate* priv = background->priv;
972
973 if(priv->laptop_upower_proxy && g_slist_find(priv->laptop_monitors, monitor))
974 return !priv->laptop_lid_closed;
975 return TRUE;
976 }
977
978 static void
greeter_background_dbus_changed_cb(GDBusProxy * proxy,GVariant * changed_properties,const gchar * const * invalidated_properties,GreeterBackground * background)979 greeter_background_dbus_changed_cb(GDBusProxy* proxy,
980 GVariant* changed_properties,
981 const gchar* const* invalidated_properties,
982 GreeterBackground* background)
983 {
984 GreeterBackgroundPrivate *priv;
985 GVariant *variant;
986 gboolean new_state;
987
988 g_return_if_fail(GREETER_IS_BACKGROUND(background));
989
990 priv = background->priv;
991
992 variant = g_dbus_proxy_get_cached_property(priv->laptop_upower_proxy, DBUS_UPOWER_PROP_LID_IS_CLOSED);
993 new_state = g_variant_get_boolean(variant);
994 g_variant_unref(variant);
995
996 if(new_state == priv->laptop_lid_closed)
997 return;
998
999 priv->laptop_lid_closed = new_state;
1000 g_debug("[Background] UPower: lid state changed to '%s'", priv->laptop_lid_closed ? "closed" : "opened");
1001
1002 if(priv->laptop_monitors)
1003 {
1004 if(priv->laptop_lid_closed)
1005 {
1006 if(g_slist_find(priv->laptop_monitors, priv->active_monitor))
1007 greeter_background_set_active_monitor(background, NULL);
1008 }
1009 else
1010 {
1011 if(!priv->follow_cursor)
1012 greeter_background_set_active_monitor(background, NULL);
1013 }
1014 }
1015 }
1016
1017 static void
greeter_background_monitors_changed_cb(GdkScreen * screen,GreeterBackground * background)1018 greeter_background_monitors_changed_cb(GdkScreen* screen,
1019 GreeterBackground* background)
1020 {
1021 g_return_if_fail(GREETER_IS_BACKGROUND(background));
1022 greeter_background_connect(background, screen);
1023 }
1024
1025 static void
greeter_background_child_destroyed_cb(GtkWidget * child,GreeterBackground * background)1026 greeter_background_child_destroyed_cb(GtkWidget* child,
1027 GreeterBackground* background)
1028 {
1029 background->priv->child = NULL;
1030 }
1031
1032 void
greeter_background_set_custom_background(GreeterBackground * background,const gchar * value)1033 greeter_background_set_custom_background(GreeterBackground* background,
1034 const gchar* value)
1035 {
1036 GreeterBackgroundPrivate *priv;
1037 GHashTable *images_cache = NULL;
1038 GSList *iter;
1039
1040 g_return_if_fail(GREETER_IS_BACKGROUND(background));
1041
1042 priv = background->priv;
1043
1044 if(priv->customized_background.type != BACKGROUND_TYPE_INVALID)
1045 background_config_finalize(&priv->customized_background);
1046 background_config_initialize(&priv->customized_background, value);
1047
1048 if(!priv->customized_monitors)
1049 return;
1050
1051 if(priv->customized_background.type == BACKGROUND_TYPE_IMAGE)
1052 images_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
1053
1054 for(iter = priv->customized_monitors; iter; iter = g_slist_next(iter))
1055 {
1056 Monitor *monitor = iter->data;
1057
1058 /* Old background_custom (if used) will be unrefed in monitor_set_background() */
1059 Background* bg = NULL;
1060 if(priv->customized_background.type != BACKGROUND_TYPE_INVALID)
1061 bg = background_new(&priv->customized_background, monitor, images_cache);
1062 if(bg)
1063 {
1064 monitor_set_background(monitor, bg);
1065 background_unref(&bg);
1066 }
1067 else
1068 monitor_set_background(monitor, monitor->background_configured);
1069 }
1070
1071 if(images_cache)
1072 g_hash_table_unref(images_cache);
1073 }
1074
1075 void
greeter_background_save_xroot(GreeterBackground * background)1076 greeter_background_save_xroot(GreeterBackground* background)
1077 {
1078 GreeterBackgroundPrivate *priv;
1079 cairo_surface_t *surface;
1080 cairo_t *cr;
1081 gsize i;
1082
1083 const GdkRGBA ROOT_COLOR = {1.0, 1.0, 1.0, 1.0};
1084
1085 g_return_if_fail(GREETER_IS_BACKGROUND(background));
1086
1087 priv = background->priv;
1088 surface = create_root_surface(priv->screen);
1089 cr = cairo_create(surface);
1090
1091 gdk_cairo_set_source_rgba(cr, &ROOT_COLOR);
1092 cairo_paint(cr);
1093
1094 for(i = 0; i < priv->monitors_size; ++i)
1095 {
1096 const Monitor* monitor = &priv->monitors[i];
1097 if(!monitor->background)
1098 continue;
1099 cairo_save(cr);
1100 cairo_translate(cr, monitor->geometry.x, monitor->geometry.y);
1101 monitor_draw_background(monitor, monitor->background, cr);
1102 cairo_restore(cr);
1103 }
1104 set_surface_as_root(priv->screen, surface);
1105
1106 cairo_destroy(cr);
1107 cairo_surface_destroy(surface);
1108 }
1109
1110 const GdkRectangle*
greeter_background_get_active_monitor_geometry(GreeterBackground * background)1111 greeter_background_get_active_monitor_geometry(GreeterBackground* background)
1112 {
1113 GreeterBackgroundPrivate *priv;
1114
1115 g_return_val_if_fail(GREETER_IS_BACKGROUND(background), NULL);
1116
1117 priv = background->priv;
1118
1119 return priv->active_monitor ? &priv->active_monitor->geometry : NULL;
1120 }
1121
1122 void
greeter_background_add_accel_group(GreeterBackground * background,GtkAccelGroup * group)1123 greeter_background_add_accel_group(GreeterBackground* background,
1124 GtkAccelGroup* group)
1125 {
1126 GreeterBackgroundPrivate *priv;
1127
1128 g_return_if_fail(GREETER_IS_BACKGROUND(background));
1129 g_return_if_fail(group != NULL);
1130
1131 priv = background->priv;
1132
1133 if(priv->monitors)
1134 {
1135 guint i;
1136 for(i = 0; i < priv->monitors_size; ++i)
1137 if(priv->monitors[i].window)
1138 gtk_window_add_accel_group(priv->monitors[i].window, group);
1139 }
1140
1141 priv->accel_groups = g_slist_append(priv->accel_groups, group);
1142 }
1143
1144 static gboolean
background_config_initialize(BackgroundConfig * config,const gchar * value)1145 background_config_initialize(BackgroundConfig* config,
1146 const gchar* value)
1147 {
1148 config->type = BACKGROUND_TYPE_INVALID;
1149 if(!value || strlen(value) == 0)
1150 return FALSE;
1151 if(g_strcmp0(value, BACKGROUND_TYPE_SKIP_VALUE) == 0)
1152 config->type = BACKGROUND_TYPE_SKIP;
1153 else if(g_strcmp0(value, BACKGROUND_TYPE_DEFAULT_VALUE) == 0)
1154 config->type = BACKGROUND_TYPE_DEFAULT;
1155 else if(gdk_rgba_parse(&config->options.color, value))
1156 config->type = BACKGROUND_TYPE_COLOR;
1157 else
1158 {
1159 const gchar** prefix = SCALING_MODE_PREFIXES;
1160 while(*prefix && !g_str_has_prefix(value, *prefix))
1161 ++prefix;
1162
1163 if(*prefix)
1164 {
1165 config->options.image.mode = (ScalingMode)(prefix - SCALING_MODE_PREFIXES);
1166 value += strlen(*prefix);
1167 }
1168 else
1169 config->options.image.mode = SCALING_MODE_ZOOMED;
1170
1171 config->options.image.path = g_strdup(value);
1172 config->type = BACKGROUND_TYPE_IMAGE;
1173 }
1174 return TRUE;
1175 }
1176
1177 static void
background_config_finalize(BackgroundConfig * config)1178 background_config_finalize(BackgroundConfig* config)
1179 {
1180 switch(config->type)
1181 {
1182 case BACKGROUND_TYPE_IMAGE:
1183 g_free(config->options.image.path);
1184 break;
1185 case BACKGROUND_TYPE_COLOR:
1186 case BACKGROUND_TYPE_DEFAULT:
1187 case BACKGROUND_TYPE_SKIP:
1188 break;
1189 case BACKGROUND_TYPE_INVALID:
1190 default:
1191 g_return_if_reached();
1192 }
1193
1194 config->type = BACKGROUND_TYPE_INVALID;
1195 }
1196
1197 static void
background_config_copy(const BackgroundConfig * source,BackgroundConfig * dest)1198 background_config_copy(const BackgroundConfig* source,
1199 BackgroundConfig* dest)
1200 {
1201 *dest = *source;
1202
1203 switch(dest->type)
1204 {
1205 case BACKGROUND_TYPE_IMAGE:
1206 dest->options.image.path = g_strdup(source->options.image.path);
1207 break;
1208 case BACKGROUND_TYPE_COLOR:
1209 case BACKGROUND_TYPE_DEFAULT:
1210 case BACKGROUND_TYPE_SKIP:
1211 break;
1212 case BACKGROUND_TYPE_INVALID:
1213 default:
1214 g_return_if_reached();
1215 }
1216 }
1217
1218 static void
monitor_config_free(MonitorConfig * config)1219 monitor_config_free(MonitorConfig* config)
1220 {
1221 background_config_finalize(&config->bg);
1222 g_free(config);
1223 }
1224
monitor_config_copy(const MonitorConfig * source,MonitorConfig * dest)1225 static MonitorConfig* monitor_config_copy(const MonitorConfig* source,
1226 MonitorConfig* dest)
1227 {
1228 if(!dest)
1229 dest = g_new0(MonitorConfig, 1);
1230 background_config_copy(&source->bg, &dest->bg);
1231 dest->user_bg = source->user_bg;
1232 dest->laptop = source->laptop;
1233 dest->transition = source->transition;
1234 return dest;
1235 }
1236
1237 static Background*
background_new(const BackgroundConfig * config,const Monitor * monitor,GHashTable * images_cache)1238 background_new(const BackgroundConfig* config,
1239 const Monitor* monitor,
1240 GHashTable* images_cache)
1241 {
1242 Background *result;
1243 Background bg = {0};
1244
1245 switch(config->type)
1246 {
1247 case BACKGROUND_TYPE_IMAGE:
1248 bg.options.image = scale_image_file(config->options.image.path, config->options.image.mode,
1249 monitor->geometry.width, monitor->geometry.height,
1250 images_cache);
1251 if(!bg.options.image)
1252 {
1253 g_warning("[Background] Failed to read wallpaper: %s", config->options.image.path);
1254 return NULL;
1255 }
1256 break;
1257 case BACKGROUND_TYPE_COLOR:
1258 bg.options.color = config->options.color;
1259 break;
1260 case BACKGROUND_TYPE_DEFAULT:
1261 break;
1262 case BACKGROUND_TYPE_SKIP:
1263 case BACKGROUND_TYPE_INVALID:
1264 default:
1265 g_return_val_if_reached(NULL);
1266 }
1267
1268 bg.type = config->type;
1269 bg.ref_count = 1;
1270
1271 result = g_new(Background, 1);
1272 *result = bg;
1273 return result;
1274 }
1275
1276 static Background*
background_ref(Background * bg)1277 background_ref(Background* bg)
1278 {
1279 bg->ref_count++;
1280 return bg;
1281 }
1282
1283 static void
background_unref(Background ** bg)1284 background_unref(Background** bg)
1285 {
1286 if(!*bg)
1287 return;
1288 (*bg)->ref_count--;
1289 if((*bg)->ref_count == 0)
1290 {
1291 background_finalize(*bg);
1292 *bg = NULL;
1293 }
1294 }
1295
1296 static void
background_finalize(Background * bg)1297 background_finalize(Background* bg)
1298 {
1299 switch(bg->type)
1300 {
1301 case BACKGROUND_TYPE_IMAGE:
1302 g_clear_object(&bg->options.image);
1303 break;
1304 case BACKGROUND_TYPE_COLOR:
1305 case BACKGROUND_TYPE_DEFAULT:
1306 break;
1307 case BACKGROUND_TYPE_SKIP:
1308 case BACKGROUND_TYPE_INVALID:
1309 default:
1310 g_return_if_reached();
1311 }
1312
1313 bg->type = BACKGROUND_TYPE_INVALID;
1314 }
1315
1316 static void
monitor_set_background(Monitor * monitor,Background * background)1317 monitor_set_background(Monitor* monitor,
1318 Background* background)
1319 {
1320 if(monitor->background == background)
1321 return;
1322 monitor_stop_transition(monitor);
1323
1324 switch(background->type)
1325 {
1326 case BACKGROUND_TYPE_IMAGE:
1327 case BACKGROUND_TYPE_COLOR:
1328 gtk_widget_set_app_paintable(GTK_WIDGET(monitor->window), TRUE);
1329 if(monitor->transition.config.duration > 0 && monitor->background &&
1330 monitor->background->type != BACKGROUND_TYPE_DEFAULT)
1331 monitor_start_transition(monitor, monitor->background, background);
1332 break;
1333 case BACKGROUND_TYPE_DEFAULT:
1334 gtk_widget_set_app_paintable(GTK_WIDGET(monitor->window), FALSE);
1335 break;
1336 case BACKGROUND_TYPE_SKIP:
1337 case BACKGROUND_TYPE_INVALID:
1338 default:
1339 g_return_if_reached();
1340 }
1341
1342 background_unref(&monitor->background);
1343 monitor->background = background_ref(background);
1344 gtk_widget_queue_draw(GTK_WIDGET(monitor->window));
1345 }
1346
1347 static void
monitor_start_transition(Monitor * monitor,Background * from,Background * to)1348 monitor_start_transition(Monitor* monitor,
1349 Background* from,
1350 Background* to)
1351 {
1352 monitor_stop_transition(monitor);
1353
1354 monitor->transition.from = background_ref(from);
1355 monitor->transition.to = background_ref(to);
1356
1357 monitor->transition.started = g_get_monotonic_time();
1358 monitor->transition.timer_id = gtk_widget_add_tick_callback(GTK_WIDGET(monitor->window),
1359 (GtkTickCallback)monitor_transition_cb,
1360 monitor,
1361 NULL);
1362 monitor->transition.stage = 0;
1363 }
1364
1365 static void
monitor_stop_transition(Monitor * monitor)1366 monitor_stop_transition(Monitor* monitor)
1367 {
1368 if(!monitor->transition.timer_id)
1369 return;
1370 gtk_widget_remove_tick_callback(GTK_WIDGET(monitor->window), monitor->transition.timer_id);
1371 monitor->transition.timer_id = 0;
1372 monitor->transition.started = 0;
1373 monitor->transition.stage = 0;
1374 background_unref(&monitor->transition.to);
1375 background_unref(&monitor->transition.from);
1376 }
1377
1378 static gboolean
monitor_transition_cb(GtkWidget * widget,GdkFrameClock * frame_clock,Monitor * monitor)1379 monitor_transition_cb(GtkWidget *widget,
1380 GdkFrameClock* frame_clock,
1381 Monitor* monitor)
1382 {
1383 gint64 span;
1384 gdouble x;
1385
1386 if(!monitor->transition.timer_id)
1387 return G_SOURCE_REMOVE;
1388
1389 span = g_get_monotonic_time() - monitor->transition.started;
1390 x = CLAMP(span/monitor->transition.config.duration/1000.0, 0.0, 1.0);
1391 monitor->transition.stage = monitor->transition.config.func(x);
1392
1393 if(x >= 1.0)
1394 monitor_stop_transition(monitor);
1395
1396 gtk_widget_queue_draw(GTK_WIDGET(monitor->window));
1397 return x >= 1.0 ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
1398 }
1399
1400 static void
monitor_transition_draw_alpha(const Monitor * monitor,cairo_t * cr)1401 monitor_transition_draw_alpha(const Monitor* monitor,
1402 cairo_t* cr)
1403 {
1404 cairo_pattern_t* alpha_pattern;
1405
1406 monitor_draw_background(monitor, monitor->transition.from, cr);
1407
1408 cairo_push_group(cr);
1409 monitor_draw_background(monitor, monitor->transition.to, cr);
1410 cairo_pop_group_to_source(cr);
1411
1412 alpha_pattern = cairo_pattern_create_rgba(0.0, 0.0, 0.0, monitor->transition.stage);
1413 cairo_mask(cr, alpha_pattern);
1414 cairo_pattern_destroy(alpha_pattern);
1415 }
1416
1417 static void
monitor_finalize(Monitor * monitor)1418 monitor_finalize(Monitor* monitor)
1419 {
1420 if(monitor->transition.config.duration)
1421 {
1422 monitor_stop_transition(monitor);
1423 if(monitor->transition.timer_id)
1424 g_source_remove(monitor->transition.timer_id);
1425 monitor->transition.config.duration = 0;
1426 }
1427
1428 if(monitor->window_draw_handler_id)
1429 g_signal_handler_disconnect(monitor->window, monitor->window_draw_handler_id);
1430
1431 background_unref(&monitor->background_configured);
1432 background_unref(&monitor->background);
1433
1434 if(monitor->window)
1435 {
1436 GtkWidget* child = gtk_bin_get_child(GTK_BIN(monitor->window));
1437 if(child) /* remove greeter widget to avoid "destroy" signal */
1438 gtk_container_remove(GTK_CONTAINER(monitor->window), child);
1439 gtk_widget_destroy(GTK_WIDGET(monitor->window));
1440 }
1441
1442 g_free(monitor->name);
1443
1444 *monitor = INVALID_MONITOR_STRUCT;
1445 }
1446
1447 static void
monitor_draw_background(const Monitor * monitor,const Background * background,cairo_t * cr)1448 monitor_draw_background(const Monitor* monitor,
1449 const Background* background,
1450 cairo_t* cr)
1451 {
1452 g_return_if_fail(monitor != NULL);
1453 g_return_if_fail(background != NULL);
1454
1455 switch(background->type)
1456 {
1457 case BACKGROUND_TYPE_IMAGE:
1458 if(background->options.image)
1459 {
1460 gdk_cairo_set_source_pixbuf(cr, background->options.image, 0, 0);
1461 cairo_paint(cr);
1462 }
1463 break;
1464 case BACKGROUND_TYPE_COLOR:
1465 cairo_rectangle(cr, 0, 0, monitor->geometry.width, monitor->geometry.height);
1466 gdk_cairo_set_source_rgba(cr, &background->options.color);
1467 cairo_fill(cr);
1468 break;
1469 case BACKGROUND_TYPE_DEFAULT:
1470 break;
1471 case BACKGROUND_TYPE_SKIP:
1472 case BACKGROUND_TYPE_INVALID:
1473 default:
1474 g_return_if_reached();
1475 }
1476 }
1477
1478 static gboolean
monitor_window_draw_cb(GtkWidget * widget,cairo_t * cr,const Monitor * monitor)1479 monitor_window_draw_cb(GtkWidget* widget,
1480 cairo_t* cr,
1481 const Monitor* monitor)
1482 {
1483 if(!monitor->background)
1484 return FALSE;
1485
1486 if(monitor->transition.started)
1487 monitor->transition.config.draw(monitor, cr);
1488 else
1489 monitor_draw_background(monitor, monitor->background, cr);
1490
1491 return FALSE;
1492 }
1493
1494 static gboolean
monitor_window_enter_notify_cb(GtkWidget * widget,GdkEventCrossing * event,const Monitor * monitor)1495 monitor_window_enter_notify_cb(GtkWidget* widget,
1496 GdkEventCrossing* event,
1497 const Monitor* monitor)
1498 {
1499 if(monitor->object->priv->active_monitor == monitor)
1500 {
1501 GdkWindow *gdkwindow = gtk_widget_get_window (widget);
1502 Window window = GDK_WINDOW_XID (gdkwindow);
1503 Display *display = GDK_WINDOW_XDISPLAY (gdkwindow);
1504 XEvent ev = {0};
1505
1506 static Atom wm_protocols = None;
1507 static Atom wm_take_focus = None;
1508
1509 if (!wm_protocols)
1510 wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);
1511 if (!wm_take_focus)
1512 wm_take_focus = XInternAtom(display, "WM_TAKE_FOCUS", False);
1513
1514 ev.xclient.type = ClientMessage;
1515 ev.xclient.window = window;
1516 ev.xclient.message_type = wm_protocols;
1517 ev.xclient.format = 32;
1518 ev.xclient.data.l[0] = wm_take_focus;
1519 ev.xclient.data.l[1] = CurrentTime;
1520 XSendEvent(display, window, False, 0L, &ev);
1521 }
1522 else if(monitor->object->priv->follow_cursor && greeter_background_monitor_enabled(monitor->object, monitor))
1523 greeter_background_set_active_monitor(monitor->object, monitor);
1524 return FALSE;
1525 }
1526
1527 static GdkPixbuf*
scale_image_file(const gchar * path,ScalingMode mode,gint width,gint height,GHashTable * cache)1528 scale_image_file(const gchar* path,
1529 ScalingMode mode,
1530 gint width, gint height,
1531 GHashTable* cache)
1532 {
1533 gchar* key = NULL;
1534 GdkPixbuf* pixbuf = NULL;
1535
1536 if(cache)
1537 {
1538 key = g_strdup_printf("%s\n%d %dx%d", path, mode, width, height);
1539 if(g_hash_table_lookup_extended(cache, key, NULL, (gpointer*)&pixbuf))
1540 {
1541 g_free(key);
1542 return GDK_PIXBUF(g_object_ref(pixbuf));
1543 }
1544 }
1545
1546 if(!cache || !g_hash_table_lookup_extended(cache, path, NULL, (gpointer*)&pixbuf))
1547 {
1548 GError *error = NULL;
1549 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1550 if(error)
1551 {
1552 g_warning("[Background] Failed to load background: %s", error->message);
1553 g_clear_error(&error);
1554 }
1555 else if(cache)
1556 g_hash_table_insert(cache, g_strdup(path), g_object_ref(pixbuf));
1557 }
1558 else
1559 pixbuf = g_object_ref(pixbuf);
1560
1561 if(pixbuf)
1562 {
1563 GdkPixbuf* scaled = scale_image(pixbuf, mode, width, height);
1564 if(cache)
1565 g_hash_table_insert(cache, g_strdup(key), g_object_ref(scaled));
1566 g_object_unref(pixbuf);
1567 pixbuf = scaled;
1568 }
1569
1570 g_free(key);
1571
1572 return pixbuf;
1573 }
1574
1575 static GdkPixbuf*
scale_image(GdkPixbuf * source,ScalingMode mode,gint width,gint height)1576 scale_image(GdkPixbuf* source,
1577 ScalingMode mode,
1578 gint width, gint height)
1579 {
1580 if(mode == SCALING_MODE_ZOOMED)
1581 {
1582 gint offset_x = 0;
1583 gint offset_y = 0;
1584 gint p_width = gdk_pixbuf_get_width(source);
1585 gint p_height = gdk_pixbuf_get_height(source);
1586 gdouble scale_x = (gdouble)width / p_width;
1587 gdouble scale_y = (gdouble)height / p_height;
1588 GdkPixbuf *pixbuf;
1589
1590 if(scale_x < scale_y)
1591 {
1592 scale_x = scale_y;
1593 offset_x = (width - (p_width * scale_x)) / 2;
1594 }
1595 else
1596 {
1597 scale_y = scale_x;
1598 offset_y = (height - (p_height * scale_y)) / 2;
1599 }
1600
1601 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE,
1602 gdk_pixbuf_get_bits_per_sample (source),
1603 width, height);
1604 gdk_pixbuf_composite(source, pixbuf, 0, 0, width, height,
1605 offset_x, offset_y, scale_x, scale_y, GDK_INTERP_BILINEAR, 0xFF);
1606 return pixbuf;
1607 }
1608 else if(mode == SCALING_MODE_STRETCHED)
1609 {
1610 return gdk_pixbuf_scale_simple(source, width, height, GDK_INTERP_BILINEAR);
1611 }
1612 return GDK_PIXBUF(g_object_ref(source));
1613 }
1614
1615 /* The following code for setting a RetainPermanent background pixmap was taken
1616 originally from Gnome, with some fixes from MATE. see:
1617 https://github.com/mate-desktop/mate-desktop/blob/master/libmate-desktop/mate-bg.c */
1618 static cairo_surface_t*
create_root_surface(GdkScreen * screen)1619 create_root_surface(GdkScreen* screen)
1620 {
1621 gint number, width, height;
1622 Display *display;
1623 Pixmap pixmap;
1624 cairo_surface_t *surface;
1625
1626 number = greeter_screen_get_number (screen);
1627 width = greeter_screen_get_width (screen);
1628 height = greeter_screen_get_height (screen);
1629
1630 /* Open a new connection so with Retain Permanent so the pixmap remains when the greeter quits */
1631 greeter_gdk_flush ();
1632 display = XOpenDisplay (gdk_display_get_name (gdk_screen_get_display (screen)));
1633 if (!display)
1634 {
1635 g_warning("[Background] Failed to create root pixmap");
1636 return NULL;
1637 }
1638
1639 XSetCloseDownMode (display, RetainPermanent);
1640 pixmap = XCreatePixmap (display, RootWindow (display, number), width, height, DefaultDepth (display, number));
1641 XCloseDisplay (display);
1642
1643 /* Convert into a Cairo surface */
1644 surface = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen),
1645 pixmap,
1646 GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
1647 width, height);
1648
1649 return surface;
1650 }
1651
1652 /* Sets the "ESETROOT_PMAP_ID" property to later be used to free the pixmap */
1653 static void
set_root_pixmap_id(GdkScreen * screen,Display * display,Pixmap xpixmap)1654 set_root_pixmap_id(GdkScreen* screen,
1655 Display* display,
1656 Pixmap xpixmap)
1657 {
1658
1659 Window xroot = RootWindow (display, greeter_screen_get_number (screen));
1660 const char *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};
1661 Atom atoms[G_N_ELEMENTS(atom_names)] = {0};
1662
1663 Atom type;
1664 int format;
1665 unsigned long nitems, after;
1666 unsigned char *data_root, *data_esetroot;
1667
1668 /* Get atoms for both properties in an array, only if they exist.
1669 * This method is to avoid multiple round-trips to Xserver
1670 */
1671 if (XInternAtoms (display, (char **)atom_names, G_N_ELEMENTS(atom_names), True, atoms) &&
1672 atoms[0] != None && atoms[1] != None)
1673 {
1674
1675 XGetWindowProperty (display, xroot, atoms[0], 0L, 1L, False, AnyPropertyType,
1676 &type, &format, &nitems, &after, &data_root);
1677 if (data_root && type == XA_PIXMAP && format == 32 && nitems == 1)
1678 {
1679 XGetWindowProperty (display, xroot, atoms[1], 0L, 1L, False, AnyPropertyType,
1680 &type, &format, &nitems, &after, &data_esetroot);
1681 if (data_esetroot && type == XA_PIXMAP && format == 32 && nitems == 1)
1682 {
1683 Pixmap xrootpmap = *((Pixmap *) data_root);
1684 Pixmap esetrootpmap = *((Pixmap *) data_esetroot);
1685 XFree (data_root);
1686 XFree (data_esetroot);
1687
1688 greeter_error_trap_push ();
1689 if (xrootpmap && xrootpmap == esetrootpmap) {
1690 XKillClient (display, xrootpmap);
1691 }
1692 if (esetrootpmap && esetrootpmap != xrootpmap) {
1693 XKillClient (display, esetrootpmap);
1694 }
1695
1696 XSync (display, False);
1697 greeter_error_trap_pop_ignored ();
1698 }
1699 }
1700 }
1701
1702 /* Get atoms for both properties in an array, create them if needed.
1703 * This method is to avoid multiple round-trips to Xserver
1704 */
1705 if (!XInternAtoms (display, (char **)atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||
1706 atoms[0] == None || atoms[1] == None) {
1707 g_warning("[Background] Could not create atoms needed to set root pixmap id/properties.\n");
1708 return;
1709 }
1710
1711 /* Set new _XROOTMAP_ID and ESETROOT_PMAP_ID properties */
1712 XChangeProperty (display, xroot, atoms[0], XA_PIXMAP, 32,
1713 PropModeReplace, (unsigned char *) &xpixmap, 1);
1714
1715 XChangeProperty (display, xroot, atoms[1], XA_PIXMAP, 32,
1716 PropModeReplace, (unsigned char *) &xpixmap, 1);
1717 }
1718
1719 /**
1720 * set_surface_as_root:
1721 * @screen: the #GdkScreen to change root background on
1722 * @surface: the #cairo_surface_t to set root background from.
1723 * Must be an xlib surface backing a pixmap.
1724 *
1725 * Set the root pixmap, and properties pointing to it. We
1726 * do this atomically with a server grab to make sure that
1727 * we won't leak the pixmap if somebody else it setting
1728 * it at the same time. (This assumes that they follow the
1729 * same conventions we do). @surface should come from a call
1730 * to create_root_surface().
1731 **/
1732 static void
set_surface_as_root(GdkScreen * screen,cairo_surface_t * surface)1733 set_surface_as_root(GdkScreen* screen,
1734 cairo_surface_t* surface)
1735 {
1736 Display *display;
1737 Pixmap pixmap_id;
1738 Window xroot;
1739
1740 g_return_if_fail(cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_XLIB);
1741
1742 /* Desktop background pixmap should be created from dummy X client since most
1743 * applications will try to kill it with XKillClient later when changing pixmap
1744 */
1745 display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
1746 pixmap_id = cairo_xlib_surface_get_drawable (surface);
1747 xroot = RootWindow (display, greeter_screen_get_number(screen));
1748
1749 XGrabServer (display);
1750
1751 XSetWindowBackgroundPixmap (display, xroot, pixmap_id);
1752 set_root_pixmap_id (screen, display, pixmap_id);
1753 XClearWindow (display, xroot);
1754
1755 XFlush (display);
1756 XUngrabServer (display);
1757 }
1758
1759 static gdouble
transition_func_linear(gdouble x)1760 transition_func_linear(gdouble x)
1761 {
1762 return x;
1763 }
1764
1765 static gdouble
transition_func_ease_in_out(gdouble x)1766 transition_func_ease_in_out(gdouble x)
1767 {
1768 return (1 - cos(M_PI*x))/2;
1769 }
1770