OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/shell_integration.h" | 5 #include "chrome/browser/shell_integration.h" |
6 | 6 |
7 #include <fcntl.h> | 7 #include <fcntl.h> |
8 #include <glib.h> | |
8 #include <stdlib.h> | 9 #include <stdlib.h> |
9 #include <sys/stat.h> | 10 #include <sys/stat.h> |
10 #include <sys/types.h> | 11 #include <sys/types.h> |
11 #include <unistd.h> | 12 #include <unistd.h> |
12 | 13 |
13 #include <string> | 14 #include <string> |
14 #include <vector> | 15 #include <vector> |
15 | 16 |
16 #include "base/command_line.h" | 17 #include "base/command_line.h" |
17 #include "base/eintr_wrapper.h" | 18 #include "base/eintr_wrapper.h" |
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
197 quoted += '\\'; | 198 quoted += '\\'; |
198 break; | 199 break; |
199 } | 200 } |
200 quoted += arg[i]; | 201 quoted += arg[i]; |
201 } | 202 } |
202 quoted += '"'; | 203 quoted += '"'; |
203 | 204 |
204 return quoted; | 205 return quoted; |
205 } | 206 } |
206 | 207 |
207 // Escape a string if needed for the right side of a Key=Value | 208 // Remove keys from the [Desktop Entry] that would be wrong if copied verbatim |
208 // construct in a desktop file. (Note that for Exec= lines this | 209 // into the new .desktop file. |
209 // should be used in conjunction with QuoteArgForDesktopFileExec, | 210 const char* kDesktopKeysToDelete[] = { |
210 // possibly escaping a backslash twice.) | 211 "GenericName", |
211 std::string EscapeStringForDesktopFile(const std::string& arg) { | 212 "Comment", |
212 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html | 213 "MimeType", |
213 if (arg.find('\\') == std::string::npos) | 214 "X-Ayatana-Desktop-Shortcuts", |
214 return arg; | 215 "StartupWMClass", |
216 NULL | |
217 }; | |
215 | 218 |
216 std::string escaped; | 219 const char* kDesktopEntry = "Desktop Entry"; |
217 for (size_t i = 0; i < arg.size(); ++i) { | 220 |
218 if (arg[i] == '\\') | 221 const char* kXdgOpenShebang = "#!/usr/bin/env xdg-open"; |
219 escaped += '\\'; | |
220 escaped += arg[i]; | |
221 } | |
222 return escaped; | |
223 } | |
224 | 222 |
225 } // namespace | 223 } // namespace |
226 | 224 |
227 // static | 225 // static |
228 std::string ShellIntegration::GetDesktopName(base::Environment* env) { | 226 std::string ShellIntegration::GetDesktopName(base::Environment* env) { |
229 #if defined(GOOGLE_CHROME_BUILD) | 227 #if defined(GOOGLE_CHROME_BUILD) |
230 return "google-chrome.desktop"; | 228 return "google-chrome.desktop"; |
231 #else // CHROMIUM_BUILD | 229 #else // CHROMIUM_BUILD |
232 // Allow $CHROME_DESKTOP to override the built-in value, so that development | 230 // Allow $CHROME_DESKTOP to override the built-in value, so that development |
233 // versions can set themselves as the default without interfering with | 231 // versions can set themselves as the default without interfering with |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
361 } | 359 } |
362 | 360 |
363 // static | 361 // static |
364 std::string ShellIntegration::GetDesktopFileContents( | 362 std::string ShellIntegration::GetDesktopFileContents( |
365 const std::string& template_contents, | 363 const std::string& template_contents, |
366 const std::string& app_name, | 364 const std::string& app_name, |
367 const GURL& url, | 365 const GURL& url, |
368 const std::string& extension_id, | 366 const std::string& extension_id, |
369 const string16& title, | 367 const string16& title, |
370 const std::string& icon_name) { | 368 const std::string& icon_name) { |
369 if (template_contents.empty()) | |
370 return std::string(kXdgOpenShebang) + "\n"; | |
371 | |
371 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ | 372 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ |
372 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its | 373 // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html |
373 // launchers with an xdg-open shebang. Follow that convention. | 374 GKeyFile* key_file = g_key_file_new(); |
374 std::string output_buffer("#!/usr/bin/env xdg-open\n"); | 375 GError* err = NULL; |
375 StringTokenizer tokenizer(template_contents, "\n"); | 376 // Loading the data will strip translations and comments from the desktop |
376 while (tokenizer.GetNext()) { | 377 // file (which we want to do!) |
377 if (tokenizer.token().substr(0, 5) == "Exec=") { | 378 if (!g_key_file_load_from_data( |
378 std::string exec_path = tokenizer.token().substr(5); | 379 key_file, |
379 StringTokenizer exec_tokenizer(exec_path, " "); | 380 template_contents.c_str(), |
380 std::string final_path; | 381 template_contents.size(), |
381 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { | 382 G_KEY_FILE_NONE, |
382 if (!final_path.empty()) | 383 &err)) { |
383 final_path += " "; | 384 NOTREACHED() << "Unable to read desktop file template:" << err->message; |
384 final_path += exec_tokenizer.token(); | 385 g_error_free(err); |
385 } | 386 return std::string(kXdgOpenShebang) + "\n"; |
386 CommandLine cmd_line = | 387 } |
387 ShellIntegration::CommandLineArgsForLauncher(url, extension_id); | 388 |
388 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); | 389 // Remove all sections except for the Desktop Entry |
389 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); | 390 gsize length = 0; |
390 i != switch_map.end(); ++i) { | 391 gchar** groups = g_key_file_get_groups(key_file, &length); |
391 if (i->second.empty()) { | 392 for (gsize i = 0; i < length; ++i) { |
392 final_path += " --" + i->first; | 393 if (strcmp(groups[i], kDesktopEntry) != 0) { |
393 } else { | 394 g_key_file_remove_group(key_file, groups[i], NULL); |
394 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + | |
395 "=" + i->second); | |
396 } | |
397 } | |
398 output_buffer += std::string("Exec=") + | |
399 EscapeStringForDesktopFile(final_path) + "\n"; | |
400 } else if (tokenizer.token().substr(0, 5) == "Name=") { | |
401 std::string final_title = UTF16ToUTF8(title); | |
402 // Make sure no endline characters can slip in and possibly introduce | |
403 // additional lines (like Exec, which makes it a security risk). Also | |
404 // use the URL as a default when the title is empty. | |
405 if (final_title.empty() || | |
406 final_title.find("\n") != std::string::npos || | |
407 final_title.find("\r") != std::string::npos) { | |
408 final_title = url.spec(); | |
409 } | |
410 output_buffer += StringPrintf("Name=%s\n", final_title.c_str()); | |
411 } else if (tokenizer.token().substr(0, 11) == "GenericName" || | |
412 tokenizer.token().substr(0, 7) == "Comment" || | |
413 tokenizer.token().substr(0, 1) == "#") { | |
414 // Skip comment lines. | |
415 } else if (tokenizer.token().substr(0, 9) == "MimeType=") { | |
416 // Skip MimeType lines, they are only relevant for a web browser | |
417 // shortcut, not a web application shortcut. | |
418 } else if (tokenizer.token().substr(0, 15) == "StartupWMClass=") { | |
419 // Skip StartupWMClass; it will certainly be wrong since we emit a | |
420 // different one based on the app name below. | |
421 } else if (tokenizer.token().substr(0, 5) == "Icon=" && | |
422 !icon_name.empty()) { | |
423 output_buffer += StringPrintf("Icon=%s\n", icon_name.c_str()); | |
424 } else { | |
425 output_buffer += tokenizer.token() + "\n"; | |
426 } | 395 } |
427 } | 396 } |
397 g_strfreev(groups); | |
398 | |
399 // Remove keys that we won't need. | |
400 for (const char** current_key = kDesktopKeysToDelete; *current_key; | |
401 ++current_key) { | |
402 g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL); | |
403 } | |
404 | |
405 // Set the "Name" key. | |
406 std::string final_title = UTF16ToUTF8(title); | |
407 // Make sure no endline characters can slip in and possibly introduce | |
408 // additional lines (like Exec, which makes it a security risk). Also | |
409 // use the URL as a default when the title is empty. | |
410 if (final_title.empty() || | |
411 final_title.find("\n") != std::string::npos || | |
412 final_title.find("\r") != std::string::npos) { | |
413 final_title = url.spec(); | |
414 } | |
415 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); | |
416 | |
417 // Set the "Exec" key. | |
418 char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec", | |
419 NULL); | |
420 if (exec_c_string) { | |
421 std::string exec_string(exec_c_string); | |
422 g_free(exec_c_string); | |
423 StringTokenizer exec_tokenizer(exec_string, " "); | |
424 | |
425 std::string final_path; | |
426 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { | |
427 if (!final_path.empty()) | |
428 final_path += " "; | |
429 final_path += exec_tokenizer.token(); | |
430 } | |
431 CommandLine cmd_line = | |
432 ShellIntegration::CommandLineArgsForLauncher(url, extension_id); | |
433 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); | |
434 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); | |
435 i != switch_map.end(); ++i) { | |
436 if (i->second.empty()) { | |
437 final_path += " --" + i->first; | |
438 } else { | |
439 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + | |
440 "=" + i->second); | |
441 } | |
442 } | |
443 | |
444 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); | |
445 } | |
446 | |
447 // Set the "Icon" key. | |
448 if (!icon_name.empty()) | |
449 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); | |
428 | 450 |
429 #if defined(TOOLKIT_USES_GTK) | 451 #if defined(TOOLKIT_USES_GTK) |
430 std::string wmclass = web_app::GetWMClassFromAppName(app_name); | 452 std::string wmclass = web_app::GetWMClassFromAppName(app_name); |
431 if (!wmclass.empty()) { | 453 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", |
432 output_buffer += StringPrintf("StartupWMClass=%s\n", wmclass.c_str()); | 454 wmclass.c_str()); |
433 } | |
434 #endif | 455 #endif |
435 | 456 |
457 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its | |
458 // launchers with an xdg-open shebang. Follow that convention. | |
459 std::string output_buffer = kXdgOpenShebang; | |
Evan Martin
2011/05/11 21:54:15
Do you need a newline here? I guess the tests mus
| |
460 length = 0; | |
461 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); | |
462 if (data_dump) { | |
463 output_buffer += data_dump; | |
464 g_free(data_dump); | |
465 } | |
466 | |
467 g_key_file_free(key_file); | |
436 return output_buffer; | 468 return output_buffer; |
437 } | 469 } |
438 | 470 |
439 // static | 471 // static |
440 void ShellIntegration::CreateDesktopShortcut( | 472 void ShellIntegration::CreateDesktopShortcut( |
441 const ShortcutInfo& shortcut_info, const std::string& shortcut_template) { | 473 const ShortcutInfo& shortcut_info, const std::string& shortcut_template) { |
442 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. | 474 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. |
443 | 475 |
444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
445 | 477 |
(...skipping 12 matching lines...) Expand all Loading... | |
458 shortcut_info.extension_id, | 490 shortcut_info.extension_id, |
459 shortcut_info.title, | 491 shortcut_info.title, |
460 icon_name); | 492 icon_name); |
461 | 493 |
462 if (shortcut_info.create_on_desktop) | 494 if (shortcut_info.create_on_desktop) |
463 CreateShortcutOnDesktop(shortcut_filename, contents); | 495 CreateShortcutOnDesktop(shortcut_filename, contents); |
464 | 496 |
465 if (shortcut_info.create_in_applications_menu) | 497 if (shortcut_info.create_in_applications_menu) |
466 CreateShortcutInApplicationsMenu(shortcut_filename, contents); | 498 CreateShortcutInApplicationsMenu(shortcut_filename, contents); |
467 } | 499 } |
OLD | NEW |