1 /**
2  * Functions for retrieving standard paths in cross-platform manner.
3  * Authors: 
4  *  $(LINK2 https://github.com/MyLittleRobo, Roman Chistokhodov)
5  * License: 
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov 2015
9  */
10 
11 module standardpaths;
12 
13 private {
14     import std.process : environment;
15     import std.array;
16     import std.path;
17     import std.file;
18     import std.algorithm : splitter, canFind;
19     import std.exception;
20     import std.range;
21     
22     debug {
23         import std.stdio : stderr;
24     }
25     
26     static if( __VERSION__ < 2066 ) enum nogc = 1;
27 }
28 
29 version(Windows) {
30     private {
31         import std.c.windows.windows;
32         import std.utf;
33         import std.uni : toLower, sicmp;
34     }
35 } else version(Posix) {
36     private {
37         import std.stdio : File, StdioException;
38         import std.conv : octal;
39         import std.string : toStringz;
40         
41         static if (is(typeof({import std.string : fromStringz;}))) {
42             import std.string : fromStringz;
43         } else { //own fromStringz implementation for compatibility reasons
44             import std.c.string : strlen;
45             @system pure inout(char)[] fromStringz(inout(char)* cString) {
46                 return cString ? cString[0..strlen(cString)] : null;
47             }
48         }
49     }
50 } else {
51     static assert(false, "Unsupported platform");
52 }
53 
54 /** 
55  * Locations that can be passed to writablePath and standardPaths functions.
56  * See_Also:
57  *  writablePath, standardPaths
58  */
59 enum StandardPath {
60     /**
61      * Location of persisted application data.
62      */
63     data,
64     Data = StandardPath.data, 
65     /**
66      * Location of configuration files.
67      * Note: on Windows it's the same as $(B Data) path.
68      */
69     config,
70     Config = StandardPath.config, 
71     /**
72      * Location of cached data.
73      * Note: on Windows it's the same as $(B Data)/cache.
74      */
75     cache,
76     Cache = StandardPath.cache,  
77     ///User's desktop directory
78     desktop,
79     Desktop = StandardPath.desktop,
80     ///User's documents
81     documents,
82     Documents = StandardPath.documents,
83     ///User's pictures
84     pictures,
85     Pictures = StandardPath.pictures, 
86     
87     ///User's music
88     music,
89     Music = StandardPath.music, 
90     
91     ///User's videos (movies)
92     videos,
93     Videos = StandardPath.videos, 
94     
95     ///Directory for user's downloaded files.
96     downloads,
97     Download = StandardPath.downloads, 
98     
99     ///Location of templates.
100     templates,
101     Templates = templates, 
102     
103     ///Public share folder.
104     publicShare,
105     PublicShare = StandardPath.publicShare, 
106     /**
107      * Location of fonts files.
108      * Note: don't rely on this on freedesktop, since it uses hardcoded paths there. Better consider using $(LINK2 http://www.freedesktop.org/wiki/Software/fontconfig/, fontconfig library)
109      */
110     fonts,
111     Fonts = StandardPath.fonts, 
112     ///User's applications.
113     applications,
114     Applications = StandardPath.applications, 
115 }
116 
117 /**
118  * Current user home directory.
119  * Returns: Path to user home directory, or an empty string if could not determine home directory.
120  * Relies on environment variables.
121  * Note: This function does not cache its result.
122  */
123 string homeDir() nothrow @safe
124 {
125     try {
126         version(Windows) {
127             //Use GetUserProfileDirectoryW from Userenv.dll?
128             string home = environment.get("USERPROFILE");
129             if (home.empty) {
130                 string homeDrive = environment.get("HOMEDRIVE");
131                 string homePath = environment.get("HOMEPATH");
132                 if (homeDrive.length && homePath.length) {
133                     home = homeDrive ~ homePath;
134                 }
135             }
136             return home;
137         } else {
138             string home = environment.get("HOME");
139             return home;
140         }
141     }
142     catch (Exception e) {
143         debug {
144             @trusted void writeException(Exception e) nothrow {
145                 collectException(stderr.writefln("Error when getting home directory %s", e.msg));
146             }
147             writeException(e);
148         }
149         return null;
150     }
151 }
152 
153 /**
154  * Getting writable paths for various locations.
155  * Returns: Path where files of $(U type) should be written to by current user, or an empty string if could not determine path.
156  * This function does not ensure if the returned path exists and appears to be accessible directory.
157  * Note: This function does not cache its results.
158  */
159 string writablePath(StandardPath type) nothrow @safe;
160 
161 /**
162  * Getting paths for various locations.
163  * Returns: Array of paths where files of $(U type) belong including one returned by $(B writablePath), or an empty array if no paths are defined for $(U type).
164  * This function does not ensure if all returned paths exist and appear to be accessible directories. Returned strings are not required to be unique.
165  * Note: This function does cache its results. 
166  * It may cause performance impact to call this function often since retrieving some paths can be relatively expensive operation.
167  * See_Also:
168  *  writablePath
169  */
170 string[] standardPaths(StandardPath type) nothrow @safe;
171 
172 
173 version(Docs)
174 {
175     /**
176      * Path to runtime user directory.
177      * Returns: User's runtime directory determined by $(B XDG_RUNTIME_DIR) environment variable. 
178      * If directory does not exist it tries to create one with appropriate permissions. On fail returns an empty string.
179      * Note: This function is Freedesktop only.
180      */
181     string runtimeDir() nothrow @trusted;
182     
183     /**
184      * Path to $(B Roaming) data directory. 
185      * Returns: User's Roaming directory. On fail returns an empty string.
186      * Note: This function is Windows only.
187      */
188     string roamingPath() nothrow @safe;
189     
190     /**
191      * The preference-ordered set of base directories to search for configuration files.
192      * Returns: config directories, without user's one.
193      * Note: This function is Freedesktop only.
194      */
195     string[] xdgConfigDirs() nothrow @trusted;
196     
197     /**
198      * The preference-ordered set of base directories to search for data files.
199      * Returns: data directories, without user's one.
200      * Note: This function is Freedesktop only.
201      */
202     string[] xdgDataDirs() nothrow @trusted;
203     
204     /**
205      * The base directory relative to which user specific data files should be stored.
206      * Returns:  the same value as standardPaths(StandardPath.data)
207      * Note: This function is Freedesktop only.
208      */
209     string xdgDataHome() nothrow @trusted;
210     
211     /**
212      * The base directory relative to which user specific configuration files should be stored.
213      * Returns: the same value as standardPaths(StandardPath.config)
214      * Note: This function is Freedesktop only.
215      */
216     string xdgConfigHome() nothrow @trusted;
217     
218     /**
219      * The base directory relative to which user specific non-essential data files should be stored.
220      * Returns: the same value as standardPaths(StandardPath.cache)
221      * Note: This function is Freedesktop only.
222      */
223     string xdgCacheHome() nothrow @trusted;
224 }
225 
226 version(Windows) {
227     private enum pathVarSeparator = ';';
228 } else version(Posix) {
229     private enum pathVarSeparator = ':';
230 }
231 
232 version(Windows) {
233     
234     private {
235         enum {
236             CSIDL_DESKTOP            =  0,
237             CSIDL_INTERNET,
238             CSIDL_PROGRAMS,
239             CSIDL_CONTROLS,
240             CSIDL_PRINTERS,
241             CSIDL_PERSONAL,
242             CSIDL_FAVORITES,
243             CSIDL_STARTUP,
244             CSIDL_RECENT,
245             CSIDL_SENDTO,
246             CSIDL_BITBUCKET,
247             CSIDL_STARTMENU,      // = 11
248             CSIDL_MYMUSIC            = 13,
249             CSIDL_MYVIDEO,        // = 14
250             CSIDL_DESKTOPDIRECTORY   = 16,
251             CSIDL_DRIVES,
252             CSIDL_NETWORK,
253             CSIDL_NETHOOD,
254             CSIDL_FONTS,
255             CSIDL_TEMPLATES,
256             CSIDL_COMMON_STARTMENU,
257             CSIDL_COMMON_PROGRAMS,
258             CSIDL_COMMON_STARTUP,
259             CSIDL_COMMON_DESKTOPDIRECTORY,
260             CSIDL_APPDATA,
261             CSIDL_PRINTHOOD,
262             CSIDL_LOCAL_APPDATA,
263             CSIDL_ALTSTARTUP,
264             CSIDL_COMMON_ALTSTARTUP,
265             CSIDL_COMMON_FAVORITES,
266             CSIDL_INTERNET_CACHE,
267             CSIDL_COOKIES,
268             CSIDL_HISTORY,
269             CSIDL_COMMON_APPDATA,
270             CSIDL_WINDOWS,
271             CSIDL_SYSTEM,
272             CSIDL_PROGRAM_FILES,
273             CSIDL_MYPICTURES,
274             CSIDL_PROFILE,
275             CSIDL_SYSTEMX86,
276             CSIDL_PROGRAM_FILESX86,
277             CSIDL_PROGRAM_FILES_COMMON,
278             CSIDL_PROGRAM_FILES_COMMONX86,
279             CSIDL_COMMON_TEMPLATES,
280             CSIDL_COMMON_DOCUMENTS,
281             CSIDL_COMMON_ADMINTOOLS,
282             CSIDL_ADMINTOOLS,
283             CSIDL_CONNECTIONS,  // = 49
284             CSIDL_COMMON_MUSIC     = 53,
285             CSIDL_COMMON_PICTURES,
286             CSIDL_COMMON_VIDEO,
287             CSIDL_RESOURCES,
288             CSIDL_RESOURCES_LOCALIZED,
289             CSIDL_COMMON_OEM_LINKS,
290             CSIDL_CDBURN_AREA,  // = 59
291             CSIDL_COMPUTERSNEARME  = 61,
292             CSIDL_FLAG_DONT_VERIFY = 0x4000,
293             CSIDL_FLAG_CREATE      = 0x8000,
294             CSIDL_FLAG_MASK        = 0xFF00
295         }
296     }
297     
298     private  {
299         private extern(Windows) @nogc @system BOOL dummy(HWND, wchar*, int, BOOL) nothrow { return 0; }
300         
301         alias typeof(&dummy) GetSpecialFolderPath;
302         
303         version(LinkedShell32) {
304             extern(Windows) @nogc @system BOOL SHGetSpecialFolderPathW(HWND, wchar*, int, BOOL) nothrow;
305             __gshared GetSpecialFolderPath ptrSHGetSpecialFolderPath = &SHGetSpecialFolderPathW;
306         } else {
307             __gshared GetSpecialFolderPath ptrSHGetSpecialFolderPath = null;
308         }
309         
310         alias typeof(&RegOpenKeyExW) func_RegOpenKeyEx;
311         alias typeof(&RegQueryValueExW) func_RegQueryValueEx;
312         alias typeof(&RegCloseKey) func_RegCloseKey;
313 
314         __gshared func_RegOpenKeyEx ptrRegOpenKeyEx;
315         __gshared func_RegQueryValueEx ptrRegQueryValueEx;
316         __gshared func_RegCloseKey ptrRegCloseKey;
317         
318         @nogc @trusted bool hasSHGetSpecialFolderPath() nothrow {
319             return ptrSHGetSpecialFolderPath != null;
320         }
321         
322         @nogc @trusted bool isAdvApiLoaded() nothrow {
323             return ptrRegOpenKeyEx && ptrRegQueryValueEx && ptrRegCloseKey;
324         }
325     }
326     
327     shared static this() 
328     {
329         version(LinkedShell32) {} else {
330             HMODULE shellLib = LoadLibraryA("Shell32");
331             if (shellLib) {
332                 ptrSHGetSpecialFolderPath = cast(GetSpecialFolderPath)GetProcAddress(shellLib, "SHGetSpecialFolderPathW");
333             }
334         }
335         
336         HMODULE advApi = LoadLibraryA("Advapi32.dll");
337         if (advApi) {
338             ptrRegOpenKeyEx = cast(func_RegOpenKeyEx)GetProcAddress(advApi, "RegOpenKeyExW");
339             ptrRegQueryValueEx = cast(func_RegQueryValueEx)GetProcAddress(advApi, "RegQueryValueExW");
340             ptrRegCloseKey = cast(func_RegCloseKey)GetProcAddress(advApi, "RegCloseKey");
341         }
342     }
343     
344     private string getShellFolder(const(wchar)* key) nothrow @trusted
345     {
346         HKEY hKey;
347         if (isAdvApiLoaded()) {    
348             auto result = ptrRegOpenKeyEx(HKEY_CURRENT_USER, 
349                 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\0"w.ptr,
350                 0,
351                 KEY_QUERY_VALUE,
352                 &hKey
353             );
354             scope(exit) ptrRegCloseKey(hKey);
355             
356             if (result == ERROR_SUCCESS) {
357                 DWORD type;
358                 BYTE[MAX_PATH*wchar.sizeof] buf = void;
359                 DWORD length = cast(DWORD)buf.length;
360                 result = ptrRegQueryValueEx(hKey, key, null, &type, buf.ptr, &length);
361                 if (result == ERROR_SUCCESS && type == REG_SZ && (length % 2 == 0)) {
362                     auto str = cast(wstring)buf[0..length];
363                     try {
364                         return toUTF8(str);
365                     } catch(Exception e) {
366                         
367                     }
368                 }
369             }
370         }
371         
372         return null;
373     }
374     
375     
376     private string getCSIDLFolder(int csidl) nothrow @trusted
377     {
378         import core.stdc.wchar_ : wcslen;
379         
380         wchar[MAX_PATH] path = void;
381         if (hasSHGetSpecialFolderPath() && ptrSHGetSpecialFolderPath(null, path.ptr, csidl, FALSE)) {
382             size_t len = wcslen(path.ptr);
383             try {
384                 return toUTF8(path[0..len]);
385             } catch(Exception e) {
386                 
387             }
388         }
389         return null;
390     }
391     
392     string roamingPath() nothrow @safe
393     {
394         return getCSIDLFolder(CSIDL_APPDATA);
395     }
396     
397     string writablePath(StandardPath type) nothrow @safe
398     {
399         final switch(type) {
400             case StandardPath.Config:
401             case StandardPath.Data:
402                 return getCSIDLFolder(CSIDL_LOCAL_APPDATA);
403             case StandardPath.Cache:
404             {
405                 string path = getCSIDLFolder(CSIDL_LOCAL_APPDATA);
406                 if (path.length) {
407                     return buildPath(path, "cache");
408                 }
409                 return null;
410             }
411             case StandardPath.Desktop:
412                 return getCSIDLFolder(CSIDL_DESKTOPDIRECTORY);
413             case StandardPath.Documents:
414                 return getCSIDLFolder(CSIDL_PERSONAL);
415             case StandardPath.Pictures:
416                 return getCSIDLFolder(CSIDL_MYPICTURES);
417             case StandardPath.Music:
418                 return getCSIDLFolder(CSIDL_MYMUSIC);
419             case StandardPath.Videos:
420                 return getCSIDLFolder(CSIDL_MYVIDEO);
421             case StandardPath.Download:
422                 return getShellFolder("{374DE290-123F-4565-9164-39C4925E467B}\0"w.ptr);
423             case StandardPath.Templates:
424                 return getCSIDLFolder(CSIDL_TEMPLATES);
425             case StandardPath.PublicShare:
426                 return null;
427             case StandardPath.Fonts:
428                 return null;
429             case StandardPath.Applications:
430                 return getCSIDLFolder(CSIDL_PROGRAMS);
431         }
432     }
433     
434     string[] standardPaths(StandardPath type) nothrow @safe
435     {   
436         string commonPath;
437         
438         switch(type) {
439             case StandardPath.Config:
440             case StandardPath.Data:
441                 commonPath = getCSIDLFolder(CSIDL_COMMON_APPDATA);
442                 break;
443             case StandardPath.Desktop:
444                 commonPath = getCSIDLFolder(CSIDL_COMMON_DESKTOPDIRECTORY);
445                 break;
446             case StandardPath.Documents:
447                 commonPath = getCSIDLFolder(CSIDL_COMMON_DOCUMENTS);
448                 break;
449             case StandardPath.Pictures:
450                 commonPath = getCSIDLFolder(CSIDL_COMMON_PICTURES);
451                 break;
452             case StandardPath.Music:
453                 commonPath = getCSIDLFolder(CSIDL_COMMON_MUSIC);
454                 break;
455             case StandardPath.Videos:
456                 commonPath = getCSIDLFolder(CSIDL_COMMON_VIDEO);
457                 break;
458             case StandardPath.Templates:
459                 commonPath = getCSIDLFolder(CSIDL_COMMON_TEMPLATES);
460                 break;
461             case StandardPath.Fonts:
462                 commonPath = getCSIDLFolder(CSIDL_FONTS);
463                 break;
464             case StandardPath.Applications:
465                 commonPath = getCSIDLFolder(CSIDL_COMMON_PROGRAMS);
466                 break;
467             default:
468                 break;
469         }
470         
471         string[] paths;
472         string userPath = writablePath(type);
473         if (userPath.length) 
474             paths ~= userPath;
475         if (commonPath.length)
476             paths ~= commonPath;
477         return paths;
478     }
479     
480     private const(string)[] executableExtensions() nothrow @trusted
481     {
482         static bool filenamesEqual(string first, string second) nothrow {
483             try {
484                 return filenameCmp(first, second) == 0;
485             } catch(Exception e) {
486                 return false;
487             }
488         }
489         
490         static string[] extensions;
491         if (extensions.empty) {
492             collectException(environment.get("PATHEXT").splitter(pathVarSeparator).array, extensions);
493             if (canFind!(filenamesEqual)(extensions, ".exe") == false) {
494                 extensions = [".exe", ".com", ".bat", ".cmd"];
495             }
496         }
497         
498         return extensions;
499     }
500 } else version(OSX) {
501     
502     private enum : short {
503         kOnSystemDisk                 = -32768L, /* previously was 0x8000 but that is an unsigned value whereas vRefNum is signed*/
504         kOnAppropriateDisk            = -32767, /* Generally, the same as kOnSystemDisk, but it's clearer that this isn't always the 'boot' disk.*/
505                                                 /* Folder Domains - Carbon only.  The constants above can continue to be used, but the folder/volume returned will*/
506                                                 /* be from one of the domains below.*/
507         kSystemDomain                 = -32766, /* Read-only system hierarchy.*/
508         kLocalDomain                  = -32765, /* All users of a single machine have access to these resources.*/
509         kNetworkDomain                = -32764, /* All users configured to use a common network server has access to these resources.*/
510         kUserDomain                   = -32763, /* Read/write. Resources that are private to the user.*/
511         kClassicDomain                = -32762, /* Domain referring to the currently configured Classic System Folder.  Not supported in Mac OS X Leopard and later.*/
512         kFolderManagerLastDomain      = -32760
513     }
514 
515     private @nogc int k(string s) nothrow {
516         return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
517     }
518 
519     private enum {
520         kDesktopFolderType            = k("desk"), /* the desktop folder; objects in this folder show on the desktop. */
521         kTrashFolderType              = k("trsh"), /* the trash folder; objects in this folder show up in the trash */
522         kWhereToEmptyTrashFolderType  = k("empt"), /* the "empty trash" folder; Finder starts empty from here down */
523         kFontsFolderType              = k("font"), /* Fonts go here */
524         kPreferencesFolderType        = k("pref"), /* preferences for applications go here */
525         kSystemPreferencesFolderType  = k("sprf"), /* the PreferencePanes folder, where Mac OS X Preference Panes go */
526         kTemporaryFolderType          = k("temp"), /*    On Mac OS X, each user has their own temporary items folder, and the Folder Manager attempts to set permissions of these*/
527                                                 /*    folders such that other users can not access the data inside.  On Mac OS X 10.4 and later the data inside the temporary*/
528                                                 /*    items folder is deleted at logout and at boot, but not otherwise.  Earlier version of Mac OS X would delete items inside*/
529                                                 /*    the temporary items folder after a period of inaccess.  You can ask for a temporary item in a specific domain or on a */
530                                                 /*    particular volume by FSVolumeRefNum.  If you want a location for temporary items for a short time, then use either*/
531                                                 /*    ( kUserDomain, kkTemporaryFolderType ) or ( kSystemDomain, kTemporaryFolderType ).  The kUserDomain varient will always be*/
532                                                 /*    on the same volume as the user's home folder, while the kSystemDomain version will be on the same volume as /var/tmp/ ( and*/
533                                                 /*    will probably be on the local hard drive in case the user's home is a network volume ).  If you want a location for a temporary*/
534                                                 /*    file or folder to use for saving a document, especially if you want to use FSpExchangeFile() to implement a safe-save, then*/
535                                                 /*    ask for the temporary items folder on the same volume as the file you are safe saving.*/
536                                                 /*    However, be prepared for a failure to find a temporary folder in any domain or on any volume.  Some volumes may not have*/
537                                                 /*    a location for a temporary folder, or the permissions of the volume may be such that the Folder Manager can not return*/
538                                                 /*    a temporary folder for the volume.*/
539                                                 /*    If your application creates an item in a temporary items older you should delete that item as soon as it is not needed,*/
540                                                 /*    and certainly before your application exits, since otherwise the item is consuming disk space until the user logs out or*/
541                                                 /*    restarts.  Any items left inside a temporary items folder should be moved into a folder inside the Trash folder on the disk*/
542                                                 /*    when the user logs in, inside a folder named "Recovered items", in case there is anything useful to the end user.*/
543         kChewableItemsFolderType      = k("flnt"), /* similar to kTemporaryItemsFolderType, except items in this folder are deleted at boot or when the disk is unmounted */
544         kTemporaryItemsInCacheDataFolderType = k("vtmp"), /* A folder inside the kCachedDataFolderType for the given domain which can be used for transient data*/
545         kApplicationsFolderType       = k("apps"), /*    Applications on Mac OS X are typically put in this folder ( or a subfolder ).*/
546         kVolumeRootFolderType         = k("root"), /* root folder of a volume or domain */
547         kDomainTopLevelFolderType     = k("dtop"), /* The top-level of a Folder domain, e.g. "/System"*/
548         kDomainLibraryFolderType      = k("dlib"), /* the Library subfolder of a particular domain*/
549         kUsersFolderType              = k("usrs"), /* "Users" folder, usually contains one folder for each user. */
550         kCurrentUserFolderType        = k("cusr"), /* The folder for the currently logged on user; domain passed in is ignored. */
551         kSharedUserDataFolderType     = k("sdat"), /* A Shared folder, readable & writeable by all users */
552         kCachedDataFolderType         = k("cach"), /* Contains various cache files for different clients*/
553         kDownloadsFolderType          = k("down"), /* Refers to the ~/Downloads folder*/
554         kApplicationSupportFolderType = k("asup"), /* third-party items and folders */
555 
556 
557         kDocumentsFolderType          = k("docs"), /*    User documents are typically put in this folder ( or a subfolder ).*/
558         kPictureDocumentsFolderType   = k("pdoc"), /* Refers to the "Pictures" folder in a users home directory*/
559         kMovieDocumentsFolderType     = k("mdoc"), /* Refers to the "Movies" folder in a users home directory*/
560         kMusicDocumentsFolderType     = 0xB5646F63/*'µdoc'*/, /* Refers to the "Music" folder in a users home directory*/
561         kInternetSitesFolderType      = k("site"), /* Refers to the "Sites" folder in a users home directory*/
562         kPublicFolderType             = k("pubb"), /* Refers to the "Public" folder in a users home directory*/
563 
564         kDropBoxFolderType            = k("drop") /* Refers to the "Drop Box" folder inside the user's home directory*/
565     };
566 
567     private {
568         struct FSRef {
569           char[80] hidden;    /* private to File Manager*/
570         };
571 
572         alias int Boolean;
573         alias int OSType;
574         alias int OSerr;
575         
576         extern(C) @nogc @system int dummy(short, int, int, FSRef*) nothrow { return 0; }
577         extern(C) @nogc @system int dummy2(const(FSRef)*, char*, uint) nothrow { return 0; }
578 
579         alias da_FSFindFolder = typeof(&dummy);
580         alias da_FSRefMakePath = typeof(&dummy2);
581 
582         __gshared da_FSFindFolder ptrFSFindFolder = null;
583         __gshared da_FSRefMakePath ptrFSRefMakePath = null;
584     }
585 
586     shared static this()
587     {
588         enum carbonPath = "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore";
589 
590         import core.sys.posix.dlfcn;
591 
592         void* handle = dlopen(toStringz(carbonPath), RTLD_NOW | RTLD_LOCAL);
593         if (handle) {
594             ptrFSFindFolder = cast(da_FSFindFolder)dlsym(handle, "FSFindFolder");
595             ptrFSRefMakePath = cast(da_FSRefMakePath)dlsym(handle, "FSRefMakePath");
596         }
597         if (ptrFSFindFolder == null || ptrFSRefMakePath == null) {
598             debug collectException(stderr.writeln("Could not load carbon functions"));
599         }
600     }
601 
602     @nogc @trusted bool isCarbonLoaded() nothrow
603     {
604         return ptrFSFindFolder != null && ptrFSRefMakePath != null;
605     }
606 
607     enum noErr = 0;
608 
609     string fsPath(short domain, OSType type) nothrow @trusted
610     {
611         import std.stdio;   
612         FSRef fsref;
613         if (isCarbonLoaded() && ptrFSFindFolder(domain, type, false, &fsref) == noErr) {
614 
615             char[2048] buf;
616             char* path = buf.ptr;
617             if (ptrFSRefMakePath(&fsref, path, buf.sizeof) == noErr) {
618                 try {
619 
620                     return fromStringz(path).idup;
621                 }
622                 catch(Exception e) {
623 
624                 }
625             }
626         }
627         return null;
628     }
629     
630     string writablePath(StandardPath type) nothrow @safe
631     {
632         final switch(type) {
633             case StandardPath.Config:
634                 return fsPath(kUserDomain, kPreferencesFolderType);
635             case StandardPath.Cache:
636                 return fsPath(kUserDomain, kCachedDataFolderType);
637             case StandardPath.Data:
638                 return fsPath(kUserDomain, kApplicationSupportFolderType);
639             case StandardPath.Desktop:
640                 return fsPath(kUserDomain, kDesktopFolderType);
641             case StandardPath.Documents:
642                 return fsPath(kUserDomain, kDocumentsFolderType);
643             case StandardPath.Pictures:
644                 return fsPath(kUserDomain, kPictureDocumentsFolderType);
645             case StandardPath.Music:
646                 return fsPath(kUserDomain, kMusicDocumentsFolderType);
647             case StandardPath.Videos:
648                 return fsPath(kUserDomain, kMovieDocumentsFolderType);
649             case StandardPath.Download:
650                 return fsPath(kUserDomain, kDownloadsFolderType);
651             case StandardPath.Templates:
652                 return null;
653             case StandardPath.PublicShare:
654                 return fsPath(kUserDomain, kPublicFolderType );
655             case StandardPath.Fonts:
656                 return fsPath(kUserDomain, kFontsFolderType);
657             case StandardPath.Applications:
658                 return fsPath(kUserDomain, kApplicationsFolderType);
659         }
660     }
661     
662     string[] standardPaths(StandardPath type) nothrow @safe
663     {
664         string commonPath;
665         
666         switch(type) {
667             case StandardPath.Fonts:
668                 commonPath = fsPath(kOnAppropriateDisk, kFontsFolderType);
669                 break;
670             case StandardPath.Applications:
671                 commonPath = fsPath(kOnAppropriateDisk, kApplicationsFolderType);
672                 break;
673             case StandardPath.Data:
674                 commonPath = fsPath(kOnAppropriateDisk, kApplicationSupportFolderType);
675                 break;
676             case StandardPath.Cache:
677                 commonPath = fsPath(kOnAppropriateDisk, kCachedDataFolderType);
678                 break;
679             default:
680                 break;
681         }
682         
683         string[] paths;
684         string userPath = writablePath(type);
685         if (userPath.length)
686             paths ~= userPath;
687         if (commonPath.length)
688             paths ~= commonPath;
689         return paths;
690     }
691     
692 } else version(Posix) {
693     
694     //Concat two strings, but if the first one is empty, then null string is returned.
695     private string maybeConcat(string start, string path) nothrow @safe
696     {
697         return start.empty ? null : start ~ path;
698     }
699     
700     private string xdgBaseDir(string envvar, string fallback) nothrow @trusted {
701         string dir;
702         collectException(environment.get(envvar), dir);
703         if (!dir.length) {
704             dir = maybeConcat(homeDir(), fallback);
705         }
706         return dir;
707     }
708     
709     private string xdgUserDir(string key, string fallback = null) nothrow @trusted {
710         import std.algorithm : startsWith;
711         import std.string : strip;
712         
713         string fileName = maybeConcat(writablePath(StandardPath.Config), "/user-dirs.dirs");
714         string home = homeDir();
715         try {
716             auto f = File(fileName, "r");
717             
718             auto xdgdir = "XDG_" ~ key ~ "_DIR";
719             
720             char[] buf;
721             while(f.readln(buf)) {
722                 char[] line = strip(buf);
723                 auto index = xdgdir.length;
724                 if (line.startsWith(xdgdir) && line.length > index && line[index] == '=') {
725                     line = line[index+1..$];
726                     if (line.length > 2 && line[0] == '"' && line[$-1] == '"') 
727                     {
728                         line = line[1..$-1];
729                     
730                         if (line.startsWith("$HOME")) {
731                             return maybeConcat(home, assumeUnique(line[5..$]));
732                         }
733                         if (line.length == 0 || line[0] != '/') {
734                             continue;
735                         }
736                         return assumeUnique(line);
737                     }
738                 }
739             }
740         } catch(Exception e) {
741             
742         }
743         
744         if (home.length) {
745             try {
746                 auto f = File("/etc/xdg/user-dirs.defaults", "r");
747                 char[] buf;
748                 while(f.readln(buf)) {
749                     char[] line = strip(buf);
750                     auto index = key.length;
751                     if (line.startsWith(key) && line.length > index && line[index] == '=') 
752                     {
753                         line = line[index+1..$];
754                         return home ~ "/" ~ assumeUnique(line);
755                     }
756                 }
757             } catch (Exception e) {
758                 
759             }
760             if (fallback.length) {
761                 return home ~ fallback;
762             }
763         }
764         return null;
765     }
766     
767     string[] xdgConfigDirs() nothrow @trusted {
768         try {
769             string configDirs = environment.get("XDG_CONFIG_DIRS");
770             if (configDirs.length) {
771                 return splitter(configDirs, pathVarSeparator).array;
772             }
773         }
774         catch(Exception e) {
775             
776         }
777         return ["/etc/xdg"];
778     }
779     
780     string[] xdgDataDirs() nothrow @trusted {
781         try {
782             string dataDirs = environment.get("XDG_DATA_DIRS");
783             if (dataDirs.length) {
784                 return splitter(dataDirs, pathVarSeparator).array;
785             }
786         } catch(Exception e) {
787             
788         }
789         return ["/usr/local/share", "/usr/share"];
790     }
791     
792     string xdgDataHome() nothrow @trusted {
793         return xdgBaseDir("XDG_DATA_HOME", "/.local/share");
794     }
795     
796     string xdgConfigHome() nothrow @trusted {
797         return xdgBaseDir("XDG_CONFIG_HOME", "/.config");
798     }
799     
800     string xdgCacheHome() nothrow @trusted {
801         return xdgBaseDir("XDG_CACHE_HOME", "/.cache");
802     }
803     
804     private string homeFontsPath() nothrow @trusted {
805         return maybeConcat(homeDir(), "/.fonts");
806     }
807     
808     private string[] fontPaths() nothrow @trusted
809     {
810         enum localShare = "/usr/local/share/fonts";
811         enum share = "/usr/share/fonts";
812         
813         string homeFonts = homeFontsPath();
814         if (homeFonts.length) {
815             return [homeFonts, localShare, share];
816         } else {
817             return [localShare, share];
818         }
819     }
820     
821     string runtimeDir() nothrow @trusted
822     {
823         // Do we need it on BSD systems?
824         
825         import core.sys.posix.pwd;
826         import core.sys.posix.unistd;
827         import core.sys.posix.sys.stat;
828         import core.sys.posix.sys.types;
829         import core.stdc.errno;
830         import core.stdc.string;
831         
832         try { //one try to rule them all and for compatibility reasons
833             const uid_t uid = getuid();
834             string runtime;
835             collectException(environment.get("XDG_RUNTIME_DIR"), runtime);
836             
837             mode_t runtimeMode = octal!700;
838             
839             if (!runtime.length) {
840                 setpwent();
841                 passwd* pw = getpwuid(uid);
842                 endpwent();
843                 
844                 try {
845                     if (pw && pw.pw_name) {
846                         runtime = tempDir() ~ "/runtime-" ~ assumeUnique(fromStringz(pw.pw_name));
847                         
848                         if (!(runtime.exists && runtime.isDir)) {
849                             if (mkdir(runtime.toStringz, runtimeMode) != 0) {
850                                 debug stderr.writefln("Failed to create runtime directory %s: %s", runtime, fromStringz(strerror(errno)));
851                                 return null;
852                             }
853                         }
854                     } else {
855                         debug stderr.writeln("Failed to get user name to create runtime directory");
856                         return null;
857                     }
858                 } catch(Exception e) {
859                     debug collectException(stderr.writefln("Error when creating runtime directory: %s", e.msg));
860                     return null;
861                 }
862             }
863             stat_t statbuf;
864             stat(runtime.toStringz, &statbuf);
865             if (statbuf.st_uid != uid) {
866                 debug collectException(stderr.writeln("Wrong ownership of runtime directory %s, %d instead of %d", runtime, statbuf.st_uid, uid));
867                 return null;
868             }
869             if ((statbuf.st_mode & octal!777) != runtimeMode) {
870                 debug collectException(stderr.writefln("Wrong permissions on runtime directory %s, %o instead of %o", runtime, statbuf.st_mode, runtimeMode));
871                 return null;
872             }
873             
874             return runtime;
875         } catch (Exception e) {
876             debug collectException(stderr.writeln("Error when getting runtime directory: %s", e.msg));
877             return null;
878         }
879     }
880     
881     string writablePath(StandardPath type) nothrow @safe
882     {
883         final switch(type) {
884             case StandardPath.Config:
885                 return xdgConfigHome();
886             case StandardPath.Cache:
887                 return xdgCacheHome();
888             case StandardPath.Data:
889                 return xdgDataHome();
890             case StandardPath.Desktop:
891                 return xdgUserDir("DESKTOP", "/Desktop");
892             case StandardPath.Documents:
893                 return xdgUserDir("DOCUMENTS");
894             case StandardPath.Pictures:
895                 return xdgUserDir("PICTURES");
896             case StandardPath.Music:
897                 return xdgUserDir("MUSIC");
898             case StandardPath.Videos:
899                 return xdgUserDir("VIDEOS");
900             case StandardPath.Download:
901                 return xdgUserDir("DOWNLOAD");
902             case StandardPath.Templates:
903                 return xdgUserDir("TEMPLATES", "/Templates");
904             case StandardPath.PublicShare:
905                 return xdgUserDir("PUBLICSHARE", "/Public");
906             case StandardPath.Fonts:
907                 return homeFontsPath();
908             case StandardPath.Applications:
909                 return maybeConcat(writablePath(StandardPath.Data), "/applications");
910         }
911     }
912     
913     string[] standardPaths(StandardPath type) nothrow @safe
914     {
915         string[] paths;
916         
917         switch(type) {
918             case StandardPath.Data:
919                 paths = xdgDataDirs();
920                 break;
921             case StandardPath.Config:
922                 paths = xdgConfigDirs();
923                 break;
924             case StandardPath.Applications:
925             {
926                 paths = xdgDataDirs();
927                 foreach(ref path; paths) {
928                     path ~= "/applications";
929                 }
930             }
931                 break;
932             case StandardPath.Fonts:
933                 return fontPaths();
934             default:
935                 break;
936         }
937         
938         string userPath = writablePath(type);
939         if (userPath.length) {
940             paths = userPath ~ paths;
941         }
942         return paths;
943     }
944 } else {
945     static assert(false, "Unsupported platform");
946 }
947 
948 private bool isExecutable(string filePath) nothrow @trusted {
949     try {
950         version(Posix) {
951             import core.sys.posix.unistd;
952             return access(toStringz(filePath), X_OK) == 0;
953         } else version(Windows) {
954             //Use GetEffectiveRightsFromAclW?
955             
956             string extension = filePath.extension;
957             const(string)[] exeExtensions = executableExtensions();
958             foreach(ext; exeExtensions) {
959                 if (sicmp(extension, ext) == 0)
960                     return true;
961             }
962             return false;
963             
964         } else {
965             static assert(false, "Unsupported platform");
966         }
967     } catch(Exception e) {
968         return false;
969     }
970 }
971 
972 private string checkExecutable(string filePath) nothrow @trusted {
973     try {
974         if (filePath.isFile && filePath.isExecutable) {
975             return buildNormalizedPath(filePath);
976         } else {
977             return null;
978         }
979     }
980     catch(Exception e) {
981         return null;
982     }
983 }
984 
985 /**
986  * System paths where executable files can be found.
987  * Returns: Range of paths as determined by $(B PATH) environment variable.
988  * Note: this function does not cache its result
989  */
990 @trusted auto binPaths() nothrow
991 {
992     string pathVar;
993     collectException(environment.get("PATH"), pathVar);
994     return splitter(pathVar, pathVarSeparator);
995 }
996 
997 /**
998  * Finds executable by $(B fileName) in the paths specified by $(B paths).
999  * Returns: Absolute path to the existing executable file or an empty string if not found.
1000  * Params:
1001  *  fileName = Name of executable to search. If it's absolute path, this function only checks if the file is executable.
1002  *  paths = Range of directories where executable should be searched.
1003  * Note: On Windows when fileName extension is omitted, executable extensions will be automatically appended during search.
1004  */
1005 @trusted nothrow string findExecutable(Range)(string fileName, Range paths) if (is(ElementType!Range : string))
1006 {   
1007     try {
1008         if (fileName.isAbsolute()) {
1009             return checkExecutable(fileName);
1010         }
1011         
1012         string toReturn;
1013         foreach(string path; paths) {
1014             string candidate = buildPath(absolutePath(path), fileName);
1015             
1016             version(Windows) {
1017                 if (candidate.extension.empty) {
1018                     foreach(exeExtension; executableExtensions()) {
1019                         toReturn = checkExecutable(setExtension(candidate, exeExtension.toLower()));
1020                         if (toReturn.length) {
1021                             return toReturn;
1022                         }
1023                     }
1024                 }
1025             }
1026             
1027             toReturn = checkExecutable(candidate);
1028             if (toReturn.length) {
1029                 return toReturn;
1030             }
1031         }
1032     } catch (Exception e) {
1033         
1034     }
1035     return null;
1036 }
1037 
1038 /**
1039  * ditto, but searches in system paths, determined by $(B PATH) environment variable.
1040  * See_Also: binPaths
1041  */
1042 @safe string findExecutable(string fileName) nothrow {    
1043     return findExecutable(fileName, binPaths());
1044 }