// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/shell_integration_linux.h" #include <fcntl.h> #if defined(USE_GLIB) #include <glib.h> #endif #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <string> #include <vector> #include "base/base_paths.h" #include "base/command_line.h" #include "base/environment.h" #include "base/file_util.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" #include "base/i18n/file_util_icu.h" #include "base/memory/ref_counted_memory.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "chrome/browser/shell_integration.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "content/public/browser/browser_thread.h" #include "grit/chrome_unscaled_resources.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image_family.h" #include "url/gurl.h" using content::BrowserThread; namespace { // The Categories for the App Launcher desktop shortcut. Should be the same as // the Chrome desktop shortcut, so they are in the same sub-menu. const char kAppListCategories[] = "Network;WebBrowser;"; // Helper to launch xdg scripts. We don't want them to ask any questions on the // terminal etc. The function returns true if the utility launches and exits // cleanly, in which case |exit_code| returns the utility's exit code. bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) { // xdg-settings internally runs xdg-mime, which uses mv to move newly-created // files on top of originals after making changes to them. In the event that // the original files are owned by another user (e.g. root, which can happen // if they are updated within sudo), mv will prompt the user to confirm if // standard input is a terminal (otherwise it just does it). So make sure it's // not, to avoid locking everything up waiting for mv. *exit_code = EXIT_FAILURE; int devnull = open("/dev/null", O_RDONLY); if (devnull < 0) return false; base::FileHandleMappingVector no_stdin; no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO)); base::ProcessHandle handle; base::LaunchOptions options; options.fds_to_remap = &no_stdin; if (!base::LaunchProcess(argv, options, &handle)) { close(devnull); return false; } close(devnull); return base::WaitForExitCode(handle, exit_code); } std::string CreateShortcutIcon(const gfx::ImageFamily& icon_images, const base::FilePath& shortcut_filename) { if (icon_images.empty()) return std::string(); // TODO(phajdan.jr): Report errors from this function, possibly as infobars. base::ScopedTempDir temp_dir; if (!temp_dir.CreateUniqueTempDir()) return std::string(); base::FilePath temp_file_path = temp_dir.path().Append( shortcut_filename.ReplaceExtension("png")); std::string icon_name = temp_file_path.BaseName().RemoveExtension().value(); for (gfx::ImageFamily::const_iterator it = icon_images.begin(); it != icon_images.end(); ++it) { int width = it->Width(); scoped_refptr<base::RefCountedMemory> png_data = it->As1xPNGBytes(); if (png_data->size() == 0) { // If the bitmap could not be encoded to PNG format, skip it. LOG(WARNING) << "Could not encode icon " << icon_name << ".png at size " << width << "."; continue; } int bytes_written = base::WriteFile(temp_file_path, png_data->front_as<char>(), png_data->size()); if (bytes_written != static_cast<int>(png_data->size())) return std::string(); std::vector<std::string> argv; argv.push_back("xdg-icon-resource"); argv.push_back("install"); // Always install in user mode, even if someone runs the browser as root // (people do that). argv.push_back("--mode"); argv.push_back("user"); argv.push_back("--size"); argv.push_back(base::IntToString(width)); argv.push_back(temp_file_path.value()); argv.push_back(icon_name); int exit_code; if (!LaunchXdgUtility(argv, &exit_code) || exit_code) { LOG(WARNING) << "Could not install icon " << icon_name << ".png at size " << width << "."; } } return icon_name; } bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename, const std::string& contents) { // Make sure that we will later call openat in a secure way. DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value()); base::FilePath desktop_path; if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) return false; int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY); if (desktop_fd < 0) return false; int fd = openat(desktop_fd, shortcut_filename.value().c_str(), O_CREAT | O_EXCL | O_WRONLY, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if (fd < 0) { if (IGNORE_EINTR(close(desktop_fd)) < 0) PLOG(ERROR) << "close"; return false; } ssize_t bytes_written = base::WriteFileDescriptor(fd, contents.data(), contents.length()); if (IGNORE_EINTR(close(fd)) < 0) PLOG(ERROR) << "close"; if (bytes_written != static_cast<ssize_t>(contents.length())) { // Delete the file. No shortuct is better than corrupted one. Use unlinkat // to make sure we're deleting the file in the directory we think we are. // Even if an attacker manager to put something other at // |shortcut_filename| we'll just undo his action. unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0); } if (IGNORE_EINTR(close(desktop_fd)) < 0) PLOG(ERROR) << "close"; return true; } void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) { base::FilePath desktop_path; if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) base::DeleteFile(desktop_path.Append(shortcut_filename), false); } // Creates a shortcut with |shortcut_filename| and |contents| in the system // applications menu. If |directory_filename| is non-empty, creates a sub-menu // with |directory_filename| and |directory_contents|, and stores the shortcut // under the sub-menu. bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename, const std::string& contents, const base::FilePath& directory_filename, const std::string& directory_contents) { base::ScopedTempDir temp_dir; if (!temp_dir.CreateUniqueTempDir()) return false; base::FilePath temp_directory_path; if (!directory_filename.empty()) { temp_directory_path = temp_dir.path().Append(directory_filename); int bytes_written = base::WriteFile(temp_directory_path, directory_contents.data(), directory_contents.length()); if (bytes_written != static_cast<int>(directory_contents.length())) return false; } base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename); int bytes_written = base::WriteFile(temp_file_path, contents.data(), contents.length()); if (bytes_written != static_cast<int>(contents.length())) return false; std::vector<std::string> argv; argv.push_back("xdg-desktop-menu"); argv.push_back("install"); // Always install in user mode, even if someone runs the browser as root // (people do that). argv.push_back("--mode"); argv.push_back("user"); // If provided, install the shortcut file inside the given directory. if (!directory_filename.empty()) argv.push_back(temp_directory_path.value()); argv.push_back(temp_file_path.value()); int exit_code; LaunchXdgUtility(argv, &exit_code); return exit_code == 0; } void DeleteShortcutInApplicationsMenu( const base::FilePath& shortcut_filename, const base::FilePath& directory_filename) { std::vector<std::string> argv; argv.push_back("xdg-desktop-menu"); argv.push_back("uninstall"); // Uninstall in user mode, to match the install. argv.push_back("--mode"); argv.push_back("user"); // The file does not need to exist anywhere - xdg-desktop-menu will uninstall // items from the menu with a matching name. // If |directory_filename| is supplied, this will also remove the item from // the directory, and remove the directory if it is empty. if (!directory_filename.empty()) argv.push_back(directory_filename.value()); argv.push_back(shortcut_filename.value()); int exit_code; LaunchXdgUtility(argv, &exit_code); } // Quote a string such that it appears as one verbatim argument for the Exec // key in a desktop file. std::string QuoteArgForDesktopFileExec(const std::string& arg) { // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html // Quoting is only necessary if the argument has a reserved character. if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos) return arg; // No quoting necessary. std::string quoted = "\""; for (size_t i = 0; i < arg.size(); ++i) { // Note that the set of backslashed characters is smaller than the // set of reserved characters. switch (arg[i]) { case '"': case '`': case '$': case '\\': quoted += '\\'; break; } quoted += arg[i]; } quoted += '"'; return quoted; } // Quote a command line so it is suitable for use as the Exec key in a desktop // file. Note: This should be used instead of GetCommandLineString, which does // not properly quote the string; this function is designed for the Exec key. std::string QuoteCommandLineForDesktopFileExec( const CommandLine& command_line) { // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html std::string quoted_path = ""; const CommandLine::StringVector& argv = command_line.argv(); for (CommandLine::StringVector::const_iterator i = argv.begin(); i != argv.end(); ++i) { if (i != argv.begin()) quoted_path += " "; quoted_path += QuoteArgForDesktopFileExec(*i); } return quoted_path; } const char kDesktopEntry[] = "Desktop Entry"; const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open"; const char kXdgSettings[] = "xdg-settings"; const char kXdgSettingsDefaultBrowser[] = "default-web-browser"; const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler"; const char kDirectoryFilename[] = "chrome-apps.directory"; #if defined(GOOGLE_CHROME_BUILD) const char kAppListDesktopName[] = "chrome-app-list"; #else // CHROMIUM_BUILD const char kAppListDesktopName[] = "chromium-app-list"; #endif // Utility function to get the path to the version of a script shipped with // Chrome. |script| gives the name of the script. |chrome_version| returns the // path to the Chrome version of the script, and the return value of the // function is true if the function is successful and the Chrome version is // not the script found on the PATH. bool GetChromeVersionOfScript(const std::string& script, std::string* chrome_version) { // Get the path to the Chrome version. base::FilePath chrome_dir; if (!PathService::Get(base::DIR_EXE, &chrome_dir)) return false; base::FilePath chrome_version_path = chrome_dir.Append(script); *chrome_version = chrome_version_path.value(); // Check if this is different to the one on path. std::vector<std::string> argv; argv.push_back("which"); argv.push_back(script); std::string path_version; if (base::GetAppOutput(CommandLine(argv), &path_version)) { // Remove trailing newline path_version.erase(path_version.length() - 1, 1); base::FilePath path_version_path(path_version); return (chrome_version_path != path_version_path); } return false; } // Value returned by xdg-settings if it can't understand our request. const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1; // We delegate the difficulty of setting the default browser and default url // scheme handler in Linux desktop environments to an xdg utility, xdg-settings. // When calling this script we first try to use the script on PATH. If that // fails we then try to use the script that we have included. This gives // scripts on the system priority over ours, as distribution vendors may have // tweaked the script, but still allows our copy to be used if the script on the // system fails, as the system copy may be missing capabilities of the Chrome // copy. // If |protocol| is empty this function sets Chrome as the default browser, // otherwise it sets Chrome as the default handler application for |protocol|. bool SetDefaultWebClient(const std::string& protocol) { #if defined(OS_CHROMEOS) return true; #else scoped_ptr<base::Environment> env(base::Environment::Create()); std::vector<std::string> argv; argv.push_back(kXdgSettings); argv.push_back("set"); if (protocol.empty()) { argv.push_back(kXdgSettingsDefaultBrowser); } else { argv.push_back(kXdgSettingsDefaultSchemeHandler); argv.push_back(protocol); } argv.push_back(shell_integration_linux::GetDesktopName(env.get())); int exit_code; bool ran_ok = LaunchXdgUtility(argv, &exit_code); if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { ran_ok = LaunchXdgUtility(argv, &exit_code); } } return ran_ok && exit_code == EXIT_SUCCESS; #endif } // If |protocol| is empty this function checks if Chrome is the default browser, // otherwise it checks if Chrome is the default handler application for // |protocol|. ShellIntegration::DefaultWebClientState GetIsDefaultWebClient( const std::string& protocol) { #if defined(OS_CHROMEOS) return ShellIntegration::UNKNOWN_DEFAULT; #else base::ThreadRestrictions::AssertIOAllowed(); scoped_ptr<base::Environment> env(base::Environment::Create()); std::vector<std::string> argv; argv.push_back(kXdgSettings); argv.push_back("check"); if (protocol.empty()) { argv.push_back(kXdgSettingsDefaultBrowser); } else { argv.push_back(kXdgSettingsDefaultSchemeHandler); argv.push_back(protocol); } argv.push_back(shell_integration_linux::GetDesktopName(env.get())); std::string reply; int success_code; bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply, &success_code); if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply, &success_code); } } if (!ran_ok || success_code != EXIT_SUCCESS) { // xdg-settings failed: we can't determine or set the default browser. return ShellIntegration::UNKNOWN_DEFAULT; } // Allow any reply that starts with "yes". return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT : ShellIntegration::NOT_DEFAULT; #endif } // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop // file, given in |shortcut_contents|. If the key is not found, returns false. bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) { #if defined(USE_GLIB) // An empty file causes a crash with glib <= 2.32, so special case here. if (shortcut_contents.empty()) return false; GKeyFile* key_file = g_key_file_new(); GError* err = NULL; if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(), shortcut_contents.size(), G_KEY_FILE_NONE, &err)) { LOG(WARNING) << "Unable to read desktop file template: " << err->message; g_error_free(err); g_key_file_free(key_file); return false; } bool nodisplay = false; char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry, "NoDisplay", &err); if (nodisplay_c_string) { if (!g_strcmp0(nodisplay_c_string, "true")) nodisplay = true; g_free(nodisplay_c_string); } else { g_error_free(err); } g_key_file_free(key_file); return nodisplay; #else NOTIMPLEMENTED(); return false; #endif } // Gets the path to the Chrome executable or wrapper script. // Returns an empty path if the executable path could not be found. base::FilePath GetChromeExePath() { // Try to get the name of the wrapper script that launched Chrome. scoped_ptr<base::Environment> environment(base::Environment::Create()); std::string wrapper_script; if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) { return base::FilePath(wrapper_script); } // Just return the name of the executable path for Chrome. base::FilePath chrome_exe_path; PathService::Get(base::FILE_EXE, &chrome_exe_path); return chrome_exe_path; } } // namespace // static ShellIntegration::DefaultWebClientSetPermission ShellIntegration::CanSetAsDefaultBrowser() { return SET_DEFAULT_UNATTENDED; } // static bool ShellIntegration::SetAsDefaultBrowser() { return SetDefaultWebClient(std::string()); } // static bool ShellIntegration::SetAsDefaultProtocolClient( const std::string& protocol) { return SetDefaultWebClient(protocol); } // static ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() { return GetIsDefaultWebClient(std::string()); } // static base::string16 ShellIntegration::GetApplicationNameForProtocol( const GURL& url) { return base::ASCIIToUTF16("xdg-open"); } // static ShellIntegration::DefaultWebClientState ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) { return GetIsDefaultWebClient(protocol); } // static bool ShellIntegration::IsFirefoxDefaultBrowser() { std::vector<std::string> argv; argv.push_back(kXdgSettings); argv.push_back("get"); argv.push_back(kXdgSettingsDefaultBrowser); std::string browser; // We don't care about the return value here. base::GetAppOutput(CommandLine(argv), &browser); return browser.find("irefox") != std::string::npos; } namespace shell_integration_linux { bool GetDataWriteLocation(base::Environment* env, base::FilePath* search_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::string xdg_data_home; std::string home; if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) && !xdg_data_home.empty()) { *search_path = base::FilePath(xdg_data_home); return true; } else if (env->GetVar("HOME", &home) && !home.empty()) { *search_path = base::FilePath(home).Append(".local").Append("share"); return true; } return false; } std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector<base::FilePath> search_paths; base::FilePath write_location; if (GetDataWriteLocation(env, &write_location)) search_paths.push_back(write_location); std::string xdg_data_dirs; if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) { base::StringTokenizer tokenizer(xdg_data_dirs, ":"); while (tokenizer.GetNext()) { base::FilePath data_dir(tokenizer.token()); search_paths.push_back(data_dir); } } else { search_paths.push_back(base::FilePath("/usr/local/share")); search_paths.push_back(base::FilePath("/usr/share")); } return search_paths; } std::string GetProgramClassName() { DCHECK(CommandLine::InitializedForCurrentProcess()); // Get the res_name component from argv[0]. const CommandLine* command_line = CommandLine::ForCurrentProcess(); std::string class_name = command_line->GetProgram().BaseName().value(); if (!class_name.empty()) class_name[0] = base::ToUpperASCII(class_name[0]); return class_name; } std::string GetDesktopName(base::Environment* env) { #if defined(GOOGLE_CHROME_BUILD) chrome::VersionInfo::Channel product_channel( chrome::VersionInfo::GetChannel()); switch (product_channel) { case chrome::VersionInfo::CHANNEL_DEV: return "google-chrome-unstable.desktop"; case chrome::VersionInfo::CHANNEL_BETA: return "google-chrome-beta.desktop"; default: return "google-chrome.desktop"; } #else // CHROMIUM_BUILD // Allow $CHROME_DESKTOP to override the built-in value, so that development // versions can set themselves as the default without interfering with // non-official, packaged versions using the built-in value. std::string name; if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty()) return name; return "chromium-browser.desktop"; #endif } std::string GetIconName() { #if defined(GOOGLE_CHROME_BUILD) return "google-chrome"; #else // CHROMIUM_BUILD return "chromium-browser"; #endif } web_app::ShortcutLocations GetExistingShortcutLocations( base::Environment* env, const base::FilePath& profile_path, const std::string& extension_id) { base::FilePath desktop_path; // If Get returns false, just leave desktop_path empty. PathService::Get(base::DIR_USER_DESKTOP, &desktop_path); return GetExistingShortcutLocations(env, profile_path, extension_id, desktop_path); } web_app::ShortcutLocations GetExistingShortcutLocations( base::Environment* env, const base::FilePath& profile_path, const std::string& extension_id, const base::FilePath& desktop_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); base::FilePath shortcut_filename = GetExtensionShortcutFilename( profile_path, extension_id); DCHECK(!shortcut_filename.empty()); web_app::ShortcutLocations locations; // Determine whether there is a shortcut on desktop. if (!desktop_path.empty()) { locations.on_desktop = base::PathExists(desktop_path.Append(shortcut_filename)); } // Determine whether there is a shortcut in the applications directory. std::string shortcut_contents; if (GetExistingShortcutContents(env, shortcut_filename, &shortcut_contents)) { // If the shortcut contents contain NoDisplay=true, it should be hidden. // Otherwise since these shortcuts are for apps, they are always in the // "Chrome Apps" directory. locations.applications_menu_location = GetNoDisplayFromDesktopFile(shortcut_contents) ? web_app::APP_MENU_LOCATION_HIDDEN : web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS; } return locations; } bool GetExistingShortcutContents(base::Environment* env, const base::FilePath& desktop_filename, std::string* output) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector<base::FilePath> search_paths = GetDataSearchLocations(env); for (std::vector<base::FilePath>::const_iterator i = search_paths.begin(); i != search_paths.end(); ++i) { base::FilePath path = i->Append("applications").Append(desktop_filename); VLOG(1) << "Looking for desktop file in " << path.value(); if (base::PathExists(path)) { VLOG(1) << "Found desktop file at " << path.value(); return base::ReadFileToString(path, output); } } return false; } base::FilePath GetWebShortcutFilename(const GURL& url) { // Use a prefix, because xdg-desktop-menu requires it. std::string filename = std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec(); file_util::ReplaceIllegalCharactersInPath(&filename, '_'); base::FilePath desktop_path; if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) return base::FilePath(); base::FilePath filepath = desktop_path.Append(filename); base::FilePath alternative_filepath(filepath.value() + ".desktop"); for (size_t i = 1; i < 100; ++i) { if (base::PathExists(base::FilePath(alternative_filepath))) { alternative_filepath = base::FilePath( filepath.value() + "_" + base::IntToString(i) + ".desktop"); } else { return base::FilePath(alternative_filepath).BaseName(); } } return base::FilePath(); } base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path, const std::string& extension_id) { DCHECK(!extension_id.empty()); // Use a prefix, because xdg-desktop-menu requires it. std::string filename(chrome::kBrowserProcessExecutableName); filename.append("-") .append(extension_id) .append("-") .append(profile_path.BaseName().value()); file_util::ReplaceIllegalCharactersInPath(&filename, '_'); // Spaces in filenames break xdg-desktop-menu // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605). base::ReplaceChars(filename, " ", "_", &filename); return base::FilePath(filename.append(".desktop")); } std::vector<base::FilePath> GetExistingProfileShortcutFilenames( const base::FilePath& profile_path, const base::FilePath& directory) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Use a prefix, because xdg-desktop-menu requires it. std::string prefix(chrome::kBrowserProcessExecutableName); prefix.append("-"); std::string suffix("-"); suffix.append(profile_path.BaseName().value()); file_util::ReplaceIllegalCharactersInPath(&suffix, '_'); // Spaces in filenames break xdg-desktop-menu // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605). base::ReplaceChars(suffix, " ", "_", &suffix); std::string glob = prefix + "*" + suffix + ".desktop"; base::FileEnumerator files(directory, false, base::FileEnumerator::FILES, glob); base::FilePath shortcut_file = files.Next(); std::vector<base::FilePath> shortcut_paths; while (!shortcut_file.empty()) { shortcut_paths.push_back(shortcut_file.BaseName()); shortcut_file = files.Next(); } return shortcut_paths; } std::string GetDesktopFileContents( const base::FilePath& chrome_exe_path, const std::string& app_name, const GURL& url, const std::string& extension_id, const base::string16& title, const std::string& icon_name, const base::FilePath& profile_path, const std::string& categories, bool no_display) { CommandLine cmd_line = ShellIntegration::CommandLineArgsForLauncher( url, extension_id, profile_path); cmd_line.SetProgram(chrome_exe_path); return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title, icon_name, categories, no_display); } std::string GetDesktopFileContentsForCommand( const CommandLine& command_line, const std::string& app_name, const GURL& url, const base::string16& title, const std::string& icon_name, const std::string& categories, bool no_display) { #if defined(USE_GLIB) // Although not required by the spec, Nautilus on Ubuntu Karmic creates its // launchers with an xdg-open shebang. Follow that convention. std::string output_buffer = std::string(kXdgOpenShebang) + "\n"; // See http://standards.freedesktop.org/desktop-entry-spec/latest/ GKeyFile* key_file = g_key_file_new(); // Set keys with fixed values. g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0"); g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false"); g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application"); // Set the "Name" key. std::string final_title = base::UTF16ToUTF8(title); // Make sure no endline characters can slip in and possibly introduce // additional lines (like Exec, which makes it a security risk). Also // use the URL as a default when the title is empty. if (final_title.empty() || final_title.find("\n") != std::string::npos || final_title.find("\r") != std::string::npos) { final_title = url.spec(); } g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); // Set the "Exec" key. std::string final_path = QuoteCommandLineForDesktopFileExec(command_line); g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); // Set the "Icon" key. if (!icon_name.empty()) { g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); } else { g_key_file_set_string(key_file, kDesktopEntry, "Icon", GetIconName().c_str()); } // Set the "Categories" key. if (!categories.empty()) { g_key_file_set_string( key_file, kDesktopEntry, "Categories", categories.c_str()); } // Set the "NoDisplay" key. if (no_display) g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true"); std::string wmclass = web_app::GetWMClassFromAppName(app_name); g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", wmclass.c_str()); gsize length = 0; gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); if (data_dump) { // If strlen(data_dump[0]) == 0, this check will fail. if (data_dump[0] == '\n') { // Older versions of glib produce a leading newline. If this is the case, // remove it to avoid double-newline after the shebang. output_buffer += (data_dump + 1); } else { output_buffer += data_dump; } g_free(data_dump); } g_key_file_free(key_file); return output_buffer; #else NOTIMPLEMENTED(); return std::string(); #endif } std::string GetDirectoryFileContents(const base::string16& title, const std::string& icon_name) { #if defined(USE_GLIB) // See http://standards.freedesktop.org/desktop-entry-spec/latest/ GKeyFile* key_file = g_key_file_new(); g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0"); g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory"); std::string final_title = base::UTF16ToUTF8(title); g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); if (!icon_name.empty()) { g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); } else { g_key_file_set_string(key_file, kDesktopEntry, "Icon", GetIconName().c_str()); } gsize length = 0; gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); std::string output_buffer; if (data_dump) { // If strlen(data_dump[0]) == 0, this check will fail. if (data_dump[0] == '\n') { // Older versions of glib produce a leading newline. If this is the case, // remove it to avoid double-newline after the shebang. output_buffer += (data_dump + 1); } else { output_buffer += data_dump; } g_free(data_dump); } g_key_file_free(key_file); return output_buffer; #else NOTIMPLEMENTED(); return std::string(); #endif } bool CreateDesktopShortcut( const web_app::ShortcutInfo& shortcut_info, const web_app::ShortcutLocations& creation_locations) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); base::FilePath shortcut_filename; if (!shortcut_info.extension_id.empty()) { shortcut_filename = GetExtensionShortcutFilename( shortcut_info.profile_path, shortcut_info.extension_id); // For extensions we do not want duplicate shortcuts. So, delete any that // already exist and replace them. if (creation_locations.on_desktop) DeleteShortcutOnDesktop(shortcut_filename); if (creation_locations.applications_menu_location != web_app::APP_MENU_LOCATION_NONE) { DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath()); } } else { shortcut_filename = GetWebShortcutFilename(shortcut_info.url); } if (shortcut_filename.empty()) return false; std::string icon_name = CreateShortcutIcon(shortcut_info.favicon, shortcut_filename); std::string app_name = web_app::GenerateApplicationNameFromInfo(shortcut_info); bool success = true; base::FilePath chrome_exe_path = GetChromeExePath(); if (chrome_exe_path.empty()) { LOG(WARNING) << "Could not get executable path."; return false; } if (creation_locations.on_desktop) { std::string contents = GetDesktopFileContents( chrome_exe_path, app_name, shortcut_info.url, shortcut_info.extension_id, shortcut_info.title, icon_name, shortcut_info.profile_path, "", false); success = CreateShortcutOnDesktop(shortcut_filename, contents); } if (creation_locations.applications_menu_location == web_app::APP_MENU_LOCATION_NONE) { return success; } base::FilePath directory_filename; std::string directory_contents; switch (creation_locations.applications_menu_location) { case web_app::APP_MENU_LOCATION_ROOT: case web_app::APP_MENU_LOCATION_HIDDEN: break; case web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS: directory_filename = base::FilePath(kDirectoryFilename); directory_contents = GetDirectoryFileContents( ShellIntegration::GetAppShortcutsSubdirName(), ""); break; default: NOTREACHED(); break; } // Set NoDisplay=true if hidden. This will hide the application from // user-facing menus. std::string contents = GetDesktopFileContents( chrome_exe_path, app_name, shortcut_info.url, shortcut_info.extension_id, shortcut_info.title, icon_name, shortcut_info.profile_path, "", creation_locations.applications_menu_location == web_app::APP_MENU_LOCATION_HIDDEN); success = CreateShortcutInApplicationsMenu( shortcut_filename, contents, directory_filename, directory_contents) && success; return success; } bool CreateAppListDesktopShortcut( const std::string& wm_class, const std::string& title) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); base::FilePath desktop_name(kAppListDesktopName); base::FilePath shortcut_filename = desktop_name.AddExtension("desktop"); // We do not want duplicate shortcuts. Delete any that already exist and // replace them. DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath()); base::FilePath chrome_exe_path = GetChromeExePath(); if (chrome_exe_path.empty()) { LOG(WARNING) << "Could not get executable path."; return false; } gfx::ImageFamily icon_images; ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance(); icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16)); icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32)); icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_48)); icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256)); std::string icon_name = CreateShortcutIcon(icon_images, desktop_name); CommandLine command_line(chrome_exe_path); command_line.AppendSwitch(switches::kShowAppList); std::string contents = GetDesktopFileContentsForCommand(command_line, wm_class, GURL(), base::UTF8ToUTF16(title), icon_name, kAppListCategories, false); return CreateShortcutInApplicationsMenu( shortcut_filename, contents, base::FilePath(), ""); } void DeleteDesktopShortcuts(const base::FilePath& profile_path, const std::string& extension_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); base::FilePath shortcut_filename = GetExtensionShortcutFilename( profile_path, extension_id); DCHECK(!shortcut_filename.empty()); DeleteShortcutOnDesktop(shortcut_filename); // Delete shortcuts from |kDirectoryFilename|. // Note that it is possible that shortcuts were not created in the Chrome Apps // directory. It doesn't matter: this will still delete the shortcut even if // it isn't in the directory. DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath(kDirectoryFilename)); } void DeleteAllDesktopShortcuts(const base::FilePath& profile_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); scoped_ptr<base::Environment> env(base::Environment::Create()); // Delete shortcuts from Desktop. base::FilePath desktop_path; if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) { std::vector<base::FilePath> shortcut_filenames_desktop = GetExistingProfileShortcutFilenames(profile_path, desktop_path); for (std::vector<base::FilePath>::const_iterator it = shortcut_filenames_desktop.begin(); it != shortcut_filenames_desktop.end(); ++it) { DeleteShortcutOnDesktop(*it); } } // Delete shortcuts from |kDirectoryFilename|. base::FilePath applications_menu; if (GetDataWriteLocation(env.get(), &applications_menu)) { applications_menu = applications_menu.AppendASCII("applications"); std::vector<base::FilePath> shortcut_filenames_app_menu = GetExistingProfileShortcutFilenames(profile_path, applications_menu); for (std::vector<base::FilePath>::const_iterator it = shortcut_filenames_app_menu.begin(); it != shortcut_filenames_app_menu.end(); ++it) { DeleteShortcutInApplicationsMenu(*it, base::FilePath(kDirectoryFilename)); } } } } // namespace shell_integration_linux