Chromium Code Reviews| 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 |