1 /* $Id$
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., Inc., 51 Franklin Street, Fifth Floor, Boston,
16 MA 02110-1301, USA.
17
18
19 Metacity - (c) 2001 Havoc Pennington
20 libwnck - (c) 2001 Havoc Pennington
21 xfwm4 - (c) 2002-2015 Olivier Fourdan
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <X11/Xlib.h>
29 #include <X11/Xutil.h>
30 #include <glib.h>
31 #include <gdk/gdk.h>
32 #include <gdk/gdkx.h>
33 #include <gdk-pixbuf/gdk-pixbuf.h>
34 #include <cairo/cairo-xlib.h>
35 #ifdef HAVE_COMPOSITOR
36 #include <cairo/cairo-xlib-xrender.h>
37 #endif
38 #include <libxfce4util/libxfce4util.h>
39
40 #include "icons.h"
41 #include "display.h"
42 #include "screen.h"
43 #include "client.h"
44 #include "compositor.h"
45 #include "hints.h"
46
47 static void
downsize_ratio(guint * width,guint * height,guint dest_w,guint dest_h)48 downsize_ratio (guint *width, guint *height, guint dest_w, guint dest_h)
49 {
50 gdouble ratio;
51 guint size;
52
53 g_return_if_fail (width != NULL);
54 g_return_if_fail (height != NULL);
55 g_return_if_fail (dest_w > 0 && dest_w > 0);
56
57 size = MIN (dest_w, dest_h);
58 if (*width > *height)
59 {
60 ratio = ((gdouble) *width) / size;
61 *width = (guint) size;
62 *height = (guint) (((gdouble) *height) / ratio);
63 }
64 else
65 {
66 ratio = ((gdouble) *height) / size;
67 *height = (guint) size;
68 *width = (guint) (((gdouble) *width) / ratio);
69 }
70 }
71
72 /*
73 * create a GdkPixbuf from inline data and scale it to a given size
74 */
75 static GdkPixbuf *
default_icon_at_size(GdkScreen * screen,guint width,guint height)76 default_icon_at_size (GdkScreen *screen, guint width, guint height)
77 {
78 GtkIconTheme *icon_theme;
79 GdkPixbuf *base;
80 GdkPixbuf *scaled;
81
82 icon_theme = gtk_icon_theme_get_for_screen (screen);
83
84 g_return_val_if_fail (icon_theme != NULL, NULL);
85
86 if (width <= 0 || height <= 0)
87 {
88 width = 160;
89 height = 160;
90 }
91
92 base = gtk_icon_theme_load_icon (icon_theme, "xfwm4-default",
93 MAX (width, height), 0, NULL);
94
95 if (base != NULL && width != height)
96 {
97 scaled = gdk_pixbuf_scale_simple (base, width, height, GDK_INTERP_BILINEAR);
98 g_object_unref (G_OBJECT (base));
99 return scaled;
100 }
101
102 return base;
103 }
104
105
106 static gboolean
find_largest_sizes(gulong * data,gulong nitems,guint * width,guint * height)107 find_largest_sizes (gulong * data, gulong nitems, guint *width, guint *height)
108 {
109 guint w, h;
110
111 *width = 0;
112 *height = 0;
113
114 while (nitems > 0)
115 {
116 if (nitems < 3)
117 {
118 return FALSE; /* no space for w, h */
119 }
120
121 w = data[0];
122 h = data[1];
123
124 if (nitems < (gulong) ((w * h) + 2))
125 {
126 return FALSE; /* not enough data */
127 }
128
129 *width = MAX (w, *width);
130 *height = MAX (h, *height);
131
132 data += (w * h) + 2;
133 nitems -= (w * h) + 2;
134 }
135
136 return TRUE;
137 }
138
139 static gboolean
find_best_size(gulong * data,gulong nitems,gint ideal_width,gint ideal_height,guint * width,guint * height,gulong ** start)140 find_best_size (gulong * data, gulong nitems, gint ideal_width, gint ideal_height,
141 guint *width, guint *height, gulong ** start)
142 {
143 gulong *best_start;
144 guint ideal_size;
145 guint w, h, best_size, this_size;
146 guint best_w, best_h, max_width, max_height;
147
148 *width = 0;
149 *height = 0;
150 *start = NULL;
151
152 if (!find_largest_sizes (data, nitems, &max_width, &max_height))
153 {
154 return FALSE;
155 }
156
157 if (ideal_width < 0)
158 {
159 ideal_width = max_width;
160 }
161 if (ideal_height < 0)
162 {
163 ideal_height = max_height;
164 }
165
166 best_w = 0;
167 best_h = 0;
168 best_start = NULL;
169
170 while (nitems > 0)
171 {
172 gboolean replace;
173
174 replace = FALSE;
175
176 if (nitems < 3)
177 {
178 return FALSE; /* no space for w, h */
179 }
180
181 w = data[0];
182 h = data[1];
183
184 if (nitems < (gulong) ((w * h) + 2))
185 {
186 break; /* not enough data */
187 }
188
189 if (best_start == NULL)
190 {
191 replace = TRUE;
192 }
193 else
194 {
195 /* work with averages */
196 ideal_size = (ideal_width + ideal_height) / 2;
197 best_size = (best_w + best_h) / 2;
198 this_size = (w + h) / 2;
199
200 if ((best_size < ideal_size) && (this_size >= ideal_size))
201 {
202 /* larger than desired is always better than smaller */
203 replace = TRUE;
204 }
205 else if ((best_size < ideal_size) && (this_size > best_size))
206 {
207 /* if we have too small, pick anything bigger */
208 replace = TRUE;
209 }
210 else if ((best_size > ideal_size) && (this_size >= ideal_size) && (this_size < best_size))
211 {
212 /* if we have too large, pick anything smaller but still >= the ideal */
213 replace = TRUE;
214 }
215 }
216
217 if (replace)
218 {
219 best_start = data + 2;
220 best_w = w;
221 best_h = h;
222 }
223
224 data += (w * h) + 2;
225 nitems -= (w * h) + 2;
226 }
227
228 if (best_start)
229 {
230 *start = best_start;
231 *width = best_w;
232 *height = best_h;
233 return TRUE;
234 }
235
236 return FALSE;
237 }
238
239 static void
argbdata_to_pixdata(gulong * argb_data,guint len,guchar ** pixdata)240 argbdata_to_pixdata (gulong * argb_data, guint len, guchar ** pixdata)
241 {
242 guchar *p;
243 guint argb;
244 guint rgba;
245 guint i;
246
247 *pixdata = g_new0 (guchar, len * 4);
248 p = *pixdata;
249
250 i = 0;
251 while (i < len)
252 {
253 argb = argb_data[i];
254 rgba = (argb << 8) | (argb >> 24);
255
256 *p = rgba >> 24; ++p;
257 *p = (rgba >> 16) & 0xff; ++p;
258 *p = (rgba >> 8) & 0xff; ++p;
259 *p = rgba & 0xff; ++p;
260
261 ++i;
262 }
263 }
264
265 static gboolean
read_rgb_icon(DisplayInfo * display_info,Window window,guint ideal_width,guint ideal_height,guint * width,guint * height,guchar ** pixdata)266 read_rgb_icon (DisplayInfo *display_info, Window window, guint ideal_width, guint ideal_height,
267 guint *width, guint *height, guchar ** pixdata)
268 {
269 gulong nitems;
270 gulong *data;
271 gulong *best;
272 guint w, h;
273
274 data = NULL;
275
276 if (!getRGBIconData (display_info, window, &data, &nitems))
277 {
278 return FALSE;
279 }
280
281 if (!find_best_size (data, nitems, ideal_width, ideal_height, &w, &h, &best))
282 {
283 XFree (data);
284 return FALSE;
285 }
286
287 *width = w;
288 *height = h;
289
290 argbdata_to_pixdata (best, w * h, pixdata);
291
292 XFree (data);
293
294 return TRUE;
295 }
296
297 static void
get_pixmap_geometry(ScreenInfo * screen_info,Pixmap pixmap,guint * out_width,guint * out_height,guint * out_depth)298 get_pixmap_geometry (ScreenInfo *screen_info, Pixmap pixmap, guint *out_width, guint *out_height, guint *out_depth)
299 {
300 Window root;
301 guint border_width;
302 gint x, y;
303 guint width, height;
304 guint depth;
305 Status rc;
306 int result;
307
308 myDisplayErrorTrapPush (screen_info->display_info);
309 rc = XGetGeometry (myScreenGetXDisplay(screen_info), pixmap, &root,
310 &x, &y, &width, &height, &border_width, &depth);
311 result = myDisplayErrorTrapPop (screen_info->display_info);
312
313 if ((rc != Success) || (result != Success))
314 {
315 return;
316 }
317
318 if (out_width != NULL)
319 {
320 *out_width = width;
321 }
322 if (out_height != NULL)
323 {
324 *out_height = height;
325 }
326 if (out_depth != NULL)
327 {
328 *out_depth = depth;
329 }
330 }
331
332 static cairo_surface_t *
get_surface_from_pixmap(ScreenInfo * screen_info,Pixmap xpixmap,guint width,guint height,guint depth)333 get_surface_from_pixmap (ScreenInfo *screen_info, Pixmap xpixmap, guint width, guint height, guint depth)
334 {
335 cairo_surface_t *surface;
336 #ifdef HAVE_COMPOSITOR
337 XRenderPictFormat *render_format;
338 #endif
339
340 if (depth == 1)
341 {
342 surface = cairo_xlib_surface_create_for_bitmap (screen_info->display_info->dpy,
343 xpixmap,
344 screen_info->xscreen,
345 width, height);
346 }
347 #ifdef HAVE_COMPOSITOR
348 else if (depth == 32)
349 {
350 render_format = XRenderFindStandardFormat (screen_info->display_info->dpy,
351 PictStandardARGB32);
352 surface = cairo_xlib_surface_create_with_xrender_format (screen_info->display_info->dpy,
353 xpixmap,
354 screen_info->xscreen,
355 render_format,
356 width, height);
357 }
358 #endif
359 else
360 {
361 surface = cairo_xlib_surface_create (screen_info->display_info->dpy,
362 xpixmap,
363 screen_info->visual,
364 width, height);
365 }
366
367 return surface;
368 }
369
370 static GdkPixbuf *
try_pixmap_and_mask(ScreenInfo * screen_info,Pixmap src_pixmap,Pixmap src_mask,guint width,guint height)371 try_pixmap_and_mask (ScreenInfo *screen_info, Pixmap src_pixmap, Pixmap src_mask, guint width, guint height)
372 {
373 GdkPixbuf *unscaled;
374 GdkPixbuf *icon;
375 guint w, h, depth;
376 cairo_surface_t *surface, *mask_surface, *image;
377 cairo_t *cr;
378
379 if (G_UNLIKELY (src_pixmap == None))
380 {
381 return NULL;
382 }
383
384 get_pixmap_geometry (screen_info, src_pixmap, &w, &h, &depth);
385 surface = get_surface_from_pixmap (screen_info, src_pixmap, w, h, depth);
386
387 if (surface && src_mask != None)
388 {
389 get_pixmap_geometry (screen_info, src_mask, &w, &h, &depth);
390 mask_surface = get_surface_from_pixmap (screen_info, src_mask, w, h, depth);
391 }
392 else
393 {
394 mask_surface = NULL;
395 }
396
397 if (G_UNLIKELY (surface == NULL))
398 {
399 return NULL;
400 }
401 myDisplayErrorTrapPush (screen_info->display_info);
402 image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
403 cr = cairo_create (image);
404
405 /* Need special code for alpha-only surfaces. We only get those
406 * for bitmaps. And in that case, it's a differentiation between
407 * foreground (white) and background (black).
408 */
409 if (mask_surface &&
410 (cairo_surface_get_content (surface) & CAIRO_CONTENT_ALPHA))
411 {
412 cairo_push_group (cr);
413
414 /* black background */
415 cairo_set_source_rgb (cr, 0, 0, 0);
416 cairo_paint (cr);
417 /* mask with white foreground */
418 cairo_set_source_rgb (cr, 1, 1, 1);
419 cairo_mask_surface (cr, surface, 0, 0);
420
421 cairo_pop_group_to_source (cr);
422 }
423 else
424 {
425 cairo_set_source_surface (cr, surface, 0, 0);
426 }
427
428 if (mask_surface)
429 {
430 cairo_mask_surface (cr, mask_surface, 0, 0);
431 cairo_surface_destroy (mask_surface);
432 }
433 else
434 {
435 cairo_paint (cr);
436 }
437
438 cairo_surface_destroy (surface);
439 cairo_destroy (cr);
440 if (myDisplayErrorTrapPop (screen_info->display_info) != Success)
441 {
442 cairo_surface_destroy (image);
443 return NULL;
444 }
445
446 unscaled = gdk_pixbuf_get_from_surface (image, 0, 0, w, h);
447 cairo_surface_destroy (image);
448
449 if (unscaled)
450 {
451 downsize_ratio (&w, &h, width, height);
452 icon = gdk_pixbuf_scale_simple (unscaled, w, h, GDK_INTERP_BILINEAR);
453 g_object_unref (G_OBJECT (unscaled));
454 return icon;
455 }
456
457 return NULL;
458 }
459
460 static void
free_pixels(guchar * pixels,gpointer data)461 free_pixels (guchar * pixels, gpointer data)
462 {
463 g_free (pixels);
464 }
465
466 static GdkPixbuf *
scaled_from_pixdata(guchar * pixdata,guint w,guint h,guint dest_w,guint dest_h)467 scaled_from_pixdata (guchar * pixdata, guint w, guint h, guint dest_w, guint dest_h)
468 {
469 GdkPixbuf *src;
470 GdkPixbuf *dest;
471
472 src = gdk_pixbuf_new_from_data (pixdata, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4, free_pixels, NULL);
473
474 if (G_UNLIKELY (src == NULL))
475 {
476 return NULL;
477 }
478
479 if (w != dest_w || h != dest_h)
480 {
481 downsize_ratio (&w, &h, dest_w, dest_h);
482 dest = gdk_pixbuf_scale_simple (src, w, h, GDK_INTERP_BILINEAR);
483 g_object_unref (G_OBJECT (src));
484 }
485 else
486 {
487 dest = src;
488 }
489
490 return dest;
491 }
492
493 GdkPixbuf *
getAppIcon(Client * c,guint width,guint height)494 getAppIcon (Client *c, guint width, guint height)
495 {
496 ScreenInfo *screen_info;
497 XWMHints *hints;
498 Pixmap pixmap;
499 Pixmap mask;
500 guchar *pixdata;
501 guint w, h;
502
503 g_return_val_if_fail (c != NULL, NULL);
504
505 pixdata = NULL;
506 pixmap = None;
507 mask = None;
508
509 screen_info = c->screen_info;
510 if (read_rgb_icon (screen_info->display_info, c->window, width, height, &w, &h, &pixdata))
511 {
512 return scaled_from_pixdata (pixdata, w, h, width, height);
513 }
514
515 myDisplayErrorTrapPush (screen_info->display_info);
516 hints = XGetWMHints (myScreenGetXDisplay(screen_info), c->window);
517 myDisplayErrorTrapPopIgnored (screen_info->display_info);
518
519 if (hints)
520 {
521 if (hints->flags & IconPixmapHint)
522 {
523 pixmap = hints->icon_pixmap;
524 }
525 if (hints->flags & IconMaskHint)
526 {
527 mask = hints->icon_mask;
528 }
529
530 XFree (hints);
531 hints = NULL;
532 }
533
534 if (pixmap != None)
535 {
536 GdkPixbuf *icon = try_pixmap_and_mask (screen_info, pixmap, mask, width, height);
537 if (icon)
538 {
539 return icon;
540 }
541 }
542
543 getKDEIcon (screen_info->display_info, c->window, &pixmap, &mask);
544 if (pixmap != None)
545 {
546 GdkPixbuf *icon = try_pixmap_and_mask (screen_info, pixmap, mask, width, height);
547 if (icon)
548 {
549 return icon;
550 }
551 }
552
553 if (c->class.res_name != NULL)
554 {
555 GdkPixbuf *icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
556 c->class.res_name,
557 MIN (width, height),
558 0, NULL);
559 if (icon)
560 {
561 return icon;
562 }
563 }
564
565 return default_icon_at_size (screen_info->gscr, width, height);
566 }
567
568 GdkPixbuf *
getClientIcon(Client * c,guint width,guint height)569 getClientIcon (Client *c, guint width, guint height)
570 {
571 ScreenInfo *screen_info;
572 GdkPixbuf *app_content;
573 GdkPixbuf *small_icon;
574 GdkPixbuf *icon_pixbuf;
575 GdkPixbuf *icon_pixbuf_stated;
576 guint small_icon_size;
577 guint app_icon_width, app_icon_height;
578 Pixmap pixmap;
579
580 g_return_val_if_fail (c != NULL, NULL);
581
582 screen_info = c->screen_info;
583 icon_pixbuf = NULL;
584 app_icon_width = width; /* Set to 0 to use gdk pixbuf scaling */
585 app_icon_height = height; /* Set to 0 to use gdk pixbuf scaling */
586
587 icon_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
588 gdk_pixbuf_fill (icon_pixbuf, 0x00);
589
590 pixmap = compositorGetWindowPixmapAtSize (screen_info, c->frame, &app_icon_width, &app_icon_height);
591 if (pixmap != None)
592 {
593 app_content = try_pixmap_and_mask (screen_info, pixmap, None, width, height);
594 XFreePixmap (myScreenGetXDisplay (screen_info), pixmap);
595 }
596 else
597 {
598 app_content = default_icon_at_size (screen_info->gscr, width, height);
599 }
600
601 app_icon_width = (guint) gdk_pixbuf_get_width (app_content);
602 app_icon_height = (guint) gdk_pixbuf_get_height (app_content);
603
604 gdk_pixbuf_copy_area (app_content, 0, 0, app_icon_width, app_icon_height, icon_pixbuf,
605 (width - app_icon_width) / 2, (height - app_icon_height) / 2);
606 g_object_unref (app_content);
607
608 small_icon_size = MIN (width / 4, height / 4);
609 small_icon_size = MIN (small_icon_size, 48);
610
611 small_icon = getAppIcon (c, small_icon_size, small_icon_size);
612
613 gdk_pixbuf_composite (small_icon, icon_pixbuf,
614 (width - small_icon_size) / 2, height - small_icon_size,
615 small_icon_size, small_icon_size,
616 (width - small_icon_size) / 2, height - small_icon_size,
617 1.0, 1.0,
618 GDK_INTERP_BILINEAR,
619 0xff);
620
621 g_object_unref (small_icon);
622
623 if (FLAG_TEST (c->flags, CLIENT_FLAG_ICONIFIED))
624 {
625 icon_pixbuf_stated = gdk_pixbuf_copy (icon_pixbuf);
626 gdk_pixbuf_saturate_and_pixelate (icon_pixbuf, icon_pixbuf_stated, 0.55, TRUE);
627 g_object_unref (icon_pixbuf);
628 icon_pixbuf = icon_pixbuf_stated;
629 }
630
631 return icon_pixbuf;
632 }
633