app_list_service_mac.mm revision 116680a4aac90f2aa7413d9095a592090648e557
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#import "chrome/browser/ui/app_list/app_list_service_mac.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <ApplicationServices/ApplicationServices.h> 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import <Cocoa/Cocoa.h> 9f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/bind.h" 1146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "base/command_line.h" 124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/file_util.h" 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/lazy_instance.h" 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/mac/mac_util.h" 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/memory/singleton.h" 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/message_loop/message_loop.h" 1703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/prefs/pref_service.h" 1803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#import "chrome/browser/app_controller_mac.h" 1903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/browser/browser_process.h" 2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/browser/extensions/extension_service.h" 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/profiles/profile_manager.h" 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h" 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_positioner.h" 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service.h" 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service_impl.h" 262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_util.h" 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_view_delegate.h" 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/browser_commands.h" 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/extensions/application_launch.h" 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/web_applications/web_app.h" 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/web_applications/web_app_mac.h" 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/chrome_switches.h" 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/chrome_version_info.h" 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/mac/app_mode_common.h" 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/pref_names.h" 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/browser_thread.h" 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "extensions/browser/extension_system.h" 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "extensions/common/manifest_handlers/file_handler_info.h" 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/chrome_unscaled_resources.h" 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/google_chrome_strings.h" 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "net/base/url_util.h" 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_view_controller.h" 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_window_controller.h" 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/app_list/search_box_model.h" 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h" 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "ui/gfx/display.h" 4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "ui/gfx/screen.h" 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace gfx { 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class ImageSkia; 532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 5446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) 552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Controller for animations that show or hide the app list. 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@interface AppListAnimationController : NSObject<NSAnimationDelegate> { 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @private 582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // When closing, the window to close. Retained until the animation ends. 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::scoped_nsobject<NSWindow> window_; 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // The animation started and owned by |self|. Reset when the animation ends. 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::scoped_nsobject<NSViewAnimation> animation_; 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) 64// Returns whether |window_| is scheduled to be closed when the animation ends. 65- (BOOL)isClosing; 66 67// Animate |window| to show or close it, after cancelling any current animation. 68// Translates from the current location to |targetOrigin| and fades in or out. 69- (void)animateWindow:(NSWindow*)window 70 targetOrigin:(NSPoint)targetOrigin 71 closing:(BOOL)closing; 72 73// Called on the UI thread once the animation has completed to reset the 74// animation state, close the window (if it is a close animation), and possibly 75// terminate Chrome. 76- (void)cleanupOnUIThread; 77 78@end 79 80namespace { 81 82// Version of the app list shortcut version installed. 83const int kShortcutVersion = 2; 84 85// Duration of show and hide animations. 86const NSTimeInterval kAnimationDuration = 0.2; 87 88// Distance towards the screen edge that the app list moves from when showing. 89const CGFloat kDistanceMovedOnShow = 20; 90 91web_app::ShortcutInfo GetAppListShortcutInfo( 92 const base::FilePath& profile_path) { 93 web_app::ShortcutInfo shortcut_info; 94 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 95 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 96 shortcut_info.title = 97 l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY); 98 } else { 99 shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME); 100 } 101 102 shortcut_info.extension_id = app_mode::kAppListModeId; 103 shortcut_info.description = shortcut_info.title; 104 shortcut_info.profile_path = profile_path; 105 106 return shortcut_info; 107} 108 109void CreateAppListShim(const base::FilePath& profile_path) { 110 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 111 WebApplicationInfo web_app_info; 112 web_app::ShortcutInfo shortcut_info = 113 GetAppListShortcutInfo(profile_path); 114 115 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance(); 116 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 117 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 118#if defined(GOOGLE_CHROME_BUILD) 119 shortcut_info.favicon.Add( 120 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16)); 121 shortcut_info.favicon.Add( 122 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32)); 123 shortcut_info.favicon.Add( 124 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128)); 125 shortcut_info.favicon.Add( 126 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256)); 127#else 128 NOTREACHED(); 129#endif 130 } else { 131 shortcut_info.favicon.Add( 132 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16)); 133 shortcut_info.favicon.Add( 134 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32)); 135 shortcut_info.favicon.Add( 136 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128)); 137 shortcut_info.favicon.Add( 138 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256)); 139 } 140 141 web_app::ShortcutLocations shortcut_locations; 142 PrefService* local_state = g_browser_process->local_state(); 143 int installed_version = 144 local_state->GetInteger(prefs::kAppLauncherShortcutVersion); 145 146 // If this is a first-time install, add a dock icon. Otherwise just update 147 // the target, and wait for OSX to refresh its icon caches. This might not 148 // occur until a reboot, but OSX does not offer a nicer way. Deleting cache 149 // files on disk and killing processes can easily result in icon corruption. 150 if (installed_version == 0) 151 shortcut_locations.in_quick_launch_bar = true; 152 153 web_app::CreateShortcutsWithInfo(web_app::SHORTCUT_CREATION_AUTOMATED, 154 shortcut_locations, 155 shortcut_info, 156 extensions::FileHandlersInfo()); 157 158 local_state->SetInteger(prefs::kAppLauncherShortcutVersion, 159 kShortcutVersion); 160} 161 162NSRunningApplication* ActiveApplicationNotChrome() { 163 NSArray* applications = [[NSWorkspace sharedWorkspace] runningApplications]; 164 for (NSRunningApplication* application in applications) { 165 if (![application isActive]) 166 continue; 167 168 if ([application isEqual:[NSRunningApplication currentApplication]]) 169 return nil; // Chrome is active. 170 171 return application; 172 } 173 174 return nil; 175} 176 177// Determines which screen edge the dock is aligned to. 178AppListPositioner::ScreenEdge DockLocationInDisplay( 179 const gfx::Display& display) { 180 // Assume the dock occupies part of the work area either on the left, right or 181 // bottom of the display. Note in the autohide case, it is always 4 pixels. 182 const gfx::Rect work_area = display.work_area(); 183 const gfx::Rect display_bounds = display.bounds(); 184 if (work_area.bottom() != display_bounds.bottom()) 185 return AppListPositioner::SCREEN_EDGE_BOTTOM; 186 187 if (work_area.x() != display_bounds.x()) 188 return AppListPositioner::SCREEN_EDGE_LEFT; 189 190 if (work_area.right() != display_bounds.right()) 191 return AppListPositioner::SCREEN_EDGE_RIGHT; 192 193 return AppListPositioner::SCREEN_EDGE_UNKNOWN; 194} 195 196// If |display|'s work area is too close to its boundary on |dock_edge|, adjust 197// the work area away from the edge by a constant amount to reduce overlap and 198// ensure the dock icon can still be clicked to dismiss the app list. 199void AdjustWorkAreaForDock(const gfx::Display& display, 200 AppListPositioner* positioner, 201 AppListPositioner::ScreenEdge dock_edge) { 202 const int kAutohideDockThreshold = 10; 203 const int kExtraDistance = 50; // A dock with 40 items is about this size. 204 205 const gfx::Rect work_area = display.work_area(); 206 const gfx::Rect display_bounds = display.bounds(); 207 208 switch (dock_edge) { 209 case AppListPositioner::SCREEN_EDGE_LEFT: 210 if (work_area.x() - display_bounds.x() <= kAutohideDockThreshold) 211 positioner->WorkAreaInset(kExtraDistance, 0, 0, 0); 212 break; 213 case AppListPositioner::SCREEN_EDGE_RIGHT: 214 if (display_bounds.right() - work_area.right() <= kAutohideDockThreshold) 215 positioner->WorkAreaInset(0, 0, kExtraDistance, 0); 216 break; 217 case AppListPositioner::SCREEN_EDGE_BOTTOM: 218 if (display_bounds.bottom() - work_area.bottom() <= 219 kAutohideDockThreshold) { 220 positioner->WorkAreaInset(0, 0, 0, kExtraDistance); 221 } 222 break; 223 case AppListPositioner::SCREEN_EDGE_UNKNOWN: 224 case AppListPositioner::SCREEN_EDGE_TOP: 225 NOTREACHED(); 226 break; 227 } 228} 229 230void GetAppListWindowOrigins( 231 NSWindow* window, NSPoint* target_origin, NSPoint* start_origin) { 232 gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]); 233 // Ensure y coordinates are flipped back into AppKit's coordinate system. 234 bool cursor_is_visible = CGCursorIsVisible(); 235 gfx::Display display; 236 gfx::Point cursor; 237 if (!cursor_is_visible) { 238 // If Chrome is the active application, display on the same display as 239 // Chrome's keyWindow since this will catch activations triggered, e.g, via 240 // WebStore install. If another application is active, OSX doesn't provide a 241 // reliable way to get the display in use. Fall back to the primary display 242 // since it has the menu bar and is likely to be correct, e.g., for 243 // activations from Spotlight. 244 const gfx::NativeView key_view = [[NSApp keyWindow] contentView]; 245 display = key_view && [NSApp isActive] ? 246 screen->GetDisplayNearestWindow(key_view) : 247 screen->GetPrimaryDisplay(); 248 } else { 249 cursor = screen->GetCursorScreenPoint(); 250 display = screen->GetDisplayNearestPoint(cursor); 251 } 252 253 const NSSize ns_window_size = [window frame].size; 254 gfx::Size window_size(ns_window_size.width, ns_window_size.height); 255 int primary_display_height = 256 NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]); 257 AppListServiceMac::FindAnchorPoint(window_size, 258 display, 259 primary_display_height, 260 cursor_is_visible, 261 cursor, 262 target_origin, 263 start_origin); 264} 265 266} // namespace 267 268AppListServiceMac::AppListServiceMac() 269 : profile_(NULL), 270 controller_delegate_(new AppListControllerDelegateImpl(this)) { 271 animation_controller_.reset([[AppListAnimationController alloc] init]); 272} 273 274AppListServiceMac::~AppListServiceMac() {} 275 276// static 277AppListServiceMac* AppListServiceMac::GetInstance() { 278 return Singleton<AppListServiceMac, 279 LeakySingletonTraits<AppListServiceMac> >::get(); 280} 281 282// static 283void AppListServiceMac::FindAnchorPoint(const gfx::Size& window_size, 284 const gfx::Display& display, 285 int primary_display_height, 286 bool cursor_is_visible, 287 const gfx::Point& cursor, 288 NSPoint* target_origin, 289 NSPoint* start_origin) { 290 AppListPositioner positioner(display, window_size, 0); 291 AppListPositioner::ScreenEdge dock_location = DockLocationInDisplay(display); 292 293 gfx::Point anchor; 294 // Snap to the dock edge. If the cursor is greater than the window 295 // width/height away or not visible, anchor to the center of the dock. 296 // Otherwise, anchor to the cursor position. 297 if (dock_location == AppListPositioner::SCREEN_EDGE_UNKNOWN) { 298 anchor = positioner.GetAnchorPointForScreenCorner( 299 AppListPositioner::SCREEN_CORNER_BOTTOM_LEFT); 300 } else { 301 int snap_distance = 302 dock_location == AppListPositioner::SCREEN_EDGE_BOTTOM || 303 dock_location == AppListPositioner::SCREEN_EDGE_TOP ? 304 window_size.height() : 305 window_size.width(); 306 // Subtract the dock area since the display's default work_area will not 307 // subtract it if the dock is set to auto-hide, and the app list should 308 // never overlap the dock. 309 AdjustWorkAreaForDock(display, &positioner, dock_location); 310 if (!cursor_is_visible || positioner.GetCursorDistanceFromShelf( 311 dock_location, cursor) > snap_distance) { 312 anchor = positioner.GetAnchorPointForShelfCenter(dock_location); 313 } else { 314 anchor = positioner.GetAnchorPointForShelfCursor(dock_location, cursor); 315 } 316 } 317 318 *target_origin = NSMakePoint( 319 anchor.x() - window_size.width() / 2, 320 primary_display_height - anchor.y() - window_size.height() / 2); 321 *start_origin = *target_origin; 322 323 // If the launcher is anchored to the dock (regardless of whether the cursor 324 // is visible), animate in inwards from the edge of screen 325 switch (dock_location) { 326 case AppListPositioner::SCREEN_EDGE_UNKNOWN: 327 break; 328 case AppListPositioner::SCREEN_EDGE_LEFT: 329 start_origin->x -= kDistanceMovedOnShow; 330 break; 331 case AppListPositioner::SCREEN_EDGE_RIGHT: 332 start_origin->x += kDistanceMovedOnShow; 333 break; 334 case AppListPositioner::SCREEN_EDGE_TOP: 335 NOTREACHED(); 336 break; 337 case AppListPositioner::SCREEN_EDGE_BOTTOM: 338 start_origin->y -= kDistanceMovedOnShow; 339 break; 340 } 341} 342 343void AppListServiceMac::Init(Profile* initial_profile) { 344 // On Mac, Init() is called multiple times for a process: any time there is no 345 // browser window open and a new window is opened, and during process startup 346 // to handle the silent launch case (e.g. for app shims). In the startup case, 347 // a profile has not yet been determined so |initial_profile| will be NULL. 348 static bool init_called_with_profile = false; 349 if (initial_profile && !init_called_with_profile) { 350 init_called_with_profile = true; 351 PerformStartupChecks(initial_profile); 352 PrefService* local_state = g_browser_process->local_state(); 353 if (!IsAppLauncherEnabled()) { 354 local_state->SetInteger(prefs::kAppLauncherShortcutVersion, 0); 355 } else { 356 int installed_shortcut_version = 357 local_state->GetInteger(prefs::kAppLauncherShortcutVersion); 358 359 if (kShortcutVersion > installed_shortcut_version) 360 CreateShortcut(); 361 } 362 } 363 364 static bool init_called = false; 365 if (init_called) 366 return; 367 368 init_called = true; 369 apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId, 370 AppListServiceMac::GetInstance()); 371 372 // Handle the case where Chrome was not running and was started with the app 373 // launcher shim. The profile has not yet been loaded. To improve response 374 // times, start animating an empty window which will be populated via 375 // OnShimLaunch(). Note that if --silent-launch is not also passed, the window 376 // will instead populate via StartupBrowserCreator::Launch(). Shim-initiated 377 // launches will always have --silent-launch. 378 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList)) 379 ShowWindowNearDock(); 380} 381 382Profile* AppListServiceMac::GetCurrentAppListProfile() { 383 return profile_; 384} 385 386void AppListServiceMac::CreateForProfile(Profile* requested_profile) { 387 if (profile_ == requested_profile) 388 return; 389 390 profile_ = requested_profile; 391 392 if (!window_controller_) 393 window_controller_.reset([[AppListWindowController alloc] init]); 394 395 scoped_ptr<app_list::AppListViewDelegate> delegate( 396 new AppListViewDelegate(profile_, GetControllerDelegate())); 397 [[window_controller_ appListViewController] setDelegate:delegate.Pass()]; 398} 399 400void AppListServiceMac::ShowForProfile(Profile* requested_profile) { 401 if (requested_profile->IsSupervised()) 402 return; 403 404 InvalidatePendingProfileLoads(); 405 406 if (requested_profile == profile_) { 407 ShowWindowNearDock(); 408 return; 409 } 410 411 SetProfilePath(requested_profile->GetPath()); 412 CreateForProfile(requested_profile); 413 ShowWindowNearDock(); 414} 415 416void AppListServiceMac::DismissAppList() { 417 if (!IsAppListVisible()) 418 return; 419 420 // If the app list is currently the main window, it will activate the next 421 // Chrome window when dismissed. But if a different application was active 422 // when the app list was shown, activate that instead. 423 base::scoped_nsobject<NSRunningApplication> prior_app; 424 if ([[window_controller_ window] isMainWindow]) 425 prior_app.swap(previously_active_application_); 426 else 427 previously_active_application_.reset(); 428 429 // If activation is successful, the app list will lose main status and try to 430 // close itself again. It can't be closed in this runloop iteration without 431 // OSX deciding to raise the next Chrome window, and _then_ activating the 432 // application on top. This also occurs if no activation option is given. 433 if ([prior_app activateWithOptions:NSApplicationActivateIgnoringOtherApps]) 434 return; 435 436 [animation_controller_ animateWindow:[window_controller_ window] 437 targetOrigin:last_start_origin_ 438 closing:YES]; 439} 440 441bool AppListServiceMac::IsAppListVisible() const { 442 return [[window_controller_ window] isVisible] && 443 ![animation_controller_ isClosing]; 444} 445 446void AppListServiceMac::EnableAppList(Profile* initial_profile, 447 AppListEnableSource enable_source) { 448 AppListServiceImpl::EnableAppList(initial_profile, enable_source); 449 AppController* controller = [NSApp delegate]; 450 [controller initAppShimMenuController]; 451} 452 453void AppListServiceMac::CreateShortcut() { 454 CreateAppListShim(GetProfilePath( 455 g_browser_process->profile_manager()->user_data_dir())); 456} 457 458NSWindow* AppListServiceMac::GetAppListWindow() { 459 return [window_controller_ window]; 460} 461 462AppListControllerDelegate* AppListServiceMac::GetControllerDelegate() { 463 return controller_delegate_.get(); 464} 465 466void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host, 467 apps::AppShimLaunchType launch_type, 468 const std::vector<base::FilePath>& files) { 469 if (profile_ && IsAppListVisible()) { 470 DismissAppList(); 471 } else { 472 // Start by showing a possibly empty window to handle the case where Chrome 473 // is running, but hasn't yet loaded the app launcher profile. 474 ShowWindowNearDock(); 475 Show(); 476 } 477 478 // Always close the shim process immediately. 479 host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_DUPLICATE_HOST); 480} 481 482void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {} 483 484void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host, 485 apps::AppShimFocusType focus_type, 486 const std::vector<base::FilePath>& files) {} 487 488void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host, 489 bool hidden) {} 490 491void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {} 492 493void AppListServiceMac::ShowWindowNearDock() { 494 if (IsAppListVisible()) 495 return; 496 497 if (!window_controller_) { 498 // Note that this will start showing an unpopulated window, the caller needs 499 // to ensure it will be populated later. 500 window_controller_.reset([[AppListWindowController alloc] init]); 501 } 502 503 NSWindow* window = GetAppListWindow(); 504 DCHECK(window); 505 NSPoint target_origin; 506 GetAppListWindowOrigins(window, &target_origin, &last_start_origin_); 507 [window setFrameOrigin:last_start_origin_]; 508 509 // Before activating, see if an application other than Chrome is currently the 510 // active application, so that it can be reactivated when dismissing. 511 previously_active_application_.reset([ActiveApplicationNotChrome() retain]); 512 513 [animation_controller_ animateWindow:[window_controller_ window] 514 targetOrigin:target_origin 515 closing:NO]; 516 [window makeKeyAndOrderFront:nil]; 517 [NSApp activateIgnoringOtherApps:YES]; 518 RecordAppListLaunch(); 519} 520 521void AppListServiceMac::WindowAnimationDidEnd() { 522 [animation_controller_ cleanupOnUIThread]; 523} 524 525// static 526AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) { 527 return AppListServiceMac::GetInstance(); 528} 529 530// static 531void AppListService::InitAll(Profile* initial_profile) { 532 AppListServiceMac::GetInstance()->Init(initial_profile); 533} 534 535@implementation AppListAnimationController 536 537- (BOOL)isClosing { 538 return !!window_; 539} 540 541- (void)animateWindow:(NSWindow*)window 542 targetOrigin:(NSPoint)targetOrigin 543 closing:(BOOL)closing { 544 // First, stop the existing animation, if there is one. 545 [animation_ stopAnimation]; 546 547 NSRect targetFrame = [window frame]; 548 targetFrame.origin = targetOrigin; 549 550 // NSViewAnimation has a quirk when setting the curve to NSAnimationEaseOut 551 // where it attempts to auto-reverse the animation. FadeOut becomes FadeIn 552 // (good), but FrameKey is also switched (bad). So |targetFrame| needs to be 553 // put on the StartFrameKey when using NSAnimationEaseOut for showing. 554 NSArray* animationArray = @[ 555 @{ 556 NSViewAnimationTargetKey : window, 557 NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect, 558 (closing ? NSViewAnimationEndFrameKey : NSViewAnimationStartFrameKey) : 559 [NSValue valueWithRect:targetFrame] 560 } 561 ]; 562 animation_.reset( 563 [[NSViewAnimation alloc] initWithViewAnimations:animationArray]); 564 [animation_ setDuration:kAnimationDuration]; 565 [animation_ setDelegate:self]; 566 567 if (closing) { 568 [animation_ setAnimationCurve:NSAnimationEaseIn]; 569 window_.reset([window retain]); 570 } else { 571 [window setAlphaValue:0.0f]; 572 [animation_ setAnimationCurve:NSAnimationEaseOut]; 573 window_.reset(); 574 } 575 // Threaded animations are buggy on Snow Leopard. See http://crbug.com/335550. 576 // Note that in the non-threaded case, the animation won't start unless the 577 // UI runloop has spun up, so on <= Lion the animation will only animate if 578 // Chrome is already running. 579 if (base::mac::IsOSMountainLionOrLater()) 580 [animation_ setAnimationBlockingMode:NSAnimationNonblockingThreaded]; 581 else 582 [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; 583 584 [animation_ startAnimation]; 585} 586 587- (void)cleanupOnUIThread { 588 bool closing = [self isClosing]; 589 [window_ close]; 590 window_.reset(); 591 animation_.reset(); 592 593 if (closing) 594 apps::AppShimHandler::MaybeTerminate(); 595} 596 597- (void)animationDidEnd:(NSAnimation*)animation { 598 content::BrowserThread::PostTask( 599 content::BrowserThread::UI, 600 FROM_HERE, 601 base::Bind(&AppListServiceMac::WindowAnimationDidEnd, 602 base::Unretained(AppListServiceMac::GetInstance()))); 603} 604 605@end 606