1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/nix/xdg_util.h"
6
7 #include <optional>
8 #include <string>
9
10 #include "base/base_paths.h"
11 #include "base/command_line.h"
12 #include "base/environment.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/no_destructor.h"
17 #include "base/path_service.h"
18 #include "base/process/launch.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_tokenizer.h"
21 #include "base/strings/string_util.h"
22 #include "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h"
23 #include "base/threading/scoped_blocking_call.h"
24
25 namespace {
26
27 // The KDE session version environment variable introduced in KDE 4.
28 const char kKDESessionEnvVar[] = "KDE_SESSION_VERSION";
29
GetXdgActivationTokenCreator()30 base::nix::XdgActivationTokenCreator& GetXdgActivationTokenCreator() {
31 static base::NoDestructor<base::nix::XdgActivationTokenCreator> creator;
32 return *creator;
33 }
34
GetXdgActivationToken()35 std::optional<std::string>& GetXdgActivationToken() {
36 static base::NoDestructor<std::optional<std::string>> token;
37 return *token;
38 }
39
40 } // namespace
41
42 namespace base::nix {
43
44 const char kDotConfigDir[] = ".config";
45 const char kXdgConfigHomeEnvVar[] = "XDG_CONFIG_HOME";
46 const char kXdgCurrentDesktopEnvVar[] = "XDG_CURRENT_DESKTOP";
47 const char kXdgSessionTypeEnvVar[] = "XDG_SESSION_TYPE";
48 const char kXdgActivationTokenEnvVar[] = "XDG_ACTIVATION_TOKEN";
49 const char kXdgActivationTokenSwitch[] = "xdg-activation-token";
50
GetXDGDirectory(Environment * env,const char * env_name,const char * fallback_dir)51 FilePath GetXDGDirectory(Environment* env,
52 const char* env_name,
53 const char* fallback_dir) {
54 FilePath path;
55 std::string env_value;
56 if (env->GetVar(env_name, &env_value) && !env_value.empty()) {
57 path = FilePath(env_value);
58 } else {
59 PathService::Get(DIR_HOME, &path);
60 path = path.Append(fallback_dir);
61 }
62 return path.StripTrailingSeparators();
63 }
64
GetXDGUserDirectory(const char * dir_name,const char * fallback_dir)65 FilePath GetXDGUserDirectory(const char* dir_name, const char* fallback_dir) {
66 FilePath path;
67 char* xdg_dir = xdg_user_dir_lookup(dir_name);
68 if (xdg_dir) {
69 path = FilePath(xdg_dir);
70 free(xdg_dir);
71 } else {
72 PathService::Get(DIR_HOME, &path);
73 path = path.Append(fallback_dir);
74 }
75 return path.StripTrailingSeparators();
76 }
77
GetXDGDataWriteLocation(Environment * env)78 FilePath GetXDGDataWriteLocation(Environment* env) {
79 return GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
80 }
81
GetXDGDataSearchLocations(Environment * env)82 std::vector<FilePath> GetXDGDataSearchLocations(Environment* env) {
83 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
84
85 std::vector<FilePath> search_paths;
86 search_paths.push_back(GetXDGDataWriteLocation(env));
87
88 std::string xdg_data_dirs;
89 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
90 StringTokenizer tokenizer(xdg_data_dirs, ":");
91 while (tokenizer.GetNext()) {
92 search_paths.emplace_back(tokenizer.token_piece());
93 }
94 } else {
95 search_paths.emplace_back("/usr/local/share");
96 search_paths.emplace_back("/usr/share");
97 }
98
99 return search_paths;
100 }
101
GetDesktopEnvironment(Environment * env)102 DesktopEnvironment GetDesktopEnvironment(Environment* env) {
103 // kXdgCurrentDesktopEnvVar is the newest standard circa 2012.
104 std::string xdg_current_desktop;
105 if (env->GetVar(kXdgCurrentDesktopEnvVar, &xdg_current_desktop)) {
106 // It could have multiple values separated by colon in priority order.
107 for (const auto& value : SplitStringPiece(
108 xdg_current_desktop, ":", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
109 if (value == "Unity") {
110 // gnome-fallback sessions set kXdgCurrentDesktopEnvVar to Unity
111 // DESKTOP_SESSION can be gnome-fallback or gnome-fallback-compiz
112 std::string desktop_session;
113 if (env->GetVar("DESKTOP_SESSION", &desktop_session) &&
114 desktop_session.find("gnome-fallback") != std::string::npos) {
115 return DESKTOP_ENVIRONMENT_GNOME;
116 }
117 return DESKTOP_ENVIRONMENT_UNITY;
118 }
119 if (value == "Deepin") {
120 return DESKTOP_ENVIRONMENT_DEEPIN;
121 }
122 if (value == "GNOME") {
123 return DESKTOP_ENVIRONMENT_GNOME;
124 }
125 if (value == "X-Cinnamon") {
126 return DESKTOP_ENVIRONMENT_CINNAMON;
127 }
128 if (value == "KDE") {
129 std::string kde_session;
130 if (env->GetVar(kKDESessionEnvVar, &kde_session)) {
131 if (kde_session == "5") {
132 return DESKTOP_ENVIRONMENT_KDE5;
133 }
134 if (kde_session == "6") {
135 return DESKTOP_ENVIRONMENT_KDE6;
136 }
137 }
138 return DESKTOP_ENVIRONMENT_KDE4;
139 }
140 if (value == "Pantheon") {
141 return DESKTOP_ENVIRONMENT_PANTHEON;
142 }
143 if (value == "XFCE") {
144 return DESKTOP_ENVIRONMENT_XFCE;
145 }
146 if (value == "UKUI") {
147 return DESKTOP_ENVIRONMENT_UKUI;
148 }
149 if (value == "LXQt") {
150 return DESKTOP_ENVIRONMENT_LXQT;
151 }
152 }
153 }
154
155 // DESKTOP_SESSION was what everyone used in 2010.
156 std::string desktop_session;
157 if (env->GetVar("DESKTOP_SESSION", &desktop_session)) {
158 if (desktop_session == "deepin") {
159 return DESKTOP_ENVIRONMENT_DEEPIN;
160 }
161 if (desktop_session == "gnome" || desktop_session == "mate") {
162 return DESKTOP_ENVIRONMENT_GNOME;
163 }
164 if (desktop_session == "kde4" || desktop_session == "kde-plasma") {
165 return DESKTOP_ENVIRONMENT_KDE4;
166 }
167 if (desktop_session == "kde") {
168 // This may mean KDE4 on newer systems, so we have to check.
169 if (env->HasVar(kKDESessionEnvVar)) {
170 return DESKTOP_ENVIRONMENT_KDE4;
171 }
172 return DESKTOP_ENVIRONMENT_KDE3;
173 }
174 if (desktop_session.find("xfce") != std::string::npos ||
175 desktop_session == "xubuntu") {
176 return DESKTOP_ENVIRONMENT_XFCE;
177 }
178 if (desktop_session == "ukui") {
179 return DESKTOP_ENVIRONMENT_UKUI;
180 }
181 }
182
183 // Fall back on some older environment variables.
184 // Useful particularly in the DESKTOP_SESSION=default case.
185 if (env->HasVar("GNOME_DESKTOP_SESSION_ID")) {
186 return DESKTOP_ENVIRONMENT_GNOME;
187 }
188 if (env->HasVar("KDE_FULL_SESSION")) {
189 if (env->HasVar(kKDESessionEnvVar)) {
190 return DESKTOP_ENVIRONMENT_KDE4;
191 }
192 return DESKTOP_ENVIRONMENT_KDE3;
193 }
194
195 return DESKTOP_ENVIRONMENT_OTHER;
196 }
197
GetDesktopEnvironmentName(DesktopEnvironment env)198 const char* GetDesktopEnvironmentName(DesktopEnvironment env) {
199 switch (env) {
200 case DESKTOP_ENVIRONMENT_OTHER:
201 return nullptr;
202 case DESKTOP_ENVIRONMENT_CINNAMON:
203 return "CINNAMON";
204 case DESKTOP_ENVIRONMENT_DEEPIN:
205 return "DEEPIN";
206 case DESKTOP_ENVIRONMENT_GNOME:
207 return "GNOME";
208 case DESKTOP_ENVIRONMENT_KDE3:
209 return "KDE3";
210 case DESKTOP_ENVIRONMENT_KDE4:
211 return "KDE4";
212 case DESKTOP_ENVIRONMENT_KDE5:
213 return "KDE5";
214 case DESKTOP_ENVIRONMENT_KDE6:
215 return "KDE6";
216 case DESKTOP_ENVIRONMENT_PANTHEON:
217 return "PANTHEON";
218 case DESKTOP_ENVIRONMENT_UNITY:
219 return "UNITY";
220 case DESKTOP_ENVIRONMENT_XFCE:
221 return "XFCE";
222 case DESKTOP_ENVIRONMENT_UKUI:
223 return "UKUI";
224 case DESKTOP_ENVIRONMENT_LXQT:
225 return "LXQT";
226 }
227 return nullptr;
228 }
229
GetDesktopEnvironmentName(Environment * env)230 const char* GetDesktopEnvironmentName(Environment* env) {
231 return GetDesktopEnvironmentName(GetDesktopEnvironment(env));
232 }
233
GetSessionType(Environment & env)234 SessionType GetSessionType(Environment& env) {
235 std::string xdg_session_type;
236 if (!env.GetVar(kXdgSessionTypeEnvVar, &xdg_session_type)) {
237 return SessionType::kUnset;
238 }
239
240 TrimWhitespaceASCII(ToLowerASCII(xdg_session_type), TrimPositions::TRIM_ALL,
241 &xdg_session_type);
242
243 if (xdg_session_type == "wayland") {
244 return SessionType::kWayland;
245 }
246
247 if (xdg_session_type == "x11") {
248 return SessionType::kX11;
249 }
250
251 if (xdg_session_type == "tty") {
252 return SessionType::kTty;
253 }
254
255 if (xdg_session_type == "mir") {
256 return SessionType::kMir;
257 }
258
259 if (xdg_session_type == "unspecified") {
260 return SessionType::kUnspecified;
261 }
262
263 LOG(ERROR) << "Unknown XDG_SESSION_TYPE: " << xdg_session_type;
264 return SessionType::kOther;
265 }
266
ExtractXdgActivationTokenFromEnv(Environment & env)267 std::optional<std::string> ExtractXdgActivationTokenFromEnv(Environment& env) {
268 std::string token;
269 if (env.GetVar(kXdgActivationTokenEnvVar, &token) && !token.empty()) {
270 GetXdgActivationToken() = std::move(token);
271 env.UnSetVar(kXdgActivationTokenEnvVar);
272 }
273 return GetXdgActivationToken();
274 }
275
ExtractXdgActivationTokenFromCmdLine(base::CommandLine & cmd_line)276 void ExtractXdgActivationTokenFromCmdLine(base::CommandLine& cmd_line) {
277 std::string token = cmd_line.GetSwitchValueASCII(kXdgActivationTokenSwitch);
278 if (!token.empty()) {
279 GetXdgActivationToken() = std::move(token);
280 cmd_line.RemoveSwitch(kXdgActivationTokenSwitch);
281 }
282 }
283
TakeXdgActivationToken()284 std::optional<std::string> TakeXdgActivationToken() {
285 auto token = GetXdgActivationToken();
286 GetXdgActivationToken().reset();
287 return token;
288 }
289
SetXdgActivationTokenCreator(XdgActivationTokenCreator token_creator)290 void SetXdgActivationTokenCreator(XdgActivationTokenCreator token_creator) {
291 GetXdgActivationTokenCreator() = std::move(token_creator);
292 }
293
CreateLaunchOptionsWithXdgActivation(XdgActivationLaunchOptionsCallback callback)294 void CreateLaunchOptionsWithXdgActivation(
295 XdgActivationLaunchOptionsCallback callback) {
296 if (!GetXdgActivationTokenCreator()) {
297 // There is no token creator, so return an empty LaunchOptions.
298 std::move(callback).Run(LaunchOptions());
299 return;
300 }
301 auto create_token_cb =
302 [](XdgActivationLaunchOptionsCallback launch_options_cb,
303 std::string token) {
304 base::LaunchOptions options;
305 if (!token.empty()) {
306 options.environment[kXdgActivationTokenEnvVar] = token;
307 }
308 std::move(launch_options_cb).Run(options);
309 };
310 GetXdgActivationTokenCreator().Run(
311 base::BindOnce(create_token_cb, std::move(callback)));
312 }
313
314 } // namespace base::nix
315