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 74 namespace { 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. 79 static const int kAnchorOffset = 25; 80 81 static const wchar_t kTrayClassName[] = L"Shell_TrayWnd"; 82 83 // Migrate chrome::kAppLauncherIsEnabled pref to 84 // chrome::kAppLauncherHasBeenEnabled pref. 85 void 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. 101 int 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 114 string16 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 121 CommandLine 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 136 string16 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 150 void 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. 187 void 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 246 class 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 275 class ScopedKeepAlive { 276 public: 277 ScopedKeepAlive() { chrome::StartKeepAlive(); } 278 ~ScopedKeepAlive() { chrome::EndKeepAlive(); } 279 280 private: 281 DISALLOW_COPY_AND_ASSIGN(ScopedKeepAlive); 282 }; 283 284 class 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. 421 class 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 512 AppListControllerDelegateWin::AppListControllerDelegateWin() {} 513 514 AppListControllerDelegateWin::~AppListControllerDelegateWin() {} 515 516 void AppListControllerDelegateWin::DismissView() { 517 AppListController::GetInstance()->DismissAppList(); 518 } 519 520 void AppListControllerDelegateWin::ViewActivationChanged(bool active) { 521 AppListController::GetInstance()->AppListActivationChanged(active); 522 } 523 524 void AppListControllerDelegateWin::ViewClosing() { 525 AppListController::GetInstance()->AppListClosing(); 526 } 527 528 gfx::NativeWindow AppListControllerDelegateWin::GetAppListWindow() { 529 return AppListController::GetInstance()->GetAppListWindow(); 530 } 531 532 gfx::ImageSkia AppListControllerDelegateWin::GetWindowIcon() { 533 gfx::ImageSkia* resource = ResourceBundle::GetSharedInstance(). 534 GetImageSkiaNamed(chrome::GetAppListIconResourceId()); 535 return *resource; 536 } 537 538 bool AppListControllerDelegateWin::CanPin() { 539 return false; 540 } 541 542 void AppListControllerDelegateWin::OnShowExtensionPrompt() { 543 AppListController::GetInstance()->set_can_close(false); 544 } 545 546 void AppListControllerDelegateWin::OnCloseExtensionPrompt() { 547 AppListController::GetInstance()->set_can_close(true); 548 } 549 550 bool AppListControllerDelegateWin::CanDoCreateShortcutsFlow( 551 bool is_platform_app) { 552 return true; 553 } 554 555 void 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 578 void 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 585 void AppListControllerDelegateWin::ActivateApp( 586 Profile* profile, const extensions::Extension* extension, int event_flags) { 587 LaunchApp(profile, extension, event_flags); 588 } 589 590 void 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 597 AppListController::AppListController() 598 : current_view_(NULL), 599 can_close_app_list_(true), 600 enable_app_list_on_next_init_(false), 601 weak_factory_(this) {} 602 603 AppListController::~AppListController() { 604 } 605 606 gfx::NativeWindow AppListController::GetAppListWindow() { 607 if (!IsAppListVisible()) 608 return NULL; 609 return current_view_ ? current_view_->GetWidget()->GetNativeWindow() : NULL; 610 } 611 612 AppListControllerDelegate* AppListController::CreateControllerDelegate() { 613 return new AppListControllerDelegateWin(); 614 } 615 616 void AppListController::OnSigninStatusChanged() { 617 if (current_view_) 618 current_view_->OnSigninStatusChanged(); 619 } 620 621 void 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 665 void AppListController::ShowAppListDuringModeSwitch( 666 Profile* requested_profile) { 667 ShowForProfile(requested_profile); 668 activation_tracker_->RegainNextLostFocus(); 669 } 670 671 void 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 688 app_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 705 void 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 736 void AppListController::DismissAppList() { 737 if (IsAppListVisible() && can_close_app_list_) { 738 current_view_->GetWidget()->Hide(); 739 activation_tracker_->OnViewHidden(); 740 FreeAnyKeepAliveForView(); 741 } 742 } 743 744 void AppListController::AppListClosing() { 745 FreeAnyKeepAliveForView(); 746 current_view_ = NULL; 747 activation_tracker_.reset(); 748 SetProfile(NULL); 749 } 750 751 void 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. 759 bool 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 772 gfx::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 // Note on Windows 8 the work area won't include split windows on the left or 792 // right, and neither will |taskbar_rect|. 793 if (taskbar_rect.width() == display.work_area().width()) { 794 if (taskbar_rect.bottom() == screen_rect.bottom()) { 795 if (taskbar_rect.y() - cursor.y() > kSnapDistance) 796 return gfx::Point(screen_rect.x(), taskbar_rect.y()); 797 798 return gfx::Point(cursor.x(), taskbar_rect.y()); 799 } 800 801 // Now try on the top. 802 if (cursor.y() - taskbar_rect.bottom() > kSnapDistance) 803 return gfx::Point(screen_rect.x(), taskbar_rect.bottom()); 804 805 return gfx::Point(cursor.x(), taskbar_rect.bottom()); 806 } 807 808 // Now try the left. 809 if (taskbar_rect.x() == screen_rect.x()) { 810 if (cursor.x() - taskbar_rect.right() > kSnapDistance) 811 return gfx::Point(taskbar_rect.right(), screen_rect.y()); 812 813 return gfx::Point(taskbar_rect.right(), cursor.y()); 814 } 815 816 // Finally, try the right. 817 if (taskbar_rect.x() - cursor.x() > kSnapDistance) 818 return gfx::Point(taskbar_rect.x(), screen_rect.y()); 819 820 return gfx::Point(taskbar_rect.x(), cursor.y()); 821 } 822 823 gfx::Point AppListController::FindAnchorPoint( 824 const gfx::Display& display, 825 const gfx::Point& cursor) { 826 const int kSnapOffset = 3; 827 828 gfx::Rect bounds_rect(display.work_area()); 829 // Always subtract the taskbar area since work_area() will not subtract it if 830 // the taskbar is set to auto-hide, and the app list should never overlap the 831 // taskbar. 832 gfx::Rect taskbar_rect; 833 if (GetTaskbarRect(&taskbar_rect)) 834 bounds_rect.Subtract(taskbar_rect); 835 836 gfx::Size view_size(current_view_->GetPreferredSize()); 837 bounds_rect.Inset(view_size.width() / 2 + kSnapOffset, 838 view_size.height() / 2 + kSnapOffset); 839 840 gfx::Point anchor = FindReferencePoint(display, cursor); 841 anchor.SetToMax(bounds_rect.origin()); 842 anchor.SetToMin(bounds_rect.bottom_right()); 843 return anchor; 844 } 845 846 void AppListController::UpdateArrowPositionAndAnchorPoint( 847 const gfx::Point& cursor) { 848 gfx::Screen* screen = 849 gfx::Screen::GetScreenFor(current_view_->GetWidget()->GetNativeView()); 850 gfx::Display display = screen->GetDisplayNearestPoint(cursor); 851 852 current_view_->SetBubbleArrow(views::BubbleBorder::FLOAT); 853 current_view_->SetAnchorPoint(FindAnchorPoint(display, cursor)); 854 } 855 856 string16 AppListController::GetAppListIconPath() { 857 base::FilePath icon_path; 858 if (!PathService::Get(base::FILE_EXE, &icon_path)) { 859 NOTREACHED(); 860 return string16(); 861 } 862 863 std::stringstream ss; 864 ss << "," << GetAppListIconIndex(); 865 string16 result = icon_path.value(); 866 result.append(UTF8ToUTF16(ss.str())); 867 return result; 868 } 869 870 void AppListController::EnsureHaveKeepAliveForView() { 871 if (!keep_alive_) 872 keep_alive_.reset(new ScopedKeepAlive()); 873 } 874 875 void AppListController::FreeAnyKeepAliveForView() { 876 if (keep_alive_) 877 keep_alive_.reset(NULL); 878 } 879 880 void AppListController::OnLoadProfileForWarmup(Profile* initial_profile) { 881 if (!IsWarmupNeeded()) 882 return; 883 884 CreateForProfile(initial_profile); 885 current_view_->Prerender(); 886 } 887 888 void AppListController::HandleFirstRun() { 889 PrefService* local_state = g_browser_process->local_state(); 890 // If the app list is already enabled during first run, then the user had 891 // opted in to the app launcher before uninstalling, so we re-enable to 892 // restore shortcuts to the app list. 893 // Note we can't directly create the shortcuts here because the IO thread 894 // hasn't been created yet. 895 enable_app_list_on_next_init_ = local_state->GetBoolean( 896 apps::prefs::kAppLauncherHasBeenEnabled); 897 } 898 899 void AppListController::Init(Profile* initial_profile) { 900 // In non-Ash metro mode, we can not show the app list for this process, so do 901 // not bother performing Init tasks. 902 if (win8::IsSingleWindowMetroMode()) 903 return; 904 905 if (enable_app_list_on_next_init_) { 906 enable_app_list_on_next_init_ = false; 907 EnableAppList(initial_profile); 908 CreateShortcut(); 909 } 910 911 PrefService* prefs = g_browser_process->local_state(); 912 if (prefs->HasPrefPath(prefs::kRestartWithAppList) && 913 prefs->GetBoolean(prefs::kRestartWithAppList)) { 914 prefs->SetBoolean(prefs::kRestartWithAppList, false); 915 AppListController::GetInstance()-> 916 ShowAppListDuringModeSwitch(initial_profile); 917 } 918 919 // Migrate from legacy app launcher if we are on a non-canary and non-chromium 920 // build. 921 #if defined(GOOGLE_CHROME_BUILD) 922 if (!InstallUtil::IsChromeSxSProcess() && 923 !chrome_launcher_support::GetAnyAppHostPath().empty()) { 924 chrome_launcher_support::InstallationState state = 925 chrome_launcher_support::GetAppLauncherInstallationState(); 926 if (state == chrome_launcher_support::NOT_INSTALLED) { 927 // If app_host.exe is found but can't be located in the registry, 928 // skip the migration as this is likely a developer build. 929 return; 930 } else if (state == chrome_launcher_support::INSTALLED_AT_SYSTEM_LEVEL) { 931 chrome_launcher_support::UninstallLegacyAppLauncher( 932 chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION); 933 } else if (state == chrome_launcher_support::INSTALLED_AT_USER_LEVEL) { 934 chrome_launcher_support::UninstallLegacyAppLauncher( 935 chrome_launcher_support::USER_LEVEL_INSTALLATION); 936 } 937 EnableAppList(initial_profile); 938 CreateShortcut(); 939 } 940 #endif 941 942 // Instantiate AppListController so it listens for profile deletions. 943 AppListController::GetInstance(); 944 945 ScheduleWarmup(); 946 947 MigrateAppLauncherEnabledPref(); 948 HandleCommandLineFlags(initial_profile); 949 } 950 951 bool AppListController::IsAppListVisible() const { 952 return current_view_ && current_view_->GetWidget()->IsVisible(); 953 } 954 955 void AppListController::CreateShortcut() { 956 // Check if the app launcher shortcuts have ever been created before. 957 // Shortcuts should only be created once. If the user unpins the taskbar 958 // shortcut, they can restore it by pinning the start menu or desktop 959 // shortcut. 960 ShellIntegration::ShortcutLocations shortcut_locations; 961 shortcut_locations.on_desktop = true; 962 shortcut_locations.in_quick_launch_bar = true; 963 shortcut_locations.in_applications_menu = true; 964 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 965 shortcut_locations.applications_menu_subdir = dist->GetAppShortCutName(); 966 base::FilePath user_data_dir( 967 g_browser_process->profile_manager()->user_data_dir()); 968 969 content::BrowserThread::PostTask( 970 content::BrowserThread::FILE, 971 FROM_HERE, 972 base::Bind(&CreateAppListShortcuts, 973 user_data_dir, GetAppModelId(), shortcut_locations)); 974 } 975 976 void AppListController::ScheduleWarmup() { 977 // Post a task to create the app list. This is posted to not impact startup 978 // time. 979 const int kInitWindowDelay = 5; 980 base::MessageLoop::current()->PostDelayedTask( 981 FROM_HERE, 982 base::Bind(&AppListController::LoadProfileForWarmup, 983 weak_factory_.GetWeakPtr()), 984 base::TimeDelta::FromSeconds(kInitWindowDelay)); 985 986 // Send app list usage stats after a delay. 987 const int kSendUsageStatsDelay = 5; 988 base::MessageLoop::current()->PostDelayedTask( 989 FROM_HERE, 990 base::Bind(&AppListController::SendAppListStats), 991 base::TimeDelta::FromSeconds(kSendUsageStatsDelay)); 992 } 993 994 bool AppListController::IsWarmupNeeded() { 995 if (!g_browser_process || g_browser_process->IsShuttingDown()) 996 return false; 997 998 // We only need to initialize the view if there's no view already created and 999 // there's no profile loading to be shown. 1000 return !current_view_ && !profile_loader().IsAnyProfileLoading(); 1001 } 1002 1003 void AppListController::LoadProfileForWarmup() { 1004 if (!IsWarmupNeeded()) 1005 return; 1006 1007 ProfileManager* profile_manager = g_browser_process->profile_manager(); 1008 base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir())); 1009 1010 profile_loader().LoadProfileInvalidatingOtherLoads( 1011 profile_path, 1012 base::Bind(&AppListController::OnLoadProfileForWarmup, 1013 weak_factory_.GetWeakPtr())); 1014 } 1015 1016 } // namespace 1017 1018 namespace chrome { 1019 1020 AppListService* GetAppListServiceWin() { 1021 return AppListController::GetInstance(); 1022 } 1023 1024 } // namespace chrome 1025