~ubuntu-branches/ubuntu/quantal/open-vm-tools/quantal-201210021442

« back to all changes in this revision

Viewing changes to lib/ghIntegration/ghIntegrationX11.c

  • Committer: Bazaar Package Importer
  • Author(s): Serge Hallyn
  • Date: 2011-03-31 14:20:05 UTC
  • mfrom: (1.4.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20110331142005-3n9red91p7ogkweo
Tags: 2011.03.28-387002-0ubuntu1
* Merge latest upstream git tag.  This has the unlocked_ioctl change
  needed to fix dkms build failures (LP: #727342)
* Changes in debian/rules:
  - work around a bug in toolbox/Makefile, where install-exec-hook is
    not happening.  This needs to get fixed the right way.
  - don't install 'vmware-user' which seems to no longer exist
  - move /etc/xdg into open-vm-toolbox (which should be done using .install)
* debian/open-vm-tools.init: add 'modprobe [-r] vmblock'. (LP: #332323)
* debian/rules and debian/open-vm-toolbox.lintian-overrides:
  - Make vmware-user-suid-wrapper suid-root (LP: #332323)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*********************************************************
2
 
 * Copyright (C) 2008 VMware, Inc. All rights reserved.
3
 
 *
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.
7
 
 *
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.
12
 
 *
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.
16
 
 *
17
 
 *********************************************************/
18
 
 
19
 
/*
20
 
 * ghIntegrationX11.c --
21
 
 *
22
 
 *    Guest-host integration implementation for POSIX-compliant platforms that run X11.
23
 
 *
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}).
30
 
 */
31
 
 
32
 
#define _BSD_SOURCE 1 // Needed on Linux to get the DT_* values for dirent->d_type
33
 
#include <dirent.h>
34
 
#include <stdio.h>
35
 
#include <limits.h>
36
 
#include <sys/types.h>
37
 
#include <sys/stat.h>
38
 
#include <unistd.h>
39
 
#include <libgen.h>
40
 
 
41
 
#ifndef GTK2
42
 
#error "Gtk 2.0 is required"
43
 
#endif
44
 
 
45
 
#include <glib.h>
46
 
#include <gdk-pixbuf/gdk-pixbuf-core.h>
47
 
 
48
 
// gdkx.h includes Xlib.h, which #defines Bool.
49
 
#include <gdk/gdkx.h>
50
 
#undef Bool
51
 
 
52
 
#include "vmware.h"
53
 
#include "base64.h"
54
 
#include "rpcin.h"
55
 
#include "dbllnklst.h"
56
 
#include "debug.h"
57
 
#include "util.h"
58
 
#include "region.h"
59
 
#include "unity.h"
60
 
#include "unityCommon.h"
61
 
#include "system.h"
62
 
#include "codeset.h"
63
 
#include "imageUtil.h"
64
 
#include "strutil.h"
65
 
#include <paths.h>
66
 
#include "vm_atomic.h"
67
 
#include "mntinfo.h"
68
 
#include "ghIntegration.h"
69
 
#include "ghIntegrationInt.h"
70
 
#include "guest_msg_def.h"
71
 
#include "Uri.h"
72
 
#define URI_TEXTRANGE_EQUAL(textrange, str) \
73
 
   (((textrange).afterLast - (textrange).first) == strlen((str))        \
74
 
    && !strncmp((textrange).first, (str), (textrange).afterLast - (textrange).first))
75
 
 
76
 
#include "appUtil.h"
77
 
 
78
 
/*
79
 
 * The following defines appear in newer versions of glib 2.x, so
80
 
 * we define them for backwards compat.
81
 
 */
82
 
#ifndef G_KEY_FILE_DESKTOP_GROUP
83
 
#define G_KEY_FILE_DESKTOP_GROUP                "Desktop Entry"
84
 
#endif
85
 
#ifndef G_KEY_FILE_DESKTOP_KEY_NAME
86
 
#define G_KEY_FILE_DESKTOP_KEY_NAME             "Name"
87
 
#endif
88
 
#ifndef G_KEY_FILE_DESKTOP_KEY_ICON
89
 
#define G_KEY_FILE_DESKTOP_KEY_ICON             "Icon"
90
 
#endif
91
 
#ifndef G_KEY_FILE_DESKTOP_KEY_EXEC
92
 
#define G_KEY_FILE_DESKTOP_KEY_EXEC             "Exec"
93
 
#endif
94
 
#ifndef G_KEY_FILE_DESKTOP_KEY_TRY_EXEC
95
 
#define G_KEY_FILE_DESKTOP_KEY_TRY_EXEC         "TryExec"
96
 
#endif
97
 
#ifndef G_KEY_FILE_DESKTOP_KEY_CATEGORIES
98
 
#define G_KEY_FILE_DESKTOP_KEY_CATEGORIES       "Categories"
99
 
#endif
100
 
#ifndef G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY
101
 
#define G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY       "NoDisplay"
102
 
#endif
103
 
#ifndef G_KEY_FILE_DESKTOP_KEY_HIDDEN
104
 
#define G_KEY_FILE_DESKTOP_KEY_HIDDEN           "Hidden"
105
 
#endif
106
 
#ifndef G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN
107
 
#define G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN     "OnlyShowIn"
108
 
#endif
109
 
#ifndef G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN
110
 
#define G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN      "NotShowIn"
111
 
#endif
112
 
 
113
 
/*
114
 
 * These describe possible start menu item flags. It should come from ghiCommon.h
115
 
 * eventually.
116
 
 */
117
 
#define UNITY_START_MENU_ITEM_DIRECTORY (1 << 0)
118
 
 
119
 
/*
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
123
 
 * case.
124
 
 *
125
 
 * > This is only an estimate. <
126
 
 */
127
 
#define ICON_SPACE_PADDING (sizeof "999x999x65535x" + 25)
128
 
 
129
 
/*
130
 
 * The GHIDirectoryWatch object represents a watch on a directory to be notified of
131
 
 * added/removed/changed .desktop files.
132
 
 *
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.
136
 
 */
137
 
typedef struct {
138
 
   char *directoryPath;
139
 
} GHIDirectoryWatch;
140
 
 
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
145
 
   /*
146
 
    * Translates arbitrary executable paths as discovered through
147
 
    * UnityPlatformGetWindowPaths to a .desktop-ful executable URI.
148
 
    *
149
 
    * Example:
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
152
 
    */
153
 
   GHashTable *appsByWindowExecutable;
154
 
 
155
 
   Bool trackingEnabled;
156
 
   GArray *directoriesTracked;
157
 
 
158
 
   int nextMenuHandle;
159
 
   GHashTable *menuHandles;
160
 
 
161
 
   /** Pre-wrapper script environment.  See @ref System_GetNativeEnviron. */
162
 
   const char **nativeEnviron;
163
 
};
164
 
 
165
 
#ifdef GTK2
166
 
 
167
 
/*
168
 
 * The GHIMenuItem object represents an individual leaf-node menu item (corresponding to
169
 
 * a .desktop file).
170
 
 */
171
 
typedef struct {
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
175
 
} GHIMenuItem;
176
 
 
177
 
/*
178
 
 * Represents a "start menu folder" so to speak.
179
 
 */
180
 
typedef struct {
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
184
 
} GHIMenuDirectory;
185
 
 
186
 
/*
187
 
 * Represents an active handle for traversing a menu.
188
 
 */
189
 
typedef struct {
190
 
   int handleID;
191
 
   enum { LAUNCH_FOLDER, FIXED_FOLDER, DIRECTORY_FOLDER } handleType;
192
 
   GHIMenuDirectory *gmd; // Only set for DIRECTORY_FOLDER handles
193
 
} GHIMenuHandle;
194
 
 
195
 
/*
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...
198
 
 */
199
 
typedef struct {
200
 
   int currentItem;
201
 
   int desiredItem;
202
 
   GHIMenuDirectory *gmd; // OUT - pointer to the Nth GHIMenuDirectory
203
 
} GHITreeTraversal;
204
 
 
205
 
static void GHIPlatformSetMenuTracking(GHIPlatform *ghip,
206
 
                                       Bool isEnabled);
207
 
static char *GHIPlatformUriPathToString(UriPathSegmentA *path);
208
 
 
209
 
 
210
 
/*
211
 
 * This is a list of directories that we search for .desktop files by default.
212
 
 */
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",
219
 
   "/usr/share/applnk",
220
 
   "~/.local/share/applications"
221
 
};
222
 
 
223
 
 
224
 
/*
225
 
 * GHI capabilities for this platform.
226
 
 */
227
 
/*
228
 
 * XXX TODO: re-enable once ShellAction is implemented.
229
 
 */
230
 
/*
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
236
 
};
237
 
*/
238
 
 
239
 
 
240
 
/*
241
 
 *-----------------------------------------------------------------------------
242
 
 *
243
 
 * GHIPlatformDestroyMenuItem --
244
 
 *
245
 
 *      Frees a menu item object (which right now is just a GKeyFile).
246
 
 *
247
 
 * Results:
248
 
 *      None.
249
 
 *
250
 
 * Side effects:
251
 
 *      The specified GHIMenuItem is no longer valid.
252
 
 *
253
 
 *-----------------------------------------------------------------------------
254
 
 */
255
 
 
256
 
static void
257
 
GHIPlatformDestroyMenuItem(gpointer data,      // IN
258
 
                           gpointer user_data) // IN (unused)
259
 
{
260
 
   GHIMenuItem *gmi;
261
 
 
262
 
   ASSERT(data);
263
 
 
264
 
   gmi = data;
265
 
   g_key_file_free(gmi->keyfile);
266
 
   g_free(gmi->keyfilePath);
267
 
   g_free(gmi->exepath);
268
 
   g_free(gmi);
269
 
}
270
 
 
271
 
 
272
 
/*
273
 
 *-----------------------------------------------------------------------------
274
 
 *
275
 
 * GHIPlatformDestroyMenuDirectory --
276
 
 *
277
 
 *      Frees the memory associated with a GHIMenuDirectory object.
278
 
 *
279
 
 * Results:
280
 
 *      None.
281
 
 *
282
 
 * Side effects:
283
 
 *      The specified GHIMenuDirectory object is no longer valid.
284
 
 *
285
 
 *-----------------------------------------------------------------------------
286
 
 */
287
 
 
288
 
static void
289
 
GHIPlatformDestroyMenuDirectory(gpointer data) // IN
290
 
{
291
 
   GHIMenuDirectory *gmd = (GHIMenuDirectory *) data;
292
 
 
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);
296
 
}
297
 
#endif // GTK2
298
 
 
299
 
 
300
 
/*
301
 
 *----------------------------------------------------------------------------
302
 
 *
303
 
 * GHIPlatformIsSupported --
304
 
 *
305
 
 *      Determine whether this guest supports guest host integration.
306
 
 *
307
 
 * Results:
308
 
 *      TRUE if the guest supports GHI
309
 
 *      FALSE otherwise
310
 
 *
311
 
 * Side effects:
312
 
 *      None
313
 
 *
314
 
 *----------------------------------------------------------------------------
315
 
 */
316
 
 
317
 
Bool
318
 
GHIPlatformIsSupported(void)
319
 
{
320
 
#ifdef GTK2
321
 
   return TRUE;
322
 
#else
323
 
   return FALSE;
324
 
#endif
325
 
}
326
 
 
327
 
 
328
 
/*
329
 
 *----------------------------------------------------------------------------
330
 
 *
331
 
 * GHIPlatformInit --
332
 
 *
333
 
 *      Sets up the platform-specific GHI state.
334
 
 *
335
 
 * Results:
336
 
 *      Pointer to platform-specific data (may be NULL).
337
 
 *
338
 
 * Side effects:
339
 
 *      None
340
 
 *
341
 
 *----------------------------------------------------------------------------
342
 
 */
343
 
 
344
 
GHIPlatform *
345
 
GHIPlatformInit(VMU_ControllerCB *vmuControllerCB,  // IN
346
 
                void *ctx)                          // IN
347
 
{
348
 
   extern const char **environ;
349
 
   GHIPlatform *ghip;
350
 
 
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);
356
 
   AppUtil_Init();
357
 
 
358
 
   return ghip;
359
 
}
360
 
 
361
 
 
362
 
/*
363
 
 *----------------------------------------------------------------------------
364
 
 *
365
 
 * GHIPlatformRegisterCaps --
366
 
 *
367
 
 *      Register guest platform specific capabilities with the VMX.
368
 
 *
369
 
 * Results:
370
 
 *      None.
371
 
 *
372
 
 * Side effects:
373
 
 *      None.
374
 
 *
375
 
 *----------------------------------------------------------------------------
376
 
 */
377
 
 
378
 
void
379
 
GHIPlatformRegisterCaps(GHIPlatform *ghip) // IN
380
 
{
381
 
   ASSERT(ghip);
382
 
   //ASSERT(platformGHICaps);
383
 
 
384
 
   /*
385
 
    * XXX TODO: re-enable once ShellAction is implemented.
386
 
    */
387
 
   //AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), TRUE);
388
 
}
389
 
 
390
 
 
391
 
/*
392
 
 *----------------------------------------------------------------------------
393
 
 *
394
 
 * GHIPlatformUnregisterCaps --
395
 
 *
396
 
 *      Register guest platform specific capabilities with the VMX.
397
 
 *
398
 
 * Results:
399
 
 *      None.
400
 
 *
401
 
 * Side effects:
402
 
 *      None.
403
 
 *
404
 
 *----------------------------------------------------------------------------
405
 
 */
406
 
 
407
 
void
408
 
GHIPlatformUnregisterCaps(GHIPlatform *ghip) // IN
409
 
{
410
 
   ASSERT(ghip);
411
 
   //ASSERT(platformGHICaps);
412
 
 
413
 
   /*
414
 
    * XXX TODO: re-enable once ShellAction is implemented.
415
 
    */
416
 
   //AppUtil_SendGuestCaps(platformGHICaps, ARRAYSIZE(platformGHICaps), FALSE);
417
 
}
418
 
 
419
 
 
420
 
#ifdef GTK2
421
 
/*
422
 
 *-----------------------------------------------------------------------------
423
 
 *
424
 
 * GHIPlatformFreeValue --
425
 
 *
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.
429
 
 *
430
 
 * Results:
431
 
 *      TRUE always.
432
 
 *
433
 
 * Side effects:
434
 
 *      The specified value will no longer be valid.
435
 
 *
436
 
 *-----------------------------------------------------------------------------
437
 
 */
438
 
 
439
 
static gboolean
440
 
GHIPlatformFreeValue(gpointer key,       // IN
441
 
                     gpointer value,     // IN
442
 
                     gpointer user_data) // IN
443
 
{
444
 
   g_free(value);
445
 
 
446
 
   return TRUE;
447
 
}
448
 
#endif // GTK2
449
 
 
450
 
 
451
 
/*
452
 
 *-----------------------------------------------------------------------------
453
 
 *
454
 
 * GHIPlatformCleanupMenuEntries --
455
 
 *
456
 
 *      Frees all the memory associated with the menu information, including active menu
457
 
 *      handles and the internal applications menu representation.
458
 
 *
459
 
 * Results:
460
 
 *      None.
461
 
 *
462
 
 * Side effects:
463
 
 *      None.
464
 
 *
465
 
 *-----------------------------------------------------------------------------
466
 
 */
467
 
 
468
 
static void
469
 
GHIPlatformCleanupMenuEntries(GHIPlatform *ghip) // IN
470
 
{
471
 
#ifdef GTK2
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;
476
 
   }
477
 
 
478
 
   if (ghip->apps) {
479
 
      g_hash_table_destroy(ghip->appsByDesktopEntry);
480
 
      g_hash_table_destroy(ghip->appsByExecutable);
481
 
      g_tree_destroy(ghip->apps);
482
 
      ghip->apps = NULL;
483
 
   }
484
 
#endif // GTK2
485
 
}
486
 
 
487
 
 
488
 
/*
489
 
 *----------------------------------------------------------------------------
490
 
 *
491
 
 * GHIPlatformCleanup --
492
 
 *
493
 
 *      Tears down the platform-specific GHI state.
494
 
 *
495
 
 * Results:
496
 
 *      None.
497
 
 *
498
 
 * Side effects:
499
 
 *      GHIPlatform is no longer valid.
500
 
 *
501
 
 *----------------------------------------------------------------------------
502
 
 */
503
 
 
504
 
void
505
 
GHIPlatformCleanup(GHIPlatform *ghip) // IN
506
 
{
507
 
   if (!ghip) {
508
 
      return;
509
 
   }
510
 
 
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;
517
 
   }
518
 
   g_hash_table_destroy(ghip->appsByWindowExecutable);
519
 
   free(ghip);
520
 
}
521
 
 
522
 
 
523
 
#ifdef GTK2
524
 
 
525
 
 
526
 
/*
527
 
 *-----------------------------------------------------------------------------
528
 
 *
529
 
 * GHIPlatformCollectIconInfo --
530
 
 *
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.
533
 
 *
534
 
 * Results:
535
 
 *      None.
536
 
 *
537
 
 * Side effects:
538
 
 *      Adds data into the DynBuf.
539
 
 *
540
 
 *-----------------------------------------------------------------------------
541
 
 */
542
 
 
543
 
static void
544
 
GHIPlatformCollectIconInfo(GHIPlatform *ghip,        // IN
545
 
                           GHIMenuItem *ghm,         // IN
546
 
                           unsigned long windowID,   // IN
547
 
                           DynBuf *buf)              // IN/OUT
548
 
{
549
 
   GPtrArray *pixbufs;
550
 
   char tbuf[1024];
551
 
   gsize totalIconBytes;
552
 
   char *ctmp = NULL;
553
 
   int i;
554
 
 
555
 
   if (ghm) {
556
 
      ctmp = g_key_file_get_string(ghm->keyfile, G_KEY_FILE_DESKTOP_GROUP,
557
 
                                   G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
558
 
   }
559
 
 
560
 
   pixbufs = AppUtil_CollectIconArray(ctmp, windowID);
561
 
 
562
 
   /*
563
 
    * Now see if all of these icons can fit into our reply.
564
 
    */
565
 
   totalIconBytes = DynBuf_GetSize(buf);
566
 
   for (i = 0; i < pixbufs->len; i++) {
567
 
      gsize thisIconBytes;
568
 
      GdkPixbuf *pixbuf = g_ptr_array_index(pixbufs, i);
569
 
 
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) {
577
 
         GdkPixbuf *newIcon;
578
 
         volatile double newWidth;
579
 
         volatile double newHeight;
580
 
         volatile double scaleFactor;
581
 
 
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) {
587
 
            /*
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.
592
 
             */
593
 
            scaleFactor = 0.95;
594
 
         }
595
 
 
596
 
         newWidth *= scaleFactor;
597
 
         newHeight *= scaleFactor;
598
 
 
599
 
         /*
600
 
          * If this is the only icon available, try scaling it down to the largest icon
601
 
          * that will comfortably fit in the reply.
602
 
          *
603
 
          * Adding 0.5 to newWidth & newHeight is an easy way of rounding to the closest
604
 
          * integer.
605
 
          */
606
 
         newIcon = gdk_pixbuf_scale_simple(pixbuf,
607
 
                                           (int)(newWidth + 0.5),
608
 
                                           (int)(newHeight + 0.5),
609
 
                                           GDK_INTERP_HYPER);
610
 
         g_object_unref(G_OBJECT(pixbuf));
611
 
         g_ptr_array_index(pixbufs, i) = newIcon;
612
 
         i--; // Try including the newly scaled-down icon
613
 
      } else {
614
 
         g_object_unref(G_OBJECT(pixbuf));
615
 
         g_ptr_array_remove_index_fast(pixbufs, i);
616
 
         i--;
617
 
      }
618
 
   }
619
 
 
620
 
   /*
621
 
    * Now that we actually have all available icons loaded and checked, dump their count
622
 
    * and contents into the reply.
623
 
    */
624
 
   Str_Sprintf(tbuf, sizeof tbuf, "%u", pixbufs->len);
625
 
   DynBuf_AppendString(buf, tbuf);
626
 
 
627
 
   for (i = 0; i < pixbufs->len; i++) {
628
 
      int width;
629
 
      int height;
630
 
      GdkPixbuf *pixbuf;
631
 
      guchar *pixels;
632
 
      int x, y;
633
 
      int rowstride;
634
 
      int n_channels;
635
 
 
636
 
      pixbuf = g_ptr_array_index(pixbufs, i);
637
 
 
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);
644
 
 
645
 
      Str_Sprintf(tbuf, sizeof tbuf, "%d", width * height * 4);
646
 
      DynBuf_AppendString(buf, tbuf);
647
 
 
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++) {
655
 
            char bgra[4];
656
 
            guchar *p; // Pointer to RGBA data in GdkPixbuf
657
 
 
658
 
            p = pixels + (y * rowstride) + (x * n_channels);
659
 
            bgra[0] = p[2];
660
 
            bgra[1] = p[1];
661
 
            bgra[2] = p[0];
662
 
            if (n_channels > 3) {
663
 
               bgra[3] = p[3];
664
 
            } else {
665
 
               bgra[3] = 0xFF;
666
 
            }
667
 
            DynBuf_Append(buf, bgra, 4);
668
 
         }
669
 
      }
670
 
 
671
 
      DynBuf_AppendString(buf, "");
672
 
 
673
 
   }
674
 
 
675
 
   AppUtil_FreeIconArray(pixbufs);
676
 
}
677
 
#endif // GTK2
678
 
 
679
 
 
680
 
/*
681
 
 *----------------------------------------------------------------------------
682
 
 *
683
 
 * GHIPlatformGetBinaryInfo --
684
 
 *
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.
689
 
 *
690
 
 * Results:
691
 
 *      TRUE if everything went ok, FALSE otherwise.
692
 
 *
693
 
 * Side effects:
694
 
 *      None
695
 
 *
696
 
 *----------------------------------------------------------------------------
697
 
 */
698
 
 
699
 
Bool
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
703
 
{
704
 
#ifdef GTK2
705
 
   const char *realCmd = NULL;
706
 
   char *keyfilePath = NULL;
707
 
   unsigned long windowID = 0;
708
 
   gpointer freeMe = NULL;
709
 
   GHIMenuItem *ghm = NULL;
710
 
   char *ctmp;
711
 
   UriParserStateA state;
712
 
   UriUriA uri;
713
 
 
714
 
   ASSERT(ghip);
715
 
   ASSERT(pathURIUtf8);
716
 
   ASSERT(buf);
717
 
 
718
 
   memset(&state, 0, sizeof state);
719
 
   memset(&uri, 0, sizeof uri);
720
 
   state.uri = &uri;
721
 
 
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;
727
 
         int itemCount;
728
 
 
729
 
         realCmd = freeMe = GHIPlatformUriPathToString(uri.pathHead);
730
 
         if (uriDissectQueryMallocA(&queryList, &itemCount,
731
 
                                    uri.query.first,
732
 
                                    uri.query.afterLast) == URI_SUCCESS) {
733
 
            UriQueryListA *cur;
734
 
 
735
 
            for (cur = queryList; cur; cur = cur->next) {
736
 
               if (!cur->value) {
737
 
                  continue;
738
 
               }
739
 
 
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);
744
 
               }
745
 
            }
746
 
 
747
 
            uriFreeQueryListA(queryList);
748
 
         }
749
 
      } else {
750
 
         uriFreeUriMembersA(&uri);
751
 
         Debug("Binary URI %s does not have a 'file' scheme\n", pathURIUtf8);
752
 
         return FALSE;
753
 
      }
754
 
   } else {
755
 
      uriFreeUriMembersA(&uri);
756
 
      return FALSE;
757
 
   }
758
 
 
759
 
   GHIPlatformSetMenuTracking(ghip, TRUE);
760
 
 
761
 
   /*
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.
764
 
    */
765
 
   if (realCmd[0] != '/') {
766
 
      ctmp = g_find_program_in_path(realCmd);
767
 
      if (ctmp && *ctmp) {
768
 
         free(freeMe);
769
 
         freeMe = ctmp;
770
 
         realCmd = ctmp;
771
 
      } else {
772
 
         free(ctmp);
773
 
         free(freeMe);
774
 
         return FALSE;
775
 
      }
776
 
   }
777
 
 
778
 
   if (keyfilePath) {
779
 
      ghm = g_hash_table_lookup(ghip->appsByDesktopEntry, keyfilePath);
780
 
      g_free(keyfilePath);
781
 
   }
782
 
 
783
 
   if (!ghm) {
784
 
      /*
785
 
       * Now that we have the full path, look it up in our hash table of GHIMenuItems
786
 
       */
787
 
      ghm = g_hash_table_lookup(ghip->appsByExecutable, realCmd);
788
 
   }
789
 
 
790
 
   if (!ghm) {
791
 
      /*
792
 
       * To deal with /usr/bin/gimp being a symlink to gimp-2.x, also try symlinks.
793
 
       */
794
 
      char newPath[PATH_MAX + 1];
795
 
      ssize_t linkLen;
796
 
 
797
 
      linkLen = readlink(realCmd, newPath, sizeof newPath - 1);
798
 
      if (linkLen > 0) {
799
 
         char *slashLoc;
800
 
 
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),
806
 
                                   realCmd, newPath);
807
 
            g_free(freeMe);
808
 
            realCmd = freeMe = ctmp;
809
 
         } else {
810
 
            realCmd = newPath;
811
 
         }
812
 
 
813
 
         ghm = g_hash_table_lookup(ghip->appsByExecutable, realCmd);
814
 
      }
815
 
   }
816
 
   /*
817
 
    * Stick the app name into 'buf'.
818
 
    */
819
 
   if (ghm) {
820
 
      ctmp = g_key_file_get_locale_string(ghm->keyfile, G_KEY_FILE_DESKTOP_GROUP,
821
 
                                   G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
822
 
      if (!ctmp) {
823
 
         ctmp = g_path_get_basename(realCmd);
824
 
      }
825
 
      DynBuf_AppendString(buf, ctmp);
826
 
      free(ctmp);
827
 
   } else {
828
 
      /*
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.
831
 
       */
832
 
      ctmp = strrchr(realCmd, '/');
833
 
      if (ctmp) {
834
 
         ctmp++;
835
 
      } else {
836
 
         ctmp = (char *) realCmd;
837
 
      }
838
 
      DynBuf_AppendString(buf, ctmp);
839
 
   }
840
 
 
841
 
   free(freeMe);
842
 
   ctmp = freeMe = NULL;
843
 
 
844
 
   GHIPlatformCollectIconInfo(ghip, ghm, windowID, buf);
845
 
 
846
 
   return TRUE;
847
 
#else // !GTK2
848
 
   return FALSE;
849
 
#endif // GTK2
850
 
}
851
 
 
852
 
 
853
 
/*
854
 
 *----------------------------------------------------------------------------
855
 
 *
856
 
 * GHIPlatformGetBinaryHandlers --
857
 
 *
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.
862
 
 *
863
 
 * Results:
864
 
 *      TRUE if everything went ok, FALSE otherwise.
865
 
 *
866
 
 * Side effects:
867
 
 *      None
868
 
 *
869
 
 *----------------------------------------------------------------------------
870
 
 */
871
 
 
872
 
Bool
873
 
GHIPlatformGetBinaryHandlers(GHIPlatform *ghip,      // IN: platform-specific state
874
 
                             const char *pathUtf8,   // IN: full path to the executable
875
 
                             XDR *xdrs)              // OUT: binary information
876
 
{
877
 
   return FALSE;
878
 
}
879
 
 
880
 
 
881
 
#ifdef GTK2
882
 
 
883
 
 
884
 
/*
885
 
 *-----------------------------------------------------------------------------
886
 
 *
887
 
 * GHIPlatformGetDesktopName --
888
 
 *
889
 
 *      Figures out which desktop environment we're running under.
890
 
 *
891
 
 * Results:
892
 
 *      Desktop name if successful, NULL otherwise.
893
 
 *
894
 
 * Side effects:
895
 
 *      Allocates memory to hold return value.
896
 
 *
897
 
 *-----------------------------------------------------------------------------
898
 
 */
899
 
 
900
 
static const char *
901
 
GHIPlatformGetDesktopName(void)
902
 
{
903
 
   int i;
904
 
   static const char *clientMappings[][2] = {
905
 
      {"gnome-panel", "GNOME"},
906
 
      {"gnome-session", "GNOME"},
907
 
      {"nautilus", "GNOME"},
908
 
      {"ksmserver", "KDE"},
909
 
      {"kicker", "KDE"},
910
 
      {"startkde", "KDE"},
911
 
      {"konqueror", "KDE"},
912
 
      {"xfce-mcs-manage", "XFCE"},
913
 
      {"xfwm4", "XFCE"},
914
 
      {"ROX-Session", "ROX"}
915
 
   };
916
 
   Display *display;
917
 
   Window rootWindow;
918
 
   Window temp1;        // throwaway
919
 
   Window temp2;        // throwaway
920
 
   Window *children = NULL;
921
 
   unsigned int nchildren;
922
 
   static const char *desktopEnvironment = NULL;
923
 
 
924
 
   /*
925
 
    * NB: While window managers may change during vmware-user's execution, TTBOMK
926
 
    * desktop environments cannot, so this is safe.
927
 
    */
928
 
   if (desktopEnvironment) {
929
 
      return desktopEnvironment;
930
 
   }
931
 
 
932
 
   display = gdk_x11_get_default_xdisplay();
933
 
   rootWindow = DefaultRootWindow(display);
934
 
 
935
 
   if (XQueryTree(display, rootWindow, &temp1, &temp2, &children, &nchildren) == 0) {
936
 
      return NULL;
937
 
   }
938
 
 
939
 
   for (i = 0; i < nchildren && !desktopEnvironment; i++) {
940
 
      XClassHint wmClass = { 0, 0 };
941
 
      int j;
942
 
 
943
 
      /*
944
 
       * Try WM_CLASS first, then try WM_NAME.
945
 
       */
946
 
 
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];
952
 
            }
953
 
         }
954
 
         XFree(wmClass.res_name);
955
 
         XFree(wmClass.res_class);
956
 
      }
957
 
 
958
 
      if (!desktopEnvironment) {
959
 
         char *name = NULL;
960
 
 
961
 
         if ((XFetchName(display, children[i], &name) == 0) ||
962
 
             name == NULL) {
963
 
            continue;
964
 
         }
965
 
 
966
 
         for (j = 0; j < ARRAYSIZE(clientMappings) && !desktopEnvironment; j++) {
967
 
            if (!strcmp(clientMappings[j][0], name)) {
968
 
               desktopEnvironment = clientMappings[j][1];
969
 
            }
970
 
         }
971
 
 
972
 
         XFree(name);
973
 
      }
974
 
   }
975
 
 
976
 
   XFree(children);
977
 
   return desktopEnvironment;
978
 
}
979
 
 
980
 
 
981
 
/*
982
 
 *-----------------------------------------------------------------------------
983
 
 *
984
 
 * GHIPlatformIsMenuItemAllowed --
985
 
 *
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.
988
 
 *
989
 
 * Results:
990
 
 *      TRUE if the item should be displayed, FALSE if it should not be.
991
 
 *
992
 
 * Side effects:
993
 
 *      None.
994
 
 *
995
 
 *-----------------------------------------------------------------------------
996
 
 */
997
 
 
998
 
static Bool
999
 
GHIPlatformIsMenuItemAllowed(GHIPlatform *ghip, // IN:
1000
 
                             GKeyFile *keyfile) // IN:
1001
 
{
1002
 
   const char *dtname;
1003
 
 
1004
 
   ASSERT(ghip);
1005
 
   ASSERT(keyfile);
1006
 
 
1007
 
   /*
1008
 
    * Examine the "NoDisplay" and "Hidden" properties.
1009
 
    */
1010
 
   if (g_key_file_get_boolean(keyfile,
1011
 
                              G_KEY_FILE_DESKTOP_GROUP,
1012
 
                              G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY,
1013
 
                              NULL) ||
1014
 
       g_key_file_get_boolean(keyfile,
1015
 
                              G_KEY_FILE_DESKTOP_GROUP,
1016
 
                              G_KEY_FILE_DESKTOP_KEY_HIDDEN,
1017
 
                              NULL)) {
1018
 
      Debug("%s: contains either NoDisplay or Hidden keys.\n", __func__);
1019
 
      return FALSE;
1020
 
   }
1021
 
 
1022
 
   /*
1023
 
    * NB: This may return NULL.
1024
 
    * XXX Perhaps that should be changed to return an empty string?
1025
 
    */
1026
 
   dtname = GHIPlatformGetDesktopName();
1027
 
 
1028
 
   /*
1029
 
    * Check our desktop environment name against the OnlyShowIn and NotShowIn
1030
 
    * lists.
1031
 
    *
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
1035
 
    * for.)
1036
 
    *
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.
1040
 
    */
1041
 
   {
1042
 
      gchar **onlyShowList = NULL;
1043
 
      gsize nstrings;
1044
 
 
1045
 
      onlyShowList = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1046
 
                                                G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN,
1047
 
                                                &nstrings, NULL);
1048
 
      if (onlyShowList && nstrings) {
1049
 
         Bool matchedOnlyShowIn = FALSE;
1050
 
         int i;
1051
 
 
1052
 
         if (dtname) {
1053
 
            for (i = 0; i < nstrings; i++) {
1054
 
               if (strcasecmp(dtname, onlyShowList[i]) == 0) {
1055
 
                  matchedOnlyShowIn = TRUE;
1056
 
                  break;
1057
 
               }
1058
 
            }
1059
 
         }
1060
 
 
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);
1065
 
            return FALSE;
1066
 
         }
1067
 
      }
1068
 
      g_strfreev(onlyShowList);
1069
 
   }
1070
 
 
1071
 
   if (dtname) {
1072
 
      gchar **notShowList = NULL;
1073
 
      gsize nstrings;
1074
 
      int i;
1075
 
 
1076
 
      notShowList = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1077
 
                                               G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN,
1078
 
                                               &nstrings, NULL);
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",
1083
 
                     __func__, dtname);
1084
 
               g_strfreev(notShowList);
1085
 
               return FALSE;
1086
 
            }
1087
 
         }
1088
 
      }
1089
 
      g_strfreev(notShowList);
1090
 
   }
1091
 
 
1092
 
   return TRUE;
1093
 
}
1094
 
 
1095
 
 
1096
 
/*
1097
 
 *-----------------------------------------------------------------------------
1098
 
 *
1099
 
 * GHIPlatformGetExecFromKeyFile --
1100
 
 *
1101
 
 *      Given a GLib GKeyFile, extract path(s) from the TryExec or Exec
1102
 
 *      keys, normalize them, and return them to the caller.
1103
 
 *
1104
 
 * Results:
1105
 
 *      Returns a string pointer to an absolute executable pathname on success or
1106
 
 *      NULL on failure.
1107
 
 *
1108
 
 * Side effects:
1109
 
 *      This routine returns memory allocated by GLib.  Caller is responsible
1110
 
 *      for freeing it via g_free.
1111
 
 *
1112
 
 *-----------------------------------------------------------------------------
1113
 
 */
1114
 
 
1115
 
static gchar *
1116
 
GHIPlatformGetExecFromKeyfile(GHIPlatform *ghip, // IN
1117
 
                              GKeyFile *keyfile) // IN
1118
 
{
1119
 
   gchar *exe = NULL;
1120
 
 
1121
 
   /*
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
1124
 
    * be skipped.
1125
 
    */
1126
 
   {
1127
 
      gchar *tryExec;
1128
 
 
1129
 
      tryExec = g_key_file_get_string(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1130
 
                                      G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL);
1131
 
      if (tryExec) {
1132
 
         gchar *ctmp;
1133
 
         ctmp = g_find_program_in_path(tryExec);
1134
 
 
1135
 
         if (ctmp == NULL) {
1136
 
            Debug("%s: Entry has TryExec=%s, but it was not found in our PATH.\n",
1137
 
                  __func__, tryExec);
1138
 
            g_free(tryExec);
1139
 
            return NULL;
1140
 
         }
1141
 
 
1142
 
         g_free(ctmp);
1143
 
         g_free(tryExec);
1144
 
      }
1145
 
   }
1146
 
 
1147
 
   /*
1148
 
    * Next up:  Look up Exec key and do some massaging to skip over common interpreters.
1149
 
    */
1150
 
   {
1151
 
      char *exec;
1152
 
      char **argv;
1153
 
      int argc;
1154
 
      int i;
1155
 
      gboolean parsed;
1156
 
 
1157
 
      exec = g_key_file_get_string(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1158
 
                                   G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
1159
 
 
1160
 
      if (!exec) {
1161
 
         Debug("%s: Missing Exec key.\n", __func__);
1162
 
         return NULL;
1163
 
      }
1164
 
 
1165
 
      parsed = g_shell_parse_argv(exec, &argc, &argv, NULL);
1166
 
      g_free(exec);
1167
 
 
1168
 
      if (!parsed) {
1169
 
         Debug("%s: Unable to parse shell arguments.\n", __func__);
1170
 
         return NULL;
1171
 
      }
1172
 
 
1173
 
      for (i = 0; i < argc; i++) {
1174
 
         /*
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.
1178
 
          */
1179
 
         if (!AppUtil_AppIsSkippable(argv[i])) {
1180
 
            exe = g_strdup(argv[i]);
1181
 
            break;
1182
 
         }
1183
 
      }
1184
 
      g_strfreev(argv);
1185
 
   }
1186
 
 
1187
 
   /*
1188
 
    * Turn it into a full path.  Yes, if we can't get an absolute path, we'll return
1189
 
    * NULL.
1190
 
    */
1191
 
   if (exe && *exe != '/') {
1192
 
      gchar *ctmp;
1193
 
 
1194
 
      ctmp = g_find_program_in_path(exe);
1195
 
      g_free(exe);
1196
 
      exe = ctmp;
1197
 
      if (!exe) {
1198
 
         Debug("%s: Unable to find program in PATH.\n", __func__);
1199
 
      }
1200
 
   }
1201
 
 
1202
 
   return exe;
1203
 
}
1204
 
 
1205
 
 
1206
 
/*
1207
 
 *-----------------------------------------------------------------------------
1208
 
 *
1209
 
 * GHIPlatformAddMenuItem --
1210
 
 *
1211
 
 *      Examines an application's .desktop file and inserts it into an appropriate
1212
 
 *      Unity application menu.
1213
 
 *
1214
 
 * Results:
1215
 
 *      A new GHIMenuItem will be created.  If our desired menu directory doesn't
1216
 
 *      already exist, then we'll create that, too.
1217
 
 *
1218
 
 * Side effects:
1219
 
 *      None.
1220
 
 *
1221
 
 *-----------------------------------------------------------------------------
1222
 
 */
1223
 
 
1224
 
static void
1225
 
GHIPlatformAddMenuItem(GHIPlatform *ghip,       // IN:
1226
 
                       const char *keyfilePath, // IN:
1227
 
                       GKeyFile *keyfile,       // IN:
1228
 
                       char *exePath)           // IN:
1229
 
{
1230
 
   /*
1231
 
    * A list of categories that a .desktop file should be in in order to be relayed to
1232
 
    * the host.
1233
 
    *
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.
1238
 
    *
1239
 
    * XXX See desktop-entry-spec and make use of .directory files.
1240
 
    */
1241
 
   static const char *validCategories[][2] = {
1242
 
      /*
1243
 
       * Bug 372348:
1244
 
       * menu-spec category     pretty string
1245
 
       */
1246
 
      { "AudioVideo",           "Sound & Video" },
1247
 
      { "Development",          0 },
1248
 
      { "Education",            0 },
1249
 
      { "Game",                 "Games" },
1250
 
      { "Graphics",             0 },
1251
 
      { "Network",              0 },
1252
 
      { "Office",               0 },
1253
 
      { "Settings",             0 },
1254
 
      { "System",               0 },
1255
 
      { "Utility",              0 },
1256
 
      { "Other",                0 }
1257
 
   };
1258
 
 
1259
 
   /*
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.
1263
 
    */
1264
 
   static const char *invalidCategories[] = {
1265
 
      "Screensaver",
1266
 
      "Shell",
1267
 
   };
1268
 
 
1269
 
   GHIMenuDirectory *gmd;
1270
 
   GHIMenuItem *gmi;
1271
 
   Bool foundIt = FALSE;
1272
 
   char **categories = NULL;
1273
 
   gsize numcats;
1274
 
   int kfIndex;                 // keyfile categories index/iterator
1275
 
   int vIndex;                  // validCategories index/iterator
1276
 
   int iIndex;                  // invalidCategories index/iterator
1277
 
 
1278
 
   /*
1279
 
    * Figure out if this .desktop file is in a category we want to put on our menus,
1280
 
    * and if so which one...
1281
 
    */
1282
 
   categories = g_key_file_get_string_list(keyfile, G_KEY_FILE_DESKTOP_GROUP,
1283
 
                                           G_KEY_FILE_DESKTOP_KEY_CATEGORIES,
1284
 
                                           &numcats, NULL);
1285
 
   if (categories) {
1286
 
      for (kfIndex = 0; kfIndex < numcats && !foundIt; kfIndex++) {
1287
 
         /*
1288
 
          * If any of this app's categories are in our blacklist, we'll just
1289
 
          * return prematurely.
1290
 
          */
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]);
1295
 
               return;
1296
 
            }
1297
 
         }
1298
 
 
1299
 
         /*
1300
 
          * NB:  See validCategories' comment re: "Other" being the final, default
1301
 
          * category.  It explains why we condition on ARRAYSIZE() - 1.
1302
 
          */
1303
 
         for (vIndex = 0; vIndex < ARRAYSIZE(validCategories) - 1; vIndex++) {
1304
 
            if (!strcasecmp(categories[kfIndex], validCategories[vIndex][0])) {
1305
 
               foundIt = TRUE;
1306
 
               break;
1307
 
            }
1308
 
         }
1309
 
      }
1310
 
      g_strfreev(categories);
1311
 
   }
1312
 
 
1313
 
   /*
1314
 
    * If not found, fall back to "Other".
1315
 
    */
1316
 
   if (!foundIt) {
1317
 
      vIndex = ARRAYSIZE(validCategories) - 1;
1318
 
   }
1319
 
 
1320
 
   /*
1321
 
    * We have all the information we need to create the new GHIMenuItem.
1322
 
    */
1323
 
   gmi = g_new0(GHIMenuItem, 1);
1324
 
   gmi->keyfilePath = g_strdup(keyfilePath);
1325
 
   gmi->keyfile = keyfile;
1326
 
   gmi->exepath = exePath;
1327
 
 
1328
 
   gmd = g_tree_lookup(ghip->apps, validCategories[vIndex][0]);
1329
 
 
1330
 
   if (!gmd) {
1331
 
      /*
1332
 
       * A GHIMenuDirectory object does not yet exist for the validCategory
1333
 
       * that this .desktop is in, so create that object.
1334
 
       */
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);
1341
 
   }
1342
 
 
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);
1347
 
}
1348
 
 
1349
 
 
1350
 
/*
1351
 
 *-----------------------------------------------------------------------------
1352
 
 *
1353
 
 * GHIPlatformReadDesktopFile --
1354
 
 *
1355
 
 *      Reads a .desktop file into our internal representation of the available
1356
 
 *      applications.
1357
 
 *
1358
 
 * Results:
1359
 
 *      None.
1360
 
 *
1361
 
 * Side effects:
1362
 
 *      None.
1363
 
 *
1364
 
 *-----------------------------------------------------------------------------
1365
 
 */
1366
 
 
1367
 
static void
1368
 
GHIPlatformReadDesktopFile(GHIPlatform *ghip, // IN
1369
 
                           const char *path)  // IN
1370
 
{
1371
 
   GKeyFile *keyfile = NULL;
1372
 
   gchar *exe = NULL;
1373
 
 
1374
 
   Debug("%s: Analyzing %s.\n", __func__, path);
1375
 
 
1376
 
   /*
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.
1379
 
    */
1380
 
 
1381
 
   keyfile = g_key_file_new();
1382
 
   if (!keyfile) {
1383
 
      Debug("%s: g_key_file_new failed.\n", __func__);
1384
 
      return;
1385
 
   }
1386
 
 
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__);
1391
 
      return;
1392
 
   }
1393
 
 
1394
 
   /*
1395
 
    * Okay, policy checks passed.  Next up, obtain a normalized executable path,
1396
 
    * and if successful insert it into our menus.
1397
 
    */
1398
 
 
1399
 
   exe = GHIPlatformGetExecFromKeyfile(ghip, keyfile);
1400
 
   if (exe) {
1401
 
      /* The following routine takes ownership of keyfile and exec. */
1402
 
      GHIPlatformAddMenuItem(ghip, path, keyfile, exe);
1403
 
   } else {
1404
 
      Debug("%s: Could not find executable for %s\n", __func__, path);
1405
 
      g_key_file_free(keyfile);
1406
 
   }
1407
 
}
1408
 
 
1409
 
 
1410
 
/*
1411
 
 *-----------------------------------------------------------------------------
1412
 
 *
1413
 
 * GHIPlatformReadApplicationsDir --
1414
 
 *
1415
 
 *      Reads in the .desktop files in a particular directory.
1416
 
 *
1417
 
 * Results:
1418
 
 *      None.
1419
 
 *
1420
 
 * Side effects:
1421
 
 *      None.
1422
 
 *
1423
 
 *-----------------------------------------------------------------------------
1424
 
 */
1425
 
 
1426
 
static void
1427
 
GHIPlatformReadApplicationsDir(GHIPlatform *ghip, // IN
1428
 
                               const char *dir)   // IN
1429
 
{
1430
 
   DIR *dirh;
1431
 
   struct dirent *dent;
1432
 
   GHIDirectoryWatch dirWatch;
1433
 
 
1434
 
   ASSERT(ghip);
1435
 
   ASSERT(dir);
1436
 
 
1437
 
   dirh = opendir(dir);
1438
 
   if (!dirh) {
1439
 
      return;
1440
 
   }
1441
 
 
1442
 
   dirWatch.directoryPath = strdup(dir);
1443
 
   g_array_append_val(ghip->directoriesTracked, dirWatch);
1444
 
 
1445
 
   while ((dent = readdir(dirh))) {
1446
 
      char subpath[PATH_MAX];
1447
 
      struct stat sbuf;
1448
 
      int subpathLen;
1449
 
 
1450
 
      if (!strcmp(dent->d_name, ".") ||
1451
 
          !strcmp(dent->d_name, "..") ||
1452
 
          !strcmp(dent->d_name, ".hidden")) {
1453
 
         continue;
1454
 
      }
1455
 
 
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);
1460
 
         closedir(dirh);
1461
 
         return;
1462
 
      }
1463
 
      if (dent->d_type == DT_UNKNOWN && stat(subpath, &sbuf)) {
1464
 
         continue;
1465
 
      }
1466
 
 
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);
1477
 
      }
1478
 
   }
1479
 
 
1480
 
   closedir(dirh);
1481
 
}
1482
 
 
1483
 
 
1484
 
/*
1485
 
 *-----------------------------------------------------------------------------
1486
 
 *
1487
 
 * GHIPlatformReadAllApplications --
1488
 
 *
1489
 
 *      Reads in information on all the applications that have .desktop files on this
1490
 
 *      system.
1491
 
 *
1492
 
 * Results:
1493
 
 *      None.
1494
 
 *
1495
 
 * Side effects:
1496
 
 *      ghip->applist is created.
1497
 
 *
1498
 
 *-----------------------------------------------------------------------------
1499
 
 */
1500
 
 
1501
 
static void
1502
 
GHIPlatformReadAllApplications(GHIPlatform *ghip) // IN
1503
 
{
1504
 
   ASSERT(ghip);
1505
 
 
1506
 
   if (!ghip->apps) {
1507
 
      int i;
1508
 
 
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);
1513
 
 
1514
 
      for (i = 0; i < ARRAYSIZE(desktopDirs); i++) {
1515
 
         if (StrUtil_StartsWith(desktopDirs[i], "~/")) {
1516
 
            char cbuf[PATH_MAX];
1517
 
 
1518
 
            Str_Sprintf(cbuf, sizeof cbuf, "%s/%s",
1519
 
                        g_get_home_dir(), desktopDirs[i] + 2);
1520
 
            GHIPlatformReadApplicationsDir(ghip, cbuf);
1521
 
         } else {
1522
 
            GHIPlatformReadApplicationsDir(ghip, desktopDirs[i]);
1523
 
         }
1524
 
      }
1525
 
   }
1526
 
}
1527
 
#endif // GTK2
1528
 
 
1529
 
 
1530
 
/*
1531
 
 *----------------------------------------------------------------------------
1532
 
 *
1533
 
 * GHIPlatformOpenStartMenuTree --
1534
 
 *
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.
1538
 
 *
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.
1542
 
 *
1543
 
 * Results:
1544
 
 *      TRUE if we were able to get the count successfully
1545
 
 *      FALSE otherwise
1546
 
 *
1547
 
 * Side effects:
1548
 
 *      None
1549
 
 *
1550
 
 *----------------------------------------------------------------------------
1551
 
 */
1552
 
 
1553
 
Bool
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
1558
 
{
1559
 
#ifdef GTK2
1560
 
   char temp[64];
1561
 
   GHIMenuHandle *gmh;
1562
 
   int itemCount = 0;
1563
 
   Bool retval = FALSE;
1564
 
 
1565
 
   ASSERT(ghip);
1566
 
   ASSERT(rootUtf8);
1567
 
   ASSERT(buf);
1568
 
 
1569
 
   GHIPlatformSetMenuTracking(ghip, TRUE);
1570
 
 
1571
 
   if (!ghip->menuHandles) {
1572
 
      ghip->menuHandles = g_hash_table_new(g_direct_hash, g_direct_equal);
1573
 
   }
1574
 
 
1575
 
   if (!ghip->apps) {
1576
 
      return FALSE;
1577
 
   }
1578
 
 
1579
 
   gmh = g_new0(GHIMenuHandle, 1);
1580
 
   gmh->handleID = ++ghip->nextMenuHandle;
1581
 
 
1582
 
   if (!strcmp(rootUtf8, UNITY_START_MENU_LAUNCH_FOLDER)) {
1583
 
      gmh->handleType = LAUNCH_FOLDER;
1584
 
      itemCount = g_tree_nnodes(ghip->apps);
1585
 
      retval = TRUE;
1586
 
   } else if (!strcmp(rootUtf8, UNITY_START_MENU_FIXED_FOLDER)) {
1587
 
      /*
1588
 
       * XXX Not yet implemented
1589
 
       */
1590
 
      gmh->handleType = FIXED_FOLDER;
1591
 
      retval = TRUE;
1592
 
   } else if (*rootUtf8) {
1593
 
      gmh->handleType = DIRECTORY_FOLDER;
1594
 
 
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));
1598
 
         if (gmh->gmd) {
1599
 
            itemCount = gmh->gmd->items->len;
1600
 
            retval = TRUE;
1601
 
         }
1602
 
      }
1603
 
   }
1604
 
 
1605
 
   if (!retval) {
1606
 
      g_free(gmh);
1607
 
      return retval;
1608
 
   }
1609
 
 
1610
 
   Debug("Opened start menu tree for %s with %d items, handle %d\n",
1611
 
         rootUtf8, itemCount, gmh->handleID);
1612
 
 
1613
 
   g_hash_table_insert(ghip->menuHandles, GINT_TO_POINTER(gmh->handleID), gmh);
1614
 
 
1615
 
   Str_Sprintf(temp, sizeof temp, "%d %d", gmh->handleID, itemCount);
1616
 
   DynBuf_AppendString(buf, temp);
1617
 
 
1618
 
   return TRUE;
1619
 
#else // !GTK2
1620
 
   return FALSE;
1621
 
#endif // GTK2
1622
 
}
1623
 
 
1624
 
 
1625
 
#ifdef GTK2
1626
 
/*
1627
 
 *-----------------------------------------------------------------------------
1628
 
 *
1629
 
 * GHIPlatformFindLaunchMenuItem --
1630
 
 *
1631
 
 *      A GTraverseFunc used to find the right item in the list of directories.
1632
 
 *
1633
 
 * Results:
1634
 
 *      TRUE if tree traversal should stop, FALSE otherwise.
1635
 
 *
1636
 
 * Side effects:
1637
 
 *      None.
1638
 
 *
1639
 
 *-----------------------------------------------------------------------------
1640
 
 */
1641
 
 
1642
 
static gboolean
1643
 
GHIPlatformFindLaunchMenuItem(gpointer key,   // IN
1644
 
                              gpointer value, // IN
1645
 
                              gpointer data)  // IN
1646
 
{
1647
 
   GHITreeTraversal *td;
1648
 
 
1649
 
   ASSERT(data);
1650
 
   ASSERT(value);
1651
 
   td = data;
1652
 
 
1653
 
   td->currentItem++;
1654
 
   if (td->currentItem == td->desiredItem) {
1655
 
      td->gmd = value;
1656
 
      return TRUE;
1657
 
   }
1658
 
 
1659
 
   return FALSE;
1660
 
}
1661
 
#endif // GTK2
1662
 
 
1663
 
 
1664
 
/*
1665
 
 *-----------------------------------------------------------------------------
1666
 
 *
1667
 
 * GHIPlatformMenuItemToURI --
1668
 
 *
1669
 
 *      Returns the URI that would be used to launch a particular GHI menu item
1670
 
 *
1671
 
 * Results:
1672
 
 *      Newly allocated URI string
1673
 
 *
1674
 
 * Side effects:
1675
 
 *      Allocates memory for the URI.
1676
 
 *
1677
 
 *-----------------------------------------------------------------------------
1678
 
 */
1679
 
 
1680
 
static char *
1681
 
GHIPlatformMenuItemToURI(GHIPlatform *ghip, // IN
1682
 
                         GHIMenuItem *gmi)  // IN
1683
 
{
1684
 
   gchar **argv;
1685
 
   gint argc;
1686
 
 
1687
 
   char *ctmp;
1688
 
   UriQueryListA *queryItems;
1689
 
   int i;
1690
 
   int err;
1691
 
   gboolean res;
1692
 
   int nchars;
1693
 
   char *uriString;
1694
 
   char *queryString;
1695
 
 
1696
 
   ASSERT(ghip);
1697
 
   ASSERT(gmi);
1698
 
 
1699
 
   ctmp = g_key_file_get_string(gmi->keyfile, G_KEY_FILE_DESKTOP_GROUP,
1700
 
                                G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
1701
 
 
1702
 
   res = g_shell_parse_argv(ctmp, &argc, &argv, NULL);
1703
 
   g_free(ctmp);
1704
 
   if (!res) {
1705
 
      return NULL;
1706
 
   }
1707
 
 
1708
 
   queryItems = alloca((argc + 1) * sizeof *queryItems);
1709
 
 
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];
1714
 
   }
1715
 
   queryItems[i].key = "DesktopEntry";
1716
 
   queryItems[i].value = gmi->keyfilePath;
1717
 
   queryItems[i].next = NULL;
1718
 
 
1719
 
   /*
1720
 
    * 10 + 3 * len is the formula recommended by uriparser for the maximum URI string
1721
 
    * length.
1722
 
    */
1723
 
   uriString = alloca(10 + 3 * strlen(gmi->exepath));
1724
 
   if (uriUnixFilenameToUriStringA(gmi->exepath, uriString)) {
1725
 
      g_strfreev(argv);
1726
 
      return NULL;
1727
 
   }
1728
 
   if (uriComposeQueryCharsRequiredA(queryItems, &nchars) != URI_SUCCESS) {
1729
 
      g_strfreev(argv);
1730
 
      return NULL;
1731
 
   }
1732
 
   queryString = alloca(nchars + 1);
1733
 
   err = uriComposeQueryA(queryString, queryItems, nchars + 1, &i);
1734
 
   g_strfreev(argv);
1735
 
   if (err != URI_SUCCESS) {
1736
 
      return NULL;
1737
 
   }
1738
 
 
1739
 
   return g_strdup_printf("%s?%s", uriString, queryString);
1740
 
}
1741
 
 
1742
 
 
1743
 
/*
1744
 
 *----------------------------------------------------------------------------
1745
 
 *
1746
 
 * GHIPlatformGetStartMenuItem --
1747
 
 *
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.
1751
 
 *
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.
1757
 
 *
1758
 
 * Results:
1759
 
 *      TRUE if there's an item at a given index, FALSE otherwise.
1760
 
 *
1761
 
 * Side effects:
1762
 
 *      None
1763
 
 *
1764
 
 *----------------------------------------------------------------------------
1765
 
 */
1766
 
 
1767
 
Bool
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
1772
 
{
1773
 
#ifdef GTK2
1774
 
   GHIMenuHandle *gmh;
1775
 
   char *itemName = NULL;
1776
 
   uint itemFlags = 0;
1777
 
   char *itemPath = NULL;
1778
 
   char *localizedItemName = NULL;
1779
 
   Bool freeItemName = FALSE;
1780
 
   Bool freeItemPath = FALSE;
1781
 
   Bool freeLocalItemName = FALSE;
1782
 
   char temp[64];
1783
 
 
1784
 
   ASSERT(ghip);
1785
 
   ASSERT(ghip->menuHandles);
1786
 
   ASSERT(buf);
1787
 
 
1788
 
   gmh = g_hash_table_lookup(ghip->menuHandles, GINT_TO_POINTER(handle));
1789
 
   if (!gmh) {
1790
 
      return FALSE;
1791
 
   }
1792
 
 
1793
 
   switch (gmh->handleType) {
1794
 
   case LAUNCH_FOLDER:
1795
 
      {
1796
 
         GHITreeTraversal traverseData = { -1, itemIndex, NULL };
1797
 
 
1798
 
         /*
1799
 
          * We're iterating through the list of directories.
1800
 
          */
1801
 
         if (!ghip->apps) {
1802
 
            return FALSE;
1803
 
         }
1804
 
 
1805
 
         g_tree_foreach(ghip->apps, GHIPlatformFindLaunchMenuItem, &traverseData);
1806
 
         if (!traverseData.gmd) {
1807
 
            return FALSE;
1808
 
         }
1809
 
 
1810
 
         itemPath = "";
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;
1818
 
      }
1819
 
      break;
1820
 
   case FIXED_FOLDER:
1821
 
      return FALSE;
1822
 
 
1823
 
   case DIRECTORY_FOLDER:
1824
 
      {
1825
 
         GHIMenuItem *gmi;
1826
 
 
1827
 
         if (gmh->gmd->items->len <= itemIndex) {
1828
 
            return FALSE;
1829
 
         }
1830
 
 
1831
 
         gmi = g_ptr_array_index(gmh->gmd->items, itemIndex);
1832
 
 
1833
 
         localizedItemName = g_key_file_get_locale_string(gmi->keyfile,
1834
 
                                                          G_KEY_FILE_DESKTOP_GROUP,
1835
 
                                                          G_KEY_FILE_DESKTOP_KEY_NAME,
1836
 
                                                          NULL, NULL);
1837
 
         freeLocalItemName = TRUE;
1838
 
         itemName = g_strdup_printf("%s/%s/%s", UNITY_START_MENU_LAUNCH_FOLDER,
1839
 
                                    gmh->gmd->dirname, localizedItemName);
1840
 
         freeItemName = TRUE;
1841
 
 
1842
 
         itemPath = GHIPlatformMenuItemToURI(ghip, gmi);
1843
 
         freeItemPath = TRUE;
1844
 
      }
1845
 
      break;
1846
 
   }
1847
 
 
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);
1853
 
 
1854
 
   if (freeItemName) {
1855
 
      g_free(itemName);
1856
 
   }
1857
 
   if (freeItemPath) {
1858
 
      g_free(itemPath);
1859
 
   }
1860
 
   if (freeLocalItemName) {
1861
 
      g_free(localizedItemName);
1862
 
   }
1863
 
 
1864
 
   return TRUE;
1865
 
#else // !GTK2
1866
 
   return FALSE;
1867
 
#endif // GTK2
1868
 
}
1869
 
 
1870
 
 
1871
 
/*
1872
 
 *----------------------------------------------------------------------------
1873
 
 *
1874
 
 * GHIPlatformCloseStartMenu --
1875
 
 *
1876
 
 *      Free all memory associated with this start menu tree and cleanup.
1877
 
 *
1878
 
 * Results:
1879
 
 *      TRUE if the handle is valid
1880
 
 *      FALSE otherwise
1881
 
 *
1882
 
 * Side effects:
1883
 
 *      None
1884
 
 *
1885
 
 *----------------------------------------------------------------------------
1886
 
 */
1887
 
 
1888
 
Bool
1889
 
GHIPlatformCloseStartMenuTree(GHIPlatform *ghip, // IN: platform-specific state
1890
 
                              uint32 handle)     // IN: handle to the tree to be closed
1891
 
{
1892
 
#ifdef GTK2
1893
 
   GHIMenuHandle *gmh;
1894
 
 
1895
 
   ASSERT(ghip);
1896
 
   if (!ghip->menuHandles) {
1897
 
      return TRUE;
1898
 
   }
1899
 
 
1900
 
   gmh = g_hash_table_lookup(ghip->menuHandles, GINT_TO_POINTER(handle));
1901
 
   if (!gmh) {
1902
 
      return TRUE;
1903
 
   }
1904
 
 
1905
 
   g_hash_table_remove(ghip->menuHandles, GINT_TO_POINTER(gmh->handleID));
1906
 
   GHIPlatformFreeValue(NULL, gmh, NULL);
1907
 
 
1908
 
   return TRUE;
1909
 
#else // !GTK2
1910
 
   return FALSE;
1911
 
#endif // GTK2
1912
 
}
1913
 
 
1914
 
 
1915
 
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1916
 
/*
1917
 
 *-----------------------------------------------------------------------------
1918
 
 *
1919
 
 * GHIPlatformFindHGFSShare --
1920
 
 *
1921
 
 *      Finds the filesystem path to a particular HGFS sharename
1922
 
 *
1923
 
 * Results:
1924
 
 *      Newly heap-allocated path to the top of the specified share.
1925
 
 *
1926
 
 * Side effects:
1927
 
 *      Allocates memory for the return value.
1928
 
 *
1929
 
 *-----------------------------------------------------------------------------
1930
 
 */
1931
 
 
1932
 
static char *
1933
 
GHIPlatformFindHGFSShare(GHIPlatform *ghip,              // IN
1934
 
                         const UriTextRangeA *sharename) // IN
1935
 
{
1936
 
   FILE *fh;
1937
 
   struct mntent *ment;
1938
 
 
1939
 
   fh = Posix_Setmntent(_PATH_MOUNTED, "r");
1940
 
   if (!fh) {
1941
 
      return NULL;
1942
 
   }
1943
 
 
1944
 
   while ((ment = Posix_Getmntent(fh))) {
1945
 
      char *fsSharename;
1946
 
      if (strcmp(ment->mnt_type, "hgfs") && strcmp(ment->mnt_type, "vmhgfs")) {
1947
 
         continue;
1948
 
      }
1949
 
 
1950
 
      if (!StrUtil_StartsWith(ment->mnt_fsname, ".host:")) {
1951
 
         Warning("HGFS filesystem has an fsname of \"%s\" rather than \".host:...\"\n",
1952
 
                 ment->mnt_fsname);
1953
 
         continue;
1954
 
      }
1955
 
 
1956
 
      if (ment->mnt_fsname[strlen(".host:")] == '/') {
1957
 
         fsSharename = ment->mnt_fsname + strlen(".host:/");
1958
 
      } else {
1959
 
         fsSharename = ment->mnt_fsname + strlen(".host:");
1960
 
      }
1961
 
 
1962
 
      /*
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.
1967
 
       */
1968
 
      if (URI_TEXTRANGE_EQUAL(*sharename, fsSharename)) {
1969
 
         char *retval = g_strdup(ment->mnt_dir);
1970
 
 
1971
 
         fclose(fh);
1972
 
 
1973
 
         return retval;
1974
 
      } else if (fsSharename == '\0') {
1975
 
         /*
1976
 
          * This is a mount of the toplevel HGFS directory, so we know it should work.
1977
 
          */
1978
 
         char *retval = g_strdup_printf("%s/%.*s",
1979
 
                                        ment->mnt_dir,
1980
 
                                        (int)(sharename->afterLast - sharename->first),
1981
 
                                        sharename->first);
1982
 
         fclose(fh);
1983
 
         return retval;
1984
 
      }
1985
 
   }
1986
 
   fclose(fh);
1987
 
 
1988
 
   return NULL;
1989
 
}
1990
 
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
1991
 
 
1992
 
 
1993
 
/*
1994
 
 *-----------------------------------------------------------------------------
1995
 
 *
1996
 
 * GHIPlatformUriPathToString --
1997
 
 *
1998
 
 *      Turns a UriPathSegment sequence into a '/' separated filesystem path.
1999
 
 *
2000
 
 * Results:
2001
 
 *      Newly heap-allocated string containing the FS path.
2002
 
 *
2003
 
 * Side effects:
2004
 
 *      Allocates memory (caller is responsible for freeing it).
2005
 
 *
2006
 
 *-----------------------------------------------------------------------------
2007
 
 */
2008
 
 
2009
 
static char *
2010
 
GHIPlatformUriPathToString(UriPathSegmentA *path) // IN
2011
 
{
2012
 
   GString *str;
2013
 
   char *retval;
2014
 
   UriPathSegmentA *cur;
2015
 
 
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);
2020
 
   }
2021
 
 
2022
 
   retval = str->str;
2023
 
   g_string_free(str, FALSE);
2024
 
 
2025
 
   return retval;
2026
 
}
2027
 
 
2028
 
 
2029
 
/*
2030
 
 *-----------------------------------------------------------------------------
2031
 
 *
2032
 
 * GHIPlatformURIToArgs --
2033
 
 *
2034
 
 *      Turns a URI into an array of arguments that are useable for execing...
2035
 
 *
2036
 
 * Results:
2037
 
 *      TRUE if successful, FALSE otherwise.
2038
 
 *
2039
 
 * Side effects:
2040
 
 *      Allocates an array of strings, and returns it in *argv...
2041
 
 *
2042
 
 *-----------------------------------------------------------------------------
2043
 
 */
2044
 
 
2045
 
static Bool
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
2051
 
{
2052
 
   UriParserStateA state;
2053
 
   UriUriA uri;
2054
 
   Bool parseQueryString = TRUE;
2055
 
   GPtrArray *newargv;
2056
 
 
2057
 
   ASSERT(ghip);
2058
 
   ASSERT(uriString);
2059
 
   ASSERT(argv);
2060
 
   ASSERT(argc);
2061
 
   ASSERT(dotDesktopPath);
2062
 
 
2063
 
   memset(&state, 0, sizeof state);
2064
 
   memset(&uri, 0, sizeof uri);
2065
 
   state.uri = &uri;
2066
 
   if (uriParseUriA(&state, uriString) != URI_SUCCESS) {
2067
 
      uriFreeUriMembersA(&uri);
2068
 
      return FALSE;
2069
 
   }
2070
 
 
2071
 
   newargv = g_ptr_array_new();
2072
 
 
2073
 
#if 0 // Temporary until ShellAction is implemented.
2074
 
   /*
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.
2077
 
    */
2078
 
   if (URI_TEXTRANGE_EQUAL(uri.scheme, "x-vmware-share")) {
2079
 
      UriTextRangeA *sharename;
2080
 
      UriPathSegmentA *sharepath;
2081
 
      char *sharedir;
2082
 
      char *subdir;
2083
 
 
2084
 
      /*
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
2087
 
       */
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;
2094
 
      } else {
2095
 
         NOT_REACHED();
2096
 
      }
2097
 
 
2098
 
      sharedir = GHIPlatformFindHGFSShare(ghip, sharename);
2099
 
      if (!sharedir) {
2100
 
         uriFreeUriMembersA(&uri);
2101
 
         g_ptr_array_free(newargv, TRUE);
2102
 
         Debug("Couldn't find a mounted HGFS filesystem for %s\n", uriString);
2103
 
         return FALSE;
2104
 
      }
2105
 
 
2106
 
      subdir = GHIPlatformUriPathToString(sharepath);
2107
 
      g_ptr_array_add(newargv, g_strconcat(sharedir, subdir, NULL));
2108
 
      g_free(sharedir);
2109
 
      g_free(subdir);
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"));
2116
 
      } else {
2117
 
         Debug("Don't know how to handle URI %s. "
2118
 
               "We definitely don't have /usr/bin/gnome-open.\n",
2119
 
               uriString);
2120
 
         NOT_IMPLEMENTED();
2121
 
      }
2122
 
   }
2123
 
#endif // Temporary until ShellAction is implemented.
2124
 
 
2125
 
   if (URI_TEXTRANGE_EQUAL(uri.scheme, "file")) {
2126
 
      char *fspath = GHIPlatformUriPathToString(uri.pathHead);
2127
 
      g_ptr_array_add(newargv, fspath);
2128
 
   } else {
2129
 
      /*
2130
 
       * Just append the unparsed URI as-is onto the command line.
2131
 
       */
2132
 
      g_ptr_array_add(newargv, g_strdup(uriString));
2133
 
      parseQueryString = FALSE;
2134
 
   }
2135
 
 
2136
 
   *dotDesktopPath = NULL;
2137
 
   if (parseQueryString) {
2138
 
      /*
2139
 
       * We may need additional command-line arguments from the part of the URI after the
2140
 
       * '?'.
2141
 
       */
2142
 
 
2143
 
      UriQueryListA *queryList;
2144
 
      int itemCount;
2145
 
 
2146
 
      if (uriDissectQueryMallocA(&queryList, &itemCount,
2147
 
                                 uri.query.first, uri.query.afterLast) == URI_SUCCESS) {
2148
 
         UriQueryListA *cur;
2149
 
 
2150
 
         for (cur = queryList; cur; cur = cur->next) {
2151
 
            if (!cur->value) {
2152
 
               continue;
2153
 
            }
2154
 
 
2155
 
            if (strcmp(cur->key, "argv[]") == 0) {
2156
 
               g_ptr_array_add(newargv, g_strdup(cur->value));
2157
 
               cur->value = NULL;
2158
 
            } else if (strcmp(cur->key, "DesktopEntry")) {
2159
 
               *dotDesktopPath = g_strdup(cur->value);
2160
 
            }
2161
 
         }
2162
 
 
2163
 
         uriFreeQueryListA(queryList);
2164
 
      } else {
2165
 
         Warning("Dissection of query string in URI %s failed\n",
2166
 
                 uriString);
2167
 
      }
2168
 
   }
2169
 
 
2170
 
   uriFreeUriMembersA(&uri);
2171
 
 
2172
 
   *argc = newargv->len;
2173
 
   g_ptr_array_add(newargv, NULL);
2174
 
   *argv = (char **) g_ptr_array_free(newargv, FALSE);
2175
 
 
2176
 
   return TRUE;
2177
 
}
2178
 
 
2179
 
 
2180
 
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2181
 
/*
2182
 
 *-----------------------------------------------------------------------------
2183
 
 *
2184
 
 * GHIPlatformStripFieldCodes --
2185
 
 *
2186
 
 *      Strip field codes from an argv-style string array.
2187
 
 *
2188
 
 * Results:
2189
 
 *      None.
2190
 
 *
2191
 
 * Side effects:
2192
 
 *      Modifies the string array, possibly freeing some members.
2193
 
 *
2194
 
 *-----------------------------------------------------------------------------
2195
 
 */
2196
 
 
2197
 
static void
2198
 
GHIPlatformStripFieldCodes(char **argv, // IN/OUT
2199
 
                           int *argc)   // IN/OUT
2200
 
{
2201
 
   int i;
2202
 
 
2203
 
   ASSERT(argv);
2204
 
   ASSERT(argc);
2205
 
 
2206
 
   for (i = 0; i < *argc; i++) {
2207
 
      if (argv[i][0] == '%'
2208
 
          && argv[i][1] != '\0'
2209
 
          && argv[i][2] == '\0') {
2210
 
         g_free(argv[i]);
2211
 
         /*
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.
2214
 
          */
2215
 
         g_memmove(argv + i, argv + i + 1,
2216
 
                   (*argc - i) * sizeof *argv);
2217
 
         (*argc)--;
2218
 
      }
2219
 
   }
2220
 
}
2221
 
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2222
 
 
2223
 
 
2224
 
/*
2225
 
 *-----------------------------------------------------------------------------
2226
 
 *
2227
 
 * GHIPlatformCombineArgs --
2228
 
 *
2229
 
 *      Takes a target URI and turns it into an argv array that we can actually
2230
 
 *      exec().
2231
 
 *
2232
 
 *      XXX TODO: accept location arguments once ShellAction is implemented.
2233
 
 *
2234
 
 * Results:
2235
 
 *      TRUE if successful, FALSE otherwise. If TRUE, fullArgv/fullArgc will
2236
 
 *      contain the exec-able argument array.
2237
 
 *
2238
 
 * Side effects:
2239
 
 *      Allocates a string array in fullArgv (owner is responsible for freeing).
2240
 
 *
2241
 
 *-----------------------------------------------------------------------------
2242
 
 */
2243
 
 
2244
 
static Bool
2245
 
GHIPlatformCombineArgs(GHIPlatform *ghip,            // IN
2246
 
                       const char *targetUtf8,       // IN
2247
 
                       char ***fullArgv,             // OUT
2248
 
                       int *fullArgc)                // OUT
2249
 
{
2250
 
   char **targetArgv = NULL;
2251
 
   int targetArgc = 0;
2252
 
   char *targetDotDesktop = NULL;
2253
 
   GPtrArray *fullargs = g_ptr_array_new();
2254
 
   GHIMenuItem *ghm = NULL;
2255
 
   int i;
2256
 
 
2257
 
   ASSERT(ghip);
2258
 
   ASSERT(targetUtf8);
2259
 
   ASSERT(fullArgv);
2260
 
   ASSERT(fullArgc);
2261
 
 
2262
 
   if (!GHIPlatformURIToArgs(ghip,
2263
 
                             targetUtf8,
2264
 
                             &targetArgv,
2265
 
                             &targetArgc,
2266
 
                             &targetDotDesktop)) {
2267
 
      Debug("Parsing URI %s failed\n", targetUtf8);
2268
 
      return FALSE;
2269
 
   }
2270
 
 
2271
 
#if 0 // Temporary until ShellAction is implemented.
2272
 
   /*
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.
2276
 
    */
2277
 
 
2278
 
   /*
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.
2282
 
    *
2283
 
    * XXX for some random older programs, we may want to do concatenation in the future.
2284
 
    */
2285
 
   char **srcArgv;
2286
 
   int srcArgc;
2287
 
   char *srcDotDesktop = NULL;
2288
 
 
2289
 
   /*
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...
2292
 
    */
2293
 
   if (! *actionArgc) {
2294
 
      srcArgv = *fileArgv;
2295
 
      srcArgc = *fileArgc;
2296
 
      srcDotDesktop = fileDotDesktop;
2297
 
   } else {
2298
 
      srcArgv = *actionArgv;
2299
 
      srcArgc = *actionArgc;
2300
 
      srcDotDesktop = actionDotDesktop;
2301
 
      if (fileDotDesktop) {
2302
 
         GHIPlatformStripFieldCodes(*fileArgv, fileArgc);
2303
 
      }
2304
 
   }
2305
 
#endif // Temporary until ShellAction is implemented.
2306
 
 
2307
 
   for (i = 0; i < targetArgc; i++) {
2308
 
      const char *thisarg = targetArgv[i];
2309
 
 
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
2314
 
            /*
2315
 
             * XXX TODO: add file location arguments
2316
 
             */
2317
 
            //if (srcArgv != *fileArgv && *fileArgc) {
2318
 
            //   g_ptr_array_add(fullargs, g_strdup((*fileArgv)[0]));
2319
 
            //}
2320
 
            break;
2321
 
         case 'U': // %U expands to multiple URLs
2322
 
         case 'u': // %u expands to a URL
2323
 
            /*
2324
 
             * XXX TODO: add URL location arguments
2325
 
             */
2326
 
            //if (srcArgv != *fileArgv && fileUtf8) {
2327
 
            //   g_ptr_array_add(fullargs, g_strdup(fileUtf8));
2328
 
            //}
2329
 
            break;
2330
 
 
2331
 
            /*
2332
 
             * These three require getting at the .desktop info for the app.
2333
 
             */
2334
 
         case 'k':
2335
 
         case 'i':
2336
 
         case 'c':
2337
 
            if (!ghm && targetDotDesktop) {
2338
 
               ghm = g_hash_table_lookup(ghip->appsByDesktopEntry,
2339
 
                                         targetDotDesktop);
2340
 
            }
2341
 
            if (!ghm) {
2342
 
               ASSERT (fullargs->len > 0);
2343
 
               ghm = g_hash_table_lookup(ghip->appsByExecutable,
2344
 
                                         g_ptr_array_index(fullargs, 0));
2345
 
            }
2346
 
 
2347
 
            if (ghm) {
2348
 
               switch (thisarg[1]) {
2349
 
               case 'c': // %c expands to the .desktop's Name=
2350
 
                  {
2351
 
                     char *ctmp =
2352
 
                        g_key_file_get_locale_string(ghm->keyfile,
2353
 
                                                     G_KEY_FILE_DESKTOP_GROUP,
2354
 
                                                     G_KEY_FILE_DESKTOP_KEY_NAME,
2355
 
                                                     NULL, NULL);
2356
 
                     if (ctmp) {
2357
 
                        g_ptr_array_add(fullargs, ctmp);
2358
 
                     }
2359
 
                  }
2360
 
                  break;
2361
 
               case 'i': // %i expands to "--icon" then the .desktop's Icon=
2362
 
                  {
2363
 
                     char *ctmp =
2364
 
                        g_key_file_get_string(ghm->keyfile,
2365
 
                                              G_KEY_FILE_DESKTOP_GROUP,
2366
 
                                              G_KEY_FILE_DESKTOP_KEY_ICON,
2367
 
                                              NULL);
2368
 
                     if (ctmp && *ctmp) {
2369
 
                        g_ptr_array_add(fullargs, g_strdup("--icon"));
2370
 
                        g_ptr_array_add(fullargs, ctmp);
2371
 
                     }
2372
 
                  }
2373
 
                  break;
2374
 
               case 'k': // %k expands to the .desktop's path
2375
 
                  g_ptr_array_add(fullargs, g_strdup(ghm->keyfilePath));
2376
 
                  break;
2377
 
               }
2378
 
            }
2379
 
            break;
2380
 
         case '%': // Expands to a literal
2381
 
            g_ptr_array_add(fullargs, g_strdup("%"));
2382
 
            break;
2383
 
         default:
2384
 
            /*
2385
 
             * Intentionally ignore an unknown field code.
2386
 
             */
2387
 
            break;
2388
 
         }
2389
 
      } else {
2390
 
         g_ptr_array_add(fullargs, g_strdup(thisarg));
2391
 
      }
2392
 
   }
2393
 
   *fullArgc = fullargs->len;
2394
 
   g_ptr_array_add(fullargs, NULL);
2395
 
   *fullArgv = (char **) g_ptr_array_free(fullargs, FALSE);
2396
 
 
2397
 
   g_strfreev(targetArgv);
2398
 
   g_free(targetDotDesktop);
2399
 
 
2400
 
   return *fullArgc ? TRUE : FALSE;
2401
 
}
2402
 
 
2403
 
 
2404
 
/*
2405
 
 *----------------------------------------------------------------------------
2406
 
 *
2407
 
 * GHIPlatformShellOpen --
2408
 
 *
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).
2412
 
 *
2413
 
 * Results:
2414
 
 *      TRUE if successful, FALSE otherwise.
2415
 
 *
2416
 
 * Side effects:
2417
 
 *      None
2418
 
 *
2419
 
 *----------------------------------------------------------------------------
2420
 
 */
2421
 
 
2422
 
Bool
2423
 
GHIPlatformShellOpen(GHIPlatform *ghip,    // IN
2424
 
                     const char *fileUtf8) // IN
2425
 
{
2426
 
   char **fullArgv = NULL;
2427
 
   int fullArgc = 0;
2428
 
   Bool retval = FALSE;
2429
 
 
2430
 
   ASSERT(ghip);
2431
 
   ASSERT(fileUtf8);
2432
 
 
2433
 
   Debug("%s: file: '%s'\n", __FUNCTION__, fileUtf8);
2434
 
 
2435
 
   if (GHIPlatformCombineArgs(ghip, fileUtf8, &fullArgv, &fullArgc) &&
2436
 
       fullArgc > 0) {
2437
 
      retval = g_spawn_async(NULL, fullArgv,
2438
 
                            /*
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.
2443
 
                             *
2444
 
                             * Comment stolen from GuestAppX11OpenUrl.
2445
 
                             */
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);
2451
 
   }
2452
 
 
2453
 
   g_strfreev(fullArgv);
2454
 
 
2455
 
   return retval;
2456
 
}
2457
 
 
2458
 
 
2459
 
/*
2460
 
 *----------------------------------------------------------------------------
2461
 
 *
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
2466
 
 *      Tools < NNNNN).
2467
 
 *      See the comment at ghIntegration.c::GHITcloShellAction for information
2468
 
 *      on the command format and supported actions.
2469
 
 *
2470
 
 * Results:
2471
 
 *      TRUE if successful, FALSE otherwise.
2472
 
 *
2473
 
 * Side effects:
2474
 
 *      None
2475
 
 *
2476
 
 *----------------------------------------------------------------------------
2477
 
 */
2478
 
 
2479
 
Bool
2480
 
GHIPlatformShellAction(GHIPlatform *ghip,       // IN: platform-specific state
2481
 
                       const XDR *xdrs)         // IN: XDR Serialized arguments
2482
 
{
2483
 
   /*
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.
2487
 
    */
2488
 
   ASSERT(ghip);
2489
 
   ASSERT(xdrs);
2490
 
 
2491
 
   Debug("%s not implemented yet.\n", __FUNCTION__);
2492
 
 
2493
 
   return FALSE;
2494
 
}
2495
 
 
2496
 
 
2497
 
#if 0 // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2498
 
/*
2499
 
 *----------------------------------------------------------------------------
2500
 
 *
2501
 
 * GHIPlatformShellUrlOpen --
2502
 
 *
2503
 
 *      Run ShellExecute on a given file.
2504
 
 *
2505
 
 * Results:
2506
 
 *      TRUE if success, FALSE otherwise.
2507
 
 *
2508
 
 * Side effects:
2509
 
 *      None
2510
 
 *
2511
 
 *----------------------------------------------------------------------------
2512
 
 */
2513
 
 
2514
 
Bool
2515
 
GHIPlatformShellUrlOpen(GHIPlatform *ghip,      // IN: platform-specific state
2516
 
                        const char *fileUtf8,   // IN: command/file
2517
 
                        const char *actionUtf8) // IN: action
2518
 
{
2519
 
#ifdef GTK2
2520
 
   char **fileArgv = NULL;
2521
 
   int fileArgc = 0;
2522
 
   char *fileDotDesktop = NULL;
2523
 
   char **actionArgv = NULL;
2524
 
   int actionArgc = 0;
2525
 
   char *actionDotDesktop = NULL;
2526
 
   char **fullArgv = NULL;
2527
 
   int fullArgc = 0;
2528
 
 
2529
 
   Bool retval = FALSE;
2530
 
 
2531
 
   ASSERT(ghip);
2532
 
 
2533
 
   if (!GHIPlatformURIToArgs(ghip, fileUtf8, &fileArgv, &fileArgc,
2534
 
                             &fileDotDesktop)) {
2535
 
      Debug("Parsing URI %s failed\n", fileUtf8);
2536
 
      return FALSE;
2537
 
   }
2538
 
 
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);
2544
 
      return FALSE;
2545
 
   }
2546
 
 
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);
2556
 
   }
2557
 
 
2558
 
   g_strfreev(fileArgv);
2559
 
   g_free(fileDotDesktop);
2560
 
   g_strfreev(actionArgv);
2561
 
   g_free(actionDotDesktop);
2562
 
   g_strfreev(fullArgv);
2563
 
 
2564
 
   return retval;
2565
 
#else // !GTK2
2566
 
   return FALSE;
2567
 
#endif // GTK2
2568
 
}
2569
 
#endif // REMOVE AFTER IMPLEMENTING GHIPlatformShellAction
2570
 
 
2571
 
 
2572
 
/*
2573
 
 *----------------------------------------------------------------------------
2574
 
 *
2575
 
 * GHIPlatformSetGuestHandler --
2576
 
 *
2577
 
 *      Set the handler for the specified filetype (or URL protocol) to the
2578
 
 *      given value.
2579
 
 *
2580
 
 * Results:
2581
 
 *      TRUE if successful, FALSE otherwise.
2582
 
 *
2583
 
 * Side effects:
2584
 
 *      None
2585
 
 *
2586
 
 *----------------------------------------------------------------------------
2587
 
 */
2588
 
 
2589
 
Bool
2590
 
GHIPlatformSetGuestHandler(GHIPlatform *ghip,    // IN: platform-specific state
2591
 
                           const XDR *xdrs)      // IN: XDR Serialized arguments
2592
 
{
2593
 
   ASSERT(ghip);
2594
 
   ASSERT(xdrs);
2595
 
 
2596
 
   return FALSE;
2597
 
}
2598
 
 
2599
 
 
2600
 
/*
2601
 
 *----------------------------------------------------------------------------
2602
 
 *
2603
 
 * GHIPlatformRestoreDefaultGuestHandler --
2604
 
 *
2605
 
 *      Restore the handler for a given type to the value in use before any
2606
 
 *      changes by tools.
2607
 
 *
2608
 
 * Results:
2609
 
 *      TRUE if successful, FALSE otherwise.
2610
 
 *
2611
 
 * Side effects:
2612
 
 *      None
2613
 
 *
2614
 
 *----------------------------------------------------------------------------
2615
 
 */
2616
 
 
2617
 
Bool
2618
 
GHIPlatformRestoreDefaultGuestHandler(GHIPlatform *ghip,  // IN: platform-specific state
2619
 
                                      const XDR *xdrs)    // IN: XDR Serialized arguments
2620
 
{
2621
 
   ASSERT(ghip);
2622
 
   ASSERT(xdrs);
2623
 
 
2624
 
   return FALSE;
2625
 
}
2626
 
 
2627
 
 
2628
 
/*
2629
 
 *-----------------------------------------------------------------------------
2630
 
 *
2631
 
 * GHIPlatformSetMenuTracking --
2632
 
 *
2633
 
 *      Turns menu tracking on/off.
2634
 
 *
2635
 
 *      XXX needs additional implementation work, as per the comment above
2636
 
 *      GHIDirectoryWatch.
2637
 
 *
2638
 
 * Results:
2639
 
 *      None.
2640
 
 *
2641
 
 * Side effects:
2642
 
 *      None.
2643
 
 *
2644
 
 *-----------------------------------------------------------------------------
2645
 
 */
2646
 
 
2647
 
static void
2648
 
GHIPlatformSetMenuTracking(GHIPlatform *ghip, // IN
2649
 
                           Bool isEnabled)    // IN
2650
 
{
2651
 
   int i;
2652
 
   ASSERT(ghip);
2653
 
 
2654
 
   if (isEnabled == ghip->trackingEnabled) {
2655
 
      return;
2656
 
   }
2657
 
 
2658
 
   ghip->trackingEnabled = isEnabled;
2659
 
   if (isEnabled) {
2660
 
      GHIPlatformReadAllApplications(ghip);
2661
 
   } else {
2662
 
      GHIPlatformCleanupMenuEntries(ghip);
2663
 
 
2664
 
      for (i = 0; i < ghip->directoriesTracked->len; i++) {
2665
 
         GHIDirectoryWatch *dirWatch;
2666
 
 
2667
 
         dirWatch = &g_array_index(ghip->directoriesTracked, GHIDirectoryWatch, i);
2668
 
         g_free(dirWatch->directoryPath);
2669
 
      }
2670
 
      g_array_set_size(ghip->directoriesTracked, 0);
2671
 
   }
2672
 
}
2673
 
 
2674
 
 
2675
 
/*
2676
 
 *------------------------------------------------------------------------------
2677
 
 *
2678
 
 * GHIPlatformGetProtocolHandlers --
2679
 
 *
2680
 
 *     XXX Needs to be implemented for Linux/X11 guests.
2681
 
 *     Retrieve the list of protocol handlers from the guest.
2682
 
 *
2683
 
 * Results:
2684
 
 *     TRUE on success
2685
 
 *     FALSE on error
2686
 
 *
2687
 
 * Side effects:
2688
 
 *     None
2689
 
 *
2690
 
 *------------------------------------------------------------------------------
2691
 
 */
2692
 
 
2693
 
Bool
2694
 
GHIPlatformGetProtocolHandlers(GHIPlatform *ghip,                           // UNUSED
2695
 
                               GHIProtocolHandlerList *protocolHandlerList) // IN
2696
 
{
2697
 
   return FALSE;
2698
 
}
2699
 
 
2700
 
 
2701
 
/*
2702
 
 *----------------------------------------------------------------------------
2703
 
 *
2704
 
 * GHIPlatformSetOutlookTempFolder --
2705
 
 *
2706
 
 *    Set the temporary folder used by Microsoft Outlook to store attachments
2707
 
 *    opened by the user.
2708
 
 *
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.
2711
 
 *
2712
 
 * Results:
2713
 
 *    TRUE if successful, FALSE otherwise.
2714
 
 *
2715
 
 * Side effects:
2716
 
 *    None
2717
 
 *
2718
 
 *----------------------------------------------------------------------------
2719
 
 */
2720
 
 
2721
 
Bool
2722
 
GHIPlatformSetOutlookTempFolder(GHIPlatform *ghip,  // IN: platform-specific state
2723
 
                                const XDR *xdrs)    // IN: XDR Serialized arguments
2724
 
{
2725
 
   ASSERT(ghip);
2726
 
   ASSERT(xdrs);
2727
 
 
2728
 
   return FALSE;
2729
 
}
2730
 
 
2731
 
 
2732
 
/*
2733
 
 *----------------------------------------------------------------------------
2734
 
 *
2735
 
 * GHIPlatformRestoreOutlookTempFolder --
2736
 
 *
2737
 
 *    Set the temporary folder used by Microsoft Outlook to store attachments
2738
 
 *    opened by the user.
2739
 
 *
2740
 
 * Results:
2741
 
 *    TRUE if successful, FALSE otherwise.
2742
 
 *
2743
 
 * Side effects:
2744
 
 *    None
2745
 
 *
2746
 
 *----------------------------------------------------------------------------
2747
 
 */
2748
 
 
2749
 
Bool
2750
 
GHIPlatformRestoreOutlookTempFolder(GHIPlatform *ghip)  // IN: platform-specific state
2751
 
{
2752
 
   ASSERT(ghip);
2753
 
 
2754
 
   return FALSE;
2755
 
}
2756
 
 
2757
 
 
2758
 
/**
2759
 
 * @brief Performs an action on the Trash (aka Recycle Bin) folder.
2760
 
 *
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.
2763
 
 *
2764
 
 * @param[in] ghip Pointer to platform-specific GHI data.
2765
 
 * @param[in] xdrs Pointer to XDR serialized arguments.
2766
 
 *
2767
 
 * @retval TRUE  The action was performed.
2768
 
 * @retval FALSE The action couldn't be performed.
2769
 
 */
2770
 
 
2771
 
Bool
2772
 
GHIPlatformTrashFolderAction(GHIPlatform *ghip,
2773
 
                             const XDR   *xdrs)
2774
 
{
2775
 
   ASSERT(ghip);
2776
 
   ASSERT(xdrs);
2777
 
   return FALSE;
2778
 
}
2779
 
 
2780
 
 
2781
 
/* @brief Returns the icon of the Trash (aka Recycle Bin) folder.
2782
 
 *
2783
 
 * Gets the icon of the Trash (aka Recycle Bin) folder, and returns it
2784
 
 * to the host.
2785
 
 *
2786
 
 * @param[in]  ghip Pointer to platform-specific GHI data.
2787
 
 * @param[out] xdrs Pointer to XDR serialized data to send to the host.
2788
 
 *
2789
 
 * @retval TRUE  The icon was fetched successfully.
2790
 
 * @retval FALSE The icon could not be fetched.
2791
 
 */
2792
 
 
2793
 
Bool
2794
 
GHIPlatformTrashFolderGetIcon(GHIPlatform *ghip,
2795
 
                              XDR         *xdrs)
2796
 
{
2797
 
   ASSERT(ghip);
2798
 
   ASSERT(xdrs);
2799
 
   return FALSE;
2800
 
}
2801
 
 
2802
 
/* @brief Send a mouse or keyboard event to a tray icon.
2803
 
 *
2804
 
 * @param[in] ghip Pointer to platform-specific GHI data.
2805
 
 *
2806
 
 * @retval TRUE Operation Succeeded.
2807
 
 * @retval FALSE Operation Failed.
2808
 
 */
2809
 
 
2810
 
Bool
2811
 
GHIPlatformTrayIconSendEvent(GHIPlatform *ghip,
2812
 
                             const XDR   *xdrs)
2813
 
{
2814
 
   ASSERT(ghip);
2815
 
   ASSERT(xdrs);
2816
 
   return FALSE;
2817
 
}
2818
 
 
2819
 
/* @brief Start sending tray icon updates to the VMX.
2820
 
 *
2821
 
 * @param[in] ghip Pointer to platform-specific GHI data.
2822
 
 *
2823
 
 * @retval TRUE Operation Succeeded.
2824
 
 * @retval FALSE Operation Failed.
2825
 
 */
2826
 
 
2827
 
Bool
2828
 
GHIPlatformTrayIconStartUpdates(GHIPlatform *ghip)
2829
 
{
2830
 
   ASSERT(ghip);
2831
 
   return FALSE;
2832
 
}
2833
 
 
2834
 
/* @brief Stop sending tray icon updates to the VMX.
2835
 
 *
2836
 
 * @param[in] ghip Pointer to platform-specific GHI data.
2837
 
 *
2838
 
 * @retval TRUE Operation Succeeded.
2839
 
 * @retval FALSE Operation Failed.
2840
 
 */
2841
 
 
2842
 
Bool
2843
 
GHIPlatformTrayIconStopUpdates(GHIPlatform *ghip)
2844
 
{
2845
 
   ASSERT(ghip);
2846
 
   return FALSE;
2847
 
}
2848
 
 
2849
 
/* @brief Set a window to be focused.
2850
 
 *
2851
 
 * @param[in] ghip Pointer to platform-specific GHI data.
2852
 
 * @param[in] xdrs Pointer to serialized data from the host.
2853
 
 *
2854
 
 * @retval TRUE Operation Succeeded.
2855
 
 * @retval FALSE Operation Failed.
2856
 
 */
2857
 
 
2858
 
Bool
2859
 
GHIPlatformSetFocusedWindow(GHIPlatform *ghip,
2860
 
                            const XDR   *xdrs)
2861
 
{
2862
 
   ASSERT(ghip);
2863
 
   ASSERT(xdrs);
2864
 
   return FALSE;
2865
 
}
2866
 
 
2867
 
 
2868
 
/**
2869
 
 * @brief Get the hash (or timestamp) of information returned by
2870
 
 * GHIPlatformGetBinaryInfo.
2871
 
 *
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.
2875
 
 *
2876
 
 * @retval TRUE Operation succeeded.
2877
 
 * @retval FALSE Operation failed.
2878
 
 */
2879
 
 
2880
 
Bool GHIPlatformGetExecInfoHash(GHIPlatform *ghip,
2881
 
                                const GHIGetExecInfoHashRequest *request,
2882
 
                                GHIGetExecInfoHashReply *reply)
2883
 
{
2884
 
   ASSERT(ghip);
2885
 
   ASSERT(request);
2886
 
   ASSERT(reply);
2887
 
 
2888
 
   return FALSE;
2889
 
}
2890
 
 
2891
 
 
2892
 
/*
2893
 
 ******************************************************************************
2894
 
 * GHIX11FindDesktopUriByExec --                                         */ /**
2895
 
 *
2896
 
 * Given an executable path, attempt to generate an "execUri" associated with a
2897
 
 * corresponding .desktop file.
2898
 
 *
2899
 
 * @sa GHIX11_FindDesktopUriByExec
2900
 
 *
2901
 
 * @note Returned pointer belongs to the GHI module.  Caller must not free it.
2902
 
 *
2903
 
 * @param[in]  ghip     GHI platform-specific context.
2904
 
 * @param[in]  execPath Input binary path.  May be absolute or relative.
2905
 
 *
2906
 
 * @return Pointer to a URI string on success, NULL on failure.
2907
 
 *
2908
 
 ******************************************************************************
2909
 
 */
2910
 
 
2911
 
const gchar *
2912
 
GHIX11FindDesktopUriByExec(GHIPlatform *ghip,
2913
 
                           const char *exec)
2914
 
{
2915
 
   char pathbuf[MAXPATHLEN];
2916
 
   gchar *pathname = NULL;
2917
 
   gchar *uri = NULL;
2918
 
   GHIMenuItem *gmi;
2919
 
   gboolean fudged = FALSE;
2920
 
   gboolean basenamed = FALSE;
2921
 
 
2922
 
   ASSERT(ghip);
2923
 
   ASSERT(exec);
2924
 
 
2925
 
   /*
2926
 
    * Check our hash table first.  Negative entries are also cached.
2927
 
    */
2928
 
   if (g_hash_table_lookup_extended(ghip->appsByWindowExecutable,
2929
 
                                    exec, NULL, (gpointer*)&uri)) {
2930
 
      return uri;
2931
 
   }
2932
 
 
2933
 
   /*
2934
 
    * Okay, execPath may be absolute or relative.
2935
 
    *
2936
 
    * We'll search for a matching .desktop entry using the following methods:
2937
 
    *
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.)
2943
 
    */
2944
 
 
2945
 
   /*
2946
 
    * Attempt #1:  Start with unmodified input.
2947
 
    */
2948
 
   Str_Strcpy(pathbuf, exec, sizeof pathbuf);
2949
 
 
2950
 
tryagain:
2951
 
   g_free(pathname);    // Placed here rather than at each goto.  I'm lazy.
2952
 
 
2953
 
   pathname = g_find_program_in_path(pathbuf);
2954
 
   if (pathname) {
2955
 
      gmi = (GHIMenuItem*)g_hash_table_lookup(ghip->appsByExecutable, pathname);
2956
 
      if (gmi) {
2957
 
         uri = GHIPlatformMenuItemToURI(ghip, gmi);
2958
 
      }
2959
 
   }
2960
 
 
2961
 
   if (!uri) {
2962
 
      /*
2963
 
       * Attempt #2:  Take the basename of exec.
2964
 
       */
2965
 
      if (!basenamed) {
2966
 
         char tmpbuf[MAXPATHLEN];
2967
 
         char *ctmp;
2968
 
 
2969
 
         basenamed = TRUE;
2970
 
 
2971
 
         /* basename(3) may modify the input buffer, so make a temporary copy. */
2972
 
         Str_Strcpy(tmpbuf, pathbuf, sizeof tmpbuf);
2973
 
         ctmp = basename(tmpbuf);
2974
 
         if (ctmp != NULL) {
2975
 
            Str_Strcpy(pathbuf, ctmp, sizeof pathbuf);
2976
 
            goto tryagain;
2977
 
         }
2978
 
      }
2979
 
 
2980
 
      /*
2981
 
       * Attempt #3:  Get our whitelist on.
2982
 
       */
2983
 
      if (!fudged) {
2984
 
         static struct {
2985
 
            const gchar *pattern;
2986
 
            const gchar *exec;
2987
 
         } fudgePatterns[] = {
2988
 
            /*
2989
 
             * XXX Worth compiling once?  Consider placing in an external filter
2990
 
             * file to allow users to update it themselves easily.
2991
 
             */
2992
 
            { "*firefox*-bin", "firefox" },
2993
 
            { "*thunderbird*-bin", "thunderbird" },
2994
 
            { "*soffice.bin", "ooffice" }
2995
 
         };
2996
 
         int i;
2997
 
 
2998
 
         fudged = TRUE;
2999
 
 
3000
 
         for (i = 0; i < ARRAYSIZE(fudgePatterns); i++) {
3001
 
            if (g_pattern_match_simple(fudgePatterns[i].pattern,
3002
 
                                       pathbuf)) {
3003
 
               Str_Strcpy(pathbuf, fudgePatterns[i].exec, sizeof pathbuf);
3004
 
               goto tryagain;
3005
 
            }
3006
 
         }
3007
 
      }
3008
 
   }
3009
 
 
3010
 
   g_free(pathname);
3011
 
 
3012
 
   /*
3013
 
    * Cache the result, even if it was negative.
3014
 
    */
3015
 
   g_hash_table_insert(ghip->appsByWindowExecutable, g_strdup(exec), uri);
3016
 
 
3017
 
   return uri;
3018
 
}