/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { void LookAndFeel::playAlertSound() { NSBeep(); } //============================================================================== class OSXMessageBox : private AsyncUpdater { public: OSXMessageBox (AlertWindow::AlertIconType type, const String& t, const String& m, const char* b1, const char* b2, const char* b3, ModalComponentManager::Callback* c, const bool runAsync) : iconType (type), title (t), message (m), callback (c), button1 (b1), button2 (b2), button3 (b3) { if (runAsync) triggerAsyncUpdate(); } int getResult() const { switch (getRawResult()) { case NSAlertFirstButtonReturn: return 1; case NSAlertThirdButtonReturn: return 2; default: return 0; } } static int show (AlertWindow::AlertIconType iconType, const String& title, const String& message, ModalComponentManager::Callback* callback, const char* b1, const char* b2, const char* b3, bool runAsync) { std::unique_ptr mb (new OSXMessageBox (iconType, title, message, b1, b2, b3, callback, runAsync)); if (! runAsync) return mb->getResult(); mb.release(); return 0; } private: AlertWindow::AlertIconType iconType; String title, message; std::unique_ptr callback; const char* button1; const char* button2; const char* button3; void handleAsyncUpdate() override { auto result = getResult(); if (callback != nullptr) callback->modalStateFinished (result); delete this; } NSInteger getRawResult() const { NSAlert* alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText: juceStringToNS (title)]; [alert setInformativeText: juceStringToNS (message)]; [alert setAlertStyle: iconType == AlertWindow::WarningIcon ? NSAlertStyleCritical : NSAlertStyleInformational]; addButton (alert, button1); addButton (alert, button2); addButton (alert, button3); return [alert runModal]; } static void addButton (NSAlert* alert, const char* button) { if (button != nullptr) [alert addButtonWithTitle: juceStringToNS (TRANS (button))]; } }; #if JUCE_MODAL_LOOPS_PERMITTED void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType, const String& title, const String& message, Component* /*associatedComponent*/) { OSXMessageBox::show (iconType, title, message, nullptr, "OK", nullptr, nullptr, false); } #endif void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { OSXMessageBox::show (iconType, title, message, callback, "OK", nullptr, nullptr, true); } bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return OSXMessageBox::show (iconType, title, message, callback, "OK", "Cancel", nullptr, callback != nullptr) == 1; } int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return OSXMessageBox::show (iconType, title, message, callback, "Yes", "Cancel", "No", callback != nullptr); } int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return OSXMessageBox::show (iconType, title, message, callback, "Yes", "No", nullptr, callback != nullptr); } //============================================================================== static NSRect getDragRect (NSView* view, NSEvent* event) { auto eventPos = [event locationInWindow]; return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f) fromView: nil]; } static NSView* getNSViewForDragEvent (Component* sourceComp) { if (sourceComp == nullptr) if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0)) sourceComp = draggingSource->getComponentUnderMouse(); if (sourceComp != nullptr) return (NSView*) sourceComp->getWindowHandle(); jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event! return nil; } struct NSDraggingSourceHelper : public ObjCClass> { NSDraggingSourceHelper() : ObjCClass> ("JUCENSDraggingSourceHelper_") { addIvar*> ("callback"); addIvar ("text"); addIvar ("operation"); addMethod (@selector (dealloc), dealloc, "v@:"); addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@"); addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@"); addMethod (@selector (draggingSession:endedAtPoint:operation:), draggingSessionEnded, "v@:@@@"); addProtocol (@protocol (NSPasteboardItemDataProvider)); registerClass(); } static void setText (id self, const String& text) { object_setInstanceVariable (self, "text", new String (text)); } static void setCompletionCallback (id self, std::function cb) { object_setInstanceVariable (self, "callback", new std::function (cb)); } static void setDragOperation (id self, NSDragOperation op) { object_setInstanceVariable (self, "operation", new NSDragOperation (op)); } private: static void dealloc (id self, SEL) { delete getIvar (self, "text"); delete getIvar*> (self, "callback"); delete getIvar (self, "operation"); sendSuperclassMessage (self, @selector (dealloc)); } static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type) { if ([type compare: NSPasteboardTypeString] == NSOrderedSame) if (auto* text = getIvar (self, "text")) [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding] forType: NSPasteboardTypeString]; } static NSDragOperation sourceOperationMaskForDraggingContext (id self, SEL, NSDraggingSession*, NSDraggingContext) { return *getIvar (self, "operation"); } static void draggingSessionEnded (id self, SEL, NSDraggingSession*, NSPoint p, NSDragOperation) { // Our view doesn't receive a mouse up when the drag ends so we need to generate one here and send it... if (auto* view = getNSViewForDragEvent (nullptr)) if (auto* cgEvent = CGEventCreateMouseEvent (nullptr, kCGEventLeftMouseUp, CGPointMake (p.x, p.y), kCGMouseButtonLeft)) if (id e = [NSEvent eventWithCGEvent: cgEvent]) [view mouseUp: e]; if (auto* cb = getIvar*> (self, "callback")) cb->operator()(); } }; static NSDraggingSourceHelper draggingSourceHelper; bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent, std::function callback) { if (text.isEmpty()) return false; if (auto* view = getNSViewForDragEvent (sourceComponent)) { JUCE_AUTORELEASEPOOL { if (auto event = [[view window] currentEvent]) { id helper = [draggingSourceHelper.createInstance() init]; NSDraggingSourceHelper::setText (helper, text); NSDraggingSourceHelper::setDragOperation (helper, NSDragOperationCopy); if (callback != nullptr) NSDraggingSourceHelper::setCompletionCallback (helper, callback); auto pasteboardItem = [[NSPasteboardItem new] autorelease]; [pasteboardItem setDataProvider: helper forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]]; auto dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease]; NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()]; [dragItem setDraggingFrame: getDragRect (view, event) contents: image]; if (auto session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem] event: event source: helper]) { session.animatesToStartingPositionsOnCancelOrFail = YES; session.draggingFormation = NSDraggingFormationNone; return true; } } } } return false; } bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles, Component* sourceComponent, std::function callback) { if (files.isEmpty()) return false; if (auto* view = getNSViewForDragEvent (sourceComponent)) { JUCE_AUTORELEASEPOOL { if (auto event = [[view window] currentEvent]) { auto dragItems = [[[NSMutableArray alloc] init] autorelease]; for (auto& filename : files) { auto* nsFilename = juceStringToNS (filename); auto fileURL = [NSURL fileURLWithPath: nsFilename]; auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL]; auto eventPos = [event locationInWindow]; auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f) fromView: nil]; auto dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename]; [dragItem setDraggingFrame: dragRect contents: dragImage]; [dragItems addObject: dragItem]; [dragItem release]; } auto helper = [draggingSourceHelper.createInstance() autorelease]; if (callback != nullptr) NSDraggingSourceHelper::setCompletionCallback (helper, callback); NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove : NSDragOperationCopy); return [view beginDraggingSessionWithItems: dragItems event: event source: helper] != nullptr; } } } return false; } //============================================================================== bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } Point MouseInputSource::getCurrentRawMousePosition() { JUCE_AUTORELEASEPOOL { auto p = [NSEvent mouseLocation]; return { (float) p.x, (float) (getMainScreenHeight() - p.y) }; } } void MouseInputSource::setRawMousePosition (Point newPosition) { // this rubbish needs to be done around the warp call, to avoid causing a // bizarre glitch.. CGAssociateMouseAndMouseCursorPosition (false); CGWarpMouseCursorPosition (convertToCGPoint (newPosition)); CGAssociateMouseAndMouseCursorPosition (true); } double Desktop::getDefaultMasterScale() { return 1.0; } Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { return upright; } //============================================================================== class ScreenSaverDefeater : public Timer { public: ScreenSaverDefeater() { startTimer (5000); timerCallback(); } void timerCallback() override { if (Process::isForegroundProcess()) { if (assertion == nullptr) assertion.reset (new PMAssertion()); } else { assertion.reset(); } } struct PMAssertion { PMAssertion() : assertionID (kIOPMNullAssertionID) { IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, CFSTR ("JUCE Playback"), &assertionID); jassert (res == kIOReturnSuccess); ignoreUnused (res); } ~PMAssertion() { if (assertionID != kIOPMNullAssertionID) IOPMAssertionRelease (assertionID); } IOPMAssertionID assertionID; }; std::unique_ptr assertion; }; static std::unique_ptr screenSaverDefeater; void Desktop::setScreenSaverEnabled (const bool isEnabled) { if (isEnabled) screenSaverDefeater.reset(); else if (screenSaverDefeater == nullptr) screenSaverDefeater.reset (new ScreenSaverDefeater()); } bool Desktop::isScreenSaverEnabled() { return screenSaverDefeater == nullptr; } //============================================================================== struct DisplaySettingsChangeCallback : private DeletedAtShutdown { DisplaySettingsChangeCallback() { CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, nullptr); } ~DisplaySettingsChangeCallback() { CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, nullptr); clearSingletonInstance(); } static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*) { const_cast (Desktop::getInstance().getDisplays()).refresh(); } JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback) }; JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback) static Rectangle convertDisplayRect (NSRect r, CGFloat mainScreenBottom) { r.origin.y = mainScreenBottom - (r.origin.y + r.size.height); return convertToRectInt (r); } static Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale) { Displays::Display d; d.isMain = (mainScreenBottom == 0); if (d.isMain) mainScreenBottom = [s frame].size.height; d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale; d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale; d.scale = masterScale; if ([s respondsToSelector: @selector (backingScaleFactor)]) d.scale *= s.backingScaleFactor; NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue]; d.dpi = (dpi.width + dpi.height) / 2.0; return d; } void Displays::findDisplays (const float masterScale) { JUCE_AUTORELEASEPOOL { DisplaySettingsChangeCallback::getInstance(); CGFloat mainScreenBottom = 0; for (NSScreen* s in [NSScreen screens]) displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale)); } } //============================================================================== bool juce_areThereAnyAlwaysOnTopWindows() { for (NSWindow* window in [NSApp windows]) if ([window level] > NSNormalWindowLevel) return true; return false; } //============================================================================== static void selectImageForDrawing (const Image& image) { [NSGraphicsContext saveGraphicsState]; #if (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithCGContext: juce_getImageContext (image) flipped: false]]; #else [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image) flipped: false]]; #endif } static void releaseImageAfterDrawing() { [[NSGraphicsContext currentContext] flushGraphics]; [NSGraphicsContext restoreGraphicsState]; } Image juce_createIconForFile (const File& file) { JUCE_AUTORELEASEPOOL { NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())]; Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true); selectImageForDrawing (result); [image drawAtPoint: NSMakePoint (0, 0) fromRect: NSMakeRect (0, 0, [image size].width, [image size].height) operation: NSCompositingOperationSourceOver fraction: 1.0f]; releaseImageAfterDrawing(); return result; } } static Image createNSWindowSnapshot (NSWindow* nsWindow) { JUCE_AUTORELEASEPOOL { CGImageRef screenShot = CGWindowListCreateImage (CGRectNull, kCGWindowListOptionIncludingWindow, (CGWindowID) [nsWindow windowNumber], kCGWindowImageBoundsIgnoreFraming); NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot]; Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true); selectImageForDrawing (result); [bitmapRep drawAtPoint: NSMakePoint (0, 0)]; releaseImageAfterDrawing(); [bitmapRep release]; CGImageRelease (screenShot); return result; } } Image createSnapshotOfNativeWindow (void*); Image createSnapshotOfNativeWindow (void* nativeWindowHandle) { if (id windowOrView = (id) nativeWindowHandle) { if ([windowOrView isKindOfClass: [NSWindow class]]) return createNSWindowSnapshot ((NSWindow*) windowOrView); if ([windowOrView isKindOfClass: [NSView class]]) return createNSWindowSnapshot ([(NSView*) windowOrView window]); } return {}; } //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) { NSPasteboard* pb = [NSPasteboard generalPasteboard]; [pb declareTypes: [NSArray arrayWithObject: NSPasteboardTypeString] owner: nil]; [pb setString: juceStringToNS (text) forType: NSPasteboardTypeString]; } String SystemClipboard::getTextFromClipboard() { return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSPasteboardTypeString]); } void Process::setDockIconVisible (bool isVisible) { ProcessSerialNumber psn { 0, kCurrentProcess }; OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication : kProcessTransformToUIElementApplication); jassert (err == 0); ignoreUnused (err); } bool Desktop::isOSXDarkModeActive() { return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")] isEqualToString: nsStringLiteral ("Dark")]; } } // namespace juce