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