shortcut_manager.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch// Copyright 2013 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 5558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch#include "chrome/browser/apps/shortcut_manager.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "apps/pref_names.h" 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/bind.h" 9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/command_line.h" 102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/compiler_specific.h" 11eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/prefs/pref_service.h" 12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string16.h" 13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h" 14eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/browser_process.h" 157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "chrome/browser/chrome_notification_types.h" 16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/extensions/extension_service.h" 17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/extensions/extension_system.h" 18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/profiles/profile.h" 19eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/profiles/profile_info_cache.h" 20eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/browser/profiles/profile_manager.h" 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/shell_integration.h" 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/web_applications/web_app_ui.h" 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/web_applications/web_app.h" 24868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "chrome/common/chrome_switches.h" 25eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "chrome/common/extensions/extension_set.h" 26eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "content/public/browser/browser_thread.h" 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/notification_details.h" 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/notification_source.h" 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 30a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)#if defined(OS_MACOSX) 31a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)#include "apps/app_shim/app_shim_mac.h" 32a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)#endif 33a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)using extensions::Extension; 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)namespace { 37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// Creates a shortcut for an application in the applications menu, if there is 39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// not already one present. 40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CreateShortcutsInApplicationsMenu( 41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const ShellIntegration::ShortcutInfo& shortcut_info) { 42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) ShellIntegration::ShortcutLocations creation_locations; 43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) creation_locations.in_applications_menu = true; 44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) // Create the shortcut in the Chrome Apps subdir. 45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) creation_locations.applications_menu_subdir = 46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) web_app::GetAppShortcutsSubdirName(); 47eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch web_app::CreateShortcuts(shortcut_info, creation_locations, 48558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch web_app::SHORTCUT_CREATION_AUTOMATED); 49eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 50eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 51eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochbool ShouldCreateShortcutFor(const extensions::Extension* extension) { 52eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return extension->is_platform_app() && 53eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch extension->location() != extensions::Manifest::COMPONENT && 54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch extension->ShouldDisplayInAppLauncher(); 55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 57c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} // namespace 58c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 59558790d6acca3451cf3a6b497803a5f07d0bec58Ben MurdochAppShortcutManager::AppShortcutManager(Profile* profile) 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) : profile_(profile), 61eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch is_profile_info_cache_observer_(false), 62eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch prefs_(profile->GetPrefs()), 63c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) weak_factory_(this) { 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) content::Source<Profile>(profile_)); 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) content::Source<Profile>(profile_)); 68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Wait for extensions to be ready before running OnceOffCreateShortcuts. 69eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, 70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch content::Source<Profile>(profile_)); 71eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 72eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // TODO(mgiuca): This use of g_browser_process should be DCHECKed for 73eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // content::BrowserThread::CurrentlyOn(content::BrowserThread::UI). This check 74eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // currently fails browser_tests, due to http://crbug.com/251191. 75eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ProfileManager* profile_manager = g_browser_process->profile_manager(); 76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // profile_manager might be NULL in testing environments. 77eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (profile_manager) { 78eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch profile_manager->GetProfileInfoCache().AddObserver(this); 79eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch is_profile_info_cache_observer_ = true; 80eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 83558790d6acca3451cf3a6b497803a5f07d0bec58Ben MurdochAppShortcutManager::~AppShortcutManager() { 84eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (g_browser_process && is_profile_info_cache_observer_) { 85eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ProfileManager* profile_manager = g_browser_process->profile_manager(); 86eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // profile_manager might be NULL in testing environments or during shutdown. 87eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (profile_manager) 88eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch profile_manager->GetProfileInfoCache().RemoveObserver(this); 89eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 90eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 92558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid AppShortcutManager::Observe(int type, 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const content::NotificationSource& source, 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const content::NotificationDetails& details) { 952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) switch (type) { 96eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch case chrome::NOTIFICATION_EXTENSIONS_READY: { 97eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch OnceOffCreateShortcuts(); 98eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch break; 99eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#if defined(OS_MACOSX) 102a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) if (!apps::IsAppShimsEnabled()) 103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) break; 104868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#endif // defined(OS_MACOSX) 105868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 106c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const extensions::InstalledExtensionInfo* installed_info = 107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) content::Details<const extensions::InstalledExtensionInfo>(details) 108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) .ptr(); 109c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const Extension* extension = installed_info->extension; 110eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (ShouldCreateShortcutFor(extension)) { 111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) // If the app is being updated, update any existing shortcuts but do not 112c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) // create new ones. If it is being installed, automatically create a 113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) // shortcut in the applications menu (e.g., Start Menu). 114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Callback<void(const ShellIntegration::ShortcutInfo&)> 115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) create_or_update; 116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (installed_info->is_update) { 11790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) string16 old_title = UTF8ToUTF16(installed_info->old_name); 11890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) create_or_update = base::Bind(&web_app::UpdateAllShortcuts, 11990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) old_title); 120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } else { 121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) create_or_update = base::Bind(&CreateShortcutsInApplicationsMenu); 122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) web_app::UpdateShortcutInfoAndIconForApp(*extension, profile_, 125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) create_or_update); 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break; 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const Extension* extension = content::Details<const Extension>( 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) details).ptr(); 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DeleteApplicationShortcuts(extension); 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break; 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) default: 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) NOTREACHED(); 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 140558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid AppShortcutManager::OnProfileWillBeRemoved( 141eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const base::FilePath& profile_path) { 142eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (profile_path != profile_->GetPath()) 143eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return; 144eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch content::BrowserThread::PostTask( 145eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch content::BrowserThread::FILE, FROM_HERE, 146eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch base::Bind(&web_app::internals::DeleteAllShortcutsForProfile, 147eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch profile_path)); 148eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 149eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 150558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid AppShortcutManager::OnceOffCreateShortcuts() { 151eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch bool was_enabled = prefs_->GetBoolean(apps::prefs::kShortcutsHaveBeenCreated); 152eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 153eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Creation of shortcuts on Mac currently sits behind --enable-app-shims. 154eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Until it is enabled permanently, we need to check the flag, and set the 155eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // pref accordingly. 156eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#if defined(OS_MACOSX) 157a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) bool is_now_enabled = apps::IsAppShimsEnabled(); 158eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#else 159eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch bool is_now_enabled = true; 160eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#endif // defined(OS_MACOSX) 161eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 162eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (was_enabled != is_now_enabled) 163eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch prefs_->SetBoolean(apps::prefs::kShortcutsHaveBeenCreated, is_now_enabled); 164eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 165eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (was_enabled || !is_now_enabled) 166eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return; 167eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 168eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Check if extension system/service are available. They might not be in 169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // tests. 170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch extensions::ExtensionSystem* extension_system; 171eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ExtensionServiceInterface* extension_service; 172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (!(extension_system = extensions::ExtensionSystem::Get(profile_)) || 173eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch !(extension_service = extension_system->extension_service())) 174eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return; 175eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 176eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Create an applications menu shortcut for each app in this profile. 177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const ExtensionSet* apps = extension_service->extensions(); 178eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch for (ExtensionSet::const_iterator it = apps->begin(); 179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch it != apps->end(); ++it) { 180eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (ShouldCreateShortcutFor(it->get())) 181eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch web_app::UpdateShortcutInfoAndIconForApp( 182eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch *it->get(), profile_, base::Bind(&CreateShortcutsInApplicationsMenu)); 183eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 184eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 185eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 186558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid AppShortcutManager::DeleteApplicationShortcuts( 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const Extension* extension) { 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ShellIntegration::ShortcutInfo delete_info = 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) web_app::ShortcutInfoForExtensionAndProfile(extension, profile_); 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) web_app::DeleteAllShortcuts(delete_info); 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 192