juce_mac_Windowing.mm 22 KB

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