blob: 187bf63f782fa8399899dfbe3a3c1fcde4a4b5fc [file] [log] [blame]
[email protected]a82af392012-02-24 04:40:201// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[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"
Alexander Dunaev2193149f2022-01-05 18:13:4846#include "chrome/browser/web_applications/web_app_helpers.h"
Song Fangzhencda4af62021-09-09 05:24:0247#include "chrome/browser/web_applications/web_app_shortcut.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
114// If |protocol| is empty this function sets Chrome as the default browser,
115// otherwise it sets Chrome as the default handler application for |protocol|.
116bool SetDefaultWebClient(const std::string& protocol) {
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");
125 if (protocol.empty()) {
126 argv.push_back(kXdgSettingsDefaultBrowser);
127 } else {
128 argv.push_back(kXdgSettingsDefaultSchemeHandler);
129 argv.push_back(protocol);
130 }
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
thomasanderson8258b1c52017-03-16 00:04:20145// If |protocol| is empty this function checks if Chrome is the default browser,
146// otherwise it checks if Chrome is the default handler application for
147// |protocol|.
Alexey Baskakovb13491b2018-08-03 07:46:38148shell_integration::DefaultWebClientState GetIsDefaultWebClient(
149 const std::string& protocol) {
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");
pmonette9fa59e882016-02-10 00:12:19161 if (protocol.empty()) {
162 argv.push_back(kXdgSettingsDefaultBrowser);
163 } else {
164 argv.push_back(kXdgSettingsDefaultSchemeHandler);
165 argv.push_back(protocol);
166 }
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
Alexey Baskakovf302efe2018-07-28 02:02:32296} // namespace
297
Alexey Baskakovb13491b2018-08-03 07:46:38298// Allows LaunchXdgUtility to join a process.
299// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
300class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
301 : public base::ScopedAllowBaseSyncPrimitives {};
302
303bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
304 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
305 // files on top of originals after making changes to them. In the event that
306 // the original files are owned by another user (e.g. root, which can happen
307 // if they are updated within sudo), mv will prompt the user to confirm if
308 // standard input is a terminal (otherwise it just does it). So make sure it's
309 // not, to avoid locking everything up waiting for mv.
310 *exit_code = EXIT_FAILURE;
311 int devnull = open("/dev/null", O_RDONLY);
312 if (devnull < 0)
313 return false;
314
315 base::LaunchOptions options;
316 options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
317 base::Process process = base::LaunchProcess(argv, options);
318 close(devnull);
319 if (!process.IsValid())
320 return false;
321 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
322 return process.WaitForExit(exit_code);
323}
324
325std::string GetWMClassFromAppName(std::string app_name) {
326 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
327 base::TrimString(app_name, "_", &app_name);
328 return app_name;
329}
330
Alexander Dunaev2193149f2022-01-05 18:13:48331std::string GetXdgAppIdForWebApp(std::string app_name,
332 const base::FilePath& profile_path) {
333 if (base::StartsWith(app_name, web_app::kCrxAppPrefix))
334 app_name = app_name.substr(strlen(web_app::kCrxAppPrefix));
335 return GetDesktopBaseName(
336 web_app::GetAppShortcutFilename(profile_path, app_name).AsUTF8Unsafe());
337}
338
Alexey Baskakovf302efe2018-07-28 02:02:32339base::FilePath GetDataWriteLocation(base::Environment* env) {
340 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
341}
342
343std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
Etienne Bergeron436d42212019-02-26 17:15:12344 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
345 base::BlockingType::MAY_BLOCK);
Alexey Baskakovf302efe2018-07-28 02:02:32346
347 std::vector<base::FilePath> search_paths;
348 base::FilePath write_location = GetDataWriteLocation(env);
349 search_paths.push_back(write_location);
350
351 std::string xdg_data_dirs;
352 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
353 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
354 while (tokenizer.GetNext()) {
Daniel Cheng04a05fd2021-04-19 17:18:52355 search_paths.emplace_back(tokenizer.token_piece());
Alexey Baskakovf302efe2018-07-28 02:02:32356 }
357 } else {
358 search_paths.push_back(base::FilePath("/usr/local/share"));
359 search_paths.push_back(base::FilePath("/usr/share"));
360 }
361
362 return search_paths;
363}
364
365namespace internal {
366
[email protected]d81a63c02013-03-07 08:49:04367// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
368// file, given in |shortcut_contents|. If the key is not found, returns false.
369bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
[email protected]73bae9d2014-05-11 00:13:55370#if defined(USE_GLIB)
[email protected]d81a63c02013-03-07 08:49:04371 // An empty file causes a crash with glib <= 2.32, so special case here.
372 if (shortcut_contents.empty())
373 return false;
374
375 GKeyFile* key_file = g_key_file_new();
376 GError* err = NULL;
377 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
378 shortcut_contents.size(), G_KEY_FILE_NONE,
379 &err)) {
380 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
381 g_error_free(err);
382 g_key_file_free(key_file);
383 return false;
384 }
385
386 bool nodisplay = false;
387 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
388 "NoDisplay", &err);
389 if (nodisplay_c_string) {
390 if (!g_strcmp0(nodisplay_c_string, "true"))
391 nodisplay = true;
392 g_free(nodisplay_c_string);
[email protected]1381af52013-11-01 19:47:32393 } else {
394 g_error_free(err);
[email protected]d81a63c02013-03-07 08:49:04395 }
396
397 g_key_file_free(key_file);
398 return nodisplay;
[email protected]73bae9d2014-05-11 00:13:55399#else
400 NOTIMPLEMENTED();
401 return false;
402#endif
[email protected]d81a63c02013-03-07 08:49:04403}
404
[email protected]fcd21d322013-06-27 12:35:56405// Gets the path to the Chrome executable or wrapper script.
thestigd2b1fcf2015-01-21 22:11:49406// Returns an empty path if the executable path could not be found, which should
407// never happen.
[email protected]fcd21d322013-06-27 12:35:56408base::FilePath GetChromeExePath() {
409 // Try to get the name of the wrapper script that launched Chrome.
dcheng4af48582016-04-19 00:29:35410 std::unique_ptr<base::Environment> environment(base::Environment::Create());
[email protected]fcd21d322013-06-27 12:35:56411 std::string wrapper_script;
thestigd2b1fcf2015-01-21 22:11:49412 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
[email protected]fcd21d322013-06-27 12:35:56413 return base::FilePath(wrapper_script);
[email protected]fcd21d322013-06-27 12:35:56414
415 // Just return the name of the executable path for Chrome.
416 base::FilePath chrome_exe_path;
Avi Drissman9098f9002018-05-04 00:11:52417 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
[email protected]fcd21d322013-06-27 12:35:56418 return chrome_exe_path;
419}
420
thomasanderson12d87582016-07-29 21:17:41421std::string GetProgramClassName(const base::CommandLine& command_line,
422 const std::string& desktop_file_name) {
Alexey Baskakovb13491b2018-08-03 07:46:38423 std::string class_name = GetDesktopBaseName(desktop_file_name);
thomasanderson12d87582016-07-29 21:17:41424 std::string user_data_dir =
425 command_line.GetSwitchValueNative(switches::kUserDataDir);
426 // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
427 // class name to "Chrome (/tmp/my-user-data)". The class name will show up in
428 // the alt-tab list in gnome-shell if you're running a binary that doesn't
429 // have a matching .desktop file.
430 return user_data_dir.empty()
431 ? class_name
432 : class_name + " (" + user_data_dir + ")";
433}
434
435std::string GetProgramClassClass(const base::CommandLine& command_line,
436 const std::string& desktop_file_name) {
437 if (command_line.HasSwitch(switches::kWmClass))
438 return command_line.GetSwitchValueASCII(switches::kWmClass);
Alexander Dunaev66b50ba2021-08-10 04:11:46439 std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
Maksim Sisov213ddfa2021-09-30 08:18:56440 if (auto* platform_utils =
441 ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
442 return platform_utils->GetWmWindowClass(desktop_base_name);
thomasanderson12d87582016-07-29 21:17:41443 }
Alexander Dunaev66b50ba2021-08-10 04:11:46444 if (!desktop_base_name.empty()) {
445 // Capitalize the first character like gtk does.
446 desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]);
447 }
448 return desktop_base_name;
thomasanderson12d87582016-07-29 21:17:41449}
450
451} // namespace internal
452
[email protected]f93a77452013-09-02 05:26:35453std::string GetProgramClassName() {
dcheng4af48582016-04-19 00:29:35454 std::unique_ptr<base::Environment> env(base::Environment::Create());
thomasanderson12d87582016-07-29 21:17:41455 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45456 chrome::GetDesktopName(env.get()));
thomasanderson12d87582016-07-29 21:17:41457}
458
459std::string GetProgramClassClass() {
460 std::unique_ptr<base::Environment> env(base::Environment::Create());
461 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45462 chrome::GetDesktopName(env.get()));
[email protected]98566d7a2012-04-17 00:28:56463}
464
[email protected]14fbaed2013-05-02 07:54:02465std::string GetIconName() {
Nico Webereaa08412019-08-14 01:24:37466#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02467 return "google-chrome";
Nico Weber897593f2019-07-25 23:17:55468#else // BUILDFLAG(CHROMIUM_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02469 return "chromium-browser";
470#endif
471}
472
[email protected]d81a63c02013-03-07 08:49:04473bool GetExistingShortcutContents(base::Environment* env,
474 const base::FilePath& desktop_filename,
475 std::string* output) {
Etienne Bergeron436d42212019-02-26 17:15:12476 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
477 base::BlockingType::MAY_BLOCK);
[email protected]620942e2010-02-16 10:12:12478
[email protected]111f0282013-08-05 10:09:29479 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
[email protected]b96aa932009-08-12 21:34:49480
[email protected]650b2d52013-02-10 03:41:45481 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49482 i != search_paths.end(); ++i) {
[email protected]d81a63c02013-03-07 08:49:04483 base::FilePath path = i->Append("applications").Append(desktop_filename);
484 VLOG(1) << "Looking for desktop file in " << path.value();
[email protected]7567484142013-07-11 17:36:07485 if (base::PathExists(path)) {
[email protected]d81a63c02013-03-07 08:49:04486 VLOG(1) << "Found desktop file at " << path.value();
[email protected]82f84b92013-08-30 18:23:50487 return base::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12488 }
[email protected]b96aa932009-08-12 21:34:49489 }
490
491 return false;
492}
493
[email protected]650b2d52013-02-10 03:41:45494base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44495 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12496 std::string filename =
[email protected]4f260d02010-12-23 18:35:42497 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]6bc03de2014-08-07 23:59:15498 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49499
[email protected]650b2d52013-02-10 03:41:45500 base::FilePath desktop_path;
Avi Drissman9098f9002018-05-04 00:11:52501 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45502 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10503
[email protected]650b2d52013-02-10 03:41:45504 base::FilePath filepath = desktop_path.Append(filename);
505 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10506 for (size_t i = 1; i < 100; ++i) {
[email protected]7567484142013-07-11 17:36:07507 if (base::PathExists(base::FilePath(alternative_filepath))) {
[email protected]650b2d52013-02-10 03:41:45508 alternative_filepath = base::FilePath(
Brett Wilson5accd242017-11-30 22:07:32509 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10510 } else {
[email protected]650b2d52013-02-10 03:41:45511 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10512 }
513 }
514
[email protected]650b2d52013-02-10 03:41:45515 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49516}
517
[email protected]111f0282013-08-05 10:09:29518std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
519 const base::FilePath& profile_path,
520 const base::FilePath& directory) {
Etienne Bergeron436d42212019-02-26 17:15:12521 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
522 base::BlockingType::MAY_BLOCK);
thestigd2b1fcf2015-01-21 22:11:49523
[email protected]111f0282013-08-05 10:09:29524 // Use a prefix, because xdg-desktop-menu requires it.
525 std::string prefix(chrome::kBrowserProcessExecutableName);
526 prefix.append("-");
527 std::string suffix("-");
528 suffix.append(profile_path.BaseName().value());
[email protected]6bc03de2014-08-07 23:59:15529 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
[email protected]111f0282013-08-05 10:09:29530 // Spaces in filenames break xdg-desktop-menu
531 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
[email protected]466c9862013-12-03 22:05:28532 base::ReplaceChars(suffix, " ", "_", &suffix);
[email protected]111f0282013-08-05 10:09:29533 std::string glob = prefix + "*" + suffix + ".desktop";
534
535 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
536 glob);
537 base::FilePath shortcut_file = files.Next();
538 std::vector<base::FilePath> shortcut_paths;
539 while (!shortcut_file.empty()) {
540 shortcut_paths.push_back(shortcut_file.BaseName());
541 shortcut_file = files.Next();
542 }
543 return shortcut_paths;
544}
545
Mandy Chend57606f2021-08-31 17:49:10546std::string GetDesktopFileContents(
547 const base::FilePath& chrome_exe_path,
548 const std::string& app_name,
549 const GURL& url,
550 const std::string& extension_id,
551 const std::u16string& title,
552 const std::string& icon_name,
553 const base::FilePath& profile_path,
554 const std::string& categories,
555 const std::string& mime_type,
556 bool no_display,
557 const std::string& run_on_os_login_mode,
558 std::set<web_app::DesktopActionInfo> action_info) {
pmonette9fa59e882016-02-10 00:12:19559 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
Mike Jacksonb3c3425d2021-03-10 00:04:51560 url, extension_id, profile_path, run_on_os_login_mode);
[email protected]2164e512014-01-22 09:32:10561 cmd_line.SetProgram(chrome_exe_path);
562 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
Jay Harris05b50bd2019-10-30 01:48:38563 icon_name, categories, mime_type,
Mandy Chend57606f2021-08-31 17:49:10564 no_display, std::move(action_info));
[email protected]2164e512014-01-22 09:32:10565}
566
567std::string GetDesktopFileContentsForCommand(
avi556c05022014-12-22 23:31:43568 const base::CommandLine& command_line,
[email protected]2164e512014-01-22 09:32:10569 const std::string& app_name,
570 const GURL& url,
Jan Wilken Dörriedec99122021-03-11 18:02:30571 const std::u16string& title,
[email protected]2164e512014-01-22 09:32:10572 const std::string& icon_name,
[email protected]4a7896822014-04-25 23:11:43573 const std::string& categories,
Jay Harris05b50bd2019-10-30 01:48:38574 const std::string& mime_type,
Mandy Chend57606f2021-08-31 17:49:10575 bool no_display,
576 std::set<web_app::DesktopActionInfo> action_info) {
[email protected]73bae9d2014-05-11 00:13:55577#if defined(USE_GLIB)
[email protected]b9eb4e52013-02-05 00:01:49578 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
579 // launchers with an xdg-open shebang. Follow that convention.
580 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20581
[email protected]b96aa932009-08-12 21:34:49582 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20583 GKeyFile* key_file = g_key_file_new();
[email protected]0a96c3f2011-05-11 22:10:20584
[email protected]14fbaed2013-05-02 07:54:02585 // Set keys with fixed values.
586 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
587 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
588 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
[email protected]0a96c3f2011-05-11 22:10:20589
590 // Set the "Name" key.
[email protected]f911df52013-12-24 23:24:23591 std::string final_title = base::UTF16ToUTF8(title);
[email protected]0a96c3f2011-05-11 22:10:20592 // Make sure no endline characters can slip in and possibly introduce
593 // additional lines (like Exec, which makes it a security risk). Also
594 // use the URL as a default when the title is empty.
595 if (final_title.empty() ||
596 final_title.find("\n") != std::string::npos ||
597 final_title.find("\r") != std::string::npos) {
598 final_title = url.spec();
599 }
600 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
601
Jay Harrisf730b6e2019-11-25 23:49:06602 base::CommandLine modified_command_line(command_line);
603
604 // Set the "MimeType" key.
605 if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
606 mime_type.find("\r") == std::string::npos) {
607 g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
608 mime_type.c_str());
609
610 // Some Linux Desktop Environments don't show file handlers unless they
611 // specify where to place file arguments.
612 // Note: We only include this parameter if the application is actually able
613 // to handle files, to prevent it showing up in the list of all applications
614 // which can handle files.
Mike Jacksoncfb1bc12021-03-24 01:22:22615 modified_command_line.AppendArg("%U");
Jay Harrisf730b6e2019-11-25 23:49:06616 }
617
[email protected]0a96c3f2011-05-11 22:10:20618 // Set the "Exec" key.
Jay Harrisf730b6e2019-11-25 23:49:06619 std::string final_path =
620 QuoteCommandLineForDesktopFileExec(modified_command_line);
[email protected]14fbaed2013-05-02 07:54:02621 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
622
[email protected]0a96c3f2011-05-11 22:10:20623 // Set the "Icon" key.
[email protected]14fbaed2013-05-02 07:54:02624 if (!icon_name.empty()) {
[email protected]0a96c3f2011-05-11 22:10:20625 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]14fbaed2013-05-02 07:54:02626 } else {
627 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
628 GetIconName().c_str());
629 }
[email protected]a0b60cfd2011-04-06 18:02:41630
[email protected]4a7896822014-04-25 23:11:43631 // Set the "Categories" key.
632 if (!categories.empty()) {
633 g_key_file_set_string(
634 key_file, kDesktopEntry, "Categories", categories.c_str());
635 }
636
[email protected]d81a63c02013-03-07 08:49:04637 // Set the "NoDisplay" key.
638 if (no_display)
639 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
640
Alexey Baskakovb13491b2018-08-03 07:46:38641 std::string wmclass = GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20642 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
643 wmclass.c_str());
[email protected]a0b60cfd2011-04-06 18:02:41644
Eric Willigers05e08652022-02-08 01:59:09645 SetActionsForDesktopApplication(command_line, key_file,
646 std::move(action_info));
Mandy Chend57606f2021-08-31 17:49:10647
[email protected]14fbaed2013-05-02 07:54:02648 gsize length = 0;
[email protected]0a96c3f2011-05-11 22:10:20649 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
650 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49651 // If strlen(data_dump[0]) == 0, this check will fail.
652 if (data_dump[0] == '\n') {
653 // Older versions of glib produce a leading newline. If this is the case,
654 // remove it to avoid double-newline after the shebang.
655 output_buffer += (data_dump + 1);
656 } else {
657 output_buffer += data_dump;
658 }
[email protected]0a96c3f2011-05-11 22:10:20659 g_free(data_dump);
660 }
661
662 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49663 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55664#else
665 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09666 return std::string();
[email protected]73bae9d2014-05-11 00:13:55667#endif
[email protected]b96aa932009-08-12 21:34:49668}
669
Jan Wilken Dörriedec99122021-03-11 18:02:30670std::string GetDirectoryFileContents(const std::u16string& title,
[email protected]a3c25952013-05-02 13:16:06671 const std::string& icon_name) {
[email protected]73bae9d2014-05-11 00:13:55672#if defined(USE_GLIB)
[email protected]a3c25952013-05-02 13:16:06673 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
674 GKeyFile* key_file = g_key_file_new();
675
676 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
677 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
[email protected]f911df52013-12-24 23:24:23678 std::string final_title = base::UTF16ToUTF8(title);
[email protected]a3c25952013-05-02 13:16:06679 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
680 if (!icon_name.empty()) {
681 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
682 } else {
683 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
684 GetIconName().c_str());
685 }
686
687 gsize length = 0;
688 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
689 std::string output_buffer;
690 if (data_dump) {
691 // If strlen(data_dump[0]) == 0, this check will fail.
692 if (data_dump[0] == '\n') {
693 // Older versions of glib produce a leading newline. If this is the case,
694 // remove it to avoid double-newline after the shebang.
695 output_buffer += (data_dump + 1);
696 } else {
697 output_buffer += data_dump;
698 }
699 g_free(data_dump);
700 }
701
702 g_key_file_free(key_file);
703 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55704#else
705 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09706 return std::string();
[email protected]73bae9d2014-05-11 00:13:55707#endif
[email protected]a3c25952013-05-02 13:16:06708}
709
Robert Woods954a2182020-03-20 06:08:27710base::FilePath GetMimeTypesRegistrationFilename(
711 const base::FilePath& profile_path,
712 const web_app::AppId& app_id) {
713 DCHECK(!profile_path.empty() && !app_id.empty());
714
715 // Use a prefix to clearly group files created by Chrome.
716 std::string filename = base::StringPrintf(
717 "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
718 profile_path.BaseName().value().c_str(), ".xml");
719
720 // Replace illegal characters and spaces in |filename|.
721 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
722 base::ReplaceChars(filename, " ", "_", &filename);
723
724 return base::FilePath(filename);
725}
726
727std::string GetMimeTypesRegistrationFileContents(
728 const apps::FileHandlers& file_handlers) {
Evan Stadeb8f43a42021-09-14 16:25:14729 XmlWriter writer;
730
731 writer.StartWriting();
732 writer.StartElement("mime-info");
733 writer.AddAttribute("xmlns",
734 "http://www.freedesktop.org/standards/shared-mime-info");
Robert Woods954a2182020-03-20 06:08:27735
736 for (const auto& file_handler : file_handlers) {
737 for (const auto& accept_entry : file_handler.accept) {
Evan Stadeb8f43a42021-09-14 16:25:14738 writer.StartElement("mime-type");
739 writer.AddAttribute("type", accept_entry.mime_type);
740
741 if (!file_handler.display_name.empty()) {
742 writer.WriteElement("comment",
743 base::UTF16ToUTF8(file_handler.display_name));
744 }
745 for (const auto& file_extension : accept_entry.file_extensions) {
746 writer.StartElement("glob");
747 writer.AddAttribute("pattern", "*" + file_extension);
748 writer.EndElement(); // "glob"
749 }
750 writer.EndElement(); // "mime-type"
Robert Woods954a2182020-03-20 06:08:27751 }
752 }
753
Evan Stadeb8f43a42021-09-14 16:25:14754 writer.EndElement(); // "mime-info"
755 writer.StopWriting();
756 return writer.GetWrittenString();
Robert Woods954a2182020-03-20 06:08:27757}
758
[email protected]06bfeb12014-05-27 14:00:09759} // namespace shell_integration_linux
Alexey Baskakovb13491b2018-08-03 07:46:38760
761namespace shell_integration {
762
763bool SetAsDefaultBrowser() {
764 return shell_integration_linux::SetDefaultWebClient(std::string());
765}
766
767bool SetAsDefaultProtocolClient(const std::string& protocol) {
768 return shell_integration_linux::SetDefaultWebClient(protocol);
769}
770
771DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
772 return SET_DEFAULT_UNATTENDED;
773}
774
Jan Wilken Dörriedec99122021-03-11 18:02:30775std::u16string GetApplicationNameForProtocol(const GURL& url) {
Jan Wilken Dörrie78e88d82e2021-03-23 15:24:22776 return u"xdg-open";
Alexey Baskakovb13491b2018-08-03 07:46:38777}
778
779DefaultWebClientState GetDefaultBrowser() {
780 return shell_integration_linux::GetIsDefaultWebClient(std::string());
781}
782
783bool IsFirefoxDefaultBrowser() {
784 std::vector<std::string> argv;
785 argv.push_back(shell_integration_linux::kXdgSettings);
786 argv.push_back("get");
787 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
788
789 std::string browser;
790 // We don't care about the return value here.
791 base::GetAppOutput(base::CommandLine(argv), &browser);
792 return browser.find("irefox") != std::string::npos;
793}
794
795DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
796 return shell_integration_linux::GetIsDefaultWebClient(protocol);
797}
798
799} // namespace shell_integration