1 /* SPDX-License-Identifier: MIT License
3 Copyright © 2021 Thomas “illwieckz” Debesse
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the “Software”),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
12 The above copyright notice and this permission notice shall be included
13 in all copies or substantial portions of the Software.
15 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 DEALINGS IN THE SOFTWARE. */
23 /* transformpath: transform file path based on keywords.
25 This is not an environment variable parser, this only supports
26 a set of keywords using common environment name syntax when it
27 exists to make the strings easier to read.
29 Supported substitution keywords,
40 Supported substitution keywords,
41 Linux, FreeBSD, macOS:
46 Supported substitution keywords,
47 Linux, FreeBSD, other XDG systems:
53 game engine directories:
55 - Windows: %ProgramFiles%\Unvanquished
56 - Linux: ${XDG_DATA_HOME}/unvanquished/base
57 - macOS: ${HOME}/Games/Unvanquished
60 game home directories:
62 - Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished
63 - Linux: ${XDG_DATA_HOME}/unvanquished
64 - macOS: ${HOME}/Application Data/Unvanquished
68 #include "globaldefs.h"
78 #pragma comment(lib, "shell32.lib")
79 #endif // !GDEF_OS_WINDOWS
81 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
83 #include <sys/types.h>
85 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
88 static std::string getUserProfilePath();
89 #endif // !GDEF_OS_WINDOWS
91 static std::string getUserName()
94 std::string path( getenv( "USERNAME" ) );
101 globalErrorStream() << "\%USERNAME\% not found.\n";
104 #endif // !GDEF_OS_WINDOWS
106 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
107 std::string path( getenv( "USERNAME" ) );
109 if ( ! path.empty() )
114 globalErrorStream() << "${USERNAME} not found, guessing…\n";
116 path = std::string( getenv( "LOGNAME" ) );
118 if ( ! path.empty() )
123 globalErrorStream() << "${LOGNAME} not found, guessing…\n";
125 path = std::string( getenv( "USER" ) );
127 if ( ! path.empty() )
132 globalErrorStream() << "${USER} not found.\n";
135 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
138 static std::string getHomePath()
141 std::string path( getenv( "HOMEPATH" ) );
143 if ( ! path.empty() )
148 globalErrorStream() << "\%HOMEPATH\% not found, guessing…\n";
150 std::string path1 = getUserProfilePath();
152 if ( ! path1.empty() )
157 globalErrorStream() << "\%HOMEPATH\% not found.\n";
160 #endif // !GDEF_OS_WINDOWS
162 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
163 // Get the path environment variable.
164 std::string path( getenv( "HOME" ) );
166 // Look up path directory in password database.
172 globalErrorStream() << "${HOME} not found, guessing…\n";
174 static char buf[ 4096 ];
175 struct passwd pw, *pwp;
177 if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 )
179 return std::string( pw.pw_dir );
182 globalErrorStream() << "${HOME} not found, guessing…\n";
184 std::string path1( "/home/" );
186 std::string path2 = getUserName();
188 if ( ! path2.empty() )
190 return path1 + path2;
193 globalErrorStream() << "${HOME} not found…\n";
196 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
200 static std::string getSystemDrive()
202 std::string path( getenv( "SYSTEMDRIVE" ) );
204 if ( ! path.empty() )
209 globalErrorStream() << "\%SYSTEMDRIVE\% not found, guessing…\n";
214 static std::string getUserProfilePath()
216 std::string path( getenv( "USERPROFILE" ) );
218 if ( ! path.empty() )
223 globalErrorStream() << "\%USERPROFILE\% not found, guessing…\n";
225 std::string path1 = getSystemDrive();
226 std::string path2 = getUserName();
228 if ( ! path2.empty() )
230 return path1 + "\\Users\\" + path2;
233 globalErrorStream() << "\%USERPROFILE\% not found.\n";
238 static std::string getProgramFilesPath()
240 std::string path( getenv( "ProgramFiles" ) );
242 if ( ! path.empty() )
247 globalErrorStream() << "\%ProgramFiles\% not found, guessing…\n";
249 std::string path1 = getSystemDrive();
250 return path1 + "\\Program Files";
253 static std::string getProgramFilesX86Path()
255 std::string path( getenv( "ProgramFiles(x86)" ) );
257 if ( ! path.empty() )
262 globalErrorStream() << "\%ProgramFiles(x86)\% not found, guessing…\n";
264 return getProgramFilesPath();
267 static std::string getProgramW6432Path()
269 std::string path( getenv( "ProgramW6432" ) );
271 if ( ! path.empty() )
276 globalErrorStream() << "\%ProgramW6432\% not found, guessing…\n";
278 return getProgramFilesPath();
281 static std::string getAppDataPath()
283 std::string path( getenv( "APPDATA" ) );
285 if ( ! path.empty() )
290 globalErrorStream() << "\%APPDATA\% not found, guessing…\n";
292 std::string path1 = getUserProfilePath();
294 if ( ! path1.empty() )
296 return path1 + "\\AppData\\Roaming";
299 globalErrorStream() << "\%APPDATA\% not found.\n";
301 return std::string( "" );
304 /* TODO: see also qFOLDERID_SavedGames in mainframe.cpp,
305 HomePaths_Realise and other things like that,
306 they look to be game paths, not NetRadiant paths. */
308 static std::string getMyDocumentsPath()
310 CHAR path[ MAX_PATH ];
311 HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
313 if ( result == S_OK )
315 return std::string( path );
318 globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n";
320 std::string path1 = getHomePath();
322 if ( ! path1.empty() )
324 path1 += "\\Documents";
329 globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n";
333 #endif // !GDEF_OS_WINDOWS
336 static std::string getXdgConfigHomePath()
338 /* FIXME: we may want to rely on g_get_user_config_dir()
340 std::string path ( getenv( "XDG_CONFIG_HOME" ) );
342 if ( ! path.empty() )
347 // This is not an error.
348 // globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n";
350 std::string path1 = getHomePath();
352 if ( ! path1.empty() )
354 return path1 + "/.config";
357 globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n";
362 static std::string getXdgDataHomePath()
364 std::string path ( getenv( "XDG_DATA_HOME" ) );
366 if ( ! path.empty() )
371 // This is not an error.
372 // globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n";
374 std::string path1 = getHomePath();
376 if ( ! path1.empty() )
378 return path1 + "/.local/share";
381 globalErrorStream() << "${XDG_DATA_HOME} not found.\n";
385 #endif // GDEF_OS_XDG
387 struct pathTransformer_t
390 std::string ( *function )();
393 static const pathTransformer_t pathTransformers[] =
396 { "\%HOMEPATH\%", getHomePath },
397 { "\%USERPROFILE\%", getUserProfilePath },
398 { "\%ProgramFiles\%", getProgramFilesPath },
399 { "\%ProgramFiles(x86)\%", getProgramFilesX86Path },
400 { "\%ProgramW6432\%", getProgramW6432Path },
401 { "\%APPDATA\%", getAppDataPath },
402 { "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath },
403 #endif // GDEF_OS_WINDOWS
405 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
406 { "~", getHomePath },
407 { "${HOME}", getHomePath },
408 #endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
411 { "${XDG_CONFIG_HOME}", getXdgConfigHomePath },
412 { "${XDG_DATA_HOME}", getXdgDataHomePath },
413 #endif // GDEF_OS_XDG
416 /* If no transformation succeeds, the path will be returned untransformed. */
417 std::string transformPath( std::string transformedPath )
419 for ( const pathTransformer_t &pathTransformer : pathTransformers )
421 if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 )
423 globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n";
425 std::string path = pathTransformer.function();
427 if ( ! path.empty() )
429 transformedPath.replace( 0, pathTransformer.pattern.length(), path );
431 globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n";
433 return transformedPath;
440 globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n";
442 return transformedPath;