| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/app_list/app_list_service_mac.h" |
| 6 |
| 5 #include <ApplicationServices/ApplicationServices.h> | 7 #include <ApplicationServices/ApplicationServices.h> |
| 6 #import <Cocoa/Cocoa.h> | 8 #import <Cocoa/Cocoa.h> |
| 7 | 9 |
| 8 #include "apps/app_launcher.h" | 10 #include "apps/app_launcher.h" |
| 9 #include "apps/app_shim/app_shim_handler_mac.h" | |
| 10 #include "apps/app_shim/app_shim_mac.h" | 11 #include "apps/app_shim/app_shim_mac.h" |
| 11 #include "apps/pref_names.h" | 12 #include "apps/pref_names.h" |
| 12 #include "base/bind.h" | 13 #include "base/bind.h" |
| 13 #include "base/command_line.h" | 14 #include "base/command_line.h" |
| 14 #include "base/file_util.h" | 15 #include "base/file_util.h" |
| 15 #include "base/lazy_instance.h" | 16 #include "base/lazy_instance.h" |
| 16 #include "base/mac/scoped_nsobject.h" | |
| 17 #include "base/memory/singleton.h" | 17 #include "base/memory/singleton.h" |
| 18 #include "base/message_loop/message_loop.h" | 18 #include "base/message_loop/message_loop.h" |
| 19 #include "chrome/browser/browser_process.h" | 19 #include "chrome/browser/browser_process.h" |
| 20 #include "chrome/browser/extensions/extension_service.h" | 20 #include "chrome/browser/extensions/extension_service.h" |
| 21 #include "chrome/browser/extensions/extension_system.h" | 21 #include "chrome/browser/extensions/extension_system.h" |
| 22 #include "chrome/browser/profiles/profile_manager.h" | 22 #include "chrome/browser/profiles/profile_manager.h" |
| 23 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h" | 23 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h" |
| 24 #include "chrome/browser/ui/app_list/app_list_service.h" | 24 #include "chrome/browser/ui/app_list/app_list_service.h" |
| 25 #include "chrome/browser/ui/app_list/app_list_service_impl.h" | 25 #include "chrome/browser/ui/app_list/app_list_service_impl.h" |
| 26 #include "chrome/browser/ui/app_list/app_list_view_delegate.h" | 26 #include "chrome/browser/ui/app_list/app_list_view_delegate.h" |
| (...skipping 17 matching lines...) Expand all Loading... |
| 44 | 44 |
| 45 namespace gfx { | 45 namespace gfx { |
| 46 class ImageSkia; | 46 class ImageSkia; |
| 47 } | 47 } |
| 48 | 48 |
| 49 namespace { | 49 namespace { |
| 50 | 50 |
| 51 // Version of the app list shortcut version installed. | 51 // Version of the app list shortcut version installed. |
| 52 const int kShortcutVersion = 1; | 52 const int kShortcutVersion = 1; |
| 53 | 53 |
| 54 // AppListServiceMac manages global resources needed for the app list to | |
| 55 // operate, and controls when the app list is opened and closed. | |
| 56 class AppListServiceMac : public AppListServiceImpl, | |
| 57 public apps::AppShimHandler { | |
| 58 public: | |
| 59 virtual ~AppListServiceMac() {} | |
| 60 | |
| 61 static AppListServiceMac* GetInstance() { | |
| 62 return Singleton<AppListServiceMac, | |
| 63 LeakySingletonTraits<AppListServiceMac> >::get(); | |
| 64 } | |
| 65 | |
| 66 void ShowWindowNearDock(); | |
| 67 | |
| 68 // AppListService overrides: | |
| 69 virtual void Init(Profile* initial_profile) OVERRIDE; | |
| 70 virtual void CreateForProfile(Profile* requested_profile) OVERRIDE; | |
| 71 virtual void ShowForProfile(Profile* requested_profile) OVERRIDE; | |
| 72 virtual void DismissAppList() OVERRIDE; | |
| 73 virtual bool IsAppListVisible() const OVERRIDE; | |
| 74 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; | |
| 75 virtual AppListControllerDelegate* CreateControllerDelegate() OVERRIDE; | |
| 76 | |
| 77 // AppListServiceImpl overrides: | |
| 78 virtual void CreateShortcut() OVERRIDE; | |
| 79 | |
| 80 // AppShimHandler overrides: | |
| 81 virtual void OnShimLaunch(apps::AppShimHandler::Host* host, | |
| 82 apps::AppShimLaunchType launch_type) OVERRIDE; | |
| 83 virtual void OnShimClose(apps::AppShimHandler::Host* host) OVERRIDE; | |
| 84 virtual void OnShimFocus(apps::AppShimHandler::Host* host, | |
| 85 apps::AppShimFocusType focus_type) OVERRIDE; | |
| 86 virtual void OnShimSetHidden(apps::AppShimHandler::Host* host, | |
| 87 bool hidden) OVERRIDE; | |
| 88 virtual void OnShimQuit(apps::AppShimHandler::Host* host) OVERRIDE; | |
| 89 | |
| 90 private: | |
| 91 friend struct DefaultSingletonTraits<AppListServiceMac>; | |
| 92 | |
| 93 AppListServiceMac() {} | |
| 94 | |
| 95 base::scoped_nsobject<AppListWindowController> window_controller_; | |
| 96 base::scoped_nsobject<NSRunningApplication> previously_active_application_; | |
| 97 | |
| 98 DISALLOW_COPY_AND_ASSIGN(AppListServiceMac); | |
| 99 }; | |
| 100 | |
| 101 class AppListControllerDelegateCocoa : public AppListControllerDelegate { | 54 class AppListControllerDelegateCocoa : public AppListControllerDelegate { |
| 102 public: | 55 public: |
| 103 AppListControllerDelegateCocoa(); | 56 AppListControllerDelegateCocoa(); |
| 104 virtual ~AppListControllerDelegateCocoa(); | 57 virtual ~AppListControllerDelegateCocoa(); |
| 105 | 58 |
| 106 private: | 59 private: |
| 107 // AppListControllerDelegate overrides: | 60 // AppListControllerDelegate overrides: |
| 108 virtual void DismissView() OVERRIDE; | 61 virtual void DismissView() OVERRIDE; |
| 109 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; | 62 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; |
| 110 virtual bool CanPin() OVERRIDE; | 63 virtual bool CanPin() OVERRIDE; |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 282 Profile* profile, const extensions::Extension* extension, int event_flags) { | 235 Profile* profile, const extensions::Extension* extension, int event_flags) { |
| 283 LaunchApp(profile, extension, event_flags); | 236 LaunchApp(profile, extension, event_flags); |
| 284 } | 237 } |
| 285 | 238 |
| 286 void AppListControllerDelegateCocoa::LaunchApp( | 239 void AppListControllerDelegateCocoa::LaunchApp( |
| 287 Profile* profile, const extensions::Extension* extension, int event_flags) { | 240 Profile* profile, const extensions::Extension* extension, int event_flags) { |
| 288 chrome::OpenApplication(chrome::AppLaunchParams( | 241 chrome::OpenApplication(chrome::AppLaunchParams( |
| 289 profile, extension, NEW_FOREGROUND_TAB)); | 242 profile, extension, NEW_FOREGROUND_TAB)); |
| 290 } | 243 } |
| 291 | 244 |
| 245 enum DockLocation { |
| 246 DockLocationOtherDisplay, |
| 247 DockLocationBottom, |
| 248 DockLocationLeft, |
| 249 DockLocationRight, |
| 250 }; |
| 251 |
| 252 DockLocation DockLocationInDisplay(const gfx::Display& display) { |
| 253 // Assume the dock occupies part of the work area either on the left, right or |
| 254 // bottom of the display. Note in the autohide case, it is always 4 pixels. |
| 255 const gfx::Rect work_area = display.work_area(); |
| 256 const gfx::Rect display_bounds = display.bounds(); |
| 257 if (work_area.bottom() != display_bounds.bottom()) |
| 258 return DockLocationBottom; |
| 259 |
| 260 if (work_area.x() != display_bounds.x()) |
| 261 return DockLocationLeft; |
| 262 |
| 263 if (work_area.right() != display_bounds.right()) |
| 264 return DockLocationRight; |
| 265 |
| 266 return DockLocationOtherDisplay; |
| 267 } |
| 268 |
| 269 // If |work_area_edge| is too close to the |screen_edge| (e.g. autohide dock), |
| 270 // adjust |anchor| away from the edge by a constant amount to reduce overlap and |
| 271 // ensure the dock icon can still be clicked to dismiss the app list. |
| 272 int AdjustPointForDynamicDock(int anchor, int screen_edge, int work_area_edge) { |
| 273 const int kAutohideDockThreshold = 10; |
| 274 const int kExtraDistance = 50; // A dock with 40 items is about this size. |
| 275 if (abs(work_area_edge - screen_edge) > kAutohideDockThreshold) |
| 276 return anchor; |
| 277 |
| 278 return anchor + |
| 279 (screen_edge < work_area_edge ? kExtraDistance : -kExtraDistance); |
| 280 } |
| 281 |
| 282 NSPoint GetAppListWindowOrigin(NSWindow* window) { |
| 283 gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]); |
| 284 // Ensure y coordinates are flipped back into AppKit's coordinate system. |
| 285 const CGFloat max_y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]); |
| 286 if (!CGCursorIsVisible()) { |
| 287 // If Chrome is the active application, display on the same display as |
| 288 // Chrome's keyWindow since this will catch activations triggered, e.g, via |
| 289 // WebStore install. If another application is active, OSX doesn't provide a |
| 290 // reliable way to get the display in use. Fall back to the primary display |
| 291 // since it has the menu bar and is likely to be correct, e.g., for |
| 292 // activations from Spotlight. |
| 293 const gfx::NativeView key_view = [[NSApp keyWindow] contentView]; |
| 294 const gfx::Rect work_area = key_view && [NSApp isActive] ? |
| 295 screen->GetDisplayNearestWindow(key_view).work_area() : |
| 296 screen->GetPrimaryDisplay().work_area(); |
| 297 return NSMakePoint(work_area.x(), max_y - work_area.bottom()); |
| 298 } |
| 299 |
| 300 gfx::Point anchor = screen->GetCursorScreenPoint(); |
| 301 const gfx::Display display = screen->GetDisplayNearestPoint(anchor); |
| 302 const DockLocation dock_location = DockLocationInDisplay(display); |
| 303 const gfx::Rect display_bounds = display.bounds(); |
| 304 |
| 305 if (dock_location == DockLocationOtherDisplay) { |
| 306 // Just display at the bottom-left of the display the cursor is on. |
| 307 return NSMakePoint(display_bounds.x(), max_y - display_bounds.bottom()); |
| 308 } |
| 309 |
| 310 // Anchor the center of the window in a region that prevents the window |
| 311 // showing outside of the work area. |
| 312 const NSSize window_size = [window frame].size; |
| 313 const gfx::Rect work_area = display.work_area(); |
| 314 gfx::Rect anchor_area = work_area; |
| 315 anchor_area.Inset(window_size.width / 2, window_size.height / 2); |
| 316 anchor.SetToMax(anchor_area.origin()); |
| 317 anchor.SetToMin(anchor_area.bottom_right()); |
| 318 |
| 319 // Move anchor to the dock, keeping the other axis aligned with the cursor. |
| 320 switch (dock_location) { |
| 321 case DockLocationBottom: |
| 322 anchor.set_y(AdjustPointForDynamicDock( |
| 323 anchor_area.bottom(), display_bounds.bottom(), work_area.bottom())); |
| 324 break; |
| 325 case DockLocationLeft: |
| 326 anchor.set_x(AdjustPointForDynamicDock( |
| 327 anchor_area.x(), display_bounds.x(), work_area.x())); |
| 328 break; |
| 329 case DockLocationRight: |
| 330 anchor.set_x(AdjustPointForDynamicDock( |
| 331 anchor_area.right(), display_bounds.right(), work_area.right())); |
| 332 break; |
| 333 default: |
| 334 NOTREACHED(); |
| 335 } |
| 336 |
| 337 return NSMakePoint( |
| 338 anchor.x() - window_size.width / 2, |
| 339 max_y - anchor.y() - window_size.height / 2); |
| 340 } |
| 341 |
| 342 } // namespace |
| 343 |
| 344 AppListServiceMac::AppListServiceMac() {} |
| 345 AppListServiceMac::~AppListServiceMac() {} |
| 346 |
| 347 // static |
| 348 AppListServiceMac* AppListServiceMac::GetInstance() { |
| 349 return Singleton<AppListServiceMac, |
| 350 LeakySingletonTraits<AppListServiceMac> >::get(); |
| 351 } |
| 352 |
| 292 void AppListServiceMac::Init(Profile* initial_profile) { | 353 void AppListServiceMac::Init(Profile* initial_profile) { |
| 293 // On Mac, Init() is called multiple times for a process: any time there is no | 354 // On Mac, Init() is called multiple times for a process: any time there is no |
| 294 // browser window open and a new window is opened, and during process startup | 355 // browser window open and a new window is opened, and during process startup |
| 295 // to handle the silent launch case (e.g. for app shims). In the startup case, | 356 // to handle the silent launch case (e.g. for app shims). In the startup case, |
| 296 // a profile has not yet been determined so |initial_profile| will be NULL. | 357 // a profile has not yet been determined so |initial_profile| will be NULL. |
| 297 static bool init_called_with_profile = false; | 358 static bool init_called_with_profile = false; |
| 298 if (initial_profile && !init_called_with_profile) { | 359 if (initial_profile && !init_called_with_profile) { |
| 299 init_called_with_profile = true; | 360 init_called_with_profile = true; |
| 300 HandleCommandLineFlags(initial_profile); | 361 HandleCommandLineFlags(initial_profile); |
| 301 PrefService* local_state = g_browser_process->local_state(); | 362 PrefService* local_state = g_browser_process->local_state(); |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 413 void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {} | 474 void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {} |
| 414 | 475 |
| 415 void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host, | 476 void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host, |
| 416 apps::AppShimFocusType focus_type) {} | 477 apps::AppShimFocusType focus_type) {} |
| 417 | 478 |
| 418 void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host, | 479 void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host, |
| 419 bool hidden) {} | 480 bool hidden) {} |
| 420 | 481 |
| 421 void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {} | 482 void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {} |
| 422 | 483 |
| 423 enum DockLocation { | |
| 424 DockLocationOtherDisplay, | |
| 425 DockLocationBottom, | |
| 426 DockLocationLeft, | |
| 427 DockLocationRight, | |
| 428 }; | |
| 429 | |
| 430 DockLocation DockLocationInDisplay(const gfx::Display& display) { | |
| 431 // Assume the dock occupies part of the work area either on the left, right or | |
| 432 // bottom of the display. Note in the autohide case, it is always 4 pixels. | |
| 433 const gfx::Rect work_area = display.work_area(); | |
| 434 const gfx::Rect display_bounds = display.bounds(); | |
| 435 if (work_area.bottom() != display_bounds.bottom()) | |
| 436 return DockLocationBottom; | |
| 437 | |
| 438 if (work_area.x() != display_bounds.x()) | |
| 439 return DockLocationLeft; | |
| 440 | |
| 441 if (work_area.right() != display_bounds.right()) | |
| 442 return DockLocationRight; | |
| 443 | |
| 444 return DockLocationOtherDisplay; | |
| 445 } | |
| 446 | |
| 447 // If |work_area_edge| is too close to the |screen_edge| (e.g. autohide dock), | |
| 448 // adjust |anchor| away from the edge by a constant amount to reduce overlap and | |
| 449 // ensure the dock icon can still be clicked to dismiss the app list. | |
| 450 int AdjustPointForDynamicDock(int anchor, int screen_edge, int work_area_edge) { | |
| 451 const int kAutohideDockThreshold = 10; | |
| 452 const int kExtraDistance = 50; // A dock with 40 items is about this size. | |
| 453 if (abs(work_area_edge - screen_edge) > kAutohideDockThreshold) | |
| 454 return anchor; | |
| 455 | |
| 456 return anchor + | |
| 457 (screen_edge < work_area_edge ? kExtraDistance : -kExtraDistance); | |
| 458 } | |
| 459 | |
| 460 NSPoint GetAppListWindowOrigin(NSWindow* window) { | |
| 461 gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]); | |
| 462 // Ensure y coordinates are flipped back into AppKit's coordinate system. | |
| 463 const CGFloat max_y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]); | |
| 464 if (!CGCursorIsVisible()) { | |
| 465 // If Chrome is the active application, display on the same display as | |
| 466 // Chrome's keyWindow since this will catch activations triggered, e.g, via | |
| 467 // WebStore install. If another application is active, OSX doesn't provide a | |
| 468 // reliable way to get the display in use. Fall back to the primary display | |
| 469 // since it has the menu bar and is likely to be correct, e.g., for | |
| 470 // activations from Spotlight. | |
| 471 const gfx::NativeView key_view = [[NSApp keyWindow] contentView]; | |
| 472 const gfx::Rect work_area = key_view && [NSApp isActive] ? | |
| 473 screen->GetDisplayNearestWindow(key_view).work_area() : | |
| 474 screen->GetPrimaryDisplay().work_area(); | |
| 475 return NSMakePoint(work_area.x(), max_y - work_area.bottom()); | |
| 476 } | |
| 477 | |
| 478 gfx::Point anchor = screen->GetCursorScreenPoint(); | |
| 479 const gfx::Display display = screen->GetDisplayNearestPoint(anchor); | |
| 480 const DockLocation dock_location = DockLocationInDisplay(display); | |
| 481 const gfx::Rect display_bounds = display.bounds(); | |
| 482 | |
| 483 if (dock_location == DockLocationOtherDisplay) { | |
| 484 // Just display at the bottom-left of the display the cursor is on. | |
| 485 return NSMakePoint(display_bounds.x(), max_y - display_bounds.bottom()); | |
| 486 } | |
| 487 | |
| 488 // Anchor the center of the window in a region that prevents the window | |
| 489 // showing outside of the work area. | |
| 490 const NSSize window_size = [window frame].size; | |
| 491 const gfx::Rect work_area = display.work_area(); | |
| 492 gfx::Rect anchor_area = work_area; | |
| 493 anchor_area.Inset(window_size.width / 2, window_size.height / 2); | |
| 494 anchor.SetToMax(anchor_area.origin()); | |
| 495 anchor.SetToMin(anchor_area.bottom_right()); | |
| 496 | |
| 497 // Move anchor to the dock, keeping the other axis aligned with the cursor. | |
| 498 switch (dock_location) { | |
| 499 case DockLocationBottom: | |
| 500 anchor.set_y(AdjustPointForDynamicDock( | |
| 501 anchor_area.bottom(), display_bounds.bottom(), work_area.bottom())); | |
| 502 break; | |
| 503 case DockLocationLeft: | |
| 504 anchor.set_x(AdjustPointForDynamicDock( | |
| 505 anchor_area.x(), display_bounds.x(), work_area.x())); | |
| 506 break; | |
| 507 case DockLocationRight: | |
| 508 anchor.set_x(AdjustPointForDynamicDock( | |
| 509 anchor_area.right(), display_bounds.right(), work_area.right())); | |
| 510 break; | |
| 511 default: | |
| 512 NOTREACHED(); | |
| 513 } | |
| 514 | |
| 515 return NSMakePoint( | |
| 516 anchor.x() - window_size.width / 2, | |
| 517 max_y - anchor.y() - window_size.height / 2); | |
| 518 } | |
| 519 | |
| 520 void AppListServiceMac::ShowWindowNearDock() { | 484 void AppListServiceMac::ShowWindowNearDock() { |
| 521 NSWindow* window = GetAppListWindow(); | 485 NSWindow* window = GetAppListWindow(); |
| 522 DCHECK(window); | 486 DCHECK(window); |
| 523 [window setFrameOrigin:GetAppListWindowOrigin(window)]; | 487 [window setFrameOrigin:GetAppListWindowOrigin(window)]; |
| 524 | 488 |
| 525 // Before activating, see if an application other than Chrome is currently the | 489 // Before activating, see if an application other than Chrome is currently the |
| 526 // active application, so that it can be reactivated when dismissing. | 490 // active application, so that it can be reactivated when dismissing. |
| 527 previously_active_application_.reset([ActiveApplicationNotChrome() retain]); | 491 previously_active_application_.reset([ActiveApplicationNotChrome() retain]); |
| 528 | 492 |
| 529 [window makeKeyAndOrderFront:nil]; | 493 [window makeKeyAndOrderFront:nil]; |
| 530 [NSApp activateIgnoringOtherApps:YES]; | 494 [NSApp activateIgnoringOtherApps:YES]; |
| 531 } | 495 } |
| 532 | 496 |
| 533 } // namespace | |
| 534 | |
| 535 // static | 497 // static |
| 536 AppListService* AppListService::Get() { | 498 AppListService* AppListService::Get() { |
| 537 return AppListServiceMac::GetInstance(); | 499 return AppListServiceMac::GetInstance(); |
| 538 } | 500 } |
| 539 | 501 |
| 540 // static | 502 // static |
| 541 void AppListService::InitAll(Profile* initial_profile) { | 503 void AppListService::InitAll(Profile* initial_profile) { |
| 542 Get()->Init(initial_profile); | 504 Get()->Init(initial_profile); |
| 543 } | 505 } |
| OLD | NEW |