// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/profiles/profile_shortcut_manager_win.h" #include "base/bind.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "chrome/browser/app_icon_win.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile_info_cache.h" #include "chrome/browser/profiles/profile_info_util.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/installer/util/auto_launch_util.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/shell_util.h" #include "content/public/browser/browser_thread.h" #include "grit/generated_resources.h" #include "skia/ext/image_operations.h" #include "skia/ext/platform_canvas.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" using content::BrowserThread; namespace { const char kProfileIconFileName[] = "Google Profile.ico"; const int kProfileAvatarShortcutBadgeWidth = 28; const int kProfileAvatarShortcutBadgeHeight = 28; const int kShortcutIconSize = 48; // Creates the argument to pass to the Windows executable that launches Chrome // with the profile in |profile_base_dir|. // For example: --profile-directory="Profile 2" string16 CreateProfileShortcutSwitch(const string16& profile_base_dir) { return base::StringPrintf(L"--%ls=\"%ls\"", ASCIIToUTF16(switches::kProfileDirectory).c_str(), profile_base_dir.c_str()); } // Wrap a ShellUtil function that returns a bool so it can be posted in a // task to the FILE thread. void CallShellUtilBoolFunction( const base::Callback& bool_function) { bool_function.Run(); } // Creates a desktop shortcut icon file (.ico) on the disk for a given profile, // badging the browser distribution icon with the profile avatar. // |profile_base_dir| is the base directory (and key) of the profile. Returns // a path to the shortcut icon file on disk, which is empty if this fails. // Use index 0 when assigning the resulting file as the icon. FilePath CreateChromeDesktopShortcutIconForProfile( const FilePath& profile_path, const gfx::Image& avatar_image) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); const SkBitmap* avatar_bitmap = avatar_image.ToSkBitmap(); HICON app_icon_handle = GetAppIconForSize(kShortcutIconSize); scoped_ptr app_icon_bitmap( IconUtil::CreateSkBitmapFromHICON(app_icon_handle)); DestroyIcon(app_icon_handle); if (!app_icon_bitmap.get()) return FilePath(); // TODO(stevet): Share this chunk of code with // avatar_menu_button::DrawTaskBarDecoration. const SkBitmap* source_bitmap = NULL; SkBitmap squarer_bitmap; if ((avatar_bitmap->width() == profiles::kAvatarIconWidth) && (avatar_bitmap->height() == profiles::kAvatarIconHeight)) { // Shave a couple of columns so the bitmap is more square. So when // resized to a square aspect ratio it looks pretty. int x = 2; avatar_bitmap->extractSubset(&squarer_bitmap, SkIRect::MakeXYWH(x, 0, profiles::kAvatarIconWidth - x * 2, profiles::kAvatarIconHeight)); source_bitmap = &squarer_bitmap; } else { source_bitmap = avatar_bitmap; } SkBitmap sk_icon = skia::ImageOperations::Resize( *source_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, kProfileAvatarShortcutBadgeWidth, kProfileAvatarShortcutBadgeHeight); // Overlay the avatar on the icon, anchoring it to the bottom-right of the // icon. scoped_ptr offscreen_canvas( skia::CreateBitmapCanvas(app_icon_bitmap->width(), app_icon_bitmap->height(), false)); DCHECK(offscreen_canvas.get()); offscreen_canvas->drawBitmap(*app_icon_bitmap, 0, 0); offscreen_canvas->drawBitmap( sk_icon, app_icon_bitmap->width() - kProfileAvatarShortcutBadgeWidth, app_icon_bitmap->height() - kProfileAvatarShortcutBadgeHeight); const SkBitmap& final_bitmap = offscreen_canvas->getDevice()->accessBitmap(false); // Finally, write the .ico file containing this new bitmap. FilePath icon_path = profile_path.AppendASCII(kProfileIconFileName); if (!IconUtil::CreateIconFileFromSkBitmap(final_bitmap, icon_path)) return FilePath(); return icon_path; } // Creates a desktop shortcut to open Chrome with the given profile name and // base directory. Iff |create|, create shortcut if it doesn't already exist. // Must be called on the FILE thread. void CreateChromeDesktopShortcutForProfile( const string16& profile_name, const string16& profile_base_dir, const FilePath& profile_path, const gfx::Image* avatar_image, bool create) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); FilePath chrome_exe; if (!PathService::Get(base::FILE_EXE, &chrome_exe)) return; BrowserDistribution* dist = BrowserDistribution::GetDistribution(); string16 description; if (!dist) return; else description = WideToUTF16(dist->GetAppDescription()); const string16& directory = CreateProfileShortcutSwitch(profile_base_dir); FilePath icon_path = avatar_image ? CreateChromeDesktopShortcutIconForProfile(profile_path, *avatar_image) : FilePath(); ShellUtil::CreateChromeDesktopShortcut( dist, chrome_exe.value(), description, profile_name, directory, icon_path.empty() ? chrome_exe.value() : icon_path.value(), icon_path.empty() ? dist->GetIconIndex() : 0, ShellUtil::CURRENT_USER, false, // Use alternate text. create); // Create if it doesn't already exist. } // Renames an existing Chrome desktop profile shortcut. Must be called on the // FILE thread. void RenameChromeDesktopShortcutForProfile( const string16& old_shortcut, const string16& new_shortcut) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); FilePath shortcut_path; if (ShellUtil::GetDesktopPath(false, // User's directory instead of system. &shortcut_path)) { FilePath old_profile = shortcut_path.Append(old_shortcut); FilePath new_profile = shortcut_path.Append(new_shortcut); file_util::Move(old_profile, new_profile); } } // Updates the arguments to a Chrome desktop shortcut for a profile. Must be // called on the FILE thread. void UpdateChromeDesktopShortcutForProfile( const string16& shortcut, const string16& arguments, const FilePath& profile_path, const gfx::Image* avatar_image) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); FilePath shortcut_path; if (!ShellUtil::GetDesktopPath(false, &shortcut_path)) return; shortcut_path = shortcut_path.Append(shortcut); FilePath chrome_exe; if (!PathService::Get(base::FILE_EXE, &chrome_exe)) return; BrowserDistribution* dist = BrowserDistribution::GetDistribution(); string16 description; if (!dist) return; else description = WideToUTF16(dist->GetAppDescription()); FilePath icon_path = avatar_image ? CreateChromeDesktopShortcutIconForProfile(profile_path, *avatar_image) : FilePath(); ShellUtil::UpdateChromeShortcut( dist, chrome_exe.value(), shortcut_path.value(), arguments, description, icon_path.empty() ? chrome_exe.value() : icon_path.value(), icon_path.empty() ? dist->GetIconIndex() : 0, false); } void DeleteAutoLaunchValueForProfile( const FilePath& profile_path) { if (auto_launch_util::WillLaunchAtLogin(FilePath(), profile_path.BaseName().value())) { auto_launch_util::SetWillLaunchAtLogin( false, FilePath(), profile_path.BaseName().value()); } } } // namespace ProfileShortcutManagerWin::ProfileShortcutManagerWin() { } ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { } void ProfileShortcutManagerWin::AddProfileShortcut( const FilePath& profile_path) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); size_t index = cache.GetIndexOfProfileWithPath(profile_path); if (index == std::string::npos) return; // Launch task to add shortcut to desktop on Windows. If this is the very // first profile created, don't add the user name to the shortcut. // TODO(mirandac): respect master_preferences choice to create no shortcuts // (see http://crbug.com/104463) if (g_browser_process->profile_manager()->GetNumberOfProfiles() > 1) { { // We make a copy of the Image to ensure that the underlying image data is // AddRef'd, in case the original copy gets deleted. gfx::Image* avatar_copy = new gfx::Image(cache.GetAvatarIconOfProfileAtIndex(index)); string16 profile_name = cache.GetNameOfProfileAtIndex(index); string16 profile_base_dir = profile_path.BaseName().value(); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&CreateChromeDesktopShortcutForProfile, profile_name, profile_base_dir, profile_path, base::Owned(avatar_copy), true)); } // If this is the second existing multi-user account created, change the // original shortcut use the first profile's details (name, badge, // argument). if (cache.GetNumberOfProfiles() == 2) { // Get the index of the first profile, based on the index of the second // profile. It's either 0 or 1, whichever the second profile isn't. size_t first_index = 0; if (cache.GetIndexOfProfileWithPath(profile_path) == 0) first_index = 1; string16 first_name = cache.GetNameOfProfileAtIndex(first_index); BrowserDistribution* dist = BrowserDistribution::GetDistribution(); string16 old_shortcut; string16 new_shortcut; if (ShellUtil::GetChromeShortcutName(dist, false, L"", &old_shortcut) && ShellUtil::GetChromeShortcutName(dist, false, first_name, &new_shortcut)) { // Update doesn't allow changing the target, so rename first. BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&RenameChromeDesktopShortcutForProfile, old_shortcut, new_shortcut)); // Fetch the avatar for the first profile and make a copy of the Image // to ensure that the underlying image data is AddRef'd, in case the // original copy is deleted. gfx::Image& first_avatar = ResourceBundle::GetSharedInstance().GetNativeImageNamed( ProfileInfoCache::GetDefaultAvatarIconResourceIDAtIndex( cache.GetAvatarIconIndexOfProfileAtIndex(first_index))); gfx::Image* first_avatar_copy = new gfx::Image(first_avatar); FilePath first_path = cache.GetPathOfProfileAtIndex(first_index); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&UpdateChromeDesktopShortcutForProfile, new_shortcut, CreateProfileShortcutSwitch(UTF8ToUTF16( first_path.BaseName().MaybeAsASCII())), first_path, base::Owned(first_avatar_copy))); } } } else { // Only one profile, so create original shortcut, with no avatar. BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&CreateChromeDesktopShortcutForProfile, string16(), string16(), FilePath(), static_cast(NULL), true)); } } void ProfileShortcutManagerWin::OnProfileAdded( const FilePath& profile_path) { } void ProfileShortcutManagerWin::OnProfileWillBeRemoved( const FilePath& profile_path) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); size_t index = cache.GetIndexOfProfileWithPath(profile_path); if (index == std::string::npos) return; string16 profile_name = cache.GetNameOfProfileAtIndex(index); BrowserDistribution* dist = BrowserDistribution::GetDistribution(); string16 shortcut; if (ShellUtil::GetChromeShortcutName(dist, false, profile_name, &shortcut)) { std::vector shortcuts(1, shortcut); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&CallShellUtilBoolFunction, base::Bind( &ShellUtil::RemoveChromeDesktopShortcutsWithAppendedNames, shortcuts))); } } void ProfileShortcutManagerWin::OnProfileWasRemoved( const FilePath& profile_path, const string16& profile_name) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&DeleteAutoLaunchValueForProfile, profile_path)); // If there is one profile left, we want to remove the badge and name from it. ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); if (cache.GetNumberOfProfiles() != 1) return; FilePath last_profile_path = cache.GetPathOfProfileAtIndex(0); string16 old_shortcut; string16 new_shortcut; BrowserDistribution* dist = BrowserDistribution::GetDistribution(); if (ShellUtil::GetChromeShortcutName( dist, false, cache.GetNameOfProfileAtIndex(0), &old_shortcut) && ShellUtil::GetChromeShortcutName( dist, false, L"", &new_shortcut)) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&RenameChromeDesktopShortcutForProfile, old_shortcut, new_shortcut)); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&UpdateChromeDesktopShortcutForProfile, new_shortcut, CreateProfileShortcutSwitch(UTF8ToUTF16( last_profile_path.BaseName().MaybeAsASCII())), last_profile_path, static_cast(NULL))); } } void ProfileShortcutManagerWin::OnProfileNameChanged( const FilePath& profile_path, const string16& old_profile_name) { // Launch task to change name of desktop shortcut on Windows. // TODO(mirandac): respect master_preferences choice to create no shortcuts // (see http://crbug.com/104463) ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); size_t index = cache.GetIndexOfProfileWithPath(profile_path); if (index == std::string::npos) return; string16 new_profile_name = cache.GetNameOfProfileAtIndex(index); string16 old_shortcut; string16 new_shortcut; BrowserDistribution* dist = BrowserDistribution::GetDistribution(); if (ShellUtil::GetChromeShortcutName( dist, false, old_profile_name, &old_shortcut) && ShellUtil::GetChromeShortcutName( dist, false, new_profile_name, &new_shortcut)) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&RenameChromeDesktopShortcutForProfile, old_shortcut, new_shortcut)); } } void ProfileShortcutManagerWin::OnProfileAvatarChanged( const FilePath& profile_path) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); size_t index = cache.GetIndexOfProfileWithPath(profile_path); if (index == std::string::npos) return; string16 profile_name = cache.GetNameOfProfileAtIndex(index); string16 profile_base_dir = UTF8ToUTF16(profile_path.BaseName().MaybeAsASCII()); const gfx::Image& avatar_image = cache.GetAvatarIconOfProfileAtIndex(index); // Launch task to change the icon of the desktop shortcut on windows. string16 new_shortcut; BrowserDistribution* dist = BrowserDistribution::GetDistribution(); if (ShellUtil::GetChromeShortcutName(dist, false, profile_name, &new_shortcut)) { // We make a copy of the Image to ensure that the underlying image data is // AddRef'd, in case the original copy gets deleted. gfx::Image* avatar_copy = new gfx::Image(avatar_image); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&UpdateChromeDesktopShortcutForProfile, new_shortcut, CreateProfileShortcutSwitch(profile_base_dir), profile_path, base::Owned(avatar_copy))); } } // static std::vector ProfileShortcutManagerWin::GenerateShortcutsFromProfiles( const std::vector& profile_names) { BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::vector shortcuts; shortcuts.reserve(profile_names.size()); for (std::vector::const_iterator it = profile_names.begin(); it != profile_names.end(); ++it) { string16 shortcut; if (ShellUtil::GetChromeShortcutName(dist, false, *it, &shortcut)) shortcuts.push_back(shortcut); } return shortcuts; }