1 /*
2  * Copyright (C) 2003 Red Hat, Inc.
3  *
4  * This is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU Library General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include <config.h>
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <gtk/gtk.h>
24 #include "debug.h"
25 #include "marshal.h"
26 #include "vtebg.h"
27 #include "vte-gtk-compat.h"
28 
29 #ifdef GDK_WINDOWING_X11
30 #include <gdk/gdkx.h>
31 #include <cairo-xlib.h>
32 #endif
33 
34 G_DEFINE_TYPE(VteBg, vte_bg, G_TYPE_OBJECT)
35 
36 struct _VteBgPrivate {
37 	GList *cache;
38 	GdkScreen *screen;
39 #ifdef GDK_WINDOWING_X11
40 	cairo_surface_t *root_surface;
41         struct {
42                 GdkDisplay *display;
43                 GdkWindow *window;
44                 XID native_window;
45                 GdkAtom atom;
46                 Atom native_atom;
47         } native;
48 #endif
49 };
50 
51 typedef struct {
52 	VteBgSourceType source_type;
53 	GdkPixbuf *source_pixbuf;
54 	char *source_file;
55 
56 	PangoColor tint_color;
57 	double saturation;
58 	cairo_surface_t *surface;
59 } VteBgCacheItem;
60 
61 static void vte_bg_cache_item_free(VteBgCacheItem *item);
62 static void vte_bg_cache_prune_int(VteBg *bg, gboolean root);
63 static const cairo_user_data_key_t item_surface_key;
64 
65 #ifdef GDK_WINDOWING_X11
66 
67 static void
_vte_bg_display_sync(VteBg * bg)68 _vte_bg_display_sync(VteBg *bg)
69 {
70         VteBgPrivate *pvt = bg->pvt;
71 
72 	gdk_display_sync(pvt->native.display);
73 }
74 
75 static gboolean
_vte_property_get_pixmaps(GdkWindow * window,GdkAtom atom,GdkAtom * type,int * size,XID ** pixmaps)76 _vte_property_get_pixmaps(GdkWindow *window, GdkAtom atom,
77 			  GdkAtom *type, int *size,
78 			  XID **pixmaps)
79 {
80 	return gdk_property_get(window, atom, GDK_TARGET_PIXMAP,
81 				0, INT_MAX - 3,
82 				FALSE,
83 				type, NULL, size,
84 				(guchar**) pixmaps);
85 }
86 
87 static cairo_surface_t *
vte_bg_root_surface(VteBg * bg)88 vte_bg_root_surface(VteBg *bg)
89 {
90         VteBgPrivate *pvt = bg->pvt;
91 	GdkAtom prop_type;
92 	int prop_size;
93 	Window root;
94 	XID *pixmaps;
95 	int x, y;
96 	unsigned int width, height, border_width, depth;
97 	cairo_surface_t *surface = NULL;
98 	Display *display;
99 	Screen *screen;
100 
101 	pixmaps = NULL;
102 	gdk_error_trap_push();
103 	if (!_vte_property_get_pixmaps(pvt->native.window, pvt->native.atom,
104                                        &prop_type, &prop_size,
105                                        &pixmaps))
106 		goto out;
107 
108 	if ((prop_type != GDK_TARGET_PIXMAP) ||
109 	    (prop_size < (int)sizeof(XID) ||
110 	     (pixmaps == NULL)))
111 		goto out_pixmaps;
112 
113 	if (!XGetGeometry (GDK_DISPLAY_XDISPLAY (pvt->native.display),
114 			   pixmaps[0], &root,
115 			   &x, &y, &width, &height, &border_width, &depth))
116 		goto out_pixmaps;
117 
118 	display = gdk_x11_display_get_xdisplay (pvt->native.display);
119 	screen = gdk_x11_screen_get_xscreen (pvt->screen);
120 	surface = cairo_xlib_surface_create (display,
121 					     pixmaps[0],
122 					     DefaultVisualOfScreen(screen),
123 					     width, height);
124 
125         _vte_debug_print(VTE_DEBUG_BG|VTE_DEBUG_EVENTS,
126                          "VteBg new background image %dx%d\n", width, height);
127 
128  out_pixmaps:
129 	g_free(pixmaps);
130  out:
131 	_vte_bg_display_sync(bg);
132 	gdk_error_trap_pop_ignored ();
133 
134 	return surface;
135 }
136 
137 static void
vte_bg_set_root_surface(VteBg * bg,cairo_surface_t * surface)138 vte_bg_set_root_surface(VteBg *bg, cairo_surface_t *surface)
139 {
140         VteBgPrivate *pvt = bg->pvt;
141 
142 	if (pvt->root_surface != NULL) {
143 		cairo_surface_destroy (pvt->root_surface);
144 	}
145 	pvt->root_surface = surface;
146 	vte_bg_cache_prune_int (bg, TRUE);
147 	g_signal_emit_by_name(bg, "root-pixmap-changed");
148 }
149 
150 static GdkFilterReturn
vte_bg_root_filter(GdkXEvent * native,GdkEvent * event,gpointer data)151 vte_bg_root_filter(GdkXEvent *native, GdkEvent *event, gpointer data)
152 {
153 	XEvent *xevent = (XEvent*) native;
154 	VteBg *bg;
155         VteBgPrivate *pvt;
156 	cairo_surface_t *surface;
157 
158 	switch (xevent->type) {
159 	case PropertyNotify:
160 		bg = VTE_BG(data);
161                 pvt = bg->pvt;
162 		if ((xevent->xproperty.window == pvt->native.native_window) &&
163 		    (xevent->xproperty.atom == pvt->native.native_atom)) {
164 			surface = vte_bg_root_surface(bg);
165 			vte_bg_set_root_surface(bg, surface);
166 		}
167 		break;
168 	default:
169 		break;
170 	}
171 	return GDK_FILTER_CONTINUE;
172 }
173 
174 #endif /* GDK_WINDOWING_X11 */
175 
176 static void
vte_bg_finalize(GObject * obj)177 vte_bg_finalize (GObject *obj)
178 {
179 	VteBg *bg = VTE_BG (obj);
180         VteBgPrivate *pvt = bg->pvt;
181 
182         g_list_foreach (pvt->cache, (GFunc)vte_bg_cache_item_free, NULL);
183         g_list_free (pvt->cache);
184 
185 	G_OBJECT_CLASS(vte_bg_parent_class)->finalize (obj);
186 }
187 
188 static void
vte_bg_class_init(VteBgClass * klass)189 vte_bg_class_init(VteBgClass *klass)
190 {
191 	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
192 
193 	gobject_class->finalize = vte_bg_finalize;
194 
195 	g_signal_new("root-pixmap-changed",
196                      G_OBJECT_CLASS_TYPE(klass),
197                      G_SIGNAL_RUN_LAST,
198                      0,
199                      NULL,
200                      NULL,
201                      g_cclosure_marshal_VOID__VOID,
202                      G_TYPE_NONE, 0);
203 	g_type_class_add_private(klass, sizeof (VteBgPrivate));
204 }
205 
206 static void
vte_bg_init(VteBg * bg)207 vte_bg_init(VteBg *bg)
208 {
209 	bg->pvt = G_TYPE_INSTANCE_GET_PRIVATE (bg, VTE_TYPE_BG, VteBgPrivate);
210 }
211 
212 /**
213  * vte_bg_get:
214  * @screen: a #GdkScreen
215  *
216  * Returns the global #VteBg object for @screen, creating it if necessary.
217  *
218  * Returns: (transfer none): a #VteBg
219  */
220 VteBg *
vte_bg_get_for_screen(GdkScreen * screen)221 vte_bg_get_for_screen(GdkScreen *screen)
222 {
223 	VteBg       *bg;
224 
225 	bg = g_object_get_data(G_OBJECT(screen), "vte-bg");
226 	if (G_UNLIKELY(bg == NULL)) {
227                 VteBgPrivate *pvt;
228 
229 		bg = g_object_new(VTE_TYPE_BG, NULL);
230 		g_object_set_data_full(G_OBJECT(screen),
231 				"vte-bg", bg, (GDestroyNotify)g_object_unref);
232 
233 		/* connect bg to screen */
234                 pvt = bg->pvt;
235 		pvt->screen = screen;
236 #ifdef GDK_WINDOWING_X11
237             {
238                 GdkEventMask events;
239                 GdkWindow   *window;
240 
241 		window = gdk_screen_get_root_window(screen);
242                 pvt->native.window = window;
243 #if GTK_CHECK_VERSION (2, 91, 6)
244                 pvt->native.native_window = GDK_WINDOW_XID (window);
245                 pvt->native.display = gdk_window_get_display(window);
246 #else
247                 pvt->native.native_window = gdk_x11_drawable_get_xid(window);
248                 pvt->native.display = gdk_drawable_get_display(GDK_DRAWABLE(window));
249 #endif
250                 pvt->native.native_atom = gdk_x11_get_xatom_by_name_for_display(pvt->native.display, "_XROOTPMAP_ID");
251                 pvt->native.atom = gdk_x11_xatom_to_atom_for_display(pvt->native.display, pvt->native.native_atom);
252 		pvt->root_surface = vte_bg_root_surface(bg);
253 		events = gdk_window_get_events(window);
254 		events |= GDK_PROPERTY_CHANGE_MASK;
255 		gdk_window_set_events(window, events);
256 		gdk_window_add_filter(window, vte_bg_root_filter, bg);
257             }
258 #endif /* GDK_WINDOWING_X11 */
259 	}
260 
261 	return bg;
262 }
263 
264 static gboolean
vte_bg_colors_equal(const PangoColor * a,const PangoColor * b)265 vte_bg_colors_equal(const PangoColor *a, const PangoColor *b)
266 {
267 	return  (a->red >> 8) == (b->red >> 8) &&
268 		(a->green >> 8) == (b->green >> 8) &&
269 		(a->blue >> 8) == (b->blue >> 8);
270 }
271 
272 static void
vte_bg_cache_item_free(VteBgCacheItem * item)273 vte_bg_cache_item_free(VteBgCacheItem *item)
274 {
275         _vte_debug_print(VTE_DEBUG_BG,
276                          "VteBgCacheItem %p freed\n", item);
277 
278 	/* Clean up whatever is left in the structure. */
279 	if (item->source_pixbuf != NULL) {
280 		g_object_remove_weak_pointer(G_OBJECT(item->source_pixbuf),
281 				(gpointer*)(void*)&item->source_pixbuf);
282 	}
283 	g_free(item->source_file);
284 
285 	if (item->surface != NULL)
286 		cairo_surface_set_user_data (item->surface,
287 					     &item_surface_key, NULL, NULL);
288 
289 	g_slice_free(VteBgCacheItem, item);
290 }
291 
292 static void
vte_bg_cache_prune_int(VteBg * bg,gboolean root)293 vte_bg_cache_prune_int(VteBg *bg, gboolean root)
294 {
295 	GList *i, *next;
296 	for (i = bg->pvt->cache; i != NULL; i = next) {
297 		VteBgCacheItem *item = i->data;
298 		next = g_list_next (i);
299 		/* Prune the item if either it is a "root pixmap" item and
300 		 * we want to prune them, or its surface is NULL because
301 		 * whichever object it created has been destroyed. */
302 		if ((root && (item->source_type == VTE_BG_SOURCE_ROOT)) ||
303 		    item->surface == NULL) {
304 			vte_bg_cache_item_free (item);
305 			bg->pvt->cache = g_list_delete_link(bg->pvt->cache, i);
306 		}
307 	}
308 }
309 
310 static void
vte_bg_cache_prune(VteBg * bg)311 vte_bg_cache_prune(VteBg *bg)
312 {
313 	vte_bg_cache_prune_int(bg, FALSE);
314 }
315 
item_surface_destroy_func(void * data)316 static void item_surface_destroy_func(void *data)
317 {
318 	VteBgCacheItem *item = data;
319 
320         _vte_debug_print(VTE_DEBUG_BG,
321                          "VteBgCacheItem %p surface destroyed\n", item);
322 
323 	item->surface = NULL;
324 }
325 
326 /*
327  * vte_bg_cache_add:
328  * @bg: a #VteBg
329  * @item: a #VteBgCacheItem
330  *
331  * Adds @item to @bg's cache, instructing all of the objects therein to
332  * clear the field which holds a pointer to the object upon its destruction.
333  */
334 static void
vte_bg_cache_add(VteBg * bg,VteBgCacheItem * item)335 vte_bg_cache_add(VteBg *bg, VteBgCacheItem *item)
336 {
337 	vte_bg_cache_prune(bg);
338 	bg->pvt->cache = g_list_prepend(bg->pvt->cache, item);
339 	if (item->source_pixbuf != NULL) {
340 		g_object_add_weak_pointer(G_OBJECT(item->source_pixbuf),
341 					  (gpointer*)(void*)&item->source_pixbuf);
342 	}
343 
344         if (item->surface != NULL)
345                 cairo_surface_set_user_data (item->surface, &item_surface_key, item,
346                                             item_surface_destroy_func);
347 }
348 
349 /*
350  * vte_bg_cache_search:
351  * @bg: a #VteBg
352  * @source_type: a #VteBgSourceType
353  * @source_pixbuf: a #GdkPixbuf, or %NULL
354  * @source_file: path of an image file, or %NULL
355  * @tint: a #PangoColor to use as tint color
356  * @saturation: the saturation as a value between 0.0 and 1.0
357  *
358  * Returns: a reference to a #cairo_surface_t, or %NULL on if
359  *   there is no matching item in the cache
360  */
361 static cairo_surface_t *
vte_bg_cache_search(VteBg * bg,VteBgSourceType source_type,const GdkPixbuf * source_pixbuf,const char * source_file,const PangoColor * tint,double saturation)362 vte_bg_cache_search(VteBg *bg,
363 		    VteBgSourceType source_type,
364 		    const GdkPixbuf *source_pixbuf,
365 		    const char *source_file,
366 		    const PangoColor *tint,
367 		    double saturation)
368 {
369 	GList *i;
370 
371 	vte_bg_cache_prune(bg);
372 	for (i = bg->pvt->cache; i != NULL; i = g_list_next(i)) {
373 		VteBgCacheItem *item = i->data;
374 		if (vte_bg_colors_equal(&item->tint_color, tint) &&
375 		    (saturation == item->saturation) &&
376 		    (source_type == item->source_type)) {
377 			switch (source_type) {
378 			case VTE_BG_SOURCE_ROOT:
379 				break;
380 			case VTE_BG_SOURCE_PIXBUF:
381 				if (item->source_pixbuf != source_pixbuf) {
382 					continue;
383 				}
384 				break;
385 			case VTE_BG_SOURCE_FILE:
386 				if (strcmp(item->source_file, source_file)) {
387 					continue;
388 				}
389 				break;
390 			default:
391 				g_assert_not_reached();
392 				break;
393 			}
394 
395 			return cairo_surface_reference(item->surface);
396 		}
397 	}
398 	return NULL;
399 }
400 
401 /*< private >
402  * vte_bg_get_surface:
403  * @bg: a #VteBg
404  * @source_type: a #VteBgSourceType
405  * @source_pixbuf: (allow-none): a #GdkPixbuf, or %NULL
406  * @source_file: (allow-none): path of an image file, or %NULL
407  * @tint: a #PangoColor to use as tint color
408  * @saturation: the saturation as a value between 0.0 and 1.0
409  * @other: a #cairo_surface_t
410  *
411  * Returns: a reference to a #cairo_surface_t, or %NULL on failure
412  */
413 cairo_surface_t *
vte_bg_get_surface(VteBg * bg,VteBgSourceType source_type,GdkPixbuf * source_pixbuf,const char * source_file,const PangoColor * tint,double saturation,cairo_surface_t * other)414 vte_bg_get_surface(VteBg *bg,
415 		   VteBgSourceType source_type,
416 		   GdkPixbuf *source_pixbuf,
417 		   const char *source_file,
418 		   const PangoColor *tint,
419 		   double saturation,
420 		   cairo_surface_t *other)
421 {
422         VteBgPrivate *pvt;
423 	VteBgCacheItem *item;
424 	GdkPixbuf *pixbuf;
425 	cairo_surface_t *cached;
426 	cairo_t *cr;
427 	int width, height;
428 
429         g_return_val_if_fail(VTE_IS_BG(bg), NULL);
430         pvt = bg->pvt;
431 
432 	if (source_type == VTE_BG_SOURCE_NONE) {
433 		return NULL;
434 	}
435 #ifndef GDK_WINDOWING_X11
436         if (source_type == VTE_BG_SOURCE_ROOT) {
437                 return NULL;
438         }
439 #endif
440 
441 	cached = vte_bg_cache_search(bg, source_type,
442 				     source_pixbuf, source_file,
443 				     tint, saturation);
444 	if (cached != NULL) {
445 		return cached;
446 	}
447 
448         /* FIXME: The above only returned a hit when the source *and*
449          * tint and saturation matched. This means that for VTE_BG_SOURCE_FILE,
450          * we will create below *another* #GdkPixbuf for the same source file,
451          * wasting memory. We should instead look up the source pixbuf regardless
452          * of tint and saturation, and just create a new #VteBgCacheItem
453          * with a new surface for it.
454          */
455 
456 	item = g_slice_new(VteBgCacheItem);
457 	item->source_type = source_type;
458 	item->source_pixbuf = NULL;
459 	item->source_file = NULL;
460 	item->tint_color = *tint;
461 	item->saturation = saturation;
462         item->surface = NULL;
463 	pixbuf = NULL;
464 
465 	switch (source_type) {
466 	case VTE_BG_SOURCE_ROOT:
467 		break;
468 	case VTE_BG_SOURCE_PIXBUF:
469 		item->source_pixbuf = g_object_ref (source_pixbuf);
470 		pixbuf = g_object_ref (source_pixbuf);
471 		break;
472 	case VTE_BG_SOURCE_FILE:
473 		if (source_file != NULL && source_file[0] != '\0') {
474 			item->source_file = g_strdup(source_file);
475 			pixbuf = gdk_pixbuf_new_from_file(source_file, NULL);
476 		}
477 		break;
478 	default:
479 		g_assert_not_reached();
480 		break;
481 	}
482 
483 	if (pixbuf) {
484 		width = gdk_pixbuf_get_width(pixbuf);
485 		height = gdk_pixbuf_get_height(pixbuf);
486 	}
487 #ifdef GDK_WINDOWING_X11
488         else if (source_type == VTE_BG_SOURCE_ROOT &&
489                  pvt->root_surface != NULL) {
490 		width = cairo_xlib_surface_get_width(pvt->root_surface);
491 		height = cairo_xlib_surface_get_height(pvt->root_surface);
492 	}
493 #endif
494         else
495                 goto out;
496 
497 	item->surface =
498 		cairo_surface_create_similar(other, CAIRO_CONTENT_COLOR_ALPHA,
499 					     width, height);
500 
501 	cr = cairo_create (item->surface);
502 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
503 	if (pixbuf)
504 		gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
505 #ifdef GDK_WINDOWING_X11
506 	else if (source_type == VTE_BG_SOURCE_ROOT)
507 		cairo_set_source_surface (cr, pvt->root_surface, 0, 0);
508 #endif
509 	cairo_paint (cr);
510 
511 	if (saturation < 1.0) {
512 		cairo_set_source_rgba (cr,
513 				       tint->red / 65535.,
514 				       tint->green / 65535.,
515 				       tint->blue / 65535.,
516 				       1 - saturation);
517 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
518 		cairo_paint (cr);
519 	}
520 	cairo_destroy (cr);
521 
522     out:
523 	vte_bg_cache_add(bg, item);
524 
525 	if (pixbuf)
526 		g_object_unref (pixbuf);
527 
528 	return item->surface;
529 }
530