shell_integration_linux.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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. 4 5#include "chrome/browser/shell_integration_linux.h" 6 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> 13 14#include <string> 15#include <vector> 16 17#include "base/base_paths.h" 18#include "base/command_line.h" 19#include "base/eintr_wrapper.h" 20#include "base/environment.h" 21#include "base/file_path.h" 22#include "base/file_util.h" 23#include "base/i18n/file_util_icu.h" 24#include "base/message_loop.h" 25#include "base/path_service.h" 26#include "base/process_util.h" 27#include "base/scoped_temp_dir.h" 28#include "base/string_number_conversions.h" 29#include "base/string_tokenizer.h" 30#include "base/threading/thread.h" 31#include "base/utf_string_conversions.h" 32#include "build/build_config.h" 33#include "chrome/browser/web_applications/web_app.h" 34#include "chrome/common/chrome_constants.h" 35#include "content/public/browser/browser_thread.h" 36#include "googleurl/src/gurl.h" 37#include "ui/gfx/codec/png_codec.h" 38 39using content::BrowserThread; 40 41namespace { 42 43// Helper to launch xdg scripts. We don't want them to ask any questions on the 44// terminal etc. The function returns true if the utility launches and exits 45// cleanly, in which case |exit_code| returns the utility's exit code. 46bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) { 47 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created 48 // files on top of originals after making changes to them. In the event that 49 // the original files are owned by another user (e.g. root, which can happen 50 // if they are updated within sudo), mv will prompt the user to confirm if 51 // standard input is a terminal (otherwise it just does it). So make sure it's 52 // not, to avoid locking everything up waiting for mv. 53 *exit_code = EXIT_FAILURE; 54 int devnull = open("/dev/null", O_RDONLY); 55 if (devnull < 0) 56 return false; 57 base::FileHandleMappingVector no_stdin; 58 no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO)); 59 60 base::ProcessHandle handle; 61 base::LaunchOptions options; 62 options.fds_to_remap = &no_stdin; 63 if (!base::LaunchProcess(argv, options, &handle)) { 64 close(devnull); 65 return false; 66 } 67 close(devnull); 68 69 return base::WaitForExitCode(handle, exit_code); 70} 71 72std::string CreateShortcutIcon( 73 const ShellIntegration::ShortcutInfo& shortcut_info, 74 const FilePath& shortcut_filename) { 75 if (shortcut_info.favicon.IsEmpty()) 76 return std::string(); 77 78 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. 79 ScopedTempDir temp_dir; 80 if (!temp_dir.CreateUniqueTempDir()) 81 return std::string(); 82 83 FilePath temp_file_path = temp_dir.path().Append( 84 shortcut_filename.ReplaceExtension("png")); 85 86 std::vector<unsigned char> png_data; 87 const SkBitmap* bitmap = shortcut_info.favicon.ToSkBitmap(); 88 gfx::PNGCodec::EncodeBGRASkBitmap(*bitmap, false, &png_data); 89 int bytes_written = file_util::WriteFile(temp_file_path, 90 reinterpret_cast<char*>(png_data.data()), png_data.size()); 91 92 if (bytes_written != static_cast<int>(png_data.size())) 93 return std::string(); 94 95 std::vector<std::string> argv; 96 argv.push_back("xdg-icon-resource"); 97 argv.push_back("install"); 98 99 // Always install in user mode, even if someone runs the browser as root 100 // (people do that). 101 argv.push_back("--mode"); 102 argv.push_back("user"); 103 104 argv.push_back("--size"); 105 argv.push_back(base::IntToString(bitmap->width())); 106 107 argv.push_back(temp_file_path.value()); 108 std::string icon_name = temp_file_path.BaseName().RemoveExtension().value(); 109 argv.push_back(icon_name); 110 int exit_code; 111 LaunchXdgUtility(argv, &exit_code); 112 return icon_name; 113} 114 115bool CreateShortcutOnDesktop(const FilePath& shortcut_filename, 116 const std::string& contents) { 117 // Make sure that we will later call openat in a secure way. 118 DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value()); 119 120 FilePath desktop_path; 121 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) 122 return false; 123 124 int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY); 125 if (desktop_fd < 0) 126 return false; 127 128 int fd = openat(desktop_fd, shortcut_filename.value().c_str(), 129 O_CREAT | O_EXCL | O_WRONLY, 130 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 131 if (fd < 0) { 132 if (HANDLE_EINTR(close(desktop_fd)) < 0) 133 PLOG(ERROR) << "close"; 134 return false; 135 } 136 137 ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(), 138 contents.length()); 139 if (HANDLE_EINTR(close(fd)) < 0) 140 PLOG(ERROR) << "close"; 141 142 if (bytes_written != static_cast<ssize_t>(contents.length())) { 143 // Delete the file. No shortuct is better than corrupted one. Use unlinkat 144 // to make sure we're deleting the file in the directory we think we are. 145 // Even if an attacker manager to put something other at 146 // |shortcut_filename| we'll just undo his action. 147 unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0); 148 } 149 150 if (HANDLE_EINTR(close(desktop_fd)) < 0) 151 PLOG(ERROR) << "close"; 152 153 return true; 154} 155 156void DeleteShortcutOnDesktop(const FilePath& shortcut_filename) { 157 FilePath desktop_path; 158 if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) 159 file_util::Delete(desktop_path.Append(shortcut_filename), false); 160} 161 162bool CreateShortcutInApplicationsMenu(const FilePath& shortcut_filename, 163 const std::string& contents) { 164 ScopedTempDir temp_dir; 165 if (!temp_dir.CreateUniqueTempDir()) 166 return false; 167 168 FilePath temp_file_path = temp_dir.path().Append(shortcut_filename); 169 170 int bytes_written = file_util::WriteFile(temp_file_path, contents.data(), 171 contents.length()); 172 173 if (bytes_written != static_cast<int>(contents.length())) 174 return false; 175 176 std::vector<std::string> argv; 177 argv.push_back("xdg-desktop-menu"); 178 argv.push_back("install"); 179 180 // Always install in user mode, even if someone runs the browser as root 181 // (people do that). 182 argv.push_back("--mode"); 183 argv.push_back("user"); 184 185 argv.push_back(temp_file_path.value()); 186 int exit_code; 187 LaunchXdgUtility(argv, &exit_code); 188 return exit_code == 0; 189} 190 191void DeleteShortcutInApplicationsMenu(const FilePath& shortcut_filename) { 192 std::vector<std::string> argv; 193 argv.push_back("xdg-desktop-menu"); 194 argv.push_back("uninstall"); 195 196 // Uninstall in user mode, to match the install. 197 argv.push_back("--mode"); 198 argv.push_back("user"); 199 200 // The file does not need to exist anywhere - xdg-desktop-menu will uninstall 201 // items from the menu with a matching name. 202 argv.push_back(shortcut_filename.value()); 203 int exit_code; 204 LaunchXdgUtility(argv, &exit_code); 205} 206 207// Quote a string such that it appears as one verbatim argument for the Exec 208// key in a desktop file. 209std::string QuoteArgForDesktopFileExec(const std::string& arg) { 210 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html 211 212 // Quoting is only necessary if the argument has a reserved character. 213 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos) 214 return arg; // No quoting necessary. 215 216 std::string quoted = "\""; 217 for (size_t i = 0; i < arg.size(); ++i) { 218 // Note that the set of backslashed characters is smaller than the 219 // set of reserved characters. 220 switch (arg[i]) { 221 case '"': 222 case '`': 223 case '$': 224 case '\\': 225 quoted += '\\'; 226 break; 227 } 228 quoted += arg[i]; 229 } 230 quoted += '"'; 231 232 return quoted; 233} 234 235// Remove keys from the [Desktop Entry] that would be wrong if copied verbatim 236// into the new .desktop file. 237const char* kDesktopKeysToDelete[] = { 238 "GenericName", 239 "Comment", 240 "MimeType", 241 "X-Ayatana-Desktop-Shortcuts", 242 "StartupWMClass", 243 NULL 244}; 245 246const char kDesktopEntry[] = "Desktop Entry"; 247 248const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open"; 249 250const char kXdgSettings[] = "xdg-settings"; 251const char kXdgSettingsDefaultBrowser[] = "default-web-browser"; 252const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler"; 253 254} // namespace 255 256namespace { 257 258// Utility function to get the path to the version of a script shipped with 259// Chrome. |script| gives the name of the script. |chrome_version| returns the 260// path to the Chrome version of the script, and the return value of the 261// function is true if the function is successful and the Chrome version is 262// not the script found on the PATH. 263bool GetChromeVersionOfScript(const std::string& script, 264 std::string* chrome_version) { 265 // Get the path to the Chrome version. 266 FilePath chrome_dir; 267 if (!PathService::Get(base::DIR_EXE, &chrome_dir)) 268 return false; 269 270 FilePath chrome_version_path = chrome_dir.Append(script); 271 *chrome_version = chrome_version_path.value(); 272 273 // Check if this is different to the one on path. 274 std::vector<std::string> argv; 275 argv.push_back("which"); 276 argv.push_back(script); 277 std::string path_version; 278 if (base::GetAppOutput(CommandLine(argv), &path_version)) { 279 // Remove trailing newline 280 path_version.erase(path_version.length() - 1, 1); 281 FilePath path_version_path(path_version); 282 return (chrome_version_path != path_version_path); 283 } 284 return false; 285} 286 287// Value returned by xdg-settings if it can't understand our request. 288const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1; 289 290// We delegate the difficulty of setting the default browser and default url 291// scheme handler in Linux desktop environments to an xdg utility, xdg-settings. 292 293// When calling this script we first try to use the script on PATH. If that 294// fails we then try to use the script that we have included. This gives 295// scripts on the system priority over ours, as distribution vendors may have 296// tweaked the script, but still allows our copy to be used if the script on the 297// system fails, as the system copy may be missing capabilities of the Chrome 298// copy. 299 300// If |protocol| is empty this function sets Chrome as the default browser, 301// otherwise it sets Chrome as the default handler application for |protocol|. 302bool SetDefaultWebClient(const std::string& protocol) { 303#if defined(OS_CHROMEOS) 304 return true; 305#else 306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 307 308 scoped_ptr<base::Environment> env(base::Environment::Create()); 309 310 std::vector<std::string> argv; 311 argv.push_back(kXdgSettings); 312 argv.push_back("set"); 313 if (protocol.empty()) { 314 argv.push_back(kXdgSettingsDefaultBrowser); 315 } else { 316 argv.push_back(kXdgSettingsDefaultSchemeHandler); 317 argv.push_back(protocol); 318 } 319 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get())); 320 321 int exit_code; 322 bool ran_ok = LaunchXdgUtility(argv, &exit_code); 323 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { 324 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { 325 ran_ok = LaunchXdgUtility(argv, &exit_code); 326 } 327 } 328 329 return ran_ok && exit_code == EXIT_SUCCESS; 330#endif 331} 332 333// If |protocol| is empty this function checks if Chrome is the default browser, 334// otherwise it checks if Chrome is the default handler application for 335// |protocol|. 336ShellIntegration::DefaultWebClientState GetIsDefaultWebClient( 337 const std::string& protocol) { 338#if defined(OS_CHROMEOS) 339 return ShellIntegration::IS_DEFAULT_WEB_CLIENT; 340#else 341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 342 343 scoped_ptr<base::Environment> env(base::Environment::Create()); 344 345 std::vector<std::string> argv; 346 argv.push_back(kXdgSettings); 347 argv.push_back("check"); 348 if (protocol.empty()) { 349 argv.push_back(kXdgSettingsDefaultBrowser); 350 } else { 351 argv.push_back(kXdgSettingsDefaultSchemeHandler); 352 argv.push_back(protocol); 353 } 354 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get())); 355 356 std::string reply; 357 int success_code; 358 bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply, 359 &success_code); 360 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { 361 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) { 362 ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply, 363 &success_code); 364 } 365 } 366 367 if (!ran_ok || success_code != EXIT_SUCCESS) { 368 // xdg-settings failed: we can't determine or set the default browser. 369 return ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT; 370 } 371 372 // Allow any reply that starts with "yes". 373 return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT_WEB_CLIENT : 374 ShellIntegration::NOT_DEFAULT_WEB_CLIENT; 375#endif 376} 377 378} // namespace 379 380// static 381ShellIntegration::DefaultWebClientSetPermission 382 ShellIntegration::CanSetAsDefaultBrowser() { 383 return SET_DEFAULT_UNATTENDED; 384} 385 386// static 387bool ShellIntegration::SetAsDefaultBrowser() { 388 return SetDefaultWebClient(""); 389} 390 391// static 392bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) { 393 return SetDefaultWebClient(protocol); 394} 395 396// static 397ShellIntegration::DefaultWebClientState ShellIntegration::IsDefaultBrowser() { 398 return GetIsDefaultWebClient(""); 399} 400 401// static 402ShellIntegration::DefaultWebClientState 403ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) { 404 return GetIsDefaultWebClient(protocol); 405} 406 407// static 408bool ShellIntegration::IsFirefoxDefaultBrowser() { 409 std::vector<std::string> argv; 410 argv.push_back(kXdgSettings); 411 argv.push_back("get"); 412 argv.push_back(kXdgSettingsDefaultBrowser); 413 414 std::string browser; 415 // We don't care about the return value here. 416 base::GetAppOutput(CommandLine(argv), &browser); 417 return browser.find("irefox") != std::string::npos; 418} 419 420namespace ShellIntegrationLinux { 421 422std::string GetDesktopName(base::Environment* env) { 423#if defined(GOOGLE_CHROME_BUILD) 424 return "google-chrome.desktop"; 425#else // CHROMIUM_BUILD 426 // Allow $CHROME_DESKTOP to override the built-in value, so that development 427 // versions can set themselves as the default without interfering with 428 // non-official, packaged versions using the built-in value. 429 std::string name; 430 if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty()) 431 return name; 432 return "chromium-browser.desktop"; 433#endif 434} 435 436bool GetDesktopShortcutTemplate(base::Environment* env, 437 std::string* output) { 438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 439 440 std::vector<FilePath> search_paths; 441 442 std::string xdg_data_home; 443 if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) && 444 !xdg_data_home.empty()) { 445 search_paths.push_back(FilePath(xdg_data_home)); 446 } 447 448 std::string xdg_data_dirs; 449 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && 450 !xdg_data_dirs.empty()) { 451 StringTokenizer tokenizer(xdg_data_dirs, ":"); 452 while (tokenizer.GetNext()) { 453 FilePath data_dir(tokenizer.token()); 454 search_paths.push_back(data_dir); 455 search_paths.push_back(data_dir.Append("applications")); 456 } 457 } 458 459 // Add some fallback paths for systems which don't have XDG_DATA_DIRS or have 460 // it incomplete. 461 search_paths.push_back(FilePath("/usr/share/applications")); 462 search_paths.push_back(FilePath("/usr/local/share/applications")); 463 464 std::string template_filename(GetDesktopName(env)); 465 for (std::vector<FilePath>::const_iterator i = search_paths.begin(); 466 i != search_paths.end(); ++i) { 467 FilePath path = i->Append(template_filename); 468 VLOG(1) << "Looking for desktop file template in " << path.value(); 469 if (file_util::PathExists(path)) { 470 VLOG(1) << "Found desktop file template at " << path.value(); 471 return file_util::ReadFileToString(path, output); 472 } 473 } 474 475 LOG(ERROR) << "Could not find desktop file template."; 476 return false; 477} 478 479FilePath GetWebShortcutFilename(const GURL& url) { 480 // Use a prefix, because xdg-desktop-menu requires it. 481 std::string filename = 482 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec(); 483 file_util::ReplaceIllegalCharactersInPath(&filename, '_'); 484 485 FilePath desktop_path; 486 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) 487 return FilePath(); 488 489 FilePath filepath = desktop_path.Append(filename); 490 FilePath alternative_filepath(filepath.value() + ".desktop"); 491 for (size_t i = 1; i < 100; ++i) { 492 if (file_util::PathExists(FilePath(alternative_filepath))) { 493 alternative_filepath = FilePath( 494 filepath.value() + "_" + base::IntToString(i) + ".desktop"); 495 } else { 496 return FilePath(alternative_filepath).BaseName(); 497 } 498 } 499 500 return FilePath(); 501} 502 503FilePath GetExtensionShortcutFilename(const FilePath& profile_path, 504 const std::string& extension_id) { 505 DCHECK(!extension_id.empty()); 506 507 // Use a prefix, because xdg-desktop-menu requires it. 508 std::string filename(chrome::kBrowserProcessExecutableName); 509 filename.append("-") 510 .append(extension_id) 511 .append("-") 512 .append(profile_path.BaseName().value()); 513 file_util::ReplaceIllegalCharactersInPath(&filename, '_'); 514 return FilePath(filename.append(".desktop")); 515} 516 517std::string GetDesktopFileContents( 518 const std::string& template_contents, 519 const std::string& app_name, 520 const GURL& url, 521 const std::string& extension_id, 522 const FilePath& extension_path, 523 const string16& title, 524 const std::string& icon_name, 525 const FilePath& profile_path) { 526 if (template_contents.empty()) 527 return std::string(kXdgOpenShebang) + "\n"; 528 529 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ 530 // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html 531 GKeyFile* key_file = g_key_file_new(); 532 GError* err = NULL; 533 // Loading the data will strip translations and comments from the desktop 534 // file (which we want to do!) 535 if (!g_key_file_load_from_data( 536 key_file, 537 template_contents.c_str(), 538 template_contents.size(), 539 G_KEY_FILE_NONE, 540 &err)) { 541 NOTREACHED() << "Unable to read desktop file template:" << err->message; 542 g_error_free(err); 543 return std::string(kXdgOpenShebang) + "\n"; 544 } 545 546 // Remove all sections except for the Desktop Entry 547 gsize length = 0; 548 gchar** groups = g_key_file_get_groups(key_file, &length); 549 for (gsize i = 0; i < length; ++i) { 550 if (strcmp(groups[i], kDesktopEntry) != 0) { 551 g_key_file_remove_group(key_file, groups[i], NULL); 552 } 553 } 554 g_strfreev(groups); 555 556 // Remove keys that we won't need. 557 for (const char** current_key = kDesktopKeysToDelete; *current_key; 558 ++current_key) { 559 g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL); 560 } 561 562 // Set the "Name" key. 563 std::string final_title = UTF16ToUTF8(title); 564 // Make sure no endline characters can slip in and possibly introduce 565 // additional lines (like Exec, which makes it a security risk). Also 566 // use the URL as a default when the title is empty. 567 if (final_title.empty() || 568 final_title.find("\n") != std::string::npos || 569 final_title.find("\r") != std::string::npos) { 570 final_title = url.spec(); 571 } 572 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); 573 574 // Set the "Exec" key. 575 char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec", 576 NULL); 577 if (exec_c_string) { 578 std::string exec_string(exec_c_string); 579 g_free(exec_c_string); 580 StringTokenizer exec_tokenizer(exec_string, " "); 581 582 std::string final_path; 583 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { 584 if (!final_path.empty()) 585 final_path += " "; 586 final_path += exec_tokenizer.token(); 587 } 588 CommandLine cmd_line(CommandLine::NO_PROGRAM); 589 cmd_line = ShellIntegration::CommandLineArgsForLauncher( 590 url, extension_id, profile_path); 591 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); 592 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); 593 i != switch_map.end(); ++i) { 594 if (i->second.empty()) { 595 final_path += " --" + i->first; 596 } else { 597 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + 598 "=" + i->second); 599 } 600 } 601 602 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); 603 } 604 605 // Set the "Icon" key. 606 if (!icon_name.empty()) 607 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); 608 609#if defined(TOOLKIT_GTK) 610 std::string wmclass = web_app::GetWMClassFromAppName(app_name); 611 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", 612 wmclass.c_str()); 613#endif 614 615 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its 616 // launchers with an xdg-open shebang. Follow that convention. 617 std::string output_buffer = kXdgOpenShebang; 618 length = 0; 619 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); 620 if (data_dump) { 621 output_buffer += data_dump; 622 g_free(data_dump); 623 } 624 625 g_key_file_free(key_file); 626 return output_buffer; 627} 628 629bool CreateDesktopShortcut( 630 const ShellIntegration::ShortcutInfo& shortcut_info, 631 const std::string& shortcut_template) { 632 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 633 634 FilePath shortcut_filename; 635 if (!shortcut_info.extension_id.empty()) { 636 shortcut_filename = GetExtensionShortcutFilename( 637 shortcut_info.profile_path, shortcut_info.extension_id); 638 // For extensions we do not want duplicate shortcuts. So, delete any that 639 // already exist and replace them. 640 if (shortcut_info.create_on_desktop) 641 DeleteShortcutOnDesktop(shortcut_filename); 642 if (shortcut_info.create_in_applications_menu) 643 DeleteShortcutInApplicationsMenu(shortcut_filename); 644 } else { 645 shortcut_filename = GetWebShortcutFilename(shortcut_info.url); 646 } 647 if (shortcut_filename.empty()) 648 return false; 649 650 std::string icon_name = CreateShortcutIcon(shortcut_info, shortcut_filename); 651 652 std::string app_name = 653 web_app::GenerateApplicationNameFromInfo(shortcut_info); 654 std::string contents = ShellIntegrationLinux::GetDesktopFileContents( 655 shortcut_template, 656 app_name, 657 shortcut_info.url, 658 shortcut_info.extension_id, 659 shortcut_info.extension_path, 660 shortcut_info.title, 661 icon_name, 662 shortcut_info.profile_path); 663 664 bool success = true; 665 666 if (shortcut_info.create_on_desktop) 667 success = CreateShortcutOnDesktop(shortcut_filename, contents); 668 669 if (shortcut_info.create_in_applications_menu) 670 success = CreateShortcutInApplicationsMenu(shortcut_filename, contents) && 671 success; 672 673 return success; 674} 675 676void DeleteDesktopShortcuts(const FilePath& profile_path, 677 const std::string& extension_id) { 678 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 679 680 FilePath shortcut_filename = GetExtensionShortcutFilename( 681 profile_path, extension_id); 682 DCHECK(!shortcut_filename.empty()); 683 684 DeleteShortcutOnDesktop(shortcut_filename); 685 DeleteShortcutInApplicationsMenu(shortcut_filename); 686} 687 688} // namespace ShellIntegrationLinux 689