1/* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 The code included in this file is provided under the terms of the ISC license 11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission 12 To use, copy, modify, and/or distribute this software for any purpose with or 13 without fee is hereby granted provided that the above copyright notice and 14 this permission notice appear in all copies. 15 16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 18 DISCLAIMED. 19 20 ============================================================================== 21*/ 22 23namespace juce 24{ 25 26using AppFocusChangeCallback = void (*)(); 27AppFocusChangeCallback appFocusChangeCallback = nullptr; 28 29using CheckEventBlockedByModalComps = bool (*)(NSEvent*); 30CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; 31 32using MenuTrackingChangedCallback = void (*)(bool); 33MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; 34 35//============================================================================== 36struct AppDelegate 37{ 38public: 39 AppDelegate() 40 { 41 static AppDelegateClass cls; 42 delegate = [cls.createInstance() init]; 43 44 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 45 46 [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) 47 name: NSMenuDidBeginTrackingNotification object: nil]; 48 [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) 49 name: NSMenuDidEndTrackingNotification object: nil]; 50 51 if (JUCEApplicationBase::isStandaloneApp()) 52 { 53 [NSApp setDelegate: delegate]; 54 55 [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate 56 selector: @selector (broadcastMessageCallback:) 57 name: getBroadcastEventName() 58 object: nil 59 suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; 60 } 61 else 62 { 63 [center addObserver: delegate selector: @selector (applicationDidResignActive:) 64 name: NSApplicationDidResignActiveNotification object: NSApp]; 65 66 [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) 67 name: NSApplicationDidBecomeActiveNotification object: NSApp]; 68 69 [center addObserver: delegate selector: @selector (applicationWillUnhide:) 70 name: NSApplicationWillUnhideNotification object: NSApp]; 71 } 72 } 73 74 ~AppDelegate() 75 { 76 [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; 77 [[NSNotificationCenter defaultCenter] removeObserver: delegate]; 78 79 if (JUCEApplicationBase::isStandaloneApp()) 80 { 81 [NSApp setDelegate: nil]; 82 83 [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate 84 name: getBroadcastEventName() 85 object: nil]; 86 } 87 88 [delegate release]; 89 } 90 91 static NSString* getBroadcastEventName() 92 { 93 return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); 94 } 95 96 MessageQueue messageQueue; 97 id delegate; 98 99private: 100 //============================================================================== 101 struct AppDelegateClass : public ObjCClass<NSObject> 102 { 103 AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") 104 { 105 addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); 106 addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); 107 addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); 108 addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); 109 addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); 110 addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); 111 addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); 112 addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); 113 addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); 114 addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); 115 addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); 116 addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); 117 addMethod (@selector (dummyMethod), dummyMethod, "v@:"); 118 119 #if JUCE_PUSH_NOTIFICATIONS 120 //============================================================================== 121 addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate"); 122 123 addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); 124 addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); 125 addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); 126 addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); 127 addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); 128 #endif 129 130 registerClass(); 131 } 132 133 private: 134 static void applicationWillFinishLaunching (id self, SEL, NSNotification*) 135 { 136 [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self 137 andSelector: @selector (getUrl:withReplyEvent:) 138 forEventClass: kInternetEventClass 139 andEventID: kAEGetURL]; 140 } 141 142 #if JUCE_PUSH_NOTIFICATIONS 143 static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) 144 { 145 if (notification.userInfo != nil) 146 { 147 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") 148 // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a 149 // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type 150 NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; 151 JUCE_END_IGNORE_WARNINGS_GCC_LIKE 152 153 if (userNotification != nil && userNotification.userInfo != nil) 154 didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); 155 } 156 } 157 #endif 158 159 static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) 160 { 161 if (auto* app = JUCEApplicationBase::getInstance()) 162 { 163 app->systemRequestedQuit(); 164 165 if (! MessageManager::getInstance()->hasStopMessageBeenSent()) 166 return NSTerminateCancel; 167 } 168 169 return NSTerminateNow; 170 } 171 172 static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) 173 { 174 JUCEApplicationBase::appWillTerminateByForce(); 175 } 176 177 static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) 178 { 179 if (auto* app = JUCEApplicationBase::getInstance()) 180 { 181 app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); 182 return YES; 183 } 184 185 return NO; 186 } 187 188 static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) 189 { 190 if (auto* app = JUCEApplicationBase::getInstance()) 191 { 192 StringArray files; 193 194 for (NSString* f in filenames) 195 files.add (quotedIfContainsSpaces (f)); 196 197 if (files.size() > 0) 198 app->anotherInstanceStarted (files.joinIntoString (" ")); 199 } 200 } 201 202 static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } 203 static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } 204 static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } 205 206 static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) 207 { 208 NSDictionary* dict = (NSDictionary*) [n userInfo]; 209 auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); 210 MessageManager::getInstance()->deliverBroadcastMessage (messageString); 211 } 212 213 static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) 214 { 215 if (menuTrackingChangedCallback != nullptr) 216 (*menuTrackingChangedCallback) (true); 217 } 218 219 static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) 220 { 221 if (menuTrackingChangedCallback != nullptr) 222 (*menuTrackingChangedCallback) (false); 223 } 224 225 static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) 226 227 static void focusChanged() 228 { 229 if (appFocusChangeCallback != nullptr) 230 (*appFocusChangeCallback)(); 231 } 232 233 static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) 234 { 235 if (auto* app = JUCEApplicationBase::getInstance()) 236 app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); 237 } 238 239 static String quotedIfContainsSpaces (NSString* file) 240 { 241 String s (nsStringToJuce (file)); 242 s = s.unquoted().replace ("\"", "\\\""); 243 244 if (s.containsChar (' ')) 245 s = s.quoted(); 246 247 return s; 248 } 249 250 #if JUCE_PUSH_NOTIFICATIONS 251 //============================================================================== 252 static void setPushNotificationsDelegate (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate) 253 { 254 object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); 255 } 256 257 static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self) 258 { 259 return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate"); 260 } 261 262 static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) 263 { 264 auto* delegate = getPushNotificationsDelegate (self); 265 266 SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:"); 267 268 if (delegate != nil && [delegate respondsToSelector: selector]) 269 { 270 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; 271 [invocation setSelector: selector]; 272 [invocation setTarget: delegate]; 273 [invocation setArgument: &application atIndex:2]; 274 [invocation setArgument: &deviceToken atIndex:3]; 275 276 [invocation invoke]; 277 } 278 } 279 280 static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) 281 { 282 auto* delegate = getPushNotificationsDelegate (self); 283 284 SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:"); 285 286 if (delegate != nil && [delegate respondsToSelector: selector]) 287 { 288 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; 289 [invocation setSelector: selector]; 290 [invocation setTarget: delegate]; 291 [invocation setArgument: &application atIndex:2]; 292 [invocation setArgument: &error atIndex:3]; 293 294 [invocation invoke]; 295 } 296 } 297 298 static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) 299 { 300 auto* delegate = getPushNotificationsDelegate (self); 301 302 SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:"); 303 304 if (delegate != nil && [delegate respondsToSelector: selector]) 305 { 306 NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; 307 [invocation setSelector: selector]; 308 [invocation setTarget: delegate]; 309 [invocation setArgument: &application atIndex:2]; 310 [invocation setArgument: &userInfo atIndex:3]; 311 312 [invocation invoke]; 313 } 314 } 315 #endif 316 }; 317}; 318 319//============================================================================== 320void MessageManager::runDispatchLoop() 321{ 322 if (quitMessagePosted.get() == 0) // check that the quit message wasn't already posted.. 323 { 324 JUCE_AUTORELEASEPOOL 325 { 326 // must only be called by the message thread! 327 jassert (isThisTheMessageThread()); 328 329 #if JUCE_PROJUCER_LIVE_BUILD 330 runDispatchLoopUntil (std::numeric_limits<int>::max()); 331 #else 332 #if JUCE_CATCH_UNHANDLED_EXCEPTIONS 333 @try 334 { 335 [NSApp run]; 336 } 337 @catch (NSException* e) 338 { 339 // An AppKit exception will kill the app, but at least this provides a chance to log it., 340 std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]); 341 JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__); 342 } 343 @finally 344 { 345 } 346 #else 347 [NSApp run]; 348 #endif 349 #endif 350 } 351 } 352} 353 354static void shutdownNSApp() 355{ 356 [NSApp stop: nil]; 357 [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; 358} 359 360void MessageManager::stopDispatchLoop() 361{ 362 #if JUCE_PROJUCER_LIVE_BUILD 363 quitMessagePosted = true; 364 #else 365 366 if (isThisTheMessageThread()) 367 { 368 quitMessagePosted = true; 369 shutdownNSApp(); 370 } 371 else 372 { 373 struct QuitCallback : public CallbackMessage 374 { 375 QuitCallback() {} 376 void messageCallback() override { MessageManager::getInstance()->stopDispatchLoop(); } 377 }; 378 379 (new QuitCallback())->post(); 380 } 381 #endif 382} 383 384#if JUCE_MODAL_LOOPS_PERMITTED 385bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) 386{ 387 jassert (millisecondsToRunFor >= 0); 388 jassert (isThisTheMessageThread()); // must only be called by the message thread 389 390 auto endTime = Time::currentTimeMillis() + millisecondsToRunFor; 391 392 while (quitMessagePosted.get() == 0) 393 { 394 JUCE_AUTORELEASEPOOL 395 { 396 auto msRemaining = endTime - Time::currentTimeMillis(); 397 398 if (msRemaining <= 0) 399 break; 400 401 CFRunLoopRunInMode (kCFRunLoopDefaultMode, jmin (1.0, msRemaining * 0.001), true); 402 403 if (NSEvent* e = [NSApp nextEventMatchingMask: NSEventMaskAny 404 untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001] 405 inMode: NSDefaultRunLoopMode 406 dequeue: YES]) 407 if (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)) 408 [NSApp sendEvent: e]; 409 } 410 } 411 412 return quitMessagePosted.get() == 0; 413} 414#endif 415 416//============================================================================== 417void initialiseNSApplication(); 418void initialiseNSApplication() 419{ 420 JUCE_AUTORELEASEPOOL 421 { 422 [NSApplication sharedApplication]; 423 } 424} 425 426static AppDelegate* appDelegate = nullptr; 427 428void MessageManager::doPlatformSpecificInitialisation() 429{ 430 if (appDelegate == nil) 431 appDelegate = new AppDelegate(); 432} 433 434void MessageManager::doPlatformSpecificShutdown() 435{ 436 delete appDelegate; 437 appDelegate = nullptr; 438} 439 440bool MessageManager::postMessageToSystemQueue (MessageBase* message) 441{ 442 jassert (appDelegate != nil); 443 appDelegate->messageQueue.post (message); 444 return true; 445} 446 447void MessageManager::broadcastMessage (const String& message) 448{ 449 NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message) 450 forKey: nsStringLiteral ("message")]; 451 452 [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroadcastEventName() 453 object: nil 454 userInfo: info]; 455} 456 457// Special function used by some plugin classes to re-post carbon events 458void __attribute__ ((visibility("default"))) repostCurrentNSEvent(); 459void __attribute__ ((visibility("default"))) repostCurrentNSEvent() 460{ 461 struct EventReposter : public CallbackMessage 462 { 463 EventReposter() : e ([[NSApp currentEvent] retain]) {} 464 ~EventReposter() override { [e release]; } 465 466 void messageCallback() override 467 { 468 [NSApp postEvent: e atStart: YES]; 469 } 470 471 NSEvent* e; 472 }; 473 474 (new EventReposter())->post(); 475} 476 477 478//============================================================================== 479#if JUCE_MAC 480struct MountedVolumeListChangeDetector::Pimpl 481{ 482 Pimpl (MountedVolumeListChangeDetector& d) : owner (d) 483 { 484 static ObserverClass cls; 485 delegate = [cls.createInstance() init]; 486 ObserverClass::setOwner (delegate, this); 487 488 NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter]; 489 490 [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil]; 491 [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil]; 492 } 493 494 ~Pimpl() 495 { 496 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate]; 497 [delegate release]; 498 } 499 500private: 501 MountedVolumeListChangeDetector& owner; 502 id delegate; 503 504 struct ObserverClass : public ObjCClass<NSObject> 505 { 506 ObserverClass() : ObjCClass<NSObject> ("JUCEDriveObserver_") 507 { 508 addIvar<Pimpl*> ("owner"); 509 addMethod (@selector (changed:), changed, "v@:@"); 510 addProtocol (@protocol (NSTextInput)); 511 registerClass(); 512 } 513 514 static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); } 515 static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } 516 517 static void changed (id self, SEL, NSNotification*) 518 { 519 getOwner (self)->owner.mountedVolumeListChanged(); 520 } 521 }; 522}; 523 524MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl.reset (new Pimpl (*this)); } 525MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} 526#endif 527 528} // namespace juce 529