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.

589 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. void LookAndFeel::playAlertSound()
  21. {
  22. NSBeep();
  23. }
  24. //==============================================================================
  25. static NSRect getDragRect (NSView* view, NSEvent* event)
  26. {
  27. auto eventPos = [event locationInWindow];
  28. return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  29. fromView: nil];
  30. }
  31. static NSView* getNSViewForDragEvent (Component* sourceComp)
  32. {
  33. if (sourceComp == nullptr)
  34. if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0))
  35. sourceComp = draggingSource->getComponentUnderMouse();
  36. if (sourceComp != nullptr)
  37. return (NSView*) sourceComp->getWindowHandle();
  38. jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
  39. return nil;
  40. }
  41. class NSDraggingSourceHelper final : public ObjCClass<NSObject<NSDraggingSource>>
  42. {
  43. public:
  44. static void setText (id self, const String& text)
  45. {
  46. object_setInstanceVariable (self, "text", new String (text));
  47. }
  48. static void setCompletionCallback (id self, std::function<void()> cb)
  49. {
  50. object_setInstanceVariable (self, "callback", new std::function<void()> (cb));
  51. }
  52. static void setDragOperation (id self, NSDragOperation op)
  53. {
  54. object_setInstanceVariable (self, "operation", new NSDragOperation (op));
  55. }
  56. static NSDraggingSourceHelper& get()
  57. {
  58. static NSDraggingSourceHelper draggingSourceHelper;
  59. return draggingSourceHelper;
  60. }
  61. private:
  62. NSDraggingSourceHelper()
  63. : ObjCClass ("JUCENSDraggingSourceHelper_")
  64. {
  65. addIvar<std::function<void()>*> ("callback");
  66. addIvar<String*> ("text");
  67. addIvar<NSDragOperation*> ("operation");
  68. addMethod (@selector (dealloc), [] (id self, SEL)
  69. {
  70. delete getIvar<String*> (self, "text");
  71. delete getIvar<std::function<void()>*> (self, "callback");
  72. delete getIvar<NSDragOperation*> (self, "operation");
  73. sendSuperclassMessage<void> (self, @selector (dealloc));
  74. });
  75. addMethod (@selector (pasteboard:item:provideDataForType:), [] (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
  76. {
  77. if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
  78. if (auto* text = getIvar<String*> (self, "text"))
  79. [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
  80. forType: NSPasteboardTypeString];
  81. });
  82. addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), [] (id self, SEL, NSDraggingSession*, NSDraggingContext)
  83. {
  84. return *getIvar<NSDragOperation*> (self, "operation");
  85. });
  86. addMethod (@selector (draggingSession:endedAtPoint:operation:), [] (id self, SEL, NSDraggingSession*, NSPoint p, NSDragOperation)
  87. {
  88. // Our view doesn't receive a mouse up when the drag ends so we need to generate one here and send it...
  89. if (auto* view = getNSViewForDragEvent (nullptr))
  90. if (auto* cgEvent = CGEventCreateMouseEvent (nullptr, kCGEventLeftMouseUp, CGPointMake (p.x, p.y), kCGMouseButtonLeft))
  91. if (id e = [NSEvent eventWithCGEvent: cgEvent])
  92. [view mouseUp: e];
  93. if (auto* cb = getIvar<std::function<void()>*> (self, "callback"))
  94. cb->operator()();
  95. });
  96. addProtocol (@protocol (NSPasteboardItemDataProvider));
  97. registerClass();
  98. }
  99. };
  100. bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent,
  101. std::function<void()> callback)
  102. {
  103. if (text.isEmpty())
  104. return false;
  105. if (auto* view = getNSViewForDragEvent (sourceComponent))
  106. {
  107. JUCE_AUTORELEASEPOOL
  108. {
  109. if (auto event = [[view window] currentEvent])
  110. {
  111. id helper = [NSDraggingSourceHelper::get().createInstance() init];
  112. NSDraggingSourceHelper::setText (helper, text);
  113. NSDraggingSourceHelper::setDragOperation (helper, NSDragOperationCopy);
  114. if (callback != nullptr)
  115. NSDraggingSourceHelper::setCompletionCallback (helper, callback);
  116. auto pasteboardItem = [[NSPasteboardItem new] autorelease];
  117. [pasteboardItem setDataProvider: helper
  118. forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
  119. auto dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
  120. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
  121. [dragItem setDraggingFrame: getDragRect (view, event) contents: image];
  122. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
  123. if (auto session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
  124. event: event
  125. source: helper])
  126. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  127. {
  128. session.animatesToStartingPositionsOnCancelOrFail = YES;
  129. session.draggingFormation = NSDraggingFormationNone;
  130. return true;
  131. }
  132. }
  133. }
  134. }
  135. return false;
  136. }
  137. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
  138. Component* sourceComponent, std::function<void()> callback)
  139. {
  140. if (files.isEmpty())
  141. return false;
  142. if (auto* view = getNSViewForDragEvent (sourceComponent))
  143. {
  144. JUCE_AUTORELEASEPOOL
  145. {
  146. if (auto event = [[view window] currentEvent])
  147. {
  148. auto dragItems = [[[NSMutableArray alloc] init] autorelease];
  149. for (auto& filename : files)
  150. {
  151. auto* nsFilename = juceStringToNS (filename);
  152. auto fileURL = [NSURL fileURLWithPath: nsFilename];
  153. auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
  154. auto eventPos = [event locationInWindow];
  155. auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  156. fromView: nil];
  157. auto dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
  158. [dragItem setDraggingFrame: dragRect
  159. contents: dragImage];
  160. [dragItems addObject: dragItem];
  161. [dragItem release];
  162. }
  163. auto helper = [NSDraggingSourceHelper::get().createInstance() autorelease];
  164. if (callback != nullptr)
  165. NSDraggingSourceHelper::setCompletionCallback (helper, callback);
  166. NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove
  167. : NSDragOperationCopy);
  168. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
  169. return [view beginDraggingSessionWithItems: dragItems
  170. event: event
  171. source: helper] != nullptr;
  172. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  173. }
  174. }
  175. }
  176. return false;
  177. }
  178. //==============================================================================
  179. bool Desktop::canUseSemiTransparentWindows() noexcept
  180. {
  181. return true;
  182. }
  183. Point<float> MouseInputSource::getCurrentRawMousePosition()
  184. {
  185. JUCE_AUTORELEASEPOOL
  186. {
  187. auto p = [NSEvent mouseLocation];
  188. return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
  189. }
  190. }
  191. static ComponentPeer* findPeerContainingPoint (Point<float> globalPos)
  192. {
  193. for (int i = 0; i < juce::ComponentPeer::getNumPeers(); ++i)
  194. {
  195. auto* peer = juce::ComponentPeer::getPeer (i);
  196. if (peer->contains (peer->globalToLocal (globalPos).toInt(), false))
  197. return peer;
  198. }
  199. return nullptr;
  200. }
  201. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  202. {
  203. const auto oldPosition = Desktop::getInstance().getMainMouseSource().getRawScreenPosition();
  204. // this rubbish needs to be done around the warp call, to avoid causing a
  205. // bizarre glitch..
  206. CGAssociateMouseAndMouseCursorPosition (false);
  207. CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
  208. CGAssociateMouseAndMouseCursorPosition (true);
  209. // Mouse enter and exit events seem to be always generated as a consequence of programmatically
  210. // moving the mouse. However, when the mouse stays within the same peer no mouse move event is
  211. // generated, and we lose track of the correct Component under the mouse. Hence, we need to
  212. // generate this missing event here.
  213. if (auto* peer = findPeerContainingPoint (newPosition); peer != nullptr
  214. && peer == findPeerContainingPoint (oldPosition))
  215. {
  216. peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
  217. peer->globalToLocal (newPosition),
  218. ModifierKeys::currentModifiers,
  219. 0.0f,
  220. 0.0f,
  221. Time::currentTimeMillis());
  222. }
  223. }
  224. double Desktop::getDefaultMasterScale()
  225. {
  226. return 1.0;
  227. }
  228. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  229. {
  230. return upright;
  231. }
  232. bool Desktop::isDarkModeActive() const
  233. {
  234. return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
  235. isEqualToString: nsStringLiteral ("Dark")];
  236. }
  237. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  238. static const auto darkModeSelector = @selector (darkModeChanged:);
  239. static const auto keyboardVisibilitySelector = @selector (keyboardVisiblityChanged:);
  240. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  241. class Desktop::NativeDarkModeChangeDetectorImpl
  242. {
  243. public:
  244. NativeDarkModeChangeDetectorImpl()
  245. {
  246. static DelegateClass delegateClass;
  247. delegate.reset ([delegateClass.createInstance() init]);
  248. observer.emplace (delegate.get(),
  249. darkModeSelector,
  250. @"AppleInterfaceThemeChangedNotification",
  251. nil,
  252. [NSDistributedNotificationCenter class]);
  253. }
  254. private:
  255. struct DelegateClass final : public ObjCClass<NSObject>
  256. {
  257. DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
  258. {
  259. addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
  260. registerClass();
  261. }
  262. };
  263. NSUniquePtr<NSObject> delegate;
  264. Optional<ScopedNotificationCenterObserver> observer;
  265. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
  266. };
  267. std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
  268. {
  269. return std::make_unique<NativeDarkModeChangeDetectorImpl>();
  270. }
  271. //==============================================================================
  272. class ScreenSaverDefeater final : public Timer
  273. {
  274. public:
  275. ScreenSaverDefeater()
  276. {
  277. startTimer (5000);
  278. timerCallback();
  279. }
  280. void timerCallback() override
  281. {
  282. if (Process::isForegroundProcess())
  283. {
  284. if (assertion == nullptr)
  285. assertion.reset (new PMAssertion());
  286. }
  287. else
  288. {
  289. assertion.reset();
  290. }
  291. }
  292. struct PMAssertion
  293. {
  294. PMAssertion() : assertionID (kIOPMNullAssertionID)
  295. {
  296. [[maybe_unused]] IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
  297. kIOPMAssertionLevelOn,
  298. CFSTR ("JUCE Playback"),
  299. &assertionID);
  300. jassert (res == kIOReturnSuccess);
  301. }
  302. ~PMAssertion()
  303. {
  304. if (assertionID != kIOPMNullAssertionID)
  305. IOPMAssertionRelease (assertionID);
  306. }
  307. IOPMAssertionID assertionID;
  308. };
  309. std::unique_ptr<PMAssertion> assertion;
  310. };
  311. static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
  312. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  313. {
  314. if (isEnabled)
  315. screenSaverDefeater.reset();
  316. else if (screenSaverDefeater == nullptr)
  317. screenSaverDefeater.reset (new ScreenSaverDefeater());
  318. }
  319. bool Desktop::isScreenSaverEnabled()
  320. {
  321. return screenSaverDefeater == nullptr;
  322. }
  323. //==============================================================================
  324. struct DisplaySettingsChangeCallback final : private DeletedAtShutdown
  325. {
  326. DisplaySettingsChangeCallback()
  327. {
  328. CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallback, this);
  329. }
  330. ~DisplaySettingsChangeCallback()
  331. {
  332. CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallback, this);
  333. clearSingletonInstance();
  334. }
  335. static void displayReconfigurationCallback (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void* userInfo)
  336. {
  337. if (auto* thisPtr = static_cast<DisplaySettingsChangeCallback*> (userInfo))
  338. NullCheckedInvocation::invoke (thisPtr->forceDisplayUpdate);
  339. }
  340. std::function<void()> forceDisplayUpdate;
  341. JUCE_DECLARE_SINGLETON (DisplaySettingsChangeCallback, false)
  342. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
  343. };
  344. JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback)
  345. static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
  346. {
  347. r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
  348. return convertToRectInt (r);
  349. }
  350. static Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
  351. {
  352. Displays::Display d;
  353. d.isMain = (approximatelyEqual (mainScreenBottom, 0.0));
  354. if (d.isMain)
  355. mainScreenBottom = [s frame].size.height;
  356. d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
  357. d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
  358. d.scale = masterScale;
  359. if ([s respondsToSelector: @selector (backingScaleFactor)])
  360. d.scale *= s.backingScaleFactor;
  361. NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
  362. d.dpi = (dpi.width + dpi.height) / 2.0;
  363. #if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
  364. if (@available (macOS 12.0, *))
  365. {
  366. const auto safeInsets = [s safeAreaInsets];
  367. d.safeAreaInsets = detail::WindowingHelpers::roundToInt (BorderSize<double> { safeInsets.top,
  368. safeInsets.left,
  369. safeInsets.bottom,
  370. safeInsets.right }.multipliedBy (1.0 / (double) masterScale));
  371. }
  372. #endif
  373. return d;
  374. }
  375. void Displays::findDisplays (const float masterScale)
  376. {
  377. JUCE_AUTORELEASEPOOL
  378. {
  379. if (DisplaySettingsChangeCallback::getInstanceWithoutCreating() == nullptr)
  380. DisplaySettingsChangeCallback::getInstance()->forceDisplayUpdate = [this] { refresh(); };
  381. CGFloat mainScreenBottom = 0;
  382. for (NSScreen* s in [NSScreen screens])
  383. displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
  384. }
  385. }
  386. //==============================================================================
  387. static void selectImageForDrawing (const Image& image)
  388. {
  389. [NSGraphicsContext saveGraphicsState];
  390. if (@available (macOS 10.10, *))
  391. {
  392. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithCGContext: juce_getImageContext (image)
  393. flipped: false]];
  394. return;
  395. }
  396. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  397. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
  398. flipped: false]];
  399. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  400. }
  401. static void releaseImageAfterDrawing()
  402. {
  403. [[NSGraphicsContext currentContext] flushGraphics];
  404. [NSGraphicsContext restoreGraphicsState];
  405. }
  406. Image detail::WindowingHelpers::createIconForFile (const File& file)
  407. {
  408. JUCE_AUTORELEASEPOOL
  409. {
  410. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
  411. Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
  412. selectImageForDrawing (result);
  413. [image drawAtPoint: NSMakePoint (0, 0)
  414. fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
  415. operation: NSCompositingOperationSourceOver fraction: 1.0f];
  416. releaseImageAfterDrawing();
  417. return result;
  418. }
  419. }
  420. static Image createNSWindowSnapshot (NSWindow* nsWindow)
  421. {
  422. JUCE_AUTORELEASEPOOL
  423. {
  424. // CGWindowListCreateImage is replaced by functions in the ScreenCaptureKit framework, but
  425. // that framework is only available from macOS 12.3 onwards.
  426. // A suitable @available check should be added once the minimum build OS is 12.3 or greater,
  427. // so that ScreenCaptureKit can be weak-linked.
  428. #if defined (MAC_OS_VERSION_14_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
  429. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  430. #define JUCE_DEPRECATION_IGNORED 1
  431. #endif
  432. CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
  433. kCGWindowListOptionIncludingWindow,
  434. (CGWindowID) [nsWindow windowNumber],
  435. kCGWindowImageBoundsIgnoreFraming);
  436. #if JUCE_DEPRECATION_IGNORED
  437. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  438. #undef JUCE_DEPRECATION_IGNORED
  439. #endif
  440. NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  441. Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
  442. selectImageForDrawing (result);
  443. [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
  444. releaseImageAfterDrawing();
  445. [bitmapRep release];
  446. CGImageRelease (screenShot);
  447. return result;
  448. }
  449. }
  450. Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
  451. {
  452. if (id windowOrView = (id) nativeWindowHandle)
  453. {
  454. if ([windowOrView isKindOfClass: [NSWindow class]])
  455. return createNSWindowSnapshot ((NSWindow*) windowOrView);
  456. if ([windowOrView isKindOfClass: [NSView class]])
  457. return createNSWindowSnapshot ([(NSView*) windowOrView window]);
  458. }
  459. return {};
  460. }
  461. //==============================================================================
  462. void SystemClipboard::copyTextToClipboard (const String& text)
  463. {
  464. NSPasteboard* pb = [NSPasteboard generalPasteboard];
  465. [pb declareTypes: [NSArray arrayWithObject: NSPasteboardTypeString]
  466. owner: nil];
  467. [pb setString: juceStringToNS (text)
  468. forType: NSPasteboardTypeString];
  469. }
  470. String SystemClipboard::getTextFromClipboard()
  471. {
  472. return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSPasteboardTypeString]);
  473. }
  474. } // namespace juce