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"
15 #include <sys/types.h>
21 #include "base/base_paths.h"
22 #include "base/command_line.h"
23 #include "base/environment.h"
24 #include "base/file_util.h"
25 #include "base/files/file_enumerator.h"
26 #include "base/files/file_path.h"
27 #include "base/files/scoped_temp_dir.h"
28 #include "base/i18n/file_util_icu.h"
29 #include "base/memory/ref_counted_memory.h"
30 #include "base/memory/scoped_ptr.h"
31 #include "base/message_loop/message_loop.h"
32 #include "base/path_service.h"
33 #include "base/posix/eintr_wrapper.h"
34 #include "base/process/kill.h"
35 #include "base/process/launch.h"
36 #include "base/strings/string_number_conversions.h"
37 #include "base/strings/string_tokenizer.h"
38 #include "base/strings/string_util.h"
39 #include "base/strings/utf_string_conversions.h"
40 #include "base/threading/thread.h"
41 #include "base/threading/thread_restrictions.h"
42 #include "build/build_config.h"
43 #include "chrome/browser/shell_integration.h"
44 #include "chrome/common/chrome_constants.h"
45 #include "chrome/common/chrome_switches.h"
46 #include "chrome/common/chrome_version_info.h"
47 #include "content/public/browser/browser_thread.h"
48 #include "grit/chrome_unscaled_resources.h"
49 #include "ui/base/resource/resource_bundle.h"
50 #include "ui/gfx/image/image_family.h"
53 using content::BrowserThread
;
57 // The Categories for the App Launcher desktop shortcut. Should be the same as
58 // the Chrome desktop shortcut, so they are in the same sub-menu.
59 const char kAppListCategories
[] = "Network;WebBrowser;";
61 // Helper to launch xdg scripts. We don't want them to ask any questions on the
62 // terminal etc. The function returns true if the utility launches and exits
63 // cleanly, in which case |exit_code| returns the utility's exit code.
64 bool LaunchXdgUtility(const std::vector
<std::string
>& argv
, int* exit_code
) {
65 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
66 // files on top of originals after making changes to them. In the event that
67 // the original files are owned by another user (e.g. root, which can happen
68 // if they are updated within sudo), mv will prompt the user to confirm if
69 // standard input is a terminal (otherwise it just does it). So make sure it's
70 // not, to avoid locking everything up waiting for mv.
71 *exit_code
= EXIT_FAILURE
;
72 int devnull
= open("/dev/null", O_RDONLY
);
75 base::FileHandleMappingVector no_stdin
;
76 no_stdin
.push_back(std::make_pair(devnull
, STDIN_FILENO
));
78 base::ProcessHandle handle
;
79 base::LaunchOptions options
;
80 options
.fds_to_remap
= &no_stdin
;
81 if (!base::LaunchProcess(argv
, options
, &handle
)) {
87 return base::WaitForExitCode(handle
, exit_code
);
90 std::string
CreateShortcutIcon(const gfx::ImageFamily
& icon_images
,
91 const base::FilePath
& shortcut_filename
) {
92 if (icon_images
.empty())
95 // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
96 base::ScopedTempDir temp_dir
;
97 if (!temp_dir
.CreateUniqueTempDir())
100 base::FilePath temp_file_path
= temp_dir
.path().Append(
101 shortcut_filename
.ReplaceExtension("png"));
102 std::string icon_name
= temp_file_path
.BaseName().RemoveExtension().value();
104 for (gfx::ImageFamily::const_iterator it
= icon_images
.begin();
105 it
!= icon_images
.end(); ++it
) {
106 int width
= it
->Width();
107 scoped_refptr
<base::RefCountedMemory
> png_data
= it
->As1xPNGBytes();
108 if (png_data
->size() == 0) {
109 // If the bitmap could not be encoded to PNG format, skip it.
110 LOG(WARNING
) << "Could not encode icon " << icon_name
<< ".png at size "
114 int bytes_written
= base::WriteFile(temp_file_path
,
115 png_data
->front_as
<char>(),
118 if (bytes_written
!= static_cast<int>(png_data
->size()))
119 return std::string();
121 std::vector
<std::string
> argv
;
122 argv
.push_back("xdg-icon-resource");
123 argv
.push_back("install");
125 // Always install in user mode, even if someone runs the browser as root
127 argv
.push_back("--mode");
128 argv
.push_back("user");
130 argv
.push_back("--size");
131 argv
.push_back(base::IntToString(width
));
133 argv
.push_back(temp_file_path
.value());
134 argv
.push_back(icon_name
);
136 if (!LaunchXdgUtility(argv
, &exit_code
) || exit_code
) {
137 LOG(WARNING
) << "Could not install icon " << icon_name
<< ".png at size "
144 bool CreateShortcutOnDesktop(const base::FilePath
& shortcut_filename
,
145 const std::string
& contents
) {
146 // Make sure that we will later call openat in a secure way.
147 DCHECK_EQ(shortcut_filename
.BaseName().value(), shortcut_filename
.value());
149 base::FilePath desktop_path
;
150 if (!PathService::Get(base::DIR_USER_DESKTOP
, &desktop_path
))
153 int desktop_fd
= open(desktop_path
.value().c_str(), O_RDONLY
| O_DIRECTORY
);
157 int fd
= openat(desktop_fd
, shortcut_filename
.value().c_str(),
158 O_CREAT
| O_EXCL
| O_WRONLY
,
159 S_IRWXU
| S_IRGRP
| S_IXGRP
| S_IROTH
| S_IXOTH
);
161 if (IGNORE_EINTR(close(desktop_fd
)) < 0)
162 PLOG(ERROR
) << "close";
166 ssize_t bytes_written
= base::WriteFileDescriptor(fd
, contents
.data(),
168 if (IGNORE_EINTR(close(fd
)) < 0)
169 PLOG(ERROR
) << "close";
171 if (bytes_written
!= static_cast<ssize_t
>(contents
.length())) {
172 // Delete the file. No shortuct is better than corrupted one. Use unlinkat
173 // to make sure we're deleting the file in the directory we think we are.
174 // Even if an attacker manager to put something other at
175 // |shortcut_filename| we'll just undo his action.
176 unlinkat(desktop_fd
, shortcut_filename
.value().c_str(), 0);
179 if (IGNORE_EINTR(close(desktop_fd
)) < 0)
180 PLOG(ERROR
) << "close";
185 void DeleteShortcutOnDesktop(const base::FilePath
& shortcut_filename
) {
186 base::FilePath desktop_path
;
187 if (PathService::Get(base::DIR_USER_DESKTOP
, &desktop_path
))
188 base::DeleteFile(desktop_path
.Append(shortcut_filename
), false);
191 // Creates a shortcut with |shortcut_filename| and |contents| in the system
192 // applications menu. If |directory_filename| is non-empty, creates a sub-menu
193 // with |directory_filename| and |directory_contents|, and stores the shortcut
194 // under the sub-menu.
195 bool CreateShortcutInApplicationsMenu(const base::FilePath
& shortcut_filename
,
196 const std::string
& contents
,
197 const base::FilePath
& directory_filename
,
198 const std::string
& directory_contents
) {
199 base::ScopedTempDir temp_dir
;
200 if (!temp_dir
.CreateUniqueTempDir())
203 base::FilePath temp_directory_path
;
204 if (!directory_filename
.empty()) {
205 temp_directory_path
= temp_dir
.path().Append(directory_filename
);
207 int bytes_written
= base::WriteFile(temp_directory_path
,
208 directory_contents
.data(),
209 directory_contents
.length());
211 if (bytes_written
!= static_cast<int>(directory_contents
.length()))
215 base::FilePath temp_file_path
= temp_dir
.path().Append(shortcut_filename
);
217 int bytes_written
= base::WriteFile(temp_file_path
, contents
.data(),
220 if (bytes_written
!= static_cast<int>(contents
.length()))
223 std::vector
<std::string
> argv
;
224 argv
.push_back("xdg-desktop-menu");
225 argv
.push_back("install");
227 // Always install in user mode, even if someone runs the browser as root
229 argv
.push_back("--mode");
230 argv
.push_back("user");
232 // If provided, install the shortcut file inside the given directory.
233 if (!directory_filename
.empty())
234 argv
.push_back(temp_directory_path
.value());
235 argv
.push_back(temp_file_path
.value());
237 LaunchXdgUtility(argv
, &exit_code
);
238 return exit_code
== 0;
241 void DeleteShortcutInApplicationsMenu(
242 const base::FilePath
& shortcut_filename
,
243 const base::FilePath
& directory_filename
) {
244 std::vector
<std::string
> argv
;
245 argv
.push_back("xdg-desktop-menu");
246 argv
.push_back("uninstall");
248 // Uninstall in user mode, to match the install.
249 argv
.push_back("--mode");
250 argv
.push_back("user");
252 // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
253 // items from the menu with a matching name.
254 // If |directory_filename| is supplied, this will also remove the item from
255 // the directory, and remove the directory if it is empty.
256 if (!directory_filename
.empty())
257 argv
.push_back(directory_filename
.value());
258 argv
.push_back(shortcut_filename
.value());
260 LaunchXdgUtility(argv
, &exit_code
);
263 // Quote a string such that it appears as one verbatim argument for the Exec
264 // key in a desktop file.
265 std::string
QuoteArgForDesktopFileExec(const std::string
& arg
) {
266 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
268 // Quoting is only necessary if the argument has a reserved character.
269 if (arg
.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos
)
270 return arg
; // No quoting necessary.
272 std::string quoted
= "\"";
273 for (size_t i
= 0; i
< arg
.size(); ++i
) {
274 // Note that the set of backslashed characters is smaller than the
275 // set of reserved characters.
291 // Quote a command line so it is suitable for use as the Exec key in a desktop
292 // file. Note: This should be used instead of GetCommandLineString, which does
293 // not properly quote the string; this function is designed for the Exec key.
294 std::string
QuoteCommandLineForDesktopFileExec(
295 const CommandLine
& command_line
) {
296 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
298 std::string quoted_path
= "";
299 const CommandLine::StringVector
& argv
= command_line
.argv();
300 for (CommandLine::StringVector::const_iterator i
= argv
.begin();
301 i
!= argv
.end(); ++i
) {
302 if (i
!= argv
.begin())
304 quoted_path
+= QuoteArgForDesktopFileExec(*i
);
310 const char kDesktopEntry
[] = "Desktop Entry";
312 const char kXdgOpenShebang
[] = "#!/usr/bin/env xdg-open";
314 const char kXdgSettings
[] = "xdg-settings";
315 const char kXdgSettingsDefaultBrowser
[] = "default-web-browser";
316 const char kXdgSettingsDefaultSchemeHandler
[] = "default-url-scheme-handler";
318 const char kDirectoryFilename
[] = "chrome-apps.directory";
320 #if defined(GOOGLE_CHROME_BUILD)
321 const char kAppListDesktopName
[] = "chrome-app-list";
322 #else // CHROMIUM_BUILD
323 const char kAppListDesktopName
[] = "chromium-app-list";
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
))
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
);
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
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)
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
);
382 argv
.push_back(kXdgSettingsDefaultSchemeHandler
);
383 argv
.push_back(protocol
);
385 argv
.push_back(shell_integration_linux::GetDesktopName(env
.get()));
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
;
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
402 ShellIntegration::DefaultWebClientState
GetIsDefaultWebClient(
403 const std::string
& protocol
) {
404 #if defined(OS_CHROMEOS)
405 return ShellIntegration::UNKNOWN_DEFAULT
;
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
);
417 argv
.push_back(kXdgSettingsDefaultSchemeHandler
);
418 argv
.push_back(protocol
);
420 argv
.push_back(shell_integration_linux::GetDesktopName(env
.get()));
424 bool ran_ok
= base::GetAppOutputWithExitCode(CommandLine(argv
), &reply
,
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
,
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
;
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 #if defined(USE_GLIB)
448 // An empty file causes a crash with glib <= 2.32, so special case here.
449 if (shortcut_contents
.empty())
452 GKeyFile
* key_file
= g_key_file_new();
454 if (!g_key_file_load_from_data(key_file
, shortcut_contents
.c_str(),
455 shortcut_contents
.size(), G_KEY_FILE_NONE
,
457 LOG(WARNING
) << "Unable to read desktop file template: " << err
->message
;
459 g_key_file_free(key_file
);
463 bool nodisplay
= false;
464 char* nodisplay_c_string
= g_key_file_get_string(key_file
, kDesktopEntry
,
466 if (nodisplay_c_string
) {
467 if (!g_strcmp0(nodisplay_c_string
, "true"))
469 g_free(nodisplay_c_string
);
474 g_key_file_free(key_file
);
482 // Gets the path to the Chrome executable or wrapper script.
483 // Returns an empty path if the executable path could not be found.
484 base::FilePath
GetChromeExePath() {
485 // Try to get the name of the wrapper script that launched Chrome.
486 scoped_ptr
<base::Environment
> environment(base::Environment::Create());
487 std::string wrapper_script
;
488 if (environment
->GetVar("CHROME_WRAPPER", &wrapper_script
)) {
489 return base::FilePath(wrapper_script
);
492 // Just return the name of the executable path for Chrome.
493 base::FilePath chrome_exe_path
;
494 PathService::Get(base::FILE_EXE
, &chrome_exe_path
);
495 return chrome_exe_path
;
501 ShellIntegration::DefaultWebClientSetPermission
502 ShellIntegration::CanSetAsDefaultBrowser() {
503 return SET_DEFAULT_UNATTENDED
;
507 bool ShellIntegration::SetAsDefaultBrowser() {
508 return SetDefaultWebClient(std::string());
512 bool ShellIntegration::SetAsDefaultProtocolClient(
513 const std::string
& protocol
) {
514 return SetDefaultWebClient(protocol
);
518 ShellIntegration::DefaultWebClientState
519 ShellIntegration::GetDefaultBrowser() {
520 return GetIsDefaultWebClient(std::string());
524 base::string16
ShellIntegration::GetApplicationNameForProtocol(
526 return base::ASCIIToUTF16("xdg-open");
530 ShellIntegration::DefaultWebClientState
531 ShellIntegration::IsDefaultProtocolClient(const std::string
& protocol
) {
532 return GetIsDefaultWebClient(protocol
);
536 bool ShellIntegration::IsFirefoxDefaultBrowser() {
537 std::vector
<std::string
> argv
;
538 argv
.push_back(kXdgSettings
);
539 argv
.push_back("get");
540 argv
.push_back(kXdgSettingsDefaultBrowser
);
543 // We don't care about the return value here.
544 base::GetAppOutput(CommandLine(argv
), &browser
);
545 return browser
.find("irefox") != std::string::npos
;
548 namespace shell_integration_linux
{
550 bool GetDataWriteLocation(base::Environment
* env
, base::FilePath
* search_path
) {
551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
553 std::string xdg_data_home
;
555 if (env
->GetVar("XDG_DATA_HOME", &xdg_data_home
) && !xdg_data_home
.empty()) {
556 *search_path
= base::FilePath(xdg_data_home
);
558 } else if (env
->GetVar("HOME", &home
) && !home
.empty()) {
559 *search_path
= base::FilePath(home
).Append(".local").Append("share");
565 std::vector
<base::FilePath
> GetDataSearchLocations(base::Environment
* env
) {
566 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
568 std::vector
<base::FilePath
> search_paths
;
570 base::FilePath write_location
;
571 if (GetDataWriteLocation(env
, &write_location
))
572 search_paths
.push_back(write_location
);
574 std::string xdg_data_dirs
;
575 if (env
->GetVar("XDG_DATA_DIRS", &xdg_data_dirs
) && !xdg_data_dirs
.empty()) {
576 base::StringTokenizer
tokenizer(xdg_data_dirs
, ":");
577 while (tokenizer
.GetNext()) {
578 base::FilePath
data_dir(tokenizer
.token());
579 search_paths
.push_back(data_dir
);
582 search_paths
.push_back(base::FilePath("/usr/local/share"));
583 search_paths
.push_back(base::FilePath("/usr/share"));
589 std::string
GetProgramClassName() {
590 DCHECK(CommandLine::InitializedForCurrentProcess());
591 // Get the res_name component from argv[0].
592 const CommandLine
* command_line
= CommandLine::ForCurrentProcess();
593 std::string class_name
= command_line
->GetProgram().BaseName().value();
594 if (!class_name
.empty())
595 class_name
[0] = base::ToUpperASCII(class_name
[0]);
599 std::string
GetDesktopName(base::Environment
* env
) {
600 #if defined(GOOGLE_CHROME_BUILD)
601 chrome::VersionInfo::Channel
product_channel(
602 chrome::VersionInfo::GetChannel());
603 switch (product_channel
) {
604 case chrome::VersionInfo::CHANNEL_DEV
:
605 return "google-chrome-unstable.desktop";
606 case chrome::VersionInfo::CHANNEL_BETA
:
607 return "google-chrome-beta.desktop";
609 return "google-chrome.desktop";
611 #else // CHROMIUM_BUILD
612 // Allow $CHROME_DESKTOP to override the built-in value, so that development
613 // versions can set themselves as the default without interfering with
614 // non-official, packaged versions using the built-in value.
616 if (env
->GetVar("CHROME_DESKTOP", &name
) && !name
.empty())
618 return "chromium-browser.desktop";
622 std::string
GetIconName() {
623 #if defined(GOOGLE_CHROME_BUILD)
624 return "google-chrome";
625 #else // CHROMIUM_BUILD
626 return "chromium-browser";
630 web_app::ShortcutLocations
GetExistingShortcutLocations(
631 base::Environment
* env
,
632 const base::FilePath
& profile_path
,
633 const std::string
& extension_id
) {
634 base::FilePath desktop_path
;
635 // If Get returns false, just leave desktop_path empty.
636 PathService::Get(base::DIR_USER_DESKTOP
, &desktop_path
);
637 return GetExistingShortcutLocations(env
, profile_path
, extension_id
,
641 web_app::ShortcutLocations
GetExistingShortcutLocations(
642 base::Environment
* env
,
643 const base::FilePath
& profile_path
,
644 const std::string
& extension_id
,
645 const base::FilePath
& desktop_path
) {
646 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
648 base::FilePath shortcut_filename
= GetExtensionShortcutFilename(
649 profile_path
, extension_id
);
650 DCHECK(!shortcut_filename
.empty());
651 web_app::ShortcutLocations locations
;
653 // Determine whether there is a shortcut on desktop.
654 if (!desktop_path
.empty()) {
655 locations
.on_desktop
=
656 base::PathExists(desktop_path
.Append(shortcut_filename
));
659 // Determine whether there is a shortcut in the applications directory.
660 std::string shortcut_contents
;
661 if (GetExistingShortcutContents(env
, shortcut_filename
, &shortcut_contents
)) {
662 // Whether this counts as "hidden" or "APP_MENU_LOCATION_SUBDIR_CHROMEAPPS"
663 // depends on whether it contains NoDisplay=true. Since these shortcuts are
664 // for apps, they are always in the "Chrome Apps" directory.
665 if (GetNoDisplayFromDesktopFile(shortcut_contents
)) {
666 locations
.hidden
= true;
668 locations
.applications_menu_location
=
669 web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS
;
676 bool GetExistingShortcutContents(base::Environment
* env
,
677 const base::FilePath
& desktop_filename
,
678 std::string
* output
) {
679 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
681 std::vector
<base::FilePath
> search_paths
= GetDataSearchLocations(env
);
683 for (std::vector
<base::FilePath
>::const_iterator i
= search_paths
.begin();
684 i
!= search_paths
.end(); ++i
) {
685 base::FilePath path
= i
->Append("applications").Append(desktop_filename
);
686 VLOG(1) << "Looking for desktop file in " << path
.value();
687 if (base::PathExists(path
)) {
688 VLOG(1) << "Found desktop file at " << path
.value();
689 return base::ReadFileToString(path
, output
);
696 base::FilePath
GetWebShortcutFilename(const GURL
& url
) {
697 // Use a prefix, because xdg-desktop-menu requires it.
698 std::string filename
=
699 std::string(chrome::kBrowserProcessExecutableName
) + "-" + url
.spec();
700 file_util::ReplaceIllegalCharactersInPath(&filename
, '_');
702 base::FilePath desktop_path
;
703 if (!PathService::Get(base::DIR_USER_DESKTOP
, &desktop_path
))
704 return base::FilePath();
706 base::FilePath filepath
= desktop_path
.Append(filename
);
707 base::FilePath
alternative_filepath(filepath
.value() + ".desktop");
708 for (size_t i
= 1; i
< 100; ++i
) {
709 if (base::PathExists(base::FilePath(alternative_filepath
))) {
710 alternative_filepath
= base::FilePath(
711 filepath
.value() + "_" + base::IntToString(i
) + ".desktop");
713 return base::FilePath(alternative_filepath
).BaseName();
717 return base::FilePath();
720 base::FilePath
GetExtensionShortcutFilename(const base::FilePath
& profile_path
,
721 const std::string
& extension_id
) {
722 DCHECK(!extension_id
.empty());
724 // Use a prefix, because xdg-desktop-menu requires it.
725 std::string
filename(chrome::kBrowserProcessExecutableName
);
727 .append(extension_id
)
729 .append(profile_path
.BaseName().value());
730 file_util::ReplaceIllegalCharactersInPath(&filename
, '_');
731 // Spaces in filenames break xdg-desktop-menu
732 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
733 base::ReplaceChars(filename
, " ", "_", &filename
);
734 return base::FilePath(filename
.append(".desktop"));
737 std::vector
<base::FilePath
> GetExistingProfileShortcutFilenames(
738 const base::FilePath
& profile_path
,
739 const base::FilePath
& directory
) {
740 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
741 // Use a prefix, because xdg-desktop-menu requires it.
742 std::string
prefix(chrome::kBrowserProcessExecutableName
);
744 std::string
suffix("-");
745 suffix
.append(profile_path
.BaseName().value());
746 file_util::ReplaceIllegalCharactersInPath(&suffix
, '_');
747 // Spaces in filenames break xdg-desktop-menu
748 // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
749 base::ReplaceChars(suffix
, " ", "_", &suffix
);
750 std::string glob
= prefix
+ "*" + suffix
+ ".desktop";
752 base::FileEnumerator
files(directory
, false, base::FileEnumerator::FILES
,
754 base::FilePath shortcut_file
= files
.Next();
755 std::vector
<base::FilePath
> shortcut_paths
;
756 while (!shortcut_file
.empty()) {
757 shortcut_paths
.push_back(shortcut_file
.BaseName());
758 shortcut_file
= files
.Next();
760 return shortcut_paths
;
763 std::string
GetDesktopFileContents(
764 const base::FilePath
& chrome_exe_path
,
765 const std::string
& app_name
,
767 const std::string
& extension_id
,
768 const base::string16
& title
,
769 const std::string
& icon_name
,
770 const base::FilePath
& profile_path
,
771 const std::string
& categories
,
773 CommandLine cmd_line
= ShellIntegration::CommandLineArgsForLauncher(
774 url
, extension_id
, profile_path
);
775 cmd_line
.SetProgram(chrome_exe_path
);
776 return GetDesktopFileContentsForCommand(cmd_line
, app_name
, url
, title
,
777 icon_name
, categories
, no_display
);
780 std::string
GetDesktopFileContentsForCommand(
781 const CommandLine
& command_line
,
782 const std::string
& app_name
,
784 const base::string16
& title
,
785 const std::string
& icon_name
,
786 const std::string
& categories
,
788 #if defined(USE_GLIB)
789 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
790 // launchers with an xdg-open shebang. Follow that convention.
791 std::string output_buffer
= std::string(kXdgOpenShebang
) + "\n";
793 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
794 GKeyFile
* key_file
= g_key_file_new();
796 // Set keys with fixed values.
797 g_key_file_set_string(key_file
, kDesktopEntry
, "Version", "1.0");
798 g_key_file_set_string(key_file
, kDesktopEntry
, "Terminal", "false");
799 g_key_file_set_string(key_file
, kDesktopEntry
, "Type", "Application");
801 // Set the "Name" key.
802 std::string final_title
= base::UTF16ToUTF8(title
);
803 // Make sure no endline characters can slip in and possibly introduce
804 // additional lines (like Exec, which makes it a security risk). Also
805 // use the URL as a default when the title is empty.
806 if (final_title
.empty() ||
807 final_title
.find("\n") != std::string::npos
||
808 final_title
.find("\r") != std::string::npos
) {
809 final_title
= url
.spec();
811 g_key_file_set_string(key_file
, kDesktopEntry
, "Name", final_title
.c_str());
813 // Set the "Exec" key.
814 std::string final_path
= QuoteCommandLineForDesktopFileExec(command_line
);
815 g_key_file_set_string(key_file
, kDesktopEntry
, "Exec", final_path
.c_str());
817 // Set the "Icon" key.
818 if (!icon_name
.empty()) {
819 g_key_file_set_string(key_file
, kDesktopEntry
, "Icon", icon_name
.c_str());
821 g_key_file_set_string(key_file
, kDesktopEntry
, "Icon",
822 GetIconName().c_str());
825 // Set the "Categories" key.
826 if (!categories
.empty()) {
827 g_key_file_set_string(
828 key_file
, kDesktopEntry
, "Categories", categories
.c_str());
831 // Set the "NoDisplay" key.
833 g_key_file_set_string(key_file
, kDesktopEntry
, "NoDisplay", "true");
835 std::string wmclass
= web_app::GetWMClassFromAppName(app_name
);
836 g_key_file_set_string(key_file
, kDesktopEntry
, "StartupWMClass",
840 gchar
* data_dump
= g_key_file_to_data(key_file
, &length
, NULL
);
842 // If strlen(data_dump[0]) == 0, this check will fail.
843 if (data_dump
[0] == '\n') {
844 // Older versions of glib produce a leading newline. If this is the case,
845 // remove it to avoid double-newline after the shebang.
846 output_buffer
+= (data_dump
+ 1);
848 output_buffer
+= data_dump
;
853 g_key_file_free(key_file
);
854 return output_buffer
;
857 return std::string();
861 std::string
GetDirectoryFileContents(const base::string16
& title
,
862 const std::string
& icon_name
) {
863 #if defined(USE_GLIB)
864 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
865 GKeyFile
* key_file
= g_key_file_new();
867 g_key_file_set_string(key_file
, kDesktopEntry
, "Version", "1.0");
868 g_key_file_set_string(key_file
, kDesktopEntry
, "Type", "Directory");
869 std::string final_title
= base::UTF16ToUTF8(title
);
870 g_key_file_set_string(key_file
, kDesktopEntry
, "Name", final_title
.c_str());
871 if (!icon_name
.empty()) {
872 g_key_file_set_string(key_file
, kDesktopEntry
, "Icon", icon_name
.c_str());
874 g_key_file_set_string(key_file
, kDesktopEntry
, "Icon",
875 GetIconName().c_str());
879 gchar
* data_dump
= g_key_file_to_data(key_file
, &length
, NULL
);
880 std::string output_buffer
;
882 // If strlen(data_dump[0]) == 0, this check will fail.
883 if (data_dump
[0] == '\n') {
884 // Older versions of glib produce a leading newline. If this is the case,
885 // remove it to avoid double-newline after the shebang.
886 output_buffer
+= (data_dump
+ 1);
888 output_buffer
+= data_dump
;
893 g_key_file_free(key_file
);
894 return output_buffer
;
897 return std::string();
901 bool CreateDesktopShortcut(
902 const web_app::ShortcutInfo
& shortcut_info
,
903 const web_app::ShortcutLocations
& creation_locations
) {
904 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
906 base::FilePath shortcut_filename
;
907 if (!shortcut_info
.extension_id
.empty()) {
908 shortcut_filename
= GetExtensionShortcutFilename(
909 shortcut_info
.profile_path
, shortcut_info
.extension_id
);
910 // For extensions we do not want duplicate shortcuts. So, delete any that
911 // already exist and replace them.
912 if (creation_locations
.on_desktop
)
913 DeleteShortcutOnDesktop(shortcut_filename
);
914 // The 'applications_menu_location' and 'hidden' locations are actually the
915 // same place ('applications').
916 if (creation_locations
.applications_menu_location
!=
917 web_app::APP_MENU_LOCATION_NONE
||
918 creation_locations
.hidden
)
919 DeleteShortcutInApplicationsMenu(shortcut_filename
, base::FilePath());
921 shortcut_filename
= GetWebShortcutFilename(shortcut_info
.url
);
923 if (shortcut_filename
.empty())
926 std::string icon_name
=
927 CreateShortcutIcon(shortcut_info
.favicon
, shortcut_filename
);
929 std::string app_name
=
930 web_app::GenerateApplicationNameFromInfo(shortcut_info
);
934 base::FilePath chrome_exe_path
= GetChromeExePath();
935 if (chrome_exe_path
.empty()) {
936 LOG(WARNING
) << "Could not get executable path.";
940 if (creation_locations
.on_desktop
) {
941 std::string contents
= GetDesktopFileContents(
945 shortcut_info
.extension_id
,
948 shortcut_info
.profile_path
,
951 success
= CreateShortcutOnDesktop(shortcut_filename
, contents
);
954 if (creation_locations
.applications_menu_location
!=
955 web_app::APP_MENU_LOCATION_NONE
||
956 creation_locations
.hidden
) {
957 base::FilePath directory_filename
;
958 std::string directory_contents
;
959 switch (creation_locations
.applications_menu_location
) {
960 case web_app::APP_MENU_LOCATION_NONE
:
961 case web_app::APP_MENU_LOCATION_ROOT
:
963 case web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS
:
964 directory_filename
= base::FilePath(kDirectoryFilename
);
965 directory_contents
= GetDirectoryFileContents(
966 ShellIntegration::GetAppShortcutsSubdirName(), "");
972 // Set NoDisplay=true if hidden but not in the applications menu. This will
973 // hide the application from user-facing menus.
974 std::string contents
= GetDesktopFileContents(
978 shortcut_info
.extension_id
,
981 shortcut_info
.profile_path
,
983 creation_locations
.applications_menu_location
==
984 web_app::APP_MENU_LOCATION_NONE
);
985 success
= CreateShortcutInApplicationsMenu(
986 shortcut_filename
, contents
, directory_filename
, directory_contents
) &&
993 bool CreateAppListDesktopShortcut(
994 const std::string
& wm_class
,
995 const std::string
& title
) {
996 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
998 base::FilePath
desktop_name(kAppListDesktopName
);
999 base::FilePath shortcut_filename
= desktop_name
.AddExtension("desktop");
1001 // We do not want duplicate shortcuts. Delete any that already exist and
1003 DeleteShortcutInApplicationsMenu(shortcut_filename
, base::FilePath());
1005 base::FilePath chrome_exe_path
= GetChromeExePath();
1006 if (chrome_exe_path
.empty()) {
1007 LOG(WARNING
) << "Could not get executable path.";
1011 gfx::ImageFamily icon_images
;
1012 ResourceBundle
& resource_bundle
= ResourceBundle::GetSharedInstance();
1013 icon_images
.Add(*resource_bundle
.GetImageSkiaNamed(IDR_APP_LIST_16
));
1014 icon_images
.Add(*resource_bundle
.GetImageSkiaNamed(IDR_APP_LIST_32
));
1015 icon_images
.Add(*resource_bundle
.GetImageSkiaNamed(IDR_APP_LIST_48
));
1016 icon_images
.Add(*resource_bundle
.GetImageSkiaNamed(IDR_APP_LIST_256
));
1017 std::string icon_name
= CreateShortcutIcon(icon_images
, desktop_name
);
1019 CommandLine
command_line(chrome_exe_path
);
1020 command_line
.AppendSwitch(switches::kShowAppList
);
1021 std::string contents
=
1022 GetDesktopFileContentsForCommand(command_line
,
1025 base::UTF8ToUTF16(title
),
1029 return CreateShortcutInApplicationsMenu(
1030 shortcut_filename
, contents
, base::FilePath(), "");
1033 void DeleteDesktopShortcuts(const base::FilePath
& profile_path
,
1034 const std::string
& extension_id
) {
1035 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1037 base::FilePath shortcut_filename
= GetExtensionShortcutFilename(
1038 profile_path
, extension_id
);
1039 DCHECK(!shortcut_filename
.empty());
1041 DeleteShortcutOnDesktop(shortcut_filename
);
1042 // Delete shortcuts from |kDirectoryFilename|.
1043 // Note that it is possible that shortcuts were not created in the Chrome Apps
1044 // directory. It doesn't matter: this will still delete the shortcut even if
1045 // it isn't in the directory.
1046 DeleteShortcutInApplicationsMenu(shortcut_filename
,
1047 base::FilePath(kDirectoryFilename
));
1050 void DeleteAllDesktopShortcuts(const base::FilePath
& profile_path
) {
1051 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1053 scoped_ptr
<base::Environment
> env(base::Environment::Create());
1055 // Delete shortcuts from Desktop.
1056 base::FilePath desktop_path
;
1057 if (PathService::Get(base::DIR_USER_DESKTOP
, &desktop_path
)) {
1058 std::vector
<base::FilePath
> shortcut_filenames_desktop
=
1059 GetExistingProfileShortcutFilenames(profile_path
, desktop_path
);
1060 for (std::vector
<base::FilePath
>::const_iterator it
=
1061 shortcut_filenames_desktop
.begin();
1062 it
!= shortcut_filenames_desktop
.end(); ++it
) {
1063 DeleteShortcutOnDesktop(*it
);
1067 // Delete shortcuts from |kDirectoryFilename|.
1068 base::FilePath applications_menu
;
1069 if (GetDataWriteLocation(env
.get(), &applications_menu
)) {
1070 applications_menu
= applications_menu
.AppendASCII("applications");
1071 std::vector
<base::FilePath
> shortcut_filenames_app_menu
=
1072 GetExistingProfileShortcutFilenames(profile_path
, applications_menu
);
1073 for (std::vector
<base::FilePath
>::const_iterator it
=
1074 shortcut_filenames_app_menu
.begin();
1075 it
!= shortcut_filenames_app_menu
.end(); ++it
) {
1076 DeleteShortcutInApplicationsMenu(*it
,
1077 base::FilePath(kDirectoryFilename
));
1082 } // namespace shell_integration_linux