Audio plugin host https://kx.studio/carla
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.

juce_mac_Windowing.mm 22KB

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