/* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2005-2007 Vincent Untz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, see . */ #include "config.h" #include "wnck-icon-cache-private.h" #include #if HAVE_CAIRO_XLIB_XRENDER #include #endif #include "private.h" #include "xutils.h" typedef enum { /* These MUST be in ascending order of preference; * i.e. if we get _NET_WM_ICON and already have * WM_HINTS, we prefer _NET_WM_ICON */ USING_NO_ICON, USING_FALLBACK_ICON, USING_KWM_WIN_ICON, USING_WM_HINTS, USING_NET_WM_ICON } IconOrigin; struct _WnckIconCache { IconOrigin origin; Pixmap prev_pixmap; Pixmap prev_mask; GdkPixbuf *icon; GdkPixbuf *mini_icon; int ideal_size; int ideal_mini_size; guint want_fallback : 1; /* TRUE if these props have changed */ guint wm_hints_dirty : 1; guint kwm_win_icon_dirty : 1; guint net_wm_icon_dirty : 1; }; static gboolean find_best_size (gulong *data, gulong nitems, int ideal_size, int *width, int *height, gulong **start) { int best_w; int best_h; gulong *best_start; *width = 0; *height = 0; *start = NULL; best_w = 0; best_h = 0; best_start = NULL; while (nitems > 0) { int w, h; gboolean replace; replace = FALSE; if (nitems < 3) return FALSE; /* no space for w, h */ w = data[0]; h = data[1]; if (nitems < ((gulong) (w * h) + 2)) break; /* not enough data */ if (best_start == NULL) { replace = TRUE; } else { /* work with averages */ int best_size = (best_w + best_h) / 2; int this_size = (w + h) / 2; /* larger than desired is always better than smaller */ if (best_size < ideal_size && this_size >= ideal_size) replace = TRUE; /* if we have too small, pick anything bigger */ else if (best_size < ideal_size && this_size > best_size) replace = TRUE; /* if we have too large, pick anything smaller * but still >= the ideal */ else if (best_size > ideal_size && this_size >= ideal_size && this_size < best_size) replace = TRUE; } if (replace) { best_start = data + 2; best_w = w; best_h = h; } data += (w * h) + 2; nitems -= (w * h) + 2; } if (best_start) { *start = best_start; *width = best_w; *height = best_h; return TRUE; } else return FALSE; } static void argbdata_to_pixdata (gulong *argb_data, int len, guchar **pixdata) { guchar *p; int i; *pixdata = g_new (guchar, len * 4); p = *pixdata; /* One could speed this up a lot. */ i = 0; while (i < len) { guint argb; guint rgba; argb = argb_data[i]; rgba = (argb << 8) | (argb >> 24); *p = rgba >> 24; ++p; *p = (rgba >> 16) & 0xff; ++p; *p = (rgba >> 8) & 0xff; ++p; *p = rgba & 0xff; ++p; ++i; } } static gboolean read_rgb_icon (Screen *screen, Window xwindow, int ideal_size, int ideal_mini_size, int *width, int *height, guchar **pixdata, int *mini_width, int *mini_height, guchar **mini_pixdata) { Display *display; Atom type; int format; gulong nitems; gulong bytes_after; int result, err; gulong *data; gulong *best; int w, h; gulong *best_mini; int mini_w, mini_h; display = DisplayOfScreen (screen); _wnck_error_trap_push (display); type = None; data = NULL; result = XGetWindowProperty (display, xwindow, _wnck_atom_get ("_NET_WM_ICON"), 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (void*)&data); err = _wnck_error_trap_pop (display); if (err != Success || result != Success) return FALSE; if (type != XA_CARDINAL) { XFree (data); return FALSE; } if (!find_best_size (data, nitems, ideal_size, &w, &h, &best)) { XFree (data); return FALSE; } if (!find_best_size (data, nitems, ideal_mini_size, &mini_w, &mini_h, &best_mini)) { XFree (data); return FALSE; } *width = w; *height = h; *mini_width = mini_w; *mini_height = mini_h; argbdata_to_pixdata (best, w * h, pixdata); argbdata_to_pixdata (best_mini, mini_w * mini_h, mini_pixdata); XFree (data); return TRUE; } static gboolean try_pixmap_and_mask (Screen *screen, Pixmap src_pixmap, Pixmap src_mask, GdkPixbuf **iconp, int ideal_size, GdkPixbuf **mini_iconp, int ideal_mini_size) { cairo_surface_t *surface, *mask_surface, *image; GdkDisplay *gdk_display; GdkPixbuf *unscaled; int width, height; cairo_t *cr; if (src_pixmap == None) return FALSE; surface = _wnck_cairo_surface_get_from_pixmap (screen, src_pixmap); if (surface && src_mask != None) mask_surface = _wnck_cairo_surface_get_from_pixmap (screen, src_mask); else mask_surface = NULL; if (surface == NULL) return FALSE; gdk_display = gdk_x11_lookup_xdisplay (XDisplayOfScreen (screen)); g_assert (gdk_display != NULL); gdk_x11_display_error_trap_push (gdk_display); width = cairo_xlib_surface_get_width (surface); height = cairo_xlib_surface_get_height (surface); image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (image); /* Need special code for alpha-only surfaces. We only get those * for bitmaps. And in that case, it's a differentiation between * foreground (white) and background (black). */ if (cairo_surface_get_content (surface) & CAIRO_CONTENT_ALPHA) { cairo_push_group (cr); /* black background */ cairo_set_source_rgb (cr, 0, 0, 0); cairo_paint (cr); /* mask with white foreground */ cairo_set_source_rgb (cr, 1, 1, 1); cairo_mask_surface (cr, surface, 0, 0); cairo_pop_group_to_source (cr); } else cairo_set_source_surface (cr, surface, 0, 0); if (mask_surface) { cairo_mask_surface (cr, mask_surface, 0, 0); cairo_surface_destroy (mask_surface); } else cairo_paint (cr); cairo_surface_destroy (surface); cairo_destroy (cr); if (gdk_x11_display_error_trap_pop (gdk_display) != Success) { cairo_surface_destroy (image); return FALSE; } unscaled = gdk_pixbuf_get_from_surface (image, 0, 0, width, height); cairo_surface_destroy (image); if (unscaled) { *iconp = gdk_pixbuf_scale_simple (unscaled, ideal_size, ideal_size, GDK_INTERP_BILINEAR); *mini_iconp = gdk_pixbuf_scale_simple (unscaled, ideal_mini_size, ideal_mini_size, GDK_INTERP_BILINEAR); g_object_unref (G_OBJECT (unscaled)); return TRUE; } else return FALSE; } static void get_kwm_win_icon (Screen *screen, Window xwindow, Pixmap *pixmap, Pixmap *mask) { Display *display; Atom type; int format; gulong nitems; gulong bytes_after; Pixmap *icons; int err, result; display = DisplayOfScreen (screen); *pixmap = None; *mask = None; _wnck_error_trap_push (display); icons = NULL; result = XGetWindowProperty (display, xwindow, _wnck_atom_get ("KWM_WIN_ICON"), 0, G_MAXLONG, False, _wnck_atom_get ("KWM_WIN_ICON"), &type, &format, &nitems, &bytes_after, (void*)&icons); err = _wnck_error_trap_pop (display); if (err != Success || result != Success) return; if (type != _wnck_atom_get ("KWM_WIN_ICON")) { XFree (icons); return; } *pixmap = icons[0]; *mask = icons[1]; XFree (icons); return; } static void clear_icon_cache (WnckIconCache *icon_cache, gboolean dirty_all) { if (icon_cache->icon) g_object_unref (G_OBJECT (icon_cache->icon)); icon_cache->icon = NULL; if (icon_cache->mini_icon) g_object_unref (G_OBJECT (icon_cache->mini_icon)); icon_cache->mini_icon = NULL; icon_cache->origin = USING_NO_ICON; if (dirty_all) { icon_cache->wm_hints_dirty = TRUE; icon_cache->kwm_win_icon_dirty = TRUE; icon_cache->net_wm_icon_dirty = TRUE; } } static void replace_cache (WnckIconCache *icon_cache, IconOrigin origin, GdkPixbuf *new_icon, GdkPixbuf *new_mini_icon) { clear_icon_cache (icon_cache, FALSE); icon_cache->origin = origin; if (new_icon) g_object_ref (G_OBJECT (new_icon)); icon_cache->icon = new_icon; if (new_mini_icon) g_object_ref (G_OBJECT (new_mini_icon)); icon_cache->mini_icon = new_mini_icon; } static void free_pixels (guchar *pixels, gpointer data) { g_free (pixels); } static GdkPixbuf* scaled_from_pixdata (guchar *pixdata, int w, int h, int new_w, int new_h) { GdkPixbuf *src; GdkPixbuf *dest; src = gdk_pixbuf_new_from_data (pixdata, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4, free_pixels, NULL); if (src == NULL) return NULL; if (w != h) { GdkPixbuf *tmp; int size; size = MAX (w, h); tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size); if (tmp != NULL) { gdk_pixbuf_fill (tmp, 0); gdk_pixbuf_copy_area (src, 0, 0, w, h, tmp, (size - w) / 2, (size - h) / 2); g_object_unref (src); src = tmp; } } if (w != new_w || h != new_h) { dest = gdk_pixbuf_scale_simple (src, new_w, new_h, GDK_INTERP_BILINEAR); g_object_unref (G_OBJECT (src)); } else { dest = src; } return dest; } WnckIconCache* _wnck_icon_cache_new (void) { WnckIconCache *icon_cache; icon_cache = g_slice_new0 (WnckIconCache); icon_cache->origin = USING_NO_ICON; icon_cache->prev_pixmap = None; icon_cache->icon = NULL; icon_cache->mini_icon = NULL; icon_cache->ideal_size = -1; /* won't be a legit size */ icon_cache->ideal_mini_size = -1; icon_cache->want_fallback = TRUE; icon_cache->wm_hints_dirty = TRUE; icon_cache->kwm_win_icon_dirty = TRUE; icon_cache->net_wm_icon_dirty = TRUE; return icon_cache; } void _wnck_icon_cache_free (WnckIconCache *icon_cache) { clear_icon_cache (icon_cache, FALSE); g_slice_free (WnckIconCache, icon_cache); } void _wnck_icon_cache_property_changed (WnckIconCache *icon_cache, Atom atom) { if (atom == _wnck_atom_get ("_NET_WM_ICON")) icon_cache->net_wm_icon_dirty = TRUE; else if (atom == _wnck_atom_get ("KWM_WIN_ICON")) icon_cache->kwm_win_icon_dirty = TRUE; else if (atom == _wnck_atom_get ("WM_HINTS")) icon_cache->wm_hints_dirty = TRUE; } gboolean _wnck_icon_cache_get_icon_invalidated (WnckIconCache *icon_cache) { if (icon_cache->origin <= USING_KWM_WIN_ICON && icon_cache->kwm_win_icon_dirty) return TRUE; else if (icon_cache->origin <= USING_WM_HINTS && icon_cache->wm_hints_dirty) return TRUE; else if (icon_cache->origin <= USING_NET_WM_ICON && icon_cache->net_wm_icon_dirty) return TRUE; else if (icon_cache->origin < USING_FALLBACK_ICON && icon_cache->want_fallback) return TRUE; else if (icon_cache->origin == USING_NO_ICON) return TRUE; else if (icon_cache->origin == USING_FALLBACK_ICON && !icon_cache->want_fallback) return TRUE; else return FALSE; } void _wnck_icon_cache_set_want_fallback (WnckIconCache *icon_cache, gboolean setting) { icon_cache->want_fallback = setting; } gboolean _wnck_icon_cache_get_is_fallback (WnckIconCache *icon_cache) { return icon_cache->origin == USING_FALLBACK_ICON; } gboolean _wnck_read_icons (WnckScreen *screen, Window xwindow, WnckIconCache *icon_cache, GdkPixbuf **iconp, int ideal_size, GdkPixbuf **mini_iconp, int ideal_mini_size) { Screen *xscreen; Display *display; guchar *pixdata; int w, h; guchar *mini_pixdata; int mini_w, mini_h; Pixmap pixmap; Pixmap mask; XWMHints *hints; /* Return value is whether the icon changed */ g_return_val_if_fail (icon_cache != NULL, FALSE); xscreen = _wnck_screen_get_xscreen (screen); display = DisplayOfScreen (xscreen); *iconp = NULL; *mini_iconp = NULL; if (ideal_size != icon_cache->ideal_size || ideal_mini_size != icon_cache->ideal_mini_size) clear_icon_cache (icon_cache, TRUE); icon_cache->ideal_size = ideal_size; icon_cache->ideal_mini_size = ideal_mini_size; if (!_wnck_icon_cache_get_icon_invalidated (icon_cache)) return FALSE; /* we have no new info to use */ pixdata = NULL; /* Our algorithm here assumes that we can't have for example origin * < USING_NET_WM_ICON and icon_cache->net_wm_icon_dirty == FALSE * unless we have tried to read NET_WM_ICON. * * Put another way, if an icon origin is not dirty, then we have * tried to read it at the current size. If it is dirty, then * we haven't done that since the last change. */ if (icon_cache->origin <= USING_NET_WM_ICON && icon_cache->net_wm_icon_dirty) { icon_cache->net_wm_icon_dirty = FALSE; if (read_rgb_icon (xscreen, xwindow, ideal_size, ideal_mini_size, &w, &h, &pixdata, &mini_w, &mini_h, &mini_pixdata)) { *iconp = scaled_from_pixdata (pixdata, w, h, ideal_size, ideal_size); *mini_iconp = scaled_from_pixdata (mini_pixdata, mini_w, mini_h, ideal_mini_size, ideal_mini_size); replace_cache (icon_cache, USING_NET_WM_ICON, *iconp, *mini_iconp); return TRUE; } } if (icon_cache->origin <= USING_WM_HINTS && icon_cache->wm_hints_dirty) { icon_cache->wm_hints_dirty = FALSE; _wnck_error_trap_push (display); hints = XGetWMHints (display, xwindow); _wnck_error_trap_pop (display); pixmap = None; mask = None; if (hints) { if (hints->flags & IconPixmapHint) pixmap = hints->icon_pixmap; if (hints->flags & IconMaskHint) mask = hints->icon_mask; XFree (hints); hints = NULL; } /* We won't update if pixmap is unchanged; * avoids a get_from_drawable() on every geometry * hints change */ if ((pixmap != icon_cache->prev_pixmap || mask != icon_cache->prev_mask) && pixmap != None) { if (try_pixmap_and_mask (xscreen, pixmap, mask, iconp, ideal_size, mini_iconp, ideal_mini_size)) { icon_cache->prev_pixmap = pixmap; icon_cache->prev_mask = mask; replace_cache (icon_cache, USING_WM_HINTS, *iconp, *mini_iconp); return TRUE; } } } if (icon_cache->origin <= USING_KWM_WIN_ICON && icon_cache->kwm_win_icon_dirty) { icon_cache->kwm_win_icon_dirty = FALSE; get_kwm_win_icon (xscreen, xwindow, &pixmap, &mask); if ((pixmap != icon_cache->prev_pixmap || mask != icon_cache->prev_mask) && pixmap != None) { if (try_pixmap_and_mask (xscreen, pixmap, mask, iconp, ideal_size, mini_iconp, ideal_mini_size)) { icon_cache->prev_pixmap = pixmap; icon_cache->prev_mask = mask; replace_cache (icon_cache, USING_KWM_WIN_ICON, *iconp, *mini_iconp); return TRUE; } } } if (icon_cache->want_fallback && icon_cache->origin < USING_FALLBACK_ICON) { _wnck_get_fallback_icons (iconp, ideal_size, mini_iconp, ideal_mini_size); replace_cache (icon_cache, USING_FALLBACK_ICON, *iconp, *mini_iconp); return TRUE; } if (!icon_cache->want_fallback && icon_cache->origin == USING_FALLBACK_ICON) { /* Get rid of current icon */ clear_icon_cache (icon_cache, FALSE); return TRUE; } /* found nothing new */ return FALSE; }