summaryrefslogtreecommitdiff
path: root/libwnck/wnck-icon-cache.c
diff options
context:
space:
mode:
authorAlberts Muktupāvels <alberts.muktupavels@gmail.com>2021-05-11 21:00:01 +0300
committerAlberts Muktupāvels <alberts.muktupavels@gmail.com>2021-05-11 21:00:01 +0300
commitbd8ab37c271c065c0bcd09362222ca37f05aedf7 (patch)
treeb1a36e11431b20df8b01b78279cd43b8f87bf46a /libwnck/wnck-icon-cache.c
parentbcc661b3a98976895553f571b59a91a406fa89cf (diff)
downloadlibwnck-bd8ab37c271c065c0bcd09362222ca37f05aedf7.tar.gz
xutils: move WnckIconCache to its own file
Diffstat (limited to 'libwnck/wnck-icon-cache.c')
-rw-r--r--libwnck/wnck-icon-cache.c756
1 files changed, 756 insertions, 0 deletions
diff --git a/libwnck/wnck-icon-cache.c b/libwnck/wnck-icon-cache.c
new file mode 100644
index 0000000..1749585
--- /dev/null
+++ b/libwnck/wnck-icon-cache.c
@@ -0,0 +1,756 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "wnck-icon-cache-private.h"
+
+#include <cairo-xlib.h>
+#if HAVE_CAIRO_XLIB_XRENDER
+#include <cairo-xlib-xrender.h>
+#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;
+}