1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/shell_integration_linux.h"
11 #include <sys/types.h>
20 #include "base/base_paths.h"
21 #include "base/command_line.h"
22 #include "base/environment.h"
23 #include "base/files/file_enumerator.h"
24 #include "base/files/file_path.h"
25 #include "base/files/file_util.h"
26 #include "base/i18n/file_util_icu.h"
27 #include "base/logging.h"
28 #include "base/memory/ref_counted_memory.h"
29 #include "base/nix/xdg_util.h"
30 #include "base/path_service.h"
31 #include "base/posix/eintr_wrapper.h"
32 #include "base/process/kill.h"
33 #include "base/process/launch.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/string_tokenizer.h"
36 #include "base/strings/string_util.h"
37 #include "base/strings/stringprintf.h"
38 #include "base/strings/utf_string_conversions.h"
39 #include "base/threading/scoped_blocking_call.h"
40 #include "base/threading/thread.h"
41 #include "base/threading/thread_restrictions.h"
42 #include "build/branding_buildflags.h"
43 #include "build/build_config.h"
44 #include "chrome/browser/shell_integration.h"
45 #include "chrome/common/buildflags.h"
46 #include "chrome/common/channel_info.h"
47 #include "chrome/common/chrome_constants.h"
48 #include "chrome/common/chrome_switches.h"
49 #include "chrome/grit/chrome_unscaled_resources.h"
50 #include "components/version_info/version_info.h"
51 #include "ui/base/resource/resource_bundle.h"
52 #include "ui/gfx/image/image_family.h"
59 namespace shell_integration_linux {
61 const char kXdgSettings[] = "xdg-settings";
62 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
63 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
65 // Utility function to get the path to the version of a script shipped with
66 // Chrome. |script| gives the name of the script. |chrome_version| returns the
67 // path to the Chrome version of the script, and the return value of the
68 // function is true if the function is successful and the Chrome version is
69 // not the script found on the PATH.
70 bool GetChromeVersionOfScript(const std::string& script,
71 std::string* chrome_version) {
72 // Get the path to the Chrome version.
73 base::FilePath chrome_dir;
74 if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
77 base::FilePath chrome_version_path = chrome_dir.Append(script);
78 *chrome_version = chrome_version_path.value();
80 // Check if this is different to the one on path.
81 std::vector<std::string> argv;
82 argv.push_back("which");
83 argv.push_back(script);
84 std::string path_version;
85 if (base::GetAppOutput(base::CommandLine(argv), &path_version)) {
86 // Remove trailing newline
87 path_version.erase(path_version.length() - 1, 1);
88 base::FilePath path_version_path(path_version);
89 return (chrome_version_path != path_version_path);
94 // Value returned by xdg-settings if it can't understand our request.
95 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
97 // We delegate the difficulty of setting the default browser and default url
98 // scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
100 // When calling this script we first try to use the script on PATH. If that
101 // fails we then try to use the script that we have included. This gives
102 // scripts on the system priority over ours, as distribution vendors may have
103 // tweaked the script, but still allows our copy to be used if the script on the
104 // system fails, as the system copy may be missing capabilities of the Chrome
107 // If |protocol| is empty this function sets Chrome as the default browser,
108 // otherwise it sets Chrome as the default handler application for |protocol|.
109 bool SetDefaultWebClient(const std::string& protocol) {
110 #if defined(OS_CHROMEOS)
113 std::unique_ptr<base::Environment> env(base::Environment::Create());
115 std::vector<std::string> argv;
116 argv.push_back(kXdgSettings);
117 argv.push_back("set");
118 if (protocol.empty()) {
119 argv.push_back(kXdgSettingsDefaultBrowser);
121 argv.push_back(kXdgSettingsDefaultSchemeHandler);
122 argv.push_back(protocol);
124 argv.push_back(chrome::GetDesktopName(env.get()));
127 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
128 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
129 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
130 ran_ok = LaunchXdgUtility(argv, &exit_code);
134 return ran_ok && exit_code == EXIT_SUCCESS;
138 // If |protocol| is empty this function checks if Chrome is the default browser,
139 // otherwise it checks if Chrome is the default handler application for
141 shell_integration::DefaultWebClientState GetIsDefaultWebClient(
142 const std::string& protocol) {
143 #if defined(OS_CHROMEOS)
144 return shell_integration::UNKNOWN_DEFAULT;
146 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
147 base::BlockingType::MAY_BLOCK);
149 std::unique_ptr<base::Environment> env(base::Environment::Create());
151 std::vector<std::string> argv;
152 argv.push_back(kXdgSettings);
153 argv.push_back("check");
154 if (protocol.empty()) {
155 argv.push_back(kXdgSettingsDefaultBrowser);
157 argv.push_back(kXdgSettingsDefaultSchemeHandler);
158 argv.push_back(protocol);
160 argv.push_back(chrome::GetDesktopName(env.get()));
164 bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
166 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
167 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
168 ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
173 if (!ran_ok || success_code != EXIT_SUCCESS) {
174 // xdg-settings failed: we can't determine or set the default browser.
175 return shell_integration::UNKNOWN_DEFAULT;
178 // Allow any reply that starts with "yes".
179 return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
180 ? shell_integration::IS_DEFAULT
181 : shell_integration::NOT_DEFAULT;
185 // https://wiki.gnome.org/Projects/GnomeShell/ApplicationBased
186 // The WM_CLASS property should be set to the same as the *.desktop file without
187 // the .desktop extension. We cannot simply use argv[0] in this case, because
188 // on the stable channel, the executable name is google-chrome-stable, but the
189 // desktop file is google-chrome.desktop.
190 std::string GetDesktopBaseName(const std::string& desktop_file_name) {
191 static const char kDesktopExtension[] = ".desktop";
192 if (base::EndsWith(desktop_file_name, kDesktopExtension,
193 base::CompareCase::SENSITIVE)) {
194 return desktop_file_name.substr(
195 0, desktop_file_name.length() - strlen(kDesktopExtension));
197 return desktop_file_name;
202 #if defined(USE_GLIB)
203 // Quote a string such that it appears as one verbatim argument for the Exec
204 // key in a desktop file.
205 std::string QuoteArgForDesktopFileExec(const std::string& arg) {
206 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
208 // Quoting is only necessary if the argument has a reserved character.
209 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
210 return arg; // No quoting necessary.
212 std::string quoted = "\"";
213 for (size_t i = 0; i < arg.size(); ++i) {
214 // Note that the set of backslashed characters is smaller than the
215 // set of reserved characters.
231 // Quote a command line so it is suitable for use as the Exec key in a desktop
232 // file. Note: This should be used instead of GetCommandLineString, which does
233 // not properly quote the string; this function is designed for the Exec key.
234 std::string QuoteCommandLineForDesktopFileExec(
235 const base::CommandLine& command_line) {
236 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
238 std::string quoted_path;
239 const base::CommandLine::StringVector& argv = command_line.argv();
240 for (auto i = argv.begin(); i != argv.end(); ++i) {
241 if (i != argv.begin())
243 quoted_path += QuoteArgForDesktopFileExec(*i);
250 #if defined(USE_GLIB)
251 const char kDesktopEntry[] = "Desktop Entry";
252 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
257 // Allows LaunchXdgUtility to join a process.
258 // thread_restrictions.h assumes it to be in shell_integration_linux namespace.
259 class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
260 : public base::ScopedAllowBaseSyncPrimitives {};
262 bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
263 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
264 // files on top of originals after making changes to them. In the event that
265 // the original files are owned by another user (e.g. root, which can happen
266 // if they are updated within sudo), mv will prompt the user to confirm if
267 // standard input is a terminal (otherwise it just does it). So make sure it's
268 // not, to avoid locking everything up waiting for mv.
269 *exit_code = EXIT_FAILURE;
270 int devnull = open("/dev/null", O_RDONLY);
274 base::LaunchOptions options;
275 options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
276 base::Process process = base::LaunchProcess(argv, options);
278 if (!process.IsValid())
280 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
281 return process.WaitForExit(exit_code);
284 std::string GetWMClassFromAppName(std::string app_name) {
285 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
286 base::TrimString(app_name, "_", &app_name);
290 base::FilePath GetDataWriteLocation(base::Environment* env) {
291 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
294 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
295 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
296 base::BlockingType::MAY_BLOCK);
298 std::vector<base::FilePath> search_paths;
299 base::FilePath write_location = GetDataWriteLocation(env);
300 search_paths.push_back(write_location);
302 std::string xdg_data_dirs;
303 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
304 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
305 while (tokenizer.GetNext()) {
306 base::FilePath data_dir(tokenizer.token());
307 search_paths.push_back(data_dir);
310 search_paths.push_back(base::FilePath("/usr/local/share"));
311 search_paths.push_back(base::FilePath("/usr/share"));
319 // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
320 // file, given in |shortcut_contents|. If the key is not found, returns false.
321 bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
322 #if defined(USE_GLIB)
323 // An empty file causes a crash with glib <= 2.32, so special case here.
324 if (shortcut_contents.empty())
327 GKeyFile* key_file = g_key_file_new();
329 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
330 shortcut_contents.size(), G_KEY_FILE_NONE,
332 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
334 g_key_file_free(key_file);
338 bool nodisplay = false;
339 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
341 if (nodisplay_c_string) {
342 if (!g_strcmp0(nodisplay_c_string, "true"))
344 g_free(nodisplay_c_string);
349 g_key_file_free(key_file);
357 // Gets the path to the Chrome executable or wrapper script.
358 // Returns an empty path if the executable path could not be found, which should
360 base::FilePath GetChromeExePath() {
361 // Try to get the name of the wrapper script that launched Chrome.
362 std::unique_ptr<base::Environment> environment(base::Environment::Create());
363 std::string wrapper_script;
364 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
365 return base::FilePath(wrapper_script);
367 // Just return the name of the executable path for Chrome.
368 base::FilePath chrome_exe_path;
369 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
370 return chrome_exe_path;
373 std::string GetProgramClassName(const base::CommandLine& command_line,
374 const std::string& desktop_file_name) {
375 std::string class_name = GetDesktopBaseName(desktop_file_name);
376 std::string user_data_dir =
377 command_line.GetSwitchValueNative(switches::kUserDataDir);
378 // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
379 // class name to "Chrome (/tmp/my-user-data)". The class name will show up in
380 // the alt-tab list in gnome-shell if you're running a binary that doesn't
381 // have a matching .desktop file.
382 return user_data_dir.empty()
384 : class_name + " (" + user_data_dir + ")";
387 std::string GetProgramClassClass(const base::CommandLine& command_line,
388 const std::string& desktop_file_name) {
389 if (command_line.HasSwitch(switches::kWmClass))
390 return command_line.GetSwitchValueASCII(switches::kWmClass);
391 std::string class_class = GetDesktopBaseName(desktop_file_name);
392 if (!class_class.empty()) {
393 // Capitalize the first character like gtk does.
394 class_class[0] = base::ToUpperASCII(class_class[0]);
399 } // namespace internal
401 std::string GetProgramClassName() {
402 std::unique_ptr<base::Environment> env(base::Environment::Create());
403 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
404 chrome::GetDesktopName(env.get()));
407 std::string GetProgramClassClass() {
408 std::unique_ptr<base::Environment> env(base::Environment::Create());
409 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
410 chrome::GetDesktopName(env.get()));
413 std::string GetIconName() {
414 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
415 return "google-chrome";
416 #else // BUILDFLAG(CHROMIUM_BRANDING)
417 return "chromium-browser";
421 bool GetExistingShortcutContents(base::Environment* env,
422 const base::FilePath& desktop_filename,
423 std::string* output) {
424 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
425 base::BlockingType::MAY_BLOCK);
427 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
429 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
430 i != search_paths.end(); ++i) {
431 base::FilePath path = i->Append("applications").Append(desktop_filename);
432 VLOG(1) << "Looking for desktop file in " << path.value();
433 if (base::PathExists(path)) {
434 VLOG(1) << "Found desktop file at " << path.value();
435 return base::ReadFileToString(path, output);
442 base::FilePath GetWebShortcutFilename(const GURL& url) {
443 // Use a prefix, because xdg-desktop-menu requires it.
444 std::string filename =
445 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
446 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
448 base::FilePath desktop_path;
449 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
450 return base::FilePath();
452 base::FilePath filepath = desktop_path.Append(filename);
453 base::FilePath alternative_filepath(filepath.value() + ".desktop");
454 for (size_t i = 1; i < 100; ++i) {
455 if (base::PathExists(base::FilePath(alternative_filepath))) {
456 alternative_filepath = base::FilePath(
457 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
459 return base::FilePath(alternative_filepath).BaseName();
463 return base::FilePath();
466 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
467 const base::FilePath& profile_path,
468 const base::FilePath& directory) {
469 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
470 base::BlockingType::MAY_BLOCK);
472 // Use a prefix, because xdg-desktop-menu requires it.
473 std::string prefix(chrome::kBrowserProcessExecutableName);
475 std::string suffix("-");
476 suffix.append(profile_path.BaseName().value());
477 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
478 // Spaces in filenames break xdg-desktop-menu
479 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
480 base::ReplaceChars(suffix, " ", "_", &suffix);
481 std::string glob = prefix + "*" + suffix + ".desktop";
483 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
485 base::FilePath shortcut_file = files.Next();
486 std::vector<base::FilePath> shortcut_paths;
487 while (!shortcut_file.empty()) {
488 shortcut_paths.push_back(shortcut_file.BaseName());
489 shortcut_file = files.Next();
491 return shortcut_paths;
494 std::string GetDesktopFileContents(const base::FilePath& chrome_exe_path,
495 const std::string& app_name,
497 const std::string& extension_id,
498 const base::string16& title,
499 const std::string& icon_name,
500 const base::FilePath& profile_path,
501 const std::string& categories,
502 const std::string& mime_type,
504 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
505 url, extension_id, profile_path);
506 cmd_line.SetProgram(chrome_exe_path);
507 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
508 icon_name, categories, mime_type,
512 std::string GetDesktopFileContentsForCommand(
513 const base::CommandLine& command_line,
514 const std::string& app_name,
516 const base::string16& title,
517 const std::string& icon_name,
518 const std::string& categories,
519 const std::string& mime_type,
521 #if defined(USE_GLIB)
522 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
523 // launchers with an xdg-open shebang. Follow that convention.
524 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
526 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
527 GKeyFile* key_file = g_key_file_new();
529 // Set keys with fixed values.
530 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
531 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
532 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
534 // Set the "Name" key.
535 std::string final_title = base::UTF16ToUTF8(title);
536 // Make sure no endline characters can slip in and possibly introduce
537 // additional lines (like Exec, which makes it a security risk). Also
538 // use the URL as a default when the title is empty.
539 if (final_title.empty() ||
540 final_title.find("\n") != std::string::npos ||
541 final_title.find("\r") != std::string::npos) {
542 final_title = url.spec();
544 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
546 base::CommandLine modified_command_line(command_line);
548 // Set the "MimeType" key.
549 if (!mime_type.empty() && mime_type.find("\n") == std::string::npos &&
550 mime_type.find("\r") == std::string::npos) {
551 g_key_file_set_string(key_file, kDesktopEntry, "MimeType",
554 // Some Linux Desktop Environments don't show file handlers unless they
555 // specify where to place file arguments.
556 // Note: We only include this parameter if the application is actually able
557 // to handle files, to prevent it showing up in the list of all applications
558 // which can handle files.
559 modified_command_line.AppendArg("%F");
562 // Set the "Exec" key.
563 std::string final_path =
564 QuoteCommandLineForDesktopFileExec(modified_command_line);
565 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
567 // Set the "Icon" key.
568 if (!icon_name.empty()) {
569 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
571 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
572 GetIconName().c_str());
575 // Set the "Categories" key.
576 if (!categories.empty()) {
577 g_key_file_set_string(
578 key_file, kDesktopEntry, "Categories", categories.c_str());
581 // Set the "NoDisplay" key.
583 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
585 std::string wmclass = GetWMClassFromAppName(app_name);
586 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
590 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
592 // If strlen(data_dump[0]) == 0, this check will fail.
593 if (data_dump[0] == '\n') {
594 // Older versions of glib produce a leading newline. If this is the case,
595 // remove it to avoid double-newline after the shebang.
596 output_buffer += (data_dump + 1);
598 output_buffer += data_dump;
603 g_key_file_free(key_file);
604 return output_buffer;
607 return std::string();
611 std::string GetDirectoryFileContents(const base::string16& title,
612 const std::string& icon_name) {
613 #if defined(USE_GLIB)
614 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
615 GKeyFile* key_file = g_key_file_new();
617 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
618 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
619 std::string final_title = base::UTF16ToUTF8(title);
620 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
621 if (!icon_name.empty()) {
622 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
624 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
625 GetIconName().c_str());
629 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
630 std::string output_buffer;
632 // If strlen(data_dump[0]) == 0, this check will fail.
633 if (data_dump[0] == '\n') {
634 // Older versions of glib produce a leading newline. If this is the case,
635 // remove it to avoid double-newline after the shebang.
636 output_buffer += (data_dump + 1);
638 output_buffer += data_dump;
643 g_key_file_free(key_file);
644 return output_buffer;
647 return std::string();
651 base::FilePath GetMimeTypesRegistrationFilename(
652 const base::FilePath& profile_path,
653 const web_app::AppId& app_id) {
654 DCHECK(!profile_path.empty() && !app_id.empty());
656 // Use a prefix to clearly group files created by Chrome.
657 std::string filename = base::StringPrintf(
658 "%s-%s-%s%s", chrome::kBrowserProcessExecutableName, app_id.c_str(),
659 profile_path.BaseName().value().c_str(), ".xml");
661 // Replace illegal characters and spaces in |filename|.
662 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
663 base::ReplaceChars(filename, " ", "_", &filename);
665 return base::FilePath(filename);
668 std::string GetMimeTypesRegistrationFileContents(
669 const apps::FileHandlers& file_handlers) {
670 std::stringstream ss;
671 ss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
673 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n";
675 for (const auto& file_handler : file_handlers) {
676 for (const auto& accept_entry : file_handler.accept) {
677 ss << " <mime-type type=\"" << accept_entry.mime_type + "\">\n";
678 for (const auto& file_extension : accept_entry.file_extensions)
679 ss << " <glob pattern=\"*" << file_extension << "\"/>\n";
680 ss << " </mime-type>\n";
684 ss << "</mime-info>\n";
688 } // namespace shell_integration_linux
690 namespace shell_integration {
692 bool SetAsDefaultBrowser() {
693 return shell_integration_linux::SetDefaultWebClient(std::string());
696 bool SetAsDefaultProtocolClient(const std::string& protocol) {
697 return shell_integration_linux::SetDefaultWebClient(protocol);
700 DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
701 return SET_DEFAULT_UNATTENDED;
704 base::string16 GetApplicationNameForProtocol(const GURL& url) {
705 return base::ASCIIToUTF16("xdg-open");
708 DefaultWebClientState GetDefaultBrowser() {
709 return shell_integration_linux::GetIsDefaultWebClient(std::string());
712 bool IsFirefoxDefaultBrowser() {
713 std::vector<std::string> argv;
714 argv.push_back(shell_integration_linux::kXdgSettings);
715 argv.push_back("get");
716 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
719 // We don't care about the return value here.
720 base::GetAppOutput(base::CommandLine(argv), &browser);
721 return browser.find("irefox") != std::string::npos;
724 DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
725 return shell_integration_linux::GetIsDefaultWebClient(protocol);
728 } // namespace shell_integration