diff options
author | Havoc Pennington <hp@pobox.com> | 2002-02-09 23:03:52 +0000 |
---|---|---|
committer | Havoc Pennington <hp@src.gnome.org> | 2002-02-09 23:03:52 +0000 |
commit | 245c9c2d7e27445744eee764ecae6e3914fabd1e (patch) | |
tree | a20a282963f71f7d064de2276619afa138eeb6af /src/iconcache.c | |
parent | 29a86695d1eabfaab0675511dc2c3f80e548258d (diff) | |
download | metacity-245c9c2d7e27445744eee764ecae6e3914fabd1e.tar.gz |
port to icon cache
2002-02-09 Havoc Pennington <hp@pobox.com>
* src/window.c (update_icon): port to icon cache
* src/iconcache.c, src/iconcache.c: begin process of cleaning up
window.c by moving the icon-reading code in here, based on the
code in libwnck, which was in turn based on the earlier metacity
code
Diffstat (limited to 'src/iconcache.c')
-rw-r--r-- | src/iconcache.c | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/src/iconcache.c b/src/iconcache.c new file mode 100644 index 00000000..00c5ba02 --- /dev/null +++ b/src/iconcache.c @@ -0,0 +1,836 @@ +/* Metacity window icons */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "iconcache.h" +#include "ui.h" +#include "errors.h" + +#include <X11/Xatom.h> + +/* The icon-reading code is also in libwnck, please sync bugfixes */ + +static void +get_fallback_icons (MetaScreen *screen, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + /* we don't scale, should be fixed if we ever un-hardcode the icon + * size + */ + *iconp = meta_ui_get_default_window_icon (screen->ui); + *mini_iconp = meta_ui_get_default_mini_icon (screen->ui); +} + +static gboolean +find_largest_sizes (gulong *data, + int nitems, + int *width, + int *height) +{ + *width = 0; + *height = 0; + + 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 < ((w * h) + 2)) + return FALSE; /* not enough data */ + + *width = MAX (w, *width); + *height = MAX (h, *height); + + data += (w * h) + 2; + nitems -= (w * h) + 2; + } + + return TRUE; +} + +static gboolean +find_best_size (gulong *data, + int nitems, + int ideal_width, + int ideal_height, + int *width, + int *height, + gulong **start) +{ + int best_w; + int best_h; + gulong *best_start; + int max_width, max_height; + + *width = 0; + *height = 0; + *start = NULL; + + if (!find_largest_sizes (data, nitems, &max_width, &max_height)) + return FALSE; + + if (ideal_width < 0) + ideal_width = max_width; + if (ideal_height < 0) + ideal_height = max_height; + + 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 < ((w * h) + 2)) + break; /* not enough data */ + + if (best_start == NULL) + { + replace = TRUE; + } + else + { + /* work with averages */ + const int ideal_size = (ideal_width + ideal_height) / 2; + 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 (MetaDisplay *display, + Window xwindow, + int ideal_width, + int ideal_height, + int ideal_mini_width, + int ideal_mini_height, + int *width, + int *height, + guchar **pixdata, + int *mini_width, + int *mini_height, + guchar **mini_pixdata) +{ + 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; + + meta_error_trap_push (display); + type = None; + data = NULL; + result = XGetWindowProperty (display->xdisplay, + xwindow, + display->atom_net_wm_icon, + 0, G_MAXLONG, + False, XA_CARDINAL, &type, &format, &nitems, + &bytes_after, ((guchar **)&data)); + + err = meta_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_width, ideal_height, + &w, &h, &best)) + { + XFree (data); + return FALSE; + } + + if (!find_best_size (data, nitems, + ideal_mini_width, ideal_mini_height, + &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 void +free_pixels (guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +static void +get_pixmap_geometry (MetaDisplay *display, + Pixmap pixmap, + int *w, + int *h, + int *d) +{ + Window root_ignored; + int x_ignored, y_ignored; + guint width, height; + guint border_width_ignored; + guint depth; + + if (w) + *w = 1; + if (h) + *h = 1; + if (d) + *d = 1; + + XGetGeometry (display->xdisplay, + pixmap, &root_ignored, &x_ignored, &y_ignored, + &width, &height, &border_width_ignored, &depth); + + if (w) + *w = width; + if (h) + *h = height; + if (d) + *d = depth; +} + +static GdkPixbuf* +apply_mask (GdkPixbuf *pixbuf, + GdkPixbuf *mask) +{ + int w, h; + int i, j; + GdkPixbuf *with_alpha; + guchar *src; + guchar *dest; + int src_stride; + int dest_stride; + + w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf)); + h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf)); + + with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + + dest = gdk_pixbuf_get_pixels (with_alpha); + src = gdk_pixbuf_get_pixels (mask); + + dest_stride = gdk_pixbuf_get_rowstride (with_alpha); + src_stride = gdk_pixbuf_get_rowstride (mask); + + i = 0; + while (i < h) + { + j = 0; + while (j < w) + { + guchar *s = src + i * src_stride + j * 3; + guchar *d = dest + i * dest_stride + j * 4; + + /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 + * otherwise + */ + if (s[0] == 0) + d[3] = 0; /* transparent */ + else + d[3] = 255; /* opaque */ + + ++j; + } + + ++i; + } + + return with_alpha; +} + +static gboolean +try_pixmap_and_mask (MetaDisplay *display, + Pixmap src_pixmap, + Pixmap src_mask, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + GdkPixbuf *unscaled = NULL; + GdkPixbuf *mask = NULL; + int w, h; + + if (src_pixmap == None) + return FALSE; + + meta_error_trap_push (display); + + get_pixmap_geometry (display, src_pixmap, &w, &h, NULL); + + unscaled = meta_gdk_pixbuf_get_from_pixmap (NULL, + src_pixmap, + 0, 0, 0, 0, + w, h); + + if (unscaled && src_mask != None) + { + get_pixmap_geometry (display, src_mask, &w, &h, NULL); + mask = meta_gdk_pixbuf_get_from_pixmap (NULL, + src_mask, + 0, 0, 0, 0, + w, h); + } + + meta_error_trap_pop (display); + + if (mask) + { + GdkPixbuf *masked; + + masked = apply_mask (unscaled, mask); + g_object_unref (G_OBJECT (unscaled)); + unscaled = masked; + + g_object_unref (G_OBJECT (mask)); + mask = NULL; + } + + if (unscaled) + { + *iconp = + gdk_pixbuf_scale_simple (unscaled, + ideal_width > 0 ? ideal_width : + gdk_pixbuf_get_width (unscaled), + ideal_height > 0 ? ideal_height : + gdk_pixbuf_get_height (unscaled), + GDK_INTERP_BILINEAR); + *mini_iconp = + gdk_pixbuf_scale_simple (unscaled, + ideal_mini_width > 0 ? ideal_mini_width : + gdk_pixbuf_get_width (unscaled), + ideal_mini_height > 0 ? ideal_mini_height : + gdk_pixbuf_get_height (unscaled), + GDK_INTERP_BILINEAR); + + g_object_unref (G_OBJECT (unscaled)); + + if (*iconp && *mini_iconp) + return TRUE; + else + { + if (*iconp) + g_object_unref (G_OBJECT (*iconp)); + if (*mini_iconp) + g_object_unref (G_OBJECT (*mini_iconp)); + return FALSE; + } + } + else + return FALSE; +} + +static void +get_kwm_win_icon (MetaDisplay *display, + Window xwindow, + Pixmap *pixmap, + Pixmap *mask) +{ + Atom type; + int format; + gulong nitems; + gulong bytes_after; + Pixmap *icons; + int err, result; + + *pixmap = None; + *mask = None; + + meta_error_trap_push (display); + icons = NULL; + result = XGetWindowProperty (display->xdisplay, xwindow, + display->atom_kwm_win_icon, + 0, G_MAXLONG, + False, + display->atom_kwm_win_icon, + &type, &format, &nitems, + &bytes_after, (guchar **)&icons); + + err = meta_error_trap_pop (display); + if (err != Success || + result != Success) + return; + + if (type != display->atom_kwm_win_icon) + { + XFree (icons); + return; + } + + *pixmap = icons[0]; + *mask = icons[1]; + + XFree (icons); + + return; +} + +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; + +void +meta_icon_cache_init (MetaIconCache *icon_cache) +{ + g_return_if_fail (icon_cache != NULL); + + icon_cache->origin = USING_NO_ICON; + icon_cache->prev_pixmap = None; +#if 0 + icon_cache->icon = NULL; + icon_cache->mini_icon = NULL; + icon_cache->ideal_width = -1; /* won't be a legit width */ + icon_cache->ideal_height = -1; + icon_cache->ideal_mini_width = -1; + icon_cache->ideal_mini_height = -1; +#endif + 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; +} + +static void +clear_icon_cache (MetaIconCache *icon_cache, + gboolean dirty_all) +{ +#if 0 + 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; +#endif + + 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; + } +} + +void +meta_icon_cache_free (MetaIconCache *icon_cache) +{ + clear_icon_cache (icon_cache, FALSE); +} + +void +meta_icon_cache_property_changed (MetaIconCache *icon_cache, + MetaDisplay *display, + Atom atom) +{ + if (atom == display->atom_net_wm_icon) + icon_cache->net_wm_icon_dirty = TRUE; + else if (atom == display->atom_kwm_win_icon) + icon_cache->kwm_win_icon_dirty = TRUE; + else if (atom == XA_WM_HINTS) + icon_cache->wm_hints_dirty = TRUE; +} + +gboolean +meta_icon_cache_get_icon_invalidated (MetaIconCache *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; +} + +static void +replace_cache (MetaIconCache *icon_cache, + IconOrigin origin, + GdkPixbuf *new_icon, + GdkPixbuf *new_mini_icon) +{ + clear_icon_cache (icon_cache, FALSE); + + icon_cache->origin = origin; + +#if 0 + 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; +#endif +} + +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 != 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; +} + +gboolean +meta_read_icons (MetaScreen *screen, + Window xwindow, + MetaIconCache *icon_cache, + Pixmap wm_hints_pixmap, + Pixmap wm_hints_mask, + GdkPixbuf **iconp, + int ideal_width, + int ideal_height, + GdkPixbuf **mini_iconp, + int ideal_mini_width, + int ideal_mini_height) +{ + guchar *pixdata; + int w, h; + guchar *mini_pixdata; + int mini_w, mini_h; + Pixmap pixmap; + Pixmap mask; + + /* Return value is whether the icon changed */ + + g_return_val_if_fail (icon_cache != NULL, FALSE); + + *iconp = NULL; + *mini_iconp = NULL; + +#if 0 + if (ideal_width != icon_cache->ideal_width || + ideal_height != icon_cache->ideal_height || + ideal_mini_width != icon_cache->ideal_mini_width || + ideal_mini_height != icon_cache->ideal_mini_height) + clear_icon_cache (icon_cache, TRUE); + + icon_cache->ideal_width = ideal_width; + icon_cache->ideal_height = ideal_height; + icon_cache->ideal_mini_width = ideal_mini_width; + icon_cache->ideal_mini_height = ideal_mini_height; +#endif + + if (!meta_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 (screen->display, xwindow, + ideal_width, ideal_height, + ideal_mini_width, ideal_mini_height, + &w, &h, &pixdata, + &mini_w, &mini_h, &mini_pixdata)) + { + *iconp = scaled_from_pixdata (pixdata, w, h, + ideal_width, ideal_height); + + *mini_iconp = scaled_from_pixdata (mini_pixdata, mini_w, mini_h, + ideal_mini_width, ideal_mini_height); + + if (*iconp && *mini_iconp) + { + replace_cache (icon_cache, USING_NET_WM_ICON, + *iconp, *mini_iconp); + + return TRUE; + } + else + { + if (*iconp) + g_object_unref (G_OBJECT (*iconp)); + if (*mini_iconp) + g_object_unref (G_OBJECT (*mini_iconp)); + } + } + } + + if (icon_cache->origin <= USING_WM_HINTS && + icon_cache->wm_hints_dirty) + { + icon_cache->wm_hints_dirty = FALSE; + + pixmap = wm_hints_pixmap; + mask = wm_hints_mask; + + /* 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 (screen->display, + pixmap, mask, + iconp, ideal_width, ideal_height, + mini_iconp, ideal_mini_width, ideal_mini_height)) + { + 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 (screen->display, xwindow, &pixmap, &mask); + + if ((pixmap != icon_cache->prev_pixmap || + mask != icon_cache->prev_mask) && + pixmap != None) + { + if (try_pixmap_and_mask (screen->display, pixmap, mask, + iconp, ideal_width, ideal_height, + mini_iconp, ideal_mini_width, ideal_mini_height)) + { + 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) + { + get_fallback_icons (screen, + iconp, + ideal_width, + ideal_height, + mini_iconp, + ideal_mini_width, + ideal_mini_height); + + 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; +} |