mutter/src/x11/iconcache.c
Jasper St. Pierre 9b903e93e3 iconcache: Fix the icon of Kerbel Space Program
Kerbel Space Program, and perhaps some other SDL-based programs, use
a really dumb way of specifying icons, which is totally
non-standards-compliant.

The ICCCM specifies that the icon_pixmap field of WM_HINTS should be a
1-bit-deep Pixmap, but we've seen applications set it to a pixmap of the
root depth as well, so we support that.

Kerbel Space Program seems to use it with a 32-bit depth Pixmap,
signifying ARGB32 (which it is), along with a 1-bit icon_mask, which
crashes us.

Keep in mind that Pixmaps, by definition, have no Visual attached, so
we simply have to make a guess at the correct visual based on the
depth. Do that by assuming that a depth-32 visual always means ARGB32,
which is a pretty safe bet.
2015-02-17 23:45:02 -08:00

591 lines
14 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Mutter 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "iconcache.h"
#include <meta/errors.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <cairo-xlib-xrender.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
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 cairo_surface_t *
argbdata_to_surface (gulong *argb_data, int w, int h)
{
cairo_surface_t *surface;
int y, x, stride;
uint32_t *data;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
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;
}
}
return surface;
}
static gboolean
read_rgb_icon (MetaDisplay *display,
Window xwindow,
int ideal_width,
int ideal_height,
int ideal_mini_width,
int ideal_mini_height,
cairo_surface_t **icon,
cairo_surface_t **mini_icon)
{
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);
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;
}
*icon = argbdata_to_surface (best, w, h);
*mini_icon = argbdata_to_surface (best_mini, mini_w, mini_h);
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 int
standard_pict_format_for_depth (int depth)
{
switch (depth)
{
case 1:
return PictStandardA1;
case 24:
return PictStandardRGB24;
case 32:
return PictStandardARGB32;
default:
g_assert_not_reached ();
}
}
static XRenderPictFormat *
pict_format_for_depth (Display *xdisplay, int depth)
{
return XRenderFindStandardFormat (xdisplay, standard_pict_format_for_depth (depth));
}
static cairo_surface_t *
surface_from_pixmap (Display *xdisplay, Pixmap xpixmap,
int width, int height)
{
Window root_return;
int x_ret, y_ret;
unsigned int w_ret, h_ret, bw_ret, depth_ret;
if (!XGetGeometry (xdisplay, xpixmap, &root_return,
&x_ret, &y_ret, &w_ret, &h_ret, &bw_ret, &depth_ret))
return NULL;
return cairo_xlib_surface_create_with_xrender_format (xdisplay, xpixmap, DefaultScreenOfDisplay (xdisplay),
pict_format_for_depth (xdisplay, depth_ret), w_ret, h_ret);
}
static gboolean
try_pixmap_and_mask (MetaDisplay *display,
Pixmap src_pixmap,
Pixmap src_mask,
cairo_surface_t **iconp)
{
Display *xdisplay = display->xdisplay;
cairo_surface_t *icon, *mask = NULL;
int w, h, d;
if (src_pixmap == None)
return FALSE;
meta_error_trap_push (display);
get_pixmap_geometry (display, src_pixmap, &w, &h, &d);
icon = surface_from_pixmap (xdisplay, src_pixmap, w, h);
if (icon && src_mask != None)
{
get_pixmap_geometry (display, src_mask, &w, &h, &d);
if (d == 1)
mask = surface_from_pixmap (xdisplay, src_mask, w, h);
}
meta_error_trap_pop (display);
if (icon && mask)
{
cairo_surface_t *masked;
cairo_t *cr;
masked = cairo_surface_create_similar_image (icon,
CAIRO_FORMAT_ARGB32,
cairo_xlib_surface_get_width (icon),
cairo_xlib_surface_get_height (icon));
cr = cairo_create (masked);
cairo_set_source_surface (cr, icon, 0, 0);
cairo_mask_surface (cr, mask, 0, 0);
cairo_destroy (cr);
cairo_surface_destroy (icon);
cairo_surface_destroy (mask);
icon = masked;
}
if (icon)
{
*iconp = icon;
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);
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;
icon_cache->wm_hints_dirty = TRUE;
icon_cache->kwm_win_icon_dirty = TRUE;
icon_cache->net_wm_icon_dirty = 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)
return TRUE;
else
return FALSE;
}
gboolean
meta_read_icons (MetaScreen *screen,
Window xwindow,
MetaIconCache *icon_cache,
Pixmap wm_hints_pixmap,
Pixmap wm_hints_mask,
cairo_surface_t **iconp,
int ideal_width,
int ideal_height,
cairo_surface_t **mini_iconp,
int ideal_mini_width,
int ideal_mini_height)
{
/* 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_width, ideal_height,
ideal_mini_width, ideal_mini_height,
iconp, mini_iconp))
{
icon_cache->origin = USING_NET_WM_ICON;
return TRUE;
}
}
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) &&
pixmap != None)
{
if (try_pixmap_and_mask (screen->display, pixmap, mask, iconp))
{
*mini_iconp = cairo_surface_reference (*iconp);
icon_cache->prev_pixmap = pixmap;
icon_cache->prev_mask = mask;
icon_cache->origin = USING_WM_HINTS;
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) &&
pixmap != None)
{
if (try_pixmap_and_mask (screen->display, pixmap, mask, iconp))
{
*mini_iconp = cairo_surface_reference (*iconp);
icon_cache->prev_pixmap = pixmap;
icon_cache->prev_mask = mask;
icon_cache->origin = USING_KWM_WIN_ICON;
return TRUE;
}
}
}
if (icon_cache->origin < USING_FALLBACK_ICON)
{
icon_cache->origin = USING_FALLBACK_ICON;
*iconp = NULL;
*mini_iconp = NULL;
return TRUE;
}
/* found nothing new */
return FALSE;
}