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