1717#include " whereami.h"
1818#include < iostream>
1919
20+ #if !_WIN64
21+ #include < unistd.h>
22+ #include < sys/types.h>
23+ #include < pwd.h>
24+ #include < cerrno>
25+ #include < cstring>
26+ const char *const TangerineAppID =
27+ // TODO: consider a reverse-dns name with the escaping recommended in:
28+ // https://docs.gtk.org/gio/type_func.Application.id_is_valid.html
29+ // See rationale in: https://docs.gtk.org/gtk4/migrating-3to4.html#set-a-proper-application-id
30+ " tangerine" ;
31+ std::optional<std::filesystem::path> GetXDGStateHome ();
32+ #endif
33+
2034
2135StatusCode TangerinePaths::PopulateInstallationPaths ()
2236{
37+ std::filesystem::path ExecutablePath;
2338 {
2439 int Length = wai_getExecutablePath (NULL , 0 , NULL );
2540 if (Length > -1 )
@@ -44,20 +59,142 @@ StatusCode TangerinePaths::PopulateInstallationPaths()
4459 }
4560 }
4661
47- ExecutableDir = ExecutablePath.parent_path ();
62+ std::filesystem::path ExecutableDir = ExecutablePath.parent_path ();
4863
4964#ifdef TANGERINE_PKGDATADIR_FROM_BINDIR
5065#define STRINGIFY (x ) #x
5166#define EXPAND_AS_STR (x ) STRINGIFY(x)
52- PkgDataDir = ExecutableDir / std::filesystem::path (EXPAND_AS_STR (TANGERINE_PKGDATADIR_FROM_BINDIR));
67+ std::filesystem::path PkgDataDir = ExecutableDir / std::filesystem::path (EXPAND_AS_STR (TANGERINE_PKGDATADIR_FROM_BINDIR));
5368#undef EXPAND_AS_STR
5469#undef STRINGIFY
5570#else
56- PkgDataDir = ExecutableDir;
71+ std::filesystem::path PkgDataDir = ExecutableDir;
5772#endif
5873
5974 ShadersDir = PkgDataDir / std::filesystem::path (" shaders" );
6075 ModelsDir = PkgDataDir / std::filesystem::path (" models" );
6176
77+ #if defined(TANGERINE_SELF_CONTAINED)
78+ BookmarksPath = PkgDataDir / std::filesystem::path (" bookmarks.txt" );
79+ #elif !_WIN64
80+ if (std::optional<std::filesystem::path> HomeDir = GetXDGStateHome ())
81+ {
82+ BookmarksPath = HomeDir.value () / std::filesystem::path (TangerineAppID) / std::filesystem::path (" bookmarks.txt" );
83+ }
84+ else
85+ {
86+ BookmarksPath = std::nullopt ;
87+ }
88+ #else // Shouldn't get here: handled in "installation.h". Using %APPDATA% / CSIDL_APPDATA / FOLDERID_RoamingAppData might be useful, though.
89+ # error "Windows currently requires TANGERINE_SELF_CONTAINED."
90+ #endif
91+
6292 return StatusCode::PASS;
6393}
94+
95+
96+ #if !_WIN64
97+ std::optional<std::filesystem::path> GetHomeDir () {
98+ // Based on `rktio_expand_user_tilde()`.
99+ // License: (Apache-2.0 OR MIT)
100+
101+ // $HOME overrides everything.
102+ if (const char *Home = std::getenv (" HOME" ))
103+ {
104+ return std::filesystem::path (Home);
105+ }
106+
107+ // $USER and $LOGNAME (in that order) override `getuid()`.
108+ const char *AltUserVar = " USER" ;
109+ const char *AltUser = std::getenv (AltUserVar);
110+ if (!AltUser)
111+ {
112+ AltUserVar = " LOGNAME" ;
113+ AltUser = std::getenv (AltUserVar);
114+ }
115+
116+ /* getpwnam(3) man page says: "If one wants to check errno after the
117+ call, it should be set to zero before the call." */
118+ errno = 0 ;
119+ struct passwd *Passwd = AltUser ? getpwnam (AltUser) : getpwuid (getuid ());
120+ int PasswdError = errno;
121+
122+ // Did we find it?
123+ if (Passwd && Passwd->pw_dir )
124+ {
125+ if (0 != PasswdError)
126+ {
127+ std::cout << " Warning: Found home directory, but " << (AltUser ? " getpwnam" : " getpwuid" ) << " reported an error.\n " ;
128+ }
129+ else
130+ {
131+ // No warning
132+ return std::filesystem::path (Passwd->pw_dir );
133+ }
134+ }
135+ else if (Passwd)
136+ {
137+ std::cout << " Warning: User exists, but does not have a home directory.\n " ;
138+ }
139+ else
140+ {
141+ std::cout << " Warning: Could not find home directory: user not found.\n " ;
142+ }
143+
144+ // Add warning details:
145+ // Was `getuid()` overridden?
146+ if (AltUser)
147+ {
148+ std::cout << " user: " << AltUser << " (from $" << AltUserVar << " );\n " ;
149+ }
150+ // Report system error.
151+ if (0 != PasswdError)
152+ {
153+ std::cout << " error: " << std::strerror (PasswdError) << " \n " ;
154+ std::cout << " errno: " << PasswdError << " \n " ;
155+ }
156+ else
157+ {
158+ std::cout << " errno: not set by " << (AltUser ? " getpwnam" : " getpwuid" ) << " \n " ;
159+ }
160+
161+ if (Passwd && Passwd->pw_dir )
162+ {
163+ return std::filesystem::path (Passwd->pw_dir );
164+ }
165+ else
166+ {
167+ return {};
168+ }
169+ }
170+
171+ std::optional<std::filesystem::path> GetXDGStateHome ()
172+ {
173+ // Based on `rktio_system_path()`.
174+ // License: (Apache-2.0 OR MIT)
175+
176+ const char *EnvVar = " XDG_STATE_HOME" ;
177+ const char *DefaultSubpath = " .local/state" ;
178+
179+ // Check the environment variable.
180+ if (const char *FromEnv = std::getenv (EnvVar))
181+ {
182+ std::filesystem::path Candidate = std::filesystem::path (FromEnv);
183+ // We must ignore the environment variable if it is not an absolute path.
184+ if (Candidate.is_absolute ()) {
185+ return Candidate;
186+ }
187+ }
188+
189+
190+ // Environment variable was unset or is invalid.
191+ if (std::optional<std::filesystem::path> Home = GetHomeDir ())
192+ {
193+ return Home.value () / std::filesystem::path (DefaultSubpath);
194+ }
195+ else
196+ {
197+ return {};
198+ }
199+ }
200+ #endif /* !_WIN64 */
0 commit comments