1
/*********************************************************
2
* Copyright (C) 2008 VMware, Inc. All rights reserved.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License as published
6
* by the Free Software Foundation version 2.1 and no later version.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10
* or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
11
* License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program; if not, write to the Free Software Foundation, Inc.,
15
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17
*********************************************************/
22
* Utility functions to retrieve application icons.
36
#error "Gtk 2.0 is required"
43
#include <X11/Xatom.h>
44
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
47
*-----------------------------------------------------------------------------
49
* AppUtilIconThemeReallyHasIcon --
51
* Utility function to detect whether an icon is really available. This is necessary
52
* because sometimes gtk_icon_theme_has_icon() lies to us...
55
* TRUE if the icon theme really has a usable icon, FALSE otherwise.
60
*-----------------------------------------------------------------------------
64
AppUtilIconThemeReallyHasIcon(GtkIconTheme *iconTheme, // IN
65
const char *iconName) // IN
70
if (!gtk_icon_theme_has_icon(iconTheme, iconName)) {
74
iconSizes = gtk_icon_theme_get_icon_sizes(iconTheme, iconName);
75
retval = iconSizes && iconSizes[0];
83
*-----------------------------------------------------------------------------
85
* AppUtilCollectNamedIcons --
87
* Tries to find icons with a particular name (which may be a full filesystem path,
88
* a filename with extension, or just an abstract app name).
94
* May add icons into 'pixbufs'
96
*-----------------------------------------------------------------------------
100
AppUtilCollectNamedIcons(GPtrArray *pixbufs, // IN/OUT
101
const char *iconName) // IN
105
* Use the GtkIconTheme to track down any available icons for this app.
107
GtkIconTheme *iconTheme = NULL;
109
Bool foundIt; // Did we find this icon in the GtkIconTheme?
110
static const char *extraIconPaths[] = {
111
"/usr/share/pixmaps",
112
"/usr/local/share/pixmaps",
113
"/usr/local/share/icons",
114
"/opt/kde3/share/icons",
115
"/opt/kde3/share/pixmaps",
116
"/opt/kde4/share/icons",
117
"/opt/kde4/share/pixmaps",
118
"/opt/gnome/share/icons",
119
"/opt/gnome/share/pixmaps"
125
iconNameLen = strlen(iconName) + 1;
126
myIconName = g_alloca(iconNameLen); // We need to modify the name sometimes
127
Str_Strcpy(myIconName, iconName, iconNameLen);
129
ctmp2 = strrchr(myIconName, '.');
130
if (*myIconName != '/' && ctmp2 && strlen(ctmp2) <= 5) {
132
* If it's a plain filename that we could possibly feed into GtkIconTheme as an
133
* icon ID, trim the file extension to turn it into an icon ID string and make
134
* GtkIconTheme happy.
139
iconTheme = gtk_icon_theme_get_default();
140
g_object_ref(G_OBJECT(iconTheme));
141
foundIt = AppUtilIconThemeReallyHasIcon(iconTheme, myIconName);
144
* Try looking through some auxiliary icon themes.
147
static const char *extraThemes[] = {
149
* Some other icon themes to try.
154
"HighContrastLargePrint",
157
g_object_unref(G_OBJECT(iconTheme));
158
iconTheme = gtk_icon_theme_new();
159
for (i = 0; i < ARRAYSIZE(extraIconPaths); i++) {
160
if (extraIconPaths[i]) {
161
if (g_file_test(extraIconPaths[i], G_FILE_TEST_EXISTS)) {
162
gtk_icon_theme_append_search_path(iconTheme, extraIconPaths[i]);
164
extraIconPaths[i] = NULL;
169
for (i = 0; extraThemes[i]; i++) {
170
gtk_icon_theme_set_custom_theme(iconTheme, extraThemes[i]);
171
foundIt = AppUtilIconThemeReallyHasIcon(iconTheme, myIconName);
180
* Try looking for it as a non-GtkIconTheme managed file, to deal with older
183
if (iconName[0] != '/') {
189
ctmpext = strrchr(iconName, '.');
190
ctmp2 = g_alloca(PATH_MAX);
191
for (i = 0; !myIconName && i < ARRAYSIZE(extraIconPaths); i++) {
192
if (!extraIconPaths[i]) {
197
g_snprintf(ctmp2, PATH_MAX, "%s/%s", extraIconPaths[i], iconName);
198
if (g_file_test(ctmp2, G_FILE_TEST_EXISTS)) {
202
static const char *iconExtensions[] = {
210
for (j = 0; j < ARRAYSIZE(iconExtensions); j++) {
211
g_snprintf(ctmp2, PATH_MAX, "%s/%s%s",
213
iconName, iconExtensions[j]);
214
if (g_file_test(ctmp2, G_FILE_TEST_EXISTS)) {
222
Str_Strcpy(myIconName, iconName, iconNameLen);
228
* If we know this icon can be loaded via GtkIconTheme, do so.
232
Bool needToUseScalable;
234
iconSizes = gtk_icon_theme_get_icon_sizes(iconTheme, myIconName);
237
needToUseScalable = (iconSizes[0] == -1 && iconSizes[1] == 0);
240
* Before we try to actually dump the icons out to the host, count how many we
243
for (i = 0; iconSizes[i]; i++) {
245
GtkIconInfo *iconInfo;
248
thisSize = iconSizes[i];
249
if (thisSize == -1 && !needToUseScalable) {
250
continue; // Skip scalable icons if we have prerendered versions
253
if (thisSize == -1) {
254
thisSize = 64; // Render SVG icons to 64x64
257
iconInfo = gtk_icon_theme_lookup_icon(iconTheme, myIconName, thisSize, 0);
260
Debug("Couldn't find %s icon for size %d\n", myIconName, thisSize);
264
pixbuf = gtk_icon_info_load_icon(iconInfo, NULL);
267
pixbuf = gtk_icon_info_get_builtin_pixbuf(iconInfo);
271
g_ptr_array_add(pixbufs, pixbuf);
273
Debug("WARNING: Not even a built-in pixbuf for icon %s\n", myIconName);
276
gtk_icon_info_free(iconInfo);
281
} else if (myIconName && myIconName[0] == '/') {
283
pixbuf = gdk_pixbuf_new_from_file(myIconName, NULL);
285
g_ptr_array_add(pixbufs, pixbuf);
290
g_object_unref(G_OBJECT(iconTheme));
296
*-----------------------------------------------------------------------------
298
* AppUtilComparePixbufSizes --
300
* Compares two GdkPixbufs to sort them by image dimensions
303
* -1 if A is larger than B, 0 if equal, 1 if A is smaller than B
308
*-----------------------------------------------------------------------------
312
AppUtilComparePixbufSizes(gconstpointer a, // IN
313
gconstpointer b) // IN
322
} else if (!a && b) {
324
} else if (!a && !b) {
328
pba = GDK_PIXBUF(*(gconstpointer *)a);
329
asize = gdk_pixbuf_get_width(pba) * gdk_pixbuf_get_height(pba);
331
pbb = GDK_PIXBUF(*(gconstpointer *)b);
332
bsize = gdk_pixbuf_get_width(pbb) * gdk_pixbuf_get_height(pbb);
336
} else if (asize < bsize) {
345
*-----------------------------------------------------------------------------
347
* AppUtil_CollectIconArray --
349
* Given a variety of information about an application (its icon name, X window ID,
350
* etc.), return an array of GdkPixbufs that represent the icons for that
354
* GPtrArray of GdkPixbufs, or NULL on failure. The returned array may have zero
355
* elements. The array will be sorted by icon size, largest to smallest.
358
* Caller becomes owner of the array and pixbufs that are allocated during this
361
*-----------------------------------------------------------------------------
365
AppUtil_CollectIconArray(const char *iconName, // IN
366
unsigned long windowID) // IN
370
ASSERT(iconName != NULL || windowID != None);
372
pixbufs = g_ptr_array_new();
375
AppUtilCollectNamedIcons(pixbufs, iconName);
378
if (!pixbufs->len && windowID != None) {
380
* Try loading the icon from the X Window's _NET_WM_ICON/WM_HINTS property.
384
Atom actualType = None;
386
unsigned long nitems = 0;
387
unsigned long bytesLeft;
389
XTextProperty wmIconName;
391
dpy = gdk_x11_get_default_xdisplay();
392
XGetWindowProperty(dpy, windowID, XInternAtom(dpy, "_NET_WM_ICON", FALSE),
393
0, LONG_MAX, False, XA_CARDINAL,
394
&actualType, &actualFormat, &nitems,
395
&bytesLeft, (unsigned char **)&value);
399
* _NET_WM_ICON: Transform ARGB data into pixbufs...
403
for (i = 0; i < nitems; ) {
412
ASSERT((nitems - i) >= 2);
414
height = value[i + 1];
416
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
418
pixels = gdk_pixbuf_get_pixels(pixbuf);
419
rowstride = gdk_pixbuf_get_rowstride(pixbuf);
421
for (y = 0; y < height; y++) {
422
for (x = 0; x < width && i < nitems; x++, i++) {
423
guchar *pixel = &pixels[y * rowstride + x * 4];
424
XID currentValue = value[i];
427
* Input data: BGRA data (high byte is A, low byte is B -
428
* freedesktop calls this ARGB, but that's not correct).
430
* Output data: RGBA data.
432
*pixel = (currentValue >> 16) & 0xFF;
433
*(pixel + 1) = (currentValue >> 8) & 0xFF;
434
*(pixel + 2) = currentValue & 0xFF;
435
*(pixel + 3) = (currentValue >> 24) & 0xFF;
439
g_ptr_array_add(pixbufs, pixbuf);
441
Debug("gdk_pixbuf_new failed when decoding _NET_WM_ICON\n");
449
XGetWindowProperty(dpy, windowID, XInternAtom(dpy, "_NET_WM_ICON_NAME", FALSE),
450
0, LONG_MAX, False, XInternAtom(dpy, "UTF8_STRING", FALSE),
451
&actualType, &actualFormat, &nitems,
452
&bytesLeft, (unsigned char **)&value) == Success
457
AppUtilCollectNamedIcons(pixbufs, (char *)value);
461
if (!pixbufs->len && XGetWMIconName(dpy, windowID, &wmIconName)) {
465
AppUtilCollectNamedIcons(pixbufs, wmIconName.value);
466
XFree(wmIconName.value);
469
if (!pixbufs->len && (wmh = XGetWMHints(dpy, windowID))) {
473
if (wmh->flags & IconPixmapHint) {
481
GdkPixbuf *pixbuf = NULL;
483
if (XGetGeometry(dpy, wmh->icon_pixmap, &dummyWin,
484
&x, &y, &width, &height, &border, &depth)) {
486
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
488
if (!gdk_pixbuf_xlib_get_from_drawable (pixbuf, wmh->icon_pixmap,
489
DefaultColormap(dpy, 0),
490
DefaultVisual(dpy, 0),
491
0, 0, 0, 0, width, height)) {
492
g_object_unref(G_OBJECT(pixbuf));
496
if (pixbuf && (wmh->flags & IconMaskHint)) {
498
* Apply the X bitmap mask.
500
GdkPixbuf *pixbuf_mask;
503
gdk_pixbuf_xlib_get_from_drawable(pixbuf,
505
DefaultColormap(dpy, 0),
506
DefaultVisual(dpy, 0),
519
pixels = gdk_pixbuf_get_pixels(pixbuf);
520
pixels_mask = gdk_pixbuf_get_pixels(pixbuf_mask);
521
rowstride = gdk_pixbuf_get_rowstride(pixbuf);
522
rowstride_mask = gdk_pixbuf_get_rowstride(pixbuf_mask);
523
depth_mask = gdk_pixbuf_get_bits_per_sample(pixbuf_mask);
524
ASSERT(gdk_pixbuf_get_bits_per_sample(pixbuf) == 8);
525
n_channels_mask = gdk_pixbuf_get_n_channels(pixbuf_mask);
527
for (y = 0; y < height; y++) {
528
guchar *thisrow_mask = pixels_mask + y * rowstride_mask;
529
guchar *thisrow = pixels + y * rowstride;
530
for (x = 0; x < width; x++) {
531
guchar newAlpha = 0xFF;
534
newAlpha = thisrow_mask[x * n_channels_mask / 8];
535
newAlpha >>= (x % 8);
536
newAlpha = newAlpha ? 0xFF : 0;
540
* For some reason, gdk-pixbuf-xlib turns a monochrome
541
* bitmap into 0/1 values in the blue channel of an RGBA
544
newAlpha = (thisrow_mask[x * n_channels_mask + 2])
552
thisrow[x * 4 + 3] = newAlpha;
559
g_ptr_array_add(pixbufs, pixbuf);
569
* Last resort - try using the WM_CLASS as an icon name
574
if (XGetClassHint(dpy, windowID, &hints)) {
575
if (hints.res_name) {
576
AppUtilCollectNamedIcons(pixbufs, hints.res_name);
579
XFree(hints.res_name);
580
XFree(hints.res_class);
586
* In order to make it easy for AppUtil users to pick the icon they want, we sort them
587
* largest-to-smallest.
589
g_ptr_array_sort(pixbufs, AppUtilComparePixbufSizes);
592
Debug("WARNING: No icons found for %s / %#lx\n", iconName, windowID);
600
*-----------------------------------------------------------------------------
602
* AppUtil_FreeIconArray --
604
* Frees the result of AppUtil_CollectIconArray
610
* Array and its contents are destroyed
612
*-----------------------------------------------------------------------------
616
AppUtil_FreeIconArray(GPtrArray *pixbufs) // IN
624
for (i = 0; i < pixbufs->len; i++) {
625
g_object_unref(G_OBJECT(g_ptr_array_index(pixbufs, i)));
628
g_ptr_array_free(pixbufs, TRUE);
631
*-----------------------------------------------------------------------------
633
* AppUtil_AppIsSkippable --
635
* Can an executable be ignored for the purposes of determining the path to run an
636
* app with? Usually true for interpreters and the like, for which the script path
637
* should be used instead.
640
* TRUE if the app should be ignored, FALSE otherwise.
645
*-----------------------------------------------------------------------------
649
AppUtil_AppIsSkippable(const char *appName)
651
static const char *skipAppsList[] = {
664
Str_Strcpy(cbuf, appName, sizeof cbuf);
665
ctmp = basename(cbuf);
667
for (i = 0; i < ARRAYSIZE(skipAppsList); i++) {
668
if (!strcmp(ctmp, skipAppsList[i])) {
678
*-----------------------------------------------------------------------------
680
* AppUtil_CanonicalizeAppName --
682
* Turns the app name (or path) into a full path for the executable.
685
* Path, or NULL if not available
688
* Allocated memory is returned
690
*-----------------------------------------------------------------------------
694
AppUtil_CanonicalizeAppName(const char *appName, // IN
695
const char *cwd) // IN
700
if (appName[0] == '/') {
701
return g_strdup(appName);
704
ctmp = g_find_program_in_path(appName);
712
getcwd(cbuf, sizeof cbuf);
714
ctmp = Posix_RealPath(appName);
725
*-----------------------------------------------------------------------------
729
* Initializes the AppUtil library for subsequent use.
735
* Internal state is initialized. Currently this is just gdk-pixbuf-xlib.
737
*-----------------------------------------------------------------------------
743
gdk_pixbuf_xlib_init(gdk_x11_get_default_xdisplay(), 0);