Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: chrome/browser/shell_integration_linux.cc

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

Powered by Google App Engine
This is Rietveld 408576698