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