Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / shell_integration_linux.cc
blobb2b111e989b3ba906f913ea6b644f21ba3a40be3
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"
7 #include <fcntl.h>
8 #include <glib.h>
9 #include <stdlib.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
14 #include <string>
15 #include <vector>
17 #include "base/base_paths.h"
18 #include "base/command_line.h"
19 #include "base/environment.h"
20 #include "base/file_util.h"
21 #include "base/files/file_enumerator.h"
22 #include "base/files/file_path.h"
23 #include "base/files/scoped_temp_dir.h"
24 #include "base/i18n/file_util_icu.h"
25 #include "base/memory/ref_counted_memory.h"
26 #include "base/memory/scoped_ptr.h"
27 #include "base/message_loop/message_loop.h"
28 #include "base/path_service.h"
29 #include "base/posix/eintr_wrapper.h"
30 #include "base/process/kill.h"
31 #include "base/process/launch.h"
32 #include "base/strings/string_number_conversions.h"
33 #include "base/strings/string_tokenizer.h"
34 #include "base/strings/string_util.h"
35 #include "base/strings/utf_string_conversions.h"
36 #include "base/threading/thread.h"
37 #include "base/threading/thread_restrictions.h"
38 #include "build/build_config.h"
39 #include "chrome/browser/shell_integration.h"
40 #include "chrome/common/chrome_constants.h"
41 #include "chrome/common/chrome_switches.h"
42 #include "chrome/common/chrome_version_info.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "grit/chrome_unscaled_resources.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/gfx/image/image_family.h"
47 #include "url/gurl.h"
49 using content::BrowserThread;
51 namespace {
53 // The Categories for the App Launcher desktop shortcut. Should be the same as
54 // the Chrome desktop shortcut, so they are in the same sub-menu.
55 const char kAppListCategories[] = "Network;WebBrowser;";
57 // Helper to launch xdg scripts. We don't want them to ask any questions on the
58 // terminal etc. The function returns true if the utility launches and exits
59 // cleanly, in which case |exit_code| returns the utility's exit code.
60 bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
61 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
62 // files on top of originals after making changes to them. In the event that
63 // the original files are owned by another user (e.g. root, which can happen
64 // if they are updated within sudo), mv will prompt the user to confirm if
65 // standard input is a terminal (otherwise it just does it). So make sure it's
66 // not, to avoid locking everything up waiting for mv.
67 *exit_code = EXIT_FAILURE;
68 int devnull = open("/dev/null", O_RDONLY);
69 if (devnull < 0)
70 return false;
71 base::FileHandleMappingVector no_stdin;
72 no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
74 base::ProcessHandle handle;
75 base::LaunchOptions options;
76 options.fds_to_remap = &no_stdin;
77 if (!base::LaunchProcess(argv, options, &handle)) {
78 close(devnull);
79 return false;
81 close(devnull);
83 return base::WaitForExitCode(handle, exit_code);
86 std::string CreateShortcutIcon(const gfx::ImageFamily& icon_images,
87 const base::FilePath& shortcut_filename) {
88 if (icon_images.empty())
89 return std::string();
91 // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
92 base::ScopedTempDir temp_dir;
93 if (!temp_dir.CreateUniqueTempDir())
94 return std::string();
96 base::FilePath temp_file_path = temp_dir.path().Append(
97 shortcut_filename.ReplaceExtension("png"));
98 std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
100 for (gfx::ImageFamily::const_iterator it = icon_images.begin();
101 it != icon_images.end(); ++it) {
102 int width = it->Width();
103 scoped_refptr<base::RefCountedMemory> png_data = it->As1xPNGBytes();
104 if (png_data->size() == 0) {
105 // If the bitmap could not be encoded to PNG format, skip it.
106 LOG(WARNING) << "Could not encode icon " << icon_name << ".png at size "
107 << width << ".";
108 continue;
110 int bytes_written = base::WriteFile(temp_file_path,
111 png_data->front_as<char>(),
112 png_data->size());
114 if (bytes_written != static_cast<int>(png_data->size()))
115 return std::string();
117 std::vector<std::string> argv;
118 argv.push_back("xdg-icon-resource");
119 argv.push_back("install");
121 // Always install in user mode, even if someone runs the browser as root
122 // (people do that).
123 argv.push_back("--mode");
124 argv.push_back("user");
126 argv.push_back("--size");
127 argv.push_back(base::IntToString(width));
129 argv.push_back(temp_file_path.value());
130 argv.push_back(icon_name);
131 int exit_code;
132 if (!LaunchXdgUtility(argv, &exit_code) || exit_code) {
133 LOG(WARNING) << "Could not install icon " << icon_name << ".png at size "
134 << width << ".";
137 return icon_name;
140 bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename,
141 const std::string& contents) {
142 // Make sure that we will later call openat in a secure way.
143 DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
145 base::FilePath desktop_path;
146 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
147 return false;
149 int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
150 if (desktop_fd < 0)
151 return false;
153 int fd = openat(desktop_fd, shortcut_filename.value().c_str(),
154 O_CREAT | O_EXCL | O_WRONLY,
155 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
156 if (fd < 0) {
157 if (IGNORE_EINTR(close(desktop_fd)) < 0)
158 PLOG(ERROR) << "close";
159 return false;
162 ssize_t bytes_written = base::WriteFileDescriptor(fd, contents.data(),
163 contents.length());
164 if (IGNORE_EINTR(close(fd)) < 0)
165 PLOG(ERROR) << "close";
167 if (bytes_written != static_cast<ssize_t>(contents.length())) {
168 // Delete the file. No shortuct is better than corrupted one. Use unlinkat
169 // to make sure we're deleting the file in the directory we think we are.
170 // Even if an attacker manager to put something other at
171 // |shortcut_filename| we'll just undo his action.
172 unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0);
175 if (IGNORE_EINTR(close(desktop_fd)) < 0)
176 PLOG(ERROR) << "close";
178 return true;
181 void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) {
182 base::FilePath desktop_path;
183 if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
184 base::DeleteFile(desktop_path.Append(shortcut_filename), false);
187 // Creates a shortcut with |shortcut_filename| and |contents| in the system
188 // applications menu. If |directory_filename| is non-empty, creates a sub-menu
189 // with |directory_filename| and |directory_contents|, and stores the shortcut
190 // under the sub-menu.
191 bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename,
192 const std::string& contents,
193 const base::FilePath& directory_filename,
194 const std::string& directory_contents) {
195 base::ScopedTempDir temp_dir;
196 if (!temp_dir.CreateUniqueTempDir())
197 return false;
199 base::FilePath temp_directory_path;
200 if (!directory_filename.empty()) {
201 temp_directory_path = temp_dir.path().Append(directory_filename);
203 int bytes_written = base::WriteFile(temp_directory_path,
204 directory_contents.data(),
205 directory_contents.length());
207 if (bytes_written != static_cast<int>(directory_contents.length()))
208 return false;
211 base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
213 int bytes_written = base::WriteFile(temp_file_path, contents.data(),
214 contents.length());
216 if (bytes_written != static_cast<int>(contents.length()))
217 return false;
219 std::vector<std::string> argv;
220 argv.push_back("xdg-desktop-menu");
221 argv.push_back("install");
223 // Always install in user mode, even if someone runs the browser as root
224 // (people do that).
225 argv.push_back("--mode");
226 argv.push_back("user");
228 // If provided, install the shortcut file inside the given directory.
229 if (!directory_filename.empty())
230 argv.push_back(temp_directory_path.value());
231 argv.push_back(temp_file_path.value());
232 int exit_code;
233 LaunchXdgUtility(argv, &exit_code);
234 return exit_code == 0;
237 void DeleteShortcutInApplicationsMenu(
238 const base::FilePath& shortcut_filename,
239 const base::FilePath& directory_filename) {
240 std::vector<std::string> argv;
241 argv.push_back("xdg-desktop-menu");
242 argv.push_back("uninstall");
244 // Uninstall in user mode, to match the install.
245 argv.push_back("--mode");
246 argv.push_back("user");
248 // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
249 // items from the menu with a matching name.
250 // If |directory_filename| is supplied, this will also remove the item from
251 // the directory, and remove the directory if it is empty.
252 if (!directory_filename.empty())
253 argv.push_back(directory_filename.value());
254 argv.push_back(shortcut_filename.value());
255 int exit_code;
256 LaunchXdgUtility(argv, &exit_code);
259 // Quote a string such that it appears as one verbatim argument for the Exec
260 // key in a desktop file.
261 std::string QuoteArgForDesktopFileExec(const std::string& arg) {
262 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
264 // Quoting is only necessary if the argument has a reserved character.
265 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
266 return arg; // No quoting necessary.
268 std::string quoted = "\"";
269 for (size_t i = 0; i < arg.size(); ++i) {
270 // Note that the set of backslashed characters is smaller than the
271 // set of reserved characters.
272 switch (arg[i]) {
273 case '"':
274 case '`':
275 case '$':
276 case '\\':
277 quoted += '\\';
278 break;
280 quoted += arg[i];
282 quoted += '"';
284 return quoted;
287 // Quote a command line so it is suitable for use as the Exec key in a desktop
288 // file. Note: This should be used instead of GetCommandLineString, which does
289 // not properly quote the string; this function is designed for the Exec key.
290 std::string QuoteCommandLineForDesktopFileExec(
291 const CommandLine& command_line) {
292 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
294 std::string quoted_path = "";
295 const CommandLine::StringVector& argv = command_line.argv();
296 for (CommandLine::StringVector::const_iterator i = argv.begin();
297 i != argv.end(); ++i) {
298 if (i != argv.begin())
299 quoted_path += " ";
300 quoted_path += QuoteArgForDesktopFileExec(*i);
303 return quoted_path;
306 const char kDesktopEntry[] = "Desktop Entry";
308 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
310 const char kXdgSettings[] = "xdg-settings";
311 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
312 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
314 const char kDirectoryFilename[] = "chrome-apps.directory";
316 #if defined(GOOGLE_CHROME_BUILD)
317 const char kAppListDesktopName[] = "chrome-app-list";
318 #else // CHROMIUM_BUILD
319 const char kAppListDesktopName[] = "chromium-app-list";
320 #endif
322 } // namespace
324 namespace {
326 // Utility function to get the path to the version of a script shipped with
327 // Chrome. |script| gives the name of the script. |chrome_version| returns the
328 // path to the Chrome version of the script, and the return value of the
329 // function is true if the function is successful and the Chrome version is
330 // not the script found on the PATH.
331 bool GetChromeVersionOfScript(const std::string& script,
332 std::string* chrome_version) {
333 // Get the path to the Chrome version.
334 base::FilePath chrome_dir;
335 if (!PathService::Get(base::DIR_EXE, &chrome_dir))
336 return false;
338 base::FilePath chrome_version_path = chrome_dir.Append(script);
339 *chrome_version = chrome_version_path.value();
341 // Check if this is different to the one on path.
342 std::vector<std::string> argv;
343 argv.push_back("which");
344 argv.push_back(script);
345 std::string path_version;
346 if (base::GetAppOutput(CommandLine(argv), &path_version)) {
347 // Remove trailing newline
348 path_version.erase(path_version.length() - 1, 1);
349 base::FilePath path_version_path(path_version);
350 return (chrome_version_path != path_version_path);
352 return false;
355 // Value returned by xdg-settings if it can't understand our request.
356 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
358 // We delegate the difficulty of setting the default browser and default url
359 // scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
361 // When calling this script we first try to use the script on PATH. If that
362 // fails we then try to use the script that we have included. This gives
363 // scripts on the system priority over ours, as distribution vendors may have
364 // tweaked the script, but still allows our copy to be used if the script on the
365 // system fails, as the system copy may be missing capabilities of the Chrome
366 // copy.
368 // If |protocol| is empty this function sets Chrome as the default browser,
369 // otherwise it sets Chrome as the default handler application for |protocol|.
370 bool SetDefaultWebClient(const std::string& protocol) {
371 #if defined(OS_CHROMEOS)
372 return true;
373 #else
374 scoped_ptr<base::Environment> env(base::Environment::Create());
376 std::vector<std::string> argv;
377 argv.push_back(kXdgSettings);
378 argv.push_back("set");
379 if (protocol.empty()) {
380 argv.push_back(kXdgSettingsDefaultBrowser);
381 } else {
382 argv.push_back(kXdgSettingsDefaultSchemeHandler);
383 argv.push_back(protocol);
385 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
387 int exit_code;
388 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
389 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
390 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
391 ran_ok = LaunchXdgUtility(argv, &exit_code);
395 return ran_ok && exit_code == EXIT_SUCCESS;
396 #endif
399 // If |protocol| is empty this function checks if Chrome is the default browser,
400 // otherwise it checks if Chrome is the default handler application for
401 // |protocol|.
402 ShellIntegration::DefaultWebClientState GetIsDefaultWebClient(
403 const std::string& protocol) {
404 #if defined(OS_CHROMEOS)
405 return ShellIntegration::UNKNOWN_DEFAULT;
406 #else
407 base::ThreadRestrictions::AssertIOAllowed();
409 scoped_ptr<base::Environment> env(base::Environment::Create());
411 std::vector<std::string> argv;
412 argv.push_back(kXdgSettings);
413 argv.push_back("check");
414 if (protocol.empty()) {
415 argv.push_back(kXdgSettingsDefaultBrowser);
416 } else {
417 argv.push_back(kXdgSettingsDefaultSchemeHandler);
418 argv.push_back(protocol);
420 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
422 std::string reply;
423 int success_code;
424 bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
425 &success_code);
426 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
427 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
428 ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
429 &success_code);
433 if (!ran_ok || success_code != EXIT_SUCCESS) {
434 // xdg-settings failed: we can't determine or set the default browser.
435 return ShellIntegration::UNKNOWN_DEFAULT;
438 // Allow any reply that starts with "yes".
439 return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT :
440 ShellIntegration::NOT_DEFAULT;
441 #endif
444 // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
445 // file, given in |shortcut_contents|. If the key is not found, returns false.
446 bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
447 // An empty file causes a crash with glib <= 2.32, so special case here.
448 if (shortcut_contents.empty())
449 return false;
451 GKeyFile* key_file = g_key_file_new();
452 GError* err = NULL;
453 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
454 shortcut_contents.size(), G_KEY_FILE_NONE,
455 &err)) {
456 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
457 g_error_free(err);
458 g_key_file_free(key_file);
459 return false;
462 bool nodisplay = false;
463 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
464 "NoDisplay", &err);
465 if (nodisplay_c_string) {
466 if (!g_strcmp0(nodisplay_c_string, "true"))
467 nodisplay = true;
468 g_free(nodisplay_c_string);
469 } else {
470 g_error_free(err);
473 g_key_file_free(key_file);
474 return nodisplay;
477 // Gets the path to the Chrome executable or wrapper script.
478 // Returns an empty path if the executable path could not be found.
479 base::FilePath GetChromeExePath() {
480 // Try to get the name of the wrapper script that launched Chrome.
481 scoped_ptr<base::Environment> environment(base::Environment::Create());
482 std::string wrapper_script;
483 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) {
484 return base::FilePath(wrapper_script);
487 // Just return the name of the executable path for Chrome.
488 base::FilePath chrome_exe_path;
489 PathService::Get(base::FILE_EXE, &chrome_exe_path);
490 return chrome_exe_path;
493 } // namespace
495 // static
496 ShellIntegration::DefaultWebClientSetPermission
497 ShellIntegration::CanSetAsDefaultBrowser() {
498 return SET_DEFAULT_UNATTENDED;
501 // static
502 bool ShellIntegration::SetAsDefaultBrowser() {
503 return SetDefaultWebClient(std::string());
506 // static
507 bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
508 return SetDefaultWebClient(protocol);
511 // static
512 ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
513 return GetIsDefaultWebClient(std::string());
516 // static
517 base::string16 ShellIntegration::GetApplicationNameForProtocol(
518 const GURL& url) {
519 return base::ASCIIToUTF16("xdg-open");
522 // static
523 ShellIntegration::DefaultWebClientState
524 ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
525 return GetIsDefaultWebClient(protocol);
528 // static
529 bool ShellIntegration::IsFirefoxDefaultBrowser() {
530 std::vector<std::string> argv;
531 argv.push_back(kXdgSettings);
532 argv.push_back("get");
533 argv.push_back(kXdgSettingsDefaultBrowser);
535 std::string browser;
536 // We don't care about the return value here.
537 base::GetAppOutput(CommandLine(argv), &browser);
538 return browser.find("irefox") != std::string::npos;
541 namespace ShellIntegrationLinux {
543 bool GetDataWriteLocation(base::Environment* env, base::FilePath* search_path) {
544 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
546 std::string xdg_data_home;
547 std::string home;
548 if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) && !xdg_data_home.empty()) {
549 *search_path = base::FilePath(xdg_data_home);
550 return true;
551 } else if (env->GetVar("HOME", &home) && !home.empty()) {
552 *search_path = base::FilePath(home).Append(".local").Append("share");
553 return true;
555 return false;
558 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
561 std::vector<base::FilePath> search_paths;
563 base::FilePath write_location;
564 if (GetDataWriteLocation(env, &write_location))
565 search_paths.push_back(write_location);
567 std::string xdg_data_dirs;
568 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
569 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
570 while (tokenizer.GetNext()) {
571 base::FilePath data_dir(tokenizer.token());
572 search_paths.push_back(data_dir);
574 } else {
575 search_paths.push_back(base::FilePath("/usr/local/share"));
576 search_paths.push_back(base::FilePath("/usr/share"));
579 return search_paths;
582 std::string GetProgramClassName() {
583 DCHECK(CommandLine::InitializedForCurrentProcess());
584 // Get the res_name component from argv[0].
585 const CommandLine* command_line = CommandLine::ForCurrentProcess();
586 std::string class_name = command_line->GetProgram().BaseName().value();
587 if (!class_name.empty())
588 class_name[0] = base::ToUpperASCII(class_name[0]);
589 return class_name;
592 std::string GetDesktopName(base::Environment* env) {
593 #if defined(GOOGLE_CHROME_BUILD)
594 chrome::VersionInfo::Channel product_channel(
595 chrome::VersionInfo::GetChannel());
596 switch (product_channel) {
597 case chrome::VersionInfo::CHANNEL_DEV:
598 return "google-chrome-unstable.desktop";
599 case chrome::VersionInfo::CHANNEL_BETA:
600 return "google-chrome-beta.desktop";
601 default:
602 return "google-chrome.desktop";
604 #else // CHROMIUM_BUILD
605 // Allow $CHROME_DESKTOP to override the built-in value, so that development
606 // versions can set themselves as the default without interfering with
607 // non-official, packaged versions using the built-in value.
608 std::string name;
609 if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
610 return name;
611 return "chromium-browser.desktop";
612 #endif
615 std::string GetIconName() {
616 #if defined(GOOGLE_CHROME_BUILD)
617 return "google-chrome";
618 #else // CHROMIUM_BUILD
619 return "chromium-browser";
620 #endif
623 web_app::ShortcutLocations GetExistingShortcutLocations(
624 base::Environment* env,
625 const base::FilePath& profile_path,
626 const std::string& extension_id) {
627 base::FilePath desktop_path;
628 // If Get returns false, just leave desktop_path empty.
629 PathService::Get(base::DIR_USER_DESKTOP, &desktop_path);
630 return GetExistingShortcutLocations(env, profile_path, extension_id,
631 desktop_path);
634 web_app::ShortcutLocations GetExistingShortcutLocations(
635 base::Environment* env,
636 const base::FilePath& profile_path,
637 const std::string& extension_id,
638 const base::FilePath& desktop_path) {
639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
641 base::FilePath shortcut_filename = GetExtensionShortcutFilename(
642 profile_path, extension_id);
643 DCHECK(!shortcut_filename.empty());
644 web_app::ShortcutLocations locations;
646 // Determine whether there is a shortcut on desktop.
647 if (!desktop_path.empty()) {
648 locations.on_desktop =
649 base::PathExists(desktop_path.Append(shortcut_filename));
652 // Determine whether there is a shortcut in the applications directory.
653 std::string shortcut_contents;
654 if (GetExistingShortcutContents(env, shortcut_filename, &shortcut_contents)) {
655 // Whether this counts as "hidden" or "APP_MENU_LOCATION_SUBDIR_CHROMEAPPS"
656 // depends on whether it contains NoDisplay=true. Since these shortcuts are
657 // for apps, they are always in the "Chrome Apps" directory.
658 if (GetNoDisplayFromDesktopFile(shortcut_contents)) {
659 locations.hidden = true;
660 } else {
661 locations.applications_menu_location =
662 web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
666 return locations;
669 bool GetExistingShortcutContents(base::Environment* env,
670 const base::FilePath& desktop_filename,
671 std::string* output) {
672 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
674 std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
676 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
677 i != search_paths.end(); ++i) {
678 base::FilePath path = i->Append("applications").Append(desktop_filename);
679 VLOG(1) << "Looking for desktop file in " << path.value();
680 if (base::PathExists(path)) {
681 VLOG(1) << "Found desktop file at " << path.value();
682 return base::ReadFileToString(path, output);
686 return false;
689 base::FilePath GetWebShortcutFilename(const GURL& url) {
690 // Use a prefix, because xdg-desktop-menu requires it.
691 std::string filename =
692 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
693 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
695 base::FilePath desktop_path;
696 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
697 return base::FilePath();
699 base::FilePath filepath = desktop_path.Append(filename);
700 base::FilePath alternative_filepath(filepath.value() + ".desktop");
701 for (size_t i = 1; i < 100; ++i) {
702 if (base::PathExists(base::FilePath(alternative_filepath))) {
703 alternative_filepath = base::FilePath(
704 filepath.value() + "_" + base::IntToString(i) + ".desktop");
705 } else {
706 return base::FilePath(alternative_filepath).BaseName();
710 return base::FilePath();
713 base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path,
714 const std::string& extension_id) {
715 DCHECK(!extension_id.empty());
717 // Use a prefix, because xdg-desktop-menu requires it.
718 std::string filename(chrome::kBrowserProcessExecutableName);
719 filename.append("-")
720 .append(extension_id)
721 .append("-")
722 .append(profile_path.BaseName().value());
723 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
724 // Spaces in filenames break xdg-desktop-menu
725 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
726 base::ReplaceChars(filename, " ", "_", &filename);
727 return base::FilePath(filename.append(".desktop"));
730 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
731 const base::FilePath& profile_path,
732 const base::FilePath& directory) {
733 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
734 // Use a prefix, because xdg-desktop-menu requires it.
735 std::string prefix(chrome::kBrowserProcessExecutableName);
736 prefix.append("-");
737 std::string suffix("-");
738 suffix.append(profile_path.BaseName().value());
739 file_util::ReplaceIllegalCharactersInPath(&suffix, '_');
740 // Spaces in filenames break xdg-desktop-menu
741 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
742 base::ReplaceChars(suffix, " ", "_", &suffix);
743 std::string glob = prefix + "*" + suffix + ".desktop";
745 base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
746 glob);
747 base::FilePath shortcut_file = files.Next();
748 std::vector<base::FilePath> shortcut_paths;
749 while (!shortcut_file.empty()) {
750 shortcut_paths.push_back(shortcut_file.BaseName());
751 shortcut_file = files.Next();
753 return shortcut_paths;
756 std::string GetDesktopFileContents(
757 const base::FilePath& chrome_exe_path,
758 const std::string& app_name,
759 const GURL& url,
760 const std::string& extension_id,
761 const base::string16& title,
762 const std::string& icon_name,
763 const base::FilePath& profile_path,
764 const std::string& categories,
765 bool no_display) {
766 CommandLine cmd_line = ShellIntegration::CommandLineArgsForLauncher(
767 url, extension_id, profile_path);
768 cmd_line.SetProgram(chrome_exe_path);
769 return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
770 icon_name, categories, no_display);
773 std::string GetDesktopFileContentsForCommand(
774 const CommandLine& command_line,
775 const std::string& app_name,
776 const GURL& url,
777 const base::string16& title,
778 const std::string& icon_name,
779 const std::string& categories,
780 bool no_display) {
781 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
782 // launchers with an xdg-open shebang. Follow that convention.
783 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
785 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
786 GKeyFile* key_file = g_key_file_new();
788 // Set keys with fixed values.
789 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
790 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
791 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
793 // Set the "Name" key.
794 std::string final_title = base::UTF16ToUTF8(title);
795 // Make sure no endline characters can slip in and possibly introduce
796 // additional lines (like Exec, which makes it a security risk). Also
797 // use the URL as a default when the title is empty.
798 if (final_title.empty() ||
799 final_title.find("\n") != std::string::npos ||
800 final_title.find("\r") != std::string::npos) {
801 final_title = url.spec();
803 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
805 // Set the "Exec" key.
806 std::string final_path = QuoteCommandLineForDesktopFileExec(command_line);
807 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
809 // Set the "Icon" key.
810 if (!icon_name.empty()) {
811 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
812 } else {
813 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
814 GetIconName().c_str());
817 // Set the "Categories" key.
818 if (!categories.empty()) {
819 g_key_file_set_string(
820 key_file, kDesktopEntry, "Categories", categories.c_str());
823 // Set the "NoDisplay" key.
824 if (no_display)
825 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
827 std::string wmclass = web_app::GetWMClassFromAppName(app_name);
828 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
829 wmclass.c_str());
831 gsize length = 0;
832 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
833 if (data_dump) {
834 // If strlen(data_dump[0]) == 0, this check will fail.
835 if (data_dump[0] == '\n') {
836 // Older versions of glib produce a leading newline. If this is the case,
837 // remove it to avoid double-newline after the shebang.
838 output_buffer += (data_dump + 1);
839 } else {
840 output_buffer += data_dump;
842 g_free(data_dump);
845 g_key_file_free(key_file);
846 return output_buffer;
849 std::string GetDirectoryFileContents(const base::string16& title,
850 const std::string& icon_name) {
851 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
852 GKeyFile* key_file = g_key_file_new();
854 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
855 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
856 std::string final_title = base::UTF16ToUTF8(title);
857 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
858 if (!icon_name.empty()) {
859 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
860 } else {
861 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
862 GetIconName().c_str());
865 gsize length = 0;
866 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
867 std::string output_buffer;
868 if (data_dump) {
869 // If strlen(data_dump[0]) == 0, this check will fail.
870 if (data_dump[0] == '\n') {
871 // Older versions of glib produce a leading newline. If this is the case,
872 // remove it to avoid double-newline after the shebang.
873 output_buffer += (data_dump + 1);
874 } else {
875 output_buffer += data_dump;
877 g_free(data_dump);
880 g_key_file_free(key_file);
881 return output_buffer;
884 bool CreateDesktopShortcut(
885 const web_app::ShortcutInfo& shortcut_info,
886 const web_app::ShortcutLocations& creation_locations) {
887 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
889 base::FilePath shortcut_filename;
890 if (!shortcut_info.extension_id.empty()) {
891 shortcut_filename = GetExtensionShortcutFilename(
892 shortcut_info.profile_path, shortcut_info.extension_id);
893 // For extensions we do not want duplicate shortcuts. So, delete any that
894 // already exist and replace them.
895 if (creation_locations.on_desktop)
896 DeleteShortcutOnDesktop(shortcut_filename);
897 // The 'applications_menu_location' and 'hidden' locations are actually the
898 // same place ('applications').
899 if (creation_locations.applications_menu_location !=
900 web_app::APP_MENU_LOCATION_NONE ||
901 creation_locations.hidden)
902 DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
903 } else {
904 shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
906 if (shortcut_filename.empty())
907 return false;
909 std::string icon_name =
910 CreateShortcutIcon(shortcut_info.favicon, shortcut_filename);
912 std::string app_name =
913 web_app::GenerateApplicationNameFromInfo(shortcut_info);
915 bool success = true;
917 base::FilePath chrome_exe_path = GetChromeExePath();
918 if (chrome_exe_path.empty()) {
919 LOG(WARNING) << "Could not get executable path.";
920 return false;
923 if (creation_locations.on_desktop) {
924 std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
925 chrome_exe_path,
926 app_name,
927 shortcut_info.url,
928 shortcut_info.extension_id,
929 shortcut_info.title,
930 icon_name,
931 shortcut_info.profile_path,
933 false);
934 success = CreateShortcutOnDesktop(shortcut_filename, contents);
937 if (creation_locations.applications_menu_location !=
938 web_app::APP_MENU_LOCATION_NONE ||
939 creation_locations.hidden) {
940 base::FilePath directory_filename;
941 std::string directory_contents;
942 switch (creation_locations.applications_menu_location) {
943 case web_app::APP_MENU_LOCATION_NONE:
944 case web_app::APP_MENU_LOCATION_ROOT:
945 break;
946 case web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS:
947 directory_filename = base::FilePath(kDirectoryFilename);
948 directory_contents = ShellIntegrationLinux::GetDirectoryFileContents(
949 ShellIntegration::GetAppShortcutsSubdirName(), "");
950 break;
951 default:
952 NOTREACHED();
953 break;
955 // Set NoDisplay=true if hidden but not in the applications menu. This will
956 // hide the application from user-facing menus.
957 std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
958 chrome_exe_path,
959 app_name,
960 shortcut_info.url,
961 shortcut_info.extension_id,
962 shortcut_info.title,
963 icon_name,
964 shortcut_info.profile_path,
966 creation_locations.applications_menu_location ==
967 web_app::APP_MENU_LOCATION_NONE);
968 success = CreateShortcutInApplicationsMenu(
969 shortcut_filename, contents, directory_filename, directory_contents) &&
970 success;
973 return success;
976 bool CreateAppListDesktopShortcut(
977 const std::string& wm_class,
978 const std::string& title) {
979 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
981 base::FilePath desktop_name(kAppListDesktopName);
982 base::FilePath shortcut_filename = desktop_name.AddExtension("desktop");
984 // We do not want duplicate shortcuts. Delete any that already exist and
985 // replace them.
986 DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
988 base::FilePath chrome_exe_path = GetChromeExePath();
989 if (chrome_exe_path.empty()) {
990 LOG(WARNING) << "Could not get executable path.";
991 return false;
994 gfx::ImageFamily icon_images;
995 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
996 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
997 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
998 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_48));
999 icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
1000 std::string icon_name = CreateShortcutIcon(icon_images, desktop_name);
1002 CommandLine command_line(chrome_exe_path);
1003 command_line.AppendSwitch(switches::kShowAppList);
1004 std::string contents =
1005 GetDesktopFileContentsForCommand(command_line,
1006 wm_class,
1007 GURL(),
1008 base::UTF8ToUTF16(title),
1009 icon_name,
1010 kAppListCategories,
1011 false);
1012 return CreateShortcutInApplicationsMenu(
1013 shortcut_filename, contents, base::FilePath(), "");
1016 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
1017 const std::string& extension_id) {
1018 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1020 base::FilePath shortcut_filename = GetExtensionShortcutFilename(
1021 profile_path, extension_id);
1022 DCHECK(!shortcut_filename.empty());
1024 DeleteShortcutOnDesktop(shortcut_filename);
1025 // Delete shortcuts from |kDirectoryFilename|.
1026 // Note that it is possible that shortcuts were not created in the Chrome Apps
1027 // directory. It doesn't matter: this will still delete the shortcut even if
1028 // it isn't in the directory.
1029 DeleteShortcutInApplicationsMenu(shortcut_filename,
1030 base::FilePath(kDirectoryFilename));
1033 void DeleteAllDesktopShortcuts(const base::FilePath& profile_path) {
1034 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1036 scoped_ptr<base::Environment> env(base::Environment::Create());
1038 // Delete shortcuts from Desktop.
1039 base::FilePath desktop_path;
1040 if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
1041 std::vector<base::FilePath> shortcut_filenames_desktop =
1042 GetExistingProfileShortcutFilenames(profile_path, desktop_path);
1043 for (std::vector<base::FilePath>::const_iterator it =
1044 shortcut_filenames_desktop.begin();
1045 it != shortcut_filenames_desktop.end(); ++it) {
1046 DeleteShortcutOnDesktop(*it);
1050 // Delete shortcuts from |kDirectoryFilename|.
1051 base::FilePath applications_menu;
1052 if (GetDataWriteLocation(env.get(), &applications_menu)) {
1053 applications_menu = applications_menu.AppendASCII("applications");
1054 std::vector<base::FilePath> shortcut_filenames_app_menu =
1055 GetExistingProfileShortcutFilenames(profile_path, applications_menu);
1056 for (std::vector<base::FilePath>::const_iterator it =
1057 shortcut_filenames_app_menu.begin();
1058 it != shortcut_filenames_app_menu.end(); ++it) {
1059 DeleteShortcutInApplicationsMenu(*it,
1060 base::FilePath(kDirectoryFilename));
1065 } // namespace ShellIntegrationLinux