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.

618 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()
  139. {
  140. if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0))
  141. if (auto* sourceComp = draggingSource->getComponentUnderMouse())
  142. return (NSView*) sourceComp->getWindowHandle();
  143. jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
  144. return nil;
  145. }
  146. struct TextDragDataProviderClass : public ObjCClass<NSObject>
  147. {
  148. TextDragDataProviderClass() : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
  149. {
  150. addIvar<String*> ("text");
  151. addMethod (@selector (dealloc), dealloc, "v@:");
  152. addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
  153. addProtocol (@protocol (NSPasteboardItemDataProvider));
  154. registerClass();
  155. }
  156. static void setText (id self, const String& text)
  157. {
  158. object_setInstanceVariable (self, "text", new String (text));
  159. }
  160. private:
  161. static void dealloc (id self, SEL)
  162. {
  163. delete getIvar<String*> (self, "text");
  164. sendSuperclassMessage (self, @selector (dealloc));
  165. }
  166. static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
  167. {
  168. if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
  169. if (auto* text = getIvar<String*> (self, "text"))
  170. [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
  171. forType: NSPasteboardTypeString];
  172. }
  173. };
  174. bool DragAndDropContainer::performExternalDragDropOfText (const String& text)
  175. {
  176. if (text.isEmpty())
  177. return false;
  178. if (auto* view = getNSViewForDragEvent())
  179. {
  180. JUCE_AUTORELEASEPOOL
  181. {
  182. if (auto* event = [[view window] currentEvent])
  183. {
  184. static TextDragDataProviderClass dataProviderClass;
  185. id delegate = [dataProviderClass.createInstance() init];
  186. TextDragDataProviderClass::setText (delegate, text);
  187. auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
  188. [pasteboardItem setDataProvider: delegate
  189. forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
  190. auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
  191. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
  192. [dragItem setDraggingFrame: getDragRect (view, event) contents: image];
  193. auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
  194. event: event
  195. source: delegate];
  196. draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
  197. draggingSession.draggingFormation = NSDraggingFormationNone;
  198. return true;
  199. }
  200. }
  201. }
  202. return false;
  203. }
  204. class NSDraggingSourceHelper : public ObjCClass <NSObject <NSDraggingSource>>
  205. {
  206. public:
  207. NSDraggingSourceHelper()
  208. : ObjCClass <NSObject <NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
  209. {
  210. addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
  211. registerClass();
  212. }
  213. private:
  214. static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext)
  215. {
  216. return NSDragOperationCopy;
  217. }
  218. };
  219. static NSDraggingSourceHelper draggingSourceHelper;
  220. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/)
  221. {
  222. if (files.isEmpty())
  223. return false;
  224. if (auto* view = getNSViewForDragEvent())
  225. {
  226. JUCE_AUTORELEASEPOOL
  227. {
  228. if (auto* event = [[view window] currentEvent])
  229. {
  230. auto* dragItems = [[[NSMutableArray alloc] init] autorelease];
  231. for (auto& filename : files)
  232. {
  233. auto* nsFilename = juceStringToNS (filename);
  234. auto* fileURL = [NSURL fileURLWithPath: nsFilename];
  235. auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
  236. auto eventPos = [event locationInWindow];
  237. auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  238. fromView: nil];
  239. auto *dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
  240. [dragItem setDraggingFrame: dragRect
  241. contents: dragImage];
  242. [dragItems addObject: dragItem];
  243. [dragItem release];
  244. }
  245. auto* helper = [draggingSourceHelper.createInstance() autorelease];
  246. if (! [view beginDraggingSessionWithItems: dragItems
  247. event: event
  248. source: helper])
  249. return false;
  250. return true;
  251. }
  252. }
  253. }
  254. return false;
  255. }
  256. //==============================================================================
  257. bool Desktop::canUseSemiTransparentWindows() noexcept
  258. {
  259. return true;
  260. }
  261. Point<float> MouseInputSource::getCurrentRawMousePosition()
  262. {
  263. JUCE_AUTORELEASEPOOL
  264. {
  265. auto p = [NSEvent mouseLocation];
  266. return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
  267. }
  268. }
  269. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  270. {
  271. // this rubbish needs to be done around the warp call, to avoid causing a
  272. // bizarre glitch..
  273. CGAssociateMouseAndMouseCursorPosition (false);
  274. CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
  275. CGAssociateMouseAndMouseCursorPosition (true);
  276. }
  277. double Desktop::getDefaultMasterScale()
  278. {
  279. return 1.0;
  280. }
  281. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  282. {
  283. return upright;
  284. }
  285. //==============================================================================
  286. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
  287. #define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
  288. #endif
  289. #if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
  290. extern "C" { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
  291. #endif
  292. class ScreenSaverDefeater : public Timer
  293. {
  294. public:
  295. #if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
  296. ScreenSaverDefeater()
  297. {
  298. startTimer (5000);
  299. timerCallback();
  300. }
  301. void timerCallback() override
  302. {
  303. if (Process::isForegroundProcess())
  304. {
  305. if (assertion == nullptr)
  306. assertion = new PMAssertion();
  307. }
  308. else
  309. {
  310. assertion = nullptr;
  311. }
  312. }
  313. struct PMAssertion
  314. {
  315. PMAssertion() : assertionID (kIOPMNullAssertionID)
  316. {
  317. IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
  318. kIOPMAssertionLevelOn,
  319. CFSTR ("JUCE Playback"),
  320. &assertionID);
  321. jassert (res == kIOReturnSuccess); ignoreUnused (res);
  322. }
  323. ~PMAssertion()
  324. {
  325. if (assertionID != kIOPMNullAssertionID)
  326. IOPMAssertionRelease (assertionID);
  327. }
  328. IOPMAssertionID assertionID;
  329. };
  330. ScopedPointer<PMAssertion> assertion;
  331. #else
  332. ScreenSaverDefeater()
  333. {
  334. startTimer (10000);
  335. timerCallback();
  336. }
  337. void timerCallback() override
  338. {
  339. if (Process::isForegroundProcess())
  340. UpdateSystemActivity (1 /*UsrActivity*/);
  341. }
  342. #endif
  343. };
  344. static ScopedPointer<ScreenSaverDefeater> screenSaverDefeater;
  345. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  346. {
  347. if (isEnabled)
  348. screenSaverDefeater = nullptr;
  349. else if (screenSaverDefeater == nullptr)
  350. screenSaverDefeater = new ScreenSaverDefeater();
  351. }
  352. bool Desktop::isScreenSaverEnabled()
  353. {
  354. return screenSaverDefeater == nullptr;
  355. }
  356. //==============================================================================
  357. class DisplaySettingsChangeCallback : private DeletedAtShutdown
  358. {
  359. public:
  360. DisplaySettingsChangeCallback()
  361. {
  362. CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
  363. }
  364. ~DisplaySettingsChangeCallback()
  365. {
  366. CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
  367. clearSingletonInstance();
  368. }
  369. static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
  370. {
  371. const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
  372. }
  373. juce_DeclareSingleton_SingleThreaded_Minimal (DisplaySettingsChangeCallback)
  374. private:
  375. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
  376. };
  377. juce_ImplementSingleton_SingleThreaded (DisplaySettingsChangeCallback)
  378. static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
  379. {
  380. r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
  381. return convertToRectInt (r);
  382. }
  383. static Desktop::Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
  384. {
  385. Desktop::Displays::Display d;
  386. d.isMain = (mainScreenBottom == 0);
  387. if (d.isMain)
  388. mainScreenBottom = [s frame].size.height;
  389. d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
  390. d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
  391. d.scale = masterScale;
  392. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
  393. if ([s respondsToSelector: @selector (backingScaleFactor)])
  394. d.scale *= s.backingScaleFactor;
  395. #endif
  396. NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
  397. d.dpi = (dpi.width + dpi.height) / 2.0;
  398. return d;
  399. }
  400. void Desktop::Displays::findDisplays (const float masterScale)
  401. {
  402. JUCE_AUTORELEASEPOOL
  403. {
  404. DisplaySettingsChangeCallback::getInstance();
  405. CGFloat mainScreenBottom = 0;
  406. for (NSScreen* s in [NSScreen screens])
  407. displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
  408. }
  409. }
  410. //==============================================================================
  411. bool juce_areThereAnyAlwaysOnTopWindows()
  412. {
  413. for (NSWindow* window in [NSApp windows])
  414. if ([window level] > NSNormalWindowLevel)
  415. return true;
  416. return false;
  417. }
  418. //==============================================================================
  419. static void selectImageForDrawing (const Image& image)
  420. {
  421. [NSGraphicsContext saveGraphicsState];
  422. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
  423. flipped: false]];
  424. }
  425. static void releaseImageAfterDrawing()
  426. {
  427. [[NSGraphicsContext currentContext] flushGraphics];
  428. [NSGraphicsContext restoreGraphicsState];
  429. }
  430. Image juce_createIconForFile (const File& file)
  431. {
  432. JUCE_AUTORELEASEPOOL
  433. {
  434. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
  435. Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
  436. selectImageForDrawing (result);
  437. [image drawAtPoint: NSMakePoint (0, 0)
  438. fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
  439. operation: NSCompositingOperationSourceOver fraction: 1.0f];
  440. releaseImageAfterDrawing();
  441. return result;
  442. }
  443. }
  444. static Image createNSWindowSnapshot (NSWindow* nsWindow)
  445. {
  446. JUCE_AUTORELEASEPOOL
  447. {
  448. CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
  449. kCGWindowListOptionIncludingWindow,
  450. (CGWindowID) [nsWindow windowNumber],
  451. kCGWindowImageBoundsIgnoreFraming);
  452. NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  453. Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
  454. selectImageForDrawing (result);
  455. [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
  456. releaseImageAfterDrawing();
  457. [bitmapRep release];
  458. CGImageRelease (screenShot);
  459. return result;
  460. }
  461. }
  462. Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
  463. {
  464. if (id windowOrView = (id) nativeWindowHandle)
  465. {
  466. if ([windowOrView isKindOfClass: [NSWindow class]])
  467. return createNSWindowSnapshot ((NSWindow*) windowOrView);
  468. if ([windowOrView isKindOfClass: [NSView class]])
  469. return createNSWindowSnapshot ([(NSView*) windowOrView window]);
  470. }
  471. return Image();
  472. }
  473. //==============================================================================
  474. void SystemClipboard::copyTextToClipboard (const String& text)
  475. {
  476. NSPasteboard* pb = [NSPasteboard generalPasteboard];
  477. [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
  478. owner: nil];
  479. [pb setString: juceStringToNS (text)
  480. forType: NSStringPboardType];
  481. }
  482. String SystemClipboard::getTextFromClipboard()
  483. {
  484. return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
  485. }
  486. void Process::setDockIconVisible (bool isVisible)
  487. {
  488. ProcessSerialNumber psn { 0, kCurrentProcess };
  489. ProcessApplicationTransformState state = isVisible
  490. ? kProcessTransformToForegroundApplication
  491. : kProcessTransformToUIElementApplication;
  492. OSStatus err = TransformProcessType (&psn, state);
  493. jassert (err == 0);
  494. ignoreUnused (err);
  495. }
  496. bool Desktop::isOSXDarkModeActive()
  497. {
  498. return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
  499. isEqualToString: nsStringLiteral ("Dark")];
  500. }