1
/*********************************************************
2
* Copyright (C) 2008-2010 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
*********************************************************/
21
* ghIntegrationX11.c --
23
* Guest-host integration implementation for POSIX-compliant platforms that run X11.
25
* The main tasks done by this code are reading in the system's .desktop files to turn
26
* them into an internal representation of available applications on the system
27
* (implemented by GHIPlatformReadAllApplications, GHIPlatformReadApplicationsDir,
28
* GHIPlatformReadDesktopFile, and kin), and feeding portions of that internal
29
* representation to the host upon request
30
* (GHIPlatform{OpenStartMenuTree,GetStartMenuItem,CloseStartMenuTree}).
36
#include <sys/param.h>
37
#include <sys/types.h>
44
#error "Gtk 2.0 is required"
52
#include <gdk-pixbuf/gdk-pixbuf-core.h>
53
#include <sigc++/sigc++.h>
55
// gdkx.h includes Xlib.h, which #defines Bool.
59
#include <gio/gdesktopappinfo.h>
63
#include "vmware/tools/guestrpc.h"
65
#include "dbllnklst.h"
70
#include "unityCommon.h"
73
#include "imageUtil.h"
77
#include "vm_atomic.h"
79
#include "guest_msg_def.h"
84
#define URI_TEXTRANGE_EQUAL(textrange, str) \
85
(((textrange).afterLast - (textrange).first) == (ssize_t) strlen((str)) \
86
&& !strncmp((textrange).first, (str), (textrange).afterLast - (textrange).first))
89
#include "ghIntegration.h"
90
#include "ghIntegrationInt.h"
91
#include "ghiX11icon.h"
94
# include "vmware/tools/ghi/menuItemManager.hh"
95
using vmware::tools::ghi::MenuItemManager;
96
using vmware::tools::ghi::MenuItem;
99
#include "vmware/tools/ghi/pseudoAppMgr.hh"
100
using vmware::tools::ghi::PseudoAppMgr;
101
using vmware::tools::ghi::PseudoApp;
104
using vmware::tools::NotifyIconCallback;
107
* These describe possible start menu item flags. It should come from ghiCommon.h
110
#define UNITY_START_MENU_ITEM_DIRECTORY (1 << 0)
113
* This macro provides an estimate of how much space an icon might take beyond the actual
114
* icon data when returned from unity.get.binary.info. This makes space for the
115
* width/height/size strings, and adds enough padding to give some breathing room just in
118
* > This is only an estimate. <
120
#define ICON_SPACE_PADDING (sizeof "999x999x65535x" + 25)
124
* GHI/X11 context object
127
struct _GHIPlatform {
128
GTree *apps; // Tree of GHIMenuDirectory's, keyed & ordered by their dirname
129
GHashTable *appsByExecutable; // Translates full executable path to GHIMenuItem
130
GHashTable *appsByDesktopEntry; // Translates full .desktop path to GHIMenuItem
132
* Translates arbitrary executable paths as discovered through
133
* UnityPlatformGetWindowPaths to a .desktop-ful executable URI.
136
* (key) /usr/lib/firefox-3.6.3/firefox-bin (via Firefox window's _NET_WM_PID)
137
* (value) file:///usr/bin/firefox?DesktopEntry=/usr/share/applications/firefox.desktop
139
GHashTable *appsByWindowExecutable;
141
/* Pre-wrapper script environment. See @ref System_GetNativeEnviron. */
142
std::vector<Glib::ustring> nativeEnviron;
144
/* Callbacks to send data (RPCs) to the host */
145
GHIHostCallbacks hostCallbacks;
148
/* Launch menu item layout generator thing. */
149
MenuItemManager *menuItemManager;
154
* The GHIMenuItem object represents an individual leaf-node menu item (corresponding to
158
char *exepath; // The full exe path for use in GHIPlatform::appsByExecutable
159
char *keyfilePath; // Key to GHIPlatform::appsByDesktopEntry, used in %k field code
160
GKeyFile *keyfile; // glib data structure representing the parsed .desktop file
164
* Represents a "start menu folder" so to speak.
167
const char *dirname; // The .desktop category that this object represents
168
const char *prettyDirname; // (optional) A prettier version of dirname.
169
GPtrArray *items; // Array of pointers to GHIMenuItems
173
* Represents an active handle for traversing a menu.
177
enum { LAUNCH_FOLDER, FIXED_FOLDER, DIRECTORY_FOLDER } handleType;
178
GHIMenuDirectory *gmd; // Only set for DIRECTORY_FOLDER handles
182
* This is used to help us find the Nth GHIMenuDirectory node in the GHIPlatform::apps
183
* tree, an operation that is needed as part of GHIPlatformGetStartMenuItem...
188
GHIMenuDirectory *gmd; // OUT - pointer to the Nth GHIMenuDirectory
191
static char *GHIPlatformUriPathToString(UriPathSegmentA *path);
195
* GHI capabilities for this platform.
198
* XXX TODO: re-enable once ShellAction is implemented.
201
static GuestCapabilities platformGHICaps[] = {
202
GHI_CAP_CMD_SHELL_ACTION,
203
GHI_CAP_SHELL_ACTION_BROWSE,
204
GHI_CAP_SHELL_ACTION_RUN,
205
GHI_CAP_SHELL_LOCATION_HGFS
209
#if !defined(OPEN_VM_TOOLS)
211
* An empty file type list - a reference to this can be returned by
212
* GHIPlatformGetBinaryHandlers() in some circumstances.
214
static FileTypeList sEmptyFileTypeList;
215
#endif // OPEN_VM_TOOLS
218
static bool AppInfoLaunchEnv(GHIPlatform* ghip, GAppInfo* appInfo);
219
static void OnMenusChanged(GHIPlatform* ghip);
223
*----------------------------------------------------------------------------
225
* GHIPlatformIsSupported --
227
* Determine whether this guest supports guest host integration.
230
* TRUE if the guest supports GHI
236
*----------------------------------------------------------------------------
240
GHIPlatformIsSupported(void)
242
const char *desktopEnv = Xdg_DetectDesktopEnv();
243
Bool supported = (g_strcmp0(desktopEnv, "GNOME") == 0) ||
244
(g_strcmp0(desktopEnv, "KDE") == 0);
246
g_message("GHI not available under unsupported desktop environment %s\n",
247
desktopEnv ? desktopEnv : "(nil)");
254
*----------------------------------------------------------------------------
258
* Sets up the platform-specific GHI state.
261
* Pointer to platform-specific data (may be NULL).
266
*----------------------------------------------------------------------------
270
GHIPlatformInit(GMainLoop *mainLoop, // IN
271
const char **envp, // IN
272
GHIHostCallbacks hostCallbacks) // IN
275
const char *desktopEnv;
277
Gtk::Main::init_gtkmm_internals();
279
if (!GHIPlatformIsSupported()) {
281
* Don't bother allocating resources if running under an unsupported
282
* desktop environment.
287
ghip = (GHIPlatform *) Util_SafeCalloc(1, sizeof *ghip);
288
ghip->appsByWindowExecutable =
289
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
290
ghip->hostCallbacks = hostCallbacks;
294
for (tmp = envp; *tmp; tmp++) {
295
ghip->nativeEnviron.push_back(*tmp);
298
desktopEnv = Xdg_DetectDesktopEnv();
299
ASSERT(desktopEnv); // Asserting based on GHIPlatformIsSupported check above.
300
g_desktop_app_info_set_desktop_env(desktopEnv);
303
ghip->menuItemManager = new MenuItemManager(desktopEnv);
304
sigc::slot<void,GHIPlatform*> menuSlot = sigc::ptr_fun(&OnMenusChanged);
305
ghip->menuItemManager->menusChanged.connect(sigc::bind(menuSlot, ghip));
306
OnMenusChanged(ghip);
314
*----------------------------------------------------------------------------
316
* GHIPlatformRegisterCaps --
318
* Register guest platform specific capabilities with the VMX.
326
*----------------------------------------------------------------------------
330
GHIPlatformRegisterCaps(GHIPlatform *ghip) // IN
333
//ASSERT(platformGHICaps);
336
* XXX TODO: re-enable once ShellAction is implemented.
338
//AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), TRUE);
343
*----------------------------------------------------------------------------
345
* GHIPlatformUnregisterCaps --
347
* Register guest platform specific capabilities with the VMX.
355
*----------------------------------------------------------------------------
359
GHIPlatformUnregisterCaps(GHIPlatform *ghip) // IN
362
//ASSERT(platformGHICaps);
365
* XXX TODO: re-enable once ShellAction is implemented.
367
//AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), FALSE);
372
*----------------------------------------------------------------------------
374
* GHIPlatformCleanup --
376
* Tears down the platform-specific GHI state.
382
* GHIPlatform is no longer valid.
384
*----------------------------------------------------------------------------
388
GHIPlatformCleanup(GHIPlatform *ghip) // IN
395
delete ghip->menuItemManager;
397
g_hash_table_destroy(ghip->appsByWindowExecutable);
403
*-----------------------------------------------------------------------------
405
* GHIPlatformRegisterNotifyIconCallback / GHIPlatformUnregisterNotifyIconCallback --
407
* Register/Unregister the NotifyIcon Callback object. Since notification icons
408
* (aka Tray icons) are unsupported on Linux guests this function does nothing.
414
* Adds data into the DynBuf.
416
*-----------------------------------------------------------------------------
420
GHIPlatformRegisterNotifyIconCallback(NotifyIconCallback *notifyIconCallback) // IN
424
void GHIPlatformUnregisterNotifyIconCallback(NotifyIconCallback *notifyIconCallback) // IN
430
*----------------------------------------------------------------------------
432
* GHIPlatformGetBinaryInfo --
434
* Get binary information (app name and icons). We're passed app info in
435
* pathURIUtf8 (in URI format), and we find the app info by looking up the
436
* path in GHIPlatform->appsByExecutable. Once we find it, we can retrieve
437
* info on the app from the .desktop file.
440
* TRUE if everything went ok, FALSE otherwise.
445
*----------------------------------------------------------------------------
449
GHIPlatformGetBinaryInfo(GHIPlatform *ghip, // IN: platform-specific state
450
const char *pathURIUtf8, // IN: full path to the binary file
451
std::string &friendlyName, // OUT: Friendly name
452
std::list<GHIBinaryIconInfo> &iconList) // OUT: Icons
454
const char *realCmd = NULL;
456
char *keyfilePath = NULL;
457
unsigned long windowID = 0;
458
gpointer freeMe = NULL;
460
UriParserStateA state;
466
memset(&state, 0, sizeof state);
467
memset(&uri, 0, sizeof uri);
470
/* Strip query component. */
471
size_t uriSize = strlen(pathURIUtf8) + 1;
472
gchar *uriSansQuery = (gchar*)g_alloca(uriSize);
473
memcpy(uriSansQuery, pathURIUtf8, uriSize);
474
gchar *tmp = strchr(uriSansQuery, '?');
475
if (tmp) { *tmp = '\0'; }
477
if (uriSansQuery[0] == '/') {
478
realCmd = uriSansQuery;
479
} else if (uriParseUriA(&state, uriSansQuery) == URI_SUCCESS) {
480
if (URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
481
gchar* tmp = (gchar*)g_alloca(strlen(uriSansQuery) + 1);
482
uriUriStringToUnixFilenameA(uriSansQuery, tmp);
484
Glib::ustring unixFile;
485
unixFile.assign(tmp);
487
Glib::ustring contentType;
489
contentType = Gio::content_type_guess(unixFile, std::string(""),
492
Bool success = FALSE;
497
* H'okay. So we're looking up icons, yeah?
499
* 1. If given a URI for an XDG desktop entry file, search for an icon based
501
* 2. If given a pseudo app URI, as identified by appMgr, use the special
502
* icon associated with said pseudo app.
503
* 3. If given a folder, try going with "folder" (per icon-naming-spec).
504
* 4. Else fall back to searching our theme for an icon based on MIME/
508
if (g_str_has_suffix(unixFile.c_str(), ".desktop")) {
509
Glib::RefPtr<Gio::DesktopAppInfo> desktopFileInfo;
511
desktopFileInfo = Gio::DesktopAppInfo::create_from_filename(unixFile);
512
if (desktopFileInfo) {
513
friendlyName = desktopFileInfo->get_name();
514
GHIX11IconGetIconsForDesktopFile(unixFile.c_str(), iconList);
517
} else if (appMgr.GetAppByUri(uriSansQuery, app)) {
518
friendlyName = app.symbolicName;
519
GHIX11IconGetIconsByName(app.iconName.c_str(), iconList);
521
} else if (Glib::file_test(unixFile, Glib::FILE_TEST_IS_DIR)) {
522
friendlyName = Glib::filename_display_basename(unixFile);
523
GHIX11IconGetIconsByName("folder", iconList);
526
friendlyName = Glib::filename_display_basename(unixFile);
528
while ((i = contentType.find('/', i)) != contentType.npos) {
529
contentType.replace(i, 1, "-");
531
GHIX11IconGetIconsByName(contentType.c_str(), iconList);
535
uriFreeUriMembersA(&uri);
538
UriQueryListA *queryList = NULL;
541
freeMe = GHIPlatformUriPathToString(uri.pathHead);
542
realCmd = (const char *) freeMe;
543
if (g_str_has_suffix(realCmd, ".desktop") &&
544
(desktopFileInfo = g_desktop_app_info_new_from_filename(realCmd))
548
friendlyName = g_app_info_get_name(G_APP_INFO(desktopFileInfo));
549
g_object_unref(desktopFileInfo);
551
success = GHIX11IconGetIconsForDesktopFile(realCmd, iconList);
552
uriFreeUriMembersA(&uri);
554
} else if (uriDissectQueryMallocA(&queryList, &itemCount,
556
uri.query.afterLast) == URI_SUCCESS) {
559
for (cur = queryList; cur; cur = cur->next) {
564
if (strcmp(cur->key, "WindowXID") == 0) {
565
sscanf(cur->value, "%lu", &windowID); // Ignore any failures
566
} else if (strcmp(cur->key, "DesktopEntry") == 0) {
567
keyfilePath = g_strdup(cur->value);
571
uriFreeQueryListA(queryList);
575
uriFreeUriMembersA(&uri);
576
Debug("Binary URI %s does not have a 'file' scheme\n", pathURIUtf8);
580
uriFreeUriMembersA(&uri);
588
* If for some reason the command we got wasn't a fullly expanded filesystem path,
589
* then expand the command into a full path.
591
if (realCmd[0] != '/') {
592
ctmp = g_find_program_in_path(realCmd);
605
ghm = (GHIMenuItem *) g_hash_table_lookup(ghip->appsByDesktopEntry, keyfilePath);
611
* Now that we have the full path, look it up in our hash table of GHIMenuItems
613
ghm = (GHIMenuItem *) g_hash_table_lookup(ghip->appsByExecutable, realCmd);
618
* To deal with /usr/bin/gimp being a symlink to gimp-2.x, also try symlinks.
620
char newPath[PATH_MAX + 1];
623
linkLen = readlink(realCmd, newPath, sizeof newPath - 1);
627
newPath[linkLen] = '\0';
628
slashLoc = strrchr(realCmd, '/');
629
if (newPath[0] != '/' && slashLoc) {
630
ctmp = g_strdup_printf("%.*s%s",
631
(int)((slashLoc + 1) - realCmd),
635
realCmd = (const char *) freeMe;
640
ghm = (GHIMenuItem *) g_hash_table_lookup(ghip->appsByExecutable, realCmd);
644
* Stick the app name into 'friendlyName'.
647
ctmp = g_key_file_get_locale_string(ghm->keyfile, G_KEY_FILE_DESKTOP_GROUP,
648
G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
650
ctmp = g_path_get_basename(realCmd);
656
* If we can't find it, then just tell the host that the app name is the same as
657
* the basename of the application's path.
659
ctmp = strrchr(realCmd, '/');
663
ctmp = (char *) realCmd;
672
GHIPlatformCollectIconInfo(ghip, ghm, windowID, iconList);
679
#if !defined(OPEN_VM_TOOLS)
681
*----------------------------------------------------------------------------
683
* GHIPlatformGetBinaryHandlers --
685
* Get the list of filetypes and URL protocols supported by a binary
686
* (application). We're passed an app path in URI format, and we find
687
* the app info by looking up the path in GHIPlatform->appsByExecutable.
688
* Once we find it, we can retrieve info on the app from the .desktop file.
691
* A Filetype list of the handlers.
696
*----------------------------------------------------------------------------
700
GHIPlatformGetBinaryHandlers(GHIPlatform *ghip, // IN: platform-specific state
701
const char *pathUtf8) // IN: full path to the executable
703
return sEmptyFileTypeList;
705
#endif // OPEN_VM_TOOLS
709
*----------------------------------------------------------------------------
711
* GHIPlatformOpenStartMenuTree --
713
* Get start menu item count for a given root. This function should be
714
* called before iterating through the menu item subtree.
715
* To start at the root of the start menu, pass in "" for the root.
717
* The output 'buf' is a string holding two numbers separated by a space:
718
* 1. A handle ID for this menu tree iterator.
719
* 2. A count of the items in this iterator.
722
* TRUE if we were able to get the count successfully
728
*----------------------------------------------------------------------------
732
GHIPlatformOpenStartMenuTree(GHIPlatform *ghip, // IN: platform-specific state
733
const char *rootUtf8, // IN: root of the tree
734
uint32 flags, // IN: flags
735
DynBuf *buf) // OUT: number of items
737
Bool success = FALSE;
740
std::pair<uint32,uint32> descriptor;
741
if (ghip->menuItemManager->OpenMenuTree(rootUtf8, &descriptor)) {
742
char tmp[2 * sizeof MAKESTR(UINT_MAX)];
743
Str_Sprintf(tmp, sizeof tmp, "%u %u", descriptor.first, descriptor.second);
744
DynBuf_AppendString(buf, tmp);
754
*-----------------------------------------------------------------------------
756
* GHIPlatformMenuItemToURI --
758
* Returns the URI that would be used to launch a particular GHI menu item
761
* Newly allocated URI string
764
* Allocates memory for the URI.
766
*-----------------------------------------------------------------------------
770
GHIPlatformMenuItemToURI(GHIPlatform *ghip, // IN
771
GHIMenuItem *gmi) // IN
777
UriQueryListA *queryItems;
788
ctmp = g_key_file_get_string(gmi->keyfile, G_KEY_FILE_DESKTOP_GROUP,
789
G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
791
res = g_shell_parse_argv(ctmp, &argc, &argv, NULL);
797
queryItems = (UriQueryListA *) alloca((argc + 1) * sizeof *queryItems);
799
for (i = 0; i < (argc - 1); i++) {
800
queryItems[i].key = "argv[]";
801
queryItems[i].value = argv[i + 1];
802
queryItems[i].next = &queryItems[i + 1];
804
queryItems[i].key = "DesktopEntry";
805
queryItems[i].value = gmi->keyfilePath;
806
queryItems[i].next = NULL;
809
* 10 + 3 * len is the formula recommended by uriparser for the maximum URI string
812
uriString = (char *) alloca(10 + 3 * strlen(gmi->exepath));
813
if (uriUnixFilenameToUriStringA(gmi->exepath, uriString)) {
817
if (uriComposeQueryCharsRequiredA(queryItems, &nchars) != URI_SUCCESS) {
821
queryString = (char *) alloca(nchars + 1);
822
err = uriComposeQueryA(queryString, queryItems, nchars + 1, &i);
824
if (err != URI_SUCCESS) {
828
return g_strdup_printf("%s?%s", uriString, queryString);
833
*----------------------------------------------------------------------------
835
* GHIPlatformGetStartMenuItem --
837
* Get start menu item at a given index. This function should be called
838
* in the loop to get all items for a menu sub-tree.
839
* If there are no more items, the function will return FALSE.
841
* Upon returning, 'buf' will hold a nul-delimited array of strings:
842
* 1. User-visible item name.
843
* 2. UNITY_START_MENU_ITEM_* flag.
844
* 3. Executable path.
845
* 4. Localized user-visible item name.
848
* TRUE if there's an item at a given index, FALSE otherwise.
853
*----------------------------------------------------------------------------
857
GHIPlatformGetStartMenuItem(GHIPlatform *ghip, // IN: platform-specific state
858
uint32 handle, // IN: tree handle
859
uint32 itemIndex, // IN: the index of the item in the tree
860
DynBuf *buf) // OUT: item
862
Bool success = FALSE;
865
const MenuItem* menuItem;
866
const Glib::ustring* path;
868
if (ghip->menuItemManager->GetMenuItem(handle, itemIndex, &menuItem, &path)) {
869
Glib::ustring key = *path + "/" + menuItem->key;
870
DynBuf_AppendString(buf, key.c_str());
872
char tmp[sizeof MAKESTR(UINT_MAX)];
873
Str_Sprintf(tmp, sizeof tmp, "%u", menuItem->isFolder ? 1 : 0);
874
DynBuf_AppendString(buf, tmp);
876
DynBuf_AppendString(buf, menuItem->execPath.c_str());
877
DynBuf_AppendString(buf, menuItem->displayName.c_str());
887
*----------------------------------------------------------------------------
889
* GHIPlatformCloseStartMenu --
891
* Free all memory associated with this start menu tree and cleanup.
894
* TRUE if the handle is valid
900
*----------------------------------------------------------------------------
904
GHIPlatformCloseStartMenuTree(GHIPlatform *ghip, // IN: platform-specific state
905
uint32 handle) // IN: handle to the tree to be closed
908
return ghip->menuItemManager->CloseMenuTree(handle);
915
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
917
*-----------------------------------------------------------------------------
919
* GHIPlatformFindHGFSShare --
921
* Finds the filesystem path to a particular HGFS sharename
924
* Newly heap-allocated path to the top of the specified share.
927
* Allocates memory for the return value.
929
*-----------------------------------------------------------------------------
933
GHIPlatformFindHGFSShare(GHIPlatform *ghip, // IN
934
const UriTextRangeA *sharename) // IN
939
fh = Posix_Setmntent(_PATH_MOUNTED, "r");
944
while ((ment = Posix_Getmntent(fh))) {
946
if (strcmp(ment->mnt_type, "hgfs") && strcmp(ment->mnt_type, "vmhgfs")) {
950
if (!StrUtil_StartsWith(ment->mnt_fsname, ".host:")) {
951
Warning("HGFS filesystem has an fsname of \"%s\" rather than \".host:...\"\n",
956
if (ment->mnt_fsname[strlen(".host:")] == '/') {
957
fsSharename = ment->mnt_fsname + strlen(".host:/");
959
fsSharename = ment->mnt_fsname + strlen(".host:");
963
* XXX this function's logic could be improved substantially to do deeper matching
964
* (e.g. if someone has .host:/foo/bar mounted, but nothing else, and is looking to
965
* open the document share://foo/bar/baz). Don't know if HGFS allows that, but
966
* that'd require passing in the whole URI rather than just the sharename.
968
if (URI_TEXTRANGE_EQUAL(*sharename, fsSharename)) {
969
char *retval = g_strdup(ment->mnt_dir);
974
} else if (fsSharename == '\0') {
976
* This is a mount of the toplevel HGFS directory, so we know it should work.
978
char *retval = g_strdup_printf("%s/%.*s",
980
(int)(sharename->afterLast - sharename->first),
990
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
994
*-----------------------------------------------------------------------------
996
* GHIPlatformUriPathToString --
998
* Turns a UriPathSegment sequence into a '/' separated filesystem path.
1001
* Newly heap-allocated string containing the FS path.
1004
* Allocates memory (caller is responsible for freeing it).
1006
*-----------------------------------------------------------------------------
1010
GHIPlatformUriPathToString(UriPathSegmentA *path) // IN
1014
UriPathSegmentA *cur;
1016
str = g_string_new("");
1017
for (cur = path; cur; cur = cur->next) {
1018
g_string_append_c(str, '/');
1019
g_string_append_len(str, cur->text.first, cur->text.afterLast - cur->text.first);
1023
g_string_free(str, FALSE);
1030
*-----------------------------------------------------------------------------
1032
* GHIPlatformURIToArgs --
1034
* Turns a URI into an array of arguments that are useable for execing...
1037
* TRUE if successful, FALSE otherwise.
1040
* Allocates an array of strings, and returns it in *argv...
1042
*-----------------------------------------------------------------------------
1046
GHIPlatformURIToArgs(GHIPlatform *ghip, // IN
1047
const char *uriString, // IN
1048
char ***argv, // IN/OUT
1049
int *argc, // IN/OUT
1050
char **dotDesktopPath) // IN/OUT
1052
UriParserStateA state;
1054
Bool parseQueryString = TRUE;
1061
ASSERT(dotDesktopPath);
1063
memset(&state, 0, sizeof state);
1064
memset(&uri, 0, sizeof uri);
1066
if (uriParseUriA(&state, uriString) != URI_SUCCESS) {
1067
uriFreeUriMembersA(&uri);
1071
newargv = g_ptr_array_new();
1073
#if 0 // Temporary until ShellAction is implemented.
1075
* This is previous code that was used for mapping x-vmware-share and
1076
* x-vmware-action URIs, but it's not being used at the moment.
1078
if (URI_TEXTRANGE_EQUAL(uri.scheme, "x-vmware-share")) {
1079
UriTextRangeA *sharename;
1080
UriPathSegmentA *sharepath;
1085
* Try to find a mounted HGFS filesystem that has the right path...
1086
* Deals with both share://sharename/baz/baz and share:///sharename/baz/baz
1088
if (uri.hostText.first) {
1089
sharename = &uri.hostText;
1090
sharepath = uri.pathHead;
1091
} else if (uri.pathHead) {
1092
sharename = &uri.pathHead->text;
1093
sharepath = uri.pathHead->next;
1098
sharedir = GHIPlatformFindHGFSShare(ghip, sharename);
1100
uriFreeUriMembersA(&uri);
1101
g_ptr_array_free(newargv, TRUE);
1102
Debug("Couldn't find a mounted HGFS filesystem for %s\n", uriString);
1106
subdir = GHIPlatformUriPathToString(sharepath);
1107
g_ptr_array_add(newargv, g_strconcat(sharedir, subdir, NULL));
1110
} else if (URI_TEXTRANGE_EQUAL(uri.scheme, "x-vmware-action")) {
1111
if (g_file_test("/usr/bin/gnome-open", G_FILE_TEST_IS_EXECUTABLE)) {
1112
g_ptr_array_add(newargv, g_strdup("/usr/bin/gnome-open"));
1113
} else if (g_file_test("/usr/bin/htmlview", G_FILE_TEST_IS_EXECUTABLE)
1114
&& URI_TEXTRANGE_EQUAL(uri.hostText, "browse")) {
1115
g_ptr_array_add(newargv, g_strdup("/usr/bin/htmlview"));
1117
Debug("Don't know how to handle URI %s. "
1118
"We definitely don't have /usr/bin/gnome-open.\n",
1123
#endif // Temporary until ShellAction is implemented.
1125
if (URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
1126
char *fspath = GHIPlatformUriPathToString(uri.pathHead);
1127
g_ptr_array_add(newargv, fspath);
1130
* Just append the unparsed URI as-is onto the command line.
1132
g_ptr_array_add(newargv, g_strdup(uriString));
1133
parseQueryString = FALSE;
1136
*dotDesktopPath = NULL;
1137
if (parseQueryString) {
1139
* We may need additional command-line arguments from the part of the URI after the
1143
UriQueryListA *queryList;
1146
if (uriDissectQueryMallocA(&queryList, &itemCount,
1147
uri.query.first, uri.query.afterLast) == URI_SUCCESS) {
1150
for (cur = queryList; cur; cur = cur->next) {
1155
if (strcmp(cur->key, "argv[]") == 0) {
1156
g_ptr_array_add(newargv, g_strdup(cur->value));
1158
} else if (strcmp(cur->key, "DesktopEntry")) {
1159
*dotDesktopPath = g_strdup(cur->value);
1163
uriFreeQueryListA(queryList);
1165
Warning("Dissection of query string in URI %s failed\n",
1170
uriFreeUriMembersA(&uri);
1172
*argc = newargv->len;
1173
g_ptr_array_add(newargv, NULL);
1174
*argv = (char **) g_ptr_array_free(newargv, FALSE);
1180
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1182
*-----------------------------------------------------------------------------
1184
* GHIPlatformStripFieldCodes --
1186
* Strip field codes from an argv-style string array.
1192
* Modifies the string array, possibly freeing some members.
1194
*-----------------------------------------------------------------------------
1198
GHIPlatformStripFieldCodes(char **argv, // IN/OUT
1199
int *argc) // IN/OUT
1206
for (i = 0; i < *argc; i++) {
1207
if (argv[i][0] == '%'
1208
&& argv[i][1] != '\0'
1209
&& argv[i][2] == '\0') {
1212
* This math may look slightly dodgy - just remember that these
1213
* argv's have a terminating NULL pointer, which is not included in its argc.
1215
g_memmove(argv + i, argv + i + 1,
1216
(*argc - i) * sizeof *argv);
1221
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1225
*-----------------------------------------------------------------------------
1227
* GHIPlatformCombineArgs --
1229
* Takes a target URI and turns it into an argv array that we can actually
1232
* XXX TODO: accept location arguments once ShellAction is implemented.
1235
* TRUE if successful, FALSE otherwise. If TRUE, fullArgv/fullArgc will
1236
* contain the exec-able argument array.
1239
* Allocates a string array in fullArgv (owner is responsible for freeing).
1241
*-----------------------------------------------------------------------------
1245
GHIPlatformCombineArgs(GHIPlatform *ghip, // IN
1246
const char *targetUtf8, // IN
1247
char ***fullArgv, // OUT
1248
int *fullArgc) // OUT
1250
char **targetArgv = NULL;
1252
char *targetDotDesktop = NULL;
1253
GPtrArray *fullargs = g_ptr_array_new();
1254
GHIMenuItem *ghm = NULL;
1262
if (!GHIPlatformURIToArgs(ghip,
1266
&targetDotDesktop)) {
1267
Debug("Parsing URI %s failed\n", targetUtf8);
1271
#if 0 // Temporary until ShellAction is implemented.
1273
* This is previous code that was used for combining file and action
1274
* arguments, but it's not being used at the moment. Our action URI format
1275
* has changed, so this will need to be updated before it's usable.
1279
* In the context of the .desktop spec
1280
* (http://standards.freedesktop.org/desktop-entry-spec/1.1/ar01s06.html),
1281
* combining the two is not as simple as just concatenating them.
1283
* XXX for some random older programs, we may want to do concatenation in the future.
1287
char *srcDotDesktop = NULL;
1290
* First, figure out which argv[] array is the 'main' one, and which one will serve
1291
* only to fill in the file/URL argument in the .desktop file...
1293
if (! *actionArgc) {
1294
srcArgv = *fileArgv;
1295
srcArgc = *fileArgc;
1296
srcDotDesktop = fileDotDesktop;
1298
srcArgv = *actionArgv;
1299
srcArgc = *actionArgc;
1300
srcDotDesktop = actionDotDesktop;
1301
if (fileDotDesktop) {
1302
GHIPlatformStripFieldCodes(*fileArgv, fileArgc);
1305
#endif // Temporary until ShellAction is implemented.
1307
for (i = 0; i < targetArgc; i++) {
1308
const char *thisarg = targetArgv[i];
1310
if (thisarg[0] == '%' && thisarg[1] != '\0' && thisarg[2] == '\0') {
1311
switch (thisarg[1]) {
1312
case 'F': // %F expands to multiple filenames
1313
case 'f': // %f expands to a filename
1315
* XXX TODO: add file location arguments
1317
//if (srcArgv != *fileArgv && *fileArgc) {
1318
// g_ptr_array_add(fullargs, g_strdup((*fileArgv)[0]));
1321
case 'U': // %U expands to multiple URLs
1322
case 'u': // %u expands to a URL
1324
* XXX TODO: add URL location arguments
1326
//if (srcArgv != *fileArgv && fileUtf8) {
1327
// g_ptr_array_add(fullargs, g_strdup(fileUtf8));
1332
* These three require getting at the .desktop info for the app.
1337
if (!ghm && targetDotDesktop) {
1338
ghm = (GHIMenuItem *) g_hash_table_lookup(ghip->appsByDesktopEntry,
1342
ASSERT (fullargs->len > 0);
1343
ghm = (GHIMenuItem *) g_hash_table_lookup(ghip->appsByExecutable,
1344
g_ptr_array_index(fullargs, 0));
1348
switch (thisarg[1]) {
1349
case 'c': // %c expands to the .desktop's Name=
1352
g_key_file_get_locale_string(ghm->keyfile,
1353
G_KEY_FILE_DESKTOP_GROUP,
1354
G_KEY_FILE_DESKTOP_KEY_NAME,
1357
g_ptr_array_add(fullargs, ctmp);
1361
case 'i': // %i expands to "--icon" then the .desktop's Icon=
1364
g_key_file_get_string(ghm->keyfile,
1365
G_KEY_FILE_DESKTOP_GROUP,
1366
G_KEY_FILE_DESKTOP_KEY_ICON,
1368
if (ctmp && *ctmp) {
1369
g_ptr_array_add(fullargs, g_strdup("--icon"));
1370
g_ptr_array_add(fullargs, ctmp);
1374
case 'k': // %k expands to the .desktop's path
1375
g_ptr_array_add(fullargs, g_strdup(ghm->keyfilePath));
1380
case '%': // Expands to a literal
1381
g_ptr_array_add(fullargs, g_strdup("%"));
1385
* Intentionally ignore an unknown field code.
1390
g_ptr_array_add(fullargs, g_strdup(thisarg));
1393
*fullArgc = fullargs->len;
1394
g_ptr_array_add(fullargs, NULL);
1395
*fullArgv = (char **) g_ptr_array_free(fullargs, FALSE);
1397
g_strfreev(targetArgv);
1398
g_free(targetDotDesktop);
1400
return *fullArgc ? TRUE : FALSE;
1405
*----------------------------------------------------------------------------
1407
* GHIPlatformShellOpen --
1409
* Open the specified file with the default shell handler (ShellExecute).
1410
* Note that the file path may be either a URI (originated with
1411
* Tools >= NNNNN), or a regular path (originated with Tools < NNNNN).
1414
* TRUE if successful, FALSE otherwise.
1419
*----------------------------------------------------------------------------
1423
GHIPlatformShellOpen(GHIPlatform *ghip, // IN
1424
const char *fileUtf8) // IN
1426
char **fullArgv = NULL;
1428
Bool retval = FALSE;
1433
Debug("%s: file: '%s'\n", __FUNCTION__, fileUtf8);
1436
* XXX This is not shippable. GHIPlatformCombineArgs may still be necessary,
1437
* and I chose to use if (1) rather than #if 0 it out in order to not have to
1438
* #if 0 out that function and everything else it calls as well.
1441
UriParserStateA upState;
1444
memset(&upState, 0, sizeof upState);
1445
memset(&uri, 0, sizeof uri);
1448
if (uriParseUriA(&upState, fileUtf8) == URI_SUCCESS &&
1449
URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
1450
Bool success = FALSE;
1452
gchar* tmp = (gchar*)g_alloca(strlen(fileUtf8) + 1);
1453
uriUriStringToUnixFilenameA(fileUtf8, tmp);
1455
Glib::ustring unixFile;
1456
unixFile.assign(tmp);
1458
Glib::ustring contentType;
1460
contentType = Gio::content_type_guess(unixFile, std::string(""), uncertain);
1462
if (contentType == "application/x-desktop") {
1463
GDesktopAppInfo* dappinfo;
1464
dappinfo = g_desktop_app_info_new_from_filename(unixFile.c_str());
1466
GAppInfo *appinfo = (GAppInfo*)G_APP_INFO(dappinfo);
1467
success = AppInfoLaunchEnv(ghip, appinfo);
1468
g_object_unref(dappinfo);
1470
} else if (Glib::file_test(unixFile, Glib::FILE_TEST_IS_REGULAR) &&
1471
Glib::file_test(unixFile, Glib::FILE_TEST_IS_EXECUTABLE)) {
1472
std::vector<Glib::ustring> argv;
1473
argv.push_back(unixFile);
1475
Glib::spawn_async("" /* inherit cwd */, argv, ghip->nativeEnviron, (Glib::SpawnFlags) 0);
1477
} catch(Glib::SpawnError& e) {
1478
g_warning("%s: %s: %s\n", __FUNCTION__, unixFile.c_str(), e.what().c_str());
1481
std::vector<Glib::ustring> argv;
1482
Glib::ustring de = Xdg_DetectDesktopEnv();
1483
// XXX Really we should just use xdg-open exclusively, but xdg-open
1484
// as shipped with xdg-utils 1.0.2 is broken. It is fixed
1485
// in portland CVS, but we need to import into modsource and
1486
// redistribute with Tools in order to guarantee a working version.
1487
if (de == "GNOME") {
1488
argv.push_back("gnome-open");
1489
} else if (de == "KDE") {
1490
argv.push_back("kde-open");
1492
argv.push_back("xdg-open");
1494
argv.push_back(unixFile);
1496
Glib::spawn_async("", argv, ghip->nativeEnviron, Glib::SPAWN_SEARCH_PATH);
1498
} catch(Glib::SpawnError& e) {
1499
g_warning("%s: %s: %s\n", __FUNCTION__, unixFile.c_str(), e.what().c_str());
1505
} else if (GHIPlatformCombineArgs(ghip, fileUtf8, &fullArgv, &fullArgc) &&
1507
// XXX Will fix this soon.
1509
retval = g_spawn_async(NULL, fullArgv,
1511
* XXX Please don't hate me for casting off the qualifier
1512
* here. Glib does -not- modify the environment, at
1513
* least not in the parent process, but their prototype
1514
* does not specify this argument as being const.
1516
* Comment stolen from GuestAppX11OpenUrl.
1518
(char **)ghip->nativeEnviron,
1519
(GSpawnFlags) (G_SPAWN_SEARCH_PATH |
1520
G_SPAWN_STDOUT_TO_DEV_NULL |
1521
G_SPAWN_STDERR_TO_DEV_NULL),
1522
NULL, NULL, NULL, NULL);
1526
g_strfreev(fullArgv);
1533
*----------------------------------------------------------------------------
1535
* GHIPlatformShellAction --
1536
* Perform the specified shell action with the optional target and
1537
* locations arguments. Note that the target may be either a URI
1538
* (originated with Tools >= NNNNN), or a regular path (originated with
1540
* See the comment at ghIntegration.c::GHITcloShellAction for information
1541
* on the command format and supported actions.
1544
* TRUE if successful, FALSE otherwise.
1549
*----------------------------------------------------------------------------
1553
GHIPlatformShellAction(GHIPlatform *ghip, // IN: platform-specific state
1554
const char *actionURI, // IN
1555
const char *targetURI, // IN
1556
const char **locations, // IN
1557
int numLocations) // IN
1560
* TODO: implement the shell action execution.
1561
* The GHIPlatformShellUrlOpen() below is left for reference, but is not
1562
* used right now. Its functionality should be integrated here.
1568
Debug("%s not implemented yet.\n", __FUNCTION__);
1574
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1576
*----------------------------------------------------------------------------
1578
* GHIPlatformShellUrlOpen --
1580
* Run ShellExecute on a given file.
1583
* TRUE if success, FALSE otherwise.
1588
*----------------------------------------------------------------------------
1592
GHIPlatformShellUrlOpen(GHIPlatform *ghip, // IN: platform-specific state
1593
const char *fileUtf8, // IN: command/file
1594
const char *actionUtf8) // IN: action
1597
char **fileArgv = NULL;
1599
char *fileDotDesktop = NULL;
1600
char **actionArgv = NULL;
1602
char *actionDotDesktop = NULL;
1603
char **fullArgv = NULL;
1606
Bool retval = FALSE;
1610
if (!GHIPlatformURIToArgs(ghip, fileUtf8, &fileArgv, &fileArgc,
1612
Debug("Parsing URI %s failed\n", fileUtf8);
1616
if (actionUtf8 && !GHIPlatformURIToArgs(ghip, actionUtf8, &actionArgv, &actionArgc,
1617
&actionDotDesktop)) {
1618
Debug("Parsing action URI %s failed\n", actionUtf8);
1619
g_strfreev(fileArgv);
1620
g_free(fileDotDesktop);
1624
if (GHIPlatformCombineArgs(ghip,
1625
fileUtf8, &fileArgv, &fileArgc, fileDotDesktop,
1626
actionUtf8, &actionArgv, &actionArgc, actionDotDesktop,
1627
&fullArgv, &fullArgc)) {
1628
retval = g_spawn_async(NULL, fullArgv, NULL,
1629
G_SPAWN_SEARCH_PATH |
1630
G_SPAWN_STDOUT_TO_DEV_NULL |
1631
G_SPAWN_STDERR_TO_DEV_NULL,
1632
NULL, NULL, NULL, NULL);
1635
g_strfreev(fileArgv);
1636
g_free(fileDotDesktop);
1637
g_strfreev(actionArgv);
1638
g_free(actionDotDesktop);
1639
g_strfreev(fullArgv);
1646
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1650
*----------------------------------------------------------------------------
1652
* GHIPlatformSetGuestHandler --
1654
* Set the handler for the specified filetype (or URL protocol) to the
1658
* TRUE if successful, FALSE otherwise.
1663
*----------------------------------------------------------------------------
1667
GHIPlatformSetGuestHandler(GHIPlatform *ghip, // IN: platform-specific state
1668
const char *suffix, // IN/OPT: suffix
1669
const char *mimeType, // IN/OPT: MIME Type
1670
const char *UTI, // IN/OPT: UTI
1671
const char *actionURI, // IN:
1672
const char *targetURI) // IN:
1681
*----------------------------------------------------------------------------
1683
* GHIPlatformRestoreDefaultGuestHandler --
1685
* Restore the handler for a given type to the value in use before any
1689
* TRUE if successful, FALSE otherwise.
1694
*----------------------------------------------------------------------------
1698
GHIPlatformRestoreDefaultGuestHandler(GHIPlatform *ghip, // IN: platform-specific state
1699
const char *suffix, // IN/OPT: Suffix
1700
const char *mimetype, // IN/OPT: MIME Type
1701
const char *UTI) // IN/OPT: UTI
1710
*----------------------------------------------------------------------------
1712
* GHIPlatformSetOutlookTempFolder --
1714
* Set the temporary folder used by Microsoft Outlook to store attachments
1715
* opened by the user.
1717
* XXX While we probably won't ever need to implement this for Linux, we
1718
* still the definition of this function in the X11 back-end.
1721
* TRUE if successful, FALSE otherwise.
1726
*----------------------------------------------------------------------------
1730
GHIPlatformSetOutlookTempFolder(GHIPlatform *ghip, // IN: platform-specific state
1731
const char *targetURI) // IN: Target URI
1740
/* @brief Send a mouse or keyboard event to a tray icon.
1742
* @param[in] ghip Pointer to platform-specific GHI data.
1744
* @retval TRUE Operation Succeeded.
1745
* @retval FALSE Operation Failed.
1749
GHIPlatformTrayIconSendEvent(GHIPlatform *ghip,
1760
/* @brief Start sending tray icon updates to the VMX.
1762
* @param[in] ghip Pointer to platform-specific GHI data.
1764
* @retval TRUE Operation Succeeded.
1765
* @retval FALSE Operation Failed.
1769
GHIPlatformTrayIconStartUpdates(GHIPlatform *ghip)
1775
/* @brief Stop sending tray icon updates to the VMX.
1777
* @param[in] ghip Pointer to platform-specific GHI data.
1779
* @retval TRUE Operation Succeeded.
1780
* @retval FALSE Operation Failed.
1784
GHIPlatformTrayIconStopUpdates(GHIPlatform *ghip)
1790
/* @brief Set a window to be focused.
1792
* @param[in] ghip Pointer to platform-specific GHI data.
1793
* @param[in] xdrs Pointer to serialized data from the host.
1795
* @retval TRUE Operation Succeeded.
1796
* @retval FALSE Operation Failed.
1800
GHIPlatformSetFocusedWindow(GHIPlatform *ghip,
1809
* @brief Get the hash (or timestamp) of information returned by
1810
* GHIPlatformGetBinaryInfo.
1812
* @param[in] ghip Pointer to platform-specific GHI data.
1813
* @param[in] request Request containing which executable to get the hash for.
1814
* @param[out] reply Reply to be filled with the hash.
1816
* @retval TRUE Operation succeeded.
1817
* @retval FALSE Operation failed.
1820
Bool GHIPlatformGetExecInfoHash(GHIPlatform *ghip,
1821
const char *execPath,
1822
char **execInfoHash)
1826
ASSERT(execInfoHash);
1833
******************************************************************************
1834
* GHIX11FindDesktopUriByExec -- */ /**
1836
* Given an executable path, attempt to generate an "execUri" associated with a
1837
* corresponding .desktop file.
1839
* @sa GHIX11_FindDesktopUriByExec
1841
* @note Returned pointer belongs to the GHI module. Caller must not free it.
1843
* @param[in] ghip GHI platform-specific context.
1844
* @param[in] execPath Input binary path. May be absolute or relative.
1846
* @return Pointer to a URI string on success, NULL on failure.
1848
******************************************************************************
1852
GHIX11FindDesktopUriByExec(GHIPlatform *ghip,
1855
char pathbuf[MAXPATHLEN];
1856
gchar *pathname = NULL;
1859
gboolean fudged = FALSE;
1860
gboolean basenamed = FALSE;
1866
* XXX This is not shippable. This is to be addressed by milestone 3 with
1867
* the improved "fuzzy logic for UNITY_RPC_GET_WINDOW_PATH" deliverable.
1873
* Check our hash table first. Negative entries are also cached.
1875
if (g_hash_table_lookup_extended(ghip->appsByWindowExecutable,
1876
exec, NULL, (gpointer*)&uri)) {
1881
* Okay, execPath may be absolute or relative.
1883
* We'll search for a matching .desktop entry using the following methods:
1885
* 1. Use absolute path of exec.
1886
* 2. Use absolute path of basename of exec. (Resolves /opt/Adobe/Reader9/
1887
* Reader/intellinux/bin/acroread to /usr/bin/acroread.)
1888
* 3. Consult whitelist of known applications and guess at possible
1889
* launchers. (firefox-bin => firefox, soffice.bin => ooffice.)
1893
* Attempt #1: Start with unmodified input.
1895
Str_Strcpy(pathbuf, exec, sizeof pathbuf);
1898
g_free(pathname); // Placed here rather than at each goto. I'm lazy.
1900
pathname = g_find_program_in_path(pathbuf);
1902
gmi = (GHIMenuItem*)g_hash_table_lookup(ghip->appsByExecutable, pathname);
1904
uri = GHIPlatformMenuItemToURI(ghip, gmi);
1910
* Attempt #2: Take the basename of exec.
1913
char tmpbuf[MAXPATHLEN];
1918
/* basename(3) may modify the input buffer, so make a temporary copy. */
1919
Str_Strcpy(tmpbuf, pathbuf, sizeof tmpbuf);
1920
ctmp = basename(tmpbuf);
1922
Str_Strcpy(pathbuf, ctmp, sizeof pathbuf);
1928
* Attempt #3: Get our whitelist on.
1932
const gchar *pattern;
1934
} fudgePatterns[] = {
1936
* XXX Worth compiling once? Consider placing in an external filter
1937
* file to allow users to update it themselves easily.
1939
{ "*firefox*-bin", "firefox" },
1940
{ "*thunderbird*-bin", "thunderbird" },
1941
{ "*soffice.bin", "ooffice" }
1947
for (i = 0; i < ARRAYSIZE(fudgePatterns); i++) {
1948
if (g_pattern_match_simple(fudgePatterns[i].pattern,
1950
Str_Strcpy(pathbuf, fudgePatterns[i].exec, sizeof pathbuf);
1960
* Cache the result, even if it was negative.
1962
g_hash_table_insert(ghip->appsByWindowExecutable, g_strdup(exec), uri);
1969
*-----------------------------------------------------------------------------
1971
* AppInfoLaunchEnv --
1973
* Wrapper around g_app_info_launch which takes a custom environment into
1976
* GHI/X11 should spawn applications using ghip->nativeEnviron, but
1977
* g_app_info_launch doesn't taken a custom environment as a parameter.
1978
* Rather than reimplement that function, we work around it by doing the
1982
* 1. Fork a child process.
1983
* 2. Block until child terminates, returning true if the child exited
1984
* with an exit code of 0.
1987
* 1. Flush the environment and build a new one from nativeEnviron.
1988
* 2. Spawn desired application with g_app_info_launch.
1989
* 3. Exit with 0 if spawn was successful, otherwise 1.
1992
* Returns true if launch succeeded, false otherwise.
1995
* Creates a child process and blocks until the child exits. Child lasts
1996
* only as long as it takes to call g_app_info_launch.
1998
*-----------------------------------------------------------------------------
2002
AppInfoLaunchEnv(GHIPlatform* ghip, // IN
2003
GAppInfo* appInfo) // IN
2005
bool success = false;
2007
pid_t myPid = fork();
2011
g_warning("%s: fork: %s\n", __FUNCTION__, strerror(errno));
2015
/* Child: Exit with _exit() so as to not trigger any atexit() routines. */
2017
if (clearenv() == 0) {
2018
std::vector<Glib::ustring>::iterator envp;
2019
for (envp = ghip->nativeEnviron.begin();
2020
envp != ghip->nativeEnviron.end();
2023
* The string passed to putenv() becomes part of the environment --
2024
* it isn't copied. That's fine, though, because we're running in
2025
* the context of a very short-lived wrapper process.
2027
if (putenv((char*)envp->c_str()) != 0) {
2028
g_warning("%s: failed to restore native environment\n", __FUNCTION__);
2032
success = g_app_info_launch(appInfo, NULL, NULL, NULL);
2034
_exit(success == true ? 0 : 1);
2039
/* Parent: Hang out until our child terminates. */
2044
ret = waitpid(myPid, &status, 0);
2045
if ((ret == -1 && errno != EINTR) ||
2046
(ret == myPid && (WIFEXITED(status) || WIFSIGNALED(status)))) {
2050
success = (ret == myPid) && WIFEXITED(status) && WEXITSTATUS(status) == 0;
2059
*-----------------------------------------------------------------------------
2063
* Signal handler for updates to launch or fixed menus.
2069
* Calls transport's launchMenuChange callback, if set.
2071
*-----------------------------------------------------------------------------
2075
OnMenusChanged(GHIPlatform *ghip) // IN
2077
if (ghip->hostCallbacks.launchMenuChange) {
2078
std::vector<const char *> folderKeysChanged;
2079
folderKeysChanged.push_back(UNITY_START_MENU_LAUNCH_FOLDER);
2080
folderKeysChanged.push_back(UNITY_START_MENU_FIXED_FOLDER);
2081
ghip->hostCallbacks.launchMenuChange(folderKeysChanged.size(),
2082
&folderKeysChanged[0]);