/* -*- 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., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include #include "iconcache.h" #include "ui.h" #include "errors.h" #include "window-private.h" #include #include /* The icon-reading code is also in libwnck, please sync bugfixes */ static void get_fallback_icons (MetaScreen *screen, cairo_surface_t **iconp, cairo_surface_t **mini_iconp) { /* 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_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 cairo_surface_t * argbdata_to_surface (gulong *argb_data, int w, int h, int ideal_w, int ideal_h, int scaling_factor) { cairo_surface_t *surface, *icon; cairo_t *cr; int x, y, stride; uint32_t *data; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); cairo_surface_set_device_scale (surface, (double)scaling_factor, (double)scaling_factor); stride = cairo_image_surface_get_stride (surface) / sizeof (uint32_t); data = (uint32_t *) cairo_image_surface_get_data (surface); /* One could speed this up a lot. */ for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { uint32_t *p = &data[y * stride + x]; gulong *d = &argb_data[y * w + x]; *p = *d; } } cairo_surface_mark_dirty (surface); icon = cairo_surface_create_similar_image (surface, cairo_image_surface_get_format (surface), ideal_w, ideal_h); cairo_surface_set_device_scale (icon, (double)scaling_factor, (double)scaling_factor); cr = cairo_create (icon); cairo_scale (cr, ideal_w / (double)w, ideal_h / (double)h); cairo_set_source_surface (cr, surface, 0, 0); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_IN); cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (surface); return icon; } static gboolean read_rgb_icon (MetaDisplay *display, Window xwindow, int ideal_size, int ideal_mini_size, cairo_surface_t **iconp, cairo_surface_t **mini_iconp, int scaling_factor) { 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 (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_size, &w, &h, &best)) { XFree (data); return FALSE; } if (!find_best_size (data_as_long, nitems, ideal_mini_size, &mini_w, &mini_h, &best_mini)) { XFree (data); return FALSE; } *iconp = argbdata_to_surface (best, w, h, ideal_size, ideal_size, scaling_factor); *mini_iconp = argbdata_to_surface (best_mini, mini_w, mini_h, ideal_mini_size, ideal_mini_size, scaling_factor); XFree (data); return TRUE; } 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 gboolean try_pixmap_and_mask (MetaDisplay *display, Pixmap src_pixmap, Pixmap src_mask, cairo_surface_t **iconp, int ideal_size, cairo_surface_t **mini_iconp, int ideal_mini_size, int scaling_factor) { cairo_surface_t *surface = NULL; cairo_surface_t *mask_surface = NULL; cairo_surface_t *image = NULL; int width, height; cairo_t *cr; if (src_pixmap == None) return FALSE; meta_error_trap_push (display); get_pixmap_geometry (display, src_pixmap, &width, &height, NULL); surface = meta_cairo_surface_get_from_pixmap (display->xdisplay, src_pixmap, scaling_factor); if (surface && src_mask != None) { get_pixmap_geometry (display, src_mask, &width, &height, NULL); mask_surface = meta_cairo_surface_get_from_pixmap (display->xdisplay, src_mask, scaling_factor); } 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 (meta_error_trap_pop_with_return (display, FALSE) != Success) { cairo_surface_destroy (image); return FALSE; } if (image) { int image_w, image_h; image_w = cairo_image_surface_get_width (image); image_h = cairo_image_surface_get_height (image); *iconp = cairo_surface_create_similar (image, cairo_surface_get_content (image), ideal_size, ideal_size); cairo_surface_set_device_scale (*iconp, (double)scaling_factor, (double)scaling_factor); cr = cairo_create (*iconp); cairo_scale (cr, ideal_size / (double)image_w, ideal_size / (double)image_h); cairo_set_source_surface (cr, image, 0, 0); cairo_paint (cr); cairo_destroy (cr); *mini_iconp = cairo_surface_create_similar (image, cairo_surface_get_content (image), ideal_mini_size, ideal_mini_size); cairo_surface_set_device_scale (*mini_iconp, (double)scaling_factor, (double)scaling_factor); cr = cairo_create (*mini_iconp); cairo_scale (cr, ideal_mini_size / (double)image_w, ideal_mini_size / (double)image_h); cairo_set_source_surface (cr, image, 0, 0); cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (image); return TRUE; } 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 (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_size = -1; /* won't be a legit size */ icon_cache->ideal_mini_size = -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; icon_cache->wm_hints_dirty_forced = FALSE; icon_cache->kwm_win_icon_dirty_forced = FALSE; icon_cache->fallback_icon_dirty_forced = FALSE; } 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_invalidate (MetaIconCache *icon_cache) { icon_cache->wm_hints_dirty = TRUE; icon_cache->kwm_win_icon_dirty = TRUE; icon_cache->net_wm_icon_dirty = TRUE; icon_cache->wm_hints_dirty_forced = TRUE; icon_cache->kwm_win_icon_dirty_forced = TRUE; icon_cache->fallback_icon_dirty_forced = TRUE; } 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, cairo_surface_t *new_icon, cairo_surface_t *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 } gboolean meta_read_icons (MetaScreen *screen, Window xwindow, char *res_name, MetaIconCache *icon_cache, Pixmap wm_hints_pixmap, Pixmap wm_hints_mask, cairo_surface_t **iconp, int ideal_size, cairo_surface_t **mini_iconp, int ideal_mini_size, int scaling_factor) { /* Return value is whether the icon changed */ g_return_val_if_fail (icon_cache != NULL, FALSE); *iconp = NULL; *mini_iconp = NULL; if (!meta_icon_cache_get_icon_invalidated (icon_cache)) return FALSE; /* we have no new info to use */ /* 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_size, ideal_mini_size, iconp, mini_iconp, scaling_factor)) { if (*iconp && *mini_iconp) { replace_cache (icon_cache, USING_NET_WM_ICON, *iconp, *mini_iconp); return TRUE; } else { if (*iconp) cairo_surface_destroy (*iconp); if (*mini_iconp) cairo_surface_destroy (*mini_iconp); } } } if (icon_cache->origin <= USING_WM_HINTS && icon_cache->wm_hints_dirty) { Pixmap pixmap; Pixmap mask; 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 || icon_cache->wm_hints_dirty_forced) && pixmap != None) { icon_cache->wm_hints_dirty_forced = FALSE; if (try_pixmap_and_mask (screen->display, pixmap, mask, iconp, ideal_size, mini_iconp, ideal_mini_size, scaling_factor)) { 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) { Pixmap pixmap; Pixmap mask; 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 || icon_cache->kwm_win_icon_dirty_forced) && pixmap != None) { icon_cache->kwm_win_icon_dirty_forced = FALSE; if (try_pixmap_and_mask (screen->display, pixmap, mask, iconp, ideal_size, mini_iconp, ideal_mini_size, scaling_factor)) { 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) || (icon_cache->fallback_icon_dirty_forced && icon_cache->origin == USING_FALLBACK_ICON)) { icon_cache->fallback_icon_dirty_forced = FALSE; if (res_name != NULL) { *iconp = meta_ui_get_window_icon_from_name (screen->ui, res_name); *mini_iconp = meta_ui_get_mini_icon_from_name (screen->ui, res_name); } if (*iconp == NULL || *mini_iconp == NULL) get_fallback_icons (screen, iconp, mini_iconp); 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; }