The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

622 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. } // namespace juce
  20. #include "../../juce_core/native/juce_osx_ObjCHelpers.h"
  21. namespace juce {
  22. //==============================================================================
  23. void LookAndFeel::playAlertSound()
  24. {
  25. NSBeep();
  26. }
  27. //==============================================================================
  28. class OSXMessageBox : private AsyncUpdater
  29. {
  30. public:
  31. OSXMessageBox (AlertWindow::AlertIconType type, const String& t, const String& m,
  32. const char* b1, const char* b2, const char* b3,
  33. ModalComponentManager::Callback* c, const bool runAsync)
  34. : iconType (type), title (t), message (m), callback (c),
  35. button1 (b1), button2 (b2), button3 (b3)
  36. {
  37. if (runAsync)
  38. triggerAsyncUpdate();
  39. }
  40. int getResult() const
  41. {
  42. switch (getRawResult())
  43. {
  44. case NSAlertFirstButtonReturn: return 1;
  45. case NSAlertThirdButtonReturn: return 2;
  46. default: return 0;
  47. }
  48. }
  49. static int show (AlertWindow::AlertIconType iconType, const String& title, const String& message,
  50. ModalComponentManager::Callback* callback, const char* b1, const char* b2, const char* b3,
  51. bool runAsync)
  52. {
  53. ScopedPointer<OSXMessageBox> mb (new OSXMessageBox (iconType, title, message, b1, b2, b3,
  54. callback, runAsync));
  55. if (! runAsync)
  56. return mb->getResult();
  57. mb.release();
  58. return 0;
  59. }
  60. private:
  61. AlertWindow::AlertIconType iconType;
  62. String title, message;
  63. ScopedPointer<ModalComponentManager::Callback> callback;
  64. const char* button1;
  65. const char* button2;
  66. const char* button3;
  67. void handleAsyncUpdate() override
  68. {
  69. const int result = getResult();
  70. if (callback != nullptr)
  71. callback->modalStateFinished (result);
  72. delete this;
  73. }
  74. NSInteger getRawResult() const
  75. {
  76. NSAlert* alert = [[[NSAlert alloc] init] autorelease];
  77. [alert setMessageText: juceStringToNS (title)];
  78. [alert setInformativeText: juceStringToNS (message)];
  79. [alert setAlertStyle: iconType == AlertWindow::WarningIcon ? NSAlertStyleCritical
  80. : NSAlertStyleInformational];
  81. addButton (alert, button1);
  82. addButton (alert, button2);
  83. addButton (alert, button3);
  84. return [alert runModal];
  85. }
  86. static void addButton (NSAlert* alert, const char* button)
  87. {
  88. if (button != nullptr)
  89. [alert addButtonWithTitle: juceStringToNS (TRANS (button))];
  90. }
  91. };
  92. #if JUCE_MODAL_LOOPS_PERMITTED
  93. void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
  94. const String& title, const String& message,
  95. Component* /*associatedComponent*/)
  96. {
  97. OSXMessageBox::show (iconType, title, message, nullptr, "OK", nullptr, nullptr, false);
  98. }
  99. #endif
  100. void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
  101. const String& title, const String& message,
  102. Component* /*associatedComponent*/,
  103. ModalComponentManager::Callback* callback)
  104. {
  105. OSXMessageBox::show (iconType, title, message, callback, "OK", nullptr, nullptr, true);
  106. }
  107. bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
  108. const String& title, const String& message,
  109. Component* /*associatedComponent*/,
  110. ModalComponentManager::Callback* callback)
  111. {
  112. return OSXMessageBox::show (iconType, title, message, callback,
  113. "OK", "Cancel", nullptr, callback != nullptr) == 1;
  114. }
  115. int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
  116. const String& title, const String& message,
  117. Component* /*associatedComponent*/,
  118. ModalComponentManager::Callback* callback)
  119. {
  120. return OSXMessageBox::show (iconType, title, message, callback,
  121. "Yes", "Cancel", "No", callback != nullptr);
  122. }
  123. int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType,
  124. const String& title, const String& message,
  125. Component* /*associatedComponent*/,
  126. ModalComponentManager::Callback* callback)
  127. {
  128. return OSXMessageBox::show (iconType, title, message, callback,
  129. "Yes", "No", nullptr, callback != nullptr);
  130. }
  131. //==============================================================================
  132. static NSRect getDragRect (NSView* view, NSEvent* event)
  133. {
  134. auto eventPos = [event locationInWindow];
  135. return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  136. fromView: nil];
  137. }
  138. NSView* getNSViewForDragEvent (Component* sourceComp)
  139. {
  140. if (sourceComp == nullptr)
  141. if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0))
  142. sourceComp = draggingSource->getComponentUnderMouse();
  143. if (sourceComp != nullptr)
  144. return (NSView*) sourceComp->getWindowHandle();
  145. jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
  146. return nil;
  147. }
  148. struct TextDragDataProviderClass : public ObjCClass<NSObject>
  149. {
  150. TextDragDataProviderClass() : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
  151. {
  152. addIvar<String*> ("text");
  153. addMethod (@selector (dealloc), dealloc, "v@:");
  154. addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
  155. addProtocol (@protocol (NSPasteboardItemDataProvider));
  156. registerClass();
  157. }
  158. static void setText (id self, const String& text)
  159. {
  160. object_setInstanceVariable (self, "text", new String (text));
  161. }
  162. private:
  163. static void dealloc (id self, SEL)
  164. {
  165. delete getIvar<String*> (self, "text");
  166. sendSuperclassMessage (self, @selector (dealloc));
  167. }
  168. static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
  169. {
  170. if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
  171. if (auto* text = getIvar<String*> (self, "text"))
  172. [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
  173. forType: NSPasteboardTypeString];
  174. }
  175. };
  176. bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent)
  177. {
  178. if (text.isEmpty())
  179. return false;
  180. if (auto* view = getNSViewForDragEvent (sourceComponent))
  181. {
  182. JUCE_AUTORELEASEPOOL
  183. {
  184. if (auto* event = [[view window] currentEvent])
  185. {
  186. static TextDragDataProviderClass dataProviderClass;
  187. id delegate = [dataProviderClass.createInstance() init];
  188. TextDragDataProviderClass::setText (delegate, text);
  189. auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
  190. [pasteboardItem setDataProvider: delegate
  191. forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
  192. auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
  193. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
  194. [dragItem setDraggingFrame: getDragRect (view, event) contents: image];
  195. auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
  196. event: event
  197. source: delegate];
  198. draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
  199. draggingSession.draggingFormation = NSDraggingFormationNone;
  200. return true;
  201. }
  202. }
  203. }
  204. return false;
  205. }
  206. class NSDraggingSourceHelper : public ObjCClass <NSObject <NSDraggingSource>>
  207. {
  208. public:
  209. NSDraggingSourceHelper()
  210. : ObjCClass <NSObject <NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
  211. {
  212. addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
  213. registerClass();
  214. }
  215. private:
  216. static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext)
  217. {
  218. return NSDragOperationCopy;
  219. }
  220. };
  221. static NSDraggingSourceHelper draggingSourceHelper;
  222. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/,
  223. Component* sourceComponent)
  224. {
  225. if (files.isEmpty())
  226. return false;
  227. if (auto* view = getNSViewForDragEvent (sourceComponent))
  228. {
  229. JUCE_AUTORELEASEPOOL
  230. {
  231. if (auto* event = [[view window] currentEvent])
  232. {
  233. auto* dragItems = [[[NSMutableArray alloc] init] autorelease];
  234. for (auto& filename : files)
  235. {
  236. auto* nsFilename = juceStringToNS (filename);
  237. auto* fileURL = [NSURL fileURLWithPath: nsFilename];
  238. auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
  239. auto eventPos = [event locationInWindow];
  240. auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  241. fromView: nil];
  242. auto *dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
  243. [dragItem setDraggingFrame: dragRect
  244. contents: dragImage];
  245. [dragItems addObject: dragItem];
  246. [dragItem release];
  247. }
  248. auto* helper = [draggingSourceHelper.createInstance() autorelease];
  249. if (! [view beginDraggingSessionWithItems: dragItems
  250. event: event
  251. source: helper])
  252. return false;
  253. return true;
  254. }
  255. }
  256. }
  257. return false;
  258. }
  259. //==============================================================================
  260. bool Desktop::canUseSemiTransparentWindows() noexcept
  261. {
  262. return true;
  263. }
  264. Point<float> MouseInputSource::getCurrentRawMousePosition()
  265. {
  266. JUCE_AUTORELEASEPOOL
  267. {
  268. auto p = [NSEvent mouseLocation];
  269. return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
  270. }
  271. }
  272. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  273. {
  274. // this rubbish needs to be done around the warp call, to avoid causing a
  275. // bizarre glitch..
  276. CGAssociateMouseAndMouseCursorPosition (false);
  277. CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
  278. CGAssociateMouseAndMouseCursorPosition (true);
  279. }
  280. double Desktop::getDefaultMasterScale()
  281. {
  282. return 1.0;
  283. }
  284. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  285. {
  286. return upright;
  287. }
  288. //==============================================================================
  289. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
  290. #define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
  291. #endif
  292. #if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
  293. extern "C" { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
  294. #endif
  295. class ScreenSaverDefeater : public Timer
  296. {
  297. public:
  298. #if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
  299. ScreenSaverDefeater()
  300. {
  301. startTimer (5000);
  302. timerCallback();
  303. }
  304. void timerCallback() override
  305. {
  306. if (Process::isForegroundProcess())
  307. {
  308. if (assertion == nullptr)
  309. assertion = new PMAssertion();
  310. }
  311. else
  312. {
  313. assertion = nullptr;
  314. }
  315. }
  316. struct PMAssertion
  317. {
  318. PMAssertion() : assertionID (kIOPMNullAssertionID)
  319. {
  320. IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
  321. kIOPMAssertionLevelOn,
  322. CFSTR ("JUCE Playback"),
  323. &assertionID);
  324. jassert (res == kIOReturnSuccess); ignoreUnused (res);
  325. }
  326. ~PMAssertion()
  327. {
  328. if (assertionID != kIOPMNullAssertionID)
  329. IOPMAssertionRelease (assertionID);
  330. }
  331. IOPMAssertionID assertionID;
  332. };
  333. ScopedPointer<PMAssertion> assertion;
  334. #else
  335. ScreenSaverDefeater()
  336. {
  337. startTimer (10000);
  338. timerCallback();
  339. }
  340. void timerCallback() override
  341. {
  342. if (Process::isForegroundProcess())
  343. UpdateSystemActivity (1 /*UsrActivity*/);
  344. }
  345. #endif
  346. };
  347. static ScopedPointer<ScreenSaverDefeater> screenSaverDefeater;
  348. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  349. {
  350. if (isEnabled)
  351. screenSaverDefeater = nullptr;
  352. else if (screenSaverDefeater == nullptr)
  353. screenSaverDefeater = new ScreenSaverDefeater();
  354. }
  355. bool Desktop::isScreenSaverEnabled()
  356. {
  357. return screenSaverDefeater == nullptr;
  358. }
  359. //==============================================================================
  360. class DisplaySettingsChangeCallback : private DeletedAtShutdown
  361. {
  362. public:
  363. DisplaySettingsChangeCallback()
  364. {
  365. CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
  366. }
  367. ~DisplaySettingsChangeCallback()
  368. {
  369. CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
  370. clearSingletonInstance();
  371. }
  372. static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
  373. {
  374. const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
  375. }
  376. juce_DeclareSingleton_SingleThreaded_Minimal (DisplaySettingsChangeCallback)
  377. private:
  378. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
  379. };
  380. juce_ImplementSingleton_SingleThreaded (DisplaySettingsChangeCallback)
  381. static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
  382. {
  383. r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
  384. return convertToRectInt (r);
  385. }
  386. static Desktop::Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
  387. {
  388. Desktop::Displays::Display d;
  389. d.isMain = (mainScreenBottom == 0);
  390. if (d.isMain)
  391. mainScreenBottom = [s frame].size.height;
  392. d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
  393. d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
  394. d.scale = masterScale;
  395. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
  396. if ([s respondsToSelector: @selector (backingScaleFactor)])
  397. d.scale *= s.backingScaleFactor;
  398. #endif
  399. NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
  400. d.dpi = (dpi.width + dpi.height) / 2.0;
  401. return d;
  402. }
  403. void Desktop::Displays::findDisplays (const float masterScale)
  404. {
  405. JUCE_AUTORELEASEPOOL
  406. {
  407. DisplaySettingsChangeCallback::getInstance();
  408. CGFloat mainScreenBottom = 0;
  409. for (NSScreen* s in [NSScreen screens])
  410. displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
  411. }
  412. }
  413. //==============================================================================
  414. bool juce_areThereAnyAlwaysOnTopWindows()
  415. {
  416. for (NSWindow* window in [NSApp windows])
  417. if ([window level] > NSNormalWindowLevel)
  418. return true;
  419. return false;
  420. }
  421. //==============================================================================
  422. static void selectImageForDrawing (const Image& image)
  423. {
  424. [NSGraphicsContext saveGraphicsState];
  425. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
  426. flipped: false]];
  427. }
  428. static void releaseImageAfterDrawing()
  429. {
  430. [[NSGraphicsContext currentContext] flushGraphics];
  431. [NSGraphicsContext restoreGraphicsState];
  432. }
  433. Image juce_createIconForFile (const File& file)
  434. {
  435. JUCE_AUTORELEASEPOOL
  436. {
  437. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
  438. Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
  439. selectImageForDrawing (result);
  440. [image drawAtPoint: NSMakePoint (0, 0)
  441. fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
  442. operation: NSCompositingOperationSourceOver fraction: 1.0f];
  443. releaseImageAfterDrawing();
  444. return result;
  445. }
  446. }
  447. static Image createNSWindowSnapshot (NSWindow* nsWindow)
  448. {
  449. JUCE_AUTORELEASEPOOL
  450. {
  451. CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
  452. kCGWindowListOptionIncludingWindow,
  453. (CGWindowID) [nsWindow windowNumber],
  454. kCGWindowImageBoundsIgnoreFraming);
  455. NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  456. Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
  457. selectImageForDrawing (result);
  458. [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
  459. releaseImageAfterDrawing();
  460. [bitmapRep release];
  461. CGImageRelease (screenShot);
  462. return result;
  463. }
  464. }
  465. Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
  466. {
  467. if (id windowOrView = (id) nativeWindowHandle)
  468. {
  469. if ([windowOrView isKindOfClass: [NSWindow class]])
  470. return createNSWindowSnapshot ((NSWindow*) windowOrView);
  471. if ([windowOrView isKindOfClass: [NSView class]])
  472. return createNSWindowSnapshot ([(NSView*) windowOrView window]);
  473. }
  474. return Image();
  475. }
  476. //==============================================================================
  477. void SystemClipboard::copyTextToClipboard (const String& text)
  478. {
  479. NSPasteboard* pb = [NSPasteboard generalPasteboard];
  480. [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
  481. owner: nil];
  482. [pb setString: juceStringToNS (text)
  483. forType: NSStringPboardType];
  484. }
  485. String SystemClipboard::getTextFromClipboard()
  486. {
  487. return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
  488. }
  489. void Process::setDockIconVisible (bool isVisible)
  490. {
  491. ProcessSerialNumber psn { 0, kCurrentProcess };
  492. ProcessApplicationTransformState state = isVisible
  493. ? kProcessTransformToForegroundApplication
  494. : kProcessTransformToUIElementApplication;
  495. OSStatus err = TransformProcessType (&psn, state);
  496. jassert (err == 0);
  497. ignoreUnused (err);
  498. }
  499. bool Desktop::isOSXDarkModeActive()
  500. {
  501. return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
  502. isEqualToString: nsStringLiteral ("Dark")];
  503. }