1/* This file is part of Clementine. 2 Copyright 2010-2011, David Sansome <davidsansome@gmail.com> 3 Copyright 2010-2012, 2014, John Maguire <john.maguire@gmail.com> 4 Copyright 2011, Tyler Rhodes <tyler.s.rhodes@gmail.com> 5 6 Clementine is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 Clementine is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with Clementine. If not, see <http://www.gnu.org/licenses/>. 18*/ 19 20#import <AppKit/NSApplication.h> 21#import <AppKit/NSEvent.h> 22#import <AppKit/NSGraphics.h> 23#import <AppKit/NSNibDeclarations.h> 24#import <AppKit/NSViewController.h> 25 26#import <Foundation/NSBundle.h> 27#import <Foundation/NSError.h> 28#import <Foundation/NSFileManager.h> 29#import <Foundation/NSPathUtilities.h> 30#import <Foundation/NSProcessInfo.h> 31#import <Foundation/NSThread.h> 32#import <Foundation/NSTimer.h> 33#import <Foundation/NSURL.h> 34 35#import <IOKit/hidsystem/ev_keymap.h> 36 37#import <Kernel/AvailabilityMacros.h> 38 39#import <QuartzCore/CALayer.h> 40 41#import "3rdparty/SPMediaKeyTap/SPMediaKeyTap.h" 42 43#include "config.h" 44#include "globalshortcuts.h" 45#include "mac_delegate.h" 46#include "mac_startup.h" 47#include "mac_utilities.h" 48#include "macglobalshortcutbackend.h" 49#include "utilities.h" 50#include "core/logging.h" 51#include "core/scoped_cftyperef.h" 52#include "core/scoped_nsautorelease_pool.h" 53 54#ifdef HAVE_SPARKLE 55#import <Sparkle/SUUpdater.h> 56#endif 57 58#include <QApplication> 59#include <QCoreApplication> 60#include <QDir> 61#include <QEvent> 62#include <QFile> 63#include <QSettings> 64#include <QWidget> 65 66#include <QtDebug> 67 68QDebug operator<<(QDebug dbg, NSObject* object) { 69 QString ns_format = [[NSString stringWithFormat:@"%@", object] UTF8String]; 70 dbg.nospace() << ns_format; 71 return dbg.space(); 72} 73 74// Capture global media keys on Mac (Cocoa only!) 75// See: 76// http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/ 77 78@interface MacApplication : NSApplication { 79 PlatformInterface* application_handler_; 80 AppDelegate* delegate_; 81 // shortcut_handler_ only used to temporarily save it 82 // AppDelegate does all the heavy-shortcut-lifting 83 MacGlobalShortcutBackend* shortcut_handler_; 84} 85 86- (MacGlobalShortcutBackend*)shortcut_handler; 87- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler; 88 89- (PlatformInterface*)application_handler; 90- (void)SetApplicationHandler:(PlatformInterface*)handler; 91 92@end 93 94#ifdef HAVE_BREAKPAD 95static bool BreakpadCallback(int, int, mach_port_t, void*) { return true; } 96 97static BreakpadRef InitBreakpad() { 98 ScopedNSAutoreleasePool pool; 99 BreakpadRef breakpad = nil; 100 NSDictionary* plist = [[NSBundle mainBundle] infoDictionary]; 101 if (plist) { 102 breakpad = BreakpadCreate(plist); 103 BreakpadSetFilterCallback(breakpad, &BreakpadCallback, nullptr); 104 } 105 [pool release]; 106 return breakpad; 107} 108#endif // HAVE_BREAKPAD 109 110@implementation AppDelegate 111 112- (id)init { 113 if ((self = [super init])) { 114 application_handler_ = nil; 115 shortcut_handler_ = nil; 116 dock_menu_ = nil; 117 } 118 return self; 119} 120 121- (id)initWithHandler:(PlatformInterface*)handler { 122 application_handler_ = handler; 123 124#ifdef HAVE_BREAKPAD 125 breakpad_ = InitBreakpad(); 126#endif 127 128 // Register defaults for the whitelist of apps that want to use media keys 129 [[NSUserDefaults standardUserDefaults] 130 registerDefaults: 131 [NSDictionary 132 dictionaryWithObjectsAndKeys: 133 [SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], 134 kMediaKeyUsingBundleIdentifiersDefaultsKey, nil]]; 135 return self; 136} 137 138- (BOOL)applicationShouldHandleReopen:(NSApplication*)app 139 hasVisibleWindows:(BOOL)flag { 140 if (application_handler_) { 141 application_handler_->Activate(); 142 } 143 return YES; 144} 145 146- (void)setDockMenu:(NSMenu*)menu { 147 dock_menu_ = menu; 148} 149 150- (NSMenu*)applicationDockMenu:(NSApplication*)sender { 151 return dock_menu_; 152} 153 154- (void)setShortcutHandler:(MacGlobalShortcutBackend*)backend { 155 shortcut_handler_ = backend; 156} 157 158- (MacGlobalShortcutBackend*)shortcut_handler { 159 return shortcut_handler_; 160} 161 162- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { 163 key_tap_ = [[SPMediaKeyTap alloc] initWithDelegate:self]; 164 if ([SPMediaKeyTap usesGlobalMediaKeyTap] && 165 ![[NSProcessInfo processInfo] 166 isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 167 .majorVersion = 10, 168 .minorVersion = 12, 169 .patchVersion = 0}]) { 170 [key_tap_ startWatchingMediaKeys]; 171 } else { 172 qLog(Warning) << "Media key monitoring disabled"; 173 } 174} 175 176- (BOOL)application:(NSApplication*)app openFile:(NSString*)filename { 177 qLog(Debug) << "Wants to open:" << [filename UTF8String]; 178 179 if (application_handler_->LoadUrl(QString::fromUtf8([filename UTF8String]))) { 180 return YES; 181 } 182 183 return NO; 184} 185 186- (void)application:(NSApplication*)app openFiles:(NSArray*)filenames { 187 qLog(Debug) << "Wants to open:" << filenames; 188 [filenames 189 enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL* stop) { 190 [self application:app openFile:(NSString*)object]; 191 }]; 192} 193 194- (void)mediaKeyTap:(SPMediaKeyTap*)keyTap 195 receivedMediaKeyEvent:(NSEvent*)event { 196 NSAssert([event type] == NSSystemDefined && 197 [event subtype] == SPSystemDefinedEventMediaKeys, 198 @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:"); 199 200 int key_code = (([event data1] & 0xFFFF0000) >> 16); 201 int key_flags = ([event data1] & 0x0000FFFF); 202 BOOL key_is_released = (((key_flags & 0xFF00) >> 8)) == 0xB; 203 // not used. keep just in case 204 // int key_repeat = (key_flags & 0x1); 205 206 if (!shortcut_handler_) { 207 return; 208 } 209 if (key_is_released) { 210 shortcut_handler_->MacMediaKeyPressed(key_code); 211 } 212} 213 214- (NSApplicationTerminateReply)applicationShouldTerminate: 215 (NSApplication*)sender { 216#ifdef HAVE_BREAKPAD 217 BreakpadRelease(breakpad_); 218#endif 219 return NSTerminateNow; 220} 221 222- (BOOL)userNotificationCenter:(id)center 223 shouldPresentNotification:(id)notification { 224 // Always show notifications, even if Clementine is in the foreground. 225 return YES; 226} 227 228@end 229 230@implementation MacApplication 231 232- (id)init { 233 if ((self = [super init])) { 234 [self SetShortcutHandler:nil]; 235 } 236 return self; 237} 238 239- (MacGlobalShortcutBackend*)shortcut_handler { 240 // should be the same as delegate_'s shortcut handler 241 return shortcut_handler_; 242} 243 244- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler { 245 shortcut_handler_ = handler; 246 if (delegate_) [delegate_ setShortcutHandler:handler]; 247} 248 249- (PlatformInterface*)application_handler { 250 return application_handler_; 251} 252 253- (void)SetApplicationHandler:(PlatformInterface*)handler { 254 delegate_ = [[AppDelegate alloc] initWithHandler:handler]; 255 // App-shortcut-handler set before delegate is set. 256 // this makes sure the delegate's shortcut_handler is set 257 [delegate_ setShortcutHandler:shortcut_handler_]; 258 [self setDelegate:delegate_]; 259 260 [[NSUserNotificationCenter defaultUserNotificationCenter] 261 setDelegate:delegate_]; 262} 263 264- (void)sendEvent:(NSEvent*)event { 265 // If event tap is not installed, handle events that reach the app instead 266 BOOL shouldHandleMediaKeyEventLocally = 267 ![SPMediaKeyTap usesGlobalMediaKeyTap]; 268 269 if (shouldHandleMediaKeyEventLocally && [event type] == NSSystemDefined && 270 [event subtype] == SPSystemDefinedEventMediaKeys) { 271 [(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:event]; 272 } 273 274 [super sendEvent:event]; 275} 276 277@end 278 279namespace mac { 280 281void MacMain() { 282 ScopedNSAutoreleasePool pool; 283 // Creates and sets the magic global variable so QApplication will find it. 284 [MacApplication sharedApplication]; 285#ifdef HAVE_SPARKLE 286 // Creates and sets the magic global variable for Sparkle. 287 [[SUUpdater sharedUpdater] setDelegate:NSApp]; 288#endif 289} 290 291void SetShortcutHandler(MacGlobalShortcutBackend* handler) { 292 [NSApp SetShortcutHandler:handler]; 293} 294 295void SetApplicationHandler(PlatformInterface* handler) { 296 [NSApp SetApplicationHandler:handler]; 297} 298 299void CheckForUpdates() { 300#ifdef HAVE_SPARKLE 301 [[SUUpdater sharedUpdater] checkForUpdates:NSApp]; 302#endif 303} 304 305QString GetBundlePath() { 306 ScopedCFTypeRef<CFURLRef> app_url( 307 CFBundleCopyBundleURL(CFBundleGetMainBundle())); 308 ScopedCFTypeRef<CFStringRef> mac_path( 309 CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle)); 310 const char* path = 311 CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding()); 312 QString bundle_path = QString::fromUtf8(path); 313 return bundle_path; 314} 315 316QString GetResourcesPath() { 317 QString bundle_path = GetBundlePath(); 318 return bundle_path + "/Contents/Resources"; 319} 320 321QString GetApplicationSupportPath() { 322 ScopedNSAutoreleasePool pool; 323 NSArray* paths = NSSearchPathForDirectoriesInDomains( 324 NSApplicationSupportDirectory, NSUserDomainMask, YES); 325 QString ret; 326 if ([paths count] > 0) { 327 NSString* user_path = [paths objectAtIndex:0]; 328 ret = QString::fromUtf8([user_path UTF8String]); 329 } else { 330 ret = "~/Library/Application Support"; 331 } 332 return ret; 333} 334 335QString GetMusicDirectory() { 336 ScopedNSAutoreleasePool pool; 337 NSArray* paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory, 338 NSUserDomainMask, YES); 339 QString ret; 340 if ([paths count] > 0) { 341 NSString* user_path = [paths objectAtIndex:0]; 342 ret = QString::fromUtf8([user_path UTF8String]); 343 } else { 344 ret = "~/Music"; 345 } 346 return ret; 347} 348 349static int MapFunctionKey(int keycode) { 350 switch (keycode) { 351 // Function keys 352 case NSInsertFunctionKey: 353 return Qt::Key_Insert; 354 case NSDeleteFunctionKey: 355 return Qt::Key_Delete; 356 case NSPauseFunctionKey: 357 return Qt::Key_Pause; 358 case NSPrintFunctionKey: 359 return Qt::Key_Print; 360 case NSSysReqFunctionKey: 361 return Qt::Key_SysReq; 362 case NSHomeFunctionKey: 363 return Qt::Key_Home; 364 case NSEndFunctionKey: 365 return Qt::Key_End; 366 case NSLeftArrowFunctionKey: 367 return Qt::Key_Left; 368 case NSUpArrowFunctionKey: 369 return Qt::Key_Up; 370 case NSRightArrowFunctionKey: 371 return Qt::Key_Right; 372 case NSDownArrowFunctionKey: 373 return Qt::Key_Down; 374 case NSPageUpFunctionKey: 375 return Qt::Key_PageUp; 376 case NSPageDownFunctionKey: 377 return Qt::Key_PageDown; 378 case NSScrollLockFunctionKey: 379 return Qt::Key_ScrollLock; 380 case NSF1FunctionKey: 381 return Qt::Key_F1; 382 case NSF2FunctionKey: 383 return Qt::Key_F2; 384 case NSF3FunctionKey: 385 return Qt::Key_F3; 386 case NSF4FunctionKey: 387 return Qt::Key_F4; 388 case NSF5FunctionKey: 389 return Qt::Key_F5; 390 case NSF6FunctionKey: 391 return Qt::Key_F6; 392 case NSF7FunctionKey: 393 return Qt::Key_F7; 394 case NSF8FunctionKey: 395 return Qt::Key_F8; 396 case NSF9FunctionKey: 397 return Qt::Key_F9; 398 case NSF10FunctionKey: 399 return Qt::Key_F10; 400 case NSF11FunctionKey: 401 return Qt::Key_F11; 402 case NSF12FunctionKey: 403 return Qt::Key_F12; 404 case NSF13FunctionKey: 405 return Qt::Key_F13; 406 case NSF14FunctionKey: 407 return Qt::Key_F14; 408 case NSF15FunctionKey: 409 return Qt::Key_F15; 410 case NSF16FunctionKey: 411 return Qt::Key_F16; 412 case NSF17FunctionKey: 413 return Qt::Key_F17; 414 case NSF18FunctionKey: 415 return Qt::Key_F18; 416 case NSF19FunctionKey: 417 return Qt::Key_F19; 418 case NSF20FunctionKey: 419 return Qt::Key_F20; 420 case NSF21FunctionKey: 421 return Qt::Key_F21; 422 case NSF22FunctionKey: 423 return Qt::Key_F22; 424 case NSF23FunctionKey: 425 return Qt::Key_F23; 426 case NSF24FunctionKey: 427 return Qt::Key_F24; 428 case NSF25FunctionKey: 429 return Qt::Key_F25; 430 case NSF26FunctionKey: 431 return Qt::Key_F26; 432 case NSF27FunctionKey: 433 return Qt::Key_F27; 434 case NSF28FunctionKey: 435 return Qt::Key_F28; 436 case NSF29FunctionKey: 437 return Qt::Key_F29; 438 case NSF30FunctionKey: 439 return Qt::Key_F30; 440 case NSF31FunctionKey: 441 return Qt::Key_F31; 442 case NSF32FunctionKey: 443 return Qt::Key_F32; 444 case NSF33FunctionKey: 445 return Qt::Key_F33; 446 case NSF34FunctionKey: 447 return Qt::Key_F34; 448 case NSF35FunctionKey: 449 return Qt::Key_F35; 450 case NSMenuFunctionKey: 451 return Qt::Key_Menu; 452 case NSHelpFunctionKey: 453 return Qt::Key_Help; 454 } 455 456 return 0; 457} 458 459QKeySequence KeySequenceFromNSEvent(NSEvent* event) { 460 NSString* str = [event charactersIgnoringModifiers]; 461 NSString* upper = [str uppercaseString]; 462 const char* chars = [upper UTF8String]; 463 NSUInteger modifiers = [event modifierFlags]; 464 int key = 0; 465 unsigned char c = chars[0]; 466 switch (c) { 467 case 0x1b: 468 key = Qt::Key_Escape; 469 break; 470 case 0x09: 471 key = Qt::Key_Tab; 472 break; 473 case 0x0d: 474 key = Qt::Key_Return; 475 break; 476 case 0x08: 477 key = Qt::Key_Backspace; 478 break; 479 case 0x03: 480 key = Qt::Key_Enter; 481 break; 482 } 483 484 if (key == 0) { 485 if (c >= 0x20 && c <= 0x7e) { // ASCII from space to ~ 486 key = c; 487 } else { 488 key = MapFunctionKey([event keyCode]); 489 if (key == 0) { 490 return QKeySequence(); 491 } 492 } 493 } 494 495 if (modifiers & NSShiftKeyMask) { 496 key += Qt::SHIFT; 497 } 498 if (modifiers & NSControlKeyMask) { 499 key += Qt::META; 500 } 501 if (modifiers & NSAlternateKeyMask) { 502 key += Qt::ALT; 503 } 504 if (modifiers & NSCommandKeyMask) { 505 key += Qt::CTRL; 506 } 507 508 return QKeySequence(key); 509} 510 511void DumpDictionary(CFDictionaryRef dict) { 512 NSDictionary* d = (NSDictionary*)dict; 513 NSLog(@"%@", d); 514} 515 516// NSWindowCollectionBehaviorFullScreenPrimary 517static const NSUInteger kFullScreenPrimary = 1 << 7; 518 519void EnableFullScreen(const QWidget& main_window) { 520 NSView* view = reinterpret_cast<NSView*>(main_window.winId()); 521 NSWindow* window = [view window]; 522 [window setCollectionBehavior:kFullScreenPrimary]; 523} 524 525float GetDevicePixelRatio(QWidget* widget) { 526 NSView* view = reinterpret_cast<NSView*>(widget->winId()); 527 return [[view window] backingScaleFactor]; 528} 529 530} // namespace mac 531