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 |