Avi Drissman | 4a8573c | 2022-09-09 19:35:54 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 8806d3b | 2012-04-13 06:46:34 | [diff] [blame] | 5 | #include "chrome/browser/shell_integration_linux.h" |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 6 | |
[email protected] | 6584f0b1 | 2009-07-20 23:38:14 | [diff] [blame] | 7 | #include <fcntl.h> |
avi | 664c07b | 2015-12-26 02:18:31 | [diff] [blame] | 8 | #include <stddef.h> |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 9 | #include <stdlib.h> |
[email protected] | 6584f0b1 | 2009-07-20 23:38:14 | [diff] [blame] | 10 | #include <sys/stat.h> |
| 11 | #include <sys/types.h> |
| 12 | #include <unistd.h> |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 13 | |
Lei Zhang | e711d89c | 2018-09-10 18:48:00 | [diff] [blame] | 14 | #include <memory> |
Robert Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 15 | #include <sstream> |
[email protected] | 4289680 | 2009-08-28 23:39:44 | [diff] [blame] | 16 | #include <string> |
Lei Zhang | e711d89c | 2018-09-10 18:48:00 | [diff] [blame] | 17 | #include <utility> |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 18 | #include <vector> |
| 19 | |
[email protected] | 6a83c424 | 2011-07-07 06:06:41 | [diff] [blame] | 20 | #include "base/base_paths.h" |
[email protected] | 4289680 | 2009-08-28 23:39:44 | [diff] [blame] | 21 | #include "base/command_line.h" |
[email protected] | 76b90d31 | 2010-08-03 03:00:50 | [diff] [blame] | 22 | #include "base/environment.h" |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 23 | #include "base/files/file_enumerator.h" |
[email protected] | 5799981 | 2013-02-24 05:40:52 | [diff] [blame] | 24 | #include "base/files/file_path.h" |
thestig | 18dfb7a5 | 2014-08-26 10:44:04 | [diff] [blame] | 25 | #include "base/files/file_util.h" |
[email protected] | d0767cb54 | 2009-10-08 17:38:30 | [diff] [blame] | 26 | #include "base/i18n/file_util_icu.h" |
Hans Wennborg | f6ad69c | 2020-06-18 18:02:32 | [diff] [blame] | 27 | #include "base/logging.h" |
[email protected] | 08a139d | 2013-04-11 03:32:54 | [diff] [blame] | 28 | #include "base/memory/ref_counted_memory.h" |
thestig | d2b1fcf | 2015-01-21 22:11:49 | [diff] [blame] | 29 | #include "base/nix/xdg_util.h" |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 30 | #include "base/path_service.h" |
[email protected] | 2025d00 | 2012-11-14 20:54:35 | [diff] [blame] | 31 | #include "base/posix/eintr_wrapper.h" |
[email protected] | d09a4ce1c | 2013-07-24 17:37:02 | [diff] [blame] | 32 | #include "base/process/kill.h" |
| 33 | #include "base/process/launch.h" |
[email protected] | 3ea1b18 | 2013-02-08 22:38:41 | [diff] [blame] | 34 | #include "base/strings/string_number_conversions.h" |
[email protected] | f4ebe77 | 2013-02-02 00:21:39 | [diff] [blame] | 35 | #include "base/strings/string_tokenizer.h" |
[email protected] | 12100ad3 | 2013-07-10 05:07:01 | [diff] [blame] | 36 | #include "base/strings/string_util.h" |
Robert Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 37 | #include "base/strings/stringprintf.h" |
[email protected] | e309f31 | 2013-06-07 21:50:08 | [diff] [blame] | 38 | #include "base/strings/utf_string_conversions.h" |
Etienne Pierre-doray | e6c53531 | 2018-08-28 15:45:39 | [diff] [blame] | 39 | #include "base/threading/scoped_blocking_call.h" |
[email protected] | 34b9963 | 2011-01-01 01:01:06 | [diff] [blame] | 40 | #include "base/threading/thread.h" |
[email protected] | 8988665 | 2012-12-11 18:09:07 | [diff] [blame] | 41 | #include "base/threading/thread_restrictions.h" |
Nico Weber | eaa0841 | 2019-08-14 01:24:37 | [diff] [blame] | 42 | #include "build/branding_buildflags.h" |
[email protected] | b03f53cd | 2011-04-06 18:18:43 | [diff] [blame] | 43 | #include "build/build_config.h" |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 44 | #include "build/chromeos_buildflags.h" |
[email protected] | 2e0424a | 2014-04-15 13:02:15 | [diff] [blame] | 45 | #include "chrome/browser/shell_integration.h" |
Daniel Murphy | 2ddeccc4 | 2022-02-17 02:14:22 | [diff] [blame] | 46 | #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h" |
Alexander Dunaev | 2193149f | 2022-01-05 18:13:48 | [diff] [blame] | 47 | #include "chrome/browser/web_applications/web_app_helpers.h" |
Scott Violet | 6200d33 | 2018-02-23 21:29:23 | [diff] [blame] | 48 | #include "chrome/common/buildflags.h" |
sdefresne | 9fb6769 | 2015-08-03 18:48:22 | [diff] [blame] | 49 | #include "chrome/common/channel_info.h" |
[email protected] | 4289680 | 2009-08-28 23:39:44 | [diff] [blame] | 50 | #include "chrome/common/chrome_constants.h" |
[email protected] | 7199367d | 2014-01-20 04:06:21 | [diff] [blame] | 51 | #include "chrome/common/chrome_switches.h" |
thestig | 4a9b0ef | 2016-08-29 08:22:12 | [diff] [blame] | 52 | #include "chrome/grit/chrome_unscaled_resources.h" |
sdefresne | 9fb6769 | 2015-08-03 18:48:22 | [diff] [blame] | 53 | #include "components/version_info/version_info.h" |
Evan Stade | b8f43a4 | 2021-09-14 16:25:14 | [diff] [blame] | 54 | #include "third_party/libxml/chromium/xml_writer.h" |
Mandy Chen | 5de2f24 | 2022-01-12 00:30:21 | [diff] [blame] | 55 | #include "third_party/re2/src/re2/re2.h" |
[email protected] | 7199367d | 2014-01-20 04:06:21 | [diff] [blame] | 56 | #include "ui/base/resource/resource_bundle.h" |
[email protected] | 08a139d | 2013-04-11 03:32:54 | [diff] [blame] | 57 | #include "ui/gfx/image/image_family.h" |
Maksim Sisov | 213ddfa | 2021-09-30 08:18:56 | [diff] [blame] | 58 | #include "ui/ozone/public/ozone_platform.h" |
| 59 | #include "ui/ozone/public/platform_utils.h" |
[email protected] | 761fa470 | 2013-07-02 15:25:15 | [diff] [blame] | 60 | #include "url/gurl.h" |
[email protected] | ef525cc | 2009-07-10 17:08:16 | [diff] [blame] | 61 | |
Lei Zhang | e711d89c | 2018-09-10 18:48:00 | [diff] [blame] | 62 | #if defined(USE_GLIB) |
| 63 | #include <glib.h> |
| 64 | #endif |
| 65 | |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 66 | namespace shell_integration_linux { |
Alexey Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 67 | |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 68 | const char kXdgSettings[] = "xdg-settings"; |
| 69 | const char kXdgSettingsDefaultBrowser[] = "default-web-browser"; |
| 70 | const 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. |
| 77 | bool GetChromeVersionOfScript(const std::string& script, |
| 78 | std::string* chrome_version) { |
| 79 | // Get the path to the Chrome version. |
| 80 | base::FilePath chrome_dir; |
Avi Drissman | 9098f900 | 2018-05-04 00:11:52 | [diff] [blame] | 81 | if (!base::PathService::Get(base::DIR_EXE, &chrome_dir)) |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 82 | 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. |
| 102 | const 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 Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 114 | // If |scheme| is empty this function sets Chrome as the default browser, |
| 115 | // otherwise it sets Chrome as the default handler application for |scheme|. |
| 116 | bool SetDefaultWebClient(const std::string& scheme) { |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 117 | #if BUILDFLAG(IS_CHROMEOS_ASH) |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 118 | return true; |
| 119 | #else |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 120 | std::unique_ptr<base::Environment> env(base::Environment::Create()); |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 121 | |
| 122 | std::vector<std::string> argv; |
| 123 | argv.push_back(kXdgSettings); |
| 124 | argv.push_back("set"); |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 125 | if (scheme.empty()) { |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 126 | argv.push_back(kXdgSettingsDefaultBrowser); |
| 127 | } else { |
| 128 | argv.push_back(kXdgSettingsDefaultSchemeHandler); |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 129 | argv.push_back(scheme); |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 130 | } |
Tom Anderson | b8a8603 | 2019-10-09 20:12:45 | [diff] [blame] | 131 | argv.push_back(chrome::GetDesktopName(env.get())); |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 132 | |
| 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 Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 145 | // If |scheme| is empty this function checks if Chrome is the default browser, |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 146 | // otherwise it checks if Chrome is the default handler application for |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 147 | // |scheme|. |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 148 | shell_integration::DefaultWebClientState GetIsDefaultWebClient( |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 149 | const std::string& scheme) { |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 150 | #if BUILDFLAG(IS_CHROMEOS_ASH) |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 151 | return shell_integration::UNKNOWN_DEFAULT; |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 152 | #else |
Etienne Bergeron | 436d4221 | 2019-02-26 17:15:12 | [diff] [blame] | 153 | base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| 154 | base::BlockingType::MAY_BLOCK); |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 155 | |
| 156 | std::unique_ptr<base::Environment> env(base::Environment::Create()); |
| 157 | |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 158 | std::vector<std::string> argv; |
| 159 | argv.push_back(kXdgSettings); |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 160 | argv.push_back("check"); |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 161 | if (scheme.empty()) { |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 162 | argv.push_back(kXdgSettingsDefaultBrowser); |
| 163 | } else { |
| 164 | argv.push_back(kXdgSettingsDefaultSchemeHandler); |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 165 | argv.push_back(scheme); |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 166 | } |
Tom Anderson | b8a8603 | 2019-10-09 20:12:45 | [diff] [blame] | 167 | argv.push_back(chrome::GetDesktopName(env.get())); |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 168 | |
| 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 | |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 180 | if (!ran_ok || success_code != EXIT_SUCCESS) { |
| 181 | // xdg-settings failed: we can't determine or set the default browser. |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 182 | return shell_integration::UNKNOWN_DEFAULT; |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 183 | } |
| 184 | |
thomasanderson | 8258b1c5 | 2017-03-16 00:04:20 | [diff] [blame] | 185 | // Allow any reply that starts with "yes". |
| 186 | return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE) |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 187 | ? shell_integration::IS_DEFAULT |
| 188 | : shell_integration::NOT_DEFAULT; |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 189 | #endif |
| 190 | } |
| 191 | |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 192 | // 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. |
| 197 | std::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 | |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 207 | namespace { |
| 208 | |
kalyan.kondapally | 577803c | 2014-08-25 20:13:18 | [diff] [blame] | 209 | #if defined(USE_GLIB) |
[email protected] | b1039293 | 2011-03-08 21:28:14 | [diff] [blame] | 210 | // Quote a string such that it appears as one verbatim argument for the Exec |
| 211 | // key in a desktop file. |
| 212 | std::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] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 238 | // 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. |
| 241 | std::string QuoteCommandLineForDesktopFileExec( |
avi | 556c0502 | 2014-12-22 23:31:43 | [diff] [blame] | 242 | const base::CommandLine& command_line) { |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 243 | // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html |
| 244 | |
Lei Zhang | 9b0be80 | 2020-05-05 21:11:48 | [diff] [blame] | 245 | std::string quoted_path; |
avi | 556c0502 | 2014-12-22 23:31:43 | [diff] [blame] | 246 | const base::CommandLine::StringVector& argv = command_line.argv(); |
jdoerrie | 601c715 | 2018-10-02 23:43:11 | [diff] [blame] | 247 | for (auto i = argv.begin(); i != argv.end(); ++i) { |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 248 | if (i != argv.begin()) |
| 249 | quoted_path += " "; |
| 250 | quoted_path += QuoteArgForDesktopFileExec(*i); |
| 251 | } |
| 252 | |
| 253 | return quoted_path; |
| 254 | } |
Alexey Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 255 | #endif |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 256 | |
Alexey Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 257 | #if defined(USE_GLIB) |
[email protected] | 4f0806a7 | 2011-09-21 03:08:45 | [diff] [blame] | 258 | const char kDesktopEntry[] = "Desktop Entry"; |
[email protected] | 4f0806a7 | 2011-09-21 03:08:45 | [diff] [blame] | 259 | const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open"; |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 260 | |
| 261 | void 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 Chen | 5de2f24 | 2022-01-12 00:30:21 | [diff] [blame] | 282 | std::string launch_url_str = info.exec_launch_url.spec(); |
| 283 | // Escape % as %%. |
| 284 | RE2::GlobalReplace(&launch_url_str, "%", "%%"); |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 285 | base::CommandLine current_cmd(command_line); |
| 286 | current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem, |
Mandy Chen | 5de2f24 | 2022-01-12 00:30:21 | [diff] [blame] | 287 | launch_url_str); |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 288 | |
| 289 | g_key_file_set_string( |
| 290 | key_file, section_title.c_str(), "Exec", |
| 291 | QuoteCommandLineForDesktopFileExec(current_cmd).c_str()); |
| 292 | } |
| 293 | } |
kalyan.kondapally | 577803c | 2014-08-25 20:13:18 | [diff] [blame] | 294 | #endif |
[email protected] | 4f0806a7 | 2011-09-21 03:08:45 | [diff] [blame] | 295 | |
Mike Jackson | 0cb0ba7 | 2022-09-26 23:22:21 | [diff] [blame] | 296 | base::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 | |
| 319 | std::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 Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 356 | } // namespace |
| 357 | |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 358 | // Allows LaunchXdgUtility to join a process. |
| 359 | // thread_restrictions.h assumes it to be in shell_integration_linux namespace. |
| 360 | class LaunchXdgUtilityScopedAllowBaseSyncPrimitives |
| 361 | : public base::ScopedAllowBaseSyncPrimitives {}; |
| 362 | |
| 363 | bool 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 | |
| 385 | std::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 Dunaev | 2193149f | 2022-01-05 18:13:48 | [diff] [blame] | 391 | std::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 Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 399 | base::FilePath GetDataWriteLocation(base::Environment* env) { |
| 400 | return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share"); |
| 401 | } |
| 402 | |
| 403 | std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) { |
Etienne Bergeron | 436d4221 | 2019-02-26 17:15:12 | [diff] [blame] | 404 | base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| 405 | base::BlockingType::MAY_BLOCK); |
Alexey Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 406 | |
| 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 Cheng | 04a05fd | 2021-04-19 17:18:52 | [diff] [blame] | 415 | search_paths.emplace_back(tokenizer.token_piece()); |
Alexey Baskakov | f302efe | 2018-07-28 02:02:32 | [diff] [blame] | 416 | } |
| 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 | |
| 425 | namespace internal { |
| 426 | |
Mike Jackson | 0cb0ba7 | 2022-09-26 23:22:21 | [diff] [blame] | 427 | std::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] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 434 | // 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. |
| 436 | bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) { |
Mike Jackson | 0cb0ba7 | 2022-09-26 23:22:21 | [diff] [blame] | 437 | std::string nodisplay_value = |
| 438 | shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile( |
| 439 | "NoDisplay", shortcut_contents); |
| 440 | return nodisplay_value == "true"; |
[email protected] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 441 | } |
| 442 | |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 443 | // Gets the path to the Chrome executable or wrapper script. |
thestig | d2b1fcf | 2015-01-21 22:11:49 | [diff] [blame] | 444 | // Returns an empty path if the executable path could not be found, which should |
| 445 | // never happen. |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 446 | base::FilePath GetChromeExePath() { |
| 447 | // Try to get the name of the wrapper script that launched Chrome. |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 448 | std::unique_ptr<base::Environment> environment(base::Environment::Create()); |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 449 | std::string wrapper_script; |
thestig | d2b1fcf | 2015-01-21 22:11:49 | [diff] [blame] | 450 | if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 451 | return base::FilePath(wrapper_script); |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 452 | |
| 453 | // Just return the name of the executable path for Chrome. |
| 454 | base::FilePath chrome_exe_path; |
Avi Drissman | 9098f900 | 2018-05-04 00:11:52 | [diff] [blame] | 455 | base::PathService::Get(base::FILE_EXE, &chrome_exe_path); |
[email protected] | fcd21d32 | 2013-06-27 12:35:56 | [diff] [blame] | 456 | return chrome_exe_path; |
| 457 | } |
| 458 | |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 459 | std::string GetProgramClassName(const base::CommandLine& command_line, |
| 460 | const std::string& desktop_file_name) { |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 461 | std::string class_name = GetDesktopBaseName(desktop_file_name); |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 462 | 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 | |
| 473 | std::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 Dunaev | 66b50ba | 2021-08-10 04:11:46 | [diff] [blame] | 477 | std::string desktop_base_name = GetDesktopBaseName(desktop_file_name); |
Maksim Sisov | 213ddfa | 2021-09-30 08:18:56 | [diff] [blame] | 478 | if (auto* platform_utils = |
| 479 | ui::OzonePlatform::GetInstance()->GetPlatformUtils()) { |
| 480 | return platform_utils->GetWmWindowClass(desktop_base_name); |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 481 | } |
Alexander Dunaev | 66b50ba | 2021-08-10 04:11:46 | [diff] [blame] | 482 | 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; |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 487 | } |
| 488 | |
| 489 | } // namespace internal |
| 490 | |
[email protected] | f93a7745 | 2013-09-02 05:26:35 | [diff] [blame] | 491 | std::string GetProgramClassName() { |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 492 | std::unique_ptr<base::Environment> env(base::Environment::Create()); |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 493 | return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(), |
Tom Anderson | b8a8603 | 2019-10-09 20:12:45 | [diff] [blame] | 494 | chrome::GetDesktopName(env.get())); |
thomasanderson | 12d8758 | 2016-07-29 21:17:41 | [diff] [blame] | 495 | } |
| 496 | |
| 497 | std::string GetProgramClassClass() { |
| 498 | std::unique_ptr<base::Environment> env(base::Environment::Create()); |
| 499 | return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(), |
Tom Anderson | b8a8603 | 2019-10-09 20:12:45 | [diff] [blame] | 500 | chrome::GetDesktopName(env.get())); |
[email protected] | 98566d7a | 2012-04-17 00:28:56 | [diff] [blame] | 501 | } |
| 502 | |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 503 | std::string GetIconName() { |
Nico Weber | eaa0841 | 2019-08-14 01:24:37 | [diff] [blame] | 504 | #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 505 | return "google-chrome"; |
Nico Weber | 897593f | 2019-07-25 23:17:55 | [diff] [blame] | 506 | #else // BUILDFLAG(CHROMIUM_BRANDING) |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 507 | return "chromium-browser"; |
| 508 | #endif |
| 509 | } |
| 510 | |
[email protected] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 511 | bool GetExistingShortcutContents(base::Environment* env, |
| 512 | const base::FilePath& desktop_filename, |
| 513 | std::string* output) { |
Etienne Bergeron | 436d4221 | 2019-02-26 17:15:12 | [diff] [blame] | 514 | base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| 515 | base::BlockingType::MAY_BLOCK); |
[email protected] | 620942e | 2010-02-16 10:12:12 | [diff] [blame] | 516 | |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 517 | std::vector<base::FilePath> search_paths = GetDataSearchLocations(env); |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 518 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 519 | for (std::vector<base::FilePath>::const_iterator i = search_paths.begin(); |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 520 | i != search_paths.end(); ++i) { |
[email protected] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 521 | base::FilePath path = i->Append("applications").Append(desktop_filename); |
| 522 | VLOG(1) << "Looking for desktop file in " << path.value(); |
[email protected] | 756748414 | 2013-07-11 17:36:07 | [diff] [blame] | 523 | if (base::PathExists(path)) { |
[email protected] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 524 | VLOG(1) << "Found desktop file at " << path.value(); |
[email protected] | 82f84b9 | 2013-08-30 18:23:50 | [diff] [blame] | 525 | return base::ReadFileToString(path, output); |
[email protected] | 620942e | 2010-02-16 10:12:12 | [diff] [blame] | 526 | } |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 527 | } |
| 528 | |
| 529 | return false; |
| 530 | } |
| 531 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 532 | base::FilePath GetWebShortcutFilename(const GURL& url) { |
[email protected] | 4289680 | 2009-08-28 23:39:44 | [diff] [blame] | 533 | // Use a prefix, because xdg-desktop-menu requires it. |
[email protected] | de294335 | 2009-10-22 23:06:12 | [diff] [blame] | 534 | std::string filename = |
[email protected] | 4f260d0 | 2010-12-23 18:35:42 | [diff] [blame] | 535 | std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec(); |
[email protected] | 6bc03de | 2014-08-07 23:59:15 | [diff] [blame] | 536 | base::i18n::ReplaceIllegalCharactersInPath(&filename, '_'); |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 537 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 538 | base::FilePath desktop_path; |
Avi Drissman | 9098f900 | 2018-05-04 00:11:52 | [diff] [blame] | 539 | if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 540 | return base::FilePath(); |
[email protected] | fcc23e84 | 2009-10-01 03:19:10 | [diff] [blame] | 541 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 542 | base::FilePath filepath = desktop_path.Append(filename); |
| 543 | base::FilePath alternative_filepath(filepath.value() + ".desktop"); |
[email protected] | fcc23e84 | 2009-10-01 03:19:10 | [diff] [blame] | 544 | for (size_t i = 1; i < 100; ++i) { |
[email protected] | 756748414 | 2013-07-11 17:36:07 | [diff] [blame] | 545 | if (base::PathExists(base::FilePath(alternative_filepath))) { |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 546 | alternative_filepath = base::FilePath( |
Brett Wilson | 5accd24 | 2017-11-30 22:07:32 | [diff] [blame] | 547 | filepath.value() + "_" + base::NumberToString(i) + ".desktop"); |
[email protected] | fcc23e84 | 2009-10-01 03:19:10 | [diff] [blame] | 548 | } else { |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 549 | return base::FilePath(alternative_filepath).BaseName(); |
[email protected] | fcc23e84 | 2009-10-01 03:19:10 | [diff] [blame] | 550 | } |
| 551 | } |
| 552 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 553 | return base::FilePath(); |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 554 | } |
| 555 | |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 556 | std::vector<base::FilePath> GetExistingProfileShortcutFilenames( |
| 557 | const base::FilePath& profile_path, |
| 558 | const base::FilePath& directory) { |
Etienne Bergeron | 436d4221 | 2019-02-26 17:15:12 | [diff] [blame] | 559 | base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| 560 | base::BlockingType::MAY_BLOCK); |
thestig | d2b1fcf | 2015-01-21 22:11:49 | [diff] [blame] | 561 | |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 562 | // 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] | 6bc03de | 2014-08-07 23:59:15 | [diff] [blame] | 567 | base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_'); |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 568 | // Spaces in filenames break xdg-desktop-menu |
| 569 | // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605). |
[email protected] | 466c986 | 2013-12-03 22:05:28 | [diff] [blame] | 570 | base::ReplaceChars(suffix, " ", "_", &suffix); |
[email protected] | 111f028 | 2013-08-05 10:09:29 | [diff] [blame] | 571 | 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 Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 584 | std::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) { |
pmonette | 9fa59e88 | 2016-02-10 00:12:19 | [diff] [blame] | 597 | base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher( |
Mike Jackson | b3c3425d | 2021-03-10 00:04:51 | [diff] [blame] | 598 | url, extension_id, profile_path, run_on_os_login_mode); |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 599 | cmd_line.SetProgram(chrome_exe_path); |
| 600 | return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title, |
Jay Harris | 05b50bd | 2019-10-30 01:48:38 | [diff] [blame] | 601 | icon_name, categories, mime_type, |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 602 | no_display, std::move(action_info)); |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 603 | } |
| 604 | |
| 605 | std::string GetDesktopFileContentsForCommand( |
avi | 556c0502 | 2014-12-22 23:31:43 | [diff] [blame] | 606 | const base::CommandLine& command_line, |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 607 | const std::string& app_name, |
| 608 | const GURL& url, |
Jan Wilken Dörrie | dec9912 | 2021-03-11 18:02:30 | [diff] [blame] | 609 | const std::u16string& title, |
[email protected] | 2164e51 | 2014-01-22 09:32:10 | [diff] [blame] | 610 | const std::string& icon_name, |
[email protected] | 4a789682 | 2014-04-25 23:11:43 | [diff] [blame] | 611 | const std::string& categories, |
Jay Harris | 05b50bd | 2019-10-30 01:48:38 | [diff] [blame] | 612 | const std::string& mime_type, |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 613 | bool no_display, |
| 614 | std::set<web_app::DesktopActionInfo> action_info) { |
[email protected] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 615 | #if defined(USE_GLIB) |
[email protected] | b9eb4e5 | 2013-02-05 00:01:49 | [diff] [blame] | 616 | // 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] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 619 | |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 620 | // See http://standards.freedesktop.org/desktop-entry-spec/latest/ |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 621 | GKeyFile* key_file = g_key_file_new(); |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 622 | |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 623 | // 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] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 627 | |
| 628 | // Set the "Name" key. |
[email protected] | f911df5 | 2013-12-24 23:24:23 | [diff] [blame] | 629 | std::string final_title = base::UTF16ToUTF8(title); |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 630 | // 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 Harris | f730b6e | 2019-11-25 23:49:06 | [diff] [blame] | 640 | 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 Jackson | cfb1bc1 | 2021-03-24 01:22:22 | [diff] [blame] | 653 | modified_command_line.AppendArg("%U"); |
Jay Harris | f730b6e | 2019-11-25 23:49:06 | [diff] [blame] | 654 | } |
| 655 | |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 656 | // Set the "Exec" key. |
Jay Harris | f730b6e | 2019-11-25 23:49:06 | [diff] [blame] | 657 | std::string final_path = |
| 658 | QuoteCommandLineForDesktopFileExec(modified_command_line); |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 659 | g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); |
| 660 | |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 661 | // Set the "Icon" key. |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 662 | if (!icon_name.empty()) { |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 663 | g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 664 | } else { |
| 665 | g_key_file_set_string(key_file, kDesktopEntry, "Icon", |
| 666 | GetIconName().c_str()); |
| 667 | } |
[email protected] | a0b60cfd | 2011-04-06 18:02:41 | [diff] [blame] | 668 | |
[email protected] | 4a789682 | 2014-04-25 23:11:43 | [diff] [blame] | 669 | // 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] | d81a63c0 | 2013-03-07 08:49:04 | [diff] [blame] | 675 | // Set the "NoDisplay" key. |
| 676 | if (no_display) |
| 677 | g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true"); |
| 678 | |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 679 | std::string wmclass = GetWMClassFromAppName(app_name); |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 680 | g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", |
| 681 | wmclass.c_str()); |
[email protected] | a0b60cfd | 2011-04-06 18:02:41 | [diff] [blame] | 682 | |
Eric Willigers | 05e0865 | 2022-02-08 01:59:09 | [diff] [blame] | 683 | SetActionsForDesktopApplication(command_line, key_file, |
| 684 | std::move(action_info)); |
Mandy Chen | d57606f | 2021-08-31 17:49:10 | [diff] [blame] | 685 | |
[email protected] | 14fbaed | 2013-05-02 07:54:02 | [diff] [blame] | 686 | gsize length = 0; |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 687 | gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); |
| 688 | if (data_dump) { |
[email protected] | b9eb4e5 | 2013-02-05 00:01:49 | [diff] [blame] | 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 | } |
[email protected] | 0a96c3f | 2011-05-11 22:10:20 | [diff] [blame] | 697 | g_free(data_dump); |
| 698 | } |
| 699 | |
| 700 | g_key_file_free(key_file); |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 701 | return output_buffer; |
[email protected] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 702 | #else |
| 703 | NOTIMPLEMENTED(); |
[email protected] | 06bfeb1 | 2014-05-27 14:00:09 | [diff] [blame] | 704 | return std::string(); |
[email protected] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 705 | #endif |
[email protected] | b96aa93 | 2009-08-12 21:34:49 | [diff] [blame] | 706 | } |
| 707 | |
Jan Wilken Dörrie | dec9912 | 2021-03-11 18:02:30 | [diff] [blame] | 708 | std::string GetDirectoryFileContents(const std::u16string& title, |
[email protected] | a3c2595 | 2013-05-02 13:16:06 | [diff] [blame] | 709 | const std::string& icon_name) { |
[email protected] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 710 | #if defined(USE_GLIB) |
[email protected] | a3c2595 | 2013-05-02 13:16:06 | [diff] [blame] | 711 | // 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] | f911df5 | 2013-12-24 23:24:23 | [diff] [blame] | 716 | std::string final_title = base::UTF16ToUTF8(title); |
[email protected] | a3c2595 | 2013-05-02 13:16:06 | [diff] [blame] | 717 | 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] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 742 | #else |
| 743 | NOTIMPLEMENTED(); |
[email protected] | 06bfeb1 | 2014-05-27 14:00:09 | [diff] [blame] | 744 | return std::string(); |
[email protected] | 73bae9d | 2014-05-11 00:13:55 | [diff] [blame] | 745 | #endif |
[email protected] | a3c2595 | 2013-05-02 13:16:06 | [diff] [blame] | 746 | } |
| 747 | |
Robert Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 748 | base::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 | |
| 765 | std::string GetMimeTypesRegistrationFileContents( |
| 766 | const apps::FileHandlers& file_handlers) { |
Evan Stade | b8f43a4 | 2021-09-14 16:25:14 | [diff] [blame] | 767 | 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 Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 773 | |
| 774 | for (const auto& file_handler : file_handlers) { |
| 775 | for (const auto& accept_entry : file_handler.accept) { |
Evan Stade | b8f43a4 | 2021-09-14 16:25:14 | [diff] [blame] | 776 | 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 Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 789 | } |
| 790 | } |
| 791 | |
Evan Stade | b8f43a4 | 2021-09-14 16:25:14 | [diff] [blame] | 792 | writer.EndElement(); // "mime-info" |
| 793 | writer.StopWriting(); |
| 794 | return writer.GetWrittenString(); |
Robert Woods | 954a218 | 2020-03-20 06:08:27 | [diff] [blame] | 795 | } |
| 796 | |
[email protected] | 06bfeb1 | 2014-05-27 14:00:09 | [diff] [blame] | 797 | } // namespace shell_integration_linux |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 798 | |
| 799 | namespace shell_integration { |
| 800 | |
| 801 | bool SetAsDefaultBrowser() { |
| 802 | return shell_integration_linux::SetDefaultWebClient(std::string()); |
| 803 | } |
| 804 | |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 805 | bool SetAsDefaultClientForScheme(const std::string& scheme) { |
| 806 | return shell_integration_linux::SetDefaultWebClient(scheme); |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 807 | } |
| 808 | |
Thiago Perrotta | b7a0c46b | 2022-10-28 18:40:34 | [diff] [blame] | 809 | DefaultWebClientSetPermission |
| 810 | GetPlatformSpecificDefaultWebClientSetPermission() { |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 811 | return SET_DEFAULT_UNATTENDED; |
| 812 | } |
| 813 | |
Avi Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 814 | std::u16string GetApplicationNameForScheme(const GURL& url) { |
Mike Jackson | 0cb0ba7 | 2022-09-26 23:22:21 | [diff] [blame] | 815 | 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 Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 831 | } |
| 832 | |
| 833 | DefaultWebClientState GetDefaultBrowser() { |
| 834 | return shell_integration_linux::GetIsDefaultWebClient(std::string()); |
| 835 | } |
| 836 | |
| 837 | bool 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 Drissman | 68395fd7 | 2023-01-04 19:52:37 | [diff] [blame] | 849 | DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) { |
| 850 | return shell_integration_linux::GetIsDefaultWebClient(scheme); |
Alexey Baskakov | b13491b | 2018-08-03 07:46:38 | [diff] [blame] | 851 | } |
| 852 | |
| 853 | } // namespace shell_integration |