summaryrefslogtreecommitdiff
path: root/src/core/iconcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/iconcache.c')
-rw-r--r--src/core/iconcache.c849
1 files changed, 849 insertions, 0 deletions
diff --git a/src/core/iconcache.c b/src/core/iconcache.c
new file mode 100644
index 00000000..af9411ba
--- /dev/null
+++ b/src/core/iconcache.c
@@ -0,0 +1,849 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco 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 <config.h>
+#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,
+ gulong nitems,
+ int *width,
+ int *height)
+{
+ *width = 0;
+ *height = 0;
+
+ while (nitems > 0)
+ {
+ int w, h;
+
+ if (nitems < 3)
+ return FALSE; /* no space for w, h */
+
+ w = data[0];
+ h = data[1];
+
+ if (nitems < ((gulong)(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,
+ gulong 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 < ((gulong)(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;
+ guchar *data;
+ gulong *best;
+ int w, h;
+ gulong *best_mini;
+ int mini_w, mini_h;
+ gulong *data_as_long;
+
+ meta_error_trap_push_with_return (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, &data);
+ err = meta_error_trap_pop_with_return (display, TRUE);
+
+ if (err != Success ||
+ result != Success)
+ return FALSE;
+
+ if (type != XA_CARDINAL)
+ {
+ XFree (data);
+ return FALSE;
+ }
+
+ data_as_long = (gulong *)data;
+
+ if (!find_best_size (data_as_long, nitems,
+ ideal_width, ideal_height,
+ &w, &h, &best))
+ {
+ XFree (data);
+ return FALSE;
+ }
+
+ if (!find_best_size (data_as_long, 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, FALSE);
+
+ 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;
+ guchar *data;
+ Pixmap *icons;
+ int err, result;
+
+ *pixmap = None;
+ *mask = None;
+
+ meta_error_trap_push_with_return (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, &data);
+ icons = (Pixmap *)data;
+
+ err = meta_error_trap_pop_with_return (display, TRUE);
+ 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;
+}
+
+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;
+ icon_cache->prev_mask = 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 != h)
+ {
+ GdkPixbuf *tmp;
+ int size;
+
+ size = MAX (w, h);
+
+ tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size);
+
+ if (tmp)
+ {
+ 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;
+}
+
+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;
+}