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-2016 9 */ 10 11 module standardpaths; 12 13 private { 14 import std.process : environment; 15 import std.path; 16 import std.file; 17 import std.exception; 18 import std.range; 19 20 import isfreedesktop; 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 static if (__VERSION__ < 2070) { 32 import std.c.windows.windows; 33 } else { 34 import core.sys.windows.windows; 35 } 36 37 import std.utf; 38 } 39 } else version(Posix) { 40 private { 41 import std.string : toStringz; 42 43 static if (is(typeof({import std.string : fromStringz;}))) { 44 import std.string : fromStringz; 45 } else { //own fromStringz implementation for compatibility reasons 46 import std.c.string : strlen; 47 @system pure inout(char)[] fromStringz(inout(char)* cString) { 48 return cString ? cString[0..strlen(cString)] : null; 49 } 50 } 51 52 string verifyIfNeeded(string path, bool shouldVerify) nothrow @trusted 53 { 54 if (path.length && shouldVerify) { 55 bool dirExists; 56 collectException(path.isDir, dirExists); 57 return dirExists ? path : null; 58 } else { 59 return path; 60 } 61 } 62 } 63 } else { 64 static assert(false, "Unsupported platform"); 65 } 66 67 /** 68 * Location types that can be passed to writablePath and standardPaths functions. 69 * 70 * Not all these paths are suggested for showing in file managers or file dialogs. 71 * Some of them are meant for internal application usage or should be treated in special way. 72 * On usual circumstances user wants to see Desktop, Documents, Downloads, Pictures, Music and Videos directories. 73 * 74 * See_Also: 75 * writablePath, standardPaths 76 */ 77 enum StandardPath { 78 /** 79 * General location of persisted application data. Every application should have its own subdirectory here. 80 * Note: on Windows it's the same as $(B config) path. 81 */ 82 data, 83 /** 84 * General location of configuration files. Every application should have its own subdirectory here. 85 * Note: on Windows it's the same as $(B data) path. 86 */ 87 config, 88 /** 89 * Location of cached data. 90 * Note: Not available on Windows. 91 */ 92 cache, 93 ///User's desktop directory. 94 desktop, 95 ///User's documents. 96 documents, 97 ///User's pictures. 98 pictures, 99 100 ///User's music. 101 music, 102 103 ///User's videos (movies). 104 videos, 105 106 ///Directory for user's downloaded files. 107 downloads, 108 109 /** 110 * Location of file templates (e.g. office suite document templates). 111 * Note: Not available on OS X. 112 */ 113 templates, 114 115 /** 116 * Public share folder. 117 * Note: Not available on Windows. 118 */ 119 publicShare, 120 /** 121 * Location of fonts files. 122 * 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) 123 */ 124 fonts, 125 /** 126 * User's applications. This has different meaning across platforms. 127 * On Windows it's directory where links (.lnk) to programs for Start menu are stored. 128 * On OS X it's folder where applications are typically put. 129 * On Freedesktop it's directory where .desktop files are put. 130 */ 131 applications, 132 133 /** 134 * Automatically started applications. 135 * On Windows it's directory where links (.lnk) to autostarted programs are stored. 136 * On OSX it's not available. 137 * On Freedesktop it's directory where autostarted .desktop files are stored. 138 */ 139 startup 140 } 141 142 /** 143 * Control behavior of functions. 144 * See_Also: writablePath 145 */ 146 enum FolderFlag 147 { 148 none = 0, /// Don't verify that folder exist. 149 /** 150 * Create if folder does not exist. 151 * On Windows created directory will have appropriate icon and other settings specific for this kind of folder. 152 */ 153 create = 1, 154 verify = 2 /// Verify that folder exists. 155 } 156 157 /** 158 * Current user home directory. 159 * Returns: Path to user home directory, or an empty string if could not determine home directory. 160 * Relies on environment variables. 161 * Note: This function does not cache its result. 162 */ 163 string homeDir() nothrow @safe 164 { 165 try { 166 version(Windows) { 167 //Use GetUserProfileDirectoryW from Userenv.dll? 168 string home = environment.get("USERPROFILE"); 169 if (home.empty) { 170 string homeDrive = environment.get("HOMEDRIVE"); 171 string homePath = environment.get("HOMEPATH"); 172 if (homeDrive.length && homePath.length) { 173 home = homeDrive ~ homePath; 174 } 175 } 176 return home; 177 } else { 178 string home = environment.get("HOME"); 179 return home; 180 } 181 } 182 catch (Exception e) { 183 debug { 184 @trusted void writeException(Exception e) nothrow { 185 collectException(stderr.writefln("Error when getting home directory %s", e.msg)); 186 } 187 writeException(e); 188 } 189 return null; 190 } 191 } 192 193 /** 194 * Getting writable paths for various locations. 195 * Returns: Path where files of $(U type) should be written to by current user, or an empty string if could not determine path. 196 * Params: 197 * type = Directory to lookup. 198 * params = Union of $(B FolderFlag)s. 199 * Note: This function does not cache its results. 200 * See_Also: FolderFlag 201 */ 202 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe; 203 204 /** 205 * Getting paths for various locations. 206 * 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). 207 * This function does not ensure if all returned paths exist and appear to be accessible directories. Returned strings are not required to be unique. 208 * Note: This function does cache its results. 209 * It may cause performance impact to call this function often since retrieving some paths can be relatively expensive operation. 210 * See_Also: 211 * writablePath 212 */ 213 string[] standardPaths(StandardPath type) nothrow @safe; 214 215 216 version(StandardPathsDocs) 217 { 218 /** 219 * Path to runtime user directory on freedesktop platforms. 220 * Returns: User's runtime directory determined by $(B XDG_RUNTIME_DIR) environment variable. 221 * If directory does not exist it tries to create one with appropriate permissions. On fail returns an empty string. 222 * Note: Deprecated, use xdgRuntimeDir instead. 223 */ 224 deprecated string runtimeDir() nothrow @trusted; 225 226 /** 227 * Path to $(B Roaming) data directory. 228 * Returns: User's Roaming directory. On fail returns an empty string. 229 * Note: This function is Windows only. 230 */ 231 string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe; 232 233 /** 234 * Location where games may store their saves. 235 * This is common path for games. One should use subfolder for their game saves. 236 * Returns: User's Saved Games directory. On fail returns an empty string. 237 * Note: This function is Windows only. 238 */ 239 string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe; 240 } 241 242 version(Windows) { 243 244 private { 245 enum { 246 CSIDL_DESKTOP = 0, 247 CSIDL_INTERNET, 248 CSIDL_PROGRAMS, 249 CSIDL_CONTROLS, 250 CSIDL_PRINTERS, 251 CSIDL_PERSONAL, 252 CSIDL_FAVORITES, 253 CSIDL_STARTUP, 254 CSIDL_RECENT, 255 CSIDL_SENDTO, 256 CSIDL_BITBUCKET, 257 CSIDL_STARTMENU, // = 11 258 CSIDL_MYMUSIC = 13, 259 CSIDL_MYVIDEO, // = 14 260 CSIDL_DESKTOPDIRECTORY = 16, 261 CSIDL_DRIVES, 262 CSIDL_NETWORK, 263 CSIDL_NETHOOD, 264 CSIDL_FONTS, 265 CSIDL_TEMPLATES, 266 CSIDL_COMMON_STARTMENU, 267 CSIDL_COMMON_PROGRAMS, 268 CSIDL_COMMON_STARTUP, 269 CSIDL_COMMON_DESKTOPDIRECTORY, 270 CSIDL_APPDATA, 271 CSIDL_PRINTHOOD, 272 CSIDL_LOCAL_APPDATA, 273 CSIDL_ALTSTARTUP, 274 CSIDL_COMMON_ALTSTARTUP, 275 CSIDL_COMMON_FAVORITES, 276 CSIDL_INTERNET_CACHE, 277 CSIDL_COOKIES, 278 CSIDL_HISTORY, 279 CSIDL_COMMON_APPDATA, 280 CSIDL_WINDOWS, 281 CSIDL_SYSTEM, 282 CSIDL_PROGRAM_FILES, 283 CSIDL_MYPICTURES, 284 CSIDL_PROFILE, 285 CSIDL_SYSTEMX86, 286 CSIDL_PROGRAM_FILESX86, 287 CSIDL_PROGRAM_FILES_COMMON, 288 CSIDL_PROGRAM_FILES_COMMONX86, 289 CSIDL_COMMON_TEMPLATES, 290 CSIDL_COMMON_DOCUMENTS, 291 CSIDL_COMMON_ADMINTOOLS, 292 CSIDL_ADMINTOOLS, 293 CSIDL_CONNECTIONS, // = 49 294 CSIDL_COMMON_MUSIC = 53, 295 CSIDL_COMMON_PICTURES, 296 CSIDL_COMMON_VIDEO, 297 CSIDL_RESOURCES, 298 CSIDL_RESOURCES_LOCALIZED, 299 CSIDL_COMMON_OEM_LINKS, 300 CSIDL_CDBURN_AREA, // = 59 301 CSIDL_COMPUTERSNEARME = 61, 302 CSIDL_FLAG_DONT_VERIFY = 0x4000, 303 CSIDL_FLAG_CREATE = 0x8000, 304 CSIDL_FLAG_MASK = 0xFF00 305 } 306 307 enum { 308 KF_FLAG_SIMPLE_IDLIST = 0x00000100, 309 KF_FLAG_NOT_PARENT_RELATIVE = 0x00000200, 310 KF_FLAG_DEFAULT_PATH = 0x00000400, 311 KF_FLAG_INIT = 0x00000800, 312 KF_FLAG_NO_ALIAS = 0x00001000, 313 KF_FLAG_DONT_UNEXPAND = 0x00002000, 314 KF_FLAG_DONT_VERIFY = 0x00004000, 315 KF_FLAG_CREATE = 0x00008000, 316 KF_FLAG_NO_APPCONTAINER_REDIRECTION = 0x00010000, 317 KF_FLAG_ALIAS_ONLY = 0x80000000 318 }; 319 320 alias GUID KNOWNFOLDERID; 321 322 enum KNOWNFOLDERID FOLDERID_LocalAppData = {0xf1b32785, 0x6fba, 0x4fcf, [0x9d,0x55,0x7b,0x8e,0x7f,0x15,0x70,0x91]}; 323 enum KNOWNFOLDERID FOLDERID_RoamingAppData = {0x3eb685db, 0x65f9, 0x4cf6, [0xa0,0x3a,0xe3,0xef,0x65,0x72,0x9f,0x3d]}; 324 325 enum KNOWNFOLDERID FOLDERID_Desktop = {0xb4bfcc3a, 0xdb2c, 0x424c, [0xb0,0x29,0x7f,0xe9,0x9a,0x87,0xc6,0x41]}; 326 enum KNOWNFOLDERID FOLDERID_Documents = {0xfdd39ad0, 0x238f, 0x46af, [0xad,0xb4,0x6c,0x85,0x48,0x3,0x69,0xc7]}; 327 enum KNOWNFOLDERID FOLDERID_Downloads = {0x374de290, 0x123f, 0x4565, [0x91,0x64,0x39,0xc4,0x92,0x5e,0x46,0x7b]}; 328 enum KNOWNFOLDERID FOLDERID_Favorites = {0x1777f761, 0x68ad, 0x4d8a, [0x87,0xbd,0x30,0xb7,0x59,0xfa,0x33,0xdd]}; 329 enum KNOWNFOLDERID FOLDERID_Links = {0xbfb9d5e0, 0xc6a9, 0x404c, [0xb2,0xb2,0xae,0x6d,0xb6,0xaf,0x49,0x68]}; 330 enum KNOWNFOLDERID FOLDERID_Music = {0x4bd8d571, 0x6d19, 0x48d3, [0xbe,0x97,0x42,0x22,0x20,0x8,0xe,0x43]}; 331 enum KNOWNFOLDERID FOLDERID_Pictures = {0x33e28130, 0x4e1e, 0x4676, [0x83,0x5a,0x98,0x39,0x5c,0x3b,0xc3,0xbb]}; 332 enum KNOWNFOLDERID FOLDERID_Programs = {0xa77f5d77, 0x2e2b, 0x44c3, [0xa6,0xa2,0xab,0xa6,0x1,0x5,0x4a,0x51]}; 333 enum KNOWNFOLDERID FOLDERID_SavedGames = {0x4c5c32ff, 0xbb9d, 0x43b0, [0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]}; 334 enum KNOWNFOLDERID FOLDERID_Startup = {0xb97d20bb, 0xf46a, 0x4c97, [0xba,0x10,0x5e,0x36,0x8,0x43,0x8,0x54]}; 335 enum KNOWNFOLDERID FOLDERID_Templates = {0xa63293e8, 0x664e, 0x48db, [0xa0,0x79,0xdf,0x75,0x9e,0x5,0x9,0xf7]}; 336 enum KNOWNFOLDERID FOLDERID_Videos = {0x18989b1d, 0x99b5, 0x455b, [0x84,0x1c,0xab,0x7c,0x74,0xe4,0xdd,0xfc]}; 337 338 enum KNOWNFOLDERID FOLDERID_Fonts = {0xfd228cb7, 0xae11, 0x4ae3, [0x86,0x4c,0x16,0xf3,0x91,0xa,0xb8,0xfe]}; 339 enum KNOWNFOLDERID FOLDERID_ProgramData = {0x62ab5d82, 0xfdc1, 0x4dc3, [0xa9,0xdd,0x7,0xd,0x1d,0x49,0x5d,0x97]}; 340 enum KNOWNFOLDERID FOLDERID_CommonPrograms = {0x139d44e, 0x6afe, 0x49f2, [0x86,0x90,0x3d,0xaf,0xca,0xe6,0xff,0xb8]}; 341 enum KNOWNFOLDERID FOLDERID_CommonStartup = {0x82a5ea35, 0xd9cd, 0x47c5, [0x96,0x29,0xe1,0x5d,0x2f,0x71,0x4e,0x6e]}; 342 enum KNOWNFOLDERID FOLDERID_CommonTemplates = {0xb94237e7, 0x57ac, 0x4347, [0x91,0x51,0xb0,0x8c,0x6c,0x32,0xd1,0xf7]}; 343 344 enum KNOWNFOLDERID FOLDERID_PublicDesktop = {0xc4aa340d, 0xf20f, 0x4863, [0xaf,0xef,0xf8,0x7e,0xf2,0xe6,0xba,0x25]}; 345 enum KNOWNFOLDERID FOLDERID_PublicDocuments = {0xed4824af, 0xdce4, 0x45a8, [0x81,0xe2,0xfc,0x79,0x65,0x8,0x36,0x34]}; 346 enum KNOWNFOLDERID FOLDERID_PublicDownloads = {0x3d644c9b, 0x1fb8, 0x4f30, [0x9b,0x45,0xf6,0x70,0x23,0x5f,0x79,0xc0]}; 347 enum KNOWNFOLDERID FOLDERID_PublicMusic = {0x3214fab5, 0x9757, 0x4298, [0xbb,0x61,0x92,0xa9,0xde,0xaa,0x44,0xff]}; 348 enum KNOWNFOLDERID FOLDERID_PublicPictures = {0xb6ebfb86, 0x6907, 0x413c, [0x9a,0xf7,0x4f,0xc2,0xab,0xf0,0x7c,0xc5]}; 349 enum KNOWNFOLDERID FOLDERID_PublicVideos = {0x2400183a, 0x6185, 0x49fb, [0xa2,0xd8,0x4a,0x39,0x2a,0x60,0x2b,0xa3]}; 350 } 351 352 private { 353 extern(Windows) @nogc @system BOOL _dummy_SHGetSpecialFolderPath(HWND, wchar*, int, BOOL) nothrow { return 0; } 354 extern(Windows) @nogc @system HRESULT _dummy_SHGetKnownFolderPath(const(KNOWNFOLDERID)* rfid, DWORD dwFlags, HANDLE hToken, wchar** ppszPath) nothrow { return 0; } 355 extern(Windows) @nogc @system void _dummy_CoTaskMemFree(void* pv) nothrow {return;} 356 357 __gshared typeof(&_dummy_SHGetSpecialFolderPath) ptrSHGetSpecialFolderPath = null; 358 __gshared typeof(&_dummy_SHGetKnownFolderPath) ptrSHGetKnownFolderPath = null; 359 __gshared typeof(&_dummy_CoTaskMemFree) ptrCoTaskMemFree = null; 360 361 @nogc @trusted bool hasSHGetSpecialFolderPath() nothrow { 362 return ptrSHGetSpecialFolderPath !is null; 363 } 364 365 @nogc @trusted bool hasSHGetKnownFolderPath() nothrow { 366 return ptrSHGetKnownFolderPath !is null && ptrCoTaskMemFree !is null; 367 } 368 } 369 370 shared static this() 371 { 372 HMODULE shellLib = LoadLibraryA("Shell32"); 373 if (shellLib !is null) { 374 ptrSHGetKnownFolderPath = cast(typeof(ptrSHGetKnownFolderPath))enforce(GetProcAddress(shellLib, "SHGetKnownFolderPath")); 375 if (ptrSHGetKnownFolderPath) { 376 HMODULE ole = LoadLibraryA("Ole32"); 377 if (ole !is null) { 378 ptrCoTaskMemFree = cast(typeof(ptrCoTaskMemFree))enforce(GetProcAddress(ole, "CoTaskMemFree")); 379 if (!ptrCoTaskMemFree) { 380 FreeLibrary(ole); 381 } 382 } 383 } 384 385 if (!hasSHGetKnownFolderPath()) { 386 ptrSHGetSpecialFolderPath = cast(typeof(ptrSHGetSpecialFolderPath))GetProcAddress(shellLib, "SHGetSpecialFolderPathW"); 387 } 388 } 389 } 390 391 private string getCSIDLFolder(int csidl, FolderFlag params = FolderFlag.none) nothrow @trusted { 392 import core.stdc.wchar_ : wcslen; 393 394 if (params & FolderFlag.create) { 395 csidl |= CSIDL_FLAG_CREATE; 396 } 397 if (!(params & FolderFlag.verify)) { 398 csidl |= CSIDL_FLAG_DONT_VERIFY; 399 } 400 wchar[MAX_PATH] path = void; 401 if (hasSHGetSpecialFolderPath() && ptrSHGetSpecialFolderPath(null, path.ptr, csidl, FALSE)) { 402 size_t len = wcslen(path.ptr); 403 try { 404 return toUTF8(path[0..len]); 405 } catch(Exception e) { 406 407 } 408 } 409 return null; 410 } 411 412 private string getKnownFolder(const(KNOWNFOLDERID) folder, FolderFlag params = FolderFlag.none) nothrow @trusted { 413 import core.stdc.wchar_ : wcslen; 414 415 wchar* str; 416 417 DWORD flags = 0; 418 if (params & FolderFlag.create) { 419 flags |= KF_FLAG_CREATE; 420 } 421 if (!(params & FolderFlag.verify)) { 422 flags |= KF_FLAG_DONT_VERIFY; 423 } 424 425 if (hasSHGetKnownFolderPath() && ptrSHGetKnownFolderPath(&folder, flags, null, &str) == S_OK) { 426 scope(exit) ptrCoTaskMemFree(str); 427 try { 428 return str[0..wcslen(str)].toUTF8; 429 } catch(Exception e) { 430 431 } 432 } 433 return null; 434 } 435 436 string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe 437 { 438 if (hasSHGetKnownFolderPath()) { 439 return getKnownFolder(FOLDERID_RoamingAppData, params); 440 } else if (hasSHGetSpecialFolderPath()) { 441 return getCSIDLFolder(CSIDL_APPDATA, params); 442 } else { 443 return null; 444 } 445 } 446 447 string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe 448 { 449 if (hasSHGetKnownFolderPath()) { 450 return getKnownFolder(FOLDERID_SavedGames, params); 451 } else { 452 return null; 453 } 454 } 455 456 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 457 { 458 if (hasSHGetKnownFolderPath()) { 459 final switch(type) { 460 case StandardPath.config: 461 case StandardPath.data: 462 return getKnownFolder(FOLDERID_LocalAppData, params); 463 case StandardPath.cache: 464 return null; 465 case StandardPath.desktop: 466 return getKnownFolder(FOLDERID_Desktop, params); 467 case StandardPath.documents: 468 return getKnownFolder(FOLDERID_Documents, params); 469 case StandardPath.pictures: 470 return getKnownFolder(FOLDERID_Pictures, params); 471 case StandardPath.music: 472 return getKnownFolder(FOLDERID_Music, params); 473 case StandardPath.videos: 474 return getKnownFolder(FOLDERID_Videos, params); 475 case StandardPath.downloads: 476 return getKnownFolder(FOLDERID_Downloads, params); 477 case StandardPath.templates: 478 return getKnownFolder(FOLDERID_Templates, params); 479 case StandardPath.publicShare: 480 return null; 481 case StandardPath.fonts: 482 return null; 483 case StandardPath.applications: 484 return getKnownFolder(FOLDERID_Programs, params); 485 case StandardPath.startup: 486 return getKnownFolder(FOLDERID_Startup, params); 487 } 488 } else if (hasSHGetSpecialFolderPath()) { 489 final switch(type) { 490 case StandardPath.config: 491 case StandardPath.data: 492 return getCSIDLFolder(CSIDL_LOCAL_APPDATA, params); 493 case StandardPath.cache: 494 return null; 495 case StandardPath.desktop: 496 return getCSIDLFolder(CSIDL_DESKTOPDIRECTORY, params); 497 case StandardPath.documents: 498 return getCSIDLFolder(CSIDL_PERSONAL, params); 499 case StandardPath.pictures: 500 return getCSIDLFolder(CSIDL_MYPICTURES, params); 501 case StandardPath.music: 502 return getCSIDLFolder(CSIDL_MYMUSIC, params); 503 case StandardPath.videos: 504 return getCSIDLFolder(CSIDL_MYVIDEO, params); 505 case StandardPath.downloads: 506 return null; 507 case StandardPath.templates: 508 return getCSIDLFolder(CSIDL_TEMPLATES, params); 509 case StandardPath.publicShare: 510 return null; 511 case StandardPath.fonts: 512 return null; 513 case StandardPath.applications: 514 return getCSIDLFolder(CSIDL_PROGRAMS, params); 515 case StandardPath.startup: 516 return getCSIDLFolder(CSIDL_STARTUP, params); 517 } 518 } else { 519 return null; 520 } 521 } 522 523 string[] standardPaths(StandardPath type) nothrow @safe 524 { 525 string commonPath; 526 527 if (hasSHGetKnownFolderPath()) { 528 switch(type) { 529 case StandardPath.config: 530 case StandardPath.data: 531 commonPath = getKnownFolder(FOLDERID_ProgramData); 532 break; 533 case StandardPath.desktop: 534 commonPath = getKnownFolder(FOLDERID_PublicDesktop); 535 break; 536 case StandardPath.documents: 537 commonPath = getKnownFolder(FOLDERID_PublicDocuments); 538 break; 539 case StandardPath.pictures: 540 commonPath = getKnownFolder(FOLDERID_PublicPictures); 541 break; 542 case StandardPath.music: 543 commonPath = getKnownFolder(FOLDERID_PublicMusic); 544 break; 545 case StandardPath.videos: 546 commonPath = getKnownFolder(FOLDERID_PublicVideos); 547 break; 548 case StandardPath.downloads: 549 commonPath = getKnownFolder(FOLDERID_PublicDownloads); 550 break; 551 case StandardPath.templates: 552 commonPath = getKnownFolder(FOLDERID_CommonTemplates); 553 break; 554 case StandardPath.fonts: 555 commonPath = getKnownFolder(FOLDERID_Fonts); 556 break; 557 case StandardPath.applications: 558 commonPath = getKnownFolder(FOLDERID_CommonPrograms); 559 break; 560 case StandardPath.startup: 561 commonPath = getKnownFolder(FOLDERID_CommonStartup); 562 break; 563 default: 564 break; 565 } 566 } else if (hasSHGetSpecialFolderPath()) { 567 switch(type) { 568 case StandardPath.config: 569 case StandardPath.data: 570 commonPath = getCSIDLFolder(CSIDL_COMMON_APPDATA); 571 break; 572 case StandardPath.desktop: 573 commonPath = getCSIDLFolder(CSIDL_COMMON_DESKTOPDIRECTORY); 574 break; 575 case StandardPath.documents: 576 commonPath = getCSIDLFolder(CSIDL_COMMON_DOCUMENTS); 577 break; 578 case StandardPath.pictures: 579 commonPath = getCSIDLFolder(CSIDL_COMMON_PICTURES); 580 break; 581 case StandardPath.music: 582 commonPath = getCSIDLFolder(CSIDL_COMMON_MUSIC); 583 break; 584 case StandardPath.videos: 585 commonPath = getCSIDLFolder(CSIDL_COMMON_VIDEO); 586 break; 587 case StandardPath.templates: 588 commonPath = getCSIDLFolder(CSIDL_COMMON_TEMPLATES); 589 break; 590 case StandardPath.fonts: 591 commonPath = getCSIDLFolder(CSIDL_FONTS); 592 break; 593 case StandardPath.applications: 594 commonPath = getCSIDLFolder(CSIDL_COMMON_PROGRAMS); 595 break; 596 case StandardPath.startup: 597 commonPath = getCSIDLFolder(CSIDL_COMMON_STARTUP); 598 break; 599 default: 600 break; 601 } 602 } 603 604 string[] paths; 605 string userPath = writablePath(type); 606 if (userPath.length) 607 paths ~= userPath; 608 if (commonPath.length) 609 paths ~= commonPath; 610 return paths; 611 } 612 } else version(OSX) { 613 614 private enum : short { 615 kOnSystemDisk = -32768L, /* previously was 0x8000 but that is an unsigned value whereas vRefNum is signed*/ 616 kOnAppropriateDisk = -32767, /* Generally, the same as kOnSystemDisk, but it's clearer that this isn't always the 'boot' disk.*/ 617 /* Folder Domains - Carbon only. The constants above can continue to be used, but the folder/volume returned will*/ 618 /* be from one of the domains below.*/ 619 kSystemDomain = -32766, /* Read-only system hierarchy.*/ 620 kLocalDomain = -32765, /* All users of a single machine have access to these resources.*/ 621 kNetworkDomain = -32764, /* All users configured to use a common network server has access to these resources.*/ 622 kUserDomain = -32763, /* Read/write. Resources that are private to the user.*/ 623 kClassicDomain = -32762, /* Domain referring to the currently configured Classic System Folder. Not supported in Mac OS X Leopard and later.*/ 624 kFolderManagerLastDomain = -32760 625 } 626 627 private @nogc int k(string s) nothrow { 628 return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]; 629 } 630 631 private enum { 632 kDesktopFolderType = k("desk"), /* the desktop folder; objects in this folder show on the desktop. */ 633 kTrashFolderType = k("trsh"), /* the trash folder; objects in this folder show up in the trash */ 634 kWhereToEmptyTrashFolderType = k("empt"), /* the "empty trash" folder; Finder starts empty from here down */ 635 kFontsFolderType = k("font"), /* Fonts go here */ 636 kPreferencesFolderType = k("pref"), /* preferences for applications go here */ 637 kSystemPreferencesFolderType = k("sprf"), /* the PreferencePanes folder, where Mac OS X Preference Panes go */ 638 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*/ 639 /* folders such that other users can not access the data inside. On Mac OS X 10.4 and later the data inside the temporary*/ 640 /* items folder is deleted at logout and at boot, but not otherwise. Earlier version of Mac OS X would delete items inside*/ 641 /* the temporary items folder after a period of inaccess. You can ask for a temporary item in a specific domain or on a */ 642 /* particular volume by FSVolumeRefNum. If you want a location for temporary items for a short time, then use either*/ 643 /* ( kUserDomain, kkTemporaryFolderType ) or ( kSystemDomain, kTemporaryFolderType ). The kUserDomain varient will always be*/ 644 /* on the same volume as the user's home folder, while the kSystemDomain version will be on the same volume as /var/tmp/ ( and*/ 645 /* 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*/ 646 /* file or folder to use for saving a document, especially if you want to use FSpExchangeFile() to implement a safe-save, then*/ 647 /* ask for the temporary items folder on the same volume as the file you are safe saving.*/ 648 /* However, be prepared for a failure to find a temporary folder in any domain or on any volume. Some volumes may not have*/ 649 /* a location for a temporary folder, or the permissions of the volume may be such that the Folder Manager can not return*/ 650 /* a temporary folder for the volume.*/ 651 /* If your application creates an item in a temporary items older you should delete that item as soon as it is not needed,*/ 652 /* and certainly before your application exits, since otherwise the item is consuming disk space until the user logs out or*/ 653 /* restarts. Any items left inside a temporary items folder should be moved into a folder inside the Trash folder on the disk*/ 654 /* when the user logs in, inside a folder named "Recovered items", in case there is anything useful to the end user.*/ 655 kChewableItemsFolderType = k("flnt"), /* similar to kTemporaryItemsFolderType, except items in this folder are deleted at boot or when the disk is unmounted */ 656 kTemporaryItemsInCacheDataFolderType = k("vtmp"), /* A folder inside the kCachedDataFolderType for the given domain which can be used for transient data*/ 657 kApplicationsFolderType = k("apps"), /* Applications on Mac OS X are typically put in this folder ( or a subfolder ).*/ 658 kVolumeRootFolderType = k("root"), /* root folder of a volume or domain */ 659 kDomainTopLevelFolderType = k("dtop"), /* The top-level of a Folder domain, e.g. "/System"*/ 660 kDomainLibraryFolderType = k("dlib"), /* the Library subfolder of a particular domain*/ 661 kUsersFolderType = k("usrs"), /* "Users" folder, usually contains one folder for each user. */ 662 kCurrentUserFolderType = k("cusr"), /* The folder for the currently logged on user; domain passed in is ignored. */ 663 kSharedUserDataFolderType = k("sdat"), /* A Shared folder, readable & writeable by all users */ 664 kCachedDataFolderType = k("cach"), /* Contains various cache files for different clients*/ 665 kDownloadsFolderType = k("down"), /* Refers to the ~/Downloads folder*/ 666 kApplicationSupportFolderType = k("asup"), /* third-party items and folders */ 667 668 669 kDocumentsFolderType = k("docs"), /* User documents are typically put in this folder ( or a subfolder ).*/ 670 kPictureDocumentsFolderType = k("pdoc"), /* Refers to the "Pictures" folder in a users home directory*/ 671 kMovieDocumentsFolderType = k("mdoc"), /* Refers to the "Movies" folder in a users home directory*/ 672 kMusicDocumentsFolderType = 0xB5646F63/*'µdoc'*/, /* Refers to the "Music" folder in a users home directory*/ 673 kInternetSitesFolderType = k("site"), /* Refers to the "Sites" folder in a users home directory*/ 674 kPublicFolderType = k("pubb"), /* Refers to the "Public" folder in a users home directory*/ 675 676 kDropBoxFolderType = k("drop") /* Refers to the "Drop Box" folder inside the user's home directory*/ 677 }; 678 679 private { 680 struct FSRef { 681 char[80] hidden; /* private to File Manager*/ 682 }; 683 684 alias int Boolean; 685 alias int OSType; 686 alias short OSErr; 687 alias int OSStatus; 688 689 extern(C) @nogc @system OSErr _dummy_FSFindFolder(short, OSType, Boolean, FSRef*) nothrow { return 0; } 690 extern(C) @nogc @system OSStatus _dummy_FSRefMakePath(const(FSRef)*, char*, uint) nothrow { return 0; } 691 692 __gshared typeof(&_dummy_FSFindFolder) ptrFSFindFolder = null; 693 __gshared typeof(&_dummy_FSRefMakePath) ptrFSRefMakePath = null; 694 } 695 696 shared static this() 697 { 698 enum carbonPath = "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore"; 699 700 import core.sys.posix.dlfcn; 701 702 void* handle = dlopen(toStringz(carbonPath), RTLD_NOW | RTLD_LOCAL); 703 if (handle) { 704 ptrFSFindFolder = cast(typeof(ptrFSFindFolder))dlsym(handle, "FSFindFolder"); 705 ptrFSRefMakePath = cast(typeof(ptrFSRefMakePath))dlsym(handle, "FSRefMakePath"); 706 } 707 if (ptrFSFindFolder == null || ptrFSRefMakePath == null) { 708 debug collectException(stderr.writeln("Could not load carbon functions")); 709 if (handle) dlclose(handle); 710 } 711 } 712 713 private @nogc @trusted bool isCarbonLoaded() nothrow 714 { 715 return ptrFSFindFolder != null && ptrFSRefMakePath != null; 716 } 717 718 private enum OSErr noErr = 0; 719 720 private string fsPath(short domain, OSType type, bool shouldCreate = false) nothrow @trusted 721 { 722 import std.stdio; 723 FSRef fsref; 724 if (isCarbonLoaded() && ptrFSFindFolder(domain, type, shouldCreate, &fsref) == noErr) { 725 726 char[2048] buf; 727 char* path = buf.ptr; 728 if (ptrFSRefMakePath(&fsref, path, buf.sizeof) == noErr) { 729 try { 730 return fromStringz(path).idup; 731 } 732 catch(Exception e) { 733 734 } 735 } 736 } 737 return null; 738 } 739 740 private string writablePathImpl(StandardPath type, bool shouldCreate = false) nothrow @safe 741 { 742 final switch(type) { 743 case StandardPath.config: 744 return fsPath(kUserDomain, kPreferencesFolderType, shouldCreate); 745 case StandardPath.cache: 746 return fsPath(kUserDomain, kCachedDataFolderType, shouldCreate); 747 case StandardPath.data: 748 return fsPath(kUserDomain, kApplicationSupportFolderType, shouldCreate); 749 case StandardPath.desktop: 750 return fsPath(kUserDomain, kDesktopFolderType, shouldCreate); 751 case StandardPath.documents: 752 return fsPath(kUserDomain, kDocumentsFolderType, shouldCreate); 753 case StandardPath.pictures: 754 return fsPath(kUserDomain, kPictureDocumentsFolderType, shouldCreate); 755 case StandardPath.music: 756 return fsPath(kUserDomain, kMusicDocumentsFolderType, shouldCreate); 757 case StandardPath.videos: 758 return fsPath(kUserDomain, kMovieDocumentsFolderType, shouldCreate); 759 case StandardPath.downloads: 760 return fsPath(kUserDomain, kDownloadsFolderType, shouldCreate); 761 case StandardPath.templates: 762 return null; 763 case StandardPath.publicShare: 764 return fsPath(kUserDomain, kPublicFolderType, shouldCreate); 765 case StandardPath.fonts: 766 return fsPath(kUserDomain, kFontsFolderType, shouldCreate); 767 case StandardPath.applications: 768 return fsPath(kUserDomain, kApplicationsFolderType, shouldCreate); 769 case StandardPath.startup: 770 return null; 771 } 772 } 773 774 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 775 { 776 const bool shouldCreate = (params & FolderFlag.create) != 0; 777 const bool shouldVerify = (params & FolderFlag.verify) != 0; 778 return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify); 779 } 780 781 string[] standardPaths(StandardPath type) nothrow @safe 782 { 783 string commonPath; 784 785 switch(type) { 786 case StandardPath.fonts: 787 commonPath = fsPath(kOnAppropriateDisk, kFontsFolderType); 788 break; 789 case StandardPath.applications: 790 commonPath = fsPath(kOnAppropriateDisk, kApplicationsFolderType); 791 break; 792 case StandardPath.data: 793 commonPath = fsPath(kOnAppropriateDisk, kApplicationSupportFolderType); 794 break; 795 case StandardPath.cache: 796 commonPath = fsPath(kOnAppropriateDisk, kCachedDataFolderType); 797 break; 798 default: 799 break; 800 } 801 802 string[] paths; 803 string userPath = writablePath(type); 804 if (userPath.length) 805 paths ~= userPath; 806 if (commonPath.length) 807 paths ~= commonPath; 808 return paths; 809 } 810 811 } else { 812 813 static if (!isFreedesktop) { 814 static assert(false, "Unsupported platform"); 815 } else { 816 public import xdgpaths; 817 818 private { 819 import std.stdio : File; 820 import std.algorithm : startsWith; 821 import std.string; 822 import std.traits; 823 } 824 825 //Concat two strings, but if the first one is empty, then null string is returned. 826 private string maybeConcat(string start, string path) nothrow @safe 827 { 828 return start.empty ? null : start ~ path; 829 } 830 831 unittest 832 { 833 assert(maybeConcat(null, "path") == string.init); 834 assert(maybeConcat("path", "/file") == "path/file"); 835 } 836 837 private @trusted string getFromUserDirs(Range)(string xdgdir, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range)) 838 { 839 foreach(line; range) { 840 line = strip(line); 841 auto index = xdgdir.length; 842 if (line.startsWith(xdgdir) && line.length > index && line[index] == '=') { 843 line = line[index+1..$]; 844 if (line.length > 2 && line[0] == '"' && line[$-1] == '"') 845 { 846 line = line[1..$-1]; 847 848 if (line.startsWith("$HOME")) { 849 return maybeConcat(home, assumeUnique(line[5..$])); 850 } 851 if (line.length == 0 || line[0] != '/') { 852 continue; 853 } 854 return assumeUnique(line); 855 } 856 } 857 } 858 return null; 859 } 860 861 862 unittest 863 { 864 string content = 865 `# Comment 866 867 XDG_DOCUMENTS_DIR="$HOME/My Documents" 868 XDG_MUSIC_DIR="/data/Music" 869 XDG_VIDEOS_DIR="data/Video" 870 `; 871 string home = "/home/user"; 872 873 assert(getFromUserDirs("XDG_DOCUMENTS_DIR", home, content.splitLines) == "/home/user/My Documents"); 874 assert(getFromUserDirs("XDG_MUSIC_DIR", home, content.splitLines) == "/data/Music"); 875 assert(getFromUserDirs("XDG_DOWNLOAD_DIR", home, content.splitLines).empty); 876 assert(getFromUserDirs("XDG_VIDEOS_DIR", home, content.splitLines).empty); 877 } 878 879 private @trusted string getFromDefaultDirs(Range)(string key, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range)) 880 { 881 foreach(line; range) { 882 line = strip(line); 883 auto index = key.length; 884 if (line.startsWith(key) && line.length > index && line[index] == '=') 885 { 886 line = line[index+1..$]; 887 return home ~ "/" ~ assumeUnique(line); 888 } 889 } 890 return null; 891 } 892 893 unittest 894 { 895 string content = 896 `# Comment 897 898 DOCUMENTS=MyDocuments 899 PICTURES=Images 900 `; 901 string home = "/home/user"; 902 assert(getFromDefaultDirs("DOCUMENTS", home, content.splitLines) == "/home/user/MyDocuments"); 903 assert(getFromDefaultDirs("PICTURES", home, content.splitLines) == "/home/user/Images"); 904 assert(getFromDefaultDirs("VIDEOS", home, content.splitLines).empty); 905 } 906 907 private string xdgUserDir(string key, string fallback = null) nothrow @trusted { 908 string fileName = maybeConcat(writablePath(StandardPath.config), "/user-dirs.dirs"); 909 string home = homeDir(); 910 try { 911 auto f = File(fileName, "r"); 912 auto xdgdir = "XDG_" ~ key ~ "_DIR"; 913 auto path = getFromUserDirs(xdgdir, home, f.byLine()); 914 if (path.length) { 915 return path; 916 } 917 } catch(Exception e) { 918 919 } 920 921 if (home.length) { 922 try { 923 auto f = File("/etc/xdg/user-dirs.defaults", "r"); 924 auto path = getFromDefaultDirs(key, home, f.byLine()); 925 if (path.length) { 926 return path; 927 } 928 } catch (Exception e) { 929 930 } 931 if (fallback.length) { 932 return home ~ fallback; 933 } 934 } 935 return null; 936 } 937 938 private string homeFontsPath() nothrow @trusted { 939 return maybeConcat(homeDir(), "/.fonts"); 940 } 941 942 private string[] fontPaths() nothrow @trusted 943 { 944 enum localShare = "/usr/local/share/fonts"; 945 enum share = "/usr/share/fonts"; 946 947 string homeFonts = homeFontsPath(); 948 if (homeFonts.length) { 949 return [homeFonts, localShare, share]; 950 } else { 951 return [localShare, share]; 952 } 953 } 954 955 private string createIfNeeded(string path, bool shouldCreate) nothrow @trusted 956 { 957 if (path.length && shouldCreate) { 958 bool pathExist; 959 collectException(path.isDir, pathExist); 960 if (pathExist || collectException(mkdirRecurse(path)) is null) { 961 return path; 962 } else { 963 return null; 964 } 965 } else { 966 return path; 967 } 968 } 969 970 private string writablePathImpl(StandardPath type, bool shouldCreate) nothrow @safe 971 { 972 final switch(type) { 973 case StandardPath.config: 974 return xdgConfigHome(null, shouldCreate); 975 case StandardPath.cache: 976 return xdgCacheHome(null, shouldCreate); 977 case StandardPath.data: 978 return xdgDataHome(null, shouldCreate); 979 case StandardPath.desktop: 980 return xdgUserDir("DESKTOP", "/Desktop").createIfNeeded(shouldCreate); 981 case StandardPath.documents: 982 return xdgUserDir("DOCUMENTS").createIfNeeded(shouldCreate); 983 case StandardPath.pictures: 984 return xdgUserDir("PICTURES").createIfNeeded(shouldCreate); 985 case StandardPath.music: 986 return xdgUserDir("MUSIC").createIfNeeded(shouldCreate); 987 case StandardPath.videos: 988 return xdgUserDir("VIDEOS").createIfNeeded(shouldCreate); 989 case StandardPath.downloads: 990 return xdgUserDir("DOWNLOAD").createIfNeeded(shouldCreate); 991 case StandardPath.templates: 992 return xdgUserDir("TEMPLATES", "/Templates").createIfNeeded(shouldCreate); 993 case StandardPath.publicShare: 994 return xdgUserDir("PUBLICSHARE", "/Public").createIfNeeded(shouldCreate); 995 case StandardPath.fonts: 996 return homeFontsPath().createIfNeeded(shouldCreate); 997 case StandardPath.applications: 998 return xdgDataHome("applications", shouldCreate); 999 case StandardPath.startup: 1000 return xdgConfigHome("autostart", shouldCreate); 1001 } 1002 } 1003 1004 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 1005 { 1006 const bool shouldCreate = (params & FolderFlag.create) != 0; 1007 const bool shouldVerify = (params & FolderFlag.verify) != 0; 1008 return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify); 1009 } 1010 1011 string[] standardPaths(StandardPath type) nothrow @safe 1012 { 1013 string[] paths; 1014 1015 switch(type) { 1016 case StandardPath.data: 1017 return xdgAllDataDirs(); 1018 case StandardPath.config: 1019 return xdgAllConfigDirs(); 1020 case StandardPath.applications: 1021 return xdgAllDataDirs("applications"); 1022 case StandardPath.startup: 1023 return xdgAllConfigDirs("autostart"); 1024 case StandardPath.fonts: 1025 return fontPaths(); 1026 default: 1027 break; 1028 } 1029 1030 string userPath = writablePath(type); 1031 if (userPath.length) { 1032 return [userPath]; 1033 } 1034 return null; 1035 } 1036 } 1037 }