blob: 8ef3320720b234df7c5ff482533cbe868ef2e1b7 [file] [log] [blame]
[email protected]a82af392012-02-24 04:40:201// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ef525cc2009-07-10 17:08:162// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]8806d3b2012-04-13 06:46:345#include "chrome/browser/shell_integration_linux.h"
[email protected]ef525cc2009-07-10 17:08:166
[email protected]6584f0b12009-07-20 23:38:147#include <fcntl.h>
[email protected]0a96c3f2011-05-11 22:10:208#include <glib.h>
[email protected]ef525cc2009-07-10 17:08:169#include <stdlib.h>
[email protected]6584f0b12009-07-20 23:38:1410#include <sys/stat.h>
11#include <sys/types.h>
12#include <unistd.h>
[email protected]ef525cc2009-07-10 17:08:1613
[email protected]42896802009-08-28 23:39:4414#include <string>
[email protected]ef525cc2009-07-10 17:08:1615#include <vector>
16
[email protected]6a83c4242011-07-07 06:06:4117#include "base/base_paths.h"
[email protected]42896802009-08-28 23:39:4418#include "base/command_line.h"
[email protected]76b90d312010-08-03 03:00:5019#include "base/environment.h"
[email protected]b96aa932009-08-12 21:34:4920#include "base/file_util.h"
[email protected]57999812013-02-24 05:40:5221#include "base/files/file_path.h"
[email protected]ea1a3f62012-11-16 20:34:2322#include "base/files/scoped_temp_dir.h"
[email protected]d0767cb542009-10-08 17:38:3023#include "base/i18n/file_util_icu.h"
[email protected]08a139d2013-04-11 03:32:5424#include "base/memory/ref_counted_memory.h"
25#include "base/memory/scoped_ptr.h"
[email protected]b96aa932009-08-12 21:34:4926#include "base/message_loop.h"
27#include "base/path_service.h"
[email protected]2025d002012-11-14 20:54:3528#include "base/posix/eintr_wrapper.h"
[email protected]ef525cc2009-07-10 17:08:1629#include "base/process_util.h"
[email protected]3ea1b182013-02-08 22:38:4130#include "base/strings/string_number_conversions.h"
[email protected]f4ebe772013-02-02 00:21:3931#include "base/strings/string_tokenizer.h"
[email protected]e309f312013-06-07 21:50:0832#include "base/strings/utf_string_conversions.h"
[email protected]34b99632011-01-01 01:01:0633#include "base/threading/thread.h"
[email protected]89886652012-12-11 18:09:0734#include "base/threading/thread_restrictions.h"
[email protected]b03f53cd2011-04-06 18:18:4335#include "build/build_config.h"
[email protected]a0b60cfd2011-04-06 18:02:4136#include "chrome/browser/web_applications/web_app.h"
[email protected]42896802009-08-28 23:39:4437#include "chrome/common/chrome_constants.h"
[email protected]c38831a12011-10-28 12:44:4938#include "content/public/browser/browser_thread.h"
[email protected]08a139d2013-04-11 03:32:5439#include "ui/gfx/image/image_family.h"
[email protected]761fa4702013-07-02 15:25:1540#include "url/gurl.h"
[email protected]ef525cc2009-07-10 17:08:1641
[email protected]631bb742011-11-02 11:29:3942using content::BrowserThread;
43
[email protected]b96aa932009-08-12 21:34:4944namespace {
45
[email protected]42896802009-08-28 23:39:4446// Helper to launch xdg scripts. We don't want them to ask any questions on the
[email protected]6a83c4242011-07-07 06:06:4147// terminal etc. The function returns true if the utility launches and exits
48// cleanly, in which case |exit_code| returns the utility's exit code.
49bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
[email protected]42896802009-08-28 23:39:4450 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
51 // files on top of originals after making changes to them. In the event that
52 // the original files are owned by another user (e.g. root, which can happen
53 // if they are updated within sudo), mv will prompt the user to confirm if
54 // standard input is a terminal (otherwise it just does it). So make sure it's
55 // not, to avoid locking everything up waiting for mv.
[email protected]6a83c4242011-07-07 06:06:4156 *exit_code = EXIT_FAILURE;
[email protected]42896802009-08-28 23:39:4457 int devnull = open("/dev/null", O_RDONLY);
58 if (devnull < 0)
59 return false;
[email protected]a82af392012-02-24 04:40:2060 base::FileHandleMappingVector no_stdin;
[email protected]42896802009-08-28 23:39:4461 no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
62
63 base::ProcessHandle handle;
[email protected]b5ce736e2011-07-13 18:51:4764 base::LaunchOptions options;
[email protected]b5ce736e2011-07-13 18:51:4765 options.fds_to_remap = &no_stdin;
[email protected]e5992182011-07-15 16:47:0266 if (!base::LaunchProcess(argv, options, &handle)) {
[email protected]42896802009-08-28 23:39:4467 close(devnull);
68 return false;
69 }
70 close(devnull);
71
[email protected]6a83c4242011-07-07 06:06:4172 return base::WaitForExitCode(handle, exit_code);
[email protected]42896802009-08-28 23:39:4473}
74
[email protected]620942e2010-02-16 10:12:1275std::string CreateShortcutIcon(
76 const ShellIntegration::ShortcutInfo& shortcut_info,
[email protected]650b2d52013-02-10 03:41:4577 const base::FilePath& shortcut_filename) {
[email protected]08a139d2013-04-11 03:32:5478 if (shortcut_info.favicon.empty())
[email protected]620942e2010-02-16 10:12:1279 return std::string();
80
81 // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
[email protected]ea1a3f62012-11-16 20:34:2382 base::ScopedTempDir temp_dir;
[email protected]620942e2010-02-16 10:12:1283 if (!temp_dir.CreateUniqueTempDir())
84 return std::string();
85
[email protected]650b2d52013-02-10 03:41:4586 base::FilePath temp_file_path = temp_dir.path().Append(
[email protected]620942e2010-02-16 10:12:1287 shortcut_filename.ReplaceExtension("png"));
[email protected]620942e2010-02-16 10:12:1288 std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
[email protected]c67d0342d2013-02-12 06:34:3789
[email protected]08a139d2013-04-11 03:32:5490 for (gfx::ImageFamily::const_iterator it = shortcut_info.favicon.begin();
91 it != shortcut_info.favicon.end(); ++it) {
92 int width = it->Width();
93 scoped_refptr<base::RefCountedMemory> png_data = it->As1xPNGBytes();
94 if (png_data->size() == 0) {
[email protected]c67d0342d2013-02-12 06:34:3795 // If the bitmap could not be encoded to PNG format, skip it.
96 LOG(WARNING) << "Could not encode icon " << icon_name << ".png at size "
[email protected]08a139d2013-04-11 03:32:5497 << width << ".";
[email protected]c67d0342d2013-02-12 06:34:3798 continue;
99 }
100 int bytes_written = file_util::WriteFile(temp_file_path,
[email protected]08a139d2013-04-11 03:32:54101 reinterpret_cast<const char*>(png_data->front()), png_data->size());
[email protected]c67d0342d2013-02-12 06:34:37102
[email protected]08a139d2013-04-11 03:32:54103 if (bytes_written != static_cast<int>(png_data->size()))
[email protected]c67d0342d2013-02-12 06:34:37104 return std::string();
105
106 std::vector<std::string> argv;
107 argv.push_back("xdg-icon-resource");
108 argv.push_back("install");
109
110 // Always install in user mode, even if someone runs the browser as root
111 // (people do that).
112 argv.push_back("--mode");
113 argv.push_back("user");
114
115 argv.push_back("--size");
[email protected]08a139d2013-04-11 03:32:54116 argv.push_back(base::IntToString(width));
[email protected]c67d0342d2013-02-12 06:34:37117
118 argv.push_back(temp_file_path.value());
119 argv.push_back(icon_name);
120 int exit_code;
121 if (!LaunchXdgUtility(argv, &exit_code) || exit_code) {
122 LOG(WARNING) << "Could not install icon " << icon_name << ".png at size "
[email protected]08a139d2013-04-11 03:32:54123 << width << ".";
[email protected]c67d0342d2013-02-12 06:34:37124 }
125 }
[email protected]620942e2010-02-16 10:12:12126 return icon_name;
127}
128
[email protected]650b2d52013-02-10 03:41:45129bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename,
[email protected]620942e2010-02-16 10:12:12130 const std::string& contents) {
[email protected]620942e2010-02-16 10:12:12131 // Make sure that we will later call openat in a secure way.
132 DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
133
[email protected]650b2d52013-02-10 03:41:45134 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52135 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]8806d3b2012-04-13 06:46:34136 return false;
[email protected]620942e2010-02-16 10:12:12137
138 int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
139 if (desktop_fd < 0)
[email protected]8806d3b2012-04-13 06:46:34140 return false;
[email protected]620942e2010-02-16 10:12:12141
142 int fd = openat(desktop_fd, shortcut_filename.value().c_str(),
143 O_CREAT | O_EXCL | O_WRONLY,
144 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
145 if (fd < 0) {
[email protected]19cb9292010-04-16 23:00:15146 if (HANDLE_EINTR(close(desktop_fd)) < 0)
147 PLOG(ERROR) << "close";
[email protected]8806d3b2012-04-13 06:46:34148 return false;
[email protected]620942e2010-02-16 10:12:12149 }
150
151 ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(),
152 contents.length());
[email protected]19cb9292010-04-16 23:00:15153 if (HANDLE_EINTR(close(fd)) < 0)
154 PLOG(ERROR) << "close";
[email protected]620942e2010-02-16 10:12:12155
156 if (bytes_written != static_cast<ssize_t>(contents.length())) {
157 // Delete the file. No shortuct is better than corrupted one. Use unlinkat
158 // to make sure we're deleting the file in the directory we think we are.
159 // Even if an attacker manager to put something other at
160 // |shortcut_filename| we'll just undo his action.
161 unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0);
162 }
163
[email protected]19cb9292010-04-16 23:00:15164 if (HANDLE_EINTR(close(desktop_fd)) < 0)
165 PLOG(ERROR) << "close";
[email protected]8806d3b2012-04-13 06:46:34166
167 return true;
[email protected]620942e2010-02-16 10:12:12168}
169
[email protected]650b2d52013-02-10 03:41:45170void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) {
171 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52172 if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]918efbf2013-07-01 19:41:02173 base::Delete(desktop_path.Append(shortcut_filename), false);
[email protected]0b7df36d2012-07-11 09:50:47174}
175
[email protected]a3c25952013-05-02 13:16:06176// Creates a shortcut with |shortcut_filename| and |contents| in the system
177// applications menu. If |directory_filename| is non-empty, creates a sub-menu
178// with |directory_filename| and |directory_contents|, and stores the shortcut
179// under the sub-menu.
[email protected]650b2d52013-02-10 03:41:45180bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename,
[email protected]a3c25952013-05-02 13:16:06181 const std::string& contents,
182 const base::FilePath& directory_filename,
183 const std::string& directory_contents) {
[email protected]ea1a3f62012-11-16 20:34:23184 base::ScopedTempDir temp_dir;
[email protected]620942e2010-02-16 10:12:12185 if (!temp_dir.CreateUniqueTempDir())
[email protected]8806d3b2012-04-13 06:46:34186 return false;
[email protected]620942e2010-02-16 10:12:12187
[email protected]a3c25952013-05-02 13:16:06188 base::FilePath temp_directory_path;
189 if (!directory_filename.empty()) {
190 temp_directory_path = temp_dir.path().Append(directory_filename);
191
192 int bytes_written = file_util::WriteFile(temp_directory_path,
193 directory_contents.data(),
194 directory_contents.length());
195
196 if (bytes_written != static_cast<int>(directory_contents.length()))
197 return false;
198 }
199
[email protected]650b2d52013-02-10 03:41:45200 base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
[email protected]620942e2010-02-16 10:12:12201
202 int bytes_written = file_util::WriteFile(temp_file_path, contents.data(),
203 contents.length());
204
205 if (bytes_written != static_cast<int>(contents.length()))
[email protected]8806d3b2012-04-13 06:46:34206 return false;
[email protected]620942e2010-02-16 10:12:12207
208 std::vector<std::string> argv;
209 argv.push_back("xdg-desktop-menu");
210 argv.push_back("install");
211
212 // Always install in user mode, even if someone runs the browser as root
213 // (people do that).
214 argv.push_back("--mode");
215 argv.push_back("user");
216
[email protected]a3c25952013-05-02 13:16:06217 // If provided, install the shortcut file inside the given directory.
218 if (!directory_filename.empty())
219 argv.push_back(temp_directory_path.value());
[email protected]620942e2010-02-16 10:12:12220 argv.push_back(temp_file_path.value());
[email protected]6a83c4242011-07-07 06:06:41221 int exit_code;
222 LaunchXdgUtility(argv, &exit_code);
[email protected]8806d3b2012-04-13 06:46:34223 return exit_code == 0;
[email protected]620942e2010-02-16 10:12:12224}
225
[email protected]a3c25952013-05-02 13:16:06226void DeleteShortcutInApplicationsMenu(
227 const base::FilePath& shortcut_filename,
228 const base::FilePath& directory_filename) {
[email protected]0b7df36d2012-07-11 09:50:47229 std::vector<std::string> argv;
230 argv.push_back("xdg-desktop-menu");
231 argv.push_back("uninstall");
232
233 // Uninstall in user mode, to match the install.
234 argv.push_back("--mode");
235 argv.push_back("user");
236
237 // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
238 // items from the menu with a matching name.
[email protected]a3c25952013-05-02 13:16:06239 // If |directory_filename| is supplied, this will also remove the item from
240 // the directory, and remove the directory if it is empty.
241 if (!directory_filename.empty())
242 argv.push_back(directory_filename.value());
[email protected]0b7df36d2012-07-11 09:50:47243 argv.push_back(shortcut_filename.value());
244 int exit_code;
245 LaunchXdgUtility(argv, &exit_code);
246}
247
[email protected]b10392932011-03-08 21:28:14248// Quote a string such that it appears as one verbatim argument for the Exec
249// key in a desktop file.
250std::string QuoteArgForDesktopFileExec(const std::string& arg) {
251 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
252
253 // Quoting is only necessary if the argument has a reserved character.
254 if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
255 return arg; // No quoting necessary.
256
257 std::string quoted = "\"";
258 for (size_t i = 0; i < arg.size(); ++i) {
259 // Note that the set of backslashed characters is smaller than the
260 // set of reserved characters.
261 switch (arg[i]) {
262 case '"':
263 case '`':
264 case '$':
265 case '\\':
266 quoted += '\\';
267 break;
268 }
269 quoted += arg[i];
270 }
271 quoted += '"';
272
273 return quoted;
274}
275
[email protected]4f0806a72011-09-21 03:08:45276const char kDesktopEntry[] = "Desktop Entry";
[email protected]0a96c3f2011-05-11 22:10:20277
[email protected]4f0806a72011-09-21 03:08:45278const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
279
280const char kXdgSettings[] = "xdg-settings";
281const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
282const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
[email protected]b10392932011-03-08 21:28:14283
[email protected]a3c25952013-05-02 13:16:06284const char kDirectoryFilename[] = "chrome-apps.directory";
285
[email protected]620942e2010-02-16 10:12:12286} // namespace
287
[email protected]6a83c4242011-07-07 06:06:41288namespace {
[email protected]620942e2010-02-16 10:12:12289
[email protected]6a83c4242011-07-07 06:06:41290// Utility function to get the path to the version of a script shipped with
291// Chrome. |script| gives the name of the script. |chrome_version| returns the
292// path to the Chrome version of the script, and the return value of the
293// function is true if the function is successful and the Chrome version is
294// not the script found on the PATH.
295bool GetChromeVersionOfScript(const std::string& script,
296 std::string* chrome_version) {
297 // Get the path to the Chrome version.
[email protected]650b2d52013-02-10 03:41:45298 base::FilePath chrome_dir;
[email protected]6a83c4242011-07-07 06:06:41299 if (!PathService::Get(base::DIR_EXE, &chrome_dir))
300 return false;
301
[email protected]650b2d52013-02-10 03:41:45302 base::FilePath chrome_version_path = chrome_dir.Append(script);
[email protected]6a83c4242011-07-07 06:06:41303 *chrome_version = chrome_version_path.value();
304
305 // Check if this is different to the one on path.
306 std::vector<std::string> argv;
307 argv.push_back("which");
308 argv.push_back(script);
309 std::string path_version;
310 if (base::GetAppOutput(CommandLine(argv), &path_version)) {
311 // Remove trailing newline
312 path_version.erase(path_version.length() - 1, 1);
[email protected]650b2d52013-02-10 03:41:45313 base::FilePath path_version_path(path_version);
[email protected]6a83c4242011-07-07 06:06:41314 return (chrome_version_path != path_version_path);
315 }
316 return false;
317}
318
319// Value returned by xdg-settings if it can't understand our request.
320const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
321
322// We delegate the difficulty of setting the default browser and default url
323// scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
324
325// When calling this script we first try to use the script on PATH. If that
326// fails we then try to use the script that we have included. This gives
327// scripts on the system priority over ours, as distribution vendors may have
328// tweaked the script, but still allows our copy to be used if the script on the
329// system fails, as the system copy may be missing capabilities of the Chrome
330// copy.
331
332// If |protocol| is empty this function sets Chrome as the default browser,
333// otherwise it sets Chrome as the default handler application for |protocol|.
334bool SetDefaultWebClient(const std::string& protocol) {
[email protected]cc50544002012-05-01 18:56:47335#if defined(OS_CHROMEOS)
336 return true;
337#else
[email protected]6a83c4242011-07-07 06:06:41338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
339
[email protected]76b90d312010-08-03 03:00:50340 scoped_ptr<base::Environment> env(base::Environment::Create());
[email protected]af71d642010-03-12 10:29:04341
[email protected]620942e2010-02-16 10:12:12342 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45343 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12344 argv.push_back("set");
[email protected]6a83c4242011-07-07 06:06:41345 if (protocol.empty()) {
[email protected]4f0806a72011-09-21 03:08:45346 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]6a83c4242011-07-07 06:06:41347 } else {
[email protected]4f0806a72011-09-21 03:08:45348 argv.push_back(kXdgSettingsDefaultSchemeHandler);
[email protected]6a83c4242011-07-07 06:06:41349 argv.push_back(protocol);
350 }
[email protected]98566d7a2012-04-17 00:28:56351 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
[email protected]6a83c4242011-07-07 06:06:41352
353 int exit_code;
354 bool ran_ok = LaunchXdgUtility(argv, &exit_code);
355 if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
[email protected]4f0806a72011-09-21 03:08:45356 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
[email protected]6a83c4242011-07-07 06:06:41357 ran_ok = LaunchXdgUtility(argv, &exit_code);
358 }
359 }
360
361 return ran_ok && exit_code == EXIT_SUCCESS;
[email protected]cc50544002012-05-01 18:56:47362#endif
[email protected]620942e2010-02-16 10:12:12363}
364
[email protected]6a83c4242011-07-07 06:06:41365// If |protocol| is empty this function checks if Chrome is the default browser,
366// otherwise it checks if Chrome is the default handler application for
367// |protocol|.
368ShellIntegration::DefaultWebClientState GetIsDefaultWebClient(
369 const std::string& protocol) {
[email protected]cc50544002012-05-01 18:56:47370#if defined(OS_CHROMEOS)
[email protected]89886652012-12-11 18:09:07371 return ShellIntegration::IS_DEFAULT;
[email protected]cc50544002012-05-01 18:56:47372#else
[email protected]89886652012-12-11 18:09:07373 base::ThreadRestrictions::AssertIOAllowed();
[email protected]8fcec3c72010-06-03 00:17:22374
[email protected]76b90d312010-08-03 03:00:50375 scoped_ptr<base::Environment> env(base::Environment::Create());
[email protected]af71d642010-03-12 10:29:04376
[email protected]620942e2010-02-16 10:12:12377 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45378 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12379 argv.push_back("check");
[email protected]6a83c4242011-07-07 06:06:41380 if (protocol.empty()) {
[email protected]4f0806a72011-09-21 03:08:45381 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]6a83c4242011-07-07 06:06:41382 } else {
[email protected]4f0806a72011-09-21 03:08:45383 argv.push_back(kXdgSettingsDefaultSchemeHandler);
[email protected]6a83c4242011-07-07 06:06:41384 argv.push_back(protocol);
385 }
[email protected]98566d7a2012-04-17 00:28:56386 argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
[email protected]620942e2010-02-16 10:12:12387
388 std::string reply;
[email protected]6a83c4242011-07-07 06:06:41389 int success_code;
390 bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
391 &success_code);
392 if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
[email protected]4f0806a72011-09-21 03:08:45393 if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
[email protected]6a83c4242011-07-07 06:06:41394 ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
395 &success_code);
396 }
397 }
398
399 if (!ran_ok || success_code != EXIT_SUCCESS) {
[email protected]620942e2010-02-16 10:12:12400 // xdg-settings failed: we can't determine or set the default browser.
[email protected]89886652012-12-11 18:09:07401 return ShellIntegration::UNKNOWN_DEFAULT;
[email protected]620942e2010-02-16 10:12:12402 }
403
404 // Allow any reply that starts with "yes".
[email protected]89886652012-12-11 18:09:07405 return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT :
406 ShellIntegration::NOT_DEFAULT;
[email protected]cc50544002012-05-01 18:56:47407#endif
[email protected]6a83c4242011-07-07 06:06:41408}
409
[email protected]d81a63c02013-03-07 08:49:04410// Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
411// file, given in |shortcut_contents|. If the key is not found, returns false.
412bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
413 // An empty file causes a crash with glib <= 2.32, so special case here.
414 if (shortcut_contents.empty())
415 return false;
416
417 GKeyFile* key_file = g_key_file_new();
418 GError* err = NULL;
419 if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
420 shortcut_contents.size(), G_KEY_FILE_NONE,
421 &err)) {
422 LOG(WARNING) << "Unable to read desktop file template: " << err->message;
423 g_error_free(err);
424 g_key_file_free(key_file);
425 return false;
426 }
427
428 bool nodisplay = false;
429 char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
430 "NoDisplay", &err);
431 if (nodisplay_c_string) {
432 if (!g_strcmp0(nodisplay_c_string, "true"))
433 nodisplay = true;
434 g_free(nodisplay_c_string);
435 }
436
437 g_key_file_free(key_file);
438 return nodisplay;
439}
440
[email protected]fcd21d322013-06-27 12:35:56441// Gets the path to the Chrome executable or wrapper script.
442// Returns an empty path if the executable path could not be found.
443base::FilePath GetChromeExePath() {
444 // Try to get the name of the wrapper script that launched Chrome.
445 scoped_ptr<base::Environment> environment(base::Environment::Create());
446 std::string wrapper_script;
447 if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) {
448 return base::FilePath(wrapper_script);
449 }
450
451 // Just return the name of the executable path for Chrome.
452 base::FilePath chrome_exe_path;
453 PathService::Get(base::FILE_EXE, &chrome_exe_path);
454 return chrome_exe_path;
455}
456
[email protected]6a83c4242011-07-07 06:06:41457} // namespace
458
459// static
[email protected]bd046bd42012-06-08 05:07:32460ShellIntegration::DefaultWebClientSetPermission
461 ShellIntegration::CanSetAsDefaultBrowser() {
462 return SET_DEFAULT_UNATTENDED;
[email protected]a01481b2011-07-15 04:30:02463}
464
465// static
[email protected]6a83c4242011-07-07 06:06:41466bool ShellIntegration::SetAsDefaultBrowser() {
[email protected]007b3f82013-04-09 08:46:45467 return SetDefaultWebClient(std::string());
[email protected]6a83c4242011-07-07 06:06:41468}
469
470// static
471bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
472 return SetDefaultWebClient(protocol);
473}
474
475// static
[email protected]89886652012-12-11 18:09:07476ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
[email protected]007b3f82013-04-09 08:46:45477 return GetIsDefaultWebClient(std::string());
[email protected]4468a5b2011-05-26 07:48:02478}
479
480// static
[email protected]42dc9402013-01-30 07:54:20481std::string ShellIntegration::GetApplicationForProtocol(const GURL& url) {
482 return std::string("xdg-open");
483}
484
485// static
[email protected]4468a5b2011-05-26 07:48:02486ShellIntegration::DefaultWebClientState
487ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
[email protected]6a83c4242011-07-07 06:06:41488 return GetIsDefaultWebClient(protocol);
[email protected]620942e2010-02-16 10:12:12489}
490
491// static
492bool ShellIntegration::IsFirefoxDefaultBrowser() {
493 std::vector<std::string> argv;
[email protected]4f0806a72011-09-21 03:08:45494 argv.push_back(kXdgSettings);
[email protected]620942e2010-02-16 10:12:12495 argv.push_back("get");
[email protected]4f0806a72011-09-21 03:08:45496 argv.push_back(kXdgSettingsDefaultBrowser);
[email protected]620942e2010-02-16 10:12:12497
498 std::string browser;
499 // We don't care about the return value here.
500 base::GetAppOutput(CommandLine(argv), &browser);
501 return browser.find("irefox") != std::string::npos;
502}
503
[email protected]98566d7a2012-04-17 00:28:56504namespace ShellIntegrationLinux {
505
506std::string GetDesktopName(base::Environment* env) {
507#if defined(GOOGLE_CHROME_BUILD)
508 return "google-chrome.desktop";
509#else // CHROMIUM_BUILD
510 // Allow $CHROME_DESKTOP to override the built-in value, so that development
511 // versions can set themselves as the default without interfering with
512 // non-official, packaged versions using the built-in value.
513 std::string name;
514 if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
515 return name;
516 return "chromium-browser.desktop";
517#endif
518}
519
[email protected]14fbaed2013-05-02 07:54:02520std::string GetIconName() {
521#if defined(GOOGLE_CHROME_BUILD)
522 return "google-chrome";
523#else // CHROMIUM_BUILD
524 return "chromium-browser";
525#endif
526}
527
[email protected]d81a63c02013-03-07 08:49:04528ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
529 base::Environment* env,
530 const base::FilePath& profile_path,
531 const std::string& extension_id) {
532 base::FilePath desktop_path;
533 // If Get returns false, just leave desktop_path empty.
534 PathService::Get(base::DIR_USER_DESKTOP, &desktop_path);
535 return GetExistingShortcutLocations(env, profile_path, extension_id,
536 desktop_path);
537}
538
539ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
540 base::Environment* env,
541 const base::FilePath& profile_path,
542 const std::string& extension_id,
543 const base::FilePath& desktop_path) {
544 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
545
546 base::FilePath shortcut_filename = GetExtensionShortcutFilename(
547 profile_path, extension_id);
548 DCHECK(!shortcut_filename.empty());
549 ShellIntegration::ShortcutLocations locations;
550
551 // Determine whether there is a shortcut on desktop.
552 if (!desktop_path.empty()) {
553 locations.on_desktop =
554 file_util::PathExists(desktop_path.Append(shortcut_filename));
555 }
556
557 // Determine whether there is a shortcut in the applications directory.
558 std::string shortcut_contents;
559 if (GetExistingShortcutContents(env, shortcut_filename, &shortcut_contents)) {
560 // Whether this counts as "hidden" or "in_applications_menu" depends on
561 // whether it contains NoDisplay=true.
562 if (GetNoDisplayFromDesktopFile(shortcut_contents))
563 locations.hidden = true;
564 else
565 locations.in_applications_menu = true;
566 }
567
568 return locations;
569}
570
571bool GetExistingShortcutContents(base::Environment* env,
572 const base::FilePath& desktop_filename,
573 std::string* output) {
[email protected]0c7d74f2010-10-11 11:55:26574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
[email protected]620942e2010-02-16 10:12:12575
[email protected]650b2d52013-02-10 03:41:45576 std::vector<base::FilePath> search_paths;
[email protected]b96aa932009-08-12 21:34:49577
[email protected]58bf9252013-03-06 04:12:36578 // Search paths as specified in the XDG Base Directory Specification.
579 // http://standards.freedesktop.org/basedir-spec/latest/
[email protected]af71d642010-03-12 10:29:04580 std::string xdg_data_home;
[email protected]58bf9252013-03-06 04:12:36581 std::string home;
[email protected]3ba7e082010-08-07 02:57:59582 if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) &&
[email protected]af71d642010-03-12 10:29:04583 !xdg_data_home.empty()) {
[email protected]650b2d52013-02-10 03:41:45584 search_paths.push_back(base::FilePath(xdg_data_home));
[email protected]58bf9252013-03-06 04:12:36585 } else if (env->GetVar("HOME", &home) && !home.empty()) {
586 search_paths.push_back(base::FilePath(home).Append(".local").Append(
587 "share"));
[email protected]af71d642010-03-12 10:29:04588 }
[email protected]b96aa932009-08-12 21:34:49589
[email protected]af71d642010-03-12 10:29:04590 std::string xdg_data_dirs;
[email protected]3ba7e082010-08-07 02:57:59591 if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) &&
[email protected]af71d642010-03-12 10:29:04592 !xdg_data_dirs.empty()) {
[email protected]f4ebe772013-02-02 00:21:39593 base::StringTokenizer tokenizer(xdg_data_dirs, ":");
[email protected]b96aa932009-08-12 21:34:49594 while (tokenizer.GetNext()) {
[email protected]650b2d52013-02-10 03:41:45595 base::FilePath data_dir(tokenizer.token());
[email protected]fd928962009-09-18 17:55:55596 search_paths.push_back(data_dir);
[email protected]b96aa932009-08-12 21:34:49597 }
[email protected]58bf9252013-03-06 04:12:36598 } else {
599 search_paths.push_back(base::FilePath("/usr/local/share"));
600 search_paths.push_back(base::FilePath("/usr/share"));
[email protected]b96aa932009-08-12 21:34:49601 }
602
[email protected]650b2d52013-02-10 03:41:45603 for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
[email protected]b96aa932009-08-12 21:34:49604 i != search_paths.end(); ++i) {
[email protected]d81a63c02013-03-07 08:49:04605 base::FilePath path = i->Append("applications").Append(desktop_filename);
606 VLOG(1) << "Looking for desktop file in " << path.value();
[email protected]620942e2010-02-16 10:12:12607 if (file_util::PathExists(path)) {
[email protected]d81a63c02013-03-07 08:49:04608 VLOG(1) << "Found desktop file at " << path.value();
[email protected]b96aa932009-08-12 21:34:49609 return file_util::ReadFileToString(path, output);
[email protected]620942e2010-02-16 10:12:12610 }
[email protected]b96aa932009-08-12 21:34:49611 }
612
[email protected]b96aa932009-08-12 21:34:49613 return false;
614}
615
[email protected]650b2d52013-02-10 03:41:45616base::FilePath GetWebShortcutFilename(const GURL& url) {
[email protected]42896802009-08-28 23:39:44617 // Use a prefix, because xdg-desktop-menu requires it.
[email protected]de2943352009-10-22 23:06:12618 std::string filename =
[email protected]4f260d02010-12-23 18:35:42619 std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
[email protected]de2943352009-10-22 23:06:12620 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]b96aa932009-08-12 21:34:49621
[email protected]650b2d52013-02-10 03:41:45622 base::FilePath desktop_path;
[email protected]dea1d7d2012-09-20 16:24:52623 if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
[email protected]650b2d52013-02-10 03:41:45624 return base::FilePath();
[email protected]fcc23e842009-10-01 03:19:10625
[email protected]650b2d52013-02-10 03:41:45626 base::FilePath filepath = desktop_path.Append(filename);
627 base::FilePath alternative_filepath(filepath.value() + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10628 for (size_t i = 1; i < 100; ++i) {
[email protected]650b2d52013-02-10 03:41:45629 if (file_util::PathExists(base::FilePath(alternative_filepath))) {
630 alternative_filepath = base::FilePath(
[email protected]528c56d2010-07-30 19:28:44631 filepath.value() + "_" + base::IntToString(i) + ".desktop");
[email protected]fcc23e842009-10-01 03:19:10632 } else {
[email protected]650b2d52013-02-10 03:41:45633 return base::FilePath(alternative_filepath).BaseName();
[email protected]fcc23e842009-10-01 03:19:10634 }
635 }
636
[email protected]650b2d52013-02-10 03:41:45637 return base::FilePath();
[email protected]b96aa932009-08-12 21:34:49638}
639
[email protected]650b2d52013-02-10 03:41:45640base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path,
641 const std::string& extension_id) {
[email protected]0b7df36d2012-07-11 09:50:47642 DCHECK(!extension_id.empty());
643
644 // Use a prefix, because xdg-desktop-menu requires it.
645 std::string filename(chrome::kBrowserProcessExecutableName);
646 filename.append("-")
647 .append(extension_id)
648 .append("-")
649 .append(profile_path.BaseName().value());
650 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
[email protected]650b2d52013-02-10 03:41:45651 return base::FilePath(filename.append(".desktop"));
[email protected]0b7df36d2012-07-11 09:50:47652}
653
[email protected]98566d7a2012-04-17 00:28:56654std::string GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02655 const base::FilePath& chrome_exe_path,
[email protected]a0b60cfd2011-04-06 18:02:41656 const std::string& app_name,
657 const GURL& url,
658 const std::string& extension_id,
[email protected]650b2d52013-02-10 03:41:45659 const base::FilePath& extension_path,
[email protected]a0b60cfd2011-04-06 18:02:41660 const string16& title,
[email protected]5951c852012-06-20 00:12:53661 const std::string& icon_name,
[email protected]d81a63c02013-03-07 08:49:04662 const base::FilePath& profile_path,
663 bool no_display) {
[email protected]b9eb4e52013-02-05 00:01:49664 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
665 // launchers with an xdg-open shebang. Follow that convention.
666 std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
[email protected]0a96c3f2011-05-11 22:10:20667
[email protected]b96aa932009-08-12 21:34:49668 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
[email protected]0a96c3f2011-05-11 22:10:20669 GKeyFile* key_file = g_key_file_new();
[email protected]0a96c3f2011-05-11 22:10:20670
[email protected]14fbaed2013-05-02 07:54:02671 // Set keys with fixed values.
672 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
673 g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
674 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
[email protected]0a96c3f2011-05-11 22:10:20675
676 // Set the "Name" key.
677 std::string final_title = UTF16ToUTF8(title);
678 // Make sure no endline characters can slip in and possibly introduce
679 // additional lines (like Exec, which makes it a security risk). Also
680 // use the URL as a default when the title is empty.
681 if (final_title.empty() ||
682 final_title.find("\n") != std::string::npos ||
683 final_title.find("\r") != std::string::npos) {
684 final_title = url.spec();
685 }
686 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
687
688 // Set the "Exec" key.
[email protected]14fbaed2013-05-02 07:54:02689 std::string final_path = chrome_exe_path.value();
690 CommandLine cmd_line(CommandLine::NO_PROGRAM);
691 cmd_line = ShellIntegration::CommandLineArgsForLauncher(
692 url, extension_id, profile_path);
693 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches();
694 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin();
695 i != switch_map.end(); ++i) {
696 if (i->second.empty()) {
697 final_path += " --" + i->first;
698 } else {
699 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first +
700 "=" + i->second);
[email protected]0a96c3f2011-05-11 22:10:20701 }
[email protected]0a96c3f2011-05-11 22:10:20702 }
703
[email protected]14fbaed2013-05-02 07:54:02704 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
705
[email protected]0a96c3f2011-05-11 22:10:20706 // Set the "Icon" key.
[email protected]14fbaed2013-05-02 07:54:02707 if (!icon_name.empty()) {
[email protected]0a96c3f2011-05-11 22:10:20708 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
[email protected]14fbaed2013-05-02 07:54:02709 } else {
710 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
711 GetIconName().c_str());
712 }
[email protected]a0b60cfd2011-04-06 18:02:41713
[email protected]d81a63c02013-03-07 08:49:04714 // Set the "NoDisplay" key.
715 if (no_display)
716 g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
717
[email protected]a13283cc2012-04-05 00:21:22718#if defined(TOOLKIT_GTK)
[email protected]a0b60cfd2011-04-06 18:02:41719 std::string wmclass = web_app::GetWMClassFromAppName(app_name);
[email protected]0a96c3f2011-05-11 22:10:20720 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
721 wmclass.c_str());
[email protected]87c914a2011-04-06 18:15:00722#endif
[email protected]a0b60cfd2011-04-06 18:02:41723
[email protected]14fbaed2013-05-02 07:54:02724 gsize length = 0;
[email protected]0a96c3f2011-05-11 22:10:20725 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
726 if (data_dump) {
[email protected]b9eb4e52013-02-05 00:01:49727 // If strlen(data_dump[0]) == 0, this check will fail.
728 if (data_dump[0] == '\n') {
729 // Older versions of glib produce a leading newline. If this is the case,
730 // remove it to avoid double-newline after the shebang.
731 output_buffer += (data_dump + 1);
732 } else {
733 output_buffer += data_dump;
734 }
[email protected]0a96c3f2011-05-11 22:10:20735 g_free(data_dump);
736 }
737
738 g_key_file_free(key_file);
[email protected]b96aa932009-08-12 21:34:49739 return output_buffer;
740}
741
[email protected]a3c25952013-05-02 13:16:06742std::string GetDirectoryFileContents(const string16& title,
743 const std::string& icon_name) {
744 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
745 GKeyFile* key_file = g_key_file_new();
746
747 g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
748 g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
749 std::string final_title = UTF16ToUTF8(title);
750 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
751 if (!icon_name.empty()) {
752 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
753 } else {
754 g_key_file_set_string(key_file, kDesktopEntry, "Icon",
755 GetIconName().c_str());
756 }
757
758 gsize length = 0;
759 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
760 std::string output_buffer;
761 if (data_dump) {
762 // If strlen(data_dump[0]) == 0, this check will fail.
763 if (data_dump[0] == '\n') {
764 // Older versions of glib produce a leading newline. If this is the case,
765 // remove it to avoid double-newline after the shebang.
766 output_buffer += (data_dump + 1);
767 } else {
768 output_buffer += data_dump;
769 }
770 g_free(data_dump);
771 }
772
773 g_key_file_free(key_file);
774 return output_buffer;
775}
776
[email protected]98566d7a2012-04-17 00:28:56777bool CreateDesktopShortcut(
778 const ShellIntegration::ShortcutInfo& shortcut_info,
[email protected]14fbaed2013-05-02 07:54:02779 const ShellIntegration::ShortcutLocations& creation_locations) {
[email protected]0c7d74f2010-10-11 11:55:26780 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
[email protected]620942e2010-02-16 10:12:12781
[email protected]650b2d52013-02-10 03:41:45782 base::FilePath shortcut_filename;
[email protected]0b7df36d2012-07-11 09:50:47783 if (!shortcut_info.extension_id.empty()) {
784 shortcut_filename = GetExtensionShortcutFilename(
785 shortcut_info.profile_path, shortcut_info.extension_id);
786 // For extensions we do not want duplicate shortcuts. So, delete any that
787 // already exist and replace them.
[email protected]b5ff7ab2013-03-01 07:48:53788 if (creation_locations.on_desktop)
[email protected]0b7df36d2012-07-11 09:50:47789 DeleteShortcutOnDesktop(shortcut_filename);
[email protected]d81a63c02013-03-07 08:49:04790 if (creation_locations.in_applications_menu || creation_locations.hidden)
[email protected]a3c25952013-05-02 13:16:06791 DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
[email protected]0b7df36d2012-07-11 09:50:47792 } else {
793 shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
794 }
[email protected]620942e2010-02-16 10:12:12795 if (shortcut_filename.empty())
[email protected]8806d3b2012-04-13 06:46:34796 return false;
[email protected]620942e2010-02-16 10:12:12797
[email protected]5951c852012-06-20 00:12:53798 std::string icon_name = CreateShortcutIcon(shortcut_info, shortcut_filename);
[email protected]620942e2010-02-16 10:12:12799
[email protected]a0b60cfd2011-04-06 18:02:41800 std::string app_name =
801 web_app::GenerateApplicationNameFromInfo(shortcut_info);
[email protected]620942e2010-02-16 10:12:12802
[email protected]8806d3b2012-04-13 06:46:34803 bool success = true;
804
[email protected]fcd21d322013-06-27 12:35:56805 base::FilePath chrome_exe_path = GetChromeExePath();
806 if (chrome_exe_path.empty()) {
[email protected]14fbaed2013-05-02 07:54:02807 LOG(WARNING) << "Could not get executable path.";
808 return false;
809 }
810
[email protected]d81a63c02013-03-07 08:49:04811 if (creation_locations.on_desktop) {
812 std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02813 chrome_exe_path,
[email protected]d81a63c02013-03-07 08:49:04814 app_name,
815 shortcut_info.url,
816 shortcut_info.extension_id,
817 shortcut_info.extension_path,
818 shortcut_info.title,
819 icon_name,
820 shortcut_info.profile_path,
821 false);
[email protected]8806d3b2012-04-13 06:46:34822 success = CreateShortcutOnDesktop(shortcut_filename, contents);
[email protected]d81a63c02013-03-07 08:49:04823 }
[email protected]620942e2010-02-16 10:12:12824
[email protected]d81a63c02013-03-07 08:49:04825 // The 'in_applications_menu' and 'hidden' locations are actually the same
826 // place ('applications').
827 if (creation_locations.in_applications_menu || creation_locations.hidden) {
[email protected]a3c25952013-05-02 13:16:06828 base::FilePath directory_filename;
829 std::string directory_contents;
830 if (!creation_locations.applications_menu_subdir.empty()) {
831 directory_filename = base::FilePath(kDirectoryFilename);
832 directory_contents = ShellIntegrationLinux::GetDirectoryFileContents(
833 creation_locations.applications_menu_subdir, "");
834 }
[email protected]d81a63c02013-03-07 08:49:04835 // Set NoDisplay=true if hidden but not in_applications_menu. This will hide
836 // the application from user-facing menus.
837 std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02838 chrome_exe_path,
[email protected]d81a63c02013-03-07 08:49:04839 app_name,
840 shortcut_info.url,
841 shortcut_info.extension_id,
842 shortcut_info.extension_path,
843 shortcut_info.title,
844 icon_name,
845 shortcut_info.profile_path,
846 !creation_locations.in_applications_menu);
[email protected]a3c25952013-05-02 13:16:06847 success = CreateShortcutInApplicationsMenu(
848 shortcut_filename, contents, directory_filename, directory_contents) &&
849 success;
[email protected]d81a63c02013-03-07 08:49:04850 }
[email protected]8806d3b2012-04-13 06:46:34851
852 return success;
[email protected]b96aa932009-08-12 21:34:49853}
[email protected]8806d3b2012-04-13 06:46:34854
[email protected]650b2d52013-02-10 03:41:45855void DeleteDesktopShortcuts(const base::FilePath& profile_path,
[email protected]0b7df36d2012-07-11 09:50:47856 const std::string& extension_id) {
857 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
858
[email protected]650b2d52013-02-10 03:41:45859 base::FilePath shortcut_filename = GetExtensionShortcutFilename(
[email protected]0b7df36d2012-07-11 09:50:47860 profile_path, extension_id);
861 DCHECK(!shortcut_filename.empty());
862
863 DeleteShortcutOnDesktop(shortcut_filename);
[email protected]a3c25952013-05-02 13:16:06864 // Delete shortcuts from |kDirectoryFilename|.
865 // Note that it is possible that shortcuts were not created in the Chrome Apps
866 // directory (depending on the value of |applications_menu_subdir| when they
867 // were created). It doesn't matter: this will still delete the shortcut even
868 // if it isn't in the directory.
869 DeleteShortcutInApplicationsMenu(shortcut_filename,
870 base::FilePath(kDirectoryFilename));
[email protected]0b7df36d2012-07-11 09:50:47871}
872
[email protected]8806d3b2012-04-13 06:46:34873} // namespace ShellIntegrationLinux