blob: 0a199b8b1381ed3956bfc1351cab9760fa4e5e91 [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2012 The Chromium Authors
[email protected]ef525cc2009-07-10 17:08:162// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]8806d3b2012-04-13 06:46:345#include "chrome/browser/shell_integration_linux.h"
[email protected]ef525cc2009-07-10 17:08:166
[email protected]6584f0b12009-07-20 23:38:147#include <fcntl.h>
avi664c07b2015-12-26 02:18:318#include <stddef.h>
[email protected]ef525cc2009-07-10 17:08:169#include <stdlib.h>
[email protected]6584f0b12009-07-20 23:38:1410#include <sys/stat.h>
11#include <sys/types.h>
12#include <unistd.h>
[email protected]ef525cc2009-07-10 17:08:1613
Lei Zhange711d89c2018-09-10 18:48:0014#include <memory>
Robert Woods954a2182020-03-20 06:08:2715#include <sstream>
[email protected]42896802009-08-28 23:39:4416#include <string>
Lei Zhange711d89c2018-09-10 18:48:0017#include <utility>
[email protected]ef525cc2009-07-10 17:08:1618#include <vector>
19
[email protected]6a83c4242011-07-07 06:06:4120#include "base/base_paths.h"
[email protected]42896802009-08-28 23:39:4421#include "base/command_line.h"
[email protected]76b90d312010-08-03 03:00:5022#include "base/environment.h"
[email protected]111f0282013-08-05 10:09:2923#include "base/files/file_enumerator.h"
[email protected]57999812013-02-24 05:40:5224#include "base/files/file_path.h"
thestig18dfb7a52014-08-26 10:44:0425#include "base/files/file_util.h"
[email protected]d0767cb542009-10-08 17:38:3026#include "base/i18n/file_util_icu.h"
Hans Wennborgf6ad69c2020-06-18 18:02:3227#include "base/logging.h"
[email protected]08a139d2013-04-11 03:32:5428#include "base/memory/ref_counted_memory.h"
thestigd2b1fcf2015-01-21 22:11:4929#include "base/nix/xdg_util.h"
[email protected]b96aa932009-08-12 21:34:4930#include "base/path_service.h"
[email protected]2025d002012-11-14 20:54:3531#include "base/posix/eintr_wrapper.h"
[email protected]d09a4ce1c2013-07-24 17:37:0232#include "base/process/kill.h"
33#include "base/process/launch.h"
[email protected]3ea1b182013-02-08 22:38:4134#include "base/strings/string_number_conversions.h"
[email protected]f4ebe772013-02-02 00:21:3935#include "base/strings/string_tokenizer.h"
[email protected]12100ad32013-07-10 05:07:0136#include "base/strings/string_util.h"
Robert Woods954a2182020-03-20 06:08:2737#include "base/strings/stringprintf.h"
[email protected]e309f312013-06-07 21:50:0838#include "base/strings/utf_string_conversions.h"
Etienne Pierre-doraye6c535312018-08-28 15:45:3939#include "base/threading/scoped_blocking_call.h"
[email protected]34b99632011-01-01 01:01:0640#include "base/threading/thread.h"
[email protected]89886652012-12-11 18:09:0741#include "base/threading/thread_restrictions.h"
Nico Webereaa08412019-08-14 01:24:3742#include "build/branding_buildflags.h"
[email protected]b03f53cd2011-04-06 18:18:4343#include "build/build_config.h"
Yuta Hijikata235fc62b2020-12-08 03:48:3244#include "build/chromeos_buildflags.h"
[email protected]2e0424a2014-04-15 13:02:1545#include "chrome/browser/shell_integration.h"
Daniel Murphy2ddeccc42022-02-17 02:14:2246#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
Alexander Dunaev2193149f2022-01-05 18:13:4847#include "chrome/browser/web_applications/web_app_helpers.h"
Scott Violet6200d332018-02-23 21:29:2348#include "chrome/common/buildflags.h"
sdefresne9fb67692015-08-03 18:48:2249#include "chrome/common/channel_info.h"
[email protected]42896802009-08-28 23:39:4450#include "chrome/common/chrome_constants.h"
[email protected]7199367d2014-01-20 04:06:2151#include "chrome/common/chrome_switches.h"
thestig4a9b0ef2016-08-29 08:22:1252#include "chrome/grit/chrome_unscaled_resources.h"
sdefresne9fb67692015-08-03 18:48:2253#include "components/version_info/version_info.h"
Evan Stadeb8f43a42021-09-14 16:25:1454#include "third_party/libxml/chromium/xml_writer.h"
Mandy Chen5de2f242022-01-12 00:30:2155#include "third_party/re2/src/re2/re2.h"
[email protected]7199367d2014-01-20 04:06:2156#include "ui/base/resource/resource_bundle.h"
[email protected]08a139d2013-04-11 03:32:5457#include "ui/gfx/image/image_family.h"
Maksim Sisov213ddfa2021-09-30 08:18:5658#include "ui/ozone/public/ozone_platform.h"
59#include "ui/ozone/public/platform_utils.h"
[email protected]761fa4702013-07-02 15:25:1560#include "url/gurl.h"
[email protected]ef525cc2009-07-10 17:08:1661
Lei Zhange711d89c2018-09-10 18:48:0062#if defined(USE_GLIB)
63#include <glib.h>
64#endif
65
Alexey Baskakovb13491b2018-08-03 07:46:3866namespace shell_integration_linux {
Alexey Baskakovf302efe2018-07-28 02:02:3267
pmonette9fa59e882016-02-10 00:12:1968const char kXdgSettings[] = "xdg-settings";
69const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
70const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
71
72// Utility function to get the path to the version of a script shipped with
73// Chrome. |script| gives the name of the script. |chrome_version| returns the
74// path to the Chrome version of the script, and the return value of the
75// function is true if the function is successful and the Chrome version is
76// not the script found on the PATH.
77bool GetChromeVersionOfScript(const std::string& script,
78 std::string* chrome_version) {
79 // Get the path to the Chrome version.
80 base::FilePath chrome_dir;
Avi Drissman9098f9002018-05-04 00:11:5281 if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
pmonette9fa59e882016-02-10 00:12:1982 return false;
83
84 base::FilePath chrome_version_path = chrome_dir.Append(script);
85 *chrome_version = chrome_version_path.value();
86
87 // Check if this is different to the one on path.
88 std::vector<std::string> argv;
89 argv.push_back("which");
90 argv.push_back(script);
91 std::string path_version;
92 if (base::GetAppOutput(base::CommandLine(argv), &path_version)) {
93 // Remove trailing newline
94 path_version.erase(path_version.length() - 1, 1);
95 base::FilePath path_version_path(path_version);
96 return (chrome_version_path != path_version_path);
97 }
98 return false;
99}
100
101// Value returned by xdg-settings if it can't understand our request.
102const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
103
104// We delegate the difficulty of setting the default browser and default url
105// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
106
107// When calling this script we first try to use the script on PATH. If that
108// fails we then try to use the script that we have included. This gives
109// scripts on the system priority over ours, as distribution vendors may have
110// tweaked the script, but still allows our copy to be used if the script on the
111// system fails, as the system copy may be missing capabilities of the Chrome
112// copy.
113
Avi Drissman68395fd72023-01-04 19:52:37114// If |scheme| is empty this function sets Chrome as the default browser,
115// otherwise it sets Chrome as the default handler application for |scheme|.
116bool SetDefaultWebClient(const std::string& scheme) {
Yuta Hijikata235fc62b2020-12-08 03:48:32117#if BUILDFLAG(IS_CHROMEOS_ASH)
pmonette9fa59e882016-02-10 00:12:19118 return true;
119#else
dcheng4af48582016-04-19 00:29:35120 std::unique_ptr<base::Environment> env(base::Environment::Create());
pmonette9fa59e882016-02-10 00:12:19121
122 std::vector<std::string> argv;
123 argv.push_back(kXdgSettings);
124 argv.push_back("set");
Avi Drissman68395fd72023-01-04 19:52:37125 if (scheme.empty()) {
pmonette9fa59e882016-02-10 00:12:19126 argv.push_back(kXdgSettingsDefaultBrowser);
127 } else {
128 argv.push_back(kXdgSettingsDefaultSchemeHandler);
Avi Drissman68395fd72023-01-04 19:52:37129 argv.push_back(scheme);
pmonette9fa59e882016-02-10 00:12:19130 }
Tom Andersonb8a86032019-10-09 20:12:45131 argv.push_back(chrome::GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19132
133 int exit_code;
134 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
135 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
136 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
137 ran_ok = LaunchXdgUtility(argv, &exit_code);
138 }
139 }
140
141 return ran_ok && exit_code == EXIT_SUCCESS;
142#endif
143}
144
Avi Drissman68395fd72023-01-04 19:52:37145// If |scheme| is empty this function checks if Chrome is the default browser,
thomasanderson8258b1c52017-03-16 00:04:20146// otherwise it checks if Chrome is the default handler application for
Avi Drissman68395fd72023-01-04 19:52:37147// |scheme|.
Alexey Baskakovb13491b2018-08-03 07:46:38148shell_integration::DefaultWebClientState GetIsDefaultWebClient(
Avi Drissman68395fd72023-01-04 19:52:37149 const std::string& scheme) {
Yuta Hijikata235fc62b2020-12-08 03:48:32150#if BUILDFLAG(IS_CHROMEOS_ASH)
Alexey Baskakovb13491b2018-08-03 07:46:38151 return shell_integration::UNKNOWN_DEFAULT;
thomasanderson8258b1c52017-03-16 00:04:20152#else
Etienne Bergeron436d42212019-02-26 17:15:12153 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
154 base::BlockingType::MAY_BLOCK);
thomasanderson8258b1c52017-03-16 00:04:20155
156 std::unique_ptr<base::Environment> env(base::Environment::Create());
157
pmonette9fa59e882016-02-10 00:12:19158 std::vector<std::string> argv;
159 argv.push_back(kXdgSettings);
thomasanderson8258b1c52017-03-16 00:04:20160 argv.push_back("check");
Avi Drissman68395fd72023-01-04 19:52:37161 if (scheme.empty()) {
pmonette9fa59e882016-02-10 00:12:19162 argv.push_back(kXdgSettingsDefaultBrowser);
163 } else {
164 argv.push_back(kXdgSettingsDefaultSchemeHandler);
Avi Drissman68395fd72023-01-04 19:52:37165 argv.push_back(scheme);
pmonette9fa59e882016-02-10 00:12:19166 }
Tom Andersonb8a86032019-10-09 20:12:45167 argv.push_back(chrome::GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19168
169 std::string reply;
170 int success_code;
171 bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
172 &success_code);
173 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
174 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
175 ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
176 &success_code);
177 }
178 }
179
thomasanderson8258b1c52017-03-16 00:04:20180 if (!ran_ok || success_code != EXIT_SUCCESS) {
181 // xdg-settings failed: we can't determine or set the default browser.
Alexey Baskakovb13491b2018-08-03 07:46:38182 return shell_integration::UNKNOWN_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19183 }
184
thomasanderson8258b1c52017-03-16 00:04:20185 // Allow any reply that starts with "yes".
186 return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
Alexey Baskakovb13491b2018-08-03 07:46:38187 ? shell_integration::IS_DEFAULT
188 : shell_integration::NOT_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19189#endif
190}
191
thomasanderson12d87582016-07-29 21:17:41192// https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
193// The WM_CLASS property should be set to the same as the *.desktop file without
194// the .desktop extension. We cannot simply use argv[0] in this case, because
195// on the stable channel, the executable name is google-chrome-stable, but the
196// desktop file is google-chrome.desktop.
197std::string GetDesktopBaseName(const std::string& desktop_file_name) {
198 static const char kDesktopExtension[] = ".desktop";
199 if (base::EndsWith(desktop_file_name, kDesktopExtension,
200 base::CompareCase::SENSITIVE)) {
201 return desktop_file_name.substr(
202 0, desktop_file_name.length() - strlen(kDesktopExtension));
203 }
204 return desktop_file_name;
205}
206
pmonette9fa59e882016-02-10 00:12:19207namespace {
208
kalyan.kondapally577803c2014-08-25 20:13:18209#if defined(USE_GLIB)
[email protected]b10392932011-03-08 21:28:14210// Quote a string such that it appears as one verbatim argument for the Exec
211// key in a desktop file.
212std::string QuoteArgForDesktopFileExec(const std::string& arg) {
213 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
214
215 // Quoting is only necessary if the argument has a reserved character.
216 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
217 return arg; // No quoting necessary.
218
219 std::string quoted = "\"";
220 for (size_t i = 0; i < arg.size(); ++i) {
221 // Note that the set of backslashed characters is smaller than the
222 // set of reserved characters.
223 switch (arg[i]) {
224 case '"':
225 case '`':
226 case '$':
227 case '\\':
228 quoted += '\\';
229 break;
230 }
231 quoted += arg[i];
232 }
233 quoted += '"';
234
235 return quoted;
236}
237
[email protected]2164e512014-01-22 09:32:10238// Quote a command line so it is suitable for use as the Exec key in a desktop
239// file. Note: This should be used instead of GetCommandLineString, which does
240// not properly quote the string; this function is designed for the Exec key.
241std::string QuoteCommandLineForDesktopFileExec(
avi556c05022014-12-22 23:31:43242 const base::CommandLine& command_line) {
[email protected]2164e512014-01-22 09:32:10243 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
244
Lei Zhang9b0be802020-05-05 21:11:48245 std::string quoted_path;
avi556c05022014-12-22 23:31:43246 const base::CommandLine::StringVector& argv = command_line.argv();
jdoerrie601c7152018-10-02 23:43:11247 for (auto i = argv.begin(); i != argv.end(); ++i) {
[email protected]2164e512014-01-22 09:32:10248 if (i != argv.begin())
249 quoted_path += " ";
250 quoted_path += QuoteArgForDesktopFileExec(*i);
251 }
252
253 return quoted_path;
254}
Alexey Baskakovf302efe2018-07-28 02:02:32255#endif
[email protected]2164e512014-01-22 09:32:10256
Alexey Baskakovf302efe2018-07-28 02:02:32257#if defined(USE_GLIB)
[email protected]4f0806a72011-09-21 03:08:45258const char kDesktopEntry[] = "Desktop Entry";
[email protected]4f0806a72011-09-21 03:08:45259const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
Mandy Chend57606f2021-08-31 17:49:10260
261void SetActionsForDesktopApplication(
262 const base::CommandLine& command_line,
263 GKeyFile* key_file,
264 std::set<web_app::DesktopActionInfo> action_info) {
265 if (action_info.empty())
266 return;
267
268 std::vector<std::string> action_ids;
269 for (const auto& info : action_info) {
270 action_ids.push_back(info.id);
271 }
272
273 std::string joined_action_ids = base::JoinString(action_ids, ";");
274 g_key_file_set_string(key_file, kDesktopEntry, "Actions",
275 joined_action_ids.c_str());
276
277 for (const auto& info : action_info) {
278 std::string section_title = "Desktop Action " + info.id;
279 g_key_file_set_string(key_file, section_title.c_str(), "Name",
280 info.name.c_str());
281
Mandy Chen5de2f242022-01-12 00:30:21282 std::string launch_url_str = info.exec_launch_url.spec();
283 // Escape % as %%.
284 RE2::GlobalReplace(&launch_url_str, "%", "%%");
Mandy Chend57606f2021-08-31 17:49:10285 base::CommandLine current_cmd(command_line);
286 current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem,
Mandy Chen5de2f242022-01-12 00:30:21287 launch_url_str);
Mandy Chend57606f2021-08-31 17:49:10288
289 g_key_file_set_string(
290 key_file, section_title.c_str(), "Exec",
291 QuoteCommandLineForDesktopFileExec(current_cmd).c_str());
292 }
293}
kalyan.kondapally577803c2014-08-25 20:13:18294#endif
[email protected]4f0806a72011-09-21 03:08:45295
Mike Jackson0cb0ba72022-09-26 23:22:21296base::FilePath GetDesktopFileForDefaultSchemeHandler(base::Environment* env,
297 const GURL& url) {
298 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
299 base::BlockingType::MAY_BLOCK);
300
301 std::vector<std::string> argv;
302 argv.push_back(shell_integration_linux::kXdgSettings);
303 argv.push_back("get");
304 argv.push_back(shell_integration_linux::kXdgSettingsDefaultSchemeHandler);
305 argv.push_back(url.scheme());
306 argv.push_back(chrome::GetDesktopName(env));
307
308 std::string desktop_file_name;
309 if (base::GetAppOutput(base::CommandLine(argv), &desktop_file_name) &&
310 desktop_file_name.find(".desktop") != std::string::npos) {
311 // Remove trailing newline
312 desktop_file_name.erase(desktop_file_name.length() - 1, 1);
313 return base::FilePath(desktop_file_name);
314 }
315
316 return base::FilePath();
317}
318
319std::string GetDesktopEntryStringValueFromFromDesktopFile(
320 const std::string& key,
321 const std::string& shortcut_contents) {
322 std::string key_value;
323#if defined(USE_GLIB)
324 // An empty file causes a crash with glib <= 2.32, so special case here.
325 if (shortcut_contents.empty())
326 return key_value;
327
328 GKeyFile* key_file = g_key_file_new();
329 GError* err = nullptr;
330 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
331 shortcut_contents.size(), G_KEY_FILE_NONE,
332 &err)) {
333 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
334 g_error_free(err);
335 g_key_file_free(key_file);
336 return key_value;
337 }
338
339 char* key_c_string =
340 g_key_file_get_string(key_file, kDesktopEntry, key.c_str(), &err);
341 if (key_c_string) {
342 key_value = key_c_string;
343 g_free(key_c_string);
344 } else {
345 g_error_free(err);
346 }
347
348 g_key_file_free(key_file);
349#else
350 NOTIMPLEMENTED();
351#endif
352
353 return key_value;
354}
355
Alexey Baskakovf302efe2018-07-28 02:02:32356} // namespace
357
Alexey Baskakovb13491b2018-08-03 07:46:38358// Allows LaunchXdgUtility to join a process.
359// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
360class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
361 : public base::ScopedAllowBaseSyncPrimitives {};
362
363bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
364 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
365 // files on top of originals after making changes to them. In the event that
366 // the original files are owned by another user (e.g. root, which can happen
367 // if they are updated within sudo), mv will prompt the user to confirm if
368 // standard input is a terminal (otherwise it just does it). So make sure it's
369 // not, to avoid locking everything up waiting for mv.
370 *exit_code = EXIT_FAILURE;
371 int devnull = open("/dev/null", O_RDONLY);
372 if (devnull < 0)
373 return false;
374
375 base::LaunchOptions options;
376 options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
377 base::Process process = base::LaunchProcess(argv, options);
378 close(devnull);
379 if (!process.IsValid())
380 return false;
381 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
382 return process.WaitForExit(exit_code);
383}
384
385std::string GetWMClassFromAppName(std::string app_name) {
386 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
387 base::TrimString(app_name, "_", &app_name);
388 return app_name;
389}
390
Alexander Dunaev2193149f2022-01-05 18:13:48391std::string GetXdgAppIdForWebApp(std::string app_name,
392 const base::FilePath& profile_path) {
393 if (base::StartsWith(app_name, web_app::kCrxAppPrefix))
394 app_name = app_name.substr(strlen(web_app::kCrxAppPrefix));
395 return GetDesktopBaseName(
396 web_app::GetAppShortcutFilename(profile_path, app_name).AsUTF8Unsafe());
397}
398
Alexey Baskakovf302efe2018-07-28 02:02:32399base::FilePath GetDataWriteLocation(base::Environment* env) {
400 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
401}
402
403std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
Etienne Bergeron436d42212019-02-26 17:15:12404 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
405 base::BlockingType::MAY_BLOCK);
Alexey Baskakovf302efe2018-07-28 02:02:32406
407 std::vector<base::FilePath> search_paths;
408 base::FilePath write_location = GetDataWriteLocation(env);
409 search_paths.push_back(write_location);
410
411 std::string xdg_data_dirs;
412 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
413 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
414 while (tokenizer.GetNext()) {
Daniel Cheng04a05fd2021-04-19 17:18:52415 search_paths.emplace_back(tokenizer.token_piece());
Alexey Baskakovf302efe2018-07-28 02:02:32416 }
417 } else {
418 search_paths.push_back(base::FilePath("/usr/local/share"));
419 search_paths.push_back(base::FilePath("/usr/share"));
420 }
421
422 return search_paths;
423}
424
425namespace internal {
426
Mike Jackson0cb0ba72022-09-26 23:22:21427std::string GetDesktopEntryStringValueFromFromDesktopFileForTest(
428 const std::string& key,
429 const std::string& shortcut_contents) {
430 return shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
431 key, shortcut_contents);
432}
433
[email protected]d81a63c02013-03-07 08:49:04434// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
435// file, given in |shortcut_contents|. If the key is not found, returns false.
436bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
Mike Jackson0cb0ba72022-09-26 23:22:21437 std::string nodisplay_value =
438 shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
439 "NoDisplay", shortcut_contents);
440 return nodisplay_value == "true";
[email protected]d81a63c02013-03-07 08:49:04441}
442
[email protected]fcd21d322013-06-27 12:35:56443// Gets the path to the Chrome executable or wrapper script.
thestigd2b1fcf2015-01-21 22:11:49444// Returns an empty path if the executable path could not be found, which should
445// never happen.
[email protected]fcd21d322013-06-27 12:35:56446base::FilePath GetChromeExePath() {
447 // Try to get the name of the wrapper script that launched Chrome.
dcheng4af48582016-04-19 00:29:35448 std::unique_ptr<base::Environment> environment(base::Environment::Create());
[email protected]fcd21d322013-06-27 12:35:56449 std::string wrapper_script;
thestigd2b1fcf2015-01-21 22:11:49450 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
[email protected]fcd21d322013-06-27 12:35:56451 return base::FilePath(wrapper_script);
[email protected]fcd21d322013-06-27 12:35:56452
453 // Just return the name of the executable path for Chrome.
454 base::FilePath chrome_exe_path;
Avi Drissman9098f9002018-05-04 00:11:52455 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
[email protected]fcd21d322013-06-27 12:35:56456 return chrome_exe_path;
457}
458
thomasanderson12d87582016-07-29 21:17:41459std::string GetProgramClassName(const base::CommandLine& command_line,
460 const std::string& desktop_file_name) {
Alexey Baskakovb13491b2018-08-03 07:46:38461 std::string class_name = GetDesktopBaseName(desktop_file_name);
thomasanderson12d87582016-07-29 21:17:41462 std::string user_data_dir =
463 command_line.GetSwitchValueNative(switches::kUserDataDir);
464 // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
465 // class name to "Chrome (/tmp/my-user-data)". The class name will show up in
466 // the alt-tab list in gnome-shell if you're running a binary that doesn't
467 // have a matching .desktop file.
468 return user_data_dir.empty()
469 ? class_name
470 : class_name + " (" + user_data_dir + ")";
471}
472
473std::string GetProgramClassClass(const base::CommandLine& command_line,
474 const std::string& desktop_file_name) {
475 if (command_line.HasSwitch(switches::kWmClass))
476 return command_line.GetSwitchValueASCII(switches::kWmClass);
Alexander Dunaev66b50ba2021-08-10 04:11:46477 std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
Maksim Sisov213ddfa2021-09-30 08:18:56478 if (auto* platform_utils =
479 ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
480 return platform_utils->GetWmWindowClass(desktop_base_name);
thomasanderson12d87582016-07-29 21:17:41481 }
Alexander Dunaev66b50ba2021-08-10 04:11:46482 if (!desktop_base_name.empty()) {
483 // Capitalize the first character like gtk does.
484 desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]);
485 }
486 return desktop_base_name;
thomasanderson12d87582016-07-29 21:17:41487}
488
489} // namespace internal
490
[email protected]f93a77452013-09-02 05:26:35491std::string GetProgramClassName() {
dcheng4af48582016-04-19 00:29:35492 std::unique_ptr<base::Environment> env(base::Environment::Create());
thomasanderson12d87582016-07-29 21:17:41493 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45494 chrome::GetDesktopName(env.get()));
thomasanderson12d87582016-07-29 21:17:41495}
496
497std::string GetProgramClassClass() {
498 std::unique_ptr<base::Environment> env(base::Environment::Create());
499 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45500 chrome::GetDesktopName(env.get()));
[email protected]98566d7a2012-04-17 00:28:56501}
502
[email protected]14fbaed2013-05-02 07:54:02503std::string GetIconName() {
Nico Webereaa08412019-08-14 01:24:37504#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02505 return "google-chrome";
Nico Weber897593f2019-07-25 23:17:55506#else // BUILDFLAG(CHROMIUM_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02507 return "chromium-browser";
508#endif
509}
510
[email protected]d81a63c02013-03-07 08:49:04511bool GetExistingShortcutContents(base::Environment* env,
512 const base::FilePath& desktop_filename,
513 std::string* output) {
Etienne Bergeron436d42212019-02-26 17:15:12514 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
515 base::BlockingType::MAY_BLOCK);
[email protected]620942e2010-02-16 10:12:12516
[email protected]111f0282013-08-05 10:09:29517 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
[email protected]b96aa932009-08-12 21:34:49518
[email protected]650b2d52013-02-10 03:41:45519 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49520 i != search_paths.end(); ++i) {
[email protected]d81a63c02013-03-07 08:49:04521 base::FilePath path = i->Append("applications").Append(desktop_filename);
522 VLOG(1) << "Looking for desktop file in " << path.value();
[email protected]7567484142013-07-11 17:36:07523 if (base::PathExists(path)) {
[email protected]d81a63c02013-03-07 08:49:04524 VLOG(1) << "Found desktop file at " << path.value();
[email protected]82f84b92013-08-30 18:23:50525 return base::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12526 }
[email protected]b96aa932009-08-12 21:34:49527 }
528
529 return false;
530}
531
[email protected]650b2d52013-02-10 03:41:45532base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44533 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12534 std::string filename =
[email protected]4f260d02010-12-23 18:35:42535 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]6bc03de2014-08-07 23:59:15536 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49537
[email protected]650b2d52013-02-10 03:41:45538 base::FilePath desktop_path;
Avi Drissman9098f9002018-05-04 00:11:52539 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45540 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10541
[email protected]650b2d52013-02-10 03:41:45542 base::FilePath filepath = desktop_path.Append(filename);
543 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10544 for (size_t i = 1; i < 100; ++i) {
[email protected]7567484142013-07-11 17:36:07545 if (base::PathExists(base::FilePath(alternative_filepath))) {
[email protected]650b2d52013-02-10 03:41:45546 alternative_filepath = base::FilePath(
Brett Wilson5accd242017-11-30 22:07:32547 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10548 } else {
[email protected]650b2d52013-02-10 03:41:45549 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10550 }
551 }
552
[email protected]650b2d52013-02-10 03:41:45553 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49554}
555
[email protected]111f0282013-08-05 10:09:29556std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
557 const base::FilePath& profile_path,
558 const base::FilePath& directory) {
Etienne Bergeron436d42212019-02-26 17:15:12559 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
560 base::BlockingType::MAY_BLOCK);
thestigd2b1fcf2015-01-21 22:11:49561
[email protected]111f0282013-08-05 10:09:29562 // Use a prefix, because xdg-desktop-menu requires it.
563 std::string prefix(chrome::kBrowserProcessExecutableName);
564 prefix.append("-");
565 std::string suffix("-");
566 suffix.append(profile_path.BaseName().value());
[email protected]6bc03de2014-08-07 23:59:15567 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
[email protected]111f0282013-08-05 10:09:29568 // Spaces in filenames break xdg-desktop-menu
569 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
[email protected]466c9862013-12-03 22:05:28570 base::ReplaceChars(suffix, " ", "_", &suffix);
[email protected]111f0282013-08-05 10:09:29571 std::string glob = prefix + "*" + suffix + ".desktop";
572
573 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
574 glob);
575 base::FilePath shortcut_file = files.Next();
576 std::vector<base::FilePath> shortcut_paths;
577 while (!shortcut_file.empty()) {
578 shortcut_paths.push_back(shortcut_file.BaseName());
579 shortcut_file = files.Next();
580 }
581 return shortcut_paths;
582}
583
Mandy Chend57606f2021-08-31 17:49:10584std::string GetDesktopFileContents(
585 const base::FilePath& chrome_exe_path,
586 const std::string& app_name,
587 const GURL& url,
588 const std::string& extension_id,
589 const std::u16string& title,
590 const std::string& icon_name,
591 const base::FilePath& profile_path,
592 const std::string& categories,
593 const std::string& mime_type,
594 bool no_display,
595 const std::string& run_on_os_login_mode,
596 std::set<web_app::DesktopActionInfo> action_info) {
pmonette9fa59e882016-02-10 00:12:19597 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
Mike Jacksonb3c3425d2021-03-10 00:04:51598 url, extension_id, profile_path, run_on_os_login_mode);
[email protected]2164e512014-01-22 09:32:10599 cmd_line.SetProgram(chrome_exe_path);
600 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
Jay Harris05b50bd2019-10-30 01:48:38601 icon_name, categories, mime_type,
Mandy Chend57606f2021-08-31 17:49:10602 no_display, std::move(action_info));
[email protected]2164e512014-01-22 09:32:10603}
604
605std::string GetDesktopFileContentsForCommand(
avi556c05022014-12-22 23:31:43606 const base::CommandLine& command_line,
[email protected]2164e512014-01-22 09:32:10607 const std::string& app_name,
608 const GURL& url,
Jan Wilken Dörriedec99122021-03-11 18:02:30609 const std::u16string& title,
[email protected]2164e512014-01-22 09:32:10610 const std::string& icon_name,
[email protected]4a7896822014-04-25 23:11:43611 const std::string& categories,
Jay Harris05b50bd2019-10-30 01:48:38612 const std::string& mime_type,
Mandy Chend57606f2021-08-31 17:49:10613 bool no_display,
614 std::set<web_app::DesktopActionInfo> action_info) {
[email protected]73bae9d2014-05-11 00:13:55615#if defined(USE_GLIB)
[email protected]b9eb4e52013-02-05 00:01:49616 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
617 // launchers with an xdg-open shebang. Follow that convention.
618 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20619
[email protected]b96aa932009-08-12 21:34:49620 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20621 GKeyFile* key_file = g_key_file_new();
[email protected]0a96c3f2011-05-11 22:10:20622
[email protected]14fbaed2013-05-02 07:54:02623 // Set keys with fixed values.
624 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
625 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
626 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
[email protected]0a96c3f2011-05-11 22:10:20627
628 // Set the "Name" key.
[email protected]f911df52013-12-24 23:24:23629 std::string final_title = base::UTF16ToUTF8(title);
[email protected]0a96c3f2011-05-11 22:10:20630 // Make sure no endline characters can slip in and possibly introduce
631 // additional lines (like Exec, which makes it a security risk). Also
632 // use the URL as a default when the title is empty.
633 if (final_title.empty() ||
634 final_title.find("\n") != std::string::npos ||
635 final_title.find("\r") != std::string::npos) {
636 final_title = url.spec();
637 }
638 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
639
Jay Harrisf730b6e2019-11-25 23:49:06640 base::CommandLine modified_command_line(command_line);
641
642 // Set the "MimeType" key.
643 if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
644 mime_type.find("\r") == std::string::npos) {
645 g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
646 mime_type.c_str());
647
648 // Some Linux Desktop Environments don't show file handlers unless they
649 // specify where to place file arguments.
650 // Note: We only include this parameter if the application is actually able
651 // to handle files, to prevent it showing up in the list of all applications
652 // which can handle files.
Mike Jacksoncfb1bc12021-03-24 01:22:22653 modified_command_line.AppendArg("%U");
Jay Harrisf730b6e2019-11-25 23:49:06654 }
655
[email protected]0a96c3f2011-05-11 22:10:20656 // Set the "Exec" key.
Jay Harrisf730b6e2019-11-25 23:49:06657 std::string final_path =
658 QuoteCommandLineForDesktopFileExec(modified_command_line);
[email protected]14fbaed2013-05-02 07:54:02659 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
660
[email protected]0a96c3f2011-05-11 22:10:20661 // Set the "Icon" key.
[email protected]14fbaed2013-05-02 07:54:02662 if (!icon_name.empty()) {
[email protected]0a96c3f2011-05-11 22:10:20663 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]14fbaed2013-05-02 07:54:02664 } else {
665 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
666 GetIconName().c_str());
667 }
[email protected]a0b60cfd2011-04-06 18:02:41668
[email protected]4a7896822014-04-25 23:11:43669 // Set the "Categories" key.
670 if (!categories.empty()) {
671 g_key_file_set_string(
672 key_file, kDesktopEntry, "Categories", categories.c_str());
673 }
674
[email protected]d81a63c02013-03-07 08:49:04675 // Set the "NoDisplay" key.
676 if (no_display)
677 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
678
Alexey Baskakovb13491b2018-08-03 07:46:38679 std::string wmclass = GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20680 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
681 wmclass.c_str());
[email protected]a0b60cfd2011-04-06 18:02:41682
Eric Willigers05e08652022-02-08 01:59:09683 SetActionsForDesktopApplication(command_line, key_file,
684 std::move(action_info));
Mandy Chend57606f2021-08-31 17:49:10685
[email protected]14fbaed2013-05-02 07:54:02686 gsize length = 0;
[email protected]0a96c3f2011-05-11 22:10:20687 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
688 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49689 // If strlen(data_dump[0]) == 0, this check will fail.
690 if (data_dump[0] == '\n') {
691 // Older versions of glib produce a leading newline. If this is the case,
692 // remove it to avoid double-newline after the shebang.
693 output_buffer += (data_dump + 1);
694 } else {
695 output_buffer += data_dump;
696 }
[email protected]0a96c3f2011-05-11 22:10:20697 g_free(data_dump);
698 }
699
700 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49701 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55702#else
703 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09704 return std::string();
[email protected]73bae9d2014-05-11 00:13:55705#endif
[email protected]b96aa932009-08-12 21:34:49706}
707
Jan Wilken Dörriedec99122021-03-11 18:02:30708std::string GetDirectoryFileContents(const std::u16string& title,
[email protected]a3c25952013-05-02 13:16:06709 const std::string& icon_name) {
[email protected]73bae9d2014-05-11 00:13:55710#if defined(USE_GLIB)
[email protected]a3c25952013-05-02 13:16:06711 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
712 GKeyFile* key_file = g_key_file_new();
713
714 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
715 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
[email protected]f911df52013-12-24 23:24:23716 std::string final_title = base::UTF16ToUTF8(title);
[email protected]a3c25952013-05-02 13:16:06717 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
718 if (!icon_name.empty()) {
719 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
720 } else {
721 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
722 GetIconName().c_str());
723 }
724
725 gsize length = 0;
726 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
727 std::string output_buffer;
728 if (data_dump) {
729 // If strlen(data_dump[0]) == 0, this check will fail.
730 if (data_dump[0] == '\n') {
731 // Older versions of glib produce a leading newline. If this is the case,
732 // remove it to avoid double-newline after the shebang.
733 output_buffer += (data_dump + 1);
734 } else {
735 output_buffer += data_dump;
736 }
737 g_free(data_dump);
738 }
739
740 g_key_file_free(key_file);
741 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55742#else
743 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09744 return std::string();
[email protected]73bae9d2014-05-11 00:13:55745#endif
[email protected]a3c25952013-05-02 13:16:06746}
747
Robert Woods954a2182020-03-20 06:08:27748base::FilePath GetMimeTypesRegistrationFilename(
749 const base::FilePath& profile_path,
750 const web_app::AppId& app_id) {
751 DCHECK(!profile_path.empty() && !app_id.empty());
752
753 // Use a prefix to clearly group files created by Chrome.
754 std::string filename = base::StringPrintf(
755 "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
756 profile_path.BaseName().value().c_str(), ".xml");
757
758 // Replace illegal characters and spaces in |filename|.
759 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
760 base::ReplaceChars(filename, " ", "_", &filename);
761
762 return base::FilePath(filename);
763}
764
765std::string GetMimeTypesRegistrationFileContents(
766 const apps::FileHandlers& file_handlers) {
Evan Stadeb8f43a42021-09-14 16:25:14767 XmlWriter writer;
768
769 writer.StartWriting();
770 writer.StartElement("mime-info");
771 writer.AddAttribute("xmlns",
772 "http://www.freedesktop.org/standards/shared-mime-info");
Robert Woods954a2182020-03-20 06:08:27773
774 for (const auto& file_handler : file_handlers) {
775 for (const auto& accept_entry : file_handler.accept) {
Evan Stadeb8f43a42021-09-14 16:25:14776 writer.StartElement("mime-type");
777 writer.AddAttribute("type", accept_entry.mime_type);
778
779 if (!file_handler.display_name.empty()) {
780 writer.WriteElement("comment",
781 base::UTF16ToUTF8(file_handler.display_name));
782 }
783 for (const auto& file_extension : accept_entry.file_extensions) {
784 writer.StartElement("glob");
785 writer.AddAttribute("pattern", "*" + file_extension);
786 writer.EndElement(); // "glob"
787 }
788 writer.EndElement(); // "mime-type"
Robert Woods954a2182020-03-20 06:08:27789 }
790 }
791
Evan Stadeb8f43a42021-09-14 16:25:14792 writer.EndElement(); // "mime-info"
793 writer.StopWriting();
794 return writer.GetWrittenString();
Robert Woods954a2182020-03-20 06:08:27795}
796
[email protected]06bfeb12014-05-27 14:00:09797} // namespace shell_integration_linux
Alexey Baskakovb13491b2018-08-03 07:46:38798
799namespace shell_integration {
800
801bool SetAsDefaultBrowser() {
802 return shell_integration_linux::SetDefaultWebClient(std::string());
803}
804
Avi Drissman68395fd72023-01-04 19:52:37805bool SetAsDefaultClientForScheme(const std::string& scheme) {
806 return shell_integration_linux::SetDefaultWebClient(scheme);
Alexey Baskakovb13491b2018-08-03 07:46:38807}
808
Thiago Perrottab7a0c46b2022-10-28 18:40:34809DefaultWebClientSetPermission
810GetPlatformSpecificDefaultWebClientSetPermission() {
Alexey Baskakovb13491b2018-08-03 07:46:38811 return SET_DEFAULT_UNATTENDED;
812}
813
Avi Drissman68395fd72023-01-04 19:52:37814std::u16string GetApplicationNameForScheme(const GURL& url) {
Mike Jackson0cb0ba72022-09-26 23:22:21815 std::unique_ptr<base::Environment> env(base::Environment::Create());
816
817 std::string desktop_file_contents;
818 std::string application_name;
819 base::FilePath desktop_filepath =
820 shell_integration_linux::GetDesktopFileForDefaultSchemeHandler(env.get(),
821 url);
822 if (shell_integration_linux::GetExistingShortcutContents(
823 env.get(), desktop_filepath, &desktop_file_contents)) {
824 application_name =
825 shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
826 "Name", desktop_file_contents);
827 }
828
829 return application_name.empty() ? u"xdg-open"
830 : base::ASCIIToUTF16(application_name);
Alexey Baskakovb13491b2018-08-03 07:46:38831}
832
833DefaultWebClientState GetDefaultBrowser() {
834 return shell_integration_linux::GetIsDefaultWebClient(std::string());
835}
836
837bool IsFirefoxDefaultBrowser() {
838 std::vector<std::string> argv;
839 argv.push_back(shell_integration_linux::kXdgSettings);
840 argv.push_back("get");
841 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
842
843 std::string browser;
844 // We don't care about the return value here.
845 base::GetAppOutput(base::CommandLine(argv), &browser);
846 return browser.find("irefox") != std::string::npos;
847}
848
Avi Drissman68395fd72023-01-04 19:52:37849DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) {
850 return shell_integration_linux::GetIsDefaultWebClient(scheme);
Alexey Baskakovb13491b2018-08-03 07:46:38851}
852
853} // namespace shell_integration