blob: 85eaf010091b54cbb12672d84d36c831cc2d871e [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"
Mandy Chend57606f2021-08-31 17:49:1046#include "chrome/browser/web_applications/components/web_app_shortcut.h"
Scott Violet6200d332018-02-23 21:29:2347#include "chrome/common/buildflags.h"
sdefresne9fb67692015-08-03 18:48:2248#include "chrome/common/channel_info.h"
[email protected]42896802009-08-28 23:39:4449#include "chrome/common/chrome_constants.h"
[email protected]7199367d2014-01-20 04:06:2150#include "chrome/common/chrome_switches.h"
thestig4a9b0ef2016-08-29 08:22:1251#include "chrome/grit/chrome_unscaled_resources.h"
sdefresne9fb67692015-08-03 18:48:2252#include "components/version_info/version_info.h"
[email protected]7199367d2014-01-20 04:06:2153#include "ui/base/resource/resource_bundle.h"
[email protected]08a139d2013-04-11 03:32:5454#include "ui/gfx/image/image_family.h"
[email protected]761fa4702013-07-02 15:25:1555#include "url/gurl.h"
[email protected]ef525cc2009-07-10 17:08:1656
Lei Zhange711d89c2018-09-10 18:48:0057#if defined(USE_GLIB)
58#include <glib.h>
59#endif
60
Alexander Dunaev66b50ba2021-08-10 04:11:4661#if defined(USE_OZONE)
62#include "ui/base/ui_base_features.h"
63#include "ui/ozone/public/ozone_platform.h"
64#include "ui/ozone/public/platform_utils.h"
65#endif
66
Alexey Baskakovb13491b2018-08-03 07:46:3867namespace shell_integration_linux {
Alexey Baskakovf302efe2018-07-28 02:02:3268
pmonette9fa59e882016-02-10 00:12:1969const char kXdgSettings[] = "xdg-settings";
70const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
71const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
72
73// Utility function to get the path to the version of a script shipped with
74// Chrome. |script| gives the name of the script. |chrome_version| returns the
75// path to the Chrome version of the script, and the return value of the
76// function is true if the function is successful and the Chrome version is
77// not the script found on the PATH.
78bool GetChromeVersionOfScript(const std::string& script,
79 std::string* chrome_version) {
80 // Get the path to the Chrome version.
81 base::FilePath chrome_dir;
Avi Drissman9098f9002018-05-04 00:11:5282 if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
pmonette9fa59e882016-02-10 00:12:1983 return false;
84
85 base::FilePath chrome_version_path = chrome_dir.Append(script);
86 *chrome_version = chrome_version_path.value();
87
88 // Check if this is different to the one on path.
89 std::vector<std::string> argv;
90 argv.push_back("which");
91 argv.push_back(script);
92 std::string path_version;
93 if (base::GetAppOutput(base::CommandLine(argv), &path_version)) {
94 // Remove trailing newline
95 path_version.erase(path_version.length() - 1, 1);
96 base::FilePath path_version_path(path_version);
97 return (chrome_version_path != path_version_path);
98 }
99 return false;
100}
101
102// Value returned by xdg-settings if it can't understand our request.
103const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
104
105// We delegate the difficulty of setting the default browser and default url
106// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
107
108// When calling this script we first try to use the script on PATH. If that
109// fails we then try to use the script that we have included. This gives
110// scripts on the system priority over ours, as distribution vendors may have
111// tweaked the script, but still allows our copy to be used if the script on the
112// system fails, as the system copy may be missing capabilities of the Chrome
113// copy.
114
115// If |protocol| is empty this function sets Chrome as the default browser,
116// otherwise it sets Chrome as the default handler application for |protocol|.
117bool SetDefaultWebClient(const std::string& protocol) {
Yuta Hijikata235fc62b2020-12-08 03:48:32118#if BUILDFLAG(IS_CHROMEOS_ASH)
pmonette9fa59e882016-02-10 00:12:19119 return true;
120#else
dcheng4af48582016-04-19 00:29:35121 std::unique_ptr<base::Environment> env(base::Environment::Create());
pmonette9fa59e882016-02-10 00:12:19122
123 std::vector<std::string> argv;
124 argv.push_back(kXdgSettings);
125 argv.push_back("set");
126 if (protocol.empty()) {
127 argv.push_back(kXdgSettingsDefaultBrowser);
128 } else {
129 argv.push_back(kXdgSettingsDefaultSchemeHandler);
130 argv.push_back(protocol);
131 }
Tom Andersonb8a86032019-10-09 20:12:45132 argv.push_back(chrome::GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19133
134 int exit_code;
135 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
136 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
137 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
138 ran_ok = LaunchXdgUtility(argv, &exit_code);
139 }
140 }
141
142 return ran_ok && exit_code == EXIT_SUCCESS;
143#endif
144}
145
thomasanderson8258b1c52017-03-16 00:04:20146// If |protocol| is empty this function checks if Chrome is the default browser,
147// otherwise it checks if Chrome is the default handler application for
148// |protocol|.
Alexey Baskakovb13491b2018-08-03 07:46:38149shell_integration::DefaultWebClientState GetIsDefaultWebClient(
150 const std::string& protocol) {
Yuta Hijikata235fc62b2020-12-08 03:48:32151#if BUILDFLAG(IS_CHROMEOS_ASH)
Alexey Baskakovb13491b2018-08-03 07:46:38152 return shell_integration::UNKNOWN_DEFAULT;
thomasanderson8258b1c52017-03-16 00:04:20153#else
Etienne Bergeron436d42212019-02-26 17:15:12154 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
155 base::BlockingType::MAY_BLOCK);
thomasanderson8258b1c52017-03-16 00:04:20156
157 std::unique_ptr<base::Environment> env(base::Environment::Create());
158
pmonette9fa59e882016-02-10 00:12:19159 std::vector<std::string> argv;
160 argv.push_back(kXdgSettings);
thomasanderson8258b1c52017-03-16 00:04:20161 argv.push_back("check");
pmonette9fa59e882016-02-10 00:12:19162 if (protocol.empty()) {
163 argv.push_back(kXdgSettingsDefaultBrowser);
164 } else {
165 argv.push_back(kXdgSettingsDefaultSchemeHandler);
166 argv.push_back(protocol);
167 }
Tom Andersonb8a86032019-10-09 20:12:45168 argv.push_back(chrome::GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19169
170 std::string reply;
171 int success_code;
172 bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
173 &success_code);
174 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
175 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
176 ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
177 &success_code);
178 }
179 }
180
thomasanderson8258b1c52017-03-16 00:04:20181 if (!ran_ok || success_code != EXIT_SUCCESS) {
182 // xdg-settings failed: we can't determine or set the default browser.
Alexey Baskakovb13491b2018-08-03 07:46:38183 return shell_integration::UNKNOWN_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19184 }
185
thomasanderson8258b1c52017-03-16 00:04:20186 // Allow any reply that starts with "yes".
187 return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
Alexey Baskakovb13491b2018-08-03 07:46:38188 ? shell_integration::IS_DEFAULT
189 : shell_integration::NOT_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19190#endif
191}
192
thomasanderson12d87582016-07-29 21:17:41193// https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
194// The WM_CLASS property should be set to the same as the *.desktop file without
195// the .desktop extension. We cannot simply use argv[0] in this case, because
196// on the stable channel, the executable name is google-chrome-stable, but the
197// desktop file is google-chrome.desktop.
198std::string GetDesktopBaseName(const std::string& desktop_file_name) {
199 static const char kDesktopExtension[] = ".desktop";
200 if (base::EndsWith(desktop_file_name, kDesktopExtension,
201 base::CompareCase::SENSITIVE)) {
202 return desktop_file_name.substr(
203 0, desktop_file_name.length() - strlen(kDesktopExtension));
204 }
205 return desktop_file_name;
206}
207
pmonette9fa59e882016-02-10 00:12:19208namespace {
209
kalyan.kondapally577803c2014-08-25 20:13:18210#if defined(USE_GLIB)
[email protected]b10392932011-03-08 21:28:14211// Quote a string such that it appears as one verbatim argument for the Exec
212// key in a desktop file.
213std::string QuoteArgForDesktopFileExec(const std::string& arg) {
214 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
215
216 // Quoting is only necessary if the argument has a reserved character.
217 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
218 return arg; // No quoting necessary.
219
220 std::string quoted = "\"";
221 for (size_t i = 0; i < arg.size(); ++i) {
222 // Note that the set of backslashed characters is smaller than the
223 // set of reserved characters.
224 switch (arg[i]) {
225 case '"':
226 case '`':
227 case '$':
228 case '\\':
229 quoted += '\\';
230 break;
231 }
232 quoted += arg[i];
233 }
234 quoted += '"';
235
236 return quoted;
237}
238
[email protected]2164e512014-01-22 09:32:10239// Quote a command line so it is suitable for use as the Exec key in a desktop
240// file. Note: This should be used instead of GetCommandLineString, which does
241// not properly quote the string; this function is designed for the Exec key.
242std::string QuoteCommandLineForDesktopFileExec(
avi556c05022014-12-22 23:31:43243 const base::CommandLine& command_line) {
[email protected]2164e512014-01-22 09:32:10244 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
245
Lei Zhang9b0be802020-05-05 21:11:48246 std::string quoted_path;
avi556c05022014-12-22 23:31:43247 const base::CommandLine::StringVector& argv = command_line.argv();
jdoerrie601c7152018-10-02 23:43:11248 for (auto i = argv.begin(); i != argv.end(); ++i) {
[email protected]2164e512014-01-22 09:32:10249 if (i != argv.begin())
250 quoted_path += " ";
251 quoted_path += QuoteArgForDesktopFileExec(*i);
252 }
253
254 return quoted_path;
255}
Alexey Baskakovf302efe2018-07-28 02:02:32256#endif
[email protected]2164e512014-01-22 09:32:10257
Alexey Baskakovf302efe2018-07-28 02:02:32258#if defined(USE_GLIB)
[email protected]4f0806a72011-09-21 03:08:45259const char kDesktopEntry[] = "Desktop Entry";
[email protected]4f0806a72011-09-21 03:08:45260const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
Mandy Chend57606f2021-08-31 17:49:10261
262void SetActionsForDesktopApplication(
263 const base::CommandLine& command_line,
264 GKeyFile* key_file,
265 std::set<web_app::DesktopActionInfo> action_info) {
266 if (action_info.empty())
267 return;
268
269 std::vector<std::string> action_ids;
270 for (const auto& info : action_info) {
271 action_ids.push_back(info.id);
272 }
273
274 std::string joined_action_ids = base::JoinString(action_ids, ";");
275 g_key_file_set_string(key_file, kDesktopEntry, "Actions",
276 joined_action_ids.c_str());
277
278 for (const auto& info : action_info) {
279 std::string section_title = "Desktop Action " + info.id;
280 g_key_file_set_string(key_file, section_title.c_str(), "Name",
281 info.name.c_str());
282
283 base::CommandLine current_cmd(command_line);
284 current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem,
285 info.exec_launch_url.spec());
286
287 g_key_file_set_string(
288 key_file, section_title.c_str(), "Exec",
289 QuoteCommandLineForDesktopFileExec(current_cmd).c_str());
290 }
291}
kalyan.kondapally577803c2014-08-25 20:13:18292#endif
[email protected]4f0806a72011-09-21 03:08:45293
Alexey Baskakovf302efe2018-07-28 02:02:32294} // namespace
295
Alexey Baskakovb13491b2018-08-03 07:46:38296// Allows LaunchXdgUtility to join a process.
297// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
298class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
299 : public base::ScopedAllowBaseSyncPrimitives {};
300
301bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
302 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
303 // files on top of originals after making changes to them. In the event that
304 // the original files are owned by another user (e.g. root, which can happen
305 // if they are updated within sudo), mv will prompt the user to confirm if
306 // standard input is a terminal (otherwise it just does it). So make sure it's
307 // not, to avoid locking everything up waiting for mv.
308 *exit_code = EXIT_FAILURE;
309 int devnull = open("/dev/null", O_RDONLY);
310 if (devnull < 0)
311 return false;
312
313 base::LaunchOptions options;
314 options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
315 base::Process process = base::LaunchProcess(argv, options);
316 close(devnull);
317 if (!process.IsValid())
318 return false;
319 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
320 return process.WaitForExit(exit_code);
321}
322
323std::string GetWMClassFromAppName(std::string app_name) {
324 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
325 base::TrimString(app_name, "_", &app_name);
326 return app_name;
327}
328
Alexey Baskakovf302efe2018-07-28 02:02:32329base::FilePath GetDataWriteLocation(base::Environment* env) {
330 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
331}
332
333std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
Etienne Bergeron436d42212019-02-26 17:15:12334 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
335 base::BlockingType::MAY_BLOCK);
Alexey Baskakovf302efe2018-07-28 02:02:32336
337 std::vector<base::FilePath> search_paths;
338 base::FilePath write_location = GetDataWriteLocation(env);
339 search_paths.push_back(write_location);
340
341 std::string xdg_data_dirs;
342 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
343 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
344 while (tokenizer.GetNext()) {
Daniel Cheng04a05fd2021-04-19 17:18:52345 search_paths.emplace_back(tokenizer.token_piece());
Alexey Baskakovf302efe2018-07-28 02:02:32346 }
347 } else {
348 search_paths.push_back(base::FilePath("/usr/local/share"));
349 search_paths.push_back(base::FilePath("/usr/share"));
350 }
351
352 return search_paths;
353}
354
355namespace internal {
356
[email protected]d81a63c02013-03-07 08:49:04357// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
358// file, given in |shortcut_contents|. If the key is not found, returns false.
359bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
[email protected]73bae9d2014-05-11 00:13:55360#if defined(USE_GLIB)
[email protected]d81a63c02013-03-07 08:49:04361 // An empty file causes a crash with glib <= 2.32, so special case here.
362 if (shortcut_contents.empty())
363 return false;
364
365 GKeyFile* key_file = g_key_file_new();
366 GError* err = NULL;
367 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
368 shortcut_contents.size(), G_KEY_FILE_NONE,
369 &err)) {
370 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
371 g_error_free(err);
372 g_key_file_free(key_file);
373 return false;
374 }
375
376 bool nodisplay = false;
377 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
378 "NoDisplay", &err);
379 if (nodisplay_c_string) {
380 if (!g_strcmp0(nodisplay_c_string, "true"))
381 nodisplay = true;
382 g_free(nodisplay_c_string);
[email protected]1381af52013-11-01 19:47:32383 } else {
384 g_error_free(err);
[email protected]d81a63c02013-03-07 08:49:04385 }
386
387 g_key_file_free(key_file);
388 return nodisplay;
[email protected]73bae9d2014-05-11 00:13:55389#else
390 NOTIMPLEMENTED();
391 return false;
392#endif
[email protected]d81a63c02013-03-07 08:49:04393}
394
[email protected]fcd21d322013-06-27 12:35:56395// Gets the path to the Chrome executable or wrapper script.
thestigd2b1fcf2015-01-21 22:11:49396// Returns an empty path if the executable path could not be found, which should
397// never happen.
[email protected]fcd21d322013-06-27 12:35:56398base::FilePath GetChromeExePath() {
399 // Try to get the name of the wrapper script that launched Chrome.
dcheng4af48582016-04-19 00:29:35400 std::unique_ptr<base::Environment> environment(base::Environment::Create());
[email protected]fcd21d322013-06-27 12:35:56401 std::string wrapper_script;
thestigd2b1fcf2015-01-21 22:11:49402 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
[email protected]fcd21d322013-06-27 12:35:56403 return base::FilePath(wrapper_script);
[email protected]fcd21d322013-06-27 12:35:56404
405 // Just return the name of the executable path for Chrome.
406 base::FilePath chrome_exe_path;
Avi Drissman9098f9002018-05-04 00:11:52407 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
[email protected]fcd21d322013-06-27 12:35:56408 return chrome_exe_path;
409}
410
thomasanderson12d87582016-07-29 21:17:41411std::string GetProgramClassName(const base::CommandLine& command_line,
412 const std::string& desktop_file_name) {
Alexey Baskakovb13491b2018-08-03 07:46:38413 std::string class_name = GetDesktopBaseName(desktop_file_name);
thomasanderson12d87582016-07-29 21:17:41414 std::string user_data_dir =
415 command_line.GetSwitchValueNative(switches::kUserDataDir);
416 // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
417 // class name to "Chrome (/tmp/my-user-data)". The class name will show up in
418 // the alt-tab list in gnome-shell if you're running a binary that doesn't
419 // have a matching .desktop file.
420 return user_data_dir.empty()
421 ? class_name
422 : class_name + " (" + user_data_dir + ")";
423}
424
425std::string GetProgramClassClass(const base::CommandLine& command_line,
426 const std::string& desktop_file_name) {
427 if (command_line.HasSwitch(switches::kWmClass))
428 return command_line.GetSwitchValueASCII(switches::kWmClass);
Alexander Dunaev66b50ba2021-08-10 04:11:46429 std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
430#if defined(USE_OZONE)
431 if (features::IsUsingOzonePlatform()) {
432 if (auto* platform_utils =
433 ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
434 return platform_utils->GetWmWindowClass(desktop_base_name);
435 }
436 return desktop_base_name;
thomasanderson12d87582016-07-29 21:17:41437 }
Alexander Dunaev66b50ba2021-08-10 04:11:46438#endif
439#if !defined(USE_X11)
440 NOTREACHED();
441#endif
442 if (!desktop_base_name.empty()) {
443 // Capitalize the first character like gtk does.
444 desktop_base_name[0] = base::ToUpperASCII(desktop_base_name[0]);
445 }
446 return desktop_base_name;
thomasanderson12d87582016-07-29 21:17:41447}
448
449} // namespace internal
450
[email protected]f93a77452013-09-02 05:26:35451std::string GetProgramClassName() {
dcheng4af48582016-04-19 00:29:35452 std::unique_ptr<base::Environment> env(base::Environment::Create());
thomasanderson12d87582016-07-29 21:17:41453 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45454 chrome::GetDesktopName(env.get()));
thomasanderson12d87582016-07-29 21:17:41455}
456
457std::string GetProgramClassClass() {
458 std::unique_ptr<base::Environment> env(base::Environment::Create());
459 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
Tom Andersonb8a86032019-10-09 20:12:45460 chrome::GetDesktopName(env.get()));
[email protected]98566d7a2012-04-17 00:28:56461}
462
[email protected]14fbaed2013-05-02 07:54:02463std::string GetIconName() {
Nico Webereaa08412019-08-14 01:24:37464#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02465 return "google-chrome";
Nico Weber897593f2019-07-25 23:17:55466#else // BUILDFLAG(CHROMIUM_BRANDING)
[email protected]14fbaed2013-05-02 07:54:02467 return "chromium-browser";
468#endif
469}
470
[email protected]d81a63c02013-03-07 08:49:04471bool GetExistingShortcutContents(base::Environment* env,
472 const base::FilePath& desktop_filename,
473 std::string* output) {
Etienne Bergeron436d42212019-02-26 17:15:12474 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
475 base::BlockingType::MAY_BLOCK);
[email protected]620942e2010-02-16 10:12:12476
[email protected]111f0282013-08-05 10:09:29477 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
[email protected]b96aa932009-08-12 21:34:49478
[email protected]650b2d52013-02-10 03:41:45479 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49480 i != search_paths.end(); ++i) {
[email protected]d81a63c02013-03-07 08:49:04481 base::FilePath path = i->Append("applications").Append(desktop_filename);
482 VLOG(1) << "Looking for desktop file in " << path.value();
[email protected]7567484142013-07-11 17:36:07483 if (base::PathExists(path)) {
[email protected]d81a63c02013-03-07 08:49:04484 VLOG(1) << "Found desktop file at " << path.value();
[email protected]82f84b92013-08-30 18:23:50485 return base::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12486 }
[email protected]b96aa932009-08-12 21:34:49487 }
488
489 return false;
490}
491
[email protected]650b2d52013-02-10 03:41:45492base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44493 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12494 std::string filename =
[email protected]4f260d02010-12-23 18:35:42495 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]6bc03de2014-08-07 23:59:15496 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49497
[email protected]650b2d52013-02-10 03:41:45498 base::FilePath desktop_path;
Avi Drissman9098f9002018-05-04 00:11:52499 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45500 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10501
[email protected]650b2d52013-02-10 03:41:45502 base::FilePath filepath = desktop_path.Append(filename);
503 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10504 for (size_t i = 1; i < 100; ++i) {
[email protected]7567484142013-07-11 17:36:07505 if (base::PathExists(base::FilePath(alternative_filepath))) {
[email protected]650b2d52013-02-10 03:41:45506 alternative_filepath = base::FilePath(
Brett Wilson5accd242017-11-30 22:07:32507 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10508 } else {
[email protected]650b2d52013-02-10 03:41:45509 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10510 }
511 }
512
[email protected]650b2d52013-02-10 03:41:45513 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49514}
515
[email protected]111f0282013-08-05 10:09:29516std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
517 const base::FilePath& profile_path,
518 const base::FilePath& directory) {
Etienne Bergeron436d42212019-02-26 17:15:12519 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
520 base::BlockingType::MAY_BLOCK);
thestigd2b1fcf2015-01-21 22:11:49521
[email protected]111f0282013-08-05 10:09:29522 // Use a prefix, because xdg-desktop-menu requires it.
523 std::string prefix(chrome::kBrowserProcessExecutableName);
524 prefix.append("-");
525 std::string suffix("-");
526 suffix.append(profile_path.BaseName().value());
[email protected]6bc03de2014-08-07 23:59:15527 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
[email protected]111f0282013-08-05 10:09:29528 // Spaces in filenames break xdg-desktop-menu
529 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
[email protected]466c9862013-12-03 22:05:28530 base::ReplaceChars(suffix, " ", "_", &suffix);
[email protected]111f0282013-08-05 10:09:29531 std::string glob = prefix + "*" + suffix + ".desktop";
532
533 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
534 glob);
535 base::FilePath shortcut_file = files.Next();
536 std::vector<base::FilePath> shortcut_paths;
537 while (!shortcut_file.empty()) {
538 shortcut_paths.push_back(shortcut_file.BaseName());
539 shortcut_file = files.Next();
540 }
541 return shortcut_paths;
542}
543
Mandy Chend57606f2021-08-31 17:49:10544std::string GetDesktopFileContents(
545 const base::FilePath& chrome_exe_path,
546 const std::string& app_name,
547 const GURL& url,
548 const std::string& extension_id,
549 const std::u16string& title,
550 const std::string& icon_name,
551 const base::FilePath& profile_path,
552 const std::string& categories,
553 const std::string& mime_type,
554 bool no_display,
555 const std::string& run_on_os_login_mode,
556 std::set<web_app::DesktopActionInfo> action_info) {
pmonette9fa59e882016-02-10 00:12:19557 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
Mike Jacksonb3c3425d2021-03-10 00:04:51558 url, extension_id, profile_path, run_on_os_login_mode);
[email protected]2164e512014-01-22 09:32:10559 cmd_line.SetProgram(chrome_exe_path);
560 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
Jay Harris05b50bd2019-10-30 01:48:38561 icon_name, categories, mime_type,
Mandy Chend57606f2021-08-31 17:49:10562 no_display, std::move(action_info));
[email protected]2164e512014-01-22 09:32:10563}
564
565std::string GetDesktopFileContentsForCommand(
avi556c05022014-12-22 23:31:43566 const base::CommandLine& command_line,
[email protected]2164e512014-01-22 09:32:10567 const std::string& app_name,
568 const GURL& url,
Jan Wilken Dörriedec99122021-03-11 18:02:30569 const std::u16string& title,
[email protected]2164e512014-01-22 09:32:10570 const std::string& icon_name,
[email protected]4a7896822014-04-25 23:11:43571 const std::string& categories,
Jay Harris05b50bd2019-10-30 01:48:38572 const std::string& mime_type,
Mandy Chend57606f2021-08-31 17:49:10573 bool no_display,
574 std::set<web_app::DesktopActionInfo> action_info) {
[email protected]73bae9d2014-05-11 00:13:55575#if defined(USE_GLIB)
[email protected]b9eb4e52013-02-05 00:01:49576 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
577 // launchers with an xdg-open shebang. Follow that convention.
578 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20579
[email protected]b96aa932009-08-12 21:34:49580 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20581 GKeyFile* key_file = g_key_file_new();
[email protected]0a96c3f2011-05-11 22:10:20582
[email protected]14fbaed2013-05-02 07:54:02583 // Set keys with fixed values.
584 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
585 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
586 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
[email protected]0a96c3f2011-05-11 22:10:20587
588 // Set the "Name" key.
[email protected]f911df52013-12-24 23:24:23589 std::string final_title = base::UTF16ToUTF8(title);
[email protected]0a96c3f2011-05-11 22:10:20590 // Make sure no endline characters can slip in and possibly introduce
591 // additional lines (like Exec, which makes it a security risk). Also
592 // use the URL as a default when the title is empty.
593 if (final_title.empty() ||
594 final_title.find("\n") != std::string::npos ||
595 final_title.find("\r") != std::string::npos) {
596 final_title = url.spec();
597 }
598 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
599
Jay Harrisf730b6e2019-11-25 23:49:06600 base::CommandLine modified_command_line(command_line);
601
602 // Set the "MimeType" key.
603 if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
604 mime_type.find("\r") == std::string::npos) {
605 g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
606 mime_type.c_str());
607
608 // Some Linux Desktop Environments don't show file handlers unless they
609 // specify where to place file arguments.
610 // Note: We only include this parameter if the application is actually able
611 // to handle files, to prevent it showing up in the list of all applications
612 // which can handle files.
Mike Jacksoncfb1bc12021-03-24 01:22:22613 modified_command_line.AppendArg("%U");
Jay Harrisf730b6e2019-11-25 23:49:06614 }
615
[email protected]0a96c3f2011-05-11 22:10:20616 // Set the "Exec" key.
Jay Harrisf730b6e2019-11-25 23:49:06617 std::string final_path =
618 QuoteCommandLineForDesktopFileExec(modified_command_line);
[email protected]14fbaed2013-05-02 07:54:02619 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
620
[email protected]0a96c3f2011-05-11 22:10:20621 // Set the "Icon" key.
[email protected]14fbaed2013-05-02 07:54:02622 if (!icon_name.empty()) {
[email protected]0a96c3f2011-05-11 22:10:20623 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]14fbaed2013-05-02 07:54:02624 } else {
625 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
626 GetIconName().c_str());
627 }
[email protected]a0b60cfd2011-04-06 18:02:41628
[email protected]4a7896822014-04-25 23:11:43629 // Set the "Categories" key.
630 if (!categories.empty()) {
631 g_key_file_set_string(
632 key_file, kDesktopEntry, "Categories", categories.c_str());
633 }
634
[email protected]d81a63c02013-03-07 08:49:04635 // Set the "NoDisplay" key.
636 if (no_display)
637 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
638
Alexey Baskakovb13491b2018-08-03 07:46:38639 std::string wmclass = GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20640 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
641 wmclass.c_str());
[email protected]a0b60cfd2011-04-06 18:02:41642
Mandy Chend57606f2021-08-31 17:49:10643 SetActionsForDesktopApplication(command_line, key_file,
644 std::move(action_info));
645
[email protected]14fbaed2013-05-02 07:54:02646 gsize length = 0;
[email protected]0a96c3f2011-05-11 22:10:20647 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
648 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49649 // If strlen(data_dump[0]) == 0, this check will fail.
650 if (data_dump[0] == '\n') {
651 // Older versions of glib produce a leading newline. If this is the case,
652 // remove it to avoid double-newline after the shebang.
653 output_buffer += (data_dump + 1);
654 } else {
655 output_buffer += data_dump;
656 }
[email protected]0a96c3f2011-05-11 22:10:20657 g_free(data_dump);
658 }
659
660 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49661 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55662#else
663 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09664 return std::string();
[email protected]73bae9d2014-05-11 00:13:55665#endif
[email protected]b96aa932009-08-12 21:34:49666}
667
Jan Wilken Dörriedec99122021-03-11 18:02:30668std::string GetDirectoryFileContents(const std::u16string& title,
[email protected]a3c25952013-05-02 13:16:06669 const std::string& icon_name) {
[email protected]73bae9d2014-05-11 00:13:55670#if defined(USE_GLIB)
[email protected]a3c25952013-05-02 13:16:06671 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
672 GKeyFile* key_file = g_key_file_new();
673
674 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
675 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
[email protected]f911df52013-12-24 23:24:23676 std::string final_title = base::UTF16ToUTF8(title);
[email protected]a3c25952013-05-02 13:16:06677 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
678 if (!icon_name.empty()) {
679 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
680 } else {
681 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
682 GetIconName().c_str());
683 }
684
685 gsize length = 0;
686 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
687 std::string output_buffer;
688 if (data_dump) {
689 // 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 }
697 g_free(data_dump);
698 }
699
700 g_key_file_free(key_file);
701 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]a3c25952013-05-02 13:16:06706}
707
Robert Woods954a2182020-03-20 06:08:27708base::FilePath GetMimeTypesRegistrationFilename(
709 const base::FilePath& profile_path,
710 const web_app::AppId& app_id) {
711 DCHECK(!profile_path.empty() && !app_id.empty());
712
713 // Use a prefix to clearly group files created by Chrome.
714 std::string filename = base::StringPrintf(
715 "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
716 profile_path.BaseName().value().c_str(), ".xml");
717
718 // Replace illegal characters and spaces in |filename|.
719 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
720 base::ReplaceChars(filename, " ", "_", &filename);
721
722 return base::FilePath(filename);
723}
724
725std::string GetMimeTypesRegistrationFileContents(
726 const apps::FileHandlers& file_handlers) {
727 std::stringstream ss;
728 ss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
729 "<mime-info "
730 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n";
731
732 for (const auto& file_handler : file_handlers) {
733 for (const auto& accept_entry : file_handler.accept) {
734 ss << " <mime-type type=\"" << accept_entry.mime_type + "\">\n";
735 for (const auto& file_extension : accept_entry.file_extensions)
736 ss << " <glob pattern=\"*" << file_extension << "\"/>\n";
737 ss << " </mime-type>\n";
738 }
739 }
740
741 ss << "</mime-info>\n";
742 return ss.str();
743}
744
[email protected]06bfeb12014-05-27 14:00:09745} // namespace shell_integration_linux
Alexey Baskakovb13491b2018-08-03 07:46:38746
747namespace shell_integration {
748
749bool SetAsDefaultBrowser() {
750 return shell_integration_linux::SetDefaultWebClient(std::string());
751}
752
753bool SetAsDefaultProtocolClient(const std::string& protocol) {
754 return shell_integration_linux::SetDefaultWebClient(protocol);
755}
756
757DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
758 return SET_DEFAULT_UNATTENDED;
759}
760
Jan Wilken Dörriedec99122021-03-11 18:02:30761std::u16string GetApplicationNameForProtocol(const GURL& url) {
Jan Wilken Dörrie78e88d82e2021-03-23 15:24:22762 return u"xdg-open";
Alexey Baskakovb13491b2018-08-03 07:46:38763}
764
765DefaultWebClientState GetDefaultBrowser() {
766 return shell_integration_linux::GetIsDefaultWebClient(std::string());
767}
768
769bool IsFirefoxDefaultBrowser() {
770 std::vector<std::string> argv;
771 argv.push_back(shell_integration_linux::kXdgSettings);
772 argv.push_back("get");
773 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
774
775 std::string browser;
776 // We don't care about the return value here.
777 base::GetAppOutput(base::CommandLine(argv), &browser);
778 return browser.find("irefox") != std::string::npos;
779}
780
781DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
782 return shell_integration_linux::GetIsDefaultWebClient(protocol);
783}
784
785} // namespace shell_integration