Merge lp:~kalgasnik/lightdm-gtk-greeter/background-transition into lp:~lightdm-gtk-greeter-team/lightdm-gtk-greeter/trunk

Proposed by Andrew P.
Status: Merged
Merged at revision: 316
Proposed branch: lp:~kalgasnik/lightdm-gtk-greeter/background-transition
Merge into: lp:~lightdm-gtk-greeter-team/lightdm-gtk-greeter/trunk
Diff against target: 1452 lines (+646/-224)
5 files modified
src/Makefile.am (+3/-2)
src/greeterbackground.c (+543/-167)
src/greeterbackground.h (+5/-5)
src/lightdm-gtk-greeter.c (+95/-44)
src/lightdm-gtk-greeter.glade (+0/-6)
To merge this branch: bzr merge lp:~kalgasnik/lightdm-gtk-greeter/background-transition
Reviewer Review Type Date Requested Status
Simon Steinbeiß Approve
Review via email: mp+241082@code.launchpad.net

Description of the change

Add transition to backgrounds change.

1. Transition function: easy-in-out
2. Animation: drawing new background with different alpha over old background
3. Duration: 500ms
4. Delay before changing to user's background: 250ms

At first I thought to make all these things configurable, but someone can say that it is too much.
"transition-duration" is enough.

Enabled by default, I'm not sure that it is good idea.

To post a comment you must log in.
Revision history for this message
Andrew P. (kalgasnik) wrote :

There is one bug related to this branch: https://bugs.launchpad.net/ubuntu/+source/lightdm-gtk-greeter/+bug/1394639/comments/8

I can't confirm it so I can't fix it. I'm not even sure that trunk version is not effected.

Revision history for this message
Sean Davis (bluesabre) wrote :

Can you provide some examples for the configuration in this case?

Revision history for this message
Andrew P. (kalgasnik) wrote :

[greeter]
# 500 by default, 0 to disable transition effect
transition-duration = <duration-in-ms>

# Transition function, "none" to disable transition effect
# "easy-in-out" by default
transition-type = easy-in-out|linear|none

Revision history for this message
Sean Davis (bluesabre) wrote :

When I try this on my laptop, there does not seem to be a transition...

transition-duration=1000
transition-type=easy-in-out

Revision history for this message
Andrew P. (kalgasnik) wrote :

Maybe you have user-background = false or identical default and user's backgrounds.
It must looks like http://youtu.be/hVtoEtG-OXE

Oh, "easy" -> "ease", my inglish is not so well. But it isn't the source of the problem.

Revision history for this message
Simon Steinbeiß (ochosi) wrote :

I've checked out your branch (mostly testing, not code review) and it works just fine! Nice work on that and sorry it took so long.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2014-08-20 23:23:59 +0000
+++ src/Makefile.am 2014-12-10 07:59:47 +0000
@@ -41,7 +41,8 @@
41 $(LIBX11_LIBS) \41 $(LIBX11_LIBS) \
42 $(LIBINDICATOR_LIBS) \42 $(LIBINDICATOR_LIBS) \
43 $(LIBIDO_LIBS) \43 $(LIBIDO_LIBS) \
44 $(LIBXKLAVIER_LIBS)44 $(LIBXKLAVIER_LIBS)\
45 -lm
4546
46if MAINTAINER_MODE47if MAINTAINER_MODE
4748
@@ -65,4 +66,4 @@
65EXTRA_DIST = \66EXTRA_DIST = \
66 lightdm-gtk-greeter.glade \67 lightdm-gtk-greeter.glade \
67 lightdm-gtk-greeter-fallback.css \68 lightdm-gtk-greeter-fallback.css \
68 lightdm-gtk-greeter-application.css
69\ No newline at end of file69\ No newline at end of file
70 lightdm-gtk-greeter-application.css
7071
=== modified file 'src/greeterbackground.c'
--- src/greeterbackground.c 2014-08-31 17:45:52 +0000
+++ src/greeterbackground.c 2014-12-10 07:59:47 +0000
@@ -1,4 +1,5 @@
11
2#include <math.h>
2#include <cairo-xlib.h>3#include <cairo-xlib.h>
3#include <gtk/gtk.h>4#include <gtk/gtk.h>
4#include <gdk/gdkx.h>5#include <gdk/gdkx.h>
@@ -14,6 +15,8 @@
14 BACKGROUND_TYPE_INVALID,15 BACKGROUND_TYPE_INVALID,
15 /* Do not use this monitor */16 /* Do not use this monitor */
16 BACKGROUND_TYPE_SKIP,17 BACKGROUND_TYPE_SKIP,
18 /* Do not override window background */
19 BACKGROUND_TYPE_DEFAULT,
17 /* Solid color */20 /* Solid color */
18 BACKGROUND_TYPE_COLOR,21 BACKGROUND_TYPE_COLOR,
19 /* Path to image and scaling mode */22 /* Path to image and scaling mode */
@@ -22,6 +25,7 @@
22} BackgroundType;25} BackgroundType;
2326
24static const gchar* BACKGROUND_TYPE_SKIP_VALUE = "#skip";27static const gchar* BACKGROUND_TYPE_SKIP_VALUE = "#skip";
28static const gchar* BACKGROUND_TYPE_DEFAULT_VALUE = "#default";
2529
26typedef enum30typedef enum
27{31{
@@ -34,6 +38,9 @@
3438
35static const gchar* SCALING_MODE_PREFIXES[] = {"#source:", "#zoomed:", "#stretched:", NULL};39static const gchar* SCALING_MODE_PREFIXES[] = {"#source:", "#zoomed:", "#stretched:", NULL};
3640
41typedef gdouble (*TransitionFunction)(gdouble x);
42typedef void (*TransitionDraw)(gconstpointer monitor, cairo_t* cr);
43
37/* Background configuration (parsed from background=... option).44/* Background configuration (parsed from background=... option).
38 Used to fill <Background> */45 Used to fill <Background> */
39typedef struct46typedef struct
@@ -50,18 +57,32 @@
50 } options;57 } options;
51} BackgroundConfig;58} BackgroundConfig;
5259
60/* Transition configuration
61 Used to as part of <MonitorConfig> and <Monitor> */
62typedef struct
63{
64 /* Transition duration, in ms */
65 glong duration;
66 TransitionFunction func;
67 /* Function to draw monitor background */
68 TransitionDraw draw;
69} TransitionConfig;
70
53/* Store monitor configuration */71/* Store monitor configuration */
54typedef struct72typedef struct
55{73{
56 BackgroundConfig bg;74 BackgroundConfig bg;
57 gboolean user_bg;75 gboolean user_bg;
58 gboolean laptop;76 gboolean laptop;
77
78 TransitionConfig transition;
59} MonitorConfig;79} MonitorConfig;
6080
61/* Actual drawing information attached to monitor.81/* Actual drawing information attached to monitor.
62 * Used to separate configured monitor background and user background. */82 * Used to separate configured monitor background and user background. */
63typedef struct83typedef struct
64{84{
85 gint ref_count;
65 BackgroundType type;86 BackgroundType type;
66 union87 union
67 {88 {
@@ -80,14 +101,29 @@
80 gulong window_draw_handler_id;101 gulong window_draw_handler_id;
81102
82 /* Configured background */103 /* Configured background */
83 Background background_configured;104 Background* background_configured;
84 /* Background used to display user-background */
85 Background background_custom;
86 /* Current monitor background: &background_configured or &background_custom105 /* Current monitor background: &background_configured or &background_custom
87 * Monitors with type = BACKGROUND_TYPE_SKIP have background = NULL */106 * Monitors with type = BACKGROUND_TYPE_SKIP have background = NULL */
88 const Background* background;107 Background* background;
108
109 struct
110 {
111 TransitionConfig config;
112
113 /* Old background, stage == 0.0 */
114 Background* from;
115 /* New background, stage == 1.0 */
116 Background* to;
117
118 guint timer_id;
119 gint64 started;
120 /* Current stage */
121 gdouble stage;
122 } transition;
89} Monitor;123} Monitor;
90124
125static const Monitor INVALID_MONITOR_STRUCT = {0};
126
91struct _GreeterBackground127struct _GreeterBackground
92{128{
93 GObject parent_instance;129 GObject parent_instance;
@@ -141,6 +177,9 @@
141 gboolean follow_cursor;177 gboolean follow_cursor;
142 /* Use cursor position to determinate initial active monitor */178 /* Use cursor position to determinate initial active monitor */
143 gboolean follow_cursor_to_init;179 gboolean follow_cursor_to_init;
180
181 /* Name => transition function, inited in set_monitor_config() */
182 GHashTable* transition_types;
144};183};
145184
146enum185enum
@@ -151,20 +190,6 @@
151190
152static guint background_signals[BACKGROUND_SIGNAL_LAST] = {0};191static guint background_signals[BACKGROUND_SIGNAL_LAST] = {0};
153192
154static const MonitorConfig DEFAULT_MONITOR_CONFIG =
155{
156 .bg =
157 {
158 .type = BACKGROUND_TYPE_COLOR,
159 .options =
160 {
161 .color = {.red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}
162 }
163 },
164 .user_bg = TRUE,
165 .laptop = FALSE
166};
167
168static const gchar* DBUS_UPOWER_NAME = "org.freedesktop.UPower";193static const gchar* DBUS_UPOWER_NAME = "org.freedesktop.UPower";
169static const gchar* DBUS_UPOWER_PATH = "/org/freedesktop/UPower";194static const gchar* DBUS_UPOWER_PATH = "/org/freedesktop/UPower";
170static const gchar* DBUS_UPOWER_INTERFACE = "org.freedesktop.UPower";195static const gchar* DBUS_UPOWER_INTERFACE = "org.freedesktop.UPower";
@@ -177,15 +202,13 @@
177202
178void greeter_background_set_active_monitor_config (GreeterBackground* background,203void greeter_background_set_active_monitor_config (GreeterBackground* background,
179 const gchar* value);204 const gchar* value);
180void greeter_background_set_default_config (GreeterBackground* background,
181 const gchar* bg,
182 gboolean user_bg,
183 gboolean laptop);
184void greeter_background_set_monitor_config (GreeterBackground* background,205void greeter_background_set_monitor_config (GreeterBackground* background,
185 const gchar* name,206 const gchar* name,
186 const gchar* bg,207 const gchar* bg, /* NULL to use fallback value */
187 gboolean user_bg, gboolean user_bg_used,208 gboolean user_bg, gboolean user_bg_used,
188 gboolean laptop, gboolean laptop_used);209 gboolean laptop, gboolean laptop_used,
210 gint transition_duration, /* -1 to use fallback value */
211 const gchar* transition_type); /* NULL to use fallback value */
189void greeter_background_remove_monitor_config (GreeterBackground* background,212void greeter_background_remove_monitor_config (GreeterBackground* background,
190 const gchar* name);213 const gchar* name);
191gchar** greeter_background_get_configured_monitors (GreeterBackground* background);214gchar** greeter_background_get_configured_monitors (GreeterBackground* background);
@@ -212,6 +235,8 @@
212 GreeterBackground* background);235 GreeterBackground* background);
213static void greeter_background_monitors_changed_cb (GdkScreen* screen,236static void greeter_background_monitors_changed_cb (GdkScreen* screen,
214 GreeterBackground* background);237 GreeterBackground* background);
238static void greeter_background_child_destroyed_cb (GtkWidget* child,
239 GreeterBackground* background);
215240
216/* struct BackgroundConfig */241/* struct BackgroundConfig */
217static gboolean background_config_initialize (BackgroundConfig* config,242static gboolean background_config_initialize (BackgroundConfig* config,
@@ -227,17 +252,28 @@
227 MonitorConfig* dest);252 MonitorConfig* dest);
228253
229/* struct Background */254/* struct Background */
230static gboolean background_initialize (Background* bg,255static Background* background_new (const BackgroundConfig* config,
231 const BackgroundConfig* config,
232 const Monitor* monitor,256 const Monitor* monitor,
233 GHashTable* images_cache);257 GHashTable* images_cache);
258static Background* background_ref (Background* bg);
259static void background_unref (Background** bg);
234static void background_finalize (Background* bg);260static void background_finalize (Background* bg);
235261
236/* struct Monitor */262/* struct Monitor */
237static void monitor_finalize (Monitor* info);263static void monitor_finalize (Monitor* info);
238static void monitor_set_background (Monitor* monitor,264static void monitor_set_background (Monitor* monitor,
239 const Background* background);265 Background* background);
266static void monitor_start_transition (Monitor* monitor,
267 Background* from,
268 Background* to);
269static void monitor_stop_transition (Monitor* monitor);
270static gboolean monitor_transition_cb (GtkWidget *widget,
271 GdkFrameClock* frame_clock,
272 Monitor* monitor);
273static void monitor_transition_draw_alpha (const Monitor* monitor,
274 cairo_t* cr);
240static void monitor_draw_background (const Monitor* monitor,275static void monitor_draw_background (const Monitor* monitor,
276 const Background* background,
241 cairo_t* cr);277 cairo_t* cr);
242static gboolean monitor_window_draw_cb (GtkWidget* widget,278static gboolean monitor_window_draw_cb (GtkWidget* widget,
243 cairo_t* cr,279 cairo_t* cr,
@@ -259,7 +295,32 @@
259 Pixmap xpixmap);295 Pixmap xpixmap);
260static void set_surface_as_root (GdkScreen* screen,296static void set_surface_as_root (GdkScreen* screen,
261 cairo_surface_t* surface);297 cairo_surface_t* surface);
262298static gdouble transition_func_linear (gdouble x);
299static gdouble transition_func_easy_in_out (gdouble x);
300
301/* Implemented in lightdm-gtk-greeter.c */
302gpointer greeter_save_focus(GtkWidget* widget);
303void greeter_restore_focus(const gpointer saved_data);
304
305static 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_easy_in_out,
321 .draw = (TransitionDraw)monitor_transition_draw_alpha
322 }
323};
263324
264/* Implementation */325/* Implementation */
265326
@@ -302,11 +363,14 @@
302 self->priv->laptop_lid_closed = FALSE;363 self->priv->laptop_lid_closed = FALSE;
303}364}
304365
305GreeterBackground* 366GreeterBackground*
306greeter_background_new(GtkWidget* child)367greeter_background_new(GtkWidget* child)
307{368{
369 g_return_val_if_fail(child != NULL, NULL);
370
308 GreeterBackground* background = GREETER_BACKGROUND(g_object_new(greeter_background_get_type(), NULL));371 GreeterBackground* background = GREETER_BACKGROUND(g_object_new(greeter_background_get_type(), NULL));
309 background->priv->child = child;372 background->priv->child = child;
373 g_signal_connect(background->priv->child, "destroy", G_CALLBACK(greeter_background_child_destroyed_cb), background);
310 return background;374 return background;
311}375}
312376
@@ -346,42 +410,55 @@
346}410}
347411
348void412void
349greeter_background_set_default_config(GreeterBackground* background,
350 const gchar* bg,
351 gboolean user_bg,
352 gboolean laptop)
353{
354 g_return_if_fail(GREETER_IS_BACKGROUND(background));
355 GreeterBackgroundPrivate* priv = background->priv;
356
357 if(priv->default_config)
358 monitor_config_free(priv->default_config);
359
360 priv->default_config = g_new0(MonitorConfig, 1);
361 if(!background_config_initialize(&priv->default_config->bg, bg))
362 background_config_copy(&DEFAULT_MONITOR_CONFIG.bg, &priv->default_config->bg);
363 priv->default_config->user_bg = user_bg;
364 priv->default_config->laptop = laptop;
365}
366
367void
368greeter_background_set_monitor_config(GreeterBackground* background,413greeter_background_set_monitor_config(GreeterBackground* background,
369 const gchar* name,414 const gchar* name,
370 const gchar* bg,415 const gchar* bg,
371 gboolean user_bg, gboolean user_bg_used,416 gboolean user_bg, gboolean user_bg_used,
372 gboolean laptop, gboolean laptop_used)417 gboolean laptop, gboolean laptop_used,
418 gint transition_duration,
419 const gchar* transition_type)
373{420{
374 g_return_if_fail(GREETER_IS_BACKGROUND(background));421 g_return_if_fail(GREETER_IS_BACKGROUND(background));
375 GreeterBackgroundPrivate* priv = background->priv;422 GreeterBackgroundPrivate* priv = background->priv;
376423
377 MonitorConfig* config = g_new0(MonitorConfig, 1);424 MonitorConfig* config = g_new0(MonitorConfig, 1);
378425
426 const MonitorConfig* FALLBACK = (g_strcmp0(name, GREETER_BACKGROUND_DEFAULT) == 0) ? &DEFAULT_MONITOR_CONFIG : priv->default_config;
427
379 if(!background_config_initialize(&config->bg, bg))428 if(!background_config_initialize(&config->bg, bg))
380 background_config_copy(&priv->default_config->bg, &config->bg);429 background_config_copy(&FALLBACK->bg, &config->bg);
381 config->user_bg = user_bg_used ? user_bg : priv->default_config->user_bg;430 config->user_bg = user_bg_used ? user_bg : FALLBACK->user_bg;
382 config->laptop = laptop_used ? laptop : priv->default_config->laptop;431 config->laptop = laptop_used ? laptop : FALLBACK->laptop;
383432 config->transition.duration = transition_duration >= 0 ? transition_duration : FALLBACK->transition.duration;
384 g_hash_table_insert(priv->configs, g_strdup(name), config);433 config->transition.draw = FALLBACK->transition.draw;
434
435 if(transition_type)
436 {
437 if(!priv->transition_types)
438 {
439 priv->transition_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
440 g_hash_table_insert(priv->transition_types, g_strdup("none"), NULL);
441 g_hash_table_insert(priv->transition_types, g_strdup("linear"), transition_func_linear);
442 g_hash_table_insert(priv->transition_types, g_strdup("easy-in-out"), transition_func_easy_in_out);
443 }
444 if(!g_hash_table_lookup_extended(priv->transition_types, transition_type, NULL, (gpointer*)&config->transition.func))
445 {
446 g_warning("[Background] Invalid transition type for '%s' monitor: '%s'. Using fallback value.",
447 name, transition_type);
448 config->transition.func = FALLBACK->transition.func;
449 }
450 }
451 else
452 config->transition.func = FALLBACK->transition.func;
453
454 if(FALLBACK == priv->default_config)
455 g_hash_table_insert(priv->configs, g_strdup(name), config);
456 else
457 {
458 if(priv->default_config)
459 monitor_config_free(priv->default_config);
460 priv->default_config = config;
461 }
385}462}
386463
387void464void
@@ -418,12 +495,9 @@
418 g_return_if_fail(GREETER_IS_BACKGROUND(background));495 g_return_if_fail(GREETER_IS_BACKGROUND(background));
419 g_return_if_fail(GDK_IS_SCREEN(screen));496 g_return_if_fail(GDK_IS_SCREEN(screen));
420497
421 g_debug("Connecting to screen");498 g_debug("[Background] Connecting to screen: %p", screen);
422499
423 GreeterBackgroundPrivate* priv = background->priv;500 GreeterBackgroundPrivate* priv = background->priv;
424 gulong screen_monitors_changed_handler_id = (priv->screen == screen) ? priv->screen_monitors_changed_handler_id : 0;
425 if(screen_monitors_changed_handler_id)
426 priv->screen_monitors_changed_handler_id = 0;
427501
428 if(priv->screen)502 if(priv->screen)
429 greeter_background_disconnect(background);503 greeter_background_disconnect(background);
@@ -433,6 +507,8 @@
433 priv->monitors = g_new0(Monitor, priv->monitors_size);507 priv->monitors = g_new0(Monitor, priv->monitors_size);
434 priv->monitors_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);508 priv->monitors_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
435509
510 g_debug("[Background] Monitors found: %" G_GSIZE_FORMAT, priv->monitors_size);
511
436 /* Used to track situation when all monitors marked as "#skip" */512 /* Used to track situation when all monitors marked as "#skip" */
437 Monitor* first_not_skipped_monitor = NULL;513 Monitor* first_not_skipped_monitor = NULL;
438514
@@ -449,25 +525,28 @@
449525
450 const gchar* printable_name = monitor->name ? monitor->name : "<unknown>";526 const gchar* printable_name = monitor->name ? monitor->name : "<unknown>";
451527
452 if(!greeter_background_find_monitor_data(background, priv->configs, monitor, (gpointer*)&config))
453 {
454 g_debug("No configuration options for monitor %s #%d, using default", printable_name, i);
455 config = priv->default_config;
456 }
457
458 gdk_screen_get_monitor_geometry(screen, i, &monitor->geometry);528 gdk_screen_get_monitor_geometry(screen, i, &monitor->geometry);
459529
460 g_debug("Monitor: %s #%d (%dx%d at %dx%d)%s", printable_name, i,530 g_debug("[Background] Monitor: %s #%d (%dx%d at %dx%d)%s", printable_name, i,
461 monitor->geometry.width, monitor->geometry.height,531 monitor->geometry.width, monitor->geometry.height,
462 monitor->geometry.x, monitor->geometry.y,532 monitor->geometry.x, monitor->geometry.y,
463 (i == gdk_screen_get_primary_monitor(screen)) ? " primary" : "");533 (i == gdk_screen_get_primary_monitor(screen)) ? " primary" : "");
464534
535 if(!greeter_background_find_monitor_data(background, priv->configs, monitor, (gpointer*)&config))
536 {
537 g_debug("[Background] No configuration options for monitor %s #%d, using default", printable_name, i);
538 config = priv->default_config;
539 }
540
465 /* Force last skipped monitor to be active monitor, if there is no other choice */541 /* Force last skipped monitor to be active monitor, if there is no other choice */
466 if(config->bg.type == BACKGROUND_TYPE_SKIP)542 if(config->bg.type == BACKGROUND_TYPE_SKIP)
467 {543 {
468 if(i < priv->monitors_size - 1 || first_not_skipped_monitor)544 if(i < priv->monitors_size - 1 || first_not_skipped_monitor)
545 {
546 g_debug("[Background] Skipping monitor %s #%d", printable_name, i);
469 continue;547 continue;
470 g_debug("Monitor %s #%d can not be skipped, using default configuration for it", printable_name, i);548 }
549 g_debug("[Background] Monitor %s #%d can not be skipped, using default configuration for it", printable_name, i);
471 if(priv->default_config->bg.type != BACKGROUND_TYPE_SKIP)550 if(priv->default_config->bg.type != BACKGROUND_TYPE_SKIP)
472 config = priv->default_config;551 config = priv->default_config;
473 else552 else
@@ -489,6 +568,12 @@
489 monitor->window_draw_handler_id = g_signal_connect(G_OBJECT(monitor->window), "draw",568 monitor->window_draw_handler_id = g_signal_connect(G_OBJECT(monitor->window), "draw",
490 G_CALLBACK(monitor_window_draw_cb),569 G_CALLBACK(monitor_window_draw_cb),
491 monitor);570 monitor);
571
572 gchar* window_name = monitor->name ? g_strdup_printf("monitor-%s", monitor->name) : g_strdup_printf("monitor-%d", i);
573 gtk_widget_set_name(GTK_WIDGET(monitor->window), window_name);
574 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(monitor->window)), "lightdm-gtk-greeter");
575 g_free(window_name);
576
492 GSList* item;577 GSList* item;
493 for(item = priv->accel_groups; item != NULL; item = g_slist_next(item))578 for(item = priv->accel_groups; item != NULL; item = g_slist_next(item))
494 gtk_window_add_accel_group(monitor->window, item->data);579 gtk_window_add_accel_group(monitor->window, item->data);
@@ -503,9 +588,13 @@
503 if(config->laptop)588 if(config->laptop)
504 priv->laptop_monitors = g_slist_prepend(priv->laptop_monitors, monitor);589 priv->laptop_monitors = g_slist_prepend(priv->laptop_monitors, monitor);
505590
506 if(!background_initialize(&monitor->background_configured, &config->bg, monitor, images_cache))591 monitor->background_configured = background_new(&config->bg, monitor, images_cache);
507 background_initialize(&monitor->background_configured, &DEFAULT_MONITOR_CONFIG.bg, monitor, images_cache);592 if(!monitor->background_configured)
508 monitor_set_background(monitor, &monitor->background_configured);593 monitor->background_configured = background_new(&DEFAULT_MONITOR_CONFIG.bg, monitor, images_cache);
594 monitor_set_background(monitor, monitor->background_configured);
595
596 if(config->transition.duration && config->transition.func)
597 monitor->transition.config = config->transition;
509598
510 if(monitor->name)599 if(monitor->name)
511 g_hash_table_insert(priv->monitors_map, g_strdup(monitor->name), monitor);600 g_hash_table_insert(priv->monitors_map, g_strdup(monitor->name), monitor);
@@ -528,18 +617,19 @@
528 if(greeter_background_monitor_enabled(background, monitor) &&617 if(greeter_background_monitor_enabled(background, monitor) &&
529 x >= monitor->geometry.x && x < monitor->geometry.x + monitor->geometry.width &&618 x >= monitor->geometry.x && x < monitor->geometry.x + monitor->geometry.width &&
530 y >= monitor->geometry.y && y < monitor->geometry.y + monitor->geometry.height)619 y >= monitor->geometry.y && y < monitor->geometry.y + monitor->geometry.height)
620 {
621 g_debug("[Background] Pointer position will be used to set active monitor: %dx%d", x, y);
531 greeter_background_set_active_monitor(background, monitor);622 greeter_background_set_active_monitor(background, monitor);
623 break;
624 }
532 }625 }
533 }626 }
534 if(!priv->active_monitor)627 if(!priv->active_monitor)
535 greeter_background_set_active_monitor(background, NULL);628 greeter_background_set_active_monitor(background, NULL);
536629
537 if(screen_monitors_changed_handler_id)630 priv->screen_monitors_changed_handler_id = g_signal_connect(G_OBJECT(screen), "monitors-changed",
538 priv->screen_monitors_changed_handler_id = screen_monitors_changed_handler_id;631 G_CALLBACK(greeter_background_monitors_changed_cb),
539 else632 background);
540 priv->screen_monitors_changed_handler_id = g_signal_connect(G_OBJECT(screen), "monitors-changed",
541 G_CALLBACK(greeter_background_monitors_changed_cb),
542 background);
543}633}
544634
545void635void
@@ -548,12 +638,11 @@
548 g_return_if_fail(GREETER_IS_BACKGROUND(background));638 g_return_if_fail(GREETER_IS_BACKGROUND(background));
549 GreeterBackgroundPrivate* priv = background->priv;639 GreeterBackgroundPrivate* priv = background->priv;
550640
551 priv->screen = NULL;
552 priv->active_monitor = NULL;
553
554 if(priv->screen_monitors_changed_handler_id)641 if(priv->screen_monitors_changed_handler_id)
555 g_signal_handler_disconnect(priv->screen, priv->screen_monitors_changed_handler_id);642 g_signal_handler_disconnect(priv->screen, priv->screen_monitors_changed_handler_id);
556 priv->screen_monitors_changed_handler_id = 0;643 priv->screen_monitors_changed_handler_id = 0;
644 priv->screen = NULL;
645 priv->active_monitor = NULL;
557646
558 gint i;647 gint i;
559 for(i = 0; i < priv->monitors_size; ++i)648 for(i = 0; i < priv->monitors_size; ++i)
@@ -610,6 +699,8 @@
610 const Monitor* monitor = g_hash_table_lookup(priv->monitors_map, iter->data);699 const Monitor* monitor = g_hash_table_lookup(priv->monitors_map, iter->data);
611 if(monitor && monitor->background && greeter_background_monitor_enabled(background, monitor))700 if(monitor && monitor->background && greeter_background_monitor_enabled(background, monitor))
612 active = monitor;701 active = monitor;
702 if(active)
703 g_debug("[Background] Active monitor is not specified, using first enabled monitor from 'active-monitor' list");
613 }704 }
614705
615 /* All monitors listed in active-monitor-config are disabled (or option is empty) */706 /* All monitors listed in active-monitor-config are disabled (or option is empty) */
@@ -617,9 +708,13 @@
617 /* Using primary monitor */708 /* Using primary monitor */
618 if(!active)709 if(!active)
619 {710 {
620 active = &priv->monitors[gdk_screen_get_primary_monitor(priv->screen)];711 gint num = gdk_screen_get_primary_monitor(priv->screen);
712 g_return_if_fail(num < priv->monitors_size);
713 active = &priv->monitors[num];
621 if(!active->background || !greeter_background_monitor_enabled(background, active))714 if(!active->background || !greeter_background_monitor_enabled(background, active))
622 active = NULL;715 active = NULL;
716 if(active)
717 g_debug("[Background] Active monitor is not specified, using primary monitor");
623 }718 }
624719
625 /* Fallback: first enabled and/or not skipped monitor (screen always have one) */720 /* Fallback: first enabled and/or not skipped monitor (screen always have one) */
@@ -639,7 +734,25 @@
639 }734 }
640 if(!active)735 if(!active)
641 active = first_not_skipped;736 active = first_not_skipped;
642 }737 if(active)
738 g_debug("[Background] Active monitor is not specified, using first enabled monitor");
739 }
740
741 if(!active && priv->laptop_monitors)
742 {
743 active = priv->laptop_monitors->data;
744 g_debug("[Background] Active monitor is not specified, using laptop monitor");
745 }
746 }
747
748 if(!active)
749 {
750 if(priv->active_monitor)
751 g_warning("[Background] Active monitor is not specified, failed to identify. Active monitor stays the same: %s #%d",
752 priv->active_monitor->name, priv->active_monitor->number);
753 else
754 g_warning("[Background] Active monitor is not specified, failed to identify. Active monitor stays the same: <not defined>");
755 return;
643 }756 }
644757
645 if(active == priv->active_monitor)758 if(active == priv->active_monitor)
@@ -647,12 +760,25 @@
647760
648 priv->active_monitor = active;761 priv->active_monitor = active;
649762
650 GtkWidget* old_parent = gtk_widget_get_parent(priv->child);763 g_return_if_fail(priv->active_monitor != NULL);
651 if(old_parent)764
652 gtk_container_remove(GTK_CONTAINER(old_parent), priv->child);765 if(priv->child)
653 gtk_container_add(GTK_CONTAINER(active->window), priv->child);766 {
654767 GtkWidget* old_parent = gtk_widget_get_parent(priv->child);
655 g_debug("Active monitor changed to: %s #%d", active->name, active->number);768 gpointer focus = greeter_save_focus(priv->child);
769
770 if(old_parent)
771 gtk_container_remove(GTK_CONTAINER(old_parent), priv->child);
772 gtk_container_add(GTK_CONTAINER(active->window), priv->child);
773
774 gtk_window_present(active->window);
775 greeter_restore_focus(focus);
776 g_free(focus);
777 }
778 else
779 g_warning("[Background] Child widget is destroyed or not defined");
780
781 g_debug("[Background] Active monitor changed to: %s #%d", active->name, active->number);
656 g_signal_emit(background, background_signals[BACKGROUND_SIGNAL_ACTIVE_MONITOR_CHANGED], 0);782 g_signal_emit(background, background_signals[BACKGROUND_SIGNAL_ACTIVE_MONITOR_CHANGED], 0);
657783
658 gint x, y;784 gint x, y;
@@ -691,7 +817,7 @@
691static void817static void
692greeter_background_try_init_dbus(GreeterBackground* background)818greeter_background_try_init_dbus(GreeterBackground* background)
693{819{
694 g_debug("Creating DBus proxy");820 g_debug("[Background] Creating DBus proxy");
695 GError* error = NULL;821 GError* error = NULL;
696 GreeterBackgroundPrivate* priv = background->priv;822 GreeterBackgroundPrivate* priv = background->priv;
697823
@@ -710,7 +836,7 @@
710 if(!priv->laptop_upower_proxy)836 if(!priv->laptop_upower_proxy)
711 {837 {
712 if(error)838 if(error)
713 g_warning("Failed to create dbus proxy: %s", error->message);839 g_warning("[Background] Failed to create dbus proxy: %s", error->message);
714 g_clear_error(&error);840 g_clear_error(&error);
715 return;841 return;
716 }842 }
@@ -719,7 +845,7 @@
719 gboolean lid_present = g_variant_get_boolean(variant);845 gboolean lid_present = g_variant_get_boolean(variant);
720 g_variant_unref(variant);846 g_variant_unref(variant);
721847
722 g_debug("UPower.%s property value: %d", DBUS_UPOWER_PROP_LID_IS_PRESENT, lid_present);848 g_debug("[Background] UPower.%s property value: %d", DBUS_UPOWER_PROP_LID_IS_PRESENT, lid_present);
723849
724 if(!lid_present)850 if(!lid_present)
725 greeter_background_stop_dbus(background);851 greeter_background_stop_dbus(background);
@@ -769,13 +895,21 @@
769 if(new_state == priv->laptop_lid_closed)895 if(new_state == priv->laptop_lid_closed)
770 return;896 return;
771897
772 g_debug("UPower: lid state changed to '%s'", priv->laptop_lid_closed ? "closed" : "opened");
773
774 priv->laptop_lid_closed = new_state;898 priv->laptop_lid_closed = new_state;
899 g_debug("[Background] UPower: lid state changed to '%s'", priv->laptop_lid_closed ? "closed" : "opened");
900
775 if(priv->laptop_monitors)901 if(priv->laptop_monitors)
776 {902 {
777 if(!priv->follow_cursor || (new_state && priv->laptop_monitors->data == priv->active_monitor))903 if(priv->laptop_lid_closed)
778 greeter_background_set_active_monitor(background, NULL);904 {
905 if(g_slist_find(priv->laptop_monitors, priv->active_monitor))
906 greeter_background_set_active_monitor(background, NULL);
907 }
908 else
909 {
910 if(!priv->follow_cursor)
911 greeter_background_set_active_monitor(background, NULL);
912 }
779 }913 }
780}914}
781915
@@ -787,6 +921,13 @@
787 greeter_background_connect(background, screen);921 greeter_background_connect(background, screen);
788}922}
789923
924static void
925greeter_background_child_destroyed_cb(GtkWidget* child,
926 GreeterBackground* background)
927{
928 background->priv->child = NULL;
929}
930
790void931void
791greeter_background_set_custom_background(GreeterBackground* background,932greeter_background_set_custom_background(GreeterBackground* background,
792 const gchar* value)933 const gchar* value)
@@ -809,13 +950,19 @@
809 {950 {
810 Monitor *monitor = iter->data;951 Monitor *monitor = iter->data;
811952
812 background_finalize(&monitor->background_custom);953 /* Old background_custom (if used) will be unrefed in monitor_set_background() */
813 if(config.type != BACKGROUND_TYPE_INVALID &&954 Background* bg = NULL;
814 background_initialize(&monitor->background_custom, &config, monitor, images_cache))955 if(config.type != BACKGROUND_TYPE_INVALID)
815 monitor_set_background(monitor, &monitor->background_custom);956 bg = background_new(&config, monitor, images_cache);
957 if(bg)
958 {
959 monitor_set_background(monitor, bg);
960 background_unref(&bg);
961 }
816 else962 else
817 monitor_set_background(monitor, &monitor->background_configured);963 monitor_set_background(monitor, monitor->background_configured);
818 }964 }
965
819 if(images_cache)966 if(images_cache)
820 g_hash_table_unref(images_cache);967 g_hash_table_unref(images_cache);
821 if(config.type != BACKGROUND_TYPE_INVALID)968 if(config.type != BACKGROUND_TYPE_INVALID)
@@ -831,18 +978,43 @@
831 cairo_surface_t* surface = create_root_surface(priv->screen);978 cairo_surface_t* surface = create_root_surface(priv->screen);
832 cairo_t* cr = cairo_create(surface);979 cairo_t* cr = cairo_create(surface);
833 gsize i;980 gsize i;
834981 gdouble child_opacity;
835 for(i = 0; i <= priv->monitors_size; ++i)982
983 const GdkRGBA ROOT_COLOR = {1.0, 1.0, 1.0, 1.0};
984 gdk_cairo_set_source_rgba(cr, &ROOT_COLOR);
985 cairo_paint(cr);
986
987 for(i = 0; i < priv->monitors_size; ++i)
836 {988 {
837 const Monitor* monitor = &priv->monitors[i];989 const Monitor* monitor = &priv->monitors[i];
838 if(monitor == priv->active_monitor || !monitor->background)990 if(!monitor->background)
839 continue;991 continue;
840 if(i == priv->monitors_size)992
841 monitor = priv->active_monitor;993 #ifdef XROOT_DRAW_BACKGROUND_DIRECTLY
994 /* Old method: can't draw default GtkWindow background */
842 cairo_save(cr);995 cairo_save(cr);
843 cairo_translate(cr, monitor->geometry.x, monitor->geometry.y);996 cairo_translate(cr, monitor->geometry.x, monitor->geometry.y);
844 monitor_draw_background(monitor, cr);997 monitor_draw_background(monitor, monitor->background, cr);
845 cairo_restore(cr);998 cairo_restore(cr);
999 #else
1000 /* New - can draw anything, but looks tricky a bit */
1001 child_opacity = gtk_widget_get_opacity(priv->child);
1002 if(monitor == priv->active_monitor)
1003 {
1004 gtk_widget_set_opacity(priv->child, 0.0);
1005 gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(priv->child)), FALSE);
1006 }
1007
1008 gdk_cairo_set_source_window(cr, gtk_widget_get_window(GTK_WIDGET(monitor->window)),
1009 monitor->geometry.x, monitor->geometry.y);
1010 cairo_paint(cr);
1011
1012 if(monitor == priv->active_monitor)
1013 {
1014 gtk_widget_set_opacity(priv->child, child_opacity);
1015 gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(priv->child)), FALSE);
1016 }
1017 #endif
846 }1018 }
847 set_surface_as_root(priv->screen, surface);1019 set_surface_as_root(priv->screen, surface);
8481020
@@ -887,6 +1059,8 @@
887 return FALSE;1059 return FALSE;
888 if(g_strcmp0(value, BACKGROUND_TYPE_SKIP_VALUE) == 0)1060 if(g_strcmp0(value, BACKGROUND_TYPE_SKIP_VALUE) == 0)
889 config->type = BACKGROUND_TYPE_SKIP;1061 config->type = BACKGROUND_TYPE_SKIP;
1062 else if(g_strcmp0(value, BACKGROUND_TYPE_DEFAULT_VALUE) == 0)
1063 config->type = BACKGROUND_TYPE_DEFAULT;
890 else if(gdk_rgba_parse(&config->options.color, value))1064 else if(gdk_rgba_parse(&config->options.color, value))
891 config->type = BACKGROUND_TYPE_COLOR;1065 config->type = BACKGROUND_TYPE_COLOR;
892 else1066 else
@@ -904,7 +1078,7 @@
904 config->options.image.mode = SCALING_MODE_ZOOMED;1078 config->options.image.mode = SCALING_MODE_ZOOMED;
9051079
906 config->options.image.path = g_strdup(value);1080 config->options.image.path = g_strdup(value);
907 config->type = BACKGROUND_TYPE_IMAGE; 1081 config->type = BACKGROUND_TYPE_IMAGE;
908 }1082 }
909 return TRUE;1083 return TRUE;
910}1084}
@@ -912,8 +1086,19 @@
912static void1086static void
913background_config_finalize(BackgroundConfig* config)1087background_config_finalize(BackgroundConfig* config)
914{1088{
915 if(config->type == BACKGROUND_TYPE_IMAGE)1089 switch(config->type)
916 g_free(config->options.image.path);1090 {
1091 case BACKGROUND_TYPE_IMAGE:
1092 g_free(config->options.image.path);
1093 break;
1094 case BACKGROUND_TYPE_COLOR:
1095 case BACKGROUND_TYPE_DEFAULT:
1096 case BACKGROUND_TYPE_SKIP:
1097 break;
1098 case BACKGROUND_TYPE_INVALID:
1099 g_return_if_reached();
1100 }
1101
917 config->type = BACKGROUND_TYPE_INVALID;1102 config->type = BACKGROUND_TYPE_INVALID;
918}1103}
9191104
@@ -922,8 +1107,19 @@
922 BackgroundConfig* dest)1107 BackgroundConfig* dest)
923{1108{
924 *dest = *source;1109 *dest = *source;
925 if(source->type == BACKGROUND_TYPE_IMAGE)1110
926 dest->options.image.path = g_strdup(source->options.image.path);1111 switch(dest->type)
1112 {
1113 case BACKGROUND_TYPE_IMAGE:
1114 dest->options.image.path = g_strdup(source->options.image.path);
1115 break;
1116 case BACKGROUND_TYPE_COLOR:
1117 case BACKGROUND_TYPE_DEFAULT:
1118 case BACKGROUND_TYPE_SKIP:
1119 break;
1120 case BACKGROUND_TYPE_INVALID:
1121 g_return_if_reached();
1122 }
927}1123}
9281124
929static void1125static void
@@ -941,84 +1137,238 @@
941 background_config_copy(&source->bg, &dest->bg);1137 background_config_copy(&source->bg, &dest->bg);
942 dest->user_bg = source->user_bg;1138 dest->user_bg = source->user_bg;
943 dest->laptop = source->laptop;1139 dest->laptop = source->laptop;
1140 dest->transition = source->transition;
944 return dest;1141 return dest;
945}1142}
9461143
947static gboolean1144static Background*
948background_initialize(Background* bg,1145background_new(const BackgroundConfig* config,
949 const BackgroundConfig* config,1146 const Monitor* monitor,
950 const Monitor* monitor,1147 GHashTable* images_cache)
951 GHashTable* images_cache)1148{
952{1149 Background bg = {0};
953 if(config->type == BACKGROUND_TYPE_IMAGE)1150
954 {1151 switch(config->type)
955 GdkPixbuf* pixbuf = scale_image_file(config->options.image.path,1152 {
956 config->options.image.mode,1153 case BACKGROUND_TYPE_IMAGE:
957 monitor->geometry.width, monitor->geometry.height,1154 bg.options.image = scale_image_file(config->options.image.path, config->options.image.mode,
958 images_cache);1155 monitor->geometry.width, monitor->geometry.height,
959 if(!pixbuf)1156 images_cache);
960 {1157 if(!bg.options.image)
961 g_warning("Failed to read wallpaper: %s", config->options.image.path);1158 {
962 return FALSE;1159 g_warning("[Background] Failed to read wallpaper: %s", config->options.image.path);
963 }1160 return NULL;
964 bg->options.image = pixbuf;1161 }
965 }1162 break;
966 else if(config->type == BACKGROUND_TYPE_COLOR)1163 case BACKGROUND_TYPE_COLOR:
967 bg->options.color = config->options.color;1164 bg.options.color = config->options.color;
968 else1165 break;
969 return FALSE;1166 case BACKGROUND_TYPE_DEFAULT:
970 bg->type = config->type;1167 break;
971 return TRUE;1168 case BACKGROUND_TYPE_SKIP:
1169 case BACKGROUND_TYPE_INVALID:
1170 g_return_val_if_reached(NULL);
1171 }
1172
1173 bg.type = config->type;
1174 bg.ref_count = 1;
1175
1176 Background* result = g_new(Background, 1);
1177 *result = bg;
1178 return result;
1179}
1180
1181static Background*
1182background_ref(Background* bg)
1183{
1184 bg->ref_count++;
1185 return bg;
1186}
1187
1188static void
1189background_unref(Background** bg)
1190{
1191 if(!*bg)
1192 return;
1193 (*bg)->ref_count--;
1194 if((*bg)->ref_count == 0)
1195 {
1196 background_finalize(*bg);
1197 *bg = NULL;
1198 }
972}1199}
9731200
974static void1201static void
975background_finalize(Background* bg)1202background_finalize(Background* bg)
976{1203{
977 if(bg->type == BACKGROUND_TYPE_IMAGE)1204 switch(bg->type)
978 g_clear_object(&bg->options.image);1205 {
1206 case BACKGROUND_TYPE_IMAGE:
1207 g_clear_object(&bg->options.image);
1208 break;
1209 case BACKGROUND_TYPE_COLOR:
1210 case BACKGROUND_TYPE_DEFAULT:
1211 break;
1212 case BACKGROUND_TYPE_SKIP:
1213 case BACKGROUND_TYPE_INVALID:
1214 g_return_if_reached();
1215 }
1216
979 bg->type = BACKGROUND_TYPE_INVALID;1217 bg->type = BACKGROUND_TYPE_INVALID;
980}1218}
9811219
982static void1220static void
983monitor_set_background(Monitor* monitor,1221monitor_set_background(Monitor* monitor,
984 const Background* background)1222 Background* background)
985{1223{
986 monitor->background = background;1224 if(monitor->background == background)
987 gtk_widget_queue_draw(GTK_WIDGET(monitor->window));1225 return;
1226 monitor_stop_transition(monitor);
1227
1228 switch(background->type)
1229 {
1230 case BACKGROUND_TYPE_IMAGE:
1231 case BACKGROUND_TYPE_COLOR:
1232 gtk_widget_set_app_paintable(GTK_WIDGET(monitor->window), TRUE);
1233 if(monitor->transition.config.duration > 0 && monitor->background &&
1234 monitor->background->type != BACKGROUND_TYPE_DEFAULT)
1235 monitor_start_transition(monitor, monitor->background, background);
1236 break;
1237 case BACKGROUND_TYPE_DEFAULT:
1238 gtk_widget_set_app_paintable(GTK_WIDGET(monitor->window), FALSE);
1239 break;
1240 case BACKGROUND_TYPE_SKIP:
1241 case BACKGROUND_TYPE_INVALID:
1242 g_return_val_if_reached(NULL);
1243 }
1244
1245 background_unref(&monitor->background);
1246 monitor->background = background_ref(background);
1247 gtk_widget_queue_draw(GTK_WIDGET(monitor->window));
1248}
1249
1250static void
1251monitor_start_transition(Monitor* monitor,
1252 Background* from,
1253 Background* to)
1254{
1255 monitor_stop_transition(monitor);
1256
1257 monitor->transition.from = background_ref(from);
1258 monitor->transition.to = background_ref(to);
1259
1260 monitor->transition.started = g_get_monotonic_time();
1261 monitor->transition.timer_id = gtk_widget_add_tick_callback(GTK_WIDGET(monitor->window),
1262 (GtkTickCallback)monitor_transition_cb,
1263 monitor,
1264 NULL);
1265 monitor->transition.stage = 0;
1266}
1267
1268static void
1269monitor_stop_transition(Monitor* monitor)
1270{
1271 if(!monitor->transition.timer_id)
1272 return;
1273 gtk_widget_remove_tick_callback(GTK_WIDGET(monitor->window), monitor->transition.timer_id);
1274 monitor->transition.timer_id = 0;
1275 monitor->transition.started = 0;
1276 monitor->transition.stage = 0;
1277 background_unref(&monitor->transition.to);
1278 background_unref(&monitor->transition.from);
1279}
1280
1281static gboolean
1282monitor_transition_cb(GtkWidget *widget,
1283 GdkFrameClock* frame_clock,
1284 Monitor* monitor)
1285{
1286 if(!monitor->transition.timer_id)
1287 return G_SOURCE_REMOVE;
1288
1289 gint64 span = g_get_monotonic_time() - monitor->transition.started;
1290 gdouble x = CLAMP(span/monitor->transition.config.duration/1000.0, 0.0, 1.0);
1291 monitor->transition.stage = monitor->transition.config.func(x);
1292
1293 if(x >= 1.0)
1294 monitor_stop_transition(monitor);
1295
1296 gtk_widget_queue_draw(GTK_WIDGET(monitor->window));
1297 return x >= 1.0 ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
1298}
1299
1300static void
1301monitor_transition_draw_alpha(const Monitor* monitor,
1302 cairo_t* cr)
1303{
1304 monitor_draw_background(monitor, monitor->transition.from, cr);
1305
1306 cairo_push_group(cr);
1307 monitor_draw_background(monitor, monitor->transition.to, cr);
1308 cairo_pop_group_to_source(cr);
1309
1310 cairo_pattern_t* alpha_pattern = cairo_pattern_create_rgba(0.0, 0.0, 0.0, monitor->transition.stage);
1311 cairo_mask(cr, alpha_pattern);
1312 cairo_pattern_destroy(alpha_pattern);
988}1313}
9891314
990static void1315static void
991monitor_finalize(Monitor* monitor)1316monitor_finalize(Monitor* monitor)
992{1317{
993 background_finalize(&monitor->background_configured);1318 if(monitor->transition.config.duration)
994 background_finalize(&monitor->background_custom);1319 {
995 g_free(monitor->name);1320 monitor_stop_transition(monitor);
1321 if(monitor->transition.timer_id)
1322 g_source_remove(monitor->transition.timer_id);
1323 monitor->transition.config.duration = 0;
1324 }
1325
996 if(monitor->window_draw_handler_id)1326 if(monitor->window_draw_handler_id)
997 g_signal_handler_disconnect(monitor->window, monitor->window_draw_handler_id);1327 g_signal_handler_disconnect(monitor->window, monitor->window_draw_handler_id);
1328
1329 background_unref(&monitor->background_configured);
1330 background_unref(&monitor->background);
1331
998 if(monitor->window)1332 if(monitor->window)
1333 {
1334 GtkWidget* child = gtk_bin_get_child(GTK_BIN(monitor->window));
1335 if(child) /* remove greeter widget to avoid "destroy" signal */
1336 gtk_container_remove(GTK_CONTAINER(monitor->window), child);
999 gtk_widget_destroy(GTK_WIDGET(monitor->window));1337 gtk_widget_destroy(GTK_WIDGET(monitor->window));
1000 monitor->name = NULL;1338 }
1001 monitor->window = NULL;1339
1002 monitor->window_draw_handler_id = 0;1340 g_free(monitor->name);
1341
1342 *monitor = INVALID_MONITOR_STRUCT;
1003}1343}
10041344
1005static void1345static void
1006monitor_draw_background(const Monitor* monitor,1346monitor_draw_background(const Monitor* monitor,
1347 const Background* background,
1007 cairo_t* cr)1348 cairo_t* cr)
1008{1349{
1009 g_return_if_fail(monitor != NULL);1350 g_return_if_fail(monitor != NULL);
1010 g_return_if_fail(monitor->background != NULL);1351 g_return_if_fail(background != NULL);
10111352
1012 if(monitor->background->type == BACKGROUND_TYPE_IMAGE && monitor->background->options.image)1353 switch(background->type)
1013 {1354 {
1014 gdk_cairo_set_source_pixbuf(cr, monitor->background->options.image, 0, 0);1355 case BACKGROUND_TYPE_IMAGE:
1015 cairo_paint(cr);1356 if(background->options.image)
1016 }1357 {
1017 else if(monitor->background->type == BACKGROUND_TYPE_COLOR)1358 gdk_cairo_set_source_pixbuf(cr, background->options.image, 0, 0);
1018 {1359 cairo_paint(cr);
1019 cairo_rectangle(cr, 0, 0, monitor->geometry.width, monitor->geometry.height);1360 }
1020 gdk_cairo_set_source_rgba(cr, &monitor->background->options.color);1361 break;
1021 cairo_fill(cr);1362 case BACKGROUND_TYPE_COLOR:
1363 cairo_rectangle(cr, 0, 0, monitor->geometry.width, monitor->geometry.height);
1364 gdk_cairo_set_source_rgba(cr, &background->options.color);
1365 cairo_fill(cr);
1366 break;
1367 case BACKGROUND_TYPE_DEFAULT:
1368 break;
1369 case BACKGROUND_TYPE_SKIP:
1370 case BACKGROUND_TYPE_INVALID:
1371 g_return_if_reached();
1022 }1372 }
1023}1373}
10241374
@@ -1027,8 +1377,14 @@
1027 cairo_t* cr,1377 cairo_t* cr,
1028 const Monitor* monitor)1378 const Monitor* monitor)
1029{1379{
1030 if(monitor->background)1380 if(!monitor->background)
1031 monitor_draw_background(monitor, cr);1381 return FALSE;
1382
1383 if(monitor->transition.started)
1384 monitor->transition.config.draw(monitor, cr);
1385 else
1386 monitor_draw_background(monitor, monitor->background, cr);
1387
1032 return FALSE;1388 return FALSE;
1033}1389}
10341390
@@ -1051,35 +1407,43 @@
1051{1407{
1052 gchar* key = NULL;1408 gchar* key = NULL;
1053 GdkPixbuf* pixbuf = NULL;1409 GdkPixbuf* pixbuf = NULL;
1410
1054 if(cache)1411 if(cache)
1055 {1412 {
1056 key = g_strdup_printf("%s\n%d %dx%d", path, mode, width, height);1413 key = g_strdup_printf("%s\n%d %dx%d", path, mode, width, height);
1057 if (g_hash_table_lookup_extended(cache, key, NULL, (gpointer*)&pixbuf))1414 if(g_hash_table_lookup_extended(cache, key, NULL, (gpointer*)&pixbuf))
1415 {
1416 g_free(key);
1058 return GDK_PIXBUF(g_object_ref(pixbuf));1417 return GDK_PIXBUF(g_object_ref(pixbuf));
1418 }
1059 }1419 }
10601420
1061 if (!cache || !g_hash_table_lookup_extended(cache, path, NULL, (gpointer*)&pixbuf))1421 if(!cache || !g_hash_table_lookup_extended(cache, path, NULL, (gpointer*)&pixbuf))
1062 {1422 {
1063 GError *error = NULL;1423 GError *error = NULL;
1064 pixbuf = gdk_pixbuf_new_from_file(path, &error);1424 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1065 if(error)1425 if(error)
1066 {1426 {
1067 g_warning("Failed to load background: %s", error->message);1427 g_warning("[Background] Failed to load background: %s", error->message);
1068 g_clear_error(&error);1428 g_clear_error(&error);
1069 }1429 }
1070 else if(cache)1430 else if(cache)
1071 g_hash_table_insert(cache, g_strdup(path), g_object_ref (pixbuf));1431 g_hash_table_insert(cache, g_strdup(path), g_object_ref(pixbuf));
1072 }1432 }
1433 else
1434 pixbuf = g_object_ref(pixbuf);
10731435
1074 if(pixbuf)1436 if(pixbuf)
1075 {1437 {
1076 GdkPixbuf* scaled = scale_image(pixbuf, mode, width, height);1438 GdkPixbuf* scaled = scale_image(pixbuf, mode, width, height);
1077 if (cache)1439 if(cache)
1078 g_hash_table_insert(cache, g_strdup(key), g_object_ref(scaled));1440 g_hash_table_insert(cache, g_strdup(key), g_object_ref(scaled));
1079 g_object_unref(pixbuf);1441 g_object_unref(pixbuf);
1080 pixbuf = scaled;1442 pixbuf = scaled;
1081 }1443 }
10821444
1445 g_free(key);
1446
1083 return pixbuf;1447 return pixbuf;
1084}1448}
10851449
@@ -1142,7 +1506,7 @@
1142 display = XOpenDisplay (gdk_display_get_name (gdk_screen_get_display (screen)));1506 display = XOpenDisplay (gdk_display_get_name (gdk_screen_get_display (screen)));
1143 if (!display)1507 if (!display)
1144 {1508 {
1145 g_warning ("Failed to create root pixmap");1509 g_warning("[Background] Failed to create root pixmap");
1146 return NULL;1510 return NULL;
1147 }1511 }
11481512
@@ -1165,7 +1529,7 @@
1165 Display* display,1529 Display* display,
1166 Pixmap xpixmap)1530 Pixmap xpixmap)
1167{1531{
1168 1532
1169 Window xroot = RootWindow (display, gdk_screen_get_number (screen));1533 Window xroot = RootWindow (display, gdk_screen_get_number (screen));
1170 char *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};1534 char *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};
1171 Atom atoms[G_N_ELEMENTS(atom_names)] = {0};1535 Atom atoms[G_N_ELEMENTS(atom_names)] = {0};
@@ -1214,7 +1578,7 @@
1214 */1578 */
1215 if (!XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||1579 if (!XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||
1216 atoms[0] == None || atoms[1] == None) {1580 atoms[0] == None || atoms[1] == None) {
1217 g_warning("Could not create atoms needed to set root pixmap id/properties.\n");1581 g_warning("[Background] Could not create atoms needed to set root pixmap id/properties.\n");
1218 return;1582 return;
1219 }1583 }
12201584
@@ -1261,3 +1625,15 @@
1261 XFlush (display);1625 XFlush (display);
1262 XUngrabServer (display);1626 XUngrabServer (display);
1263}1627}
1628
1629static gdouble
1630transition_func_linear(gdouble x)
1631{
1632 return x;
1633}
1634
1635static gdouble
1636transition_func_easy_in_out(gdouble x)
1637{
1638 return (1 - cos(M_PI*x))/2;
1639}
12641640
=== modified file 'src/greeterbackground.h'
--- src/greeterbackground.h 2014-08-31 17:45:52 +0000
+++ src/greeterbackground.h 2014-12-10 07:59:47 +0000
@@ -12,6 +12,8 @@
12#define GREETER_IS_BACKGROUND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GREETER_BACKGROUND_TYPE))12#define GREETER_IS_BACKGROUND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GREETER_BACKGROUND_TYPE))
13#define GREETER_IS_BACKGROUND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GREETER_BACKGROUND_TYPE))13#define GREETER_IS_BACKGROUND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GREETER_BACKGROUND_TYPE))
1414
15#define GREETER_BACKGROUND_DEFAULT "*"
16
15typedef struct _GreeterBackground GreeterBackground;17typedef struct _GreeterBackground GreeterBackground;
16typedef struct _GreeterBackgroundClass GreeterBackgroundClass;18typedef struct _GreeterBackgroundClass GreeterBackgroundClass;
1719
@@ -20,15 +22,13 @@
20GreeterBackground* greeter_background_new (GtkWidget* child);22GreeterBackground* greeter_background_new (GtkWidget* child);
21void greeter_background_set_active_monitor_config (GreeterBackground* background,23void greeter_background_set_active_monitor_config (GreeterBackground* background,
22 const gchar* value);24 const gchar* value);
23void greeter_background_set_default_config (GreeterBackground* background,
24 const gchar* bg,
25 gboolean user_bg,
26 gboolean laptop);
27void greeter_background_set_monitor_config (GreeterBackground* background,25void greeter_background_set_monitor_config (GreeterBackground* background,
28 const gchar* name,26 const gchar* name,
29 const gchar* bg,27 const gchar* bg,
30 gboolean user_bg, gboolean user_bg_used,28 gboolean user_bg, gboolean user_bg_used,
31 gboolean laptop, gboolean laptop_used);29 gboolean laptop, gboolean laptop_used,
30 gint transition_duration,
31 const gchar* transition_func);
32void greeter_background_remove_monitor_config (GreeterBackground* background,32void greeter_background_remove_monitor_config (GreeterBackground* background,
33 const gchar* name);33 const gchar* name);
34gchar** greeter_background_get_configured_monitors (GreeterBackground* background);34gchar** greeter_background_get_configured_monitors (GreeterBackground* background);
3535
=== modified file 'src/lightdm-gtk-greeter.c'
--- src/lightdm-gtk-greeter.c 2014-09-01 07:58:56 +0000
+++ src/lightdm-gtk-greeter.c 2014-12-10 07:59:47 +0000
@@ -127,9 +127,6 @@
127static const WindowPosition KEYBOARD_POSITION = {.x = { 50, +1, TRUE, 0}, .y = { 0, -1, FALSE, +1}, .use_size = TRUE,127static const WindowPosition KEYBOARD_POSITION = {.x = { 50, +1, TRUE, 0}, .y = { 0, -1, FALSE, +1}, .use_size = TRUE,
128 .width = {50, 0, TRUE, 0}, .height = {25, 0, TRUE, 0}};128 .width = {50, 0, TRUE, 0}, .height = {25, 0, TRUE, 0}};
129129
130/* Configuration */
131static gboolean key_file_get_boolean_extended (GKeyFile *key_file, const gchar *group_name, const gchar *key, gboolean default_value);
132
133/* Clock */130/* Clock */
134static gchar *clock_format;131static gchar *clock_format;
135static gboolean clock_timeout_thread (void);132static gboolean clock_timeout_thread (void);
@@ -183,6 +180,7 @@
183static int timeout, interval, prefer_blanking, allow_exposures;180static int timeout, interval, prefer_blanking, allow_exposures;
184181
185/* Handling monitors backgrounds */182/* Handling monitors backgrounds */
183static const gint USER_BACKGROUND_DELAY = 250;
186static GreeterBackground *greeter_background;184static GreeterBackground *greeter_background;
187185
188/* Authentication state */186/* Authentication state */
@@ -285,6 +283,42 @@
285void restart_cb (GtkWidget *widget, LightDMGreeter *greeter);283void restart_cb (GtkWidget *widget, LightDMGreeter *greeter);
286void shutdown_cb (GtkWidget *widget, LightDMGreeter *greeter);284void shutdown_cb (GtkWidget *widget, LightDMGreeter *greeter);
287285
286gpointer greeter_save_focus(GtkWidget* widget);
287void greeter_restore_focus(const gpointer saved_data);
288
289struct SavedFocusData
290{
291 GtkWidget *widget;
292 gint editable_pos;
293};
294
295gpointer
296greeter_save_focus(GtkWidget* widget)
297{
298 GtkWidget *window = gtk_widget_get_toplevel(widget);
299 if (!GTK_IS_WINDOW (window))
300 return NULL;
301
302 struct SavedFocusData *data = g_new0 (struct SavedFocusData, 1);
303 data->widget = gtk_window_get_focus (GTK_WINDOW (window));
304 data->editable_pos = GTK_IS_EDITABLE(data->widget) ? gtk_editable_get_position (GTK_EDITABLE (data->widget)) : -1;
305
306 return data;
307}
308
309void
310greeter_restore_focus(const gpointer saved_data)
311{
312 if (!saved_data)
313 return;
314
315 struct SavedFocusData *data = saved_data;
316 if (GTK_IS_WIDGET (data->widget))
317 gtk_widget_grab_focus (data->widget);
318 if (GTK_IS_EDITABLE(data->widget) && data->editable_pos > -1)
319 gtk_editable_set_position(GTK_EDITABLE(data->widget), data->editable_pos);
320}
321
288/* State file */322/* State file */
289323
290static void324static void
@@ -536,21 +570,6 @@
536 return TRUE;570 return TRUE;
537}571}
538572
539/* Configuration */
540
541static gboolean
542key_file_get_boolean_extended (GKeyFile *key_file, const gchar *group_name, const gchar *key, gboolean default_value)
543{
544 GError* error = NULL;
545 gboolean result = g_key_file_get_boolean (key_file, group_name, key, &error);
546 if (error)
547 {
548 g_clear_error (&error);
549 return default_value;
550 }
551 return result;
552}
553
554/* Clock */573/* Clock */
555574
556static gboolean575static gboolean
@@ -1771,24 +1790,47 @@
1771 gtk_button_set_label (login_button, _("Unlock"));1790 gtk_button_set_label (login_button, _("Unlock"));
1772 else1791 else
1773 gtk_button_set_label (login_button, _("Log In"));1792 gtk_button_set_label (login_button, _("Log In"));
1774 gtk_widget_set_can_default (GTK_WIDGET (login_button), TRUE);
1775 gtk_widget_grab_default (GTK_WIDGET (login_button));
1776 /* and disable the session and language widgets */1793 /* and disable the session and language widgets */
1777 gtk_widget_set_sensitive (GTK_WIDGET (session_menuitem), !logged_in);1794 gtk_widget_set_sensitive (GTK_WIDGET (session_menuitem), !logged_in);
1778 gtk_widget_set_sensitive (GTK_WIDGET (language_menuitem), !logged_in);1795 gtk_widget_set_sensitive (GTK_WIDGET (language_menuitem), !logged_in);
1779}1796}
17801797
1798static guint set_user_background_delayed_id = 0;
1799
1800static gboolean
1801set_user_background_delayed_cb (const gchar *value)
1802{
1803 greeter_background_set_custom_background (greeter_background, value);
1804 set_user_background_delayed_id = 0;
1805 return G_SOURCE_REMOVE;
1806}
1807
1781static void1808static void
1782set_user_background (const gchar *username)1809set_user_background (const gchar *user_name)
1783{1810{
1784 const gchar *path = NULL;1811 const gchar *value = NULL;
1785 if (username)1812 if (user_name)
1786 {1813 {
1787 LightDMUser *user = lightdm_user_list_get_user_by_name (lightdm_user_list_get_instance (), username);1814 LightDMUser *user = lightdm_user_list_get_user_by_name (lightdm_user_list_get_instance (), user_name);
1788 if (user)1815 if (user)
1789 path = lightdm_user_get_background (user);1816 value = lightdm_user_get_background (user);
1790 }1817 }
1791 greeter_background_set_custom_background (greeter_background, path);1818
1819 if (set_user_background_delayed_id)
1820 {
1821 g_source_remove (set_user_background_delayed_id);
1822 set_user_background_delayed_id = 0;
1823 }
1824
1825 if (!value)
1826 greeter_background_set_custom_background (greeter_background, NULL);
1827 else
1828 {
1829 /* Small delay before changing background */
1830 set_user_background_delayed_id = g_timeout_add_full (G_PRIORITY_DEFAULT, USER_BACKGROUND_DELAY,
1831 (GSourceFunc)set_user_background_delayed_cb,
1832 g_strdup (value), g_free);
1833 }
1792}1834}
17931835
1794static void1836static void
@@ -2852,36 +2894,45 @@
2852 greeter_background_set_active_monitor_config (greeter_background, value ? value : "#cursor");2894 greeter_background_set_active_monitor_config (greeter_background, value ? value : "#cursor");
2853 g_free (value);2895 g_free (value);
28542896
2855 value = g_key_file_get_value (config, "greeter", "background", NULL);
2856 greeter_background_set_default_config (greeter_background, value,
2857 key_file_get_boolean_extended (config, "greeter", "user-background", TRUE),
2858 key_file_get_boolean_extended (config, "greeter", "laptop", FALSE));
2859 g_free (value);
2860
2861 const gchar *CONFIG_MONITOR_PREFIX = "monitor:";2897 const gchar *CONFIG_MONITOR_PREFIX = "monitor:";
2862 gchar **config_group;2898 gchar **config_group;
2863 gchar **config_groups = g_key_file_get_groups (config, NULL);2899 gchar **config_groups = g_key_file_get_groups (config, NULL);
2864 for (config_group = config_groups; *config_group; ++config_group)2900 for (config_group = config_groups; *config_group; ++config_group)
2865 {2901 {
2866 if (!g_str_has_prefix (*config_group, CONFIG_MONITOR_PREFIX))2902 gchar *name_to_free = NULL;
2867 continue;2903 const gchar *name = *config_group;
2868 const gchar *name = *config_group + sizeof (CONFIG_MONITOR_PREFIX);2904
2869 while (*name && g_ascii_isspace (*name))2905 if (g_strcmp0 (*config_group, "greeter") != 0)
2870 ++name;2906 {
2907 if (!g_str_has_prefix (name, CONFIG_MONITOR_PREFIX))
2908 continue;
2909 name = *config_group + sizeof (CONFIG_MONITOR_PREFIX);
2910 while (*name && g_ascii_isspace (*name))
2911 ++name;
2912 }
2913 else
2914 name = name_to_free = g_strdup (GREETER_BACKGROUND_DEFAULT);
2915
2871 g_debug ("Monitor configuration found: '%s'", name);2916 g_debug ("Monitor configuration found: '%s'", name);
28722917
2873 GError *user_bg_error = NULL, *laptop_error = NULL;2918 GError *user_bg_error = NULL, *laptop_error = NULL, *duration_error = NULL;
2874 gboolean user_bg = g_key_file_get_boolean (config, *config_group, "user-background", &user_bg_error);2919 gboolean user_bg = g_key_file_get_boolean (config, *config_group, "user-background", &user_bg_error);
2875 gboolean laptop = g_key_file_get_boolean (config, *config_group, "laptop", &laptop_error);2920 gboolean laptop = g_key_file_get_boolean (config, *config_group, "laptop", &laptop_error);
2876 value = g_key_file_get_value (config, *config_group, "background", NULL);2921 gchar *background = g_key_file_get_value (config, *config_group, "background", NULL);
2922 gchar *tr_type = g_key_file_get_string (config, *config_group, "transition-type", NULL);
2923 gint tr_duration = g_key_file_get_integer (config, *config_group, "transition-duration", &duration_error);
28772924
2878 greeter_background_set_monitor_config (greeter_background, name, value,2925 greeter_background_set_monitor_config (greeter_background, name, background,
2879 user_bg, user_bg_error == NULL,2926 user_bg, user_bg_error == NULL,
2880 laptop, laptop_error == NULL);2927 laptop, laptop_error == NULL,
2928 duration_error == NULL ? tr_duration : -1,
2929 tr_type);
28812930
2882 g_free (value);2931 g_free (tr_type);
2932 g_free (background);
2933 g_free (name_to_free);
2934 g_clear_error (&user_bg_error);
2883 g_clear_error (&laptop_error);2935 g_clear_error (&laptop_error);
2884 g_clear_error (&user_bg_error);
2885 }2936 }
2886 g_strfreev (config_groups);2937 g_strfreev (config_groups);
28872938
28882939
=== modified file 'src/lightdm-gtk-greeter.glade'
--- src/lightdm-gtk-greeter.glade 2014-09-24 17:41:33 +0000
+++ src/lightdm-gtk-greeter.glade 2014-12-10 07:59:47 +0000
@@ -282,7 +282,6 @@
282 <property name="name">cancel_button</property>282 <property name="name">cancel_button</property>
283 <property name="visible">True</property>283 <property name="visible">True</property>
284 <property name="can_focus">True</property>284 <property name="can_focus">True</property>
285 <property name="receives_default">True</property>
286 <signal name="clicked" handler="power_button_clicked_cb" swapped="no"/>285 <signal name="clicked" handler="power_button_clicked_cb" swapped="no"/>
287 </object>286 </object>
288 <packing>287 <packing>
@@ -297,7 +296,6 @@
297 <property name="name">power_ok_button</property>296 <property name="name">power_ok_button</property>
298 <property name="visible">True</property>297 <property name="visible">True</property>
299 <property name="can_focus">True</property>298 <property name="can_focus">True</property>
300 <property name="receives_default">True</property>
301 <signal name="clicked" handler="power_button_clicked_cb" swapped="no"/>299 <signal name="clicked" handler="power_button_clicked_cb" swapped="no"/>
302 </object>300 </object>
303 <packing>301 <packing>
@@ -432,7 +430,6 @@
432 <object class="GtkEntry" id="username_entry">430 <object class="GtkEntry" id="username_entry">
433 <property name="name">prompt_entry</property>431 <property name="name">prompt_entry</property>
434 <property name="can_focus">True</property>432 <property name="can_focus">True</property>
435 <property name="no_show_all">True</property>
436 <property name="hexpand">True</property>433 <property name="hexpand">True</property>
437 <property name="invisible_char">•</property>434 <property name="invisible_char">•</property>
438 <property name="placeholder_text" translatable="yes">Enter your username</property>435 <property name="placeholder_text" translatable="yes">Enter your username</property>
@@ -480,7 +477,6 @@
480 <child>477 <child>
481 <object class="GtkInfoBar" id="greeter_infobar">478 <object class="GtkInfoBar" id="greeter_infobar">
482 <property name="name">greeter_infobar</property>479 <property name="name">greeter_infobar</property>
483 <property name="app_paintable">True</property>
484 <property name="can_focus">False</property>480 <property name="can_focus">False</property>
485 <child internal-child="action_area">481 <child internal-child="action_area">
486 <object class="GtkButtonBox" id="infobar-action_area">482 <object class="GtkButtonBox" id="infobar-action_area">
@@ -545,7 +541,6 @@
545 <property name="name">cancel_button</property>541 <property name="name">cancel_button</property>
546 <property name="visible">True</property>542 <property name="visible">True</property>
547 <property name="can_focus">True</property>543 <property name="can_focus">True</property>
548 <property name="receives_default">True</property>
549 <signal name="clicked" handler="cancel_cb" swapped="no"/>544 <signal name="clicked" handler="cancel_cb" swapped="no"/>
550 </object>545 </object>
551 <packing>546 <packing>
@@ -560,7 +555,6 @@
560 <property name="name">login_button</property>555 <property name="name">login_button</property>
561 <property name="visible">True</property>556 <property name="visible">True</property>
562 <property name="can_focus">True</property>557 <property name="can_focus">True</property>
563 <property name="receives_default">True</property>
564 <signal name="clicked" handler="login_cb" swapped="no"/>558 <signal name="clicked" handler="login_cb" swapped="no"/>
565 </object>559 </object>
566 <packing>560 <packing>

Subscribers

People subscribed via source and target branches