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