├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE_1_0.txt ├── README.md ├── appveyor.yml ├── dub.json ├── examples ├── getpath.d └── printdirs.d └── source └── standardpaths.d /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | pull_request: 4 | push: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | Build: 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | dc: 16 | - ldc-latest 17 | - dmd-latest 18 | exclude: 19 | - os: macos-latest 20 | dc: dmd-latest 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: recursive 26 | 27 | - name: Setup D 28 | uses: dlang-community/setup-dlang@v1 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | 32 | - name: Build and run example 33 | run: | 34 | dub build 35 | dub --single examples/printdirs.d 36 | 37 | - name: Build and run example with Cocoa 38 | if: ${{ startsWith(matrix.os, 'macos') }} 39 | run: | 40 | dub build --config=cocoa 41 | dub --single examples/printdirs.d --override-config=standardpaths/cocoa 42 | 43 | - name: Run tests on xdg 44 | if: ${{ startsWith(matrix.os, 'ubuntu') }} 45 | run: | 46 | dub test -b unittest 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | *.a 7 | *.lib 8 | *.so 9 | *.dll 10 | *.exe 11 | bin/ 12 | docs/ 13 | lib/ 14 | *.lst 15 | .DS_store 16 | dub.selections.json 17 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Standard paths 2 | 3 | D library for getting standard paths (e.g. Pictures, Music, Documents and also generic configuration and data paths). 4 | Inspired by QStandardPaths from Qt. 5 | 6 | [![Build Status](https://github.com/FreeSlave/standardpaths/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/FreeSlave/standardpaths/actions/workflows/ci.yml) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/FreeSlave/standardpaths?branch=master&svg=true)](https://ci.appveyor.com/project/FreeSlave/standardpaths) 7 | 8 | [Online documentation](http://freeslave.github.io/standardpaths/standardpaths.html) 9 | 10 | ## Platform support 11 | 12 | Works on Freedesktop (GNU/Linux, FreeBSD, etc.), Windows and OS X. 13 | 14 | ## Running examples 15 | 16 | ### [Print directories](examples/printdirs.d) 17 | 18 | Prints some standard paths to stdout. 19 | 20 | dub examples/printdirs.d 21 | 22 | On OSX it also can be built to use Cocoa instead of Carbon: 23 | 24 | dub --single examples/printdirs.d --override-config=standardpaths/cocoa 25 | 26 | ### [Get path](examples/getpath.d) 27 | 28 | Get path of given type, verify it exists or create if it does not. 29 | 30 | dub examples/getpath.d --verify --create templates 31 | 32 | Use Cocoa instead of Carbon on OSX: 33 | 34 | dub --single examples/getpath.d --override-config=standardpaths/cocoa -- --create music 35 | 36 | With subfolder: 37 | 38 | dub examples/getpath.d --subfolder=MyLittleCompany/MyLittleApplication data 39 | 40 | ## Use cases 41 | 42 | Some code snippets showing how standardpaths library is supposed to be used. 43 | 44 | ### Building file dialogs 45 | 46 | Let's say you have some fancy FileDialog class and you want to provide shortcuts to standard user directories to improve experience. 47 | Your code may look like this: 48 | 49 | ```d 50 | import standardpaths; 51 | import std.file; 52 | import std.stdio; 53 | 54 | void showFileDialog() 55 | { 56 | auto fileDialog = new FileDialog; 57 | auto folderFlag = FolderFlag.verify; 58 | 59 | string[] paths = [ 60 | homeDir(), 61 | writablePath(StandardPath.desktop, folderFlag), 62 | writablePath(StandardPath.downloads, folderFlag), 63 | writablePath(StandardPath.documents, folderFlag), 64 | writablePath(StandardPath.pictures, folderFlag), 65 | writablePath(StandardPath.music, folderFlag), 66 | writablePath(StandardPath.videos, folderFlag), 67 | writablePath(StandardPath.templates, folderFlag), 68 | writablePath(StandardPath.publicShare, folderFlag) 69 | ]; 70 | foreach(path; paths) { 71 | if (path.length) { 72 | string label = path.baseName(); 73 | fileDialog.addPath(label, path); 74 | } 75 | } 76 | fileDialog.show(); 77 | } 78 | ``` 79 | 80 | ### Writing configuration files 81 | 82 | Usually your application will have some configuration file (or files) to store user's preferences and settings. That's where you could use StandardPath.config path. 83 | While the library returns generic paths for configuration, data and cache, you want to have separate folders specially for your application, so you will not accidentally read or modify files used by other programs. 84 | Usually these paths are built by concatenating of generic path, organization name and application name. 85 | 86 | ```d 87 | //You may have these as constants somewhere in your code 88 | enum organizationName = "MyLittleCompany"; 89 | enum applicationName = "MyLittleApplication"; 90 | 91 | import standardpaths; 92 | import std.stdio; 93 | import std.path; 94 | 95 | void saveSettings(const Config config) 96 | { 97 | string configDir = writablePath(StandardPath.config, buildPath(organizationName, applicationName), FolderFlag.create); 98 | if (!configDir.length) { 99 | throw new Exception("Could not create config directory"); 100 | } 101 | string configFile = buildPath(configDir, "config.conf"); 102 | 103 | auto f = File(configFile, "w"); 104 | // write settings 105 | writeln("Settings saved!"); 106 | } 107 | ``` 108 | 109 | ### Reading configuration files 110 | 111 | Since one can save settings it also should be able to read them. Before the first start application does not have any user-specific settings, though it may provide some global default settings upon installing. 112 | It's up to developer to decide how to read configs, e.g. whether to read the first found file only or to merge settings from all found config consequentially. 113 | 114 | ```d 115 | Config readSettings() 116 | { 117 | string[] configDirs = standardPaths(StandardPath.config, buildPath(organizationName, applicationName)); 118 | 119 | foreach(configDir; configDirs) { 120 | string configFile = buildPath(configDir, "config.conf"); 121 | if (configFile.exists) { 122 | auto f = File(configFile, "r"); 123 | Config config; 124 | //read settings... 125 | return config;//consider using of the first found file 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | ## Implementation details 132 | 133 | ### Freedesktop 134 | 135 | On freedesktop systems (GNU/Linux, FreeBSD, etc.) library follows [XDG Base Directory Specification](http://standards.freedesktop.org/basedir-spec/latest/index.html#introduction) and also provides behavior similiar to [xdg-user-dirs](http://www.freedesktop.org/wiki/Software/xdg-user-dirs/). 136 | 137 | ### Windows 138 | 139 | On Windows it utilizes [SHGetKnownFolderPath](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762188(v=vs.85).aspx) or [SHGetSpecialFolderPath](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762204(v=vs.85).aspx) as fallback. 140 | 141 | ### Mac OS X 142 | 143 | Depending on configuration the library uses FSFindFolder from Carbon framework or URLForDirectory from Cocoa. 144 | [See here](http://cocoadev.com/ApplicationSupportFolder). 145 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: x64 2 | environment: 3 | matrix: 4 | - DC: dmd 5 | DVersion: 2.071.0 6 | arch: x64 7 | - DC: dmd 8 | DVersion: 2.071.0 9 | arch: x86 10 | 11 | skip_tags: true 12 | branches: 13 | only: 14 | - master 15 | - stable 16 | 17 | install: 18 | - ps: function SetUpDCompiler 19 | { 20 | if($env:DC -eq "dmd"){ 21 | if($env:arch -eq "x86"){ 22 | $env:DConf = "m32"; 23 | } 24 | elseif($env:arch -eq "x64"){ 25 | $env:DConf = "m64"; 26 | } 27 | echo "downloading ..."; 28 | $env:toolchain = "msvc"; 29 | $version = $env:DVersion; 30 | Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z"; 31 | echo "finished."; 32 | pushd c:\\; 33 | 7z x dmd.7z > $null; 34 | popd; 35 | } 36 | elseif($env:DC -eq "ldc"){ 37 | echo "downloading ..."; 38 | if($env:arch -eq "x86"){ 39 | $env:DConf = "m32"; 40 | } 41 | elseif($env:arch -eq "x64"){ 42 | $env:DConf = "m64"; 43 | } 44 | $env:toolchain = "msvc"; 45 | $version = $env:DVersion; 46 | Invoke-WebRequest "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-win64-msvc.zip" -OutFile "c:\ldc.zip"; 47 | echo "finished."; 48 | pushd c:\\; 49 | 7z x ldc.zip > $null; 50 | popd; 51 | } 52 | } 53 | - ps: SetUpDCompiler 54 | - powershell -Command Invoke-WebRequest http://code.dlang.org/files/dub-1.0.0-windows-x86.zip -OutFile dub.zip 55 | - 7z x dub.zip -odub > nul 56 | - set PATH=%CD%\%binpath%;%CD%\dub;%PATH% 57 | - dub --version 58 | 59 | before_build: 60 | - ps: if($env:arch -eq "x86"){ 61 | $env:compilersetupargs = "x86"; 62 | $env:Darch = "x86"; 63 | } 64 | elseif($env:arch -eq "x64"){ 65 | $env:compilersetupargs = "amd64"; 66 | $env:Darch = "x86_64"; 67 | } 68 | - ps : if($env:DC -eq "dmd"){ 69 | $env:PATH += ";C:\dmd2\windows\bin;"; 70 | } 71 | elseif($env:DC -eq "ldc"){ 72 | $version = $env:DVersion; 73 | $env:PATH += ";C:\ldc2-$($version)-win64-msvc\bin"; 74 | $env:DC = "ldc2"; 75 | } 76 | - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall"; 77 | - '"%compilersetup%" %compilersetupargs%' 78 | 79 | build_script: 80 | - echo dummy build script - dont remove me 81 | 82 | test_script: 83 | - echo %PLATFORM% 84 | - echo %Darch% 85 | - echo %DC% 86 | - echo %PATH% 87 | - '%DC% --version' 88 | - dub test --arch=%Darch% --compiler=%DC% 89 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "standardpaths", 3 | "description" : "Getting standard system locations", 4 | "copyright": "Copyright © 2015-2016, Roman Chistokhodov", 5 | "license" : "BSL-1.0", 6 | "authors" : ["Roman Chistokhodov"], 7 | "targetName" : "standardpaths", 8 | "targetPath" : "lib", 9 | "targetType" : "library", 10 | 11 | "dependencies" : { 12 | "xdgpaths" : "~>0.2.5" 13 | }, 14 | "configurations" : [ 15 | { 16 | "name" : "default" 17 | }, 18 | { 19 | "name" : "cocoa", 20 | "versions-osx" : ["StandardPathsCocoa"], 21 | "lflags-osx" : ["-framework", "Foundation"], 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/getpath.d: -------------------------------------------------------------------------------- 1 | /+dub.sdl: 2 | name "printdirs" 3 | dependency "standardpaths" path="../" 4 | +/ 5 | 6 | import std.stdio; 7 | import std.getopt; 8 | import standardpaths; 9 | 10 | void main(string[] args) 11 | { 12 | import std.conv : to; 13 | import std.range : iota; 14 | StandardPath[string] stringToType; 15 | foreach(StandardPath i; StandardPath.min..StandardPath.max) { 16 | stringToType[to!string(i)] = i; 17 | } 18 | bool verify; 19 | bool create; 20 | string subfolder; 21 | auto helpInformation = getopt(args, 22 | "verify", "Verify if path exists", &verify, 23 | "create", "Create if does not exist", &create, 24 | "subfolder", "Subfolder path", &subfolder 25 | ); 26 | 27 | if (helpInformation.helpWanted) 28 | { 29 | defaultGetoptPrinter("Usage: getpath [options...] ", helpInformation.options); 30 | return; 31 | } 32 | 33 | if (args.length < 2) { 34 | stderr.writeln("Path type must be specified"); 35 | return; 36 | } 37 | 38 | FolderFlag flags; 39 | if (verify) { 40 | flags |= FolderFlag.verify; 41 | } 42 | if (create) { 43 | flags |= FolderFlag.create; 44 | } 45 | foreach(pathType; args[1..$]) { 46 | StandardPath* typePtr = pathType in stringToType; 47 | if (typePtr) { 48 | StandardPath type = *typePtr; 49 | string path; 50 | if (subfolder.length) { 51 | path = writablePath(type, subfolder, flags); 52 | } else { 53 | path = writablePath(type, flags); 54 | } 55 | 56 | if (path.length) { 57 | writefln("%s: %s", pathType, path); 58 | } else { 59 | if (create) { 60 | stderr.writefln("%s: could not create path", pathType); 61 | } else if (verify) { 62 | writefln("%s: path does not exist", pathType); 63 | } else { 64 | writefln("%s: could not determine path", pathType); 65 | } 66 | } 67 | } else { 68 | stderr.writefln("Unknown type: %s", pathType); 69 | writefln("Available types: %s", iota(StandardPath.min, StandardPath.max)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/printdirs.d: -------------------------------------------------------------------------------- 1 | /+dub.sdl: 2 | name "printdirs" 3 | dependency "standardpaths" path="../" 4 | +/ 5 | 6 | import std.stdio; 7 | import standardpaths; 8 | 9 | void main() 10 | { 11 | writeln("Home: ", homeDir()); 12 | 13 | writeln("\nUser directories"); 14 | writeln("Config: ", writablePath(StandardPath.config)); 15 | writeln("Cache: ", writablePath(StandardPath.cache)); 16 | writeln("Data: ", writablePath(StandardPath.data)); 17 | 18 | writeln("Desktop: ", writablePath(StandardPath.desktop)); 19 | writeln("Documents: ", writablePath(StandardPath.documents)); 20 | writeln("Pictures: ", writablePath(StandardPath.pictures)); 21 | writeln("Music: ", writablePath(StandardPath.music)); 22 | writeln("Videos: ", writablePath(StandardPath.videos)); 23 | writeln("Downloads: ", writablePath(StandardPath.downloads)); 24 | 25 | writeln("Templates: ", writablePath(StandardPath.templates)); 26 | writeln("Public: ", writablePath(StandardPath.publicShare)); 27 | 28 | writeln("Fonts: ", writablePath(StandardPath.fonts)); 29 | writeln("Applications: ", writablePath(StandardPath.applications)); 30 | writeln("Startup: ", writablePath(StandardPath.startup)); 31 | 32 | version(Windows) { 33 | writeln("\nSpecific functions for Windows:"); 34 | writeln("Roaming data: ", writablePath(StandardPath.roaming)); 35 | writeln("Saved games: ", writablePath(StandardPath.savedGames)); 36 | } 37 | 38 | writeln("\nSystem directories"); 39 | writefln("Config dirs: %-(%s, %)", standardPaths(StandardPath.config)); 40 | writefln("Cache dirs: %-(%s, %)", standardPaths(StandardPath.cache)); 41 | writefln("Data dirs: %-(%s, %)", standardPaths(StandardPath.data)); 42 | writefln("Font dirs: %-(%s, %)", standardPaths(StandardPath.fonts)); 43 | writefln("Applications dirs: %-(%s, %)", standardPaths(StandardPath.applications)); 44 | writefln("Startup dirs: %-(%s, %)", standardPaths(StandardPath.startup)); 45 | 46 | version(Windows) { 47 | writefln("Desktop dirs: %-(%s, %)", standardPaths(StandardPath.desktop)); 48 | writefln("Documents dirs: %-(%s, %)", standardPaths(StandardPath.documents)); 49 | writefln("Downloads dirs: %-(%s, %)", standardPaths(StandardPath.downloads)); 50 | writefln("Pictures dirs: %-(%s, %)", standardPaths(StandardPath.pictures)); 51 | writefln("Music dirs: %-(%s, %)", standardPaths(StandardPath.music)); 52 | writefln("Videos dirs: %-(%s, %)", standardPaths(StandardPath.videos)); 53 | writefln("Templates dirs: %-(%s, %)", standardPaths(StandardPath.templates)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/standardpaths.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions for retrieving standard paths in cross-platform manner. 3 | * Authors: 4 | * $(LINK2 https://github.com/FreeSlave, 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 | string verifyIfNeeded(string path, bool shouldVerify) nothrow @trusted 27 | { 28 | if (path.length && shouldVerify) { 29 | bool dirExists; 30 | collectException(path.isDir, dirExists); 31 | return dirExists ? path : null; 32 | } else { 33 | return path; 34 | } 35 | } 36 | 37 | string createIfNeeded(string path, bool shouldCreate) nothrow @trusted 38 | { 39 | if (path.length && shouldCreate) { 40 | bool pathExist; 41 | collectException(path.isDir, pathExist); 42 | if (pathExist || collectException(mkdirRecurse(path)) is null) { 43 | return path; 44 | } else { 45 | return null; 46 | } 47 | } else { 48 | return path; 49 | } 50 | } 51 | } 52 | 53 | version(Windows) { 54 | private { 55 | import core.sys.windows.windows; 56 | import std.utf; 57 | } 58 | } else version(Posix) { 59 | private { 60 | //Concat two strings, but if the first one is empty, then null string is returned. 61 | string maybeConcat(string start, string path) nothrow @safe 62 | { 63 | return start.empty ? null : start ~ path; 64 | } 65 | 66 | string maybeBuild(string start, string path) nothrow @safe 67 | { 68 | return start.empty ? null : buildPath(start, path); 69 | } 70 | } 71 | } else { 72 | static assert(false, "Unsupported platform"); 73 | } 74 | 75 | /** 76 | * Location types that can be passed to $(D writablePath) and $(D standardPaths) functions. 77 | * 78 | * Not all these paths are suggested for showing in file managers or file dialogs. 79 | * Some of them are meant for internal application usage or should be treated in special way. 80 | * On usual circumstances user wants to see Desktop, Documents, Downloads, Pictures, Music and Videos directories. 81 | * 82 | * See_Also: 83 | * $(D writablePath), $(D standardPaths) 84 | */ 85 | enum StandardPath { 86 | /** 87 | * General location of persisted application data. Every application should have its own subdirectory here. 88 | * Note: on Windows it's the same as $(D config) path. 89 | */ 90 | data, 91 | /** 92 | * General location of configuration files. Every application should have its own subdirectory here. 93 | * Note: on Windows it's the same as $(D data) path. 94 | */ 95 | config, 96 | /** 97 | * Location of cached data. 98 | * Note: Not available on Windows. 99 | */ 100 | cache, 101 | ///User's desktop directory. 102 | desktop, 103 | ///User's documents. 104 | documents, 105 | ///User's pictures. 106 | pictures, 107 | 108 | ///User's music. 109 | music, 110 | 111 | ///User's videos (movies). 112 | videos, 113 | 114 | ///Directory for user's downloaded files. 115 | downloads, 116 | 117 | /** 118 | * Location of file templates (e.g. office suite document templates). 119 | * Note: Not available on OS X. 120 | */ 121 | templates, 122 | 123 | /** 124 | * Public share folder. 125 | * Note: Not available on Windows. 126 | */ 127 | publicShare, 128 | /** 129 | * Location of fonts files. 130 | * 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) 131 | */ 132 | fonts, 133 | /** 134 | * User's applications. This has different meaning across platforms. 135 | * On Windows it's directory where links (.lnk) to programs for Start menu are stored. 136 | * On OS X it's folder where applications are typically put. 137 | * On Freedesktop it's directory where .desktop files are put. 138 | */ 139 | applications, 140 | 141 | /** 142 | * Automatically started applications. 143 | * On Windows it's directory where links (.lnk) to autostarted programs are stored. 144 | * On OSX it's not available. 145 | * On Freedesktop it's directory where autostarted .desktop files are stored. 146 | */ 147 | startup, 148 | /** 149 | * Roaming directory that stores a user data which should be shared between user profiles on different machines. Windows-only. 150 | */ 151 | roaming, 152 | /** 153 | * Common directory for game save files. Windows-only. 154 | */ 155 | savedGames 156 | } 157 | 158 | /** 159 | * Control behavior of functions. 160 | * See_Also: $(D writablePath) 161 | */ 162 | enum FolderFlag 163 | { 164 | none = 0, /// Don't verify that folder exist. 165 | /** 166 | * Create if folder does not exist. 167 | * On Windows and OS X directory will be created using platform specific API, so it will have appropriate icon and other settings special for this kind of folder. 168 | */ 169 | create = 1, 170 | /** 171 | * Verify that folder exists. 172 | * On Windows directory is verified using platform specific API. 173 | */ 174 | verify = 2 175 | } 176 | 177 | /** 178 | * Current user home directory. 179 | * Returns: Path to user home directory, or an empty string if could not determine home directory. 180 | * Relies on environment variables. 181 | * Note: This function does not cache its result. 182 | */ 183 | string homeDir() nothrow @safe 184 | { 185 | try { 186 | version(Windows) { 187 | //Use GetUserProfileDirectoryW from Userenv.dll? 188 | string home = environment.get("USERPROFILE"); 189 | if (home.empty) { 190 | string homeDrive = environment.get("HOMEDRIVE"); 191 | string homePath = environment.get("HOMEPATH"); 192 | if (homeDrive.length && homePath.length) { 193 | home = homeDrive ~ homePath; 194 | } 195 | } 196 | return home; 197 | } else { 198 | string home = environment.get("HOME"); 199 | return home; 200 | } 201 | } 202 | catch (Exception e) { 203 | debug { 204 | @trusted void writeException(Exception e) nothrow { 205 | collectException(stderr.writefln("Error when getting home directory %s", e.msg)); 206 | } 207 | writeException(e); 208 | } 209 | return null; 210 | } 211 | } 212 | 213 | /** 214 | * Get writable path for specific location. 215 | * Returns: Path where files of $(U type) should be written to by current user, or an empty string if could not determine path. 216 | * Params: 217 | * type = Location to lookup. 218 | * params = Union of $(D FolderFlag)s. 219 | * Note: This function does not cache its results. 220 | * Example: 221 | -------------------- 222 | string downloadsDir = writablePath(StandardPath.downloads, FolderFlag.verify); 223 | if (downloadsDir.length) { 224 | //Open file dialog with this directory. 225 | } else { 226 | //Could not detect default downloads directory. 227 | //Ask user to choose default downloads directory for this application. 228 | } 229 | -------------------- 230 | * See_Also: $(D StandardPath), $(D FolderFlag), $(D standardPaths) 231 | */ 232 | string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe; 233 | 234 | /** 235 | * Get paths for various locations. 236 | * Returns: Array of paths where files of $(U type) belong including one returned by $(D writablePath), or an empty array if no paths are defined for $(U type). 237 | * This function does not ensure if all returned paths exist and appear to be accessible directories. Returned strings are not required to be unique. 238 | * Note: This function does not cache its results. 239 | * It may cause performance impact to call this function often since retrieving some paths can be relatively expensive operation. 240 | * Example: 241 | -------------------- 242 | string[] templateDirs = standardPaths(StandardPath.templates); 243 | //List all available file templates including system defined and user created ones. 244 | -------------------- 245 | * See_Also: $(D StandardPath), $(D writablePath) 246 | */ 247 | string[] standardPaths(StandardPath type) nothrow @safe; 248 | 249 | /** 250 | * Evaluate writable path for specific location and append subfolder. 251 | * This can be used with $(D StandardPath.config) and $(D StandardPath.data) to retrieve folder specific for this application instead of generic path. 252 | * Returns: Path where files of $(U type) should be written to by current user concatenated with subfolder, 253 | * or an empty string if could not determine path. 254 | * Params: 255 | * type = Location to lookup. 256 | * subfolder = Subfolder that will be appended to base writable path. 257 | * params = Union of $(D FolderFlag)s. This affects both base path and sub path. 258 | * Note: This function does not cache its results. 259 | * Example: 260 | -------------------- 261 | enum organizationName = "MyLittleCompany"; 262 | enum applicationName = "MyLittleApplication"; 263 | 264 | string configDir = writablePath(StandardPath.config, buildPath(organizationName, applicationName), FolderFlag.create); 265 | if (configDir.length) { 266 | string configFile = buildPath(configDir, "config.conf"); 267 | //read or write configuration file. 268 | } else { 269 | throw new Exception("Could not create application config directory"); 270 | } 271 | -------------------- 272 | */ 273 | string writablePath(StandardPath type, string subfolder, FolderFlag params = FolderFlag.none) nothrow @safe 274 | { 275 | string baseWritablePath = writablePath(type, params); 276 | if (baseWritablePath.length) { 277 | string toReturn = buildPath(baseWritablePath, subfolder); 278 | const bool shouldCreate = (params & FolderFlag.create) != 0; 279 | const bool shouldVerify = (params & FolderFlag.verify) != 0; 280 | return toReturn.createIfNeeded(shouldCreate).verifyIfNeeded(shouldVerify); 281 | } else { 282 | return null; 283 | } 284 | } 285 | 286 | /** 287 | * Evaluate paths for various locations and append subfolder. 288 | * Example: 289 | -------------------- 290 | enum organizationName = "MyLittleCompany"; 291 | enum applicationName = "MyLittleApplication"; 292 | 293 | string[] appDataDirs = standardPaths(StandardPath.data, buildPath(organizationName, applicationName)); 294 | //Gather data files for this application from each found directory. 295 | -------------------- 296 | */ 297 | string[] standardPaths(StandardPath type, string subfolder) nothrow @safe 298 | { 299 | auto toReturn = standardPaths(type); 300 | foreach(ref s; toReturn) { 301 | s = buildPath(s, subfolder); 302 | } 303 | return toReturn; 304 | } 305 | 306 | version(D_Ddoc) 307 | { 308 | /** 309 | * Path to $(B Roaming) data directory. Windows only. 310 | * Returns: User's Roaming directory. On fail returns an empty string. 311 | * See_Also: $(D writablePath), $(D FolderFlag) 312 | */ 313 | deprecated string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe; 314 | 315 | /** 316 | * Location where games may store their saves. Windows only. 317 | * Note: This is common path for games. One should use subfolder for their game saves. 318 | * Returns: User's Saved Games directory. On fail returns an empty string. 319 | * See_Also: $(D writablePath), $(D FolderFlag) 320 | */ 321 | deprecated string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe; 322 | } 323 | 324 | version(Windows) { 325 | private { 326 | enum { 327 | CSIDL_DESKTOP = 0, 328 | CSIDL_INTERNET, 329 | CSIDL_PROGRAMS, 330 | CSIDL_CONTROLS, 331 | CSIDL_PRINTERS, 332 | CSIDL_PERSONAL, 333 | CSIDL_FAVORITES, 334 | CSIDL_STARTUP, 335 | CSIDL_RECENT, 336 | CSIDL_SENDTO, 337 | CSIDL_BITBUCKET, 338 | CSIDL_STARTMENU, // = 11 339 | CSIDL_MYMUSIC = 13, 340 | CSIDL_MYVIDEO, // = 14 341 | CSIDL_DESKTOPDIRECTORY = 16, 342 | CSIDL_DRIVES, 343 | CSIDL_NETWORK, 344 | CSIDL_NETHOOD, 345 | CSIDL_FONTS, 346 | CSIDL_TEMPLATES, 347 | CSIDL_COMMON_STARTMENU, 348 | CSIDL_COMMON_PROGRAMS, 349 | CSIDL_COMMON_STARTUP, 350 | CSIDL_COMMON_DESKTOPDIRECTORY, 351 | CSIDL_APPDATA, 352 | CSIDL_PRINTHOOD, 353 | CSIDL_LOCAL_APPDATA, 354 | CSIDL_ALTSTARTUP, 355 | CSIDL_COMMON_ALTSTARTUP, 356 | CSIDL_COMMON_FAVORITES, 357 | CSIDL_INTERNET_CACHE, 358 | CSIDL_COOKIES, 359 | CSIDL_HISTORY, 360 | CSIDL_COMMON_APPDATA, 361 | CSIDL_WINDOWS, 362 | CSIDL_SYSTEM, 363 | CSIDL_PROGRAM_FILES, 364 | CSIDL_MYPICTURES, 365 | CSIDL_PROFILE, 366 | CSIDL_SYSTEMX86, 367 | CSIDL_PROGRAM_FILESX86, 368 | CSIDL_PROGRAM_FILES_COMMON, 369 | CSIDL_PROGRAM_FILES_COMMONX86, 370 | CSIDL_COMMON_TEMPLATES, 371 | CSIDL_COMMON_DOCUMENTS, 372 | CSIDL_COMMON_ADMINTOOLS, 373 | CSIDL_ADMINTOOLS, 374 | CSIDL_CONNECTIONS, // = 49 375 | CSIDL_COMMON_MUSIC = 53, 376 | CSIDL_COMMON_PICTURES, 377 | CSIDL_COMMON_VIDEO, 378 | CSIDL_RESOURCES, 379 | CSIDL_RESOURCES_LOCALIZED, 380 | CSIDL_COMMON_OEM_LINKS, 381 | CSIDL_CDBURN_AREA, // = 59 382 | CSIDL_COMPUTERSNEARME = 61, 383 | CSIDL_FLAG_DONT_VERIFY = 0x4000, 384 | CSIDL_FLAG_CREATE = 0x8000, 385 | CSIDL_FLAG_MASK = 0xFF00 386 | } 387 | 388 | enum { 389 | KF_FLAG_SIMPLE_IDLIST = 0x00000100, 390 | KF_FLAG_NOT_PARENT_RELATIVE = 0x00000200, 391 | KF_FLAG_DEFAULT_PATH = 0x00000400, 392 | KF_FLAG_INIT = 0x00000800, 393 | KF_FLAG_NO_ALIAS = 0x00001000, 394 | KF_FLAG_DONT_UNEXPAND = 0x00002000, 395 | KF_FLAG_DONT_VERIFY = 0x00004000, 396 | KF_FLAG_CREATE = 0x00008000, 397 | KF_FLAG_NO_APPCONTAINER_REDIRECTION = 0x00010000, 398 | KF_FLAG_ALIAS_ONLY = 0x80000000 399 | }; 400 | 401 | alias GUID KNOWNFOLDERID; 402 | 403 | enum KNOWNFOLDERID FOLDERID_LocalAppData = {0xf1b32785, 0x6fba, 0x4fcf, [0x9d,0x55,0x7b,0x8e,0x7f,0x15,0x70,0x91]}; 404 | enum KNOWNFOLDERID FOLDERID_RoamingAppData = {0x3eb685db, 0x65f9, 0x4cf6, [0xa0,0x3a,0xe3,0xef,0x65,0x72,0x9f,0x3d]}; 405 | 406 | enum KNOWNFOLDERID FOLDERID_Desktop = {0xb4bfcc3a, 0xdb2c, 0x424c, [0xb0,0x29,0x7f,0xe9,0x9a,0x87,0xc6,0x41]}; 407 | enum KNOWNFOLDERID FOLDERID_Documents = {0xfdd39ad0, 0x238f, 0x46af, [0xad,0xb4,0x6c,0x85,0x48,0x3,0x69,0xc7]}; 408 | enum KNOWNFOLDERID FOLDERID_Downloads = {0x374de290, 0x123f, 0x4565, [0x91,0x64,0x39,0xc4,0x92,0x5e,0x46,0x7b]}; 409 | enum KNOWNFOLDERID FOLDERID_Favorites = {0x1777f761, 0x68ad, 0x4d8a, [0x87,0xbd,0x30,0xb7,0x59,0xfa,0x33,0xdd]}; 410 | enum KNOWNFOLDERID FOLDERID_Links = {0xbfb9d5e0, 0xc6a9, 0x404c, [0xb2,0xb2,0xae,0x6d,0xb6,0xaf,0x49,0x68]}; 411 | enum KNOWNFOLDERID FOLDERID_Music = {0x4bd8d571, 0x6d19, 0x48d3, [0xbe,0x97,0x42,0x22,0x20,0x8,0xe,0x43]}; 412 | enum KNOWNFOLDERID FOLDERID_Pictures = {0x33e28130, 0x4e1e, 0x4676, [0x83,0x5a,0x98,0x39,0x5c,0x3b,0xc3,0xbb]}; 413 | enum KNOWNFOLDERID FOLDERID_Programs = {0xa77f5d77, 0x2e2b, 0x44c3, [0xa6,0xa2,0xab,0xa6,0x1,0x5,0x4a,0x51]}; 414 | enum KNOWNFOLDERID FOLDERID_SavedGames = {0x4c5c32ff, 0xbb9d, 0x43b0, [0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]}; 415 | enum KNOWNFOLDERID FOLDERID_Startup = {0xb97d20bb, 0xf46a, 0x4c97, [0xba,0x10,0x5e,0x36,0x8,0x43,0x8,0x54]}; 416 | enum KNOWNFOLDERID FOLDERID_Templates = {0xa63293e8, 0x664e, 0x48db, [0xa0,0x79,0xdf,0x75,0x9e,0x5,0x9,0xf7]}; 417 | enum KNOWNFOLDERID FOLDERID_Videos = {0x18989b1d, 0x99b5, 0x455b, [0x84,0x1c,0xab,0x7c,0x74,0xe4,0xdd,0xfc]}; 418 | 419 | enum KNOWNFOLDERID FOLDERID_Fonts = {0xfd228cb7, 0xae11, 0x4ae3, [0x86,0x4c,0x16,0xf3,0x91,0xa,0xb8,0xfe]}; 420 | enum KNOWNFOLDERID FOLDERID_ProgramData = {0x62ab5d82, 0xfdc1, 0x4dc3, [0xa9,0xdd,0x7,0xd,0x1d,0x49,0x5d,0x97]}; 421 | enum KNOWNFOLDERID FOLDERID_CommonPrograms = {0x139d44e, 0x6afe, 0x49f2, [0x86,0x90,0x3d,0xaf,0xca,0xe6,0xff,0xb8]}; 422 | enum KNOWNFOLDERID FOLDERID_CommonStartup = {0x82a5ea35, 0xd9cd, 0x47c5, [0x96,0x29,0xe1,0x5d,0x2f,0x71,0x4e,0x6e]}; 423 | enum KNOWNFOLDERID FOLDERID_CommonTemplates = {0xb94237e7, 0x57ac, 0x4347, [0x91,0x51,0xb0,0x8c,0x6c,0x32,0xd1,0xf7]}; 424 | 425 | enum KNOWNFOLDERID FOLDERID_PublicDesktop = {0xc4aa340d, 0xf20f, 0x4863, [0xaf,0xef,0xf8,0x7e,0xf2,0xe6,0xba,0x25]}; 426 | enum KNOWNFOLDERID FOLDERID_PublicDocuments = {0xed4824af, 0xdce4, 0x45a8, [0x81,0xe2,0xfc,0x79,0x65,0x8,0x36,0x34]}; 427 | enum KNOWNFOLDERID FOLDERID_PublicDownloads = {0x3d644c9b, 0x1fb8, 0x4f30, [0x9b,0x45,0xf6,0x70,0x23,0x5f,0x79,0xc0]}; 428 | enum KNOWNFOLDERID FOLDERID_PublicMusic = {0x3214fab5, 0x9757, 0x4298, [0xbb,0x61,0x92,0xa9,0xde,0xaa,0x44,0xff]}; 429 | enum KNOWNFOLDERID FOLDERID_PublicPictures = {0xb6ebfb86, 0x6907, 0x413c, [0x9a,0xf7,0x4f,0xc2,0xab,0xf0,0x7c,0xc5]}; 430 | enum KNOWNFOLDERID FOLDERID_PublicVideos = {0x2400183a, 0x6185, 0x49fb, [0xa2,0xd8,0x4a,0x39,0x2a,0x60,0x2b,0xa3]}; 431 | } 432 | 433 | private { 434 | extern(Windows) @nogc @system BOOL _dummy_SHGetSpecialFolderPath(HWND, wchar*, int, BOOL) nothrow { return 0; } 435 | extern(Windows) @nogc @system HRESULT _dummy_SHGetKnownFolderPath(const(KNOWNFOLDERID)* rfid, DWORD dwFlags, HANDLE hToken, wchar** ppszPath) nothrow { return 0; } 436 | extern(Windows) @nogc @system void _dummy_CoTaskMemFree(void* pv) nothrow {return;} 437 | 438 | __gshared typeof(&_dummy_SHGetSpecialFolderPath) ptrSHGetSpecialFolderPath = null; 439 | __gshared typeof(&_dummy_SHGetKnownFolderPath) ptrSHGetKnownFolderPath = null; 440 | __gshared typeof(&_dummy_CoTaskMemFree) ptrCoTaskMemFree = null; 441 | 442 | @nogc @trusted bool hasSHGetSpecialFolderPath() nothrow { 443 | return ptrSHGetSpecialFolderPath !is null; 444 | } 445 | 446 | @nogc @trusted bool hasSHGetKnownFolderPath() nothrow { 447 | return ptrSHGetKnownFolderPath !is null && ptrCoTaskMemFree !is null; 448 | } 449 | } 450 | 451 | shared static this() 452 | { 453 | HMODULE shellLib = LoadLibraryA("Shell32"); 454 | if (shellLib !is null) { 455 | ptrSHGetKnownFolderPath = cast(typeof(ptrSHGetKnownFolderPath))enforce(GetProcAddress(shellLib, "SHGetKnownFolderPath")); 456 | if (ptrSHGetKnownFolderPath) { 457 | HMODULE ole = LoadLibraryA("Ole32"); 458 | if (ole !is null) { 459 | ptrCoTaskMemFree = cast(typeof(ptrCoTaskMemFree))enforce(GetProcAddress(ole, "CoTaskMemFree")); 460 | if (!ptrCoTaskMemFree) { 461 | FreeLibrary(ole); 462 | } 463 | } 464 | } 465 | 466 | if (!hasSHGetKnownFolderPath()) { 467 | ptrSHGetSpecialFolderPath = cast(typeof(ptrSHGetSpecialFolderPath))GetProcAddress(shellLib, "SHGetSpecialFolderPathW"); 468 | } 469 | } 470 | } 471 | 472 | private string getCSIDLFolder(int csidl, FolderFlag params = FolderFlag.none) nothrow @trusted { 473 | import core.stdc.wchar_ : wcslen; 474 | 475 | if (params & FolderFlag.create) { 476 | csidl |= CSIDL_FLAG_CREATE; 477 | } 478 | if (!(params & FolderFlag.verify)) { 479 | csidl |= CSIDL_FLAG_DONT_VERIFY; 480 | } 481 | wchar[MAX_PATH] path = void; 482 | if (hasSHGetSpecialFolderPath() && ptrSHGetSpecialFolderPath(null, path.ptr, csidl, FALSE)) { 483 | size_t len = wcslen(path.ptr); 484 | try { 485 | return toUTF8(path[0..len]); 486 | } catch(Exception e) { 487 | 488 | } 489 | } 490 | return null; 491 | } 492 | 493 | private string getKnownFolder(const(KNOWNFOLDERID) folder, FolderFlag params = FolderFlag.none) nothrow @trusted { 494 | import core.stdc.wchar_ : wcslen; 495 | 496 | wchar* str; 497 | 498 | DWORD flags = 0; 499 | if (params & FolderFlag.create) { 500 | flags |= KF_FLAG_CREATE; 501 | } 502 | if (!(params & FolderFlag.verify)) { 503 | flags |= KF_FLAG_DONT_VERIFY; 504 | } 505 | 506 | if (hasSHGetKnownFolderPath() && ptrSHGetKnownFolderPath(&folder, flags, null, &str) == S_OK) { 507 | scope(exit) ptrCoTaskMemFree(str); 508 | try { 509 | return str[0..wcslen(str)].toUTF8; 510 | } catch(Exception e) { 511 | 512 | } 513 | } 514 | return null; 515 | } 516 | 517 | deprecated("use writablePath(StandardPath.roaming)") string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe 518 | { 519 | return writablePath(StandardPath.roaming, params); 520 | } 521 | 522 | deprecated("use writablePath(StandardPath.savedGames)") string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe 523 | { 524 | return writablePath(StandardPath.savedGames, params); 525 | } 526 | 527 | string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 528 | { 529 | if (hasSHGetKnownFolderPath()) { 530 | final switch(type) { 531 | case StandardPath.config: 532 | case StandardPath.data: 533 | return getKnownFolder(FOLDERID_LocalAppData, params); 534 | case StandardPath.cache: 535 | return null; 536 | case StandardPath.desktop: 537 | return getKnownFolder(FOLDERID_Desktop, params); 538 | case StandardPath.documents: 539 | return getKnownFolder(FOLDERID_Documents, params); 540 | case StandardPath.pictures: 541 | return getKnownFolder(FOLDERID_Pictures, params); 542 | case StandardPath.music: 543 | return getKnownFolder(FOLDERID_Music, params); 544 | case StandardPath.videos: 545 | return getKnownFolder(FOLDERID_Videos, params); 546 | case StandardPath.downloads: 547 | return getKnownFolder(FOLDERID_Downloads, params); 548 | case StandardPath.templates: 549 | return getKnownFolder(FOLDERID_Templates, params); 550 | case StandardPath.publicShare: 551 | return null; 552 | case StandardPath.fonts: 553 | return null; 554 | case StandardPath.applications: 555 | return getKnownFolder(FOLDERID_Programs, params); 556 | case StandardPath.startup: 557 | return getKnownFolder(FOLDERID_Startup, params); 558 | case StandardPath.roaming: 559 | return getKnownFolder(FOLDERID_RoamingAppData, params); 560 | case StandardPath.savedGames: 561 | return getKnownFolder(FOLDERID_SavedGames, params); 562 | } 563 | } else if (hasSHGetSpecialFolderPath()) { 564 | final switch(type) { 565 | case StandardPath.config: 566 | case StandardPath.data: 567 | return getCSIDLFolder(CSIDL_LOCAL_APPDATA, params); 568 | case StandardPath.cache: 569 | return null; 570 | case StandardPath.desktop: 571 | return getCSIDLFolder(CSIDL_DESKTOPDIRECTORY, params); 572 | case StandardPath.documents: 573 | return getCSIDLFolder(CSIDL_PERSONAL, params); 574 | case StandardPath.pictures: 575 | return getCSIDLFolder(CSIDL_MYPICTURES, params); 576 | case StandardPath.music: 577 | return getCSIDLFolder(CSIDL_MYMUSIC, params); 578 | case StandardPath.videos: 579 | return getCSIDLFolder(CSIDL_MYVIDEO, params); 580 | case StandardPath.downloads: 581 | return null; 582 | case StandardPath.templates: 583 | return getCSIDLFolder(CSIDL_TEMPLATES, params); 584 | case StandardPath.publicShare: 585 | return null; 586 | case StandardPath.fonts: 587 | return null; 588 | case StandardPath.applications: 589 | return getCSIDLFolder(CSIDL_PROGRAMS, params); 590 | case StandardPath.startup: 591 | return getCSIDLFolder(CSIDL_STARTUP, params); 592 | case StandardPath.roaming: 593 | return getCSIDLFolder(CSIDL_APPDATA, params); 594 | case StandardPath.savedGames: 595 | return null; 596 | } 597 | } else { 598 | return null; 599 | } 600 | } 601 | 602 | string[] standardPaths(StandardPath type) nothrow @safe 603 | { 604 | string commonPath; 605 | 606 | if (hasSHGetKnownFolderPath()) { 607 | switch(type) { 608 | case StandardPath.config: 609 | case StandardPath.data: 610 | commonPath = getKnownFolder(FOLDERID_ProgramData); 611 | break; 612 | case StandardPath.desktop: 613 | commonPath = getKnownFolder(FOLDERID_PublicDesktop); 614 | break; 615 | case StandardPath.documents: 616 | commonPath = getKnownFolder(FOLDERID_PublicDocuments); 617 | break; 618 | case StandardPath.pictures: 619 | commonPath = getKnownFolder(FOLDERID_PublicPictures); 620 | break; 621 | case StandardPath.music: 622 | commonPath = getKnownFolder(FOLDERID_PublicMusic); 623 | break; 624 | case StandardPath.videos: 625 | commonPath = getKnownFolder(FOLDERID_PublicVideos); 626 | break; 627 | case StandardPath.downloads: 628 | commonPath = getKnownFolder(FOLDERID_PublicDownloads); 629 | break; 630 | case StandardPath.templates: 631 | commonPath = getKnownFolder(FOLDERID_CommonTemplates); 632 | break; 633 | case StandardPath.fonts: 634 | commonPath = getKnownFolder(FOLDERID_Fonts); 635 | break; 636 | case StandardPath.applications: 637 | commonPath = getKnownFolder(FOLDERID_CommonPrograms); 638 | break; 639 | case StandardPath.startup: 640 | commonPath = getKnownFolder(FOLDERID_CommonStartup); 641 | break; 642 | default: 643 | break; 644 | } 645 | } else if (hasSHGetSpecialFolderPath()) { 646 | switch(type) { 647 | case StandardPath.config: 648 | case StandardPath.data: 649 | commonPath = getCSIDLFolder(CSIDL_COMMON_APPDATA); 650 | break; 651 | case StandardPath.desktop: 652 | commonPath = getCSIDLFolder(CSIDL_COMMON_DESKTOPDIRECTORY); 653 | break; 654 | case StandardPath.documents: 655 | commonPath = getCSIDLFolder(CSIDL_COMMON_DOCUMENTS); 656 | break; 657 | case StandardPath.pictures: 658 | commonPath = getCSIDLFolder(CSIDL_COMMON_PICTURES); 659 | break; 660 | case StandardPath.music: 661 | commonPath = getCSIDLFolder(CSIDL_COMMON_MUSIC); 662 | break; 663 | case StandardPath.videos: 664 | commonPath = getCSIDLFolder(CSIDL_COMMON_VIDEO); 665 | break; 666 | case StandardPath.templates: 667 | commonPath = getCSIDLFolder(CSIDL_COMMON_TEMPLATES); 668 | break; 669 | case StandardPath.fonts: 670 | commonPath = getCSIDLFolder(CSIDL_FONTS); 671 | break; 672 | case StandardPath.applications: 673 | commonPath = getCSIDLFolder(CSIDL_COMMON_PROGRAMS); 674 | break; 675 | case StandardPath.startup: 676 | commonPath = getCSIDLFolder(CSIDL_COMMON_STARTUP); 677 | break; 678 | default: 679 | break; 680 | } 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 | } else version(OSX) { 692 | private { 693 | import std.string : fromStringz; 694 | version(StandardPathsCocoa) { 695 | import core.attribute : selector; 696 | 697 | alias size_t NSUInteger; 698 | 699 | enum objectiveC_declarations = q{ 700 | extern (Objective-C) 701 | interface NSString 702 | { 703 | NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:"); 704 | const(char)* UTF8String() @selector("UTF8String"); 705 | void release() @selector("release"); 706 | } 707 | 708 | extern(Objective-C) 709 | interface NSArray 710 | { 711 | NSString objectAtIndex(size_t) @selector("objectAtIndex:"); 712 | NSString firstObject() @selector("firstObject"); 713 | NSUInteger count() @selector("count"); 714 | void release() @selector("release"); 715 | } 716 | 717 | extern(Objective-C) 718 | interface NSURL 719 | { 720 | NSString absoluteString() @selector("absoluteString"); 721 | void release() @selector("release"); 722 | } 723 | 724 | extern(Objective-C) 725 | interface NSError 726 | { 727 | 728 | } 729 | 730 | extern (C) NSFileManager objc_lookUpClass(in char* name); 731 | 732 | extern(Objective-C) 733 | interface NSFileManager 734 | { 735 | NSFileManager defaultManager() @selector("defaultManager"); 736 | NSURL URLForDirectory(NSSearchPathDirectory, NSSearchPathDomainMask domain, NSURL url, int shouldCreate, NSError* error) @selector("URLForDirectory:inDomain:appropriateForURL:create:error:"); 737 | } 738 | }; 739 | 740 | mixin(objectiveC_declarations); 741 | 742 | enum : NSUInteger { 743 | NSApplicationDirectory = 1, 744 | NSDemoApplicationDirectory, 745 | NSDeveloperApplicationDirectory, 746 | NSAdminApplicationDirectory, 747 | NSLibraryDirectory, 748 | NSDeveloperDirectory, 749 | NSUserDirectory, 750 | NSDocumentationDirectory, 751 | NSDocumentDirectory, 752 | NSCoreServiceDirectory, 753 | NSAutosavedInformationDirectory = 11, 754 | NSDesktopDirectory = 12, 755 | NSCachesDirectory = 13, 756 | NSApplicationSupportDirectory = 14, 757 | NSDownloadsDirectory = 15, 758 | NSInputMethodsDirectory = 16, 759 | NSMoviesDirectory = 17, 760 | NSMusicDirectory = 18, 761 | NSPicturesDirectory = 19, 762 | NSPrinterDescriptionDirectory = 20, 763 | NSSharedPublicDirectory = 21, 764 | NSPreferencePanesDirectory = 22, 765 | NSItemReplacementDirectory = 99, 766 | NSAllApplicationsDirectory = 100, 767 | NSAllLibrariesDirectory = 101, 768 | }; 769 | 770 | alias NSUInteger NSSearchPathDirectory; 771 | 772 | enum : NSUInteger { 773 | NSUserDomainMask = 1, 774 | NSLocalDomainMask = 2, 775 | NSNetworkDomainMask = 4, 776 | NSSystemDomainMask = 8, 777 | NSAllDomainsMask = 0x0ffff, 778 | }; 779 | 780 | alias NSUInteger NSSearchPathDomainMask; 781 | 782 | string domainDir(NSSearchPathDirectory dir, NSSearchPathDomainMask domain, bool shouldCreate = false) nothrow @trusted 783 | { 784 | import std.uri; 785 | import std.algorithm : startsWith; 786 | 787 | try { 788 | auto managerInterface = objc_lookUpClass("NSFileManager"); 789 | if (!managerInterface) { 790 | return null; 791 | } 792 | 793 | auto manager = managerInterface.defaultManager(); 794 | if (!manager) { 795 | return null; 796 | } 797 | 798 | NSURL url = manager.URLForDirectory(dir, domain, null, shouldCreate, null); 799 | if (!url) { 800 | return null; 801 | } 802 | scope(exit) url.release(); 803 | NSString nsstr = url.absoluteString(); 804 | scope(exit) nsstr.release(); 805 | 806 | string str = fromStringz(nsstr.UTF8String()).idup; 807 | 808 | enum fileProtocol = "file://"; 809 | if (str.startsWith(fileProtocol)) { 810 | str = str.decode()[fileProtocol.length..$]; 811 | if (str.length > 1 && str[$-1] == '/') { 812 | return str[0..$-1]; 813 | } else { 814 | return str; 815 | } 816 | } 817 | } catch(Exception e) { 818 | 819 | } 820 | return null; 821 | } 822 | } else { 823 | private enum : short { 824 | kOnSystemDisk = -32768L, /* previously was 0x8000 but that is an unsigned value whereas vRefNum is signed*/ 825 | kOnAppropriateDisk = -32767, /* Generally, the same as kOnSystemDisk, but it's clearer that this isn't always the 'boot' disk.*/ 826 | /* Folder Domains - Carbon only. The constants above can continue to be used, but the folder/volume returned will*/ 827 | /* be from one of the domains below.*/ 828 | kSystemDomain = -32766, /* Read-only system hierarchy.*/ 829 | kLocalDomain = -32765, /* All users of a single machine have access to these resources.*/ 830 | kNetworkDomain = -32764, /* All users configured to use a common network server has access to these resources.*/ 831 | kUserDomain = -32763, /* Read/write. Resources that are private to the user.*/ 832 | kClassicDomain = -32762, /* Domain referring to the currently configured Classic System Folder. Not supported in Mac OS X Leopard and later.*/ 833 | kFolderManagerLastDomain = -32760 834 | } 835 | 836 | private @nogc int k(string s) nothrow { 837 | return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]; 838 | } 839 | 840 | private enum { 841 | kDesktopFolderType = k("desk"), /* the desktop folder; objects in this folder show on the desktop. */ 842 | kTrashFolderType = k("trsh"), /* the trash folder; objects in this folder show up in the trash */ 843 | kWhereToEmptyTrashFolderType = k("empt"), /* the "empty trash" folder; Finder starts empty from here down */ 844 | kFontsFolderType = k("font"), /* Fonts go here */ 845 | kPreferencesFolderType = k("pref"), /* preferences for applications go here */ 846 | kSystemPreferencesFolderType = k("sprf"), /* the PreferencePanes folder, where Mac OS X Preference Panes go */ 847 | 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*/ 848 | /* folders such that other users can not access the data inside. On Mac OS X 10.4 and later the data inside the temporary*/ 849 | /* items folder is deleted at logout and at boot, but not otherwise. Earlier version of Mac OS X would delete items inside*/ 850 | /* the temporary items folder after a period of inaccess. You can ask for a temporary item in a specific domain or on a */ 851 | /* particular volume by FSVolumeRefNum. If you want a location for temporary items for a short time, then use either*/ 852 | /* ( kUserDomain, kkTemporaryFolderType ) or ( kSystemDomain, kTemporaryFolderType ). The kUserDomain varient will always be*/ 853 | /* on the same volume as the user's home folder, while the kSystemDomain version will be on the same volume as /var/tmp/ ( and*/ 854 | /* 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*/ 855 | /* file or folder to use for saving a document, especially if you want to use FSpExchangeFile() to implement a safe-save, then*/ 856 | /* ask for the temporary items folder on the same volume as the file you are safe saving.*/ 857 | /* However, be prepared for a failure to find a temporary folder in any domain or on any volume. Some volumes may not have*/ 858 | /* a location for a temporary folder, or the permissions of the volume may be such that the Folder Manager can not return*/ 859 | /* a temporary folder for the volume.*/ 860 | /* If your application creates an item in a temporary items older you should delete that item as soon as it is not needed,*/ 861 | /* and certainly before your application exits, since otherwise the item is consuming disk space until the user logs out or*/ 862 | /* restarts. Any items left inside a temporary items folder should be moved into a folder inside the Trash folder on the disk*/ 863 | /* when the user logs in, inside a folder named "Recovered items", in case there is anything useful to the end user.*/ 864 | kChewableItemsFolderType = k("flnt"), /* similar to kTemporaryItemsFolderType, except items in this folder are deleted at boot or when the disk is unmounted */ 865 | kTemporaryItemsInCacheDataFolderType = k("vtmp"), /* A folder inside the kCachedDataFolderType for the given domain which can be used for transient data*/ 866 | kApplicationsFolderType = k("apps"), /* Applications on Mac OS X are typically put in this folder ( or a subfolder ).*/ 867 | kVolumeRootFolderType = k("root"), /* root folder of a volume or domain */ 868 | kDomainTopLevelFolderType = k("dtop"), /* The top-level of a Folder domain, e.g. "/System"*/ 869 | kDomainLibraryFolderType = k("dlib"), /* the Library subfolder of a particular domain*/ 870 | kUsersFolderType = k("usrs"), /* "Users" folder, usually contains one folder for each user. */ 871 | kCurrentUserFolderType = k("cusr"), /* The folder for the currently logged on user; domain passed in is ignored. */ 872 | kSharedUserDataFolderType = k("sdat"), /* A Shared folder, readable & writeable by all users */ 873 | kCachedDataFolderType = k("cach"), /* Contains various cache files for different clients*/ 874 | kDownloadsFolderType = k("down"), /* Refers to the ~/Downloads folder*/ 875 | kApplicationSupportFolderType = k("asup"), /* third-party items and folders */ 876 | 877 | 878 | kDocumentsFolderType = k("docs"), /* User documents are typically put in this folder ( or a subfolder ).*/ 879 | kPictureDocumentsFolderType = k("pdoc"), /* Refers to the "Pictures" folder in a users home directory*/ 880 | kMovieDocumentsFolderType = k("mdoc"), /* Refers to the "Movies" folder in a users home directory*/ 881 | kMusicDocumentsFolderType = 0xB5646F63/*'µdoc'*/, /* Refers to the "Music" folder in a users home directory*/ 882 | kInternetSitesFolderType = k("site"), /* Refers to the "Sites" folder in a users home directory*/ 883 | kPublicFolderType = k("pubb"), /* Refers to the "Public" folder in a users home directory*/ 884 | 885 | kDropBoxFolderType = k("drop") /* Refers to the "Drop Box" folder inside the user's home directory*/ 886 | }; 887 | 888 | struct FSRef { 889 | char[80] hidden; /* private to File Manager*/ 890 | }; 891 | 892 | alias ubyte Boolean; 893 | alias int OSType; 894 | alias short OSErr; 895 | alias int OSStatus; 896 | 897 | extern(C) @nogc @system OSErr _dummy_FSFindFolder(short, OSType, Boolean, FSRef*) nothrow { return 0; } 898 | extern(C) @nogc @system OSStatus _dummy_FSRefMakePath(const(FSRef)*, char*, uint) nothrow { return 0; } 899 | 900 | __gshared typeof(&_dummy_FSFindFolder) ptrFSFindFolder = null; 901 | __gshared typeof(&_dummy_FSRefMakePath) ptrFSRefMakePath = null; 902 | } 903 | } 904 | 905 | version(StandardPathsCocoa) { 906 | 907 | } else { 908 | shared static this() 909 | { 910 | enum carbonPath = "CoreServices.framework/Versions/A/CoreServices\0"; 911 | 912 | // This one still work as of macOS 15.1, it was used in Dplug for a good while 913 | enum carbonPath2 = "/System/Library/Frameworks/CoreServices.framework/CoreServices\0"; 914 | 915 | import core.sys.posix.dlfcn; 916 | 917 | void* handle = dlopen(carbonPath.ptr, RTLD_NOW | RTLD_LOCAL); 918 | if (!handle) 919 | handle = dlopen(carbonPath2.ptr, RTLD_NOW | RTLD_LOCAL); 920 | 921 | if (handle) { 922 | ptrFSFindFolder = cast(typeof(ptrFSFindFolder))dlsym(handle, "FSFindFolder"); 923 | ptrFSRefMakePath = cast(typeof(ptrFSRefMakePath))dlsym(handle, "FSRefMakePath"); 924 | } 925 | if (ptrFSFindFolder == null || ptrFSRefMakePath == null) { 926 | debug collectException(stderr.writeln("Could not load carbon functions")); 927 | if (handle) dlclose(handle); 928 | } 929 | } 930 | 931 | private @nogc @trusted bool isCarbonLoaded() nothrow 932 | { 933 | return ptrFSFindFolder != null && ptrFSRefMakePath != null; 934 | } 935 | 936 | private enum OSErr noErr = 0; 937 | 938 | private string fsPath(short domain, OSType type, bool shouldCreate = false) nothrow @trusted 939 | { 940 | import std.stdio; 941 | FSRef fsref; 942 | if (isCarbonLoaded() && ptrFSFindFolder(domain, type, shouldCreate, &fsref) == noErr) { 943 | 944 | char[2048] buf; 945 | char* path = buf.ptr; 946 | if (ptrFSRefMakePath(&fsref, path, buf.sizeof) == noErr) { 947 | try { 948 | return fromStringz(path).idup; 949 | } 950 | catch(Exception e) { 951 | 952 | } 953 | } 954 | } 955 | return null; 956 | } 957 | } 958 | 959 | private string writablePathImpl(StandardPath type, bool shouldCreate = false) nothrow @safe 960 | { 961 | version(StandardPathsCocoa) { 962 | final switch(type) { 963 | case StandardPath.config: 964 | return domainDir(NSLibraryDirectory, NSUserDomainMask, shouldCreate).maybeBuild("Preferences").createIfNeeded(shouldCreate); 965 | case StandardPath.cache: 966 | return domainDir(NSCachesDirectory, NSUserDomainMask, shouldCreate); 967 | case StandardPath.data: 968 | return domainDir(NSApplicationSupportDirectory, NSUserDomainMask, shouldCreate); 969 | case StandardPath.desktop: 970 | return domainDir(NSDesktopDirectory, NSUserDomainMask, shouldCreate); 971 | case StandardPath.documents: 972 | return domainDir(NSDocumentDirectory, NSUserDomainMask, shouldCreate); 973 | case StandardPath.pictures: 974 | return domainDir(NSPicturesDirectory, NSUserDomainMask, shouldCreate); 975 | case StandardPath.music: 976 | return domainDir(NSMusicDirectory, NSUserDomainMask, shouldCreate); 977 | case StandardPath.videos: 978 | return domainDir(NSMoviesDirectory, NSUserDomainMask, shouldCreate); 979 | case StandardPath.downloads: 980 | return domainDir(NSDownloadsDirectory, NSUserDomainMask, shouldCreate); 981 | case StandardPath.templates: 982 | return null; 983 | case StandardPath.publicShare: 984 | return domainDir(NSSharedPublicDirectory, NSUserDomainMask, shouldCreate); 985 | case StandardPath.fonts: 986 | return domainDir(NSLibraryDirectory, NSUserDomainMask, shouldCreate).maybeBuild("Fonts").createIfNeeded(shouldCreate); 987 | case StandardPath.applications: 988 | return domainDir(NSApplicationDirectory, NSUserDomainMask, shouldCreate); 989 | case StandardPath.startup: 990 | return null; 991 | case StandardPath.roaming: 992 | return null; 993 | case StandardPath.savedGames: 994 | return null; 995 | } 996 | } else { 997 | final switch(type) { 998 | case StandardPath.config: 999 | return fsPath(kUserDomain, kPreferencesFolderType, shouldCreate); 1000 | case StandardPath.cache: 1001 | return fsPath(kUserDomain, kCachedDataFolderType, shouldCreate); 1002 | case StandardPath.data: 1003 | return fsPath(kUserDomain, kApplicationSupportFolderType, shouldCreate); 1004 | case StandardPath.desktop: 1005 | return fsPath(kUserDomain, kDesktopFolderType, shouldCreate); 1006 | case StandardPath.documents: 1007 | return fsPath(kUserDomain, kDocumentsFolderType, shouldCreate); 1008 | case StandardPath.pictures: 1009 | return fsPath(kUserDomain, kPictureDocumentsFolderType, shouldCreate); 1010 | case StandardPath.music: 1011 | return fsPath(kUserDomain, kMusicDocumentsFolderType, shouldCreate); 1012 | case StandardPath.videos: 1013 | return fsPath(kUserDomain, kMovieDocumentsFolderType, shouldCreate); 1014 | case StandardPath.downloads: 1015 | return fsPath(kUserDomain, kDownloadsFolderType, shouldCreate); 1016 | case StandardPath.templates: 1017 | return null; 1018 | case StandardPath.publicShare: 1019 | return fsPath(kUserDomain, kPublicFolderType, shouldCreate); 1020 | case StandardPath.fonts: 1021 | return fsPath(kUserDomain, kFontsFolderType, shouldCreate); 1022 | case StandardPath.applications: 1023 | return fsPath(kUserDomain, kApplicationsFolderType, shouldCreate); 1024 | case StandardPath.startup: 1025 | return null; 1026 | case StandardPath.roaming: 1027 | return null; 1028 | case StandardPath.savedGames: 1029 | return null; 1030 | } 1031 | } 1032 | } 1033 | 1034 | string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 1035 | { 1036 | const bool shouldCreate = (params & FolderFlag.create) != 0; 1037 | const bool shouldVerify = (params & FolderFlag.verify) != 0; 1038 | return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify); 1039 | } 1040 | 1041 | string[] standardPaths(StandardPath type) nothrow @safe 1042 | { 1043 | string commonPath; 1044 | 1045 | version(StandardPathsCocoa) { 1046 | switch(type) { 1047 | case StandardPath.fonts: 1048 | commonPath = domainDir(NSLibraryDirectory, NSSystemDomainMask).maybeBuild("Fonts"); 1049 | break; 1050 | case StandardPath.applications: 1051 | commonPath = domainDir(NSApplicationDirectory, NSSystemDomainMask); 1052 | break; 1053 | case StandardPath.data: 1054 | commonPath = domainDir(NSApplicationSupportDirectory, NSSystemDomainMask); 1055 | break; 1056 | case StandardPath.cache: 1057 | commonPath = domainDir(NSCachesDirectory, NSSystemDomainMask); 1058 | break; 1059 | default: 1060 | break; 1061 | } 1062 | } else { 1063 | switch(type) { 1064 | case StandardPath.fonts: 1065 | commonPath = fsPath(kOnAppropriateDisk, kFontsFolderType); 1066 | break; 1067 | case StandardPath.applications: 1068 | commonPath = fsPath(kOnAppropriateDisk, kApplicationsFolderType); 1069 | break; 1070 | case StandardPath.data: 1071 | commonPath = fsPath(kOnAppropriateDisk, kApplicationSupportFolderType); 1072 | break; 1073 | case StandardPath.cache: 1074 | commonPath = fsPath(kOnAppropriateDisk, kCachedDataFolderType); 1075 | break; 1076 | default: 1077 | break; 1078 | } 1079 | } 1080 | 1081 | string[] paths; 1082 | string userPath = writablePath(type); 1083 | if (userPath.length) 1084 | paths ~= userPath; 1085 | if (commonPath.length) 1086 | paths ~= commonPath; 1087 | return paths; 1088 | } 1089 | 1090 | } else { 1091 | 1092 | static if (!isFreedesktop) { 1093 | static assert(false, "Unsupported platform"); 1094 | } else { 1095 | public import xdgpaths; 1096 | 1097 | private { 1098 | import std.stdio : File; 1099 | import std.algorithm : startsWith; 1100 | import std.string; 1101 | import std.traits; 1102 | } 1103 | 1104 | unittest 1105 | { 1106 | assert(maybeConcat(null, "path") == string.init); 1107 | assert(maybeConcat("path", "/file") == "path/file"); 1108 | } 1109 | 1110 | private @trusted string getFromUserDirs(Range)(string xdgdir, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range)) 1111 | { 1112 | foreach(line; range) { 1113 | line = strip(line); 1114 | auto index = xdgdir.length; 1115 | if (line.startsWith(xdgdir) && line.length > index && line[index] == '=') { 1116 | line = line[index+1..$]; 1117 | if (line.length > 2 && line[0] == '"' && line[$-1] == '"') 1118 | { 1119 | line = line[1..$-1]; 1120 | 1121 | if (line.startsWith("$HOME")) { 1122 | return maybeConcat(home, assumeUnique(line[5..$])); 1123 | } 1124 | if (line.length == 0 || line[0] != '/') { 1125 | continue; 1126 | } 1127 | return assumeUnique(line); 1128 | } 1129 | } 1130 | } 1131 | return null; 1132 | } 1133 | 1134 | 1135 | unittest 1136 | { 1137 | string content = 1138 | `# Comment 1139 | 1140 | XDG_DOCUMENTS_DIR="$HOME/My Documents" 1141 | XDG_MUSIC_DIR="/data/Music" 1142 | XDG_VIDEOS_DIR="data/Video" 1143 | `; 1144 | string home = "/home/user"; 1145 | 1146 | assert(getFromUserDirs("XDG_DOCUMENTS_DIR", home, content.splitLines) == "/home/user/My Documents"); 1147 | assert(getFromUserDirs("XDG_MUSIC_DIR", home, content.splitLines) == "/data/Music"); 1148 | assert(getFromUserDirs("XDG_DOWNLOAD_DIR", home, content.splitLines).empty); 1149 | assert(getFromUserDirs("XDG_VIDEOS_DIR", home, content.splitLines).empty); 1150 | } 1151 | 1152 | private @trusted string getFromDefaultDirs(Range)(string key, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range)) 1153 | { 1154 | foreach(line; range) { 1155 | line = strip(line); 1156 | auto index = key.length; 1157 | if (line.startsWith(key) && line.length > index && line[index] == '=') 1158 | { 1159 | line = line[index+1..$]; 1160 | return home ~ "/" ~ assumeUnique(line); 1161 | } 1162 | } 1163 | return null; 1164 | } 1165 | 1166 | unittest 1167 | { 1168 | string content = 1169 | `# Comment 1170 | 1171 | DOCUMENTS=MyDocuments 1172 | PICTURES=Images 1173 | `; 1174 | string home = "/home/user"; 1175 | assert(getFromDefaultDirs("DOCUMENTS", home, content.splitLines) == "/home/user/MyDocuments"); 1176 | assert(getFromDefaultDirs("PICTURES", home, content.splitLines) == "/home/user/Images"); 1177 | assert(getFromDefaultDirs("VIDEOS", home, content.splitLines).empty); 1178 | } 1179 | 1180 | private string xdgUserDir(string key, string fallback = null) nothrow @trusted { 1181 | string fileName = maybeConcat(writablePath(StandardPath.config), "/user-dirs.dirs"); 1182 | string home = homeDir(); 1183 | try { 1184 | auto f = File(fileName, "r"); 1185 | auto xdgdir = "XDG_" ~ key ~ "_DIR"; 1186 | auto path = getFromUserDirs(xdgdir, home, f.byLine()); 1187 | if (path.length) { 1188 | return path; 1189 | } 1190 | } catch(Exception e) { 1191 | 1192 | } 1193 | 1194 | if (home.length) { 1195 | try { 1196 | auto f = File("/etc/xdg/user-dirs.defaults", "r"); 1197 | auto path = getFromDefaultDirs(key, home, f.byLine()); 1198 | if (path.length) { 1199 | return path; 1200 | } 1201 | } catch (Exception e) { 1202 | 1203 | } 1204 | if (fallback.length) { 1205 | return home ~ fallback; 1206 | } 1207 | } 1208 | return null; 1209 | } 1210 | 1211 | private string homeFontsPath() nothrow @trusted { 1212 | return maybeConcat(homeDir(), "/.fonts"); 1213 | } 1214 | 1215 | private string[] fontPaths() nothrow @trusted 1216 | { 1217 | enum localShare = "/usr/local/share/fonts"; 1218 | enum share = "/usr/share/fonts"; 1219 | 1220 | string homeFonts = homeFontsPath(); 1221 | if (homeFonts.length) { 1222 | return [homeFonts, localShare, share]; 1223 | } else { 1224 | return [localShare, share]; 1225 | } 1226 | } 1227 | 1228 | private string writablePathImpl(StandardPath type, bool shouldCreate) nothrow @safe 1229 | { 1230 | final switch(type) { 1231 | case StandardPath.config: 1232 | return xdgConfigHome(null, shouldCreate); 1233 | case StandardPath.cache: 1234 | return xdgCacheHome(null, shouldCreate); 1235 | case StandardPath.data: 1236 | return xdgDataHome(null, shouldCreate); 1237 | case StandardPath.desktop: 1238 | return xdgUserDir("DESKTOP", "/Desktop").createIfNeeded(shouldCreate); 1239 | case StandardPath.documents: 1240 | return xdgUserDir("DOCUMENTS").createIfNeeded(shouldCreate); 1241 | case StandardPath.pictures: 1242 | return xdgUserDir("PICTURES").createIfNeeded(shouldCreate); 1243 | case StandardPath.music: 1244 | return xdgUserDir("MUSIC").createIfNeeded(shouldCreate); 1245 | case StandardPath.videos: 1246 | return xdgUserDir("VIDEOS").createIfNeeded(shouldCreate); 1247 | case StandardPath.downloads: 1248 | return xdgUserDir("DOWNLOAD").createIfNeeded(shouldCreate); 1249 | case StandardPath.templates: 1250 | return xdgUserDir("TEMPLATES", "/Templates").createIfNeeded(shouldCreate); 1251 | case StandardPath.publicShare: 1252 | return xdgUserDir("PUBLICSHARE", "/Public").createIfNeeded(shouldCreate); 1253 | case StandardPath.fonts: 1254 | return homeFontsPath().createIfNeeded(shouldCreate); 1255 | case StandardPath.applications: 1256 | return xdgDataHome("applications", shouldCreate); 1257 | case StandardPath.startup: 1258 | return xdgConfigHome("autostart", shouldCreate); 1259 | case StandardPath.roaming: 1260 | return null; 1261 | case StandardPath.savedGames: 1262 | return null; 1263 | } 1264 | } 1265 | 1266 | string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe 1267 | { 1268 | const bool shouldCreate = (params & FolderFlag.create) != 0; 1269 | const bool shouldVerify = (params & FolderFlag.verify) != 0; 1270 | return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify); 1271 | } 1272 | 1273 | string[] standardPaths(StandardPath type) nothrow @safe 1274 | { 1275 | string[] paths; 1276 | 1277 | switch(type) { 1278 | case StandardPath.data: 1279 | return xdgAllDataDirs(); 1280 | case StandardPath.config: 1281 | return xdgAllConfigDirs(); 1282 | case StandardPath.applications: 1283 | return xdgAllDataDirs("applications"); 1284 | case StandardPath.startup: 1285 | return xdgAllConfigDirs("autostart"); 1286 | case StandardPath.fonts: 1287 | return fontPaths(); 1288 | default: 1289 | break; 1290 | } 1291 | 1292 | string userPath = writablePath(type); 1293 | if (userPath.length) { 1294 | return [userPath]; 1295 | } 1296 | return null; 1297 | } 1298 | } 1299 | } 1300 | --------------------------------------------------------------------------------