1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.program.Program;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.graphics.ImageData;
17 import org.eclipse.swt.internal.Compatibility;
18 import org.eclipse.swt.internal.Converter;
19 import org.eclipse.swt.internal.Library;
20 import org.eclipse.swt.internal.gtk.OS;
21 import org.eclipse.swt.widgets.Display;
22 import org.eclipse.swt.widgets.Event;
23 import org.eclipse.swt.widgets.Listener;
24 import java.lang.all;
25 import java.nonstandard.SharedLib;
26 
27 version(Tango){
28     static import tango.core.Array;
29     static import tango.io.device.File;
30     static import tango.io.stream.Lines;
31 } else { // Phobos
32     static import std..string;
33     static import std.file;
34     static import std.algorithm;
35 }
36 
37 private extern(C) {
38     alias int GnomeIconLookupResultFlags;
39     alias int GnomeIconLookupFlags;
40     GnomeIconTheme *gnome_icon_theme_new  ();
41     char *gnome_icon_lookup      (
42                 GtkIconTheme *icon_theme,
43                 void  *thumbnail_factory,
44                 char  *file_uri,
45                 char  *custom_icon,
46                 void  *file_info,
47                 char  *mime_type,
48                 GnomeIconLookupFlags        flags,
49                 GnomeIconLookupResultFlags *result);
50     int gnome_vfs_init();
51     char* gnome_icon_theme_lookup_icon(GnomeIconTheme *theme,char *icon_name,int size, GnomeIconData **icon_data, int *base_size) ;
52 
53     alias void GnomeIconTheme;
54     alias void GnomeIconData;
55 
56     struct GnomeVFSMimeApplication{
57         /*< public > */
58         char *id;
59         char *name;
60 
61         /*< private > */
62         char *command;
63         int    can_open_multiple_files;
64         int    expects_uris;
65         GList *supported_uri_schemes;
66         int    requires_terminal;
67 
68         /* Padded to avoid future breaks in ABI compatibility */
69         void * reserved1;
70 
71         void * priv;
72     }
73 }
74 
75 struct GNOME {
76     private static extern(C){
77         enum {
78             GNOME_ICON_LOOKUP_FLAGS_NONE = 0,
79             GNOME_ICON_LOOKUP_FLAGS_EMBEDDING_TEXT = 1<<0,
80             GNOME_ICON_LOOKUP_FLAGS_SHOW_SMALL_IMAGES_AS_THEMSELVES = 1<<1,
81             GNOME_ICON_LOOKUP_FLAGS_ALLOW_SVG_AS_THEMSELVES = 1<<2
82         }
83         enum {
84             GNOME_VFS_MAKE_URI_DIR_NONE = 0,
85             GNOME_VFS_MAKE_URI_DIR_HOMEDIR = 1 << 0,
86             GNOME_VFS_MAKE_URI_DIR_CURRENT = 1 << 1
87         }
88         alias int GnomeVFSMakeURIDirs;
89         enum {
90             GNOME_VFS_OK,
91             GNOME_VFS_ERROR_NOT_FOUND,
92             GNOME_VFS_ERROR_GENERIC,
93             GNOME_VFS_ERROR_INTERNAL,
94             GNOME_VFS_ERROR_BAD_PARAMETERS,
95             GNOME_VFS_ERROR_NOT_SUPPORTED,
96             GNOME_VFS_ERROR_IO,
97             GNOME_VFS_ERROR_CORRUPTED_DATA,
98             GNOME_VFS_ERROR_WRONG_FORMAT,
99             GNOME_VFS_ERROR_BAD_FILE,
100             GNOME_VFS_ERROR_TOO_BIG,
101             GNOME_VFS_ERROR_NO_SPACE,
102             GNOME_VFS_ERROR_READ_ONLY,
103             GNOME_VFS_ERROR_INVALID_URI,
104             GNOME_VFS_ERROR_NOT_OPEN,
105             GNOME_VFS_ERROR_INVALID_OPEN_MODE,
106             GNOME_VFS_ERROR_ACCESS_DENIED,
107             GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES,
108             GNOME_VFS_ERROR_EOF,
109             GNOME_VFS_ERROR_NOT_A_DIRECTORY,
110             GNOME_VFS_ERROR_IN_PROGRESS,
111             GNOME_VFS_ERROR_INTERRUPTED,
112             GNOME_VFS_ERROR_FILE_EXISTS,
113             GNOME_VFS_ERROR_LOOP,
114             GNOME_VFS_ERROR_NOT_PERMITTED,
115             GNOME_VFS_ERROR_IS_DIRECTORY,
116             GNOME_VFS_ERROR_NO_MEMORY,
117             GNOME_VFS_ERROR_HOST_NOT_FOUND,
118             GNOME_VFS_ERROR_INVALID_HOST_NAME,
119             GNOME_VFS_ERROR_HOST_HAS_NO_ADDRESS,
120             GNOME_VFS_ERROR_LOGIN_FAILED,
121             GNOME_VFS_ERROR_CANCELLED,
122             GNOME_VFS_ERROR_DIRECTORY_BUSY,
123             GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY,
124             GNOME_VFS_ERROR_TOO_MANY_LINKS,
125             GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM,
126             GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM,
127             GNOME_VFS_ERROR_NAME_TOO_LONG,
128             GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE,
129             GNOME_VFS_ERROR_SERVICE_OBSOLETE,
130             GNOME_VFS_ERROR_PROTOCOL_ERROR,
131             GNOME_VFS_ERROR_NO_MASTER_BROWSER,
132             GNOME_VFS_ERROR_NO_DEFAULT,
133             GNOME_VFS_ERROR_NO_HANDLER,
134             GNOME_VFS_ERROR_PARSE,
135             GNOME_VFS_ERROR_LAUNCH,
136             GNOME_VFS_ERROR_TIMEOUT,
137             GNOME_VFS_ERROR_NAMESERVER,
138             GNOME_VFS_ERROR_LOCKED,
139             GNOME_VFS_ERROR_DEPRECATED_FUNCTION,
140             GNOME_VFS_ERROR_INVALID_FILENAME,
141             GNOME_VFS_ERROR_NOT_A_SYMBOLIC_LINK,
142             GNOME_VFS_NUM_ERRORS
143         }
144         alias int GnomeVFSResult;
145 
146         enum {
147             GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS,
148             GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_PATHS,
149             GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS_FOR_NON_FILES
150         }
151 
152         alias GtkIconTheme GnomeIconTheme;
153         alias .gnome_icon_theme_lookup_icon gnome_icon_theme_lookup_icon;
154         alias .gnome_vfs_init gnome_vfs_init;
155         alias .gnome_icon_lookup gnome_icon_lookup;
156         alias .gnome_icon_theme_new gnome_icon_theme_new;
157 
158 
159         GnomeVFSMimeApplication * function ( char *mime_type ) gnome_vfs_mime_get_default_application;
160         char* function(char*, int ) gnome_vfs_make_uri_from_input_with_dirs;
161         GnomeVFSResult function( GnomeVFSMimeApplication*, GList*) gnome_vfs_mime_application_launch;
162         void function(GnomeVFSMimeApplication*) gnome_vfs_mime_application_free;
163         GList* function(char*) gnome_vfs_mime_get_extensions_list;
164         void function(GList*) gnome_vfs_mime_extensions_list_free;
165         void function(GList*) gnome_vfs_mime_registered_mime_type_list_free;
166         GnomeVFSResult function(char*) gnome_vfs_url_show;
167         char* function(char*) gnome_vfs_make_uri_from_input;
168         GList* function() gnome_vfs_get_registered_mime_types;
169         char* function(char*) gnome_vfs_mime_type_from_name;
170     }
171     static Symbol[] symbols;
172     static this () {
173         symbols = [
174             Symbol("gnome_vfs_mime_get_default_application", cast(void**)&gnome_vfs_mime_get_default_application ),
175             Symbol("gnome_vfs_make_uri_from_input_with_dirs", cast(void**)&gnome_vfs_make_uri_from_input_with_dirs ),
176             Symbol("gnome_vfs_mime_application_launch", cast(void**)&gnome_vfs_mime_application_launch ),
177             Symbol("gnome_vfs_mime_application_free", cast(void**)&gnome_vfs_mime_application_free ),
178             Symbol("gnome_vfs_url_show", cast(void**)&gnome_vfs_url_show ),
179             Symbol("gnome_vfs_make_uri_from_input", cast(void**)&gnome_vfs_make_uri_from_input ),
180             Symbol("gnome_vfs_get_registered_mime_types", cast(void**)&gnome_vfs_get_registered_mime_types ),
181             Symbol("gnome_vfs_mime_get_extensions_list", cast(void**)&gnome_vfs_mime_get_extensions_list ),
182             Symbol("gnome_vfs_mime_extensions_list_free", cast(void**)&gnome_vfs_mime_extensions_list_free ),
183             Symbol("gnome_vfs_mime_registered_mime_type_list_free", cast(void**)&gnome_vfs_mime_registered_mime_type_list_free ),
184             Symbol("gnome_vfs_mime_type_from_name", cast(void**)&gnome_vfs_mime_type_from_name )
185         ];
186     }
187 }
188 
189 /**
190  * Instances of this class represent programs and
191  * their associated file extensions in the operating
192  * system.
193  *
194  * @see <a href="http://www.eclipse.org/swt/snippets/#program">Program snippets</a>
195  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
196  */
197 public final class Program {
198     String name;
199     String command;
200     String iconPath;
201     Display display;
202 
203     /* Gnome specific
204      * true if command expects a URI
205      * false if expects a path
206      */
207     bool gnomeExpectUri;
208 
209     static ptrdiff_t cdeShell;
210 
211     static const String[] CDE_ICON_EXT = [ ".m.pm"[],   ".l.pm",   ".s.pm",   ".t.pm" ];
212     static const String[] CDE_MASK_EXT = [ ".m_m.bm"[], ".l_m.bm", ".s_m.bm", ".t_m.bm" ];
213     static const String DESKTOP_DATA = "Program_DESKTOP";
214     static const String ICON_THEME_DATA = "Program_GNOME_ICON_THEME";
215     static const String PREFIX_HTTP = "http://"; //$NON-NLS-1$
216     static const String PREFIX_HTTPS = "https://"; //$NON-NLS-1$
217     static const int DESKTOP_UNKNOWN = 0;
218     static const int DESKTOP_GNOME = 1;
219     static const int DESKTOP_GNOME_24 = 2;
220     static const int DESKTOP_CDE = 3;
221     static const int PREFERRED_ICON_SIZE = 16;
222 
223 /**
224  * Prevents uninitialized instances from being created outside the package.
225  */
226 this() {
227 }
228 
229 /* Determine the desktop for the given display. */
230 static int getDesktop(Display display) {
231     if (display is null) return DESKTOP_UNKNOWN;
232     Integer desktopValue = cast(Integer)display.getData(DESKTOP_DATA);
233     if (desktopValue !is null) return desktopValue.intValue;
234     int desktop = DESKTOP_UNKNOWN;
235 
236     /* Get the list of properties on the root window. */
237     void* xDisplay = OS.GDK_DISPLAY();
238     size_t rootWindow = OS.XDefaultRootWindow(xDisplay);
239     int numProp;
240     size_t* propList = OS.XListProperties(xDisplay, rootWindow, &numProp);
241     size_t[] property = new size_t[numProp];
242     if (propList !is null) {
243         property[ 0 .. numProp ] = propList[ 0 .. numProp ];
244         OS.XFree(propList);
245     }
246 
247     /*
248      * Feature in Linux Desktop. There is currently no official way to
249      * determine whether the Gnome window manager or gnome-vfs is
250      * available. Earlier versions including Red Hat 9 and Suse 9 provide
251      * a documented Gnome specific property on the root window
252      * WIN_SUPPORTING_WM_CHECK. This property is no longer supported in newer
253      * versions such as Fedora Core 2.
254      * The workaround is to simply check that the window manager is a
255      * compliant one (property _NET_SUPPORTING_WM_CHECK) and to attempt to load
256      * our native library that depends on gnome-vfs.
257      */
258     if (desktop is DESKTOP_UNKNOWN) {
259         String gnomeName = "_NET_SUPPORTING_WM_CHECK";
260         ptrdiff_t gnome = OS.XInternAtom(xDisplay, gnomeName.ptr, true);
261         if (gnome !is OS.None && (OS.GTK_VERSION >= OS.buildVERSION (2, 2, 0)) && gnome_init()) {
262             desktop = DESKTOP_GNOME;
263             int icon_theme = cast(int)GNOME.gnome_icon_theme_new();
264             display.setData(ICON_THEME_DATA, new Integer(icon_theme));
265             display.addListener(SWT.Dispose, new class(display) Listener {
266                 Display display;
267                 this( Display display ){ this.display = display; }
268                 public void handleEvent(Event event) {
269                     Integer gnomeIconTheme = cast(Integer)display.getData(ICON_THEME_DATA);
270                     if (gnomeIconTheme is null) return;
271                     display.setData(ICON_THEME_DATA, null);
272                     /*
273                      * Note.  gnome_icon_theme_new uses g_object_new to allocate the
274                      * data it returns. Use g_object_unref to free the pointer it returns.
275                      */
276                     if (gnomeIconTheme.intValue !is 0) OS.g_object_unref( cast(void*)gnomeIconTheme.intValue);
277                 }
278             });
279             /* Check for libgnomevfs-2 version 2.4 */
280             String buffer = "libgnomevfs-2.so.0";
281             void delegate (void*) dg = (void*){ desktop = DESKTOP_GNOME_24; };
282             SharedLib.tryUseSymbol( "gnome_vfs_url_show", buffer, dg);
283             if( desktop is DESKTOP_GNOME_24 ){
284                 SharedLib.loadLibSymbols( GNOME.symbols, buffer );
285             }
286         }
287     }
288 
289 // PORTING CDE not supported
290 /+
291     /*
292     * On CDE, the atom below may exist without DTWM running. If the atom
293     * below is defined, the CDE database exists and the available
294     * applications can be queried.
295     */
296     if (desktop is DESKTOP_UNKNOWN) {
297         String cdeName = "_DT_SM_PREFERENCES";
298         ptrdiff_t cde = OS.XInternAtom(xDisplay, cdeName.ptr, true);
299         for (int index = 0; desktop is DESKTOP_UNKNOWN && index < property.length; index++) {
300             if (property[index] is OS.None) continue; /* do not match atoms that do not exist */
301             if (property[index] is cde && cde_init(display)) desktop = DESKTOP_CDE;
302         }
303     }
304 +/
305 
306     display.setData(DESKTOP_DATA, new Integer(desktop));
307     return desktop;
308 }
309 
310 // PORTING CDE not supported
311 /+
312 bool cde_execute(String fileName) {
313     /* Use the character encoding for the default locale */
314     char* action = toStringz(command);
315     char* ptr = cast(char*)OS.g_malloc(fileName.length+1);
316     ptr[ 0 .. fileName.length ] = fileName;
317     ptr[ fileName.length ] = 0;
318     DtActionArg args = new DtActionArg();
319     args.argClass = CDE.DtACTION_FILE;
320     args.name = ptr;
321     long actionID = CDE.DtActionInvoke(cdeShell, action, args, 1, null, null, null, 1, 0, 0);
322     OS.g_free(ptr);
323     return actionID !is 0;
324 }
325 
326 static String cde_getAction(String dataType) {
327     String action  = null;
328     String actions = cde_getAttribute(dataType, CDE.DtDTS_DA_ACTION_LIST);
329     if (actions !is null) {
330         int index = actions.indexOf("Open");
331         if (index !is -1) {
332             action = actions.substring(index, index + 4);
333         } else {
334             index = actions.indexOf(",");
335             action = index !is -1 ? actions.substring(0, index) : actions;
336         }
337     }
338     return action;
339 }
340 
341 static String cde_getAttribute(String dataType, String attrName) {
342     /* Use the character encoding for the default locale */
343     byte[] dataTypeBuf = Converter.wcsToMbcs(null, dataType, true);
344     byte[] attrNameBuf = Converter.wcsToMbcs(null, attrName, true);
345     byte[] optNameBuf = null;
346     ptrdiff_t attrValue = CDE.DtDtsDataTypeToAttributeValue(dataTypeBuf, attrNameBuf, optNameBuf);
347     if (attrValue is 0) return null;
348     int length = OS.strlen(attrValue);
349     byte[] attrValueBuf = new byte[length];
350     OS.memmove(attrValueBuf, attrValue, length);
351     CDE.DtDtsFreeAttributeValue(attrValue);
352     /* Use the character encoding for the default locale */
353     return new String(Converter.mbcsToWcs(null, attrValueBuf));
354 }
355 
356 static String[][ String ] cde_getDataTypeInfo() {
357     String[][ String ] dataTypeInfo;
358     int index;
359     ptrdiff_t dataTypeList = CDE.DtDtsDataTypeNames();
360     if (dataTypeList !is 0) {
361         /* For each data type name in the list */
362         index = 0;
363         ptrdiff_t [] dataType = new ptrdiff_t [1];
364         OS.memmove(dataType, dataTypeList + (index++ * 4), 4);
365         while (dataType[0] !is 0) {
366             int length = OS.strlen(dataType[0]);
367             byte[] dataTypeBuf = new byte[length];
368             OS.memmove(dataTypeBuf, dataType[0], length);
369             /* Use the character encoding for the default locale */
370             String dataTypeName = new String(Converter.mbcsToWcs(null, dataTypeBuf));
371 
372             /* The data type is valid if it is not an action, and it has an extension and an action. */
373             String extension = cde_getExtension(dataTypeName);
374             if (!CDE.DtDtsDataTypeIsAction(dataTypeBuf) &&
375                 extension !is null && cde_getAction(dataTypeName) !is null) {
376                 String[] exts;
377                 exts ~= extension;
378                 dataTypeInfo[ dataTypeName ] = exts;
379             }
380             OS.memmove(dataType, dataTypeList + (index++ * 4), 4);
381         }
382         CDE.DtDtsFreeDataTypeNames(dataTypeList);
383     }
384 
385     return dataTypeInfo;
386 }
387 
388 static String cde_getExtension(String dataType) {
389     String fileExt = cde_getAttribute(dataType, CDE.DtDTS_DA_NAME_TEMPLATE);
390     if (fileExt is null || fileExt.indexOf("%s.") is -1) return null;
391     int dot = fileExt.indexOf(".");
392     return fileExt.substring(dot);
393 }
394 
395 /**
396  * CDE - Get Image Data
397  *
398  * This method returns the image data of the icon associated with
399  * the data type. Since CDE supports multiple sizes of icons, several
400  * attempts are made to locate an icon of the desired size and format.
401  * CDE supports the sizes: tiny, small, medium and large. The best
402  * search order is medium, large, small and then tiny. Althoug CDE supports
403  * colour and monochrome bitmaps, only colour icons are tried. (The order is
404  * defined by the  cdeIconExt and cdeMaskExt arrays above.)
405  */
406 ImageData cde_getImageData() {
407     // TODO
408     return null;
409 }
410 
411 static String cde_getMimeType(String extension) {
412     String mimeType = null;
413     String[][ String ] mimeInfo = cde_getDataTypeInfo();
414     if (mimeInfo is null) return null;
415     String[] keys = mimeInfo.keys();
416     int keyIdx = 0;
417     while (mimeType is null && keyIdx < keys.length ) {
418         String type = keys[ keyIdx ];
419         String[] mimeExts = mimeInfo[type];
420         for (int index = 0; index < mimeExts.length; index++){
421             if (extension.equals(mimeExts[index])) {
422                 mimeType = type;
423                 break;
424             }
425         }
426         keyIdx++;
427     }
428     return mimeType;
429 }
430 
431 static Program cde_getProgram(Display display, String mimeType) {
432     Program program = new Program();
433     program.display = display;
434     program.name = mimeType;
435     program.command = cde_getAction(mimeType);
436     program.iconPath = cde_getAttribute(program.name, CDE.DtDTS_DA_ICON);
437     return program;
438 }
439 
440 static bool cde_init(Display display) {
441     try {
442         Library.loadLibrary("swt-cde");
443     } catch (Throwable e) {
444         return false;
445     }
446 
447     /* Use the character encoding for the default locale */
448     CDE.XtToolkitInitialize();
449     ptrdiff_t xtContext = CDE.XtCreateApplicationContext ();
450     ptrdiff_t xDisplay = OS.GDK_DISPLAY();
451     byte[] appName = Converter.wcsToMbcs(null, "CDE", true);
452     byte[] appClass = Converter.wcsToMbcs(null, "CDE", true);
453     ptrdiff_t [] argc = [0];
454     CDE.XtDisplayInitialize(xtContext, xDisplay, appName, appClass, 0, 0, argc, 0);
455     ptrdiff_t widgetClass = CDE.topLevelShellWidgetClass ();
456     cdeShell = CDE.XtAppCreateShell (appName, appClass, widgetClass, xDisplay, null, 0);
457     CDE.XtSetMappedWhenManaged (cdeShell, false);
458     CDE.XtResizeWidget (cdeShell, 10, 10, 0);
459     CDE.XtRealizeWidget (cdeShell);
460     bool initOK = CDE.DtAppInitialize(xtContext, xDisplay, cdeShell, appName, appName);
461     if (initOK) CDE.DtDbLoad();
462     return initOK;
463 }
464 +/
465 
466 static String[] parseCommand(String cmd) {
467     String[] args;
468     int sIndex = 0;
469     int eIndex;
470     while (sIndex < cmd.length) {
471         /* Trim initial white space of argument. */
472         while (sIndex < cmd.length && Compatibility.isWhitespace(cmd.charAt(sIndex))) {
473             sIndex++;
474         }
475         if (sIndex < cmd.length) {
476             /* If the command is a quoted string */
477             if (cmd.charAt(sIndex) is '"' || cmd.charAt(sIndex) is '\'') {
478                 /* Find the terminating quote (or end of line).
479                  * This code currently does not handle escaped characters (e.g., " a\"b").
480                  */
481                 eIndex = sIndex + 1;
482                 while (eIndex < cmd.length && cmd.charAt(eIndex) !is cmd.charAt(sIndex)) eIndex++;
483                 if (eIndex >= cmd.length) {
484                     /* The terminating quote was not found
485                      * Add the argument as is with only one initial quote.
486                      */
487                     args ~= cmd.substring(sIndex, eIndex);
488                 } else {
489                     /* Add the argument, trimming off the quotes. */
490                     args ~= cmd.substring(sIndex + 1, eIndex);
491                 }
492                 sIndex = eIndex + 1;
493             }
494             else {
495                 /* Use white space for the delimiters. */
496                 eIndex = sIndex;
497                 while (eIndex < cmd.length && !Compatibility.isWhitespace(cmd.charAt(eIndex))) eIndex++;
498                 args ~= cmd.substring(sIndex, eIndex);
499                 sIndex = eIndex + 1;
500             }
501         }
502     }
503 
504     String[] strings = new String[args.length];
505     for (int index =0; index < args.length; index++) {
506         strings[index] = args[index];
507     }
508     return strings;
509 }
510 
511 /**
512  * GNOME 2.4 - Execute the program for the given file.
513  */
514 bool gnome_24_execute(String fileName) {
515     char* mimeTypeBuffer = toStringz(name);
516     auto ptr = GNOME.gnome_vfs_mime_get_default_application(mimeTypeBuffer);
517     char* fileNameBuffer = toStringz(fileName);
518     char* uri = GNOME.gnome_vfs_make_uri_from_input_with_dirs(fileNameBuffer, GNOME.GNOME_VFS_MAKE_URI_DIR_CURRENT);
519     GList* list = OS.g_list_append( null, uri);
520     int result = GNOME.gnome_vfs_mime_application_launch(ptr, list);
521     GNOME.gnome_vfs_mime_application_free(ptr);
522     OS.g_free(uri);
523     OS.g_list_free(list);
524     return result is GNOME.GNOME_VFS_OK;
525 }
526 
527 /**
528  * GNOME 2.4 - Launch the default program for the given file.
529  */
530 static bool gnome_24_launch(String fileName) {
531     char* fileNameBuffer = toStringz(fileName);
532     char* uri = GNOME.gnome_vfs_make_uri_from_input_with_dirs(fileNameBuffer, GNOME.GNOME_VFS_MAKE_URI_DIR_CURRENT);
533     int result = GNOME.gnome_vfs_url_show(uri);
534     OS.g_free(uri);
535     return (result is GNOME.GNOME_VFS_OK);
536 }
537 
538 /**
539  * GNOME 2.2 - Execute the program for the given file.
540  */
541 bool gnome_execute(String fileName) {
542     if (gnomeExpectUri) {
543         /* Convert the given path into a URL */
544         char* fileNameBuffer = toStringz(fileName);
545         char* uri = GNOME.gnome_vfs_make_uri_from_input(fileNameBuffer);
546         if (uri !is null) {
547             fileName = fromStringz( uri )._idup();
548             OS.g_free(uri);
549         }
550     }
551 
552     /* Parse the command into its individual arguments. */
553     String[] args = parseCommand(command);
554     int fileArg = -1;
555     int index;
556     for (index = 0; index < args.length; index++) {
557         int j = args[index].indexOf("%f");
558         if (j !is -1) {
559             String value = args[index];
560             fileArg = index;
561             args[index] = value.substring(0, j) ~ fileName ~ value.substring(j + 2);
562         }
563     }
564 
565     /* If a file name was given but the command did not have "%f" */
566     if ((fileName.length > 0) && (fileArg < 0)) {
567         String[] newArgs = new String[args.length + 1];
568         for (index = 0; index < args.length; index++) newArgs[index] = args[index];
569         newArgs[args.length] = fileName;
570         args = newArgs;
571     }
572 
573     /* Execute the command. */
574     try {
575         Compatibility.exec(args);
576     } catch (IOException e) {
577         return false;
578     }
579     return true;
580 }
581 
582 /**
583  * GNOME - Get Image Data
584  *
585  */
586 ImageData gnome_getImageData() {
587     if (iconPath is null) return null;
588     try {
589         return new ImageData(iconPath);
590     } catch (Exception e) {}
591     return null;
592 }
593 
594 /++
595  + SWT Extension
596  + This is a temporary workaround until SWT will get the real implementation.
597  +/
598 static String[][ String ] gnome24_getMimeInfo() {
599     version(Tango){
600         scope file = new tango.io.device.File.File("/usr/share/mime/globs");
601         scope it = new tango.io.stream.Lines.Lines!(char)( file );
602     } else { // Phobos
603         scope it = std..string.splitLines( cast(String)std.file.read("/usr/share/mime/globs"));
604     }
605     // process file one line at a time
606     String[][ String ] mimeInfo;
607     foreach (line; it ){
608         int colon = line.indexOf(':');
609         if( colon is line.length ){
610             continue;
611         }
612         if( line.length < colon+3 || line[colon+1 .. colon+3 ] != "*." ){
613             continue;
614         }
615         String mimeType = line[0..colon]._idup();
616         String ext      = line[colon+3 .. $]._idup();
617         if( auto exts = mimeType in mimeInfo ){
618             mimeInfo[ mimeType ] = *exts ~ ext;
619         }
620         else{
621             mimeInfo[ mimeType ] = [ ext ];
622         }
623     }
624     return mimeInfo;
625 }
626 /**
627  * GNOME - Get mime types
628  *
629  * Obtain the registered mime type information and
630  * return it in a map. The key of each entry
631  * in the map is the mime type name. The value is
632  * a vector of the associated file extensions.
633  */
634 static String[][ String ] gnome_getMimeInfo() {
635     String[][ String ] mimeInfo;
636     GList* mimeList = GNOME.gnome_vfs_get_registered_mime_types();
637     GList* mimeElement = mimeList;
638     while (mimeElement !is null) {
639         auto mimePtr = cast(char*) OS.g_list_data(mimeElement);
640         String mimeTypeBuffer = fromStringz(mimePtr)._idup();
641         String mimeType = mimeTypeBuffer;//new String(Converter.mbcsToWcs(null, mimeTypeBuffer));
642         GList* extensionList = GNOME.gnome_vfs_mime_get_extensions_list(mimePtr);
643         if (extensionList !is null) {
644             String[] extensions;
645             GList* extensionElement = extensionList;
646             while (extensionElement !is null) {
647                 char* extensionPtr = cast(char*) OS.g_list_data(extensionElement);
648                 String extensionBuffer = fromStringz(extensionPtr)._idup();
649                 String extension = extensionBuffer;
650                 extension = '.' ~ extension;
651                 extensions ~= extension;
652                 extensionElement = OS.g_list_next(extensionElement);
653             }
654             GNOME.gnome_vfs_mime_extensions_list_free(extensionList);
655             if (extensions.length > 0) mimeInfo[ mimeType ] = extensions;
656         }
657         mimeElement = OS.g_list_next(mimeElement);
658     }
659     if (mimeList !is null) GNOME.gnome_vfs_mime_registered_mime_type_list_free(mimeList);
660     return mimeInfo;
661 }
662 
663 static String gnome_getMimeType(String extension) {
664     String mimeType = null;
665     String fileName = "swt" ~ extension;
666     char* extensionBuffer = toStringz(fileName);
667     char* typeName = GNOME.gnome_vfs_mime_type_from_name(extensionBuffer);
668     if (typeName !is null) {
669         mimeType = fromStringz(typeName)._idup();
670     }
671     return mimeType;
672 }
673 
674 static Program gnome_getProgram(Display display, String mimeType) {
675     Program program = null;
676     char* mimeTypeBuffer = toStringz(mimeType);
677     GnomeVFSMimeApplication* ptr = GNOME.gnome_vfs_mime_get_default_application(mimeTypeBuffer);
678     if (ptr !is null) {
679         program = new Program();
680         program.display = display;
681         program.name = mimeType;
682         GnomeVFSMimeApplication* application = ptr;
683         String buffer = fromStringz(application.command)._idup();
684         program.command = buffer;
685         program.gnomeExpectUri = application.expects_uris is GNOME.GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS;
686 
687         buffer = (fromStringz( application.id) ~ '\0')._idup();
688         Integer gnomeIconTheme = cast(Integer)display.getData(ICON_THEME_DATA);
689         char* icon_name = GNOME.gnome_icon_lookup( cast(GtkIconTheme*) gnomeIconTheme.intValue, null, null, cast(char*)buffer.ptr, null, mimeTypeBuffer,
690                 GNOME.GNOME_ICON_LOOKUP_FLAGS_NONE, null);
691         char* path = null;
692         if (icon_name !is null) path = GNOME.gnome_icon_theme_lookup_icon(cast(GtkIconTheme*)gnomeIconTheme.intValue, icon_name, PREFERRED_ICON_SIZE, null, null);
693         if (path !is null) {
694             program.iconPath = fromStringz( path)._idup();
695             OS.g_free(path);
696         }
697         if (icon_name !is null) OS.g_free(icon_name);
698         GNOME.gnome_vfs_mime_application_free(ptr);
699     }
700     return program;
701 }
702 
703 static bool gnome_init() {
704     return cast(bool) GNOME.gnome_vfs_init();
705 }
706 
707 /**
708  * Finds the program that is associated with an extension.
709  * The extension may or may not begin with a '.'.  Note that
710  * a <code>Display</code> must already exist to guarantee that
711  * this method returns an appropriate result.
712  *
713  * @param extension the program extension
714  * @return the program or <code>null</code>
715  *
716  */
717 public static Program findProgram(String extension) {
718     return findProgram(Display.getCurrent(), extension);
719 }
720 
721 /*
722  *  API: When support for multiple displays is added, this method will
723  *       become public and the original method above can be deprecated.
724  */
725 static Program findProgram(Display display, String extension) {
726     // SWT extension: allow null for zero length string
727     //if (extension is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
728     if (extension.length is 0) return null;
729     if (extension.charAt(0) !is '.') extension = "." ~ extension;
730     int desktop = getDesktop(display);
731     String mimeType = null;
732     switch (desktop) {
733         case DESKTOP_GNOME_24:
734         case DESKTOP_GNOME: mimeType = gnome_getMimeType(extension); break;
735         //case DESKTOP_CDE: mimeType = cde_getMimeType(extension); break;
736         default:
737     }
738     if (mimeType is null) return null;
739     Program program = null;
740     switch (desktop) {
741         case DESKTOP_GNOME_24:
742         case DESKTOP_GNOME: program = gnome_getProgram(display, mimeType); break;
743         //case DESKTOP_CDE: program = cde_getProgram(display, mimeType); break;
744         default:
745     }
746     return program;
747 }
748 
749 /**
750  * Answer all program extensions in the operating system.  Note
751  * that a <code>Display</code> must already exist to guarantee
752  * that this method returns an appropriate result.
753  *
754  * @return an array of extensions
755  */
756 public static String[] getExtensions() {
757     return getExtensions(Display.getCurrent());
758 }
759 
760 /*
761  *  API: When support for multiple displays is added, this method will
762  *       become public and the original method above can be deprecated.
763  */
764 static String[] getExtensions(Display display) {
765     int desktop = getDesktop(display);
766     String[][ String ] mimeInfo = null;
767     switch (desktop) {
768         case DESKTOP_GNOME_24: mimeInfo = gnome24_getMimeInfo(); break;
769         case DESKTOP_GNOME: mimeInfo = gnome_getMimeInfo(); break;
770         //case DESKTOP_CDE: mimeInfo = cde_getDataTypeInfo(); break;
771         default:
772     }
773     if (mimeInfo is null) return null;
774 
775     /* Create a unique set of the file extensions. */
776     String[] extensions;
777     String[] keys = mimeInfo.keys;
778     int keyIdx = 0;
779     while ( keyIdx < keys.length ) {
780         String mimeType = keys[ keyIdx ];
781         String[] mimeExts = mimeInfo[mimeType];
782         for (int index = 0; index < mimeExts.length; index++){
783             version(Tango){
784                 bool contains = cast(bool)tango.core.Array.contains(extensions, mimeExts[index]);
785             } else { // Phobos
786                 bool contains = std.algorithm.canFind(extensions, mimeExts[index]);
787             }
788             if (!contains) {
789                 extensions ~= mimeExts[index];
790             }
791         }
792         keyIdx++;
793     }
794 
795     /* Return the list of extensions. */
796     String[] extStrings = new String[]( extensions.length );
797     for (int index = 0; index < extensions.length; index++) {
798         extStrings[index] = extensions[index];
799     }
800     return extStrings;
801 }
802 
803 /**
804  * Answers all available programs in the operating system.  Note
805  * that a <code>Display</code> must already exist to guarantee
806  * that this method returns an appropriate result.
807  *
808  * @return an array of programs
809  */
810 public static Program[] getPrograms() {
811     return getPrograms(Display.getCurrent());
812 }
813 
814 /*
815  *  API: When support for multiple displays is added, this method will
816  *       become public and the original method above can be deprecated.
817  */
818 static Program[] getPrograms(Display display) {
819     int desktop = getDesktop(display);
820     String[][ String ] mimeInfo = null;
821     switch (desktop) {
822         case DESKTOP_GNOME_24: break;
823         case DESKTOP_GNOME: mimeInfo = gnome_getMimeInfo(); break;
824         //case DESKTOP_CDE: mimeInfo = cde_getDataTypeInfo(); break;
825         default:
826     }
827     if (mimeInfo is null) return new Program[0];
828     Program[] programs;
829     String[] keys = mimeInfo.keys;
830     int keyIdx = 0;
831     while ( keyIdx < keys.length ) {
832         String mimeType = keys[ keyIdx ];
833         Program program = null;
834         switch (desktop) {
835             case DESKTOP_GNOME: program = gnome_getProgram(display, mimeType); break;
836             //case DESKTOP_CDE: program = cde_getProgram(display, mimeType); break;
837             default:
838         }
839         if (program !is null) programs ~= program;
840         keyIdx++;
841     }
842     Program[] programList = new Program[programs.length];
843     for (int index = 0; index < programList.length; index++) {
844         programList[index] = programs[index];
845     }
846     return programList;
847 }
848 
849 /**
850  * Launches the operating system executable associated with the file or
851  * URL (http:// or https://).  If the file is an executable then the
852  * executable is launched.  Note that a <code>Display</code> must already
853  * exist to guarantee that this method returns an appropriate result.
854  *
855  * @param fileName the file or program name or URL (http:// or https://)
856  * @return <code>true</code> if the file is launched, otherwise <code>false</code>
857  *
858  * @exception IllegalArgumentException <ul>
859  *    <li>ERROR_NULL_ARGUMENT when fileName is null</li>
860  * </ul>
861  */
862 public static bool launch(String fileName) {
863     return launch(Display.getCurrent(), fileName);
864 }
865 
866 /*
867  *  API: When support for multiple displays is added, this method will
868  *       become public and the original method above can be deprecated.
869  */
870 static bool launch (Display display, String fileName) {
871     if (fileName is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
872     switch (getDesktop (display)) {
873         case DESKTOP_GNOME_24:
874             if (gnome_24_launch (fileName)) return true;
875             goto default;
876         default:
877             int index = fileName.lastIndexOf ('.');
878             if (index !is -1) {
879                 String extension = fileName.substring (index);
880                 Program program = Program.findProgram (display, extension);
881                 if (program !is null && program.execute (fileName)) return true;
882             }
883             String lowercaseName = fileName.toLowerCase ();
884             if (lowercaseName.startsWith (PREFIX_HTTP) || lowercaseName.startsWith (PREFIX_HTTPS)) {
885                 Program program = Program.findProgram (display, ".html"); //$NON-NLS-1$
886                 if (program is null) {
887                     program = Program.findProgram (display, ".htm"); //$NON-NLS-1$
888                 }
889                 if (program !is null && program.execute (fileName)) return true;
890             }
891             break;
892     }
893     try {
894         Compatibility.exec (fileName);
895         return true;
896     } catch (IOException e) {
897         return false;
898     }
899 }
900 
901 /**
902  * Compares the argument to the receiver, and returns true
903  * if they represent the <em>same</em> object using a class
904  * specific comparison.
905  *
906  * @param other the object to compare with this object
907  * @return <code>true</code> if the object is the same as this object and <code>false</code> otherwise
908  *
909  * @see #hashCode()
910  */
911 public override equals_t opEquals(Object other) {
912     if (this is other) return true;
913     if (!(cast(Program)other)) return false;
914     Program program = cast(Program)other;
915     return display is program.display && name.equals(program.name) && command.equals(program.command);
916 }
917 
918 /**
919  * Executes the program with the file as the single argument
920  * in the operating system.  It is the responsibility of the
921  * programmer to ensure that the file contains valid data for
922  * this program.
923  *
924  * @param fileName the file or program name
925  * @return <code>true</code> if the file is launched, otherwise <code>false</code>
926  *
927  * @exception IllegalArgumentException <ul>
928  *    <li>ERROR_NULL_ARGUMENT when fileName is null</li>
929  * </ul>
930  */
931 public bool execute(String fileName) {
932     if (fileName is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
933     int desktop = getDesktop(display);
934     switch (desktop) {
935         case DESKTOP_GNOME_24: return gnome_24_execute(fileName);
936         case DESKTOP_GNOME: return gnome_execute(fileName);
937         //case DESKTOP_CDE: return cde_execute(fileName);
938         default:
939     }
940     return false;
941 }
942 
943 /**
944  * Returns the receiver's image data.  This is the icon
945  * that is associated with the receiver in the operating
946  * system.
947  *
948  * @return the image data for the program, may be null
949  */
950 public ImageData getImageData() {
951     switch (getDesktop(display)) {
952         case DESKTOP_GNOME_24:
953         case DESKTOP_GNOME: return gnome_getImageData();
954         //case DESKTOP_CDE: return cde_getImageData();
955         default:
956     }
957     return null;
958 }
959 
960 /**
961  * Returns the receiver's name.  This is as short and
962  * descriptive a name as possible for the program.  If
963  * the program has no descriptive name, this string may
964  * be the executable name, path or empty.
965  *
966  * @return the name of the program
967  */
968 public String getName() {
969     return name;
970 }
971 
972 /**
973  * Returns an integer hash code for the receiver. Any two
974  * objects that return <code>true</code> when passed to
975  * <code>equals</code> must return the same value for this
976  * method.
977  *
978  * @return the receiver's hash
979  *
980  * @see #equals(Object)
981  */
982 public override hash_t toHash() {
983     return .toHash(name) ^ .toHash(command) ^ display.toHash();
984 }
985 
986 /**
987  * Returns a string containing a concise, human-readable
988  * description of the receiver.
989  *
990  * @return a string representation of the program
991  */
992 override
993 public String toString() {
994     return Format( "Program {{{}}", name );
995 }
996 }