app_list_controller_win.cc revision 7c720b7466665b17575e0fd6976f9321c6bff489
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 <dwmapi.h> 6#include <sstream> 7 8#include "apps/pref_names.h" 9#include "base/command_line.h" 10#include "base/file_util.h" 11#include "base/lazy_instance.h" 12#include "base/memory/singleton.h" 13#include "base/memory/weak_ptr.h" 14#include "base/path_service.h" 15#include "base/prefs/pref_service.h" 16#include "base/strings/utf_string_conversions.h" 17#include "base/threading/sequenced_worker_pool.h" 18#include "base/time/time.h" 19#include "base/timer/timer.h" 20#include "base/win/shortcut.h" 21#include "base/win/windows_version.h" 22#include "chrome/app/chrome_dll_resource.h" 23#include "chrome/browser/browser_process.h" 24#include "chrome/browser/extensions/extension_service.h" 25#include "chrome/browser/extensions/extension_system.h" 26#include "chrome/browser/lifetime/application_lifetime.h" 27#include "chrome/browser/platform_util.h" 28#include "chrome/browser/profiles/profile.h" 29#include "chrome/browser/profiles/profile_manager.h" 30#include "chrome/browser/shell_integration.h" 31#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 32#include "chrome/browser/ui/app_list/app_list_service_impl.h" 33#include "chrome/browser/ui/app_list/app_list_service_win.h" 34#include "chrome/browser/ui/app_list/app_list_view_delegate.h" 35#include "chrome/browser/ui/browser_commands.h" 36#include "chrome/browser/ui/extensions/app_metro_infobar_delegate_win.h" 37#include "chrome/browser/ui/extensions/application_launch.h" 38#include "chrome/browser/ui/views/browser_dialogs.h" 39#include "chrome/browser/web_applications/web_app.h" 40#include "chrome/common/chrome_constants.h" 41#include "chrome/common/chrome_switches.h" 42#include "chrome/common/chrome_version_info.h" 43#include "chrome/common/pref_names.h" 44#include "chrome/installer/launcher_support/chrome_launcher_support.h" 45#include "chrome/installer/util/browser_distribution.h" 46#include "chrome/installer/util/google_update_settings.h" 47#include "chrome/installer/util/install_util.h" 48#include "chrome/installer/util/util_constants.h" 49#include "content/public/browser/browser_thread.h" 50#include "grit/chromium_strings.h" 51#include "grit/generated_resources.h" 52#include "grit/google_chrome_strings.h" 53#include "ui/app_list/pagination_model.h" 54#include "ui/app_list/views/app_list_view.h" 55#include "ui/base/l10n/l10n_util.h" 56#include "ui/base/resource/resource_bundle.h" 57#include "ui/base/win/shell.h" 58#include "ui/gfx/display.h" 59#include "ui/gfx/image/image_skia.h" 60#include "ui/gfx/screen.h" 61#include "ui/views/bubble/bubble_border.h" 62#include "ui/views/widget/widget.h" 63#include "win8/util/win8_util.h" 64 65#if defined(GOOGLE_CHROME_BUILD) 66#include "chrome/installer/util/install_util.h" 67#endif 68 69#if defined(USE_AURA) 70#include "ui/aura/root_window.h" 71#include "ui/aura/window.h" 72#endif 73 74namespace { 75 76// Offset from the cursor to the point of the bubble arrow. It looks weird 77// if the arrow comes up right on top of the cursor, so it is offset by this 78// amount. 79static const int kAnchorOffset = 25; 80 81static const wchar_t kTrayClassName[] = L"Shell_TrayWnd"; 82 83// Migrate chrome::kAppLauncherIsEnabled pref to 84// chrome::kAppLauncherHasBeenEnabled pref. 85void MigrateAppLauncherEnabledPref() { 86 PrefService* prefs = g_browser_process->local_state(); 87 if (prefs->HasPrefPath(apps::prefs::kAppLauncherIsEnabled)) { 88 prefs->SetBoolean(apps::prefs::kAppLauncherHasBeenEnabled, 89 prefs->GetBoolean(apps::prefs::kAppLauncherIsEnabled)); 90 prefs->ClearPref(apps::prefs::kAppLauncherIsEnabled); 91 } 92} 93 94// Icons are added to the resources of the DLL using icon names. The icon index 95// for the app list icon is named IDR_X_APP_LIST or (for official builds) 96// IDR_X_APP_LIST_SXS for Chrome Canary. Creating shortcuts needs to specify a 97// resource index, which are different to icon names. They are 0 based and 98// contiguous. As Google Chrome builds have extra icons the icon for Google 99// Chrome builds need to be higher. Unfortunately these indexes are not in any 100// generated header file. 101int GetAppListIconIndex() { 102 const int kAppListIconIndex = 5; 103 const int kAppListIconIndexSxS = 6; 104 const int kAppListIconIndexChromium = 1; 105#if defined(GOOGLE_CHROME_BUILD) 106 if (InstallUtil::IsChromeSxSProcess()) 107 return kAppListIconIndexSxS; 108 return kAppListIconIndex; 109#else 110 return kAppListIconIndexChromium; 111#endif 112} 113 114string16 GetAppListShortcutName() { 115 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 116 if (channel == chrome::VersionInfo::CHANNEL_CANARY) 117 return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY); 118 return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME); 119} 120 121CommandLine GetAppListCommandLine() { 122 const char* const kSwitchesToCopy[] = { switches::kUserDataDir }; 123 CommandLine* current = CommandLine::ForCurrentProcess(); 124 base::FilePath chrome_exe; 125 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 126 NOTREACHED(); 127 return CommandLine(CommandLine::NO_PROGRAM); 128 } 129 CommandLine command_line(chrome_exe); 130 command_line.CopySwitchesFrom(*current, kSwitchesToCopy, 131 arraysize(kSwitchesToCopy)); 132 command_line.AppendSwitch(switches::kShowAppList); 133 return command_line; 134} 135 136string16 GetAppModelId() { 137 // The AppModelId should be the same for all profiles in a user data directory 138 // but different for different user data directories, so base it on the 139 // initial profile in the current user data directory. 140 base::FilePath initial_profile_path; 141 CommandLine* command_line = CommandLine::ForCurrentProcess(); 142 if (command_line->HasSwitch(switches::kUserDataDir)) { 143 initial_profile_path = 144 command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII( 145 chrome::kInitialProfile); 146 } 147 return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path); 148} 149 150void SetDidRunForNDayActiveStats() { 151 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 152 base::FilePath exe_path; 153 if (!PathService::Get(base::DIR_EXE, &exe_path)) { 154 NOTREACHED(); 155 return; 156 } 157 bool system_install = 158 !InstallUtil::IsPerUserInstall(exe_path.value().c_str()); 159 // Using Chrome Binary dist: Chrome dist may not exist for the legacy 160 // App Launcher, and App Launcher dist may be "shadow", which does not 161 // contain the information needed to determine multi-install. 162 // Edge case involving Canary: crbug/239163. 163 BrowserDistribution* chrome_binaries_dist = 164 BrowserDistribution::GetSpecificDistribution( 165 BrowserDistribution::CHROME_BINARIES); 166 if (chrome_binaries_dist && 167 InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) { 168 BrowserDistribution* app_launcher_dist = 169 BrowserDistribution::GetSpecificDistribution( 170 BrowserDistribution::CHROME_APP_HOST); 171 GoogleUpdateSettings::UpdateDidRunStateForDistribution( 172 app_launcher_dist, 173 true /* did_run */, 174 system_install); 175 } 176} 177 178// The start menu shortcut is created on first run by users that are 179// upgrading. The desktop and taskbar shortcuts are created the first time the 180// user enables the app list. The taskbar shortcut is created in 181// |user_data_dir| and will use a Windows Application Model Id of 182// |app_model_id|. This runs on the FILE thread and not in the blocking IO 183// thread pool as there are other tasks running (also on the FILE thread) 184// which fiddle with shortcut icons 185// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads 186// fiddle with the same shortcuts could cause race issues. 187void CreateAppListShortcuts( 188 const base::FilePath& user_data_dir, 189 const string16& app_model_id, 190 const ShellIntegration::ShortcutLocations& creation_locations) { 191 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); 192 193 // Shortcut paths under which to create shortcuts. 194 std::vector<base::FilePath> shortcut_paths = 195 web_app::internals::GetShortcutPaths(creation_locations); 196 197 bool pin_to_taskbar = creation_locations.in_quick_launch_bar && 198 (base::win::GetVersion() >= base::win::VERSION_WIN7); 199 200 // Create a shortcut in the |user_data_dir| for taskbar pinning. 201 if (pin_to_taskbar) 202 shortcut_paths.push_back(user_data_dir); 203 bool success = true; 204 205 base::FilePath chrome_exe; 206 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 207 NOTREACHED(); 208 return; 209 } 210 211 string16 app_list_shortcut_name = GetAppListShortcutName(); 212 213 string16 wide_switches(GetAppListCommandLine().GetArgumentsString()); 214 215 base::win::ShortcutProperties shortcut_properties; 216 shortcut_properties.set_target(chrome_exe); 217 shortcut_properties.set_working_dir(chrome_exe.DirName()); 218 shortcut_properties.set_arguments(wide_switches); 219 shortcut_properties.set_description(app_list_shortcut_name); 220 shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex()); 221 shortcut_properties.set_app_id(app_model_id); 222 223 for (size_t i = 0; i < shortcut_paths.size(); ++i) { 224 base::FilePath shortcut_file = 225 shortcut_paths[i].Append(app_list_shortcut_name). 226 AddExtension(installer::kLnkExt); 227 if (!base::PathExists(shortcut_file.DirName()) && 228 !file_util::CreateDirectory(shortcut_file.DirName())) { 229 NOTREACHED(); 230 return; 231 } 232 success = success && base::win::CreateOrUpdateShortcutLink( 233 shortcut_file, shortcut_properties, 234 base::win::SHORTCUT_CREATE_ALWAYS); 235 } 236 237 if (success && pin_to_taskbar) { 238 base::FilePath shortcut_to_pin = 239 user_data_dir.Append(app_list_shortcut_name). 240 AddExtension(installer::kLnkExt); 241 success = base::win::TaskbarPinShortcutLink( 242 shortcut_to_pin.value().c_str()) && success; 243 } 244} 245 246class AppListControllerDelegateWin : public AppListControllerDelegate { 247 public: 248 AppListControllerDelegateWin(); 249 virtual ~AppListControllerDelegateWin(); 250 251 private: 252 // AppListController overrides: 253 virtual void DismissView() OVERRIDE; 254 virtual void ViewClosing() OVERRIDE; 255 virtual void ViewActivationChanged(bool active) OVERRIDE; 256 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; 257 virtual gfx::ImageSkia GetWindowIcon() OVERRIDE; 258 virtual bool CanPin() OVERRIDE; 259 virtual void OnShowExtensionPrompt() OVERRIDE; 260 virtual void OnCloseExtensionPrompt() OVERRIDE; 261 virtual bool CanDoCreateShortcutsFlow(bool is_platform_app) OVERRIDE; 262 virtual void DoCreateShortcutsFlow(Profile* profile, 263 const std::string& extension_id) OVERRIDE; 264 virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE; 265 virtual void ActivateApp(Profile* profile, 266 const extensions::Extension* extension, 267 int event_flags) OVERRIDE; 268 virtual void LaunchApp(Profile* profile, 269 const extensions::Extension* extension, 270 int event_flags) OVERRIDE; 271 272 DISALLOW_COPY_AND_ASSIGN(AppListControllerDelegateWin); 273}; 274 275class ScopedKeepAlive { 276 public: 277 ScopedKeepAlive() { chrome::StartKeepAlive(); } 278 ~ScopedKeepAlive() { chrome::EndKeepAlive(); } 279 280 private: 281 DISALLOW_COPY_AND_ASSIGN(ScopedKeepAlive); 282}; 283 284class ActivationTracker { 285 public: 286 ActivationTracker(app_list::AppListView* view, 287 const base::Closure& on_active_lost) 288 : view_(view), 289 on_active_lost_(on_active_lost), 290 regain_next_lost_focus_(false), 291 preserving_focus_for_taskbar_menu_(false) { 292 } 293 294 void RegainNextLostFocus() { 295 regain_next_lost_focus_ = true; 296 } 297 298 void OnActivationChanged(bool active) { 299 const int kFocusCheckIntervalMS = 250; 300 if (active) { 301 timer_.Stop(); 302 return; 303 } 304 305 preserving_focus_for_taskbar_menu_ = false; 306 timer_.Start(FROM_HERE, 307 base::TimeDelta::FromMilliseconds(kFocusCheckIntervalMS), this, 308 &ActivationTracker::CheckTaskbarOrViewHasFocus); 309 } 310 311 void OnViewHidden() { 312 timer_.Stop(); 313 } 314 315 void CheckTaskbarOrViewHasFocus() { 316 // Remember if the taskbar had focus without the right mouse button being 317 // down. 318 bool was_preserving_focus = preserving_focus_for_taskbar_menu_; 319 preserving_focus_for_taskbar_menu_ = false; 320 321 // First get the taskbar and jump lists windows (the jump list is the 322 // context menu which the taskbar uses). 323 HWND jump_list_hwnd = FindWindow(L"DV2ControlHost", NULL); 324 HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL); 325 326 // This code is designed to hide the app launcher when it loses focus, 327 // except for the cases necessary to allow the launcher to be pinned or 328 // closed via the taskbar context menu. 329 // First work out if the left or right button is currently down. 330 int swapped = GetSystemMetrics(SM_SWAPBUTTON); 331 int left_button = swapped ? VK_RBUTTON : VK_LBUTTON; 332 bool left_button_down = GetAsyncKeyState(left_button) < 0; 333 int right_button = swapped ? VK_LBUTTON : VK_RBUTTON; 334 bool right_button_down = GetAsyncKeyState(right_button) < 0; 335 336 // Now get the window that currently has focus. 337 HWND focused_hwnd = GetForegroundWindow(); 338 if (!focused_hwnd) { 339 // Sometimes the focused window is NULL. This can happen when the focus is 340 // changing due to a mouse button press. If the button is still being 341 // pressed the launcher should not be hidden. 342 if (right_button_down || left_button_down) 343 return; 344 345 // If the focused window is NULL, and the mouse button is not being 346 // pressed, then the launcher no longer has focus. 347 on_active_lost_.Run(); 348 return; 349 } 350 351 while (focused_hwnd) { 352 // If the focused window is the right click menu (called a jump list) or 353 // the app list, don't hide the launcher. 354 if (focused_hwnd == jump_list_hwnd || 355 focused_hwnd == view_->GetHWND()) { 356 return; 357 } 358 359 if (focused_hwnd == taskbar_hwnd) { 360 // If the focused window is the taskbar, and the right button is down, 361 // don't hide the launcher as the user might be bringing up the menu. 362 if (right_button_down) 363 return; 364 365 // There is a short period between the right mouse button being down 366 // and the menu gaining focus, where the taskbar has focus and no button 367 // is down. If the taskbar is observed in this state once the launcher 368 // is not dismissed. If it happens twice in a row it is dismissed. 369 if (!was_preserving_focus) { 370 preserving_focus_for_taskbar_menu_ = true; 371 return; 372 } 373 374 break; 375 } 376 focused_hwnd = GetParent(focused_hwnd); 377 } 378 379 if (regain_next_lost_focus_) { 380 regain_next_lost_focus_ = false; 381 view_->GetWidget()->Activate(); 382 return; 383 } 384 385 // If we get here, the focused window is not the taskbar, it's context menu, 386 // or the app list. 387 on_active_lost_.Run(); 388 } 389 390 private: 391 // The window to track the active state of. 392 app_list::AppListView* view_; 393 394 // Called to request |view_| be closed. 395 base::Closure on_active_lost_; 396 397 // True if we are anticipating that the app list will lose focus, and we want 398 // to take it back. This is used when switching out of Metro mode, and the 399 // browser regains focus after showing the app list. 400 bool regain_next_lost_focus_; 401 402 // When the context menu on the app list's taskbar icon is brought up the 403 // app list should not be hidden, but it should be if the taskbar is clicked 404 // on. There can be a period of time when the taskbar gets focus between a 405 // right mouse click and the menu showing; to prevent hiding the app launcher 406 // when this happens it is kept visible if the taskbar is seen briefly without 407 // the right mouse button down, but not if this happens twice in a row. 408 bool preserving_focus_for_taskbar_menu_; 409 410 // Timer used to check if the taskbar or app list is active. Using a timer 411 // means we don't need to hook Windows, which is apparently not possible 412 // since Vista (and is not nice at any time). 413 base::RepeatingTimer<ActivationTracker> timer_; 414}; 415 416// The AppListController class manages global resources needed for the app 417// list to operate, and controls when the app list is opened and closed. 418// TODO(tapted): Rename this class to AppListServiceWin and move entire file to 419// chrome/browser/ui/app_list/app_list_service_win.cc after removing 420// chrome/browser/ui/views dependency. 421class AppListController : public AppListServiceImpl { 422 public: 423 virtual ~AppListController(); 424 425 static AppListController* GetInstance() { 426 return Singleton<AppListController, 427 LeakySingletonTraits<AppListController> >::get(); 428 } 429 430 void set_can_close(bool can_close) { can_close_app_list_ = can_close; } 431 bool can_close() { return can_close_app_list_; } 432 433 void AppListClosing(); 434 void AppListActivationChanged(bool active); 435 void ShowAppListDuringModeSwitch(Profile* requested_profile); 436 437 app_list::AppListView* GetView() { return current_view_; } 438 439 // AppListService overrides: 440 virtual void HandleFirstRun() OVERRIDE; 441 virtual void Init(Profile* initial_profile) OVERRIDE; 442 virtual void CreateForProfile(Profile* requested_profile) OVERRIDE; 443 virtual void ShowForProfile(Profile* requested_profile) OVERRIDE; 444 virtual void DismissAppList() OVERRIDE; 445 virtual bool IsAppListVisible() const OVERRIDE; 446 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; 447 virtual AppListControllerDelegate* CreateControllerDelegate() OVERRIDE; 448 449 // AppListServiceImpl overrides: 450 virtual void CreateShortcut() OVERRIDE; 451 virtual void OnSigninStatusChanged() OVERRIDE; 452 453 private: 454 friend struct DefaultSingletonTraits<AppListController>; 455 456 AppListController(); 457 458 bool IsWarmupNeeded(); 459 void ScheduleWarmup(); 460 461 // Loads the profile last used with the app list and populates the view from 462 // it without showing it so that the next show is faster. Does nothing if the 463 // view already exists, or another profile is in the middle of being loaded to 464 // be shown. 465 void LoadProfileForWarmup(); 466 void OnLoadProfileForWarmup(Profile* initial_profile); 467 468 // Creates an AppListView. 469 app_list::AppListView* CreateAppListView(); 470 471 // Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up 472 // restart params). 473 void SetWindowAttributes(HWND hwnd); 474 475 // Utility methods for showing the app list. 476 gfx::Point FindAnchorPoint(const gfx::Display& display, 477 const gfx::Point& cursor); 478 void UpdateArrowPositionAndAnchorPoint(const gfx::Point& cursor); 479 string16 GetAppListIconPath(); 480 481 // Check if the app list or the taskbar has focus. The app list is kept 482 // visible whenever either of these have focus, which allows it to be 483 // pinned but will hide it if it otherwise loses focus. This is checked 484 // periodically whenever the app list does not have focus. 485 void CheckTaskbarOrViewHasFocus(); 486 487 // Utilities to manage browser process keep alive for the view itself. Note 488 // keep alives are also used when asynchronously loading profiles. 489 void EnsureHaveKeepAliveForView(); 490 void FreeAnyKeepAliveForView(); 491 492 // Weak pointer. The view manages its own lifetime. 493 app_list::AppListView* current_view_; 494 495 scoped_ptr<ActivationTracker> activation_tracker_; 496 497 app_list::PaginationModel pagination_model_; 498 499 // True if the controller can close the app list. 500 bool can_close_app_list_; 501 502 // Used to keep the browser process alive while the app list is visible. 503 scoped_ptr<ScopedKeepAlive> keep_alive_; 504 505 bool enable_app_list_on_next_init_; 506 507 base::WeakPtrFactory<AppListController> weak_factory_; 508 509 DISALLOW_COPY_AND_ASSIGN(AppListController); 510}; 511 512AppListControllerDelegateWin::AppListControllerDelegateWin() {} 513 514AppListControllerDelegateWin::~AppListControllerDelegateWin() {} 515 516void AppListControllerDelegateWin::DismissView() { 517 AppListController::GetInstance()->DismissAppList(); 518} 519 520void AppListControllerDelegateWin::ViewActivationChanged(bool active) { 521 AppListController::GetInstance()->AppListActivationChanged(active); 522} 523 524void AppListControllerDelegateWin::ViewClosing() { 525 AppListController::GetInstance()->AppListClosing(); 526} 527 528gfx::NativeWindow AppListControllerDelegateWin::GetAppListWindow() { 529 return AppListController::GetInstance()->GetAppListWindow(); 530} 531 532gfx::ImageSkia AppListControllerDelegateWin::GetWindowIcon() { 533 gfx::ImageSkia* resource = ResourceBundle::GetSharedInstance(). 534 GetImageSkiaNamed(chrome::GetAppListIconResourceId()); 535 return *resource; 536} 537 538bool AppListControllerDelegateWin::CanPin() { 539 return false; 540} 541 542void AppListControllerDelegateWin::OnShowExtensionPrompt() { 543 AppListController::GetInstance()->set_can_close(false); 544} 545 546void AppListControllerDelegateWin::OnCloseExtensionPrompt() { 547 AppListController::GetInstance()->set_can_close(true); 548} 549 550bool AppListControllerDelegateWin::CanDoCreateShortcutsFlow( 551 bool is_platform_app) { 552 return true; 553} 554 555void AppListControllerDelegateWin::DoCreateShortcutsFlow( 556 Profile* profile, 557 const std::string& extension_id) { 558 ExtensionService* service = 559 extensions::ExtensionSystem::Get(profile)->extension_service(); 560 DCHECK(service); 561 const extensions::Extension* extension = service->GetInstalledExtension( 562 extension_id); 563 DCHECK(extension); 564 565 app_list::AppListView* view = AppListController::GetInstance()->GetView(); 566 if (!view) 567 return; 568 569 gfx::NativeWindow parent_hwnd = 570 view->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); 571 OnShowExtensionPrompt(); 572 chrome::ShowCreateChromeAppShortcutsDialog( 573 parent_hwnd, profile, extension, 574 base::Bind(&AppListControllerDelegateWin::OnCloseExtensionPrompt, 575 base::Unretained(this))); 576} 577 578void AppListControllerDelegateWin::CreateNewWindow(Profile* profile, 579 bool incognito) { 580 Profile* window_profile = incognito ? 581 profile->GetOffTheRecordProfile() : profile; 582 chrome::NewEmptyWindow(window_profile, chrome::GetActiveDesktop()); 583} 584 585void AppListControllerDelegateWin::ActivateApp( 586 Profile* profile, const extensions::Extension* extension, int event_flags) { 587 LaunchApp(profile, extension, event_flags); 588} 589 590void AppListControllerDelegateWin::LaunchApp( 591 Profile* profile, const extensions::Extension* extension, int event_flags) { 592 AppListServiceImpl::RecordAppListAppLaunch(); 593 chrome::OpenApplication(chrome::AppLaunchParams( 594 profile, extension, NEW_FOREGROUND_TAB)); 595} 596 597AppListController::AppListController() 598 : current_view_(NULL), 599 can_close_app_list_(true), 600 enable_app_list_on_next_init_(false), 601 weak_factory_(this) {} 602 603AppListController::~AppListController() { 604} 605 606gfx::NativeWindow AppListController::GetAppListWindow() { 607 if (!IsAppListVisible()) 608 return NULL; 609 return current_view_ ? current_view_->GetWidget()->GetNativeWindow() : NULL; 610} 611 612AppListControllerDelegate* AppListController::CreateControllerDelegate() { 613 return new AppListControllerDelegateWin(); 614} 615 616void AppListController::OnSigninStatusChanged() { 617 if (current_view_) 618 current_view_->OnSigninStatusChanged(); 619} 620 621void AppListController::ShowForProfile(Profile* requested_profile) { 622 DCHECK(requested_profile); 623 if (requested_profile->IsManaged()) 624 return; 625 626 ScopedKeepAlive show_app_list_keepalive; 627 628 content::BrowserThread::PostBlockingPoolTask( 629 FROM_HERE, base::Bind(SetDidRunForNDayActiveStats)); 630 631 if (win8::IsSingleWindowMetroMode()) { 632 // This request came from Windows 8 in desktop mode, but chrome is currently 633 // running in Metro mode. 634 AppMetroInfoBarDelegateWin::Create( 635 requested_profile, AppMetroInfoBarDelegateWin::SHOW_APP_LIST, 636 std::string()); 637 return; 638 } 639 640 InvalidatePendingProfileLoads(); 641 642 // If the app list is already displaying |profile| just activate it (in case 643 // we have lost focus). 644 if (IsAppListVisible() && (requested_profile == profile())) { 645 current_view_->GetWidget()->Show(); 646 current_view_->GetWidget()->Activate(); 647 return; 648 } 649 650 SetProfilePath(requested_profile->GetPath()); 651 652 DismissAppList(); 653 CreateForProfile(requested_profile); 654 655 DCHECK(current_view_); 656 EnsureHaveKeepAliveForView(); 657 gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 658 UpdateArrowPositionAndAnchorPoint(cursor); 659 current_view_->GetWidget()->Show(); 660 current_view_->GetWidget()->GetTopLevelWidget()->UpdateWindowIcon(); 661 current_view_->GetWidget()->Activate(); 662 RecordAppListLaunch(); 663} 664 665void AppListController::ShowAppListDuringModeSwitch( 666 Profile* requested_profile) { 667 ShowForProfile(requested_profile); 668 activation_tracker_->RegainNextLostFocus(); 669} 670 671void AppListController::CreateForProfile(Profile* requested_profile) { 672 // Aura has problems with layered windows and bubble delegates. The app 673 // launcher has a trick where it only hides the window when it is dismissed, 674 // reshowing it again later. This does not work with win aura for some 675 // reason. This change temporarily makes it always get recreated, only on win 676 // aura. See http://crbug.com/176186. 677#if !defined(USE_AURA) 678 if (requested_profile == profile()) 679 return; 680#endif 681 682 SetProfile(requested_profile); 683 current_view_ = CreateAppListView(); 684 activation_tracker_.reset(new ActivationTracker(current_view_, 685 base::Bind(&AppListController::DismissAppList, base::Unretained(this)))); 686} 687 688app_list::AppListView* AppListController::CreateAppListView() { 689 // The controller will be owned by the view delegate, and the delegate is 690 // owned by the app list view. The app list view manages it's own lifetime. 691 AppListViewDelegate* view_delegate = 692 new AppListViewDelegate(CreateControllerDelegate(), profile()); 693 app_list::AppListView* view = new app_list::AppListView(view_delegate); 694 gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 695 view->InitAsBubble(NULL, 696 &pagination_model_, 697 NULL, 698 cursor, 699 views::BubbleBorder::FLOAT, 700 false /* border_accepts_events */); 701 SetWindowAttributes(view->GetHWND()); 702 return view; 703} 704 705void AppListController::SetWindowAttributes(HWND hwnd) { 706 // Vista and lower do not offer pinning to the taskbar, which makes any 707 // presence on the taskbar useless. So, hide the window on the taskbar 708 // for these versions of Windows. 709 if (base::win::GetVersion() <= base::win::VERSION_VISTA) { 710 LONG_PTR ex_styles = GetWindowLongPtr(hwnd, GWL_EXSTYLE); 711 ex_styles |= WS_EX_TOOLWINDOW; 712 SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex_styles); 713 } 714 715 if (base::win::GetVersion() > base::win::VERSION_VISTA) { 716 // Disable aero peek. Without this, hovering over the taskbar popup puts 717 // Windows into a mode for switching between windows in the same 718 // application. The app list has just one window, so it is just distracting. 719 BOOL disable_value = TRUE; 720 ::DwmSetWindowAttribute(hwnd, 721 DWMWA_DISALLOW_PEEK, 722 &disable_value, 723 sizeof(disable_value)); 724 } 725 726 ui::win::SetAppIdForWindow(GetAppModelId(), hwnd); 727 CommandLine relaunch = GetAppListCommandLine(); 728 string16 app_name(GetAppListShortcutName()); 729 ui::win::SetRelaunchDetailsForWindow( 730 relaunch.GetCommandLineString(), app_name, hwnd); 731 ::SetWindowText(hwnd, app_name.c_str()); 732 string16 icon_path = GetAppListIconPath(); 733 ui::win::SetAppIconForWindow(icon_path, hwnd); 734} 735 736void AppListController::DismissAppList() { 737 if (IsAppListVisible() && can_close_app_list_) { 738 current_view_->GetWidget()->Hide(); 739 activation_tracker_->OnViewHidden(); 740 FreeAnyKeepAliveForView(); 741 } 742} 743 744void AppListController::AppListClosing() { 745 FreeAnyKeepAliveForView(); 746 current_view_ = NULL; 747 activation_tracker_.reset(); 748 SetProfile(NULL); 749} 750 751void AppListController::AppListActivationChanged(bool active) { 752 activation_tracker_->OnActivationChanged(active); 753} 754 755// Attempts to find the bounds of the Windows taskbar. Returns true on success. 756// |rect| is in screen coordinates. If the taskbar is in autohide mode and is 757// not visible, |rect| will be outside the current monitor's bounds, except for 758// one pixel of overlap where the edge of the taskbar is shown. 759bool GetTaskbarRect(gfx::Rect* rect) { 760 HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL); 761 if (!taskbar_hwnd) 762 return false; 763 764 RECT win_rect; 765 if (!GetWindowRect(taskbar_hwnd, &win_rect)) 766 return false; 767 768 *rect = gfx::Rect(win_rect); 769 return true; 770} 771 772gfx::Point FindReferencePoint(const gfx::Display& display, 773 const gfx::Point& cursor) { 774 const int kSnapDistance = 50; 775 776 // If we can't find the taskbar, snap to the bottom left. 777 // If the display size is the same as the work area, and does not contain the 778 // taskbar, either the taskbar is hidden or on another monitor, so just snap 779 // to the bottom left. 780 gfx::Rect taskbar_rect; 781 if (!GetTaskbarRect(&taskbar_rect) || 782 (display.work_area() == display.bounds() && 783 !display.work_area().Contains(taskbar_rect))) { 784 return display.work_area().bottom_left(); 785 } 786 787 // Snap to the taskbar edge. If the cursor is greater than kSnapDistance away, 788 // also move to the left (for horizontal taskbars) or top (for vertical). 789 const gfx::Rect& screen_rect = display.bounds(); 790 // First handle taskbar on bottom. 791 if (taskbar_rect.width() == screen_rect.width()) { 792 if (taskbar_rect.bottom() == screen_rect.bottom()) { 793 if (taskbar_rect.y() - cursor.y() > kSnapDistance) 794 return gfx::Point(screen_rect.x(), taskbar_rect.y()); 795 796 return gfx::Point(cursor.x(), taskbar_rect.y()); 797 } 798 799 // Now try on the top. 800 if (cursor.y() - taskbar_rect.bottom() > kSnapDistance) 801 return gfx::Point(screen_rect.x(), taskbar_rect.bottom()); 802 803 return gfx::Point(cursor.x(), taskbar_rect.bottom()); 804 } 805 806 // Now try the left. 807 if (taskbar_rect.x() == screen_rect.x()) { 808 if (cursor.x() - taskbar_rect.right() > kSnapDistance) 809 return gfx::Point(taskbar_rect.right(), screen_rect.y()); 810 811 return gfx::Point(taskbar_rect.right(), cursor.y()); 812 } 813 814 // Finally, try the right. 815 if (taskbar_rect.x() - cursor.x() > kSnapDistance) 816 return gfx::Point(taskbar_rect.x(), screen_rect.y()); 817 818 return gfx::Point(taskbar_rect.x(), cursor.y()); 819} 820 821gfx::Point AppListController::FindAnchorPoint( 822 const gfx::Display& display, 823 const gfx::Point& cursor) { 824 const int kSnapOffset = 3; 825 826 gfx::Rect bounds_rect(display.work_area()); 827 // Always subtract the taskbar area since work_area() will not subtract it if 828 // the taskbar is set to auto-hide, and the app list should never overlap the 829 // taskbar. 830 gfx::Rect taskbar_rect; 831 if (GetTaskbarRect(&taskbar_rect)) 832 bounds_rect.Subtract(taskbar_rect); 833 834 gfx::Size view_size(current_view_->GetPreferredSize()); 835 bounds_rect.Inset(view_size.width() / 2 + kSnapOffset, 836 view_size.height() / 2 + kSnapOffset); 837 838 gfx::Point anchor = FindReferencePoint(display, cursor); 839 anchor.SetToMax(bounds_rect.origin()); 840 anchor.SetToMin(bounds_rect.bottom_right()); 841 return anchor; 842} 843 844void AppListController::UpdateArrowPositionAndAnchorPoint( 845 const gfx::Point& cursor) { 846 gfx::Screen* screen = 847 gfx::Screen::GetScreenFor(current_view_->GetWidget()->GetNativeView()); 848 gfx::Display display = screen->GetDisplayNearestPoint(cursor); 849 850 current_view_->SetBubbleArrow(views::BubbleBorder::FLOAT); 851 current_view_->SetAnchorPoint(FindAnchorPoint(display, cursor)); 852} 853 854string16 AppListController::GetAppListIconPath() { 855 base::FilePath icon_path; 856 if (!PathService::Get(base::FILE_EXE, &icon_path)) { 857 NOTREACHED(); 858 return string16(); 859 } 860 861 std::stringstream ss; 862 ss << "," << GetAppListIconIndex(); 863 string16 result = icon_path.value(); 864 result.append(UTF8ToUTF16(ss.str())); 865 return result; 866} 867 868void AppListController::EnsureHaveKeepAliveForView() { 869 if (!keep_alive_) 870 keep_alive_.reset(new ScopedKeepAlive()); 871} 872 873void AppListController::FreeAnyKeepAliveForView() { 874 if (keep_alive_) 875 keep_alive_.reset(NULL); 876} 877 878void AppListController::OnLoadProfileForWarmup(Profile* initial_profile) { 879 if (!IsWarmupNeeded()) 880 return; 881 882 CreateForProfile(initial_profile); 883 current_view_->Prerender(); 884} 885 886void AppListController::HandleFirstRun() { 887 PrefService* local_state = g_browser_process->local_state(); 888 // If the app list is already enabled during first run, then the user had 889 // opted in to the app launcher before uninstalling, so we re-enable to 890 // restore shortcuts to the app list. 891 // Note we can't directly create the shortcuts here because the IO thread 892 // hasn't been created yet. 893 enable_app_list_on_next_init_ = local_state->GetBoolean( 894 apps::prefs::kAppLauncherHasBeenEnabled); 895} 896 897void AppListController::Init(Profile* initial_profile) { 898 // In non-Ash metro mode, we can not show the app list for this process, so do 899 // not bother performing Init tasks. 900 if (win8::IsSingleWindowMetroMode()) 901 return; 902 903 if (enable_app_list_on_next_init_) { 904 enable_app_list_on_next_init_ = false; 905 EnableAppList(initial_profile); 906 CreateShortcut(); 907 } 908 909 PrefService* prefs = g_browser_process->local_state(); 910 if (prefs->HasPrefPath(prefs::kRestartWithAppList) && 911 prefs->GetBoolean(prefs::kRestartWithAppList)) { 912 prefs->SetBoolean(prefs::kRestartWithAppList, false); 913 AppListController::GetInstance()-> 914 ShowAppListDuringModeSwitch(initial_profile); 915 } 916 917 // Migrate from legacy app launcher if we are on a non-canary and non-chromium 918 // build. 919#if defined(GOOGLE_CHROME_BUILD) 920 if (!InstallUtil::IsChromeSxSProcess() && 921 !chrome_launcher_support::GetAnyAppHostPath().empty()) { 922 chrome_launcher_support::InstallationState state = 923 chrome_launcher_support::GetAppLauncherInstallationState(); 924 if (state == chrome_launcher_support::NOT_INSTALLED) { 925 // If app_host.exe is found but can't be located in the registry, 926 // skip the migration as this is likely a developer build. 927 return; 928 } else if (state == chrome_launcher_support::INSTALLED_AT_SYSTEM_LEVEL) { 929 chrome_launcher_support::UninstallLegacyAppLauncher( 930 chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION); 931 } else if (state == chrome_launcher_support::INSTALLED_AT_USER_LEVEL) { 932 chrome_launcher_support::UninstallLegacyAppLauncher( 933 chrome_launcher_support::USER_LEVEL_INSTALLATION); 934 } 935 EnableAppList(initial_profile); 936 CreateShortcut(); 937 } 938#endif 939 940 // Instantiate AppListController so it listens for profile deletions. 941 AppListController::GetInstance(); 942 943 ScheduleWarmup(); 944 945 MigrateAppLauncherEnabledPref(); 946 HandleCommandLineFlags(initial_profile); 947} 948 949bool AppListController::IsAppListVisible() const { 950 return current_view_ && current_view_->GetWidget()->IsVisible(); 951} 952 953void AppListController::CreateShortcut() { 954 // Check if the app launcher shortcuts have ever been created before. 955 // Shortcuts should only be created once. If the user unpins the taskbar 956 // shortcut, they can restore it by pinning the start menu or desktop 957 // shortcut. 958 ShellIntegration::ShortcutLocations shortcut_locations; 959 shortcut_locations.on_desktop = true; 960 shortcut_locations.in_quick_launch_bar = true; 961 shortcut_locations.in_applications_menu = true; 962 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 963 shortcut_locations.applications_menu_subdir = dist->GetAppShortCutName(); 964 base::FilePath user_data_dir( 965 g_browser_process->profile_manager()->user_data_dir()); 966 967 content::BrowserThread::PostTask( 968 content::BrowserThread::FILE, 969 FROM_HERE, 970 base::Bind(&CreateAppListShortcuts, 971 user_data_dir, GetAppModelId(), shortcut_locations)); 972} 973 974void AppListController::ScheduleWarmup() { 975 // Post a task to create the app list. This is posted to not impact startup 976 // time. 977 const int kInitWindowDelay = 5; 978 base::MessageLoop::current()->PostDelayedTask( 979 FROM_HERE, 980 base::Bind(&AppListController::LoadProfileForWarmup, 981 weak_factory_.GetWeakPtr()), 982 base::TimeDelta::FromSeconds(kInitWindowDelay)); 983 984 // Send app list usage stats after a delay. 985 const int kSendUsageStatsDelay = 5; 986 base::MessageLoop::current()->PostDelayedTask( 987 FROM_HERE, 988 base::Bind(&AppListController::SendAppListStats), 989 base::TimeDelta::FromSeconds(kSendUsageStatsDelay)); 990} 991 992bool AppListController::IsWarmupNeeded() { 993 if (!g_browser_process || g_browser_process->IsShuttingDown()) 994 return false; 995 996 // We only need to initialize the view if there's no view already created and 997 // there's no profile loading to be shown. 998 return !current_view_ && !profile_loader().IsAnyProfileLoading(); 999} 1000 1001void AppListController::LoadProfileForWarmup() { 1002 if (!IsWarmupNeeded()) 1003 return; 1004 1005 ProfileManager* profile_manager = g_browser_process->profile_manager(); 1006 base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir())); 1007 1008 profile_loader().LoadProfileInvalidatingOtherLoads( 1009 profile_path, 1010 base::Bind(&AppListController::OnLoadProfileForWarmup, 1011 weak_factory_.GetWeakPtr())); 1012} 1013 1014} // namespace 1015 1016namespace chrome { 1017 1018AppListService* GetAppListServiceWin() { 1019 return AppListController::GetInstance(); 1020} 1021 1022} // namespace chrome 1023