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
*********************************************************/
20
* ghIntegrationX11.c --
22
* Guest-host integration implementation for POSIX-compliant platforms that run X11.
24
* The main tasks done by this code are reading in the system's .desktop files to turn
25
* them into an internal representation of available applications on the system
26
* (implemented by GHIPlatformReadAllApplications, GHIPlatformReadApplicationsDir,
27
* GHIPlatformReadDesktopFile, and kin), and feeding portions of that internal
28
* representation to the host upon request
29
* (GHIPlatform{OpenStartMenuTree,GetStartMenuItem,CloseStartMenuTree}).
32
#define _BSD_SOURCE 1 // Needed on Linux to get the DT_* values for dirent->d_type
36
#include <sys/types.h>
42
#error "Gtk 2.0 is required"
46
#include <gdk-pixbuf/gdk-pixbuf-core.h>
48
// gdkx.h includes Xlib.h, which #defines Bool.
55
#include "dbllnklst.h"
60
#include "unityCommon.h"
63
#include "imageUtil.h"
66
#include "vm_atomic.h"
68
#include "ghIntegration.h"
69
#include "ghIntegrationInt.h"
70
#include "guest_msg_def.h"
72
#define URI_TEXTRANGE_EQUAL(textrange, str) \
73
(((textrange).afterLast - (textrange).first) == strlen((str)) \
74
&& !strncmp((textrange).first, (str), (textrange).afterLast - (textrange).first))
79
* The following defines appear in newer versions of glib 2.x, so
80
* we define them for backwards compat.
82
#ifndef G_KEY_FILE_DESKTOP_GROUP
83
#define G_KEY_FILE_DESKTOP_GROUP "Desktop Entry"
85
#ifndef G_KEY_FILE_DESKTOP_KEY_NAME
86
#define G_KEY_FILE_DESKTOP_KEY_NAME "Name"
88
#ifndef G_KEY_FILE_DESKTOP_KEY_ICON
89
#define G_KEY_FILE_DESKTOP_KEY_ICON "Icon"
91
#ifndef G_KEY_FILE_DESKTOP_KEY_EXEC
92
#define G_KEY_FILE_DESKTOP_KEY_EXEC "Exec"
94
#ifndef G_KEY_FILE_DESKTOP_KEY_TRY_EXEC
95
#define G_KEY_FILE_DESKTOP_KEY_TRY_EXEC "TryExec"
97
#ifndef G_KEY_FILE_DESKTOP_KEY_CATEGORIES
98
#define G_KEY_FILE_DESKTOP_KEY_CATEGORIES "Categories"
100
#ifndef G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY
101
#define G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY "NoDisplay"
103
#ifndef G_KEY_FILE_DESKTOP_KEY_HIDDEN
104
#define G_KEY_FILE_DESKTOP_KEY_HIDDEN "Hidden"
106
#ifndef G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN
107
#define G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN "OnlyShowIn"
109
#ifndef G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN
110
#define G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN "NotShowIn"
114
* These describe possible start menu item flags. It should come from ghiCommon.h
117
#define UNITY_START_MENU_ITEM_DIRECTORY (1 << 0)
120
* This macro provides an estimate of how much space an icon might take beyond the actual
121
* icon data when returned from unity.get.binary.info. This makes space for the
122
* width/height/size strings, and adds enough padding to give some breathing room just in
125
* > This is only an estimate. <
127
#define ICON_SPACE_PADDING (sizeof "999x999x65535x" + 25)
130
* The GHIDirectoryWatch object represents a watch on a directory to be notified of
131
* added/removed/changed .desktop files.
133
* XXX Watching directories for added/changed/removed .desktop files is not yet
134
* implemented. We need to figure out whether we want to use inotify, dnotify, gamin,
135
* etc. and work through all the backwards compat issues.
141
struct _GHIPlatform {
142
GTree *apps; // Tree of GHIMenuDirectory's, keyed & ordered by their dirname
143
GHashTable *appsByExecutable; // Translates full executable path to GHIMenuItem
144
GHashTable *appsByDesktopEntry; // Translates full .desktop path to GHIMenuItem
146
* Translates arbitrary executable paths as discovered through
147
* UnityPlatformGetWindowPaths to a .desktop-ful executable URI.
150
* (key) /usr/lib/firefox-3.6.3/firefox-bin (via Firefox window's _NET_WM_PID)
151
* (value) file:///usr/bin/firefox?DesktopEntry=/usr/share/applications/firefox.desktop
153
GHashTable *appsByWindowExecutable;
155
Bool trackingEnabled;
156
GArray *directoriesTracked;
159
GHashTable *menuHandles;
161
/** Pre-wrapper script environment. See @ref System_GetNativeEnviron. */
162
const char **nativeEnviron;
168
* The GHIMenuItem object represents an individual leaf-node menu item (corresponding to
172
char *exepath; // The full exe path for use in GHIPlatform::appsByExecutable
173
char *keyfilePath; // Key to GHIPlatform::appsByDesktopEntry, used in %k field code
174
GKeyFile *keyfile; // glib data structure representing the parsed .desktop file
178
* Represents a "start menu folder" so to speak.
181
const char *dirname; // The .desktop category that this object represents
182
const char *prettyDirname; // (optional) A prettier version of dirname.
183
GPtrArray *items; // Array of pointers to GHIMenuItems
187
* Represents an active handle for traversing a menu.
191
enum { LAUNCH_FOLDER, FIXED_FOLDER, DIRECTORY_FOLDER } handleType;
192
GHIMenuDirectory *gmd; // Only set for DIRECTORY_FOLDER handles
196
* This is used to help us find the Nth GHIMenuDirectory node in the GHIPlatform::apps
197
* tree, an operation that is needed as part of GHIPlatformGetStartMenuItem...
202
GHIMenuDirectory *gmd; // OUT - pointer to the Nth GHIMenuDirectory
205
static void GHIPlatformSetMenuTracking(GHIPlatform *ghip,
207
static char *GHIPlatformUriPathToString(UriPathSegmentA *path);
211
* This is a list of directories that we search for .desktop files by default.
213
static const char *desktopDirs[] = {
214
"/usr/share/applications",
215
"/opt/gnome/share/applications",
216
"/opt/kde3/share/applications",
217
"/opt/kde4/share/applications",
218
"/opt/kde/share/applications",
220
"~/.local/share/applications"
225
* GHI capabilities for this platform.
228
* XXX TODO: re-enable once ShellAction is implemented.
231
static GuestCapabilities platformGHICaps[] = {
232
GHI_CAP_CMD_SHELL_ACTION,
233
GHI_CAP_SHELL_ACTION_BROWSE,
234
GHI_CAP_SHELL_ACTION_RUN,
235
GHI_CAP_SHELL_LOCATION_HGFS
241
*-----------------------------------------------------------------------------
243
* GHIPlatformDestroyMenuItem --
245
* Frees a menu item object (which right now is just a GKeyFile).
251
* The specified GHIMenuItem is no longer valid.
253
*-----------------------------------------------------------------------------
257
GHIPlatformDestroyMenuItem(gpointer data, // IN
258
gpointer user_data) // IN (unused)
265
g_key_file_free(gmi->keyfile);
266
g_free(gmi->keyfilePath);
267
g_free(gmi->exepath);
273
*-----------------------------------------------------------------------------
275
* GHIPlatformDestroyMenuDirectory --
277
* Frees the memory associated with a GHIMenuDirectory object.
283
* The specified GHIMenuDirectory object is no longer valid.
285
*-----------------------------------------------------------------------------
289
GHIPlatformDestroyMenuDirectory(gpointer data) // IN
291
GHIMenuDirectory *gmd = (GHIMenuDirectory *) data;
293
// gmd->dirname comes from a static const array, so it should never be freed
294
g_ptr_array_foreach(gmd->items, GHIPlatformDestroyMenuItem, NULL);
295
g_ptr_array_free(gmd->items, TRUE);
301
*----------------------------------------------------------------------------
303
* GHIPlatformIsSupported --
305
* Determine whether this guest supports guest host integration.
308
* TRUE if the guest supports GHI
314
*----------------------------------------------------------------------------
318
GHIPlatformIsSupported(void)
329
*----------------------------------------------------------------------------
333
* Sets up the platform-specific GHI state.
336
* Pointer to platform-specific data (may be NULL).
341
*----------------------------------------------------------------------------
345
GHIPlatformInit(VMU_ControllerCB *vmuControllerCB, // IN
348
extern const char **environ;
351
ghip = Util_SafeCalloc(1, sizeof *ghip);
352
ghip->directoriesTracked = g_array_new(FALSE, FALSE, sizeof(GHIDirectoryWatch));
353
ghip->nativeEnviron = System_GetNativeEnviron(environ);
354
ghip->appsByWindowExecutable =
355
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
363
*----------------------------------------------------------------------------
365
* GHIPlatformRegisterCaps --
367
* Register guest platform specific capabilities with the VMX.
375
*----------------------------------------------------------------------------
379
GHIPlatformRegisterCaps(GHIPlatform *ghip) // IN
382
//ASSERT(platformGHICaps);
385
* XXX TODO: re-enable once ShellAction is implemented.
387
//AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), TRUE);
392
*----------------------------------------------------------------------------
394
* GHIPlatformUnregisterCaps --
396
* Register guest platform specific capabilities with the VMX.
404
*----------------------------------------------------------------------------
408
GHIPlatformUnregisterCaps(GHIPlatform *ghip) // IN
411
//ASSERT(platformGHICaps);
414
* XXX TODO: re-enable once ShellAction is implemented.
416
//AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), FALSE);
422
*-----------------------------------------------------------------------------
424
* GHIPlatformFreeValue --
426
* Frees a hash table entry. Typically called from a g_hashtable
427
* iterator. Just the value is destroyed, not the key.
428
* Also called directly from GHIPlatformCloseStartMenu.
434
* The specified value will no longer be valid.
436
*-----------------------------------------------------------------------------
440
GHIPlatformFreeValue(gpointer key, // IN
441
gpointer value, // IN
442
gpointer user_data) // IN
452
*-----------------------------------------------------------------------------
454
* GHIPlatformCleanupMenuEntries --
456
* Frees all the memory associated with the menu information, including active menu
457
* handles and the internal applications menu representation.
465
*-----------------------------------------------------------------------------
469
GHIPlatformCleanupMenuEntries(GHIPlatform *ghip) // IN
472
if (ghip->menuHandles) {
473
g_hash_table_foreach_remove(ghip->menuHandles, GHIPlatformFreeValue, NULL);
474
g_hash_table_destroy(ghip->menuHandles);
475
ghip->menuHandles = NULL;
479
g_hash_table_destroy(ghip->appsByDesktopEntry);
480
g_hash_table_destroy(ghip->appsByExecutable);
481
g_tree_destroy(ghip->apps);
489
*----------------------------------------------------------------------------
491
* GHIPlatformCleanup --
493
* Tears down the platform-specific GHI state.
499
* GHIPlatform is no longer valid.
501
*----------------------------------------------------------------------------
505
GHIPlatformCleanup(GHIPlatform *ghip) // IN
511
GHIPlatformSetMenuTracking(ghip, FALSE);
512
g_array_free(ghip->directoriesTracked, TRUE);
513
ghip->directoriesTracked = NULL;
514
if (ghip->nativeEnviron) {
515
System_FreeNativeEnviron(ghip->nativeEnviron);
516
ghip->nativeEnviron = NULL;
518
g_hash_table_destroy(ghip->appsByWindowExecutable);
527
*-----------------------------------------------------------------------------
529
* GHIPlatformCollectIconInfo --
531
* Sucks all the icon information for a particular application from the system, and
532
* appends it into the DynBuf for returning to the host.
538
* Adds data into the DynBuf.
540
*-----------------------------------------------------------------------------
544
GHIPlatformCollectIconInfo(GHIPlatform *ghip, // IN
545
GHIMenuItem *ghm, // IN
546
unsigned long windowID, // IN
547
DynBuf *buf) // IN/OUT
551
gsize totalIconBytes;
556
ctmp = g_key_file_get_string(ghm->keyfile, G_KEY_FILE_DESKTOP_GROUP,
557
G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
560
pixbufs = AppUtil_CollectIconArray(ctmp, windowID);
563
* Now see if all of these icons can fit into our reply.
565
totalIconBytes = DynBuf_GetSize(buf);
566
for (i = 0; i < pixbufs->len; i++) {
568
GdkPixbuf *pixbuf = g_ptr_array_index(pixbufs, i);
570
thisIconBytes = ICON_SPACE_PADDING; // Space used by the width/height/size strings, and breathing room
571
thisIconBytes += gdk_pixbuf_get_width(pixbuf)
572
* gdk_pixbuf_get_height(pixbuf)
573
* 4 /* image will be BGRA */;
574
if ((thisIconBytes + totalIconBytes) < GUESTMSG_MAX_IN_SIZE) {
575
totalIconBytes += thisIconBytes;
576
} else if (pixbufs->len == 1) {
578
volatile double newWidth;
579
volatile double newHeight;
580
volatile double scaleFactor;
582
newWidth = gdk_pixbuf_get_width(pixbuf);
583
newHeight = gdk_pixbuf_get_height(pixbuf);
584
scaleFactor = (GUESTMSG_MAX_IN_SIZE - totalIconBytes - ICON_SPACE_PADDING);
585
scaleFactor /= (newWidth * newHeight * 4.0);
586
if (scaleFactor > 0.95) {
588
* Ensures that we remove at least a little bit of data from the icon.
589
* Otherwise we can get things like scalefactors of '0.999385' which result
590
* in an image of exactly the same size. A scaleFactor of 0.95 will remove at
591
* least one row or column from any icon large enough to go past the limit.
596
newWidth *= scaleFactor;
597
newHeight *= scaleFactor;
600
* If this is the only icon available, try scaling it down to the largest icon
601
* that will comfortably fit in the reply.
603
* Adding 0.5 to newWidth & newHeight is an easy way of rounding to the closest
606
newIcon = gdk_pixbuf_scale_simple(pixbuf,
607
(int)(newWidth + 0.5),
608
(int)(newHeight + 0.5),
610
g_object_unref(G_OBJECT(pixbuf));
611
g_ptr_array_index(pixbufs, i) = newIcon;
612
i--; // Try including the newly scaled-down icon
614
g_object_unref(G_OBJECT(pixbuf));
615
g_ptr_array_remove_index_fast(pixbufs, i);
621
* Now that we actually have all available icons loaded and checked, dump their count
622
* and contents into the reply.
624
Str_Sprintf(tbuf, sizeof tbuf, "%u", pixbufs->len);
625
DynBuf_AppendString(buf, tbuf);
627
for (i = 0; i < pixbufs->len; i++) {
636
pixbuf = g_ptr_array_index(pixbufs, i);
638
width = gdk_pixbuf_get_width(pixbuf);
639
height = gdk_pixbuf_get_height(pixbuf);
640
Str_Sprintf(tbuf, sizeof tbuf, "%d", width);
641
DynBuf_AppendString(buf, tbuf);
642
Str_Sprintf(tbuf, sizeof tbuf, "%d", height);
643
DynBuf_AppendString(buf, tbuf);
645
Str_Sprintf(tbuf, sizeof tbuf, "%d", width * height * 4);
646
DynBuf_AppendString(buf, tbuf);
648
ASSERT (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
649
ASSERT (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
650
rowstride = gdk_pixbuf_get_rowstride(pixbuf);
651
n_channels = gdk_pixbuf_get_n_channels(pixbuf);
652
pixels = gdk_pixbuf_get_pixels(pixbuf);
653
for (y = height - 1; y >= 0; y--) { // GetBinaryInfo icons are bottom-to-top. :(
654
for (x = 0; x < width; x++) {
656
guchar *p; // Pointer to RGBA data in GdkPixbuf
658
p = pixels + (y * rowstride) + (x * n_channels);
662
if (n_channels > 3) {
667
DynBuf_Append(buf, bgra, 4);
671
DynBuf_AppendString(buf, "");
675
AppUtil_FreeIconArray(pixbufs);
681
*----------------------------------------------------------------------------
683
* GHIPlatformGetBinaryInfo --
685
* Get binary information (app name and icons). We're passed app info in
686
* pathURIUtf8 (in URI format), and we find the app info by looking up the
687
* path in GHIPlatform->appsByExecutable. Once we find it, we can retrieve
688
* info on the app from the .desktop file.
691
* TRUE if everything went ok, FALSE otherwise.
696
*----------------------------------------------------------------------------
700
GHIPlatformGetBinaryInfo(GHIPlatform *ghip, // IN: platform-specific state
701
const char *pathURIUtf8, // IN: full path to the binary file
702
DynBuf *buf) // OUT: binary information
705
const char *realCmd = NULL;
706
char *keyfilePath = NULL;
707
unsigned long windowID = 0;
708
gpointer freeMe = NULL;
709
GHIMenuItem *ghm = NULL;
711
UriParserStateA state;
718
memset(&state, 0, sizeof state);
719
memset(&uri, 0, sizeof uri);
722
if (pathURIUtf8[0] == '/') {
723
realCmd = pathURIUtf8;
724
} else if (uriParseUriA(&state, pathURIUtf8) == URI_SUCCESS) {
725
if (URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
726
UriQueryListA *queryList = NULL;
729
realCmd = freeMe = GHIPlatformUriPathToString(uri.pathHead);
730
if (uriDissectQueryMallocA(&queryList, &itemCount,
732
uri.query.afterLast) == URI_SUCCESS) {
735
for (cur = queryList; cur; cur = cur->next) {
740
if (strcmp(cur->key, "WindowXID") == 0) {
741
sscanf(cur->value, "%lu", &windowID); // Ignore any failures
742
} else if (strcmp(cur->key, "DesktopEntry") == 0) {
743
keyfilePath = g_strdup(cur->value);
747
uriFreeQueryListA(queryList);
750
uriFreeUriMembersA(&uri);
751
Debug("Binary URI %s does not have a 'file' scheme\n", pathURIUtf8);
755
uriFreeUriMembersA(&uri);
759
GHIPlatformSetMenuTracking(ghip, TRUE);
762
* If for some reason the command we got wasn't a fullly expanded filesystem path,
763
* then expand the command into a full path.
765
if (realCmd[0] != '/') {
766
ctmp = g_find_program_in_path(realCmd);
779
ghm = g_hash_table_lookup(ghip->appsByDesktopEntry, keyfilePath);
785
* Now that we have the full path, look it up in our hash table of GHIMenuItems
787
ghm = g_hash_table_lookup(ghip->appsByExecutable, realCmd);
792
* To deal with /usr/bin/gimp being a symlink to gimp-2.x, also try symlinks.
794
char newPath[PATH_MAX + 1];
797
linkLen = readlink(realCmd, newPath, sizeof newPath - 1);
801
newPath[linkLen] = '\0';
802
slashLoc = strrchr(realCmd, '/');
803
if (newPath[0] != '/' && slashLoc) {
804
ctmp = g_strdup_printf("%.*s%s",
805
(int)((slashLoc + 1) - realCmd),
808
realCmd = freeMe = ctmp;
813
ghm = g_hash_table_lookup(ghip->appsByExecutable, realCmd);
817
* Stick the app name into 'buf'.
820
ctmp = g_key_file_get_locale_string(ghm->keyfile, G_KEY_FILE_DESKTOP_GROUP,
821
G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
823
ctmp = g_path_get_basename(realCmd);
825
DynBuf_AppendString(buf, ctmp);
829
* If we can't find it, then just tell the host that the app name is the same as
830
* the basename of the application's path.
832
ctmp = strrchr(realCmd, '/');
836
ctmp = (char *) realCmd;
838
DynBuf_AppendString(buf, ctmp);
842
ctmp = freeMe = NULL;
844
GHIPlatformCollectIconInfo(ghip, ghm, windowID, buf);
854
*----------------------------------------------------------------------------
856
* GHIPlatformGetBinaryHandlers --
858
* Get the list of filetypes and URL protocols supported by a binary
859
* (application). We're passed an app path in URI format, and we find
860
* the app info by looking up the path in GHIPlatform->appsByExecutable.
861
* Once we find it, we can retrieve info on the app from the .desktop file.
864
* TRUE if everything went ok, FALSE otherwise.
869
*----------------------------------------------------------------------------
873
GHIPlatformGetBinaryHandlers(GHIPlatform *ghip, // IN: platform-specific state
874
const char *pathUtf8, // IN: full path to the executable
875
XDR *xdrs) // OUT: binary information
885
*-----------------------------------------------------------------------------
887
* GHIPlatformGetDesktopName --
889
* Figures out which desktop environment we're running under.
892
* Desktop name if successful, NULL otherwise.
895
* Allocates memory to hold return value.
897
*-----------------------------------------------------------------------------
901
GHIPlatformGetDesktopName(void)
904
static const char *clientMappings[][2] = {
905
{"gnome-panel", "GNOME"},
906
{"gnome-session", "GNOME"},
907
{"nautilus", "GNOME"},
908
{"ksmserver", "KDE"},
911
{"konqueror", "KDE"},
912
{"xfce-mcs-manage", "XFCE"},
914
{"ROX-Session", "ROX"}
918
Window temp1; // throwaway
919
Window temp2; // throwaway
920
Window *children = NULL;
921
unsigned int nchildren;
922
static const char *desktopEnvironment = NULL;
925
* NB: While window managers may change during vmware-user's execution, TTBOMK
926
* desktop environments cannot, so this is safe.
928
if (desktopEnvironment) {
929
return desktopEnvironment;
932
display = gdk_x11_get_default_xdisplay();
933
rootWindow = DefaultRootWindow(display);
935
if (XQueryTree(display, rootWindow, &temp1, &temp2, &children, &nchildren) == 0) {
939
for (i = 0; i < nchildren && !desktopEnvironment; i++) {
940
XClassHint wmClass = { 0, 0 };
944
* Try WM_CLASS first, then try WM_NAME.
947
if (XGetClassHint(display, children[i], &wmClass) != 0) {
948
for (j = 0; j < ARRAYSIZE(clientMappings) && !desktopEnvironment; j++) {
949
if ((strcasecmp(clientMappings[j][0], wmClass.res_name) == 0) ||
950
(strcasecmp(clientMappings[j][0], wmClass.res_class) == 0)) {
951
desktopEnvironment = clientMappings[j][1];
954
XFree(wmClass.res_name);
955
XFree(wmClass.res_class);
958
if (!desktopEnvironment) {
961
if ((XFetchName(display, children[i], &name) == 0) ||
966
for (j = 0; j < ARRAYSIZE(clientMappings) && !desktopEnvironment; j++) {
967
if (!strcmp(clientMappings[j][0], name)) {
968
desktopEnvironment = clientMappings[j][1];
977
return desktopEnvironment;
982
*-----------------------------------------------------------------------------
984
* GHIPlatformIsMenuItemAllowed --
986
* This routine tells the caller, based on policies defined by the .desktop file,
987
* whether the requested application should be displayed in the Unity menus.
990
* TRUE if the item should be displayed, FALSE if it should not be.
995
*-----------------------------------------------------------------------------
999
GHIPlatformIsMenuItemAllowed(GHIPlatform *ghip, // IN:
1000
GKeyFile *keyfile) // IN:
1008
* Examine the "NoDisplay" and "Hidden" properties.
1010
if (g_key_file_get_boolean(keyfile,
1011
G_KEY_FILE_DESKTOP_GROUP,
1012
G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY,
1014
g_key_file_get_boolean(keyfile,
1015
G_KEY_FILE_DESKTOP_GROUP,
1016
G_KEY_FILE_DESKTOP_KEY_HIDDEN,
1018
Debug("%s: contains either NoDisplay or Hidden keys.\n", __func__);
1023
* NB: This may return NULL.
1024
* XXX Perhaps that should be changed to return an empty string?
1026
dtname = GHIPlatformGetDesktopName();
1029
* Check our desktop environment name against the OnlyShowIn and NotShowIn
1032
* NB: If the .desktop file defines OnlyShowIn as an empty string, we
1033
* effectively ignore it. (Another interpretation would be that an application
1034
* shouldn't appear at all, but that's what the NoDisplay and Hidden keys are
1037
* XXX I didn't see anything in the Key-value file parser reference, but I'm
1038
* wondering if there's some other GLib string list searching goodness that
1039
* would obviate the boilerplate-ish code below.
1042
gchar **onlyShowList = NULL;
1045
onlyShowList = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1046
G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN,
1048
if (onlyShowList && nstrings) {
1049
Bool matchedOnlyShowIn = FALSE;
1053
for (i = 0; i < nstrings; i++) {
1054
if (strcasecmp(dtname, onlyShowList[i]) == 0) {
1055
matchedOnlyShowIn = TRUE;
1061
if (!matchedOnlyShowIn) {
1062
Debug("%s: OnlyShowIn does not include our desktop environment, %s.\n",
1063
__func__, dtname ? dtname : "(not set)");
1064
g_strfreev(onlyShowList);
1068
g_strfreev(onlyShowList);
1072
gchar **notShowList = NULL;
1076
notShowList = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1077
G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN,
1079
if (notShowList && nstrings) {
1080
for (i = 0; i < nstrings; i++) {
1081
if (strcasecmp(dtname, notShowList[i]) == 0) {
1082
Debug("%s: NotShowIn includes our desktop environment, %s.\n",
1084
g_strfreev(notShowList);
1089
g_strfreev(notShowList);
1097
*-----------------------------------------------------------------------------
1099
* GHIPlatformGetExecFromKeyFile --
1101
* Given a GLib GKeyFile, extract path(s) from the TryExec or Exec
1102
* keys, normalize them, and return them to the caller.
1105
* Returns a string pointer to an absolute executable pathname on success or
1109
* This routine returns memory allocated by GLib. Caller is responsible
1110
* for freeing it via g_free.
1112
*-----------------------------------------------------------------------------
1116
GHIPlatformGetExecFromKeyfile(GHIPlatform *ghip, // IN
1117
GKeyFile *keyfile) // IN
1122
* TryExec is supposed to be a path to an executable without arguments that,
1123
* if set but not found or not executable, indicates that this menu item should
1129
tryExec = g_key_file_get_string(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1130
G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL);
1133
ctmp = g_find_program_in_path(tryExec);
1136
Debug("%s: Entry has TryExec=%s, but it was not found in our PATH.\n",
1148
* Next up: Look up Exec key and do some massaging to skip over common interpreters.
1157
exec = g_key_file_get_string(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1158
G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
1161
Debug("%s: Missing Exec key.\n", __func__);
1165
parsed = g_shell_parse_argv(exec, &argc, &argv, NULL);
1169
Debug("%s: Unable to parse shell arguments.\n", __func__);
1173
for (i = 0; i < argc; i++) {
1175
* The Exec= line in the .desktop file may list other boring helper apps before
1176
* the name of the main app. getproxy is a common one. We need to skip those
1177
* arguments in the cmdline.
1179
if (!AppUtil_AppIsSkippable(argv[i])) {
1180
exe = g_strdup(argv[i]);
1188
* Turn it into a full path. Yes, if we can't get an absolute path, we'll return
1191
if (exe && *exe != '/') {
1194
ctmp = g_find_program_in_path(exe);
1198
Debug("%s: Unable to find program in PATH.\n", __func__);
1207
*-----------------------------------------------------------------------------
1209
* GHIPlatformAddMenuItem --
1211
* Examines an application's .desktop file and inserts it into an appropriate
1212
* Unity application menu.
1215
* A new GHIMenuItem will be created. If our desired menu directory doesn't
1216
* already exist, then we'll create that, too.
1221
*-----------------------------------------------------------------------------
1225
GHIPlatformAddMenuItem(GHIPlatform *ghip, // IN:
1226
const char *keyfilePath, // IN:
1227
GKeyFile *keyfile, // IN:
1228
char *exePath) // IN:
1231
* A list of categories that a .desktop file should be in in order to be relayed to
1234
* NB: "Other" is a generic category where we dump applications for which we can't
1235
* determine an appropriate category. This is "safe" as long as menu-spec doesn't
1236
* register it, and I don't expect that to happen any time soon. It is -extremely-
1237
* important that "Other" be the final entry in this list.
1239
* XXX See desktop-entry-spec and make use of .directory files.
1241
static const char *validCategories[][2] = {
1244
* menu-spec category pretty string
1246
{ "AudioVideo", "Sound & Video" },
1247
{ "Development", 0 },
1249
{ "Game", "Games" },
1260
* Applications belonging to certain categories should never show up
1261
* in our launch menus. List taken from
1262
* http://standards.freedesktop.org/menu-spec/latest/apa.html table 2.
1264
static const char *invalidCategories[] = {
1269
GHIMenuDirectory *gmd;
1271
Bool foundIt = FALSE;
1272
char **categories = NULL;
1274
int kfIndex; // keyfile categories index/iterator
1275
int vIndex; // validCategories index/iterator
1276
int iIndex; // invalidCategories index/iterator
1279
* Figure out if this .desktop file is in a category we want to put on our menus,
1280
* and if so which one...
1282
categories = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1283
G_KEY_FILE_DESKTOP_KEY_CATEGORIES,
1286
for (kfIndex = 0; kfIndex < numcats && !foundIt; kfIndex++) {
1288
* If any of this app's categories are in our blacklist, we'll just
1289
* return prematurely.
1291
for (iIndex = 0; iIndex < ARRAYSIZE(invalidCategories); iIndex++) {
1292
if (!strcasecmp(categories[kfIndex], invalidCategories[iIndex])) {
1293
g_debug("Ignoring app %s because it's a member of category %s.\n",
1294
keyfilePath, categories[kfIndex]);
1300
* NB: See validCategories' comment re: "Other" being the final, default
1301
* category. It explains why we condition on ARRAYSIZE() - 1.
1303
for (vIndex = 0; vIndex < ARRAYSIZE(validCategories) - 1; vIndex++) {
1304
if (!strcasecmp(categories[kfIndex], validCategories[vIndex][0])) {
1310
g_strfreev(categories);
1314
* If not found, fall back to "Other".
1317
vIndex = ARRAYSIZE(validCategories) - 1;
1321
* We have all the information we need to create the new GHIMenuItem.
1323
gmi = g_new0(GHIMenuItem, 1);
1324
gmi->keyfilePath = g_strdup(keyfilePath);
1325
gmi->keyfile = keyfile;
1326
gmi->exepath = exePath;
1328
gmd = g_tree_lookup(ghip->apps, validCategories[vIndex][0]);
1332
* A GHIMenuDirectory object does not yet exist for the validCategory
1333
* that this .desktop is in, so create that object.
1335
gmd = g_new0(GHIMenuDirectory, 1);
1336
gmd->dirname = validCategories[vIndex][0];
1337
gmd->prettyDirname = validCategories[vIndex][1];
1338
gmd->items = g_ptr_array_new();
1339
g_tree_insert(ghip->apps, (gpointer)validCategories[vIndex][0], gmd);
1340
Debug("Created new category '%s'\n", gmd->dirname);
1343
g_ptr_array_add(gmd->items, gmi);
1344
g_hash_table_insert(ghip->appsByExecutable, gmi->exepath, gmi);
1345
g_hash_table_insert(ghip->appsByDesktopEntry, gmi->keyfilePath, gmi);
1346
Debug("Loaded desktop item for %s into %s\n", gmi->exepath, gmd->dirname);
1351
*-----------------------------------------------------------------------------
1353
* GHIPlatformReadDesktopFile --
1355
* Reads a .desktop file into our internal representation of the available
1364
*-----------------------------------------------------------------------------
1368
GHIPlatformReadDesktopFile(GHIPlatform *ghip, // IN
1369
const char *path) // IN
1371
GKeyFile *keyfile = NULL;
1374
Debug("%s: Analyzing %s.\n", __func__, path);
1377
* First load our .desktop file into a GLib GKeyFile structure. Then perform
1378
* some rudimentary policy checks based on keys like NoDisplay and OnlyShowIn.
1381
keyfile = g_key_file_new();
1383
Debug("%s: g_key_file_new failed.\n", __func__);
1387
if (!g_key_file_load_from_file(keyfile, path, 0, NULL) ||
1388
!GHIPlatformIsMenuItemAllowed(ghip, keyfile)) {
1389
g_key_file_free(keyfile);
1390
Debug("%s: Unable to load .desktop file or told to skip it.\n", __func__);
1395
* Okay, policy checks passed. Next up, obtain a normalized executable path,
1396
* and if successful insert it into our menus.
1399
exe = GHIPlatformGetExecFromKeyfile(ghip, keyfile);
1401
/* The following routine takes ownership of keyfile and exec. */
1402
GHIPlatformAddMenuItem(ghip, path, keyfile, exe);
1404
Debug("%s: Could not find executable for %s\n", __func__, path);
1405
g_key_file_free(keyfile);
1411
*-----------------------------------------------------------------------------
1413
* GHIPlatformReadApplicationsDir --
1415
* Reads in the .desktop files in a particular directory.
1423
*-----------------------------------------------------------------------------
1427
GHIPlatformReadApplicationsDir(GHIPlatform *ghip, // IN
1428
const char *dir) // IN
1431
struct dirent *dent;
1432
GHIDirectoryWatch dirWatch;
1437
dirh = opendir(dir);
1442
dirWatch.directoryPath = strdup(dir);
1443
g_array_append_val(ghip->directoriesTracked, dirWatch);
1445
while ((dent = readdir(dirh))) {
1446
char subpath[PATH_MAX];
1450
if (!strcmp(dent->d_name, ".") ||
1451
!strcmp(dent->d_name, "..") ||
1452
!strcmp(dent->d_name, ".hidden")) {
1456
subpathLen = Str_Sprintf(subpath, sizeof subpath, "%s/%s", dir, dent->d_name);
1457
if (subpathLen >= (sizeof subpath - 1)) {
1458
Warning("There may be a recursive symlink or long path,"
1459
" somewhere above %s. Skipping.\n", subpath);
1463
if (dent->d_type == DT_UNKNOWN && stat(subpath, &sbuf)) {
1467
if (dent->d_type == DT_DIR ||
1468
(dent->d_type == DT_UNKNOWN
1469
&& S_ISDIR(sbuf.st_mode))) {
1470
GHIPlatformReadApplicationsDir(ghip, subpath);
1471
} else if ((dent->d_type == DT_REG ||
1472
dent->d_type == DT_LNK ||
1473
(dent->d_type == DT_UNKNOWN
1474
&& S_ISREG(sbuf.st_mode)))
1475
&& StrUtil_EndsWith(dent->d_name, ".desktop")) {
1476
GHIPlatformReadDesktopFile(ghip, subpath);
1485
*-----------------------------------------------------------------------------
1487
* GHIPlatformReadAllApplications --
1489
* Reads in information on all the applications that have .desktop files on this
1496
* ghip->applist is created.
1498
*-----------------------------------------------------------------------------
1502
GHIPlatformReadAllApplications(GHIPlatform *ghip) // IN
1509
ghip->apps = g_tree_new_full((GCompareDataFunc)strcmp, NULL, NULL,
1510
GHIPlatformDestroyMenuDirectory);
1511
ghip->appsByExecutable = g_hash_table_new(g_str_hash, g_str_equal);
1512
ghip->appsByDesktopEntry = g_hash_table_new(g_str_hash, g_str_equal);
1514
for (i = 0; i < ARRAYSIZE(desktopDirs); i++) {
1515
if (StrUtil_StartsWith(desktopDirs[i], "~/")) {
1516
char cbuf[PATH_MAX];
1518
Str_Sprintf(cbuf, sizeof cbuf, "%s/%s",
1519
g_get_home_dir(), desktopDirs[i] + 2);
1520
GHIPlatformReadApplicationsDir(ghip, cbuf);
1522
GHIPlatformReadApplicationsDir(ghip, desktopDirs[i]);
1531
*----------------------------------------------------------------------------
1533
* GHIPlatformOpenStartMenuTree --
1535
* Get start menu item count for a given root. This function should be
1536
* called before iterating through the menu item subtree.
1537
* To start at the root of the start menu, pass in "" for the root.
1539
* The output 'buf' is a string holding two numbers separated by a space:
1540
* 1. A handle ID for this menu tree iterator.
1541
* 2. A count of the items in this iterator.
1544
* TRUE if we were able to get the count successfully
1550
*----------------------------------------------------------------------------
1554
GHIPlatformOpenStartMenuTree(GHIPlatform *ghip, // IN: platform-specific state
1555
const char *rootUtf8, // IN: root of the tree
1556
uint32 flags, // IN: flags
1557
DynBuf *buf) // OUT: number of items
1563
Bool retval = FALSE;
1569
GHIPlatformSetMenuTracking(ghip, TRUE);
1571
if (!ghip->menuHandles) {
1572
ghip->menuHandles = g_hash_table_new(g_direct_hash, g_direct_equal);
1579
gmh = g_new0(GHIMenuHandle, 1);
1580
gmh->handleID = ++ghip->nextMenuHandle;
1582
if (!strcmp(rootUtf8, UNITY_START_MENU_LAUNCH_FOLDER)) {
1583
gmh->handleType = LAUNCH_FOLDER;
1584
itemCount = g_tree_nnodes(ghip->apps);
1586
} else if (!strcmp(rootUtf8, UNITY_START_MENU_FIXED_FOLDER)) {
1588
* XXX Not yet implemented
1590
gmh->handleType = FIXED_FOLDER;
1592
} else if (*rootUtf8) {
1593
gmh->handleType = DIRECTORY_FOLDER;
1595
if (StrUtil_StartsWith(rootUtf8, UNITY_START_MENU_LAUNCH_FOLDER)) {
1596
gmh->gmd = g_tree_lookup(ghip->apps,
1597
rootUtf8 + sizeof(UNITY_START_MENU_LAUNCH_FOLDER));
1599
itemCount = gmh->gmd->items->len;
1610
Debug("Opened start menu tree for %s with %d items, handle %d\n",
1611
rootUtf8, itemCount, gmh->handleID);
1613
g_hash_table_insert(ghip->menuHandles, GINT_TO_POINTER(gmh->handleID), gmh);
1615
Str_Sprintf(temp, sizeof temp, "%d %d", gmh->handleID, itemCount);
1616
DynBuf_AppendString(buf, temp);
1627
*-----------------------------------------------------------------------------
1629
* GHIPlatformFindLaunchMenuItem --
1631
* A GTraverseFunc used to find the right item in the list of directories.
1634
* TRUE if tree traversal should stop, FALSE otherwise.
1639
*-----------------------------------------------------------------------------
1643
GHIPlatformFindLaunchMenuItem(gpointer key, // IN
1644
gpointer value, // IN
1645
gpointer data) // IN
1647
GHITreeTraversal *td;
1654
if (td->currentItem == td->desiredItem) {
1665
*-----------------------------------------------------------------------------
1667
* GHIPlatformMenuItemToURI --
1669
* Returns the URI that would be used to launch a particular GHI menu item
1672
* Newly allocated URI string
1675
* Allocates memory for the URI.
1677
*-----------------------------------------------------------------------------
1681
GHIPlatformMenuItemToURI(GHIPlatform *ghip, // IN
1682
GHIMenuItem *gmi) // IN
1688
UriQueryListA *queryItems;
1699
ctmp = g_key_file_get_string(gmi->keyfile, G_KEY_FILE_DESKTOP_GROUP,
1700
G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
1702
res = g_shell_parse_argv(ctmp, &argc, &argv, NULL);
1708
queryItems = alloca((argc + 1) * sizeof *queryItems);
1710
for (i = 0; i < (argc - 1); i++) {
1711
queryItems[i].key = "argv[]";
1712
queryItems[i].value = argv[i + 1];
1713
queryItems[i].next = &queryItems[i + 1];
1715
queryItems[i].key = "DesktopEntry";
1716
queryItems[i].value = gmi->keyfilePath;
1717
queryItems[i].next = NULL;
1720
* 10 + 3 * len is the formula recommended by uriparser for the maximum URI string
1723
uriString = alloca(10 + 3 * strlen(gmi->exepath));
1724
if (uriUnixFilenameToUriStringA(gmi->exepath, uriString)) {
1728
if (uriComposeQueryCharsRequiredA(queryItems, &nchars) != URI_SUCCESS) {
1732
queryString = alloca(nchars + 1);
1733
err = uriComposeQueryA(queryString, queryItems, nchars + 1, &i);
1735
if (err != URI_SUCCESS) {
1739
return g_strdup_printf("%s?%s", uriString, queryString);
1744
*----------------------------------------------------------------------------
1746
* GHIPlatformGetStartMenuItem --
1748
* Get start menu item at a given index. This function should be called
1749
* in the loop to get all items for a menu sub-tree.
1750
* If there are no more items, the function will return FALSE.
1752
* Upon returning, 'buf' will hold a nul-delimited array of strings:
1753
* 1. User-visible item name.
1754
* 2. UNITY_START_MENU_ITEM_* flag.
1755
* 3. Executable path.
1756
* 4. Localized user-visible item name.
1759
* TRUE if there's an item at a given index, FALSE otherwise.
1764
*----------------------------------------------------------------------------
1768
GHIPlatformGetStartMenuItem(GHIPlatform *ghip, // IN: platform-specific state
1769
uint32 handle, // IN: tree handle
1770
uint32 itemIndex, // IN: the index of the item in the tree
1771
DynBuf *buf) // OUT: item
1775
char *itemName = NULL;
1777
char *itemPath = NULL;
1778
char *localizedItemName = NULL;
1779
Bool freeItemName = FALSE;
1780
Bool freeItemPath = FALSE;
1781
Bool freeLocalItemName = FALSE;
1785
ASSERT(ghip->menuHandles);
1788
gmh = g_hash_table_lookup(ghip->menuHandles, GINT_TO_POINTER(handle));
1793
switch (gmh->handleType) {
1796
GHITreeTraversal traverseData = { -1, itemIndex, NULL };
1799
* We're iterating through the list of directories.
1805
g_tree_foreach(ghip->apps, GHIPlatformFindLaunchMenuItem, &traverseData);
1806
if (!traverseData.gmd) {
1811
itemFlags = UNITY_START_MENU_ITEM_DIRECTORY; // It's a directory
1812
itemName = g_strdup_printf("%s/%s", UNITY_START_MENU_LAUNCH_FOLDER,
1813
traverseData.gmd->dirname);
1814
freeItemName = TRUE;
1815
localizedItemName = traverseData.gmd->prettyDirname ?
1816
(char *)traverseData.gmd->prettyDirname :
1817
(char *)traverseData.gmd->dirname;
1823
case DIRECTORY_FOLDER:
1827
if (gmh->gmd->items->len <= itemIndex) {
1831
gmi = g_ptr_array_index(gmh->gmd->items, itemIndex);
1833
localizedItemName = g_key_file_get_locale_string(gmi->keyfile,
1834
G_KEY_FILE_DESKTOP_GROUP,
1835
G_KEY_FILE_DESKTOP_KEY_NAME,
1837
freeLocalItemName = TRUE;
1838
itemName = g_strdup_printf("%s/%s/%s", UNITY_START_MENU_LAUNCH_FOLDER,
1839
gmh->gmd->dirname, localizedItemName);
1840
freeItemName = TRUE;
1842
itemPath = GHIPlatformMenuItemToURI(ghip, gmi);
1843
freeItemPath = TRUE;
1848
DynBuf_AppendString(buf, itemName);
1849
Str_Sprintf(temp, sizeof temp, "%u", itemFlags);
1850
DynBuf_AppendString(buf, temp);
1851
DynBuf_AppendString(buf, itemPath ? itemPath : "");
1852
DynBuf_AppendString(buf, localizedItemName ? localizedItemName : itemName);
1860
if (freeLocalItemName) {
1861
g_free(localizedItemName);
1872
*----------------------------------------------------------------------------
1874
* GHIPlatformCloseStartMenu --
1876
* Free all memory associated with this start menu tree and cleanup.
1879
* TRUE if the handle is valid
1885
*----------------------------------------------------------------------------
1889
GHIPlatformCloseStartMenuTree(GHIPlatform *ghip, // IN: platform-specific state
1890
uint32 handle) // IN: handle to the tree to be closed
1896
if (!ghip->menuHandles) {
1900
gmh = g_hash_table_lookup(ghip->menuHandles, GINT_TO_POINTER(handle));
1905
g_hash_table_remove(ghip->menuHandles, GINT_TO_POINTER(gmh->handleID));
1906
GHIPlatformFreeValue(NULL, gmh, NULL);
1915
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1917
*-----------------------------------------------------------------------------
1919
* GHIPlatformFindHGFSShare --
1921
* Finds the filesystem path to a particular HGFS sharename
1924
* Newly heap-allocated path to the top of the specified share.
1927
* Allocates memory for the return value.
1929
*-----------------------------------------------------------------------------
1933
GHIPlatformFindHGFSShare(GHIPlatform *ghip, // IN
1934
const UriTextRangeA *sharename) // IN
1937
struct mntent *ment;
1939
fh = Posix_Setmntent(_PATH_MOUNTED, "r");
1944
while ((ment = Posix_Getmntent(fh))) {
1946
if (strcmp(ment->mnt_type, "hgfs") && strcmp(ment->mnt_type, "vmhgfs")) {
1950
if (!StrUtil_StartsWith(ment->mnt_fsname, ".host:")) {
1951
Warning("HGFS filesystem has an fsname of \"%s\" rather than \".host:...\"\n",
1956
if (ment->mnt_fsname[strlen(".host:")] == '/') {
1957
fsSharename = ment->mnt_fsname + strlen(".host:/");
1959
fsSharename = ment->mnt_fsname + strlen(".host:");
1963
* XXX this function's logic could be improved substantially to do deeper matching
1964
* (e.g. if someone has .host:/foo/bar mounted, but nothing else, and is looking to
1965
* open the document share://foo/bar/baz). Don't know if HGFS allows that, but
1966
* that'd require passing in the whole URI rather than just the sharename.
1968
if (URI_TEXTRANGE_EQUAL(*sharename, fsSharename)) {
1969
char *retval = g_strdup(ment->mnt_dir);
1974
} else if (fsSharename == '\0') {
1976
* This is a mount of the toplevel HGFS directory, so we know it should work.
1978
char *retval = g_strdup_printf("%s/%.*s",
1980
(int)(sharename->afterLast - sharename->first),
1990
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1994
*-----------------------------------------------------------------------------
1996
* GHIPlatformUriPathToString --
1998
* Turns a UriPathSegment sequence into a '/' separated filesystem path.
2001
* Newly heap-allocated string containing the FS path.
2004
* Allocates memory (caller is responsible for freeing it).
2006
*-----------------------------------------------------------------------------
2010
GHIPlatformUriPathToString(UriPathSegmentA *path) // IN
2014
UriPathSegmentA *cur;
2016
str = g_string_new("");
2017
for (cur = path; cur; cur = cur->next) {
2018
g_string_append_c(str, '/');
2019
g_string_append_len(str, cur->text.first, cur->text.afterLast - cur->text.first);
2023
g_string_free(str, FALSE);
2030
*-----------------------------------------------------------------------------
2032
* GHIPlatformURIToArgs --
2034
* Turns a URI into an array of arguments that are useable for execing...
2037
* TRUE if successful, FALSE otherwise.
2040
* Allocates an array of strings, and returns it in *argv...
2042
*-----------------------------------------------------------------------------
2046
GHIPlatformURIToArgs(GHIPlatform *ghip, // IN
2047
const char *uriString, // IN
2048
char ***argv, // IN/OUT
2049
int *argc, // IN/OUT
2050
char **dotDesktopPath) // IN/OUT
2052
UriParserStateA state;
2054
Bool parseQueryString = TRUE;
2061
ASSERT(dotDesktopPath);
2063
memset(&state, 0, sizeof state);
2064
memset(&uri, 0, sizeof uri);
2066
if (uriParseUriA(&state, uriString) != URI_SUCCESS) {
2067
uriFreeUriMembersA(&uri);
2071
newargv = g_ptr_array_new();
2073
#if 0 // Temporary until ShellAction is implemented.
2075
* This is previous code that was used for mapping x-vmware-share and
2076
* x-vmware-action URIs, but it's not being used at the moment.
2078
if (URI_TEXTRANGE_EQUAL(uri.scheme, "x-vmware-share")) {
2079
UriTextRangeA *sharename;
2080
UriPathSegmentA *sharepath;
2085
* Try to find a mounted HGFS filesystem that has the right path...
2086
* Deals with both share://sharename/baz/baz and share:///sharename/baz/baz
2088
if (uri.hostText.first) {
2089
sharename = &uri.hostText;
2090
sharepath = uri.pathHead;
2091
} else if (uri.pathHead) {
2092
sharename = &uri.pathHead->text;
2093
sharepath = uri.pathHead->next;
2098
sharedir = GHIPlatformFindHGFSShare(ghip, sharename);
2100
uriFreeUriMembersA(&uri);
2101
g_ptr_array_free(newargv, TRUE);
2102
Debug("Couldn't find a mounted HGFS filesystem for %s\n", uriString);
2106
subdir = GHIPlatformUriPathToString(sharepath);
2107
g_ptr_array_add(newargv, g_strconcat(sharedir, subdir, NULL));
2110
} else if (URI_TEXTRANGE_EQUAL(uri.scheme, "x-vmware-action")) {
2111
if (g_file_test("/usr/bin/gnome-open", G_FILE_TEST_IS_EXECUTABLE)) {
2112
g_ptr_array_add(newargv, g_strdup("/usr/bin/gnome-open"));
2113
} else if (g_file_test("/usr/bin/htmlview", G_FILE_TEST_IS_EXECUTABLE)
2114
&& URI_TEXTRANGE_EQUAL(uri.hostText, "browse")) {
2115
g_ptr_array_add(newargv, g_strdup("/usr/bin/htmlview"));
2117
Debug("Don't know how to handle URI %s. "
2118
"We definitely don't have /usr/bin/gnome-open.\n",
2123
#endif // Temporary until ShellAction is implemented.
2125
if (URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
2126
char *fspath = GHIPlatformUriPathToString(uri.pathHead);
2127
g_ptr_array_add(newargv, fspath);
2130
* Just append the unparsed URI as-is onto the command line.
2132
g_ptr_array_add(newargv, g_strdup(uriString));
2133
parseQueryString = FALSE;
2136
*dotDesktopPath = NULL;
2137
if (parseQueryString) {
2139
* We may need additional command-line arguments from the part of the URI after the
2143
UriQueryListA *queryList;
2146
if (uriDissectQueryMallocA(&queryList, &itemCount,
2147
uri.query.first, uri.query.afterLast) == URI_SUCCESS) {
2150
for (cur = queryList; cur; cur = cur->next) {
2155
if (strcmp(cur->key, "argv[]") == 0) {
2156
g_ptr_array_add(newargv, g_strdup(cur->value));
2158
} else if (strcmp(cur->key, "DesktopEntry")) {
2159
*dotDesktopPath = g_strdup(cur->value);
2163
uriFreeQueryListA(queryList);
2165
Warning("Dissection of query string in URI %s failed\n",
2170
uriFreeUriMembersA(&uri);
2172
*argc = newargv->len;
2173
g_ptr_array_add(newargv, NULL);
2174
*argv = (char **) g_ptr_array_free(newargv, FALSE);
2180
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2182
*-----------------------------------------------------------------------------
2184
* GHIPlatformStripFieldCodes --
2186
* Strip field codes from an argv-style string array.
2192
* Modifies the string array, possibly freeing some members.
2194
*-----------------------------------------------------------------------------
2198
GHIPlatformStripFieldCodes(char **argv, // IN/OUT
2199
int *argc) // IN/OUT
2206
for (i = 0; i < *argc; i++) {
2207
if (argv[i][0] == '%'
2208
&& argv[i][1] != '\0'
2209
&& argv[i][2] == '\0') {
2212
* This math may look slightly dodgy - just remember that these
2213
* argv's have a terminating NULL pointer, which is not included in its argc.
2215
g_memmove(argv + i, argv + i + 1,
2216
(*argc - i) * sizeof *argv);
2221
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2225
*-----------------------------------------------------------------------------
2227
* GHIPlatformCombineArgs --
2229
* Takes a target URI and turns it into an argv array that we can actually
2232
* XXX TODO: accept location arguments once ShellAction is implemented.
2235
* TRUE if successful, FALSE otherwise. If TRUE, fullArgv/fullArgc will
2236
* contain the exec-able argument array.
2239
* Allocates a string array in fullArgv (owner is responsible for freeing).
2241
*-----------------------------------------------------------------------------
2245
GHIPlatformCombineArgs(GHIPlatform *ghip, // IN
2246
const char *targetUtf8, // IN
2247
char ***fullArgv, // OUT
2248
int *fullArgc) // OUT
2250
char **targetArgv = NULL;
2252
char *targetDotDesktop = NULL;
2253
GPtrArray *fullargs = g_ptr_array_new();
2254
GHIMenuItem *ghm = NULL;
2262
if (!GHIPlatformURIToArgs(ghip,
2266
&targetDotDesktop)) {
2267
Debug("Parsing URI %s failed\n", targetUtf8);
2271
#if 0 // Temporary until ShellAction is implemented.
2273
* This is previous code that was used for combining file and action
2274
* arguments, but it's not being used at the moment. Our action URI format
2275
* has changed, so this will need to be updated before it's usable.
2279
* In the context of the .desktop spec
2280
* (http://standards.freedesktop.org/desktop-entry-spec/1.1/ar01s06.html),
2281
* combining the two is not as simple as just concatenating them.
2283
* XXX for some random older programs, we may want to do concatenation in the future.
2287
char *srcDotDesktop = NULL;
2290
* First, figure out which argv[] array is the 'main' one, and which one will serve
2291
* only to fill in the file/URL argument in the .desktop file...
2293
if (! *actionArgc) {
2294
srcArgv = *fileArgv;
2295
srcArgc = *fileArgc;
2296
srcDotDesktop = fileDotDesktop;
2298
srcArgv = *actionArgv;
2299
srcArgc = *actionArgc;
2300
srcDotDesktop = actionDotDesktop;
2301
if (fileDotDesktop) {
2302
GHIPlatformStripFieldCodes(*fileArgv, fileArgc);
2305
#endif // Temporary until ShellAction is implemented.
2307
for (i = 0; i < targetArgc; i++) {
2308
const char *thisarg = targetArgv[i];
2310
if (thisarg[0] == '%' && thisarg[1] != '\0' && thisarg[2] == '\0') {
2311
switch (thisarg[1]) {
2312
case 'F': // %F expands to multiple filenames
2313
case 'f': // %f expands to a filename
2315
* XXX TODO: add file location arguments
2317
//if (srcArgv != *fileArgv && *fileArgc) {
2318
// g_ptr_array_add(fullargs, g_strdup((*fileArgv)[0]));
2321
case 'U': // %U expands to multiple URLs
2322
case 'u': // %u expands to a URL
2324
* XXX TODO: add URL location arguments
2326
//if (srcArgv != *fileArgv && fileUtf8) {
2327
// g_ptr_array_add(fullargs, g_strdup(fileUtf8));
2332
* These three require getting at the .desktop info for the app.
2337
if (!ghm && targetDotDesktop) {
2338
ghm = g_hash_table_lookup(ghip->appsByDesktopEntry,
2342
ASSERT (fullargs->len > 0);
2343
ghm = g_hash_table_lookup(ghip->appsByExecutable,
2344
g_ptr_array_index(fullargs, 0));
2348
switch (thisarg[1]) {
2349
case 'c': // %c expands to the .desktop's Name=
2352
g_key_file_get_locale_string(ghm->keyfile,
2353
G_KEY_FILE_DESKTOP_GROUP,
2354
G_KEY_FILE_DESKTOP_KEY_NAME,
2357
g_ptr_array_add(fullargs, ctmp);
2361
case 'i': // %i expands to "--icon" then the .desktop's Icon=
2364
g_key_file_get_string(ghm->keyfile,
2365
G_KEY_FILE_DESKTOP_GROUP,
2366
G_KEY_FILE_DESKTOP_KEY_ICON,
2368
if (ctmp && *ctmp) {
2369
g_ptr_array_add(fullargs, g_strdup("--icon"));
2370
g_ptr_array_add(fullargs, ctmp);
2374
case 'k': // %k expands to the .desktop's path
2375
g_ptr_array_add(fullargs, g_strdup(ghm->keyfilePath));
2380
case '%': // Expands to a literal
2381
g_ptr_array_add(fullargs, g_strdup("%"));
2385
* Intentionally ignore an unknown field code.
2390
g_ptr_array_add(fullargs, g_strdup(thisarg));
2393
*fullArgc = fullargs->len;
2394
g_ptr_array_add(fullargs, NULL);
2395
*fullArgv = (char **) g_ptr_array_free(fullargs, FALSE);
2397
g_strfreev(targetArgv);
2398
g_free(targetDotDesktop);
2400
return *fullArgc ? TRUE : FALSE;
2405
*----------------------------------------------------------------------------
2407
* GHIPlatformShellOpen --
2409
* Open the specified file with the default shell handler (ShellExecute).
2410
* Note that the file path may be either a URI (originated with
2411
* Tools >= NNNNN), or a regular path (originated with Tools < NNNNN).
2414
* TRUE if successful, FALSE otherwise.
2419
*----------------------------------------------------------------------------
2423
GHIPlatformShellOpen(GHIPlatform *ghip, // IN
2424
const char *fileUtf8) // IN
2426
char **fullArgv = NULL;
2428
Bool retval = FALSE;
2433
Debug("%s: file: '%s'\n", __FUNCTION__, fileUtf8);
2435
if (GHIPlatformCombineArgs(ghip, fileUtf8, &fullArgv, &fullArgc) &&
2437
retval = g_spawn_async(NULL, fullArgv,
2439
* XXX Please don't hate me for casting off the qualifier
2440
* here. Glib does -not- modify the environment, at
2441
* least not in the parent process, but their prototype
2442
* does not specify this argument as being const.
2444
* Comment stolen from GuestAppX11OpenUrl.
2446
(char **)ghip->nativeEnviron,
2447
G_SPAWN_SEARCH_PATH |
2448
G_SPAWN_STDOUT_TO_DEV_NULL |
2449
G_SPAWN_STDERR_TO_DEV_NULL,
2450
NULL, NULL, NULL, NULL);
2453
g_strfreev(fullArgv);
2460
*----------------------------------------------------------------------------
2462
* GHIPlatformShellAction --
2463
* Perform the specified shell action with the optional target and
2464
* locations arguments. Note that the target may be either a URI
2465
* (originated with Tools >= NNNNN), or a regular path (originated with
2467
* See the comment at ghIntegration.c::GHITcloShellAction for information
2468
* on the command format and supported actions.
2471
* TRUE if successful, FALSE otherwise.
2476
*----------------------------------------------------------------------------
2480
GHIPlatformShellAction(GHIPlatform *ghip, // IN: platform-specific state
2481
const XDR *xdrs) // IN: XDR Serialized arguments
2484
* TODO: implement the shell action execution.
2485
* The GHIPlatformShellUrlOpen() below is left for reference, but is not
2486
* used right now. Its functionality should be integrated here.
2491
Debug("%s not implemented yet.\n", __FUNCTION__);
2497
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2499
*----------------------------------------------------------------------------
2501
* GHIPlatformShellUrlOpen --
2503
* Run ShellExecute on a given file.
2506
* TRUE if success, FALSE otherwise.
2511
*----------------------------------------------------------------------------
2515
GHIPlatformShellUrlOpen(GHIPlatform *ghip, // IN: platform-specific state
2516
const char *fileUtf8, // IN: command/file
2517
const char *actionUtf8) // IN: action
2520
char **fileArgv = NULL;
2522
char *fileDotDesktop = NULL;
2523
char **actionArgv = NULL;
2525
char *actionDotDesktop = NULL;
2526
char **fullArgv = NULL;
2529
Bool retval = FALSE;
2533
if (!GHIPlatformURIToArgs(ghip, fileUtf8, &fileArgv, &fileArgc,
2535
Debug("Parsing URI %s failed\n", fileUtf8);
2539
if (actionUtf8 && !GHIPlatformURIToArgs(ghip, actionUtf8, &actionArgv, &actionArgc,
2540
&actionDotDesktop)) {
2541
Debug("Parsing action URI %s failed\n", actionUtf8);
2542
g_strfreev(fileArgv);
2543
g_free(fileDotDesktop);
2547
if (GHIPlatformCombineArgs(ghip,
2548
fileUtf8, &fileArgv, &fileArgc, fileDotDesktop,
2549
actionUtf8, &actionArgv, &actionArgc, actionDotDesktop,
2550
&fullArgv, &fullArgc)) {
2551
retval = g_spawn_async(NULL, fullArgv, NULL,
2552
G_SPAWN_SEARCH_PATH |
2553
G_SPAWN_STDOUT_TO_DEV_NULL |
2554
G_SPAWN_STDERR_TO_DEV_NULL,
2555
NULL, NULL, NULL, NULL);
2558
g_strfreev(fileArgv);
2559
g_free(fileDotDesktop);
2560
g_strfreev(actionArgv);
2561
g_free(actionDotDesktop);
2562
g_strfreev(fullArgv);
2569
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2573
*----------------------------------------------------------------------------
2575
* GHIPlatformSetGuestHandler --
2577
* Set the handler for the specified filetype (or URL protocol) to the
2581
* TRUE if successful, FALSE otherwise.
2586
*----------------------------------------------------------------------------
2590
GHIPlatformSetGuestHandler(GHIPlatform *ghip, // IN: platform-specific state
2591
const XDR *xdrs) // IN: XDR Serialized arguments
2601
*----------------------------------------------------------------------------
2603
* GHIPlatformRestoreDefaultGuestHandler --
2605
* Restore the handler for a given type to the value in use before any
2609
* TRUE if successful, FALSE otherwise.
2614
*----------------------------------------------------------------------------
2618
GHIPlatformRestoreDefaultGuestHandler(GHIPlatform *ghip, // IN: platform-specific state
2619
const XDR *xdrs) // IN: XDR Serialized arguments
2629
*-----------------------------------------------------------------------------
2631
* GHIPlatformSetMenuTracking --
2633
* Turns menu tracking on/off.
2635
* XXX needs additional implementation work, as per the comment above
2636
* GHIDirectoryWatch.
2644
*-----------------------------------------------------------------------------
2648
GHIPlatformSetMenuTracking(GHIPlatform *ghip, // IN
2649
Bool isEnabled) // IN
2654
if (isEnabled == ghip->trackingEnabled) {
2658
ghip->trackingEnabled = isEnabled;
2660
GHIPlatformReadAllApplications(ghip);
2662
GHIPlatformCleanupMenuEntries(ghip);
2664
for (i = 0; i < ghip->directoriesTracked->len; i++) {
2665
GHIDirectoryWatch *dirWatch;
2667
dirWatch = &g_array_index(ghip->directoriesTracked, GHIDirectoryWatch, i);
2668
g_free(dirWatch->directoryPath);
2670
g_array_set_size(ghip->directoriesTracked, 0);
2676
*------------------------------------------------------------------------------
2678
* GHIPlatformGetProtocolHandlers --
2680
* XXX Needs to be implemented for Linux/X11 guests.
2681
* Retrieve the list of protocol handlers from the guest.
2690
*------------------------------------------------------------------------------
2694
GHIPlatformGetProtocolHandlers(GHIPlatform *ghip, // UNUSED
2695
GHIProtocolHandlerList *protocolHandlerList) // IN
2702
*----------------------------------------------------------------------------
2704
* GHIPlatformSetOutlookTempFolder --
2706
* Set the temporary folder used by Microsoft Outlook to store attachments
2707
* opened by the user.
2709
* XXX While we probably won't ever need to implement this for Linux, we
2710
* still the definition of this function in the X11 back-end.
2713
* TRUE if successful, FALSE otherwise.
2718
*----------------------------------------------------------------------------
2722
GHIPlatformSetOutlookTempFolder(GHIPlatform *ghip, // IN: platform-specific state
2723
const XDR *xdrs) // IN: XDR Serialized arguments
2733
*----------------------------------------------------------------------------
2735
* GHIPlatformRestoreOutlookTempFolder --
2737
* Set the temporary folder used by Microsoft Outlook to store attachments
2738
* opened by the user.
2741
* TRUE if successful, FALSE otherwise.
2746
*----------------------------------------------------------------------------
2750
GHIPlatformRestoreOutlookTempFolder(GHIPlatform *ghip) // IN: platform-specific state
2759
* @brief Performs an action on the Trash (aka Recycle Bin) folder.
2761
* Performs an action on the Trash (aka Recycle Bin) folder. Currently, the
2762
* only supported actions are to open the folder, or empty it.
2764
* @param[in] ghip Pointer to platform-specific GHI data.
2765
* @param[in] xdrs Pointer to XDR serialized arguments.
2767
* @retval TRUE The action was performed.
2768
* @retval FALSE The action couldn't be performed.
2772
GHIPlatformTrashFolderAction(GHIPlatform *ghip,
2781
/* @brief Returns the icon of the Trash (aka Recycle Bin) folder.
2783
* Gets the icon of the Trash (aka Recycle Bin) folder, and returns it
2786
* @param[in] ghip Pointer to platform-specific GHI data.
2787
* @param[out] xdrs Pointer to XDR serialized data to send to the host.
2789
* @retval TRUE The icon was fetched successfully.
2790
* @retval FALSE The icon could not be fetched.
2794
GHIPlatformTrashFolderGetIcon(GHIPlatform *ghip,
2802
/* @brief Send a mouse or keyboard event to a tray icon.
2804
* @param[in] ghip Pointer to platform-specific GHI data.
2806
* @retval TRUE Operation Succeeded.
2807
* @retval FALSE Operation Failed.
2811
GHIPlatformTrayIconSendEvent(GHIPlatform *ghip,
2819
/* @brief Start sending tray icon updates to the VMX.
2821
* @param[in] ghip Pointer to platform-specific GHI data.
2823
* @retval TRUE Operation Succeeded.
2824
* @retval FALSE Operation Failed.
2828
GHIPlatformTrayIconStartUpdates(GHIPlatform *ghip)
2834
/* @brief Stop sending tray icon updates to the VMX.
2836
* @param[in] ghip Pointer to platform-specific GHI data.
2838
* @retval TRUE Operation Succeeded.
2839
* @retval FALSE Operation Failed.
2843
GHIPlatformTrayIconStopUpdates(GHIPlatform *ghip)
2849
/* @brief Set a window to be focused.
2851
* @param[in] ghip Pointer to platform-specific GHI data.
2852
* @param[in] xdrs Pointer to serialized data from the host.
2854
* @retval TRUE Operation Succeeded.
2855
* @retval FALSE Operation Failed.
2859
GHIPlatformSetFocusedWindow(GHIPlatform *ghip,
2869
* @brief Get the hash (or timestamp) of information returned by
2870
* GHIPlatformGetBinaryInfo.
2872
* @param[in] ghip Pointer to platform-specific GHI data.
2873
* @param[in] request Request containing which executable to get the hash for.
2874
* @param[out] reply Reply to be filled with the hash.
2876
* @retval TRUE Operation succeeded.
2877
* @retval FALSE Operation failed.
2880
Bool GHIPlatformGetExecInfoHash(GHIPlatform *ghip,
2881
const GHIGetExecInfoHashRequest *request,
2882
GHIGetExecInfoHashReply *reply)
2893
******************************************************************************
2894
* GHIX11FindDesktopUriByExec -- */ /**
2896
* Given an executable path, attempt to generate an "execUri" associated with a
2897
* corresponding .desktop file.
2899
* @sa GHIX11_FindDesktopUriByExec
2901
* @note Returned pointer belongs to the GHI module. Caller must not free it.
2903
* @param[in] ghip GHI platform-specific context.
2904
* @param[in] execPath Input binary path. May be absolute or relative.
2906
* @return Pointer to a URI string on success, NULL on failure.
2908
******************************************************************************
2912
GHIX11FindDesktopUriByExec(GHIPlatform *ghip,
2915
char pathbuf[MAXPATHLEN];
2916
gchar *pathname = NULL;
2919
gboolean fudged = FALSE;
2920
gboolean basenamed = FALSE;
2926
* Check our hash table first. Negative entries are also cached.
2928
if (g_hash_table_lookup_extended(ghip->appsByWindowExecutable,
2929
exec, NULL, (gpointer*)&uri)) {
2934
* Okay, execPath may be absolute or relative.
2936
* We'll search for a matching .desktop entry using the following methods:
2938
* 1. Use absolute path of exec.
2939
* 2. Use absolute path of basename of exec. (Resolves /opt/Adobe/Reader9/
2940
* Reader/intellinux/bin/acroread to /usr/bin/acroread.)
2941
* 3. Consult whitelist of known applications and guess at possible
2942
* launchers. (firefox-bin => firefox, soffice.bin => ooffice.)
2946
* Attempt #1: Start with unmodified input.
2948
Str_Strcpy(pathbuf, exec, sizeof pathbuf);
2951
g_free(pathname); // Placed here rather than at each goto. I'm lazy.
2953
pathname = g_find_program_in_path(pathbuf);
2955
gmi = (GHIMenuItem*)g_hash_table_lookup(ghip->appsByExecutable, pathname);
2957
uri = GHIPlatformMenuItemToURI(ghip, gmi);
2963
* Attempt #2: Take the basename of exec.
2966
char tmpbuf[MAXPATHLEN];
2971
/* basename(3) may modify the input buffer, so make a temporary copy. */
2972
Str_Strcpy(tmpbuf, pathbuf, sizeof tmpbuf);
2973
ctmp = basename(tmpbuf);
2975
Str_Strcpy(pathbuf, ctmp, sizeof pathbuf);
2981
* Attempt #3: Get our whitelist on.
2985
const gchar *pattern;
2987
} fudgePatterns[] = {
2989
* XXX Worth compiling once? Consider placing in an external filter
2990
* file to allow users to update it themselves easily.
2992
{ "*firefox*-bin", "firefox" },
2993
{ "*thunderbird*-bin", "thunderbird" },
2994
{ "*soffice.bin", "ooffice" }
3000
for (i = 0; i < ARRAYSIZE(fudgePatterns); i++) {
3001
if (g_pattern_match_simple(fudgePatterns[i].pattern,
3003
Str_Strcpy(pathbuf, fudgePatterns[i].exec, sizeof pathbuf);
3013
* Cache the result, even if it was negative.
3015
g_hash_table_insert(ghip->appsByWindowExecutable, g_strdup(exec), uri);