blob: bfd0f1c16666ef8071b00e9a1f85e80b9806ed87 [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>
[email protected]42896802009-08-28 23:39:4415#include <string>
Lei Zhange711d89c2018-09-10 18:48:0016#include <utility>
[email protected]ef525cc2009-07-10 17:08:1617#include <vector>
18
[email protected]6a83c4242011-07-07 06:06:4119#include "base/base_paths.h"
[email protected]42896802009-08-28 23:39:4420#include "base/command_line.h"
[email protected]76b90d312010-08-03 03:00:5021#include "base/environment.h"
[email protected]111f0282013-08-05 10:09:2922#include "base/files/file_enumerator.h"
[email protected]57999812013-02-24 05:40:5223#include "base/files/file_path.h"
thestig18dfb7a52014-08-26 10:44:0424#include "base/files/file_util.h"
[email protected]d0767cb542009-10-08 17:38:3025#include "base/i18n/file_util_icu.h"
[email protected]08a139d2013-04-11 03:32:5426#include "base/memory/ref_counted_memory.h"
thestigd2b1fcf2015-01-21 22:11:4927#include "base/nix/xdg_util.h"
[email protected]b96aa932009-08-12 21:34:4928#include "base/path_service.h"
[email protected]2025d002012-11-14 20:54:3529#include "base/posix/eintr_wrapper.h"
[email protected]d09a4ce1c2013-07-24 17:37:0230#include "base/process/kill.h"
31#include "base/process/launch.h"
[email protected]3ea1b182013-02-08 22:38:4132#include "base/strings/string_number_conversions.h"
[email protected]f4ebe772013-02-02 00:21:3933#include "base/strings/string_tokenizer.h"
[email protected]12100ad32013-07-10 05:07:0134#include "base/strings/string_util.h"
[email protected]e309f312013-06-07 21:50:0835#include "base/strings/utf_string_conversions.h"
Etienne Pierre-doraye6c535312018-08-28 15:45:3936#include "base/threading/scoped_blocking_call.h"
[email protected]34b99632011-01-01 01:01:0637#include "base/threading/thread.h"
[email protected]89886652012-12-11 18:09:0738#include "base/threading/thread_restrictions.h"
[email protected]b03f53cd2011-04-06 18:18:4339#include "build/build_config.h"
[email protected]2e0424a2014-04-15 13:02:1540#include "chrome/browser/shell_integration.h"
Scott Violet6200d332018-02-23 21:29:2341#include "chrome/common/buildflags.h"
sdefresne9fb67692015-08-03 18:48:2242#include "chrome/common/channel_info.h"
[email protected]42896802009-08-28 23:39:4443#include "chrome/common/chrome_constants.h"
[email protected]7199367d2014-01-20 04:06:2144#include "chrome/common/chrome_switches.h"
thestig4a9b0ef2016-08-29 08:22:1245#include "chrome/grit/chrome_unscaled_resources.h"
sdefresne9fb67692015-08-03 18:48:2246#include "components/version_info/version_info.h"
[email protected]7199367d2014-01-20 04:06:2147#include "ui/base/resource/resource_bundle.h"
[email protected]08a139d2013-04-11 03:32:5448#include "ui/gfx/image/image_family.h"
[email protected]761fa4702013-07-02 15:25:1549#include "url/gurl.h"
[email protected]ef525cc2009-07-10 17:08:1650
Lei Zhange711d89c2018-09-10 18:48:0051#if defined(USE_GLIB)
52#include <glib.h>
53#endif
54
Alexey Baskakovb13491b2018-08-03 07:46:3855namespace shell_integration_linux {
Alexey Baskakovf302efe2018-07-28 02:02:3256
pmonette9fa59e882016-02-10 00:12:1957const char kXdgSettings[] = "xdg-settings";
58const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
59const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
60
61// Utility function to get the path to the version of a script shipped with
62// Chrome. |script| gives the name of the script. |chrome_version| returns the
63// path to the Chrome version of the script, and the return value of the
64// function is true if the function is successful and the Chrome version is
65// not the script found on the PATH.
66bool GetChromeVersionOfScript(const std::string& script,
67 std::string* chrome_version) {
68 // Get the path to the Chrome version.
69 base::FilePath chrome_dir;
Avi Drissman9098f9002018-05-04 00:11:5270 if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
pmonette9fa59e882016-02-10 00:12:1971 return false;
72
73 base::FilePath chrome_version_path = chrome_dir.Append(script);
74 *chrome_version = chrome_version_path.value();
75
76 // Check if this is different to the one on path.
77 std::vector<std::string> argv;
78 argv.push_back("which");
79 argv.push_back(script);
80 std::string path_version;
81 if (base::GetAppOutput(base::CommandLine(argv), &path_version)) {
82 // Remove trailing newline
83 path_version.erase(path_version.length() - 1, 1);
84 base::FilePath path_version_path(path_version);
85 return (chrome_version_path != path_version_path);
86 }
87 return false;
88}
89
90// Value returned by xdg-settings if it can't understand our request.
91const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
92
93// We delegate the difficulty of setting the default browser and default url
94// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
95
96// When calling this script we first try to use the script on PATH. If that
97// fails we then try to use the script that we have included. This gives
98// scripts on the system priority over ours, as distribution vendors may have
99// tweaked the script, but still allows our copy to be used if the script on the
100// system fails, as the system copy may be missing capabilities of the Chrome
101// copy.
102
103// If |protocol| is empty this function sets Chrome as the default browser,
104// otherwise it sets Chrome as the default handler application for |protocol|.
105bool SetDefaultWebClient(const std::string& protocol) {
106#if defined(OS_CHROMEOS)
107 return true;
108#else
dcheng4af48582016-04-19 00:29:35109 std::unique_ptr<base::Environment> env(base::Environment::Create());
pmonette9fa59e882016-02-10 00:12:19110
111 std::vector<std::string> argv;
112 argv.push_back(kXdgSettings);
113 argv.push_back("set");
114 if (protocol.empty()) {
115 argv.push_back(kXdgSettingsDefaultBrowser);
116 } else {
117 argv.push_back(kXdgSettingsDefaultSchemeHandler);
118 argv.push_back(protocol);
119 }
Alexey Baskakovb13491b2018-08-03 07:46:38120 argv.push_back(GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19121
122 int exit_code;
123 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
124 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
125 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
126 ran_ok = LaunchXdgUtility(argv, &exit_code);
127 }
128 }
129
130 return ran_ok && exit_code == EXIT_SUCCESS;
131#endif
132}
133
thomasanderson8258b1c52017-03-16 00:04:20134// If |protocol| is empty this function checks if Chrome is the default browser,
135// otherwise it checks if Chrome is the default handler application for
136// |protocol|.
Alexey Baskakovb13491b2018-08-03 07:46:38137shell_integration::DefaultWebClientState GetIsDefaultWebClient(
138 const std::string& protocol) {
thomasanderson8258b1c52017-03-16 00:04:20139#if defined(OS_CHROMEOS)
Alexey Baskakovb13491b2018-08-03 07:46:38140 return shell_integration::UNKNOWN_DEFAULT;
thomasanderson8258b1c52017-03-16 00:04:20141#else
Etienne Pierre-doraye6c535312018-08-28 15:45:39142 base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
thomasanderson8258b1c52017-03-16 00:04:20143
144 std::unique_ptr<base::Environment> env(base::Environment::Create());
145
pmonette9fa59e882016-02-10 00:12:19146 std::vector<std::string> argv;
147 argv.push_back(kXdgSettings);
thomasanderson8258b1c52017-03-16 00:04:20148 argv.push_back("check");
pmonette9fa59e882016-02-10 00:12:19149 if (protocol.empty()) {
150 argv.push_back(kXdgSettingsDefaultBrowser);
151 } else {
152 argv.push_back(kXdgSettingsDefaultSchemeHandler);
153 argv.push_back(protocol);
154 }
Alexey Baskakovb13491b2018-08-03 07:46:38155 argv.push_back(GetDesktopName(env.get()));
pmonette9fa59e882016-02-10 00:12:19156
157 std::string reply;
158 int success_code;
159 bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
160 &success_code);
161 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
162 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
163 ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
164 &success_code);
165 }
166 }
167
thomasanderson8258b1c52017-03-16 00:04:20168 if (!ran_ok || success_code != EXIT_SUCCESS) {
169 // xdg-settings failed: we can't determine or set the default browser.
Alexey Baskakovb13491b2018-08-03 07:46:38170 return shell_integration::UNKNOWN_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19171 }
172
thomasanderson8258b1c52017-03-16 00:04:20173 // Allow any reply that starts with "yes".
174 return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
Alexey Baskakovb13491b2018-08-03 07:46:38175 ? shell_integration::IS_DEFAULT
176 : shell_integration::NOT_DEFAULT;
pmonette9fa59e882016-02-10 00:12:19177#endif
178}
179
thomasanderson12d87582016-07-29 21:17:41180// https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
181// The WM_CLASS property should be set to the same as the *.desktop file without
182// the .desktop extension. We cannot simply use argv[0] in this case, because
183// on the stable channel, the executable name is google-chrome-stable, but the
184// desktop file is google-chrome.desktop.
185std::string GetDesktopBaseName(const std::string& desktop_file_name) {
186 static const char kDesktopExtension[] = ".desktop";
187 if (base::EndsWith(desktop_file_name, kDesktopExtension,
188 base::CompareCase::SENSITIVE)) {
189 return desktop_file_name.substr(
190 0, desktop_file_name.length() - strlen(kDesktopExtension));
191 }
192 return desktop_file_name;
193}
194
pmonette9fa59e882016-02-10 00:12:19195namespace {
196
kalyan.kondapally577803c2014-08-25 20:13:18197#if defined(USE_GLIB)
[email protected]b10392932011-03-08 21:28:14198// Quote a string such that it appears as one verbatim argument for the Exec
199// key in a desktop file.
200std::string QuoteArgForDesktopFileExec(const std::string& arg) {
201 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
202
203 // Quoting is only necessary if the argument has a reserved character.
204 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
205 return arg; // No quoting necessary.
206
207 std::string quoted = "\"";
208 for (size_t i = 0; i < arg.size(); ++i) {
209 // Note that the set of backslashed characters is smaller than the
210 // set of reserved characters.
211 switch (arg[i]) {
212 case '"':
213 case '`':
214 case '$':
215 case '\\':
216 quoted += '\\';
217 break;
218 }
219 quoted += arg[i];
220 }
221 quoted += '"';
222
223 return quoted;
224}
225
[email protected]2164e512014-01-22 09:32:10226// Quote a command line so it is suitable for use as the Exec key in a desktop
227// file. Note: This should be used instead of GetCommandLineString, which does
228// not properly quote the string; this function is designed for the Exec key.
229std::string QuoteCommandLineForDesktopFileExec(
avi556c05022014-12-22 23:31:43230 const base::CommandLine& command_line) {
[email protected]2164e512014-01-22 09:32:10231 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
232
233 std::string quoted_path = "";
avi556c05022014-12-22 23:31:43234 const base::CommandLine::StringVector& argv = command_line.argv();
jdoerrie601c7152018-10-02 23:43:11235 for (auto i = argv.begin(); i != argv.end(); ++i) {
[email protected]2164e512014-01-22 09:32:10236 if (i != argv.begin())
237 quoted_path += " ";
238 quoted_path += QuoteArgForDesktopFileExec(*i);
239 }
240
241 return quoted_path;
242}
Alexey Baskakovf302efe2018-07-28 02:02:32243#endif
[email protected]2164e512014-01-22 09:32:10244
Alexey Baskakovf302efe2018-07-28 02:02:32245#if defined(USE_GLIB)
[email protected]4f0806a72011-09-21 03:08:45246const char kDesktopEntry[] = "Desktop Entry";
[email protected]4f0806a72011-09-21 03:08:45247const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
kalyan.kondapally577803c2014-08-25 20:13:18248#endif
[email protected]4f0806a72011-09-21 03:08:45249
Alexey Baskakovf302efe2018-07-28 02:02:32250// TODO(loyso): shell_integraion_linux.cc won't compile with app_list disabled?
brettwd1bc9da2016-10-14 19:04:24251#if BUILDFLAG(ENABLE_APP_LIST)
[email protected]7199367d2014-01-20 04:06:21252#if defined(GOOGLE_CHROME_BUILD)
253const char kAppListDesktopName[] = "chrome-app-list";
254#else // CHROMIUM_BUILD
255const char kAppListDesktopName[] = "chromium-app-list";
256#endif
thestig06a90372016-07-18 22:04:11257#endif
[email protected]7199367d2014-01-20 04:06:21258
Alexey Baskakovf302efe2018-07-28 02:02:32259} // namespace
260
Alexey Baskakovb13491b2018-08-03 07:46:38261// Allows LaunchXdgUtility to join a process.
262// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
263class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
264 : public base::ScopedAllowBaseSyncPrimitives {};
265
266bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
267 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
268 // files on top of originals after making changes to them. In the event that
269 // the original files are owned by another user (e.g. root, which can happen
270 // if they are updated within sudo), mv will prompt the user to confirm if
271 // standard input is a terminal (otherwise it just does it). So make sure it's
272 // not, to avoid locking everything up waiting for mv.
273 *exit_code = EXIT_FAILURE;
274 int devnull = open("/dev/null", O_RDONLY);
275 if (devnull < 0)
276 return false;
277
278 base::LaunchOptions options;
279 options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
280 base::Process process = base::LaunchProcess(argv, options);
281 close(devnull);
282 if (!process.IsValid())
283 return false;
284 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
285 return process.WaitForExit(exit_code);
286}
287
288std::string GetWMClassFromAppName(std::string app_name) {
289 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
290 base::TrimString(app_name, "_", &app_name);
291 return app_name;
292}
293
Alexey Baskakovf302efe2018-07-28 02:02:32294base::FilePath GetDataWriteLocation(base::Environment* env) {
295 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
296}
297
298std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
Etienne Pierre-doraye6c535312018-08-28 15:45:39299 base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
Alexey Baskakovf302efe2018-07-28 02:02:32300
301 std::vector<base::FilePath> search_paths;
302 base::FilePath write_location = GetDataWriteLocation(env);
303 search_paths.push_back(write_location);
304
305 std::string xdg_data_dirs;
306 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
307 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
308 while (tokenizer.GetNext()) {
309 base::FilePath data_dir(tokenizer.token());
310 search_paths.push_back(data_dir);
311 }
312 } else {
313 search_paths.push_back(base::FilePath("/usr/local/share"));
314 search_paths.push_back(base::FilePath("/usr/share"));
315 }
316
317 return search_paths;
318}
319
320namespace internal {
321
[email protected]d81a63c02013-03-07 08:49:04322// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
323// file, given in |shortcut_contents|. If the key is not found, returns false.
324bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
[email protected]73bae9d2014-05-11 00:13:55325#if defined(USE_GLIB)
[email protected]d81a63c02013-03-07 08:49:04326 // An empty file causes a crash with glib <= 2.32, so special case here.
327 if (shortcut_contents.empty())
328 return false;
329
330 GKeyFile* key_file = g_key_file_new();
331 GError* err = NULL;
332 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
333 shortcut_contents.size(), G_KEY_FILE_NONE,
334 &err)) {
335 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
336 g_error_free(err);
337 g_key_file_free(key_file);
338 return false;
339 }
340
341 bool nodisplay = false;
342 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
343 "NoDisplay", &err);
344 if (nodisplay_c_string) {
345 if (!g_strcmp0(nodisplay_c_string, "true"))
346 nodisplay = true;
347 g_free(nodisplay_c_string);
[email protected]1381af52013-11-01 19:47:32348 } else {
349 g_error_free(err);
[email protected]d81a63c02013-03-07 08:49:04350 }
351
352 g_key_file_free(key_file);
353 return nodisplay;
[email protected]73bae9d2014-05-11 00:13:55354#else
355 NOTIMPLEMENTED();
356 return false;
357#endif
[email protected]d81a63c02013-03-07 08:49:04358}
359
[email protected]fcd21d322013-06-27 12:35:56360// Gets the path to the Chrome executable or wrapper script.
thestigd2b1fcf2015-01-21 22:11:49361// Returns an empty path if the executable path could not be found, which should
362// never happen.
[email protected]fcd21d322013-06-27 12:35:56363base::FilePath GetChromeExePath() {
364 // Try to get the name of the wrapper script that launched Chrome.
dcheng4af48582016-04-19 00:29:35365 std::unique_ptr<base::Environment> environment(base::Environment::Create());
[email protected]fcd21d322013-06-27 12:35:56366 std::string wrapper_script;
thestigd2b1fcf2015-01-21 22:11:49367 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
[email protected]fcd21d322013-06-27 12:35:56368 return base::FilePath(wrapper_script);
[email protected]fcd21d322013-06-27 12:35:56369
370 // Just return the name of the executable path for Chrome.
371 base::FilePath chrome_exe_path;
Avi Drissman9098f9002018-05-04 00:11:52372 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
[email protected]fcd21d322013-06-27 12:35:56373 return chrome_exe_path;
374}
375
thomasanderson12d87582016-07-29 21:17:41376std::string GetProgramClassName(const base::CommandLine& command_line,
377 const std::string& desktop_file_name) {
Alexey Baskakovb13491b2018-08-03 07:46:38378 std::string class_name = GetDesktopBaseName(desktop_file_name);
thomasanderson12d87582016-07-29 21:17:41379 std::string user_data_dir =
380 command_line.GetSwitchValueNative(switches::kUserDataDir);
381 // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
382 // class name to "Chrome (/tmp/my-user-data)". The class name will show up in
383 // the alt-tab list in gnome-shell if you're running a binary that doesn't
384 // have a matching .desktop file.
385 return user_data_dir.empty()
386 ? class_name
387 : class_name + " (" + user_data_dir + ")";
388}
389
390std::string GetProgramClassClass(const base::CommandLine& command_line,
391 const std::string& desktop_file_name) {
392 if (command_line.HasSwitch(switches::kWmClass))
393 return command_line.GetSwitchValueASCII(switches::kWmClass);
Alexey Baskakovb13491b2018-08-03 07:46:38394 std::string class_class = GetDesktopBaseName(desktop_file_name);
thomasanderson12d87582016-07-29 21:17:41395 if (!class_class.empty()) {
396 // Capitalize the first character like gtk does.
397 class_class[0] = base::ToUpperASCII(class_class[0]);
398 }
399 return class_class;
400}
401
402} // namespace internal
403
[email protected]f93a77452013-09-02 05:26:35404std::string GetProgramClassName() {
dcheng4af48582016-04-19 00:29:35405 std::unique_ptr<base::Environment> env(base::Environment::Create());
thomasanderson12d87582016-07-29 21:17:41406 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
407 GetDesktopName(env.get()));
408}
409
410std::string GetProgramClassClass() {
411 std::unique_ptr<base::Environment> env(base::Environment::Create());
412 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
413 GetDesktopName(env.get()));
[email protected]f93a77452013-09-02 05:26:35414}
415
[email protected]98566d7a2012-04-17 00:28:56416std::string GetDesktopName(base::Environment* env) {
417#if defined(GOOGLE_CHROME_BUILD)
sdefresne9fb67692015-08-03 18:48:22418 version_info::Channel product_channel(chrome::GetChannel());
[email protected]81b349002014-03-04 18:42:58419 switch (product_channel) {
sdefresne6e883e42015-07-30 08:05:54420 case version_info::Channel::DEV:
[email protected]81b349002014-03-04 18:42:58421 return "google-chrome-unstable.desktop";
sdefresne6e883e42015-07-30 08:05:54422 case version_info::Channel::BETA:
[email protected]81b349002014-03-04 18:42:58423 return "google-chrome-beta.desktop";
424 default:
425 return "google-chrome.desktop";
426 }
[email protected]98566d7a2012-04-17 00:28:56427#else // CHROMIUM_BUILD
428 // Allow $CHROME_DESKTOP to override the built-in value, so that development
429 // versions can set themselves as the default without interfering with
430 // non-official, packaged versions using the built-in value.
431 std::string name;
432 if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
433 return name;
434 return "chromium-browser.desktop";
435#endif
436}
437
[email protected]14fbaed2013-05-02 07:54:02438std::string GetIconName() {
439#if defined(GOOGLE_CHROME_BUILD)
440 return "google-chrome";
441#else // CHROMIUM_BUILD
442 return "chromium-browser";
443#endif
444}
445
[email protected]d81a63c02013-03-07 08:49:04446bool GetExistingShortcutContents(base::Environment* env,
447 const base::FilePath& desktop_filename,
448 std::string* output) {
Etienne Pierre-doraye6c535312018-08-28 15:45:39449 base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
[email protected]620942e2010-02-16 10:12:12450
[email protected]111f0282013-08-05 10:09:29451 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
[email protected]b96aa932009-08-12 21:34:49452
[email protected]650b2d52013-02-10 03:41:45453 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49454 i != search_paths.end(); ++i) {
[email protected]d81a63c02013-03-07 08:49:04455 base::FilePath path = i->Append("applications").Append(desktop_filename);
456 VLOG(1) << "Looking for desktop file in " << path.value();
[email protected]7567484142013-07-11 17:36:07457 if (base::PathExists(path)) {
[email protected]d81a63c02013-03-07 08:49:04458 VLOG(1) << "Found desktop file at " << path.value();
[email protected]82f84b92013-08-30 18:23:50459 return base::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12460 }
[email protected]b96aa932009-08-12 21:34:49461 }
462
463 return false;
464}
465
[email protected]650b2d52013-02-10 03:41:45466base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44467 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12468 std::string filename =
[email protected]4f260d02010-12-23 18:35:42469 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]6bc03de2014-08-07 23:59:15470 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49471
[email protected]650b2d52013-02-10 03:41:45472 base::FilePath desktop_path;
Avi Drissman9098f9002018-05-04 00:11:52473 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45474 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10475
[email protected]650b2d52013-02-10 03:41:45476 base::FilePath filepath = desktop_path.Append(filename);
477 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10478 for (size_t i = 1; i < 100; ++i) {
[email protected]7567484142013-07-11 17:36:07479 if (base::PathExists(base::FilePath(alternative_filepath))) {
[email protected]650b2d52013-02-10 03:41:45480 alternative_filepath = base::FilePath(
Brett Wilson5accd242017-11-30 22:07:32481 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10482 } else {
[email protected]650b2d52013-02-10 03:41:45483 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10484 }
485 }
486
[email protected]650b2d52013-02-10 03:41:45487 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49488}
489
[email protected]111f0282013-08-05 10:09:29490std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
491 const base::FilePath& profile_path,
492 const base::FilePath& directory) {
Etienne Pierre-doraye6c535312018-08-28 15:45:39493 base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
thestigd2b1fcf2015-01-21 22:11:49494
[email protected]111f0282013-08-05 10:09:29495 // Use a prefix, because xdg-desktop-menu requires it.
496 std::string prefix(chrome::kBrowserProcessExecutableName);
497 prefix.append("-");
498 std::string suffix("-");
499 suffix.append(profile_path.BaseName().value());
[email protected]6bc03de2014-08-07 23:59:15500 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
[email protected]111f0282013-08-05 10:09:29501 // Spaces in filenames break xdg-desktop-menu
502 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
[email protected]466c9862013-12-03 22:05:28503 base::ReplaceChars(suffix, " ", "_", &suffix);
[email protected]111f0282013-08-05 10:09:29504 std::string glob = prefix + "*" + suffix + ".desktop";
505
506 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
507 glob);
508 base::FilePath shortcut_file = files.Next();
509 std::vector<base::FilePath> shortcut_paths;
510 while (!shortcut_file.empty()) {
511 shortcut_paths.push_back(shortcut_file.BaseName());
512 shortcut_file = files.Next();
513 }
514 return shortcut_paths;
515}
516
[email protected]98566d7a2012-04-17 00:28:56517std::string GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02518 const base::FilePath& chrome_exe_path,
[email protected]a0b60cfd2011-04-06 18:02:41519 const std::string& app_name,
520 const GURL& url,
521 const std::string& extension_id,
[email protected]a04db822013-12-11 19:14:40522 const base::string16& title,
[email protected]5951c852012-06-20 00:12:53523 const std::string& icon_name,
[email protected]d81a63c02013-03-07 08:49:04524 const base::FilePath& profile_path,
[email protected]4a7896822014-04-25 23:11:43525 const std::string& categories,
[email protected]2164e512014-01-22 09:32:10526 bool no_display) {
pmonette9fa59e882016-02-10 00:12:19527 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
528 url, extension_id, profile_path);
[email protected]2164e512014-01-22 09:32:10529 cmd_line.SetProgram(chrome_exe_path);
530 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
[email protected]4a7896822014-04-25 23:11:43531 icon_name, categories, no_display);
[email protected]2164e512014-01-22 09:32:10532}
533
534std::string GetDesktopFileContentsForCommand(
avi556c05022014-12-22 23:31:43535 const base::CommandLine& command_line,
[email protected]2164e512014-01-22 09:32:10536 const std::string& app_name,
537 const GURL& url,
538 const base::string16& title,
539 const std::string& icon_name,
[email protected]4a7896822014-04-25 23:11:43540 const std::string& categories,
[email protected]2164e512014-01-22 09:32:10541 bool no_display) {
[email protected]73bae9d2014-05-11 00:13:55542#if defined(USE_GLIB)
[email protected]b9eb4e52013-02-05 00:01:49543 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
544 // launchers with an xdg-open shebang. Follow that convention.
545 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20546
[email protected]b96aa932009-08-12 21:34:49547 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20548 GKeyFile* key_file = g_key_file_new();
[email protected]0a96c3f2011-05-11 22:10:20549
[email protected]14fbaed2013-05-02 07:54:02550 // Set keys with fixed values.
551 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
552 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
553 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
[email protected]0a96c3f2011-05-11 22:10:20554
555 // Set the "Name" key.
[email protected]f911df52013-12-24 23:24:23556 std::string final_title = base::UTF16ToUTF8(title);
[email protected]0a96c3f2011-05-11 22:10:20557 // Make sure no endline characters can slip in and possibly introduce
558 // additional lines (like Exec, which makes it a security risk). Also
559 // use the URL as a default when the title is empty.
560 if (final_title.empty() ||
561 final_title.find("\n") != std::string::npos ||
562 final_title.find("\r") != std::string::npos) {
563 final_title = url.spec();
564 }
565 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
566
567 // Set the "Exec" key.
[email protected]2164e512014-01-22 09:32:10568 std::string final_path = QuoteCommandLineForDesktopFileExec(command_line);
[email protected]14fbaed2013-05-02 07:54:02569 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
570
[email protected]0a96c3f2011-05-11 22:10:20571 // Set the "Icon" key.
[email protected]14fbaed2013-05-02 07:54:02572 if (!icon_name.empty()) {
[email protected]0a96c3f2011-05-11 22:10:20573 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]14fbaed2013-05-02 07:54:02574 } else {
575 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
576 GetIconName().c_str());
577 }
[email protected]a0b60cfd2011-04-06 18:02:41578
[email protected]4a7896822014-04-25 23:11:43579 // Set the "Categories" key.
580 if (!categories.empty()) {
581 g_key_file_set_string(
582 key_file, kDesktopEntry, "Categories", categories.c_str());
583 }
584
[email protected]d81a63c02013-03-07 08:49:04585 // Set the "NoDisplay" key.
586 if (no_display)
587 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
588
Alexey Baskakovb13491b2018-08-03 07:46:38589 std::string wmclass = GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20590 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
591 wmclass.c_str());
[email protected]a0b60cfd2011-04-06 18:02:41592
[email protected]14fbaed2013-05-02 07:54:02593 gsize length = 0;
[email protected]0a96c3f2011-05-11 22:10:20594 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
595 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49596 // If strlen(data_dump[0]) == 0, this check will fail.
597 if (data_dump[0] == '\n') {
598 // Older versions of glib produce a leading newline. If this is the case,
599 // remove it to avoid double-newline after the shebang.
600 output_buffer += (data_dump + 1);
601 } else {
602 output_buffer += data_dump;
603 }
[email protected]0a96c3f2011-05-11 22:10:20604 g_free(data_dump);
605 }
606
607 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49608 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55609#else
610 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09611 return std::string();
[email protected]73bae9d2014-05-11 00:13:55612#endif
[email protected]b96aa932009-08-12 21:34:49613}
614
[email protected]a04db822013-12-11 19:14:40615std::string GetDirectoryFileContents(const base::string16& title,
[email protected]a3c25952013-05-02 13:16:06616 const std::string& icon_name) {
[email protected]73bae9d2014-05-11 00:13:55617#if defined(USE_GLIB)
[email protected]a3c25952013-05-02 13:16:06618 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
619 GKeyFile* key_file = g_key_file_new();
620
621 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
622 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
[email protected]f911df52013-12-24 23:24:23623 std::string final_title = base::UTF16ToUTF8(title);
[email protected]a3c25952013-05-02 13:16:06624 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
625 if (!icon_name.empty()) {
626 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
627 } else {
628 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
629 GetIconName().c_str());
630 }
631
632 gsize length = 0;
633 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
634 std::string output_buffer;
635 if (data_dump) {
636 // If strlen(data_dump[0]) == 0, this check will fail.
637 if (data_dump[0] == '\n') {
638 // Older versions of glib produce a leading newline. If this is the case,
639 // remove it to avoid double-newline after the shebang.
640 output_buffer += (data_dump + 1);
641 } else {
642 output_buffer += data_dump;
643 }
644 g_free(data_dump);
645 }
646
647 g_key_file_free(key_file);
648 return output_buffer;
[email protected]73bae9d2014-05-11 00:13:55649#else
650 NOTIMPLEMENTED();
[email protected]06bfeb12014-05-27 14:00:09651 return std::string();
[email protected]73bae9d2014-05-11 00:13:55652#endif
[email protected]a3c25952013-05-02 13:16:06653}
654
brettwd1bc9da2016-10-14 19:04:24655#if BUILDFLAG(ENABLE_APP_LIST)
[email protected]7199367d2014-01-20 04:06:21656bool CreateAppListDesktopShortcut(
657 const std::string& wm_class,
658 const std::string& title) {
Etienne Pierre-doraye6c535312018-08-28 15:45:39659 base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
[email protected]7199367d2014-01-20 04:06:21660
661 base::FilePath desktop_name(kAppListDesktopName);
662 base::FilePath shortcut_filename = desktop_name.AddExtension("desktop");
663
664 // We do not want duplicate shortcuts. Delete any that already exist and
665 // replace them.
666 DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
667
668 base::FilePath chrome_exe_path = GetChromeExePath();
669 if (chrome_exe_path.empty()) {
thestigd2b1fcf2015-01-21 22:11:49670 NOTREACHED();
[email protected]7199367d2014-01-20 04:06:21671 return false;
672 }
673
674 gfx::ImageFamily icon_images;
Lei Zhang7640d542017-10-03 16:26:49675 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
[email protected]7199367d2014-01-20 04:06:21676 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
677 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
678 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_48));
679 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
680 std::string icon_name = CreateShortcutIcon(icon_images, desktop_name);
681
avi556c05022014-12-22 23:31:43682 base::CommandLine command_line(chrome_exe_path);
[email protected]2164e512014-01-22 09:32:10683 command_line.AppendSwitch(switches::kShowAppList);
[email protected]4a7896822014-04-25 23:11:43684 std::string contents =
685 GetDesktopFileContentsForCommand(command_line,
686 wm_class,
687 GURL(),
688 base::UTF8ToUTF16(title),
689 icon_name,
690 kAppListCategories,
691 false);
[email protected]7199367d2014-01-20 04:06:21692 return CreateShortcutInApplicationsMenu(
693 shortcut_filename, contents, base::FilePath(), "");
694}
thestig06a90372016-07-18 22:04:11695#endif
[email protected]7199367d2014-01-20 04:06:21696
[email protected]06bfeb12014-05-27 14:00:09697} // namespace shell_integration_linux
Alexey Baskakovb13491b2018-08-03 07:46:38698
699namespace shell_integration {
700
701bool SetAsDefaultBrowser() {
702 return shell_integration_linux::SetDefaultWebClient(std::string());
703}
704
705bool SetAsDefaultProtocolClient(const std::string& protocol) {
706 return shell_integration_linux::SetDefaultWebClient(protocol);
707}
708
709DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
710 return SET_DEFAULT_UNATTENDED;
711}
712
713base::string16 GetApplicationNameForProtocol(const GURL& url) {
714 return base::ASCIIToUTF16("xdg-open");
715}
716
717DefaultWebClientState GetDefaultBrowser() {
718 return shell_integration_linux::GetIsDefaultWebClient(std::string());
719}
720
721bool IsFirefoxDefaultBrowser() {
722 std::vector<std::string> argv;
723 argv.push_back(shell_integration_linux::kXdgSettings);
724 argv.push_back("get");
725 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
726
727 std::string browser;
728 // We don't care about the return value here.
729 base::GetAppOutput(base::CommandLine(argv), &browser);
730 return browser.find("irefox") != std::string::npos;
731}
732
733DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
734 return shell_integration_linux::GetIsDefaultWebClient(protocol);
735}
736
737} // namespace shell_integration