
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 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 gboolean read_rgb_icon( 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;

	type = None;
	data = NULL;
	result = XGetWindowProperty(dpy, xwindow, intern_atom[NET_WM_ICON], 0, G_MAXLONG,
								False, XA_CARDINAL, &type, &format, &nitems,
								&bytes_after, ((guchar **) & data));

	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 get_kwm_win_icon(Window xwindow, Pixmap * pixmap, Pixmap * mask)
{
	Atom type;
	int format;
	gulong nitems;
	gulong bytes_after;
	Pixmap *icons;
	int err, result;

	*pixmap = None;
	*mask = None;

	misc_trap_xerrors();
	
	icons = NULL;
	result = XGetWindowProperty(dpy, xwindow, intern_atom[KWM_WIN_ICON],
								0, G_MAXLONG, False, intern_atom[KWM_WIN_ICON],
								&type, &format, &nitems, &bytes_after, (guchar **) & icons);

	err = misc_untrap_xerrors();
	if (err != Success || result != Success)
		return;

	if (type != intern_atom[KWM_WIN_ICON])
	{
		XFree(icons);
		return;
	}

	*pixmap = icons[0];
	*mask = icons[1];

	XFree(icons);
}


gboolean read_icons( 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;
}
