1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2001 Ximian, Inc.
4 * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
5 * Copyright (C) 2007 Red Hat, Inc.
6 * Copyright (C) 2012 Jasmine Hassan <jasmine.aura@gmail.com>
7 * Copyright (C) 2012-2021 MATE Developers
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
22 *
23 */
24
25 #include "config.h"
26
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include <locale.h>
36
37 #include <glib.h>
38 #include <glib/gi18n.h>
39 #include <gdk/gdk.h>
40 #include <gdk/gdkx.h>
41 #include <gio/gio.h>
42
43 #define MATE_DESKTOP_USE_UNSTABLE_API
44 #include <libmate-desktop/mate-bg.h>
45 #include <X11/Xatom.h>
46
47 #include "mate-settings-profile.h"
48 #include "msd-background-manager.h"
49
50 #define MATE_SESSION_MANAGER_DBUS_NAME "org.gnome.SessionManager"
51 #define MATE_SESSION_MANAGER_DBUS_PATH "/org/gnome/SessionManager"
52
53 struct _MsdBackgroundManager {
54 GObject parent;
55
56 GSettings *settings;
57 MateBG *bg;
58 cairo_surface_t *surface;
59 MateBGCrossfade *fade;
60 GList *scr_sizes;
61
62 gboolean msd_can_draw;
63 gboolean caja_can_draw;
64 gboolean do_fade;
65 gboolean draw_in_progress;
66
67 guint timeout_id;
68
69 GDBusProxy *proxy;
70 gulong proxy_signal_id;
71 };
72
73 G_DEFINE_TYPE (MsdBackgroundManager, msd_background_manager, G_TYPE_OBJECT)
74
75 static gpointer manager_object = NULL;
76
77 /* Whether MSD is allowed to draw background */
78 static gboolean
msd_can_draw_bg(MsdBackgroundManager * manager)79 msd_can_draw_bg (MsdBackgroundManager *manager)
80 {
81 return g_settings_get_boolean (manager->settings, MATE_BG_KEY_DRAW_BACKGROUND);
82 }
83
84 /* Whether to change background with a fade effect */
85 static gboolean
can_fade_bg(MsdBackgroundManager * manager)86 can_fade_bg (MsdBackgroundManager *manager)
87 {
88 return g_settings_get_boolean (manager->settings, MATE_BG_KEY_BACKGROUND_FADE);
89 }
90
91 /* Whether Caja is configured to draw desktop (show-desktop-icons) */
92 static gboolean
caja_can_draw_bg(MsdBackgroundManager * manager)93 caja_can_draw_bg (MsdBackgroundManager *manager)
94 {
95 return g_settings_get_boolean (manager->settings, MATE_BG_KEY_SHOW_DESKTOP);
96 }
97
98 static gboolean
caja_is_drawing_bg(MsdBackgroundManager * manager)99 caja_is_drawing_bg (MsdBackgroundManager *manager)
100 {
101 Display *display = gdk_x11_get_default_xdisplay ();
102 Window window = gdk_x11_get_default_root_xwindow ();
103 Atom caja_prop, wmclass_prop, type;
104 Window caja_window;
105 int format;
106 unsigned long nitems, after;
107 unsigned char *data;
108 GdkDisplay *gdk_display;
109 gboolean running = FALSE;
110
111 if (!manager->caja_can_draw)
112 return FALSE;
113
114 caja_prop = XInternAtom (display, "CAJA_DESKTOP_WINDOW_ID", True);
115 if (caja_prop == None)
116 return FALSE;
117
118 XGetWindowProperty (display, window, caja_prop, 0, 1, False,
119 XA_WINDOW, &type, &format, &nitems, &after, &data);
120
121 if (data == NULL)
122 return FALSE;
123
124 caja_window = *(Window *) data;
125 XFree (data);
126
127 if (type != XA_WINDOW || format != 32)
128 return FALSE;
129
130 wmclass_prop = XInternAtom (display, "WM_CLASS", True);
131 if (wmclass_prop == None)
132 return FALSE;
133
134 gdk_display = gdk_display_get_default ();
135 gdk_x11_display_error_trap_push (gdk_display);
136
137 XGetWindowProperty (display, caja_window, wmclass_prop, 0, 20, False,
138 XA_STRING, &type, &format, &nitems, &after, &data);
139
140 XSync (display, False);
141
142 if (gdk_x11_display_error_trap_pop (gdk_display) == BadWindow || data == NULL)
143 return FALSE;
144
145 /* See: caja_desktop_window_new(), in src/caja-desktop-window.c */
146 if (nitems == 20 && after == 0 && format == 8 &&
147 !strcmp((char*) data, "desktop_window") &&
148 !strcmp((char*) data + strlen((char*) data) + 1, "Caja"))
149 {
150 running = TRUE;
151 }
152 XFree (data);
153
154 return running;
155 }
156
157 static void
free_fade(MsdBackgroundManager * manager)158 free_fade (MsdBackgroundManager *manager)
159 {
160 if (manager->fade != NULL) {
161 g_object_unref (manager->fade);
162 manager->fade = NULL;
163 }
164 }
165
166 static void
free_bg_surface(MsdBackgroundManager * manager)167 free_bg_surface (MsdBackgroundManager *manager)
168 {
169 if (manager->surface != NULL) {
170 cairo_surface_destroy (manager->surface);
171 manager->surface = NULL;
172 }
173 }
174
175 static void
free_scr_sizes(MsdBackgroundManager * manager)176 free_scr_sizes (MsdBackgroundManager *manager)
177 {
178 if (manager->scr_sizes != NULL) {
179 g_list_free_full (manager->scr_sizes, g_free);
180 manager->scr_sizes = NULL;
181 }
182 }
183
184 static void
real_draw_bg(MsdBackgroundManager * manager,GdkScreen * screen)185 real_draw_bg (MsdBackgroundManager *manager,
186 GdkScreen *screen)
187 {
188 GdkWindow *window = gdk_screen_get_root_window (screen);
189 gint scale = gdk_window_get_scale_factor (window);
190 gint width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
191 gint height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
192
193 free_bg_surface (manager);
194 manager->surface = mate_bg_create_surface_scale (manager->bg, window, width, height, scale, TRUE);
195
196 if (manager->do_fade)
197 {
198 free_fade (manager);
199 manager->fade = mate_bg_set_surface_as_root_with_crossfade (screen, manager->surface);
200 g_signal_connect_swapped (manager->fade, "finished", G_CALLBACK (free_fade), manager);
201 }
202 else
203 {
204 mate_bg_set_surface_as_root (screen, manager->surface);
205 }
206 manager->scr_sizes = g_list_prepend (manager->scr_sizes, g_strdup_printf ("%dx%d", width, height));
207 }
208
209 static void
draw_background(MsdBackgroundManager * manager,gboolean may_fade)210 draw_background (MsdBackgroundManager *manager,
211 gboolean may_fade)
212 {
213 if (!manager->msd_can_draw || manager->draw_in_progress || caja_is_drawing_bg (manager))
214 return;
215
216 mate_settings_profile_start (NULL);
217
218 GdkDisplay *display = gdk_display_get_default ();
219
220 manager->draw_in_progress = TRUE;
221 manager->do_fade = may_fade && can_fade_bg (manager);
222 free_scr_sizes (manager);
223
224 g_debug ("Drawing background on Screen");
225 real_draw_bg (manager, gdk_display_get_default_screen (display));
226
227 manager->scr_sizes = g_list_reverse (manager->scr_sizes);
228
229 manager->draw_in_progress = FALSE;
230 mate_settings_profile_end (NULL);
231 }
232
233 static void
on_bg_changed(MateBG * bg G_GNUC_UNUSED,MsdBackgroundManager * manager)234 on_bg_changed (MateBG *bg G_GNUC_UNUSED,
235 MsdBackgroundManager *manager)
236 {
237 g_debug ("Background changed");
238 draw_background (manager, TRUE);
239 }
240
241 static void
on_bg_transitioned(MateBG * bg G_GNUC_UNUSED,MsdBackgroundManager * manager)242 on_bg_transitioned (MateBG *bg G_GNUC_UNUSED,
243 MsdBackgroundManager *manager)
244 {
245 g_debug ("Background transitioned");
246 draw_background (manager, FALSE);
247 }
248
249 static void
on_screen_size_changed(GdkScreen * screen,MsdBackgroundManager * manager)250 on_screen_size_changed (GdkScreen *screen,
251 MsdBackgroundManager *manager)
252 {
253 if (!manager->msd_can_draw || manager->draw_in_progress || caja_is_drawing_bg (manager))
254 return;
255
256 GdkWindow *window = gdk_screen_get_root_window (screen);
257 gint scale = gdk_window_get_scale_factor (window);
258 gint scr_num = gdk_x11_screen_get_screen_number (screen);
259 gchar *old_size = g_list_nth_data (manager->scr_sizes, (guint) scr_num);
260 gchar *new_size = g_strdup_printf ("%dx%d", WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale,
261 HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale);
262 if (g_strcmp0 (old_size, new_size) != 0)
263 {
264 g_debug ("Screen%d size changed: %s -> %s", scr_num, old_size, new_size);
265 draw_background (manager, FALSE);
266 } else {
267 g_debug ("Screen%d size unchanged (%s). Ignoring.", scr_num, old_size);
268 }
269 g_free (new_size);
270 }
271
272 static void
disconnect_screen_signals(MsdBackgroundManager * manager)273 disconnect_screen_signals (MsdBackgroundManager *manager)
274 {
275 GdkDisplay *display = gdk_display_get_default();
276
277 g_signal_handlers_disconnect_by_func
278 (gdk_display_get_default_screen (display),
279 G_CALLBACK (on_screen_size_changed), manager);
280 }
281
282 static void
connect_screen_signals(MsdBackgroundManager * manager)283 connect_screen_signals (MsdBackgroundManager *manager)
284 {
285 GdkDisplay *display = gdk_display_get_default();
286
287 GdkScreen *screen = gdk_display_get_default_screen (display);
288
289 g_signal_connect (screen, "monitors-changed",
290 G_CALLBACK (on_screen_size_changed), manager);
291 g_signal_connect (screen, "size-changed",
292 G_CALLBACK (on_screen_size_changed), manager);
293 }
294
295 static gboolean
settings_change_event_idle_cb(MsdBackgroundManager * manager)296 settings_change_event_idle_cb (MsdBackgroundManager *manager)
297 {
298 mate_settings_profile_start ("settings_change_event_idle_cb");
299
300 mate_bg_load_from_preferences (manager->bg);
301
302 mate_settings_profile_end ("settings_change_event_idle_cb");
303
304 return FALSE; /* remove from the list of event sources */
305 }
306
307 static gboolean
settings_change_event_cb(GSettings * settings G_GNUC_UNUSED,gpointer keys G_GNUC_UNUSED,gint n_keys G_GNUC_UNUSED,MsdBackgroundManager * manager)308 settings_change_event_cb (GSettings *settings G_GNUC_UNUSED,
309 gpointer keys G_GNUC_UNUSED,
310 gint n_keys G_GNUC_UNUSED,
311 MsdBackgroundManager *manager)
312 {
313 /* Complements on_bg_handling_changed() */
314 manager->msd_can_draw = msd_can_draw_bg (manager);
315 manager->caja_can_draw = caja_can_draw_bg (manager);
316
317 if (manager->msd_can_draw && manager->bg != NULL && !caja_is_drawing_bg (manager))
318 {
319 /* Defer signal processing to avoid making the dconf backend deadlock */
320 g_idle_add ((GSourceFunc) settings_change_event_idle_cb, manager);
321 }
322
323 return FALSE; /* let the event propagate further */
324 }
325
326 static void
setup_background(MsdBackgroundManager * manager)327 setup_background (MsdBackgroundManager *manager)
328 {
329 g_return_if_fail (manager->bg == NULL);
330
331 manager->bg = mate_bg_new();
332
333 manager->draw_in_progress = FALSE;
334
335 g_signal_connect(manager->bg, "changed", G_CALLBACK (on_bg_changed), manager);
336
337 g_signal_connect(manager->bg, "transitioned", G_CALLBACK (on_bg_transitioned), manager);
338
339 mate_bg_load_from_gsettings (manager->bg, manager->settings);
340
341 connect_screen_signals (manager);
342
343 g_signal_connect (manager->settings, "change-event",
344 G_CALLBACK (settings_change_event_cb), manager);
345 }
346
347 static void
remove_background(MsdBackgroundManager * manager)348 remove_background (MsdBackgroundManager *manager)
349 {
350 disconnect_screen_signals (manager);
351
352 g_signal_handlers_disconnect_by_func (manager->settings, settings_change_event_cb, manager);
353
354 if (manager->settings != NULL) {
355 g_object_unref (manager->settings);
356 manager->settings = NULL;
357 }
358
359 if (manager->bg != NULL) {
360 g_object_unref (manager->bg);
361 manager->bg = NULL;
362 }
363
364 free_scr_sizes (manager);
365 free_bg_surface (manager);
366 free_fade (manager);
367 }
368
369 static void
on_bg_handling_changed(GSettings * settings G_GNUC_UNUSED,const char * key G_GNUC_UNUSED,MsdBackgroundManager * manager)370 on_bg_handling_changed (GSettings *settings G_GNUC_UNUSED,
371 const char *key G_GNUC_UNUSED,
372 MsdBackgroundManager *manager)
373 {
374 mate_settings_profile_start (NULL);
375
376 if (caja_is_drawing_bg (manager))
377 {
378 if (manager->bg != NULL)
379 remove_background (manager);
380 }
381 else if (manager->msd_can_draw && manager->bg == NULL)
382 {
383 setup_background (manager);
384 }
385
386 mate_settings_profile_end (NULL);
387 }
388
389 static gboolean
queue_setup_background(MsdBackgroundManager * manager)390 queue_setup_background (MsdBackgroundManager *manager)
391 {
392 manager->timeout_id = 0;
393
394 setup_background (manager);
395
396 return FALSE;
397 }
398
399 static void
queue_timeout(MsdBackgroundManager * manager)400 queue_timeout (MsdBackgroundManager *manager)
401 {
402 if (manager->timeout_id > 0)
403 return;
404
405 /* SessionRunning: now check if Caja is drawing background, and if not, set it.
406 *
407 * FIXME: We wait a few seconds after the session is up because Caja tells the
408 * session manager that its ready before it sets the background.
409 * https://bugzilla.gnome.org/show_bug.cgi?id=568588
410 */
411 manager->timeout_id =
412 g_timeout_add_seconds (8, (GSourceFunc) queue_setup_background, manager);
413 }
414
415 static void
disconnect_session_manager_listener(MsdBackgroundManager * manager)416 disconnect_session_manager_listener (MsdBackgroundManager* manager)
417 {
418 if (manager->proxy && manager->proxy_signal_id) {
419 g_signal_handler_disconnect (manager->proxy, manager->proxy_signal_id);
420 manager->proxy_signal_id = 0;
421 }
422 }
423
424 static void
on_session_manager_signal(GDBusProxy * proxy G_GNUC_UNUSED,const gchar * sender_name G_GNUC_UNUSED,const gchar * signal_name,GVariant * parameters G_GNUC_UNUSED,gpointer user_data)425 on_session_manager_signal (GDBusProxy *proxy G_GNUC_UNUSED,
426 const gchar *sender_name G_GNUC_UNUSED,
427 const gchar *signal_name,
428 GVariant *parameters G_GNUC_UNUSED,
429 gpointer user_data)
430 {
431 MsdBackgroundManager *manager = MSD_BACKGROUND_MANAGER (user_data);
432
433 if (g_strcmp0 (signal_name, "SessionRunning") == 0) {
434 queue_timeout (manager);
435 disconnect_session_manager_listener (manager);
436 }
437 }
438
439 static void
draw_bg_after_session_loads(MsdBackgroundManager * manager)440 draw_bg_after_session_loads (MsdBackgroundManager *manager)
441 {
442 GError *error = NULL;
443 GDBusProxyFlags flags;
444
445 flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
446 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;
447 manager->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
448 flags,
449 NULL, /* GDBusInterfaceInfo */
450 MATE_SESSION_MANAGER_DBUS_NAME,
451 MATE_SESSION_MANAGER_DBUS_PATH,
452 MATE_SESSION_MANAGER_DBUS_NAME,
453 NULL, /* GCancellable */
454 &error);
455 if (manager->proxy == NULL) {
456 g_warning ("Could not listen to session manager: %s",
457 error->message);
458 g_error_free (error);
459 return;
460 }
461
462 manager->proxy_signal_id = g_signal_connect (manager->proxy,
463 "g-signal",
464 G_CALLBACK (on_session_manager_signal),
465 manager);
466 }
467
468 gboolean
msd_background_manager_start(MsdBackgroundManager * manager,GError ** error)469 msd_background_manager_start (MsdBackgroundManager *manager,
470 GError **error)
471 {
472 g_debug ("Starting background manager");
473 mate_settings_profile_start (NULL);
474
475 manager->settings = g_settings_new (MATE_BG_SCHEMA);
476
477 manager->msd_can_draw = msd_can_draw_bg (manager);
478 manager->caja_can_draw = caja_can_draw_bg (manager);
479
480 g_signal_connect (manager->settings, "changed::" MATE_BG_KEY_DRAW_BACKGROUND,
481 G_CALLBACK (on_bg_handling_changed), manager);
482 g_signal_connect (manager->settings, "changed::" MATE_BG_KEY_SHOW_DESKTOP,
483 G_CALLBACK (on_bg_handling_changed), manager);
484
485 /* If Caja is set to draw the background, it is very likely in our session.
486 * But it might not be started yet, so caja_is_drawing_bg() would fail.
487 * In this case, we wait till the session is loaded, to avoid double-draws.
488 */
489 if (manager->msd_can_draw)
490 {
491 if (manager->caja_can_draw)
492 draw_bg_after_session_loads (manager);
493 else
494 setup_background (manager);
495 }
496
497 mate_settings_profile_end (NULL);
498
499 return TRUE;
500 }
501
502 void
msd_background_manager_stop(MsdBackgroundManager * manager)503 msd_background_manager_stop (MsdBackgroundManager *manager)
504 {
505 g_debug ("Stopping background manager");
506
507 if (manager->proxy)
508 {
509 disconnect_session_manager_listener (manager);
510 g_object_unref (manager->proxy);
511 }
512
513 if (manager->timeout_id != 0) {
514 g_source_remove (manager->timeout_id);
515 manager->timeout_id = 0;
516 }
517
518 remove_background (manager);
519 }
520
521 static GObject*
msd_background_manager_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)522 msd_background_manager_constructor (GType type,
523 guint n_construct_properties,
524 GObjectConstructParam* construct_properties)
525 {
526 MsdBackgroundManager *manager =
527 MSD_BACKGROUND_MANAGER (
528 G_OBJECT_CLASS (msd_background_manager_parent_class)->constructor (
529 type, n_construct_properties, construct_properties));
530
531 return G_OBJECT(manager);
532 }
533
534 static void
msd_background_manager_finalize(GObject * object)535 msd_background_manager_finalize (GObject *object)
536 {
537 g_return_if_fail (object != NULL);
538 g_return_if_fail (MSD_IS_BACKGROUND_MANAGER (object));
539
540 G_OBJECT_CLASS(msd_background_manager_parent_class)->finalize(object);
541 }
542
543 static void
msd_background_manager_init(MsdBackgroundManager * manager)544 msd_background_manager_init (MsdBackgroundManager* manager)
545 {
546 }
547
548 static void
msd_background_manager_class_init(MsdBackgroundManagerClass * klass)549 msd_background_manager_class_init (MsdBackgroundManagerClass *klass)
550 {
551 GObjectClass *object_class = G_OBJECT_CLASS(klass);
552
553 object_class->constructor = msd_background_manager_constructor;
554 object_class->finalize = msd_background_manager_finalize;
555 }
556
557 MsdBackgroundManager*
msd_background_manager_new(void)558 msd_background_manager_new (void)
559 {
560 if (manager_object != NULL)
561 {
562 g_object_ref(manager_object);
563 }
564 else
565 {
566 manager_object = g_object_new(MSD_TYPE_BACKGROUND_MANAGER, NULL);
567
568 g_object_add_weak_pointer(manager_object, (gpointer*) &manager_object);
569 }
570
571 return MSD_BACKGROUND_MANAGER(manager_object);
572 }
573