app_list_service_mac.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// Copyright 2013 The Chromium Authors. All rights reserved. 28bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 38bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// found in the LICENSE file. 48bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 58bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include <ApplicationServices/ApplicationServices.h> 68bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import <Cocoa/Cocoa.h> 78bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 88bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "apps/app_shim/app_shim_handler_mac.h" 98bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/bind.h" 108bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/command_line.h" 118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/file_util.h" 121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/lazy_instance.h" 138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/mac/scoped_nsobject.h" 14c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/memory/singleton.h" 15c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/message_loop.h" 16c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/observer_list.h" 17c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "chrome/browser/extensions/extension_service.h" 188bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/extensions/extension_system.h" 198bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service.h" 218bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service_impl.h" 228bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_view_delegate.h" 23f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "chrome/browser/ui/extensions/application_launch.h" 248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/web_applications/web_app_ui.h" 25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "chrome/browser/web_applications/web_app.h" 268bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/web_applications/web_app_mac.h" 278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/chrome_switches.h" 288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/chrome_version_info.h" 298bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/mac/app_mode_common.h" 308bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "content/public/browser/browser_thread.h" 318bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "grit/chrome_unscaled_resources.h" 328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "grit/google_chrome_strings.h" 338bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_view_controller.h" 348bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_window_controller.h" 358bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h" 368bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 378bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/gfx/display.h" 388bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/gfx/screen.h" 398bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)namespace gfx { 418bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)class ImageSkia; 428bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)} 438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 448bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)namespace { 458bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 468bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// AppListServiceMac manages global resources needed for the app list to 478bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// operate, and controls when the app list is opened and closed. 488bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)class AppListServiceMac : public AppListServiceImpl, 498bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public apps::AppShimHandler { 505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public: 518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual ~AppListServiceMac() {} 528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) static AppListServiceMac* GetInstance() { 545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return Singleton<AppListServiceMac, 558bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) LeakySingletonTraits<AppListServiceMac> >::get(); 568bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 588bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) void CreateAppList(Profile* profile); 598bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) void ShowWindowNearDock(); 608bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 618bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // AppListService overrides: 628bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void Init(Profile* initial_profile) OVERRIDE; 635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) virtual void ShowAppList(Profile* requested_profile) OVERRIDE; 648bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void DismissAppList() OVERRIDE; 658bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual bool IsAppListVisible() const OVERRIDE; 668bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void EnableAppList(Profile* initial_profile) OVERRIDE; 678bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; 688bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 69a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // AppListServiceImpl override: 708bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnSigninStatusChanged() OVERRIDE; 718bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 728bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // AppShimHandler overrides: 738bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnShimLaunch(apps::AppShimHandler::Host* host, 748bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) apps::AppShimLaunchType launch_type) OVERRIDE; 758bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnShimClose(apps::AppShimHandler::Host* host) OVERRIDE; 768bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnShimFocus(apps::AppShimHandler::Host* host, 778bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) apps::AppShimFocusType focus_type) OVERRIDE; 788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnShimSetHidden(apps::AppShimHandler::Host* host, 798bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) bool hidden) OVERRIDE; 808bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void OnShimQuit(apps::AppShimHandler::Host* host) OVERRIDE; 818bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 828bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private: 838bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) friend struct DefaultSingletonTraits<AppListServiceMac>; 848bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) AppListServiceMac() {} 865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::scoped_nsobject<AppListWindowController> window_controller_; 888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // App shim hosts observing when the app list is dismissed. In normal user 908bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // usage there should only be one. However, it can't be guaranteed, so use 918bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // an ObserverList rather than handling corner cases. 928bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) ObserverList<apps::AppShimHandler::Host> observers_; 93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(AppListServiceMac); 95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}; 96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class AppListControllerDelegateCocoa : public AppListControllerDelegate { 98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public: 99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) AppListControllerDelegateCocoa(); 100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) virtual ~AppListControllerDelegateCocoa(); 101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private: 103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // AppListControllerDelegate overrides: 1048bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void DismissView() OVERRIDE; 1058bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; 1068bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual bool CanPin() OVERRIDE; 1078bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual bool CanDoCreateShortcutsFlow(bool is_platform_app) OVERRIDE; 1088bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void DoCreateShortcutsFlow(Profile* profile, 1098bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) const std::string& extension_id) OVERRIDE; 1108bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void ActivateApp(Profile* profile, 1118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) const extensions::Extension* extension, 1128bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int event_flags) OVERRIDE; 1138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) virtual void LaunchApp(Profile* profile, 1148bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) const extensions::Extension* extension, 1158bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int event_flags) OVERRIDE; 1168bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1178bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(AppListControllerDelegateCocoa); 1188bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}; 1198bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)ShellIntegration::ShortcutInfo GetAppListShortcutInfo( 1218bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) const base::FilePath& profile_path) { 1228bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) ShellIntegration::ShortcutInfo shortcut_info; 1238bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 124effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 125effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch shortcut_info.title = 126effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY); 127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } else { 1288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME); 1298bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1308bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1318bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) shortcut_info.extension_id = app_mode::kAppListModeId; 1328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) shortcut_info.description = shortcut_info.title; 1330f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) shortcut_info.profile_path = profile_path; 1340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return shortcut_info; 1360f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)} 1370f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 1380f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)void CreateAppListShim(const base::FilePath& profile_path) { 1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 1408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) WebApplicationInfo web_app_info; 141 ShellIntegration::ShortcutInfo shortcut_info = 142 GetAppListShortcutInfo(profile_path); 143 144 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance(); 145 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 146 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 147#if defined(GOOGLE_CHROME_BUILD) 148 shortcut_info.favicon.Add( 149 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16)); 150 shortcut_info.favicon.Add( 151 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32)); 152 shortcut_info.favicon.Add( 153 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128)); 154 shortcut_info.favicon.Add( 155 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256)); 156#else 157 NOTREACHED(); 158#endif 159 } else { 160 shortcut_info.favicon.Add( 161 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16)); 162 shortcut_info.favicon.Add( 163 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32)); 164 shortcut_info.favicon.Add( 165 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128)); 166 shortcut_info.favicon.Add( 167 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256)); 168 } 169 170 // TODO(tapted): Create a dock icon using chrome/browser/mac/dock.h . 171 web_app::CreateShortcuts(shortcut_info, 172 ShellIntegration::ShortcutLocations(), 173 web_app::ALLOW_DUPLICATE_SHORTCUTS); 174} 175 176// Check that there is an app list shim. If enabling and there is not, make one. 177// If disabling with --enable-app-list-shim=0, and there is one, delete it. 178void CheckAppListShimOnFileThread(const base::FilePath& profile_path) { 179 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); 180 const bool enable = 181 CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppListShim); 182 base::FilePath install_path = web_app::GetAppInstallPath( 183 GetAppListShortcutInfo(profile_path)); 184 if (enable == base::PathExists(install_path)) 185 return; 186 187 if (enable) { 188 content::BrowserThread::PostTask( 189 content::BrowserThread::UI, FROM_HERE, 190 base::Bind(&CreateAppListShim, profile_path)); 191 return; 192 } 193 194 // Sanity check because deleting things recursively is scary. 195 CHECK(install_path.MatchesExtension(".app")); 196 base::DeleteFile(install_path, true /* recursive */); 197} 198 199void CreateShortcutsInDefaultLocation( 200 const ShellIntegration::ShortcutInfo& shortcut_info) { 201 web_app::CreateShortcuts(shortcut_info, 202 ShellIntegration::ShortcutLocations(), 203 web_app::ALLOW_DUPLICATE_SHORTCUTS); 204} 205 206AppListControllerDelegateCocoa::AppListControllerDelegateCocoa() {} 207 208AppListControllerDelegateCocoa::~AppListControllerDelegateCocoa() {} 209 210void AppListControllerDelegateCocoa::DismissView() { 211 AppListServiceMac::GetInstance()->DismissAppList(); 212} 213 214gfx::NativeWindow AppListControllerDelegateCocoa::GetAppListWindow() { 215 return AppListServiceMac::GetInstance()->GetAppListWindow(); 216} 217 218bool AppListControllerDelegateCocoa::CanPin() { 219 return false; 220} 221 222bool AppListControllerDelegateCocoa::CanDoCreateShortcutsFlow( 223 bool is_platform_app) { 224 return is_platform_app && 225 CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppShims); 226} 227 228void AppListControllerDelegateCocoa::DoCreateShortcutsFlow( 229 Profile* profile, const std::string& extension_id) { 230 ExtensionService* service = 231 extensions::ExtensionSystem::Get(profile)->extension_service(); 232 DCHECK(service); 233 const extensions::Extension* extension = 234 service->GetInstalledExtension(extension_id); 235 DCHECK(extension); 236 237 web_app::UpdateShortcutInfoAndIconForApp( 238 *extension, profile, base::Bind(&CreateShortcutsInDefaultLocation)); 239} 240 241void AppListControllerDelegateCocoa::ActivateApp( 242 Profile* profile, const extensions::Extension* extension, int event_flags) { 243 LaunchApp(profile, extension, event_flags); 244} 245 246void AppListControllerDelegateCocoa::LaunchApp( 247 Profile* profile, const extensions::Extension* extension, int event_flags) { 248 chrome::OpenApplication(chrome::AppLaunchParams( 249 profile, extension, NEW_FOREGROUND_TAB)); 250} 251 252void AppListServiceMac::CreateAppList(Profile* requested_profile) { 253 if (profile() == requested_profile) 254 return; 255 256 // The Objective C objects might be released at some unknown point in the 257 // future, so explicitly clear references to C++ objects. 258 [[window_controller_ appListViewController] 259 setDelegate:scoped_ptr<app_list::AppListViewDelegate>()]; 260 261 SetProfile(requested_profile); 262 scoped_ptr<app_list::AppListViewDelegate> delegate( 263 new AppListViewDelegate(new AppListControllerDelegateCocoa(), profile())); 264 window_controller_.reset([[AppListWindowController alloc] init]); 265 [[window_controller_ appListViewController] setDelegate:delegate.Pass()]; 266} 267 268void AppListServiceMac::Init(Profile* initial_profile) { 269 // On Mac, Init() is called multiple times for a process: any time there is no 270 // browser window open and a new window is opened, and during process startup 271 // to handle the silent launch case (e.g. for app shims). In the startup case, 272 // a profile has not yet been determined so |initial_profile| will be NULL. 273 if (initial_profile) { 274 static bool checked_shim = false; 275 if (!checked_shim) { 276 checked_shim = true; 277 content::BrowserThread::PostTask( 278 content::BrowserThread::FILE, FROM_HERE, 279 base::Bind(&CheckAppListShimOnFileThread, 280 initial_profile->GetPath())); 281 } 282 } 283 284 static bool init_called = false; 285 if (init_called) 286 return; 287 288 init_called = true; 289 apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId, 290 AppListServiceMac::GetInstance()); 291} 292 293void AppListServiceMac::ShowAppList(Profile* requested_profile) { 294 InvalidatePendingProfileLoads(); 295 296 if (IsAppListVisible() && (requested_profile == profile())) { 297 ShowWindowNearDock(); 298 return; 299 } 300 301 SaveProfilePathToLocalState(requested_profile->GetPath()); 302 303 DismissAppList(); 304 CreateAppList(requested_profile); 305 ShowWindowNearDock(); 306} 307 308void AppListServiceMac::DismissAppList() { 309 if (!IsAppListVisible()) 310 return; 311 312 [[window_controller_ window] close]; 313 314 FOR_EACH_OBSERVER(apps::AppShimHandler::Host, 315 observers_, 316 OnAppClosed()); 317} 318 319bool AppListServiceMac::IsAppListVisible() const { 320 return [[window_controller_ window] isVisible]; 321} 322 323void AppListServiceMac::EnableAppList(Profile* initial_profile) { 324 // TODO(tapted): Implement enable logic here for OSX. 325} 326 327NSWindow* AppListServiceMac::GetAppListWindow() { 328 return [window_controller_ window]; 329} 330 331void AppListServiceMac::OnSigninStatusChanged() { 332 [[window_controller_ appListViewController] onSigninStatusChanged]; 333} 334 335void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host, 336 apps::AppShimLaunchType launch_type) { 337 ShowForSavedProfile(); 338 observers_.AddObserver(host); 339 host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_SUCCESS); 340} 341 342void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) { 343 observers_.RemoveObserver(host); 344 DismissAppList(); 345} 346 347void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host, 348 apps::AppShimFocusType focus_type) { 349 DismissAppList(); 350} 351 352void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host, 353 bool hidden) {} 354 355void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) { 356 DismissAppList(); 357} 358 359enum DockLocation { 360 DockLocationOtherDisplay, 361 DockLocationBottom, 362 DockLocationLeft, 363 DockLocationRight, 364}; 365 366DockLocation DockLocationInDisplay(const gfx::Display& display) { 367 // Assume the dock occupies part of the work area either on the left, right or 368 // bottom of the display. Note in the autohide case, it is always 4 pixels. 369 const gfx::Rect work_area = display.work_area(); 370 const gfx::Rect display_bounds = display.bounds(); 371 if (work_area.bottom() != display_bounds.bottom()) 372 return DockLocationBottom; 373 374 if (work_area.x() != display_bounds.x()) 375 return DockLocationLeft; 376 377 if (work_area.right() != display_bounds.right()) 378 return DockLocationRight; 379 380 return DockLocationOtherDisplay; 381} 382 383// If |work_area_edge| is too close to the |screen_edge| (e.g. autohide dock), 384// adjust |anchor| away from the edge by a constant amount to reduce overlap and 385// ensure the dock icon can still be clicked to dismiss the app list. 386int AdjustPointForDynamicDock(int anchor, int screen_edge, int work_area_edge) { 387 const int kAutohideDockThreshold = 10; 388 const int kExtraDistance = 50; // A dock with 40 items is about this size. 389 if (abs(work_area_edge - screen_edge) > kAutohideDockThreshold) 390 return anchor; 391 392 return anchor + 393 (screen_edge < work_area_edge ? kExtraDistance : -kExtraDistance); 394} 395 396NSPoint GetAppListWindowOrigin(NSWindow* window) { 397 gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]); 398 // Ensure y coordinates are flipped back into AppKit's coordinate system. 399 const CGFloat max_y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]); 400 if (!CGCursorIsVisible()) { 401 // If Chrome is the active application, display on the same display as 402 // Chrome's keyWindow since this will catch activations triggered, e.g, via 403 // WebStore install. If another application is active, OSX doesn't provide a 404 // reliable way to get the display in use. Fall back to the primary display 405 // since it has the menu bar and is likely to be correct, e.g., for 406 // activations from Spotlight. 407 const gfx::NativeView key_view = [[NSApp keyWindow] contentView]; 408 const gfx::Rect work_area = key_view && [NSApp isActive] ? 409 screen->GetDisplayNearestWindow(key_view).work_area() : 410 screen->GetPrimaryDisplay().work_area(); 411 return NSMakePoint(work_area.x(), max_y - work_area.bottom()); 412 } 413 414 gfx::Point anchor = screen->GetCursorScreenPoint(); 415 const gfx::Display display = screen->GetDisplayNearestPoint(anchor); 416 const DockLocation dock_location = DockLocationInDisplay(display); 417 const gfx::Rect display_bounds = display.bounds(); 418 419 if (dock_location == DockLocationOtherDisplay) { 420 // Just display at the bottom-left of the display the cursor is on. 421 return NSMakePoint(display_bounds.x(), max_y - display_bounds.bottom()); 422 } 423 424 // Anchor the center of the window in a region that prevents the window 425 // showing outside of the work area. 426 const NSSize window_size = [window frame].size; 427 const gfx::Rect work_area = display.work_area(); 428 gfx::Rect anchor_area = work_area; 429 anchor_area.Inset(window_size.width / 2, window_size.height / 2); 430 anchor.SetToMax(anchor_area.origin()); 431 anchor.SetToMin(anchor_area.bottom_right()); 432 433 // Move anchor to the dock, keeping the other axis aligned with the cursor. 434 switch (dock_location) { 435 case DockLocationBottom: 436 anchor.set_y(AdjustPointForDynamicDock( 437 anchor_area.bottom(), display_bounds.bottom(), work_area.bottom())); 438 break; 439 case DockLocationLeft: 440 anchor.set_x(AdjustPointForDynamicDock( 441 anchor_area.x(), display_bounds.x(), work_area.x())); 442 break; 443 case DockLocationRight: 444 anchor.set_x(AdjustPointForDynamicDock( 445 anchor_area.right(), display_bounds.right(), work_area.right())); 446 break; 447 default: 448 NOTREACHED(); 449 } 450 451 return NSMakePoint( 452 anchor.x() - window_size.width / 2, 453 max_y - anchor.y() - window_size.height / 2); 454} 455 456void AppListServiceMac::ShowWindowNearDock() { 457 NSWindow* window = GetAppListWindow(); 458 DCHECK(window); 459 [window setFrameOrigin:GetAppListWindowOrigin(window)]; 460 [window makeKeyAndOrderFront:nil]; 461 [NSApp activateIgnoringOtherApps:YES]; 462} 463 464} // namespace 465 466// static 467AppListService* AppListService::Get() { 468 return AppListServiceMac::GetInstance(); 469} 470 471// static 472void AppListService::InitAll(Profile* initial_profile) { 473 Get()->Init(initial_profile); 474} 475