1 // Copyright 2012 The Chromium Authors
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 "build/chromeos_buildflags.h"
45 #include "chrome/browser/shell_integration.h"
46 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
47 #include "chrome/browser/web_applications/web_app_helpers.h"
48 #include "chrome/common/buildflags.h"
49 #include "chrome/common/channel_info.h"
50 #include "chrome/common/chrome_constants.h"
51 #include "chrome/common/chrome_switches.h"
52 #include "chrome/grit/chrome_unscaled_resources.h"
53 #include "components/version_info/version_info.h"
54 #include "third_party/libxml/chromium/xml_writer.h"
55 #include "third_party/re2/src/re2/re2.h"
56 #include "ui/base/resource/resource_bundle.h"
57 #include "ui/gfx/image/image_family.h"
58 #include "ui/ozone/public/ozone_platform.h"
59 #include "ui/ozone/public/platform_utils.h"
66 namespace shell_integration_linux {
68 const char kXdgSettings[] = "xdg-settings";
69 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
70 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
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;
81 if (!base::PathService::Get(base::DIR_EXE, &chrome_dir))
84 base::FilePath chrome_version_path = chrome_dir.Append(script);
85 *chrome_version = chrome_version_path.value();
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);
101 // Value returned by xdg-settings if it can't understand our request.
102 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
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.
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
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) {
117 #if BUILDFLAG(IS_CHROMEOS_ASH)
120 std::unique_ptr<base::Environment> env(base::Environment::Create());
122 std::vector<std::string> argv;
123 argv.push_back(kXdgSettings);
124 argv.push_back("set");
125 if (scheme.empty()) {
126 argv.push_back(kXdgSettingsDefaultBrowser);
128 argv.push_back(kXdgSettingsDefaultSchemeHandler);
129 argv.push_back(scheme);
131 argv.push_back(chrome::GetDesktopName(env.get()));
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);
141 return ran_ok && exit_code == EXIT_SUCCESS;
145 // If |scheme| is empty this function checks if Chrome is the default browser,
146 // otherwise it checks if Chrome is the default handler application for
148 shell_integration::DefaultWebClientState GetIsDefaultWebClient(
149 const std::string& scheme) {
150 #if BUILDFLAG(IS_CHROMEOS_ASH)
151 return shell_integration::UNKNOWN_DEFAULT;
153 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
154 base::BlockingType::MAY_BLOCK);
156 std::unique_ptr<base::Environment> env(base::Environment::Create());
158 std::vector<std::string> argv;
159 argv.push_back(kXdgSettings);
160 argv.push_back("check");
161 if (scheme.empty()) {
162 argv.push_back(kXdgSettingsDefaultBrowser);
164 argv.push_back(kXdgSettingsDefaultSchemeHandler);
165 argv.push_back(scheme);
167 argv.push_back(chrome::GetDesktopName(env.get()));
171 bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply,
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,
180 if (!ran_ok || success_code != EXIT_SUCCESS) {
181 // xdg-settings failed: we can't determine or set the default browser.
182 return shell_integration::UNKNOWN_DEFAULT;
185 // Allow any reply that starts with "yes".
186 return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
187 ? shell_integration::IS_DEFAULT
188 : shell_integration::NOT_DEFAULT;
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));
204 return desktop_file_name;
209 #if defined(USE_GLIB)
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
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.
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.
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(
242 const base::CommandLine& command_line) {
243 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
245 std::string quoted_path;
246 const base::CommandLine::StringVector& argv = command_line.argv();
247 for (auto i = argv.begin(); i != argv.end(); ++i) {
248 if (i != argv.begin())
250 quoted_path += QuoteArgForDesktopFileExec(*i);
257 #if defined(USE_GLIB)
258 const char kDesktopEntry[] = "Desktop Entry";
259 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
261 void SetActionsForDesktopApplication(
262 const base::CommandLine& command_line,
264 std::set<web_app::DesktopActionInfo> action_info) {
265 if (action_info.empty())
268 std::vector<std::string> action_ids;
269 for (const auto& info : action_info) {
270 action_ids.push_back(info.id);
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());
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",
282 std::string launch_url_str = info.exec_launch_url.spec();
284 RE2::GlobalReplace(&launch_url_str, "%", "%%");
285 base::CommandLine current_cmd(command_line);
286 current_cmd.AppendSwitchASCII(switches::kAppLaunchUrlForShortcutsMenuItem,
289 g_key_file_set_string(
290 key_file, section_title.c_str(), "Exec",
291 QuoteCommandLineForDesktopFileExec(current_cmd).c_str());
296 base::FilePath GetDesktopFileForDefaultSchemeHandler(base::Environment* env,
298 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
299 base::BlockingType::MAY_BLOCK);
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));
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);
316 return base::FilePath();
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())
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,
333 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
335 g_key_file_free(key_file);
340 g_key_file_get_string(key_file, kDesktopEntry, key.c_str(), &err);
342 key_value = key_c_string;
343 g_free(key_c_string);
348 g_key_file_free(key_file);
358 // Allows LaunchXdgUtility to join a process.
359 // thread_restrictions.h assumes it to be in shell_integration_linux namespace.
360 class [[maybe_unused, nodiscard]] LaunchXdgUtilityScopedAllowBaseSyncPrimitives
361 : public base::ScopedAllowBaseSyncPrimitives{};
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);
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);
379 if (!process.IsValid())
381 LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
382 return process.WaitForExit(exit_code);
385 std::string GetWMClassFromAppName(std::string app_name) {
386 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
387 base::TrimString(app_name, "_", &app_name);
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());
399 base::FilePath GetDataWriteLocation(base::Environment* env) {
400 return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
403 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
404 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
405 base::BlockingType::MAY_BLOCK);
407 std::vector<base::FilePath> search_paths;
408 base::FilePath write_location = GetDataWriteLocation(env);
409 search_paths.push_back(write_location);
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()) {
415 search_paths.emplace_back(tokenizer.token_piece());
418 search_paths.push_back(base::FilePath("/usr/local/share"));
419 search_paths.push_back(base::FilePath("/usr/share"));
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);
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) {
437 std::string nodisplay_value =
438 shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
439 "NoDisplay", shortcut_contents);
440 return nodisplay_value == "true";
443 // Gets the path to the Chrome executable or wrapper script.
444 // Returns an empty path if the executable path could not be found, which should
446 base::FilePath GetChromeExePath() {
447 // Try to get the name of the wrapper script that launched Chrome.
448 std::unique_ptr<base::Environment> environment(base::Environment::Create());
449 std::string wrapper_script;
450 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script))
451 return base::FilePath(wrapper_script);
453 // Just return the name of the executable path for Chrome.
454 base::FilePath chrome_exe_path;
455 base::PathService::Get(base::FILE_EXE, &chrome_exe_path);
456 return chrome_exe_path;
459 std::string GetProgramClassName(const base::CommandLine& command_line,
460 const std::string& desktop_file_name) {
461 std::string class_name = GetDesktopBaseName(desktop_file_name);
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()
470 : class_name + " (" + user_data_dir + ")";
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);
477 std::string desktop_base_name = GetDesktopBaseName(desktop_file_name);
478 if (auto* platform_utils =
479 ui::OzonePlatform::GetInstance()->GetPlatformUtils()) {
480 return platform_utils->GetWmWindowClass(desktop_base_name);
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]);
486 return desktop_base_name;
489 } // namespace internal
491 std::string GetProgramClassName() {
492 std::unique_ptr<base::Environment> env(base::Environment::Create());
493 return internal::GetProgramClassName(*base::CommandLine::ForCurrentProcess(),
494 chrome::GetDesktopName(env.get()));
497 std::string GetProgramClassClass() {
498 std::unique_ptr<base::Environment> env(base::Environment::Create());
499 return internal::GetProgramClassClass(*base::CommandLine::ForCurrentProcess(),
500 chrome::GetDesktopName(env.get()));
503 std::string GetIconName() {
504 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
505 return "google-chrome";
506 #else // BUILDFLAG(CHROMIUM_BRANDING)
507 return "chromium-browser";
511 bool GetExistingShortcutContents(base::Environment* env,
512 const base::FilePath& desktop_filename,
513 std::string* output) {
514 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
515 base::BlockingType::MAY_BLOCK);
517 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
519 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
520 i != search_paths.end(); ++i) {
521 base::FilePath path = i->Append("applications").Append(desktop_filename);
522 VLOG(1) << "Looking for desktop file in " << path.value();
523 if (base::PathExists(path)) {
524 VLOG(1) << "Found desktop file at " << path.value();
525 return base::ReadFileToString(path, output);
532 base::FilePath GetWebShortcutFilename(const GURL& url) {
533 // Use a prefix, because xdg-desktop-menu requires it.
534 std::string filename =
535 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
536 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
538 base::FilePath desktop_path;
539 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
540 return base::FilePath();
542 base::FilePath filepath = desktop_path.Append(filename);
543 base::FilePath alternative_filepath(filepath.value() + ".desktop");
544 for (size_t i = 1; i < 100; ++i) {
545 if (base::PathExists(base::FilePath(alternative_filepath))) {
546 alternative_filepath = base::FilePath(
547 filepath.value() + "_" + base::NumberToString(i) + ".desktop");
549 return base::FilePath(alternative_filepath).BaseName();
553 return base::FilePath();
556 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
557 const base::FilePath& profile_path,
558 const base::FilePath& directory) {
559 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
560 base::BlockingType::MAY_BLOCK);
562 // Use a prefix, because xdg-desktop-menu requires it.
563 std::string prefix(chrome::kBrowserProcessExecutableName);
565 std::string suffix("-");
566 suffix.append(profile_path.BaseName().value());
567 base::i18n::ReplaceIllegalCharactersInPath(&suffix, '_');
568 // Spaces in filenames break xdg-desktop-menu
569 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
570 base::ReplaceChars(suffix, " ", "_", &suffix);
571 std::string glob = prefix + "*" + suffix + ".desktop";
573 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
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();
581 return shortcut_paths;
584 std::string GetDesktopFileContents(
585 const base::FilePath& chrome_exe_path,
586 const std::string& app_name,
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,
595 const std::string& run_on_os_login_mode,
596 std::set<web_app::DesktopActionInfo> action_info) {
597 base::CommandLine cmd_line = shell_integration::CommandLineArgsForLauncher(
598 url, extension_id, profile_path, run_on_os_login_mode);
599 cmd_line.SetProgram(chrome_exe_path);
600 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
601 icon_name, categories, mime_type,
602 no_display, std::move(action_info));
605 std::string GetDesktopFileContentsForCommand(
606 const base::CommandLine& command_line,
607 const std::string& app_name,
609 const std::u16string& title,
610 const std::string& icon_name,
611 const std::string& categories,
612 const std::string& mime_type,
614 std::set<web_app::DesktopActionInfo> action_info) {
615 #if defined(USE_GLIB)
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";
620 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
621 GKeyFile* key_file = g_key_file_new();
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");
628 // Set the "Name" key.
629 std::string final_title = base::UTF16ToUTF8(title);
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();
638 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
640 base::CommandLine modified_command_line(command_line);
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",
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.
653 modified_command_line.AppendArg("%U");
656 // Set the "Exec" key.
657 std::string final_path =
658 QuoteCommandLineForDesktopFileExec(modified_command_line);
659 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
661 // Set the "Icon" key.
662 if (!icon_name.empty()) {
663 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
665 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
666 GetIconName().c_str());
669 // Set the "Categories" key.
670 if (!categories.empty()) {
671 g_key_file_set_string(
672 key_file, kDesktopEntry, "Categories", categories.c_str());
675 // Set the "NoDisplay" key.
677 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
679 std::string wmclass = GetWMClassFromAppName(app_name);
680 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
683 SetActionsForDesktopApplication(command_line, key_file,
684 std::move(action_info));
687 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
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);
695 output_buffer += data_dump;
700 g_key_file_free(key_file);
701 return output_buffer;
704 return std::string();
708 std::string GetDirectoryFileContents(const std::u16string& title,
709 const std::string& icon_name) {
710 #if defined(USE_GLIB)
711 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
712 GKeyFile* key_file = g_key_file_new();
714 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
715 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
716 std::string final_title = base::UTF16ToUTF8(title);
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());
721 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
722 GetIconName().c_str());
726 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
727 std::string output_buffer;
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);
735 output_buffer += data_dump;
740 g_key_file_free(key_file);
741 return output_buffer;
744 return std::string();
748 base::FilePath GetMimeTypesRegistrationFilename(
749 const base::FilePath& profile_path,
750 const webapps::AppId& app_id) {
751 DCHECK(!profile_path.empty() && !app_id.empty());
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");
758 // Replace illegal characters and spaces in |filename|.
759 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
760 base::ReplaceChars(filename, " ", "_", &filename);
762 return base::FilePath(filename);
765 std::string GetMimeTypesRegistrationFileContents(
766 const apps::FileHandlers& file_handlers) {
769 writer.StartWriting();
770 writer.StartElement("mime-info");
771 writer.AddAttribute("xmlns",
772 "http://www.freedesktop.org/standards/shared-mime-info");
774 for (const auto& file_handler : file_handlers) {
775 for (const auto& accept_entry : file_handler.accept) {
776 writer.StartElement("mime-type");
777 writer.AddAttribute("type", accept_entry.mime_type);
779 if (!file_handler.display_name.empty()) {
780 writer.WriteElement("comment",
781 base::UTF16ToUTF8(file_handler.display_name));
783 for (const auto& file_extension : accept_entry.file_extensions) {
784 writer.StartElement("glob");
785 writer.AddAttribute("pattern", "*" + file_extension);
786 writer.EndElement(); // "glob"
788 writer.EndElement(); // "mime-type"
792 writer.EndElement(); // "mime-info"
793 writer.StopWriting();
794 return writer.GetWrittenString();
797 } // namespace shell_integration_linux
799 namespace shell_integration {
801 bool SetAsDefaultBrowser() {
802 return shell_integration_linux::SetDefaultWebClient(std::string());
805 bool SetAsDefaultClientForScheme(const std::string& scheme) {
806 return shell_integration_linux::SetDefaultWebClient(scheme);
809 std::u16string GetApplicationNameForScheme(const GURL& url) {
810 std::unique_ptr<base::Environment> env(base::Environment::Create());
812 std::string desktop_file_contents;
813 std::string application_name;
814 base::FilePath desktop_filepath =
815 shell_integration_linux::GetDesktopFileForDefaultSchemeHandler(env.get(),
817 if (shell_integration_linux::GetExistingShortcutContents(
818 env.get(), desktop_filepath, &desktop_file_contents)) {
820 shell_integration_linux::GetDesktopEntryStringValueFromFromDesktopFile(
821 "Name", desktop_file_contents);
824 return application_name.empty() ? u"xdg-open"
825 : base::ASCIIToUTF16(application_name);
828 DefaultWebClientState GetDefaultBrowser() {
829 return shell_integration_linux::GetIsDefaultWebClient(std::string());
832 bool IsFirefoxDefaultBrowser() {
833 std::vector<std::string> argv;
834 argv.push_back(shell_integration_linux::kXdgSettings);
835 argv.push_back("get");
836 argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
839 // We don't care about the return value here.
840 base::GetAppOutput(base::CommandLine(argv), &browser);
841 return browser.find("irefox") != std::string::npos;
844 DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) {
845 return shell_integration_linux::GetIsDefaultWebClient(scheme);
850 DefaultWebClientSetPermission GetPlatformSpecificDefaultWebClientSetPermission(
851 WebClientSetMethod method) {
852 return SET_DEFAULT_UNATTENDED;
855 } // namespace internal
857 } // namespace shell_integration