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