2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "WinSystemOSX.h"
11 #include "ServiceBroker.h"
12 #include "application/AppInboundProtocol.h"
13 #include "cores/AudioEngine/AESinkFactory.h"
14 #include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
15 #include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h"
16 #include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
17 #include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
18 #include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
19 #include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h"
20 #include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h"
21 #include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
22 #include "guilib/GUIWindowManager.h"
23 #include "messaging/ApplicationMessenger.h"
24 #include "rendering/gl/ScreenshotSurfaceGL.h"
25 #include "settings/DisplaySettings.h"
26 #include "settings/Settings.h"
27 #include "settings/SettingsComponent.h"
28 #include "threads/CriticalSection.h"
29 #include "utils/StringUtils.h"
30 #include "utils/log.h"
31 #include "windowing/osx/CocoaDPMSSupport.h"
32 #include "windowing/osx/OSScreenSaverOSX.h"
33 #import "windowing/osx/OpenGL/OSXGLView.h"
34 #import "windowing/osx/OpenGL/WindowControllerMacOS.h"
35 #include "windowing/osx/VideoSyncOsx.h"
36 #include "windowing/osx/WinEventsOSX.h"
38 #include "platform/darwin/osx/CocoaInterface.h"
39 #include "platform/darwin/osx/powermanagement/CocoaPowerSyscall.h"
46 #import <IOKit/graphics/IOGraphicsLib.h>
49 using namespace MESSAGING;
50 using namespace WINDOWING;
51 using namespace std::chrono_literals;
55 constexpr int MAX_DISPLAYS = 32;
56 constexpr const char* DEFAULT_SCREEN_NAME = "Default";
59 static std::array<NSWindowController*, MAX_DISPLAYS> blankingWindowControllers;
61 size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode)
63 size_t bitsPerPixel = 0;
65 #pragma GCC diagnostic push
66 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
67 // No replacement for CGDisplayModeCopyPixelEncoding
68 // Disable warning for now
69 CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode);
70 #pragma GCC diagnostic pop
72 if (CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) ==
77 else if (CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) ==
82 else if (CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) ==
93 #pragma mark - GetDisplay
95 CGDirectDisplayID GetDisplayID(NSUInteger screen_index)
97 CGDirectDisplayID displayArray[MAX_DISPLAYS];
98 CGDisplayCount numDisplays;
100 // Get the list of displays.
101 CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
102 if (screen_index >= 0 && screen_index < static_cast<NSUInteger>(numDisplays))
103 return displayArray[screen_index];
105 return displayArray[0];
108 #pragma mark - GetScreenName
110 NSString* GetScreenName(NSUInteger screenIdx)
112 NSString* screenName;
113 const CGDirectDisplayID displayID = GetDisplayID(screenIdx);
115 if (@available(macOS 10.15, *))
117 screenName = [NSScreen.screens objectAtIndex:screenIdx].localizedName;
121 //! TODO: Remove when 10.15 is the minimal target
125 #pragma GCC diagnostic push
126 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
127 // No real replacement of CGDisplayIOServicePort
128 // Stackoverflow links to https://github.com/glfw/glfw/pull/192 as a possible replacement
129 // disable warning for now
130 NSDictionary* deviceInfo = (__bridge_transfer NSDictionary*)IODisplayCreateInfoDictionary(
131 CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
133 #pragma GCC diagnostic pop
135 NSDictionary* localizedNames =
136 [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
138 if ([localizedNames count] > 0)
140 screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
147 EdgeInsets GetScreenEdgeInsets(NSUInteger screenIdx)
149 EdgeInsets safeAreaInsets;
150 NSScreen* pScreen = NSScreen.screens[screenIdx];
152 // Update safeareainsets (display may have a notch)
153 //! @TODO update code block once minimal SDK version is bumped to at least 12.0 (remove NSInvocation and selector)
154 auto safeAreaInsetsSelector = @selector(safeAreaInsets);
155 if ([pScreen respondsToSelector:safeAreaInsetsSelector])
158 NSMethodSignature* safeAreaSignature =
159 [pScreen methodSignatureForSelector:safeAreaInsetsSelector];
160 NSInvocation* safeAreaInvocation =
161 [NSInvocation invocationWithMethodSignature:safeAreaSignature];
162 [safeAreaInvocation setSelector:safeAreaInsetsSelector];
163 [safeAreaInvocation invokeWithTarget:pScreen];
164 [safeAreaInvocation getReturnValue:&insets];
165 // screen backing factor might be higher than 1 (point size vs pixel size in retina displays)
166 safeAreaInsets = EdgeInsets(
167 insets.right * pScreen.backingScaleFactor, insets.bottom * pScreen.backingScaleFactor,
168 insets.left * pScreen.backingScaleFactor, insets.top * pScreen.backingScaleFactor);
170 return safeAreaInsets;
173 NSString* screenNameForDisplay(NSUInteger screenIdx)
175 // screen id 0 is always called "Default"
178 return @(DEFAULT_SCREEN_NAME);
181 const CGDirectDisplayID displayID = GetDisplayID(screenIdx);
182 NSString* screenName = GetScreenName(screenIdx);
184 if (screenName == nil)
186 screenName = [[NSString alloc] initWithFormat:@"%u", displayID];
190 // ensure screen name is unique by appending displayid
191 screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID - 1) stringValue]];
197 void CheckAndUpdateCurrentMonitor(NSUInteger screenNumber)
199 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
200 const std::string storedScreenName = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
201 const std::string currentScreenName = screenNameForDisplay(screenNumber).UTF8String;
202 if (storedScreenName != currentScreenName)
204 CDisplaySettings::GetInstance().SetMonitor(currentScreenName);
208 CGDirectDisplayID GetDisplayIDFromScreen(NSScreen* screen)
210 NSDictionary* screenInfo = screen.deviceDescription;
211 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
213 return (CGDirectDisplayID)[screenID longValue];
216 int GetDisplayIndex(CGDirectDisplayID display)
218 CGDirectDisplayID displayArray[MAX_DISPLAYS];
219 CGDisplayCount numDisplays;
221 // Get the list of displays.
222 CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
223 while (numDisplays > 0)
225 if (display == displayArray[--numDisplays])
231 NSUInteger GetDisplayIndex(const std::string& dispName)
235 // Add full screen settings for additional monitors
236 const NSUInteger numDisplays = NSScreen.screens.count;
237 for (NSUInteger disp = 0; disp <= numDisplays - 1; disp++)
239 NSString* name = screenNameForDisplay(disp);
240 if (name.UTF8String == dispName)
249 #pragma mark - Display Modes
251 std::string ComputeVideoModeId(
252 size_t resWidth, size_t resHeight, size_t pixelWidth, size_t pixelHeight, bool interlaced)
254 const char* interlacedDesc = interlaced ? "i" : "p";
255 return StringUtils::Format("{}x{}{}({}x{})", resWidth, resHeight, interlacedDesc, pixelWidth,
259 CFArrayRef CopyAllDisplayModes(CGDirectDisplayID display)
263 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
266 CLog::LogF(LOGERROR, "Could not create Number!");
270 CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
271 CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&key,
272 (const void**)&number, 1, nullptr, nullptr);
277 CLog::LogF(LOGERROR, "Could not create Dictionary!");
281 CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
286 CLog::LogF(LOGERROR, "No displaymodes found!");
293 CGDisplayModeRef CreateModeById(const std::string& modeId, NSUInteger screenIdx)
300 bool safeForHardware;
308 CLog::LogF(LOGDEBUG, "Looking for mode for screen {} with id {}", screenIdx, modeId);
310 CFArrayRef allModes = CopyAllDisplayModes(GetDisplayID(screenIdx));
315 for (int i = 0; i < CFArrayGetCount(allModes); ++i)
317 CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
318 uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
319 stretched = (flags & kDisplayModeStretchedFlag) != 0;
320 bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
321 safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
322 interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
323 resWidth = CGDisplayModeGetWidth(displayMode);
324 resHeight = CGDisplayModeGetHeight(displayMode);
325 pixelWidth = CGDisplayModeGetPixelWidth(displayMode);
326 pixelHeight = CGDisplayModeGetPixelHeight(displayMode);
328 if (bitsperpixel == 32 && safeForHardware && !stretched &&
329 modeId == ComputeVideoModeId(resWidth, resHeight, pixelWidth, pixelHeight, interlaced))
331 CGDisplayModeRetain(displayMode);
333 CLog::LogF(LOGDEBUG, "Found a match!");
339 CLog::LogF(LOGERROR, "No match found!");
343 // try to find mode that matches the desired size, refreshrate
344 // non interlaced, nonstretched, safe for hardware
345 CGDisplayModeRef CreateMode(size_t width, size_t height, double refreshrate, NSUInteger screenIdx)
347 if (screenIdx >= [[NSScreen screens] count])
352 bool safeForHardware;
359 CLog::LogF(LOGDEBUG, "Looking for suitable mode with {} x {} @ {} Hz on display {}", width,
360 height, refreshrate, screenIdx);
362 CFArrayRef allModes = CopyAllDisplayModes(GetDisplayID(screenIdx));
367 for (int i = 0; i < CFArrayGetCount(allModes); ++i)
369 CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
370 uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
371 stretched = (flags & kDisplayModeStretchedFlag) != 0;
372 interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
373 bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
374 safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
375 w = CGDisplayModeGetPixelWidth(displayMode);
376 h = CGDisplayModeGetPixelHeight(displayMode);
378 rate = CGDisplayModeGetRefreshRate(displayMode);
380 if (bitsperpixel == 32 && safeForHardware && !stretched && !interlaced == false && w == width &&
381 h == height && (rate == refreshrate || rate == 0))
383 CGDisplayModeRetain(displayMode);
385 CLog::LogF(LOGDEBUG, "Found a match!");
391 CLog::LogF(LOGERROR, "No match found!");
395 // mimic former behavior of deprecated CGDisplayBestModeForParameters
396 CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display,
401 // Loop through all display modes to determine the closest match.
402 // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior
403 // Try to find a mode with the requested depth and equal or greater dimensions first.
404 // If no match is found, try to find a mode with greater depth and same or greater dimensions.
405 // If still no match is found, just use the current mode.
406 CFArrayRef allModes = CopyAllDisplayModes(display);
411 CGDisplayModeRef displayMode = nullptr;
413 for (int i = 0; i < CFArrayGetCount(allModes); i++)
415 CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
420 if (DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
423 if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
426 CGDisplayModeRetain(displayMode);
431 // No depth match was found
434 for (int i = 0; i < CFArrayGetCount(allModes); i++)
436 CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
441 if (DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
444 if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
447 CGDisplayModeRetain(displayMode);
458 #pragma mark - Blank Displays
460 void BlankOtherDisplays(NSUInteger screenBeingUsed)
462 const NSUInteger numDisplays = NSScreen.screens.count;
464 // Blank all other displays except the current one.
465 for (NSUInteger i = 0; i < numDisplays; i++)
467 if (i != screenBeingUsed)
469 // Get the size of the screen
470 NSScreen* pScreen = [NSScreen.screens objectAtIndex:i];
471 NSRect screenRect = pScreen.frame;
472 screenRect.origin = NSZeroPoint;
474 dispatch_sync(dispatch_get_main_queue(), ^{
475 // Build a blanking (black) window.
476 auto blankingWindow = [[NSWindow alloc] initWithContentRect:screenRect
477 styleMask:NSWindowStyleMaskBorderless
478 backing:NSBackingStoreBuffered
481 [blankingWindow setBackgroundColor:NSColor.blackColor];
482 [blankingWindow setLevel:CGShieldingWindowLevel()];
483 [blankingWindow makeKeyAndOrderFront:nil];
485 // Create a controller and bind the blanking window to it
486 blankingWindowControllers[i] = [[NSWindowController alloc] init];
487 [blankingWindowControllers[i] setWindow:blankingWindow];
493 void UnblankDisplay(NSUInteger screenToUnblank)
495 if (screenToUnblank < blankingWindowControllers.size() &&
496 blankingWindowControllers[screenToUnblank])
498 [[blankingWindowControllers[screenToUnblank] window] close];
499 blankingWindowControllers[screenToUnblank] = nil;
503 void UnblankDisplays(NSUInteger screenBeingUsed)
505 for (NSUInteger i = 0; i < NSScreen.screens.count; i++)
507 if (blankingWindowControllers[i] && i != screenBeingUsed)
509 // Get rid of the blanking windows we created.
510 // Note after closing the window, setting the NSWindowController to nil will dealoc
511 dispatch_sync(dispatch_get_main_queue(), ^{
518 //---------------------------------------------------------------------------------
519 static void DisplayReconfigured(CGDirectDisplayID display,
520 CGDisplayChangeSummaryFlags flags,
523 CWinSystemOSX* winsys = (CWinSystemOSX*)userData;
527 CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags);
529 // we fire the callbacks on start of configuration
530 // or when the mode set was finished
531 // or when we are called with flags == 0 (which is undocumented but seems to happen
532 // on some macs - we treat it as device reset)
534 // first check if we need to call OnLostDevice
535 if (flags & kCGDisplayBeginConfigurationFlag)
537 // pre/post-reconfiguration changes
538 if (!winsys->HasValidResolution())
541 NSScreen* pScreen = nil;
542 unsigned int screenIdx = 0;
544 if (screenIdx < NSScreen.screens.count)
546 pScreen = [NSScreen.screens objectAtIndex:screenIdx];
549 // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
553 CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
554 if (xbmc_display == display)
556 // we only respond to changes on the display we are running on.
557 winsys->AnnounceOnLostDevice();
558 winsys->StartLostDeviceTimer();
562 else // the else case checks if we need to call OnResetDevice
564 // we fire if kCGDisplaySetModeFlag is set or if flags == 0
565 // (which is undocumented but seems to happen
566 // on some macs - we treat it as device reset)
567 // we also don't check the screen here as we might not even have
568 // one anymore (e.x. when tv is turned off)
569 if (flags & kCGDisplaySetModeFlag || flags == 0)
571 winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
572 winsys->HandleOnResetDevice();
577 #pragma mark - CWinSystemOSX
578 //------------------------------------------------------------------------------
579 CWinSystemOSX::CWinSystemOSX() : CWinSystemBase(), m_lostDeviceTimer(this)
581 m_appWindow = nullptr;
583 m_lastDisplayNr = -1;
585 m_delayDispReset = false;
587 m_winEvents = std::make_unique<CWinEventsOSX>();
589 AE::CAESinkFactory::ClearSinks();
590 CAESinkDARWINOSX::Register();
591 CCocoaPowerSyscall::Register();
592 m_dpms = std::make_shared<CCocoaDPMSSupport>();
595 CWinSystemOSX::~CWinSystemOSX() = default;
597 void CWinSystemOSX::Register(IDispResource* resource)
599 std::unique_lock<CCriticalSection> lock(m_resourceSection);
600 m_resources.push_back(resource);
603 void CWinSystemOSX::Unregister(IDispResource* resource)
605 std::unique_lock<CCriticalSection> lock(m_resourceSection);
606 std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
607 if (i != m_resources.end())
608 m_resources.erase(i);
611 void CWinSystemOSX::AnnounceOnLostDevice()
613 std::unique_lock<CCriticalSection> lock(m_resourceSection);
614 // tell any shared resources
615 CLog::LogF(LOGDEBUG, "Lost Device Announce");
616 for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
617 (*i)->OnLostDisplay();
620 void CWinSystemOSX::HandleOnResetDevice()
623 std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
624 "videoscreen.delayrefreshchange") *
628 m_delayDispReset = true;
629 m_dispResetTimer.Set(delay);
633 AnnounceOnResetDevice();
637 void CWinSystemOSX::AnnounceOnResetDevice()
639 // ensure that graphics context knows about the current refreshrate before doing the callbacks
640 auto screenResolution = GetScreenResolution(m_lastDisplayNr);
641 m_gfxContext->SetFPS(screenResolution.refreshrate);
643 std::unique_lock<CCriticalSection> lock(m_resourceSection);
644 // tell any shared resources
645 CLog::LogF(LOGDEBUG, "Reset Device Announce");
646 for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
647 (*i)->OnResetDisplay();
650 #pragma mark - Timers
652 void CWinSystemOSX::StartLostDeviceTimer()
654 if (m_lostDeviceTimer.IsRunning())
655 m_lostDeviceTimer.Restart();
657 m_lostDeviceTimer.Start(3000ms, false);
660 void CWinSystemOSX::StopLostDeviceTimer()
662 m_lostDeviceTimer.Stop();
665 void CWinSystemOSX::OnTimeout()
667 HandleOnResetDevice();
670 #pragma mark - WindowSystem
672 bool CWinSystemOSX::InitWindowSystem()
674 if (!CWinSystemBase::InitWindowSystem())
677 CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
682 bool CWinSystemOSX::DestroyWindowSystem()
684 CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
686 DestroyWindowInternal();
693 UnblankDisplays(m_lastDisplayNr);
697 bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
699 // find the screen where the application started the last time. It'd be the default screen if the
700 // screen index is not found/available.
701 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
702 m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
703 NSScreen* screen = nil;
704 if (m_lastDisplayNr < NSScreen.screens.count)
706 screen = [NSScreen.screens objectAtIndex:m_lastDisplayNr];
709 // force initial window creation to be windowed, if fullscreen, it will switch to it below
710 // fixes the white screen of death if starting fullscreen and switching to windowed.
711 RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
712 m_nWidth = resInfo.iWidth;
713 m_nHeight = resInfo.iHeight;
714 m_bFullScreen = false;
717 __block NSRect bounds;
718 dispatch_sync(dispatch_get_main_queue(), ^{
719 auto title = [NSString stringWithUTF8String:m_name.c_str()];
720 auto size = NSMakeSize(m_nWidth, m_nHeight);
721 // propose the window size based on the last stored RES_WINDOW resolution info
722 m_appWindowController = [[XBMCWindowControllerMacOS alloc] initWithTitle:title
725 m_appWindow = m_appWindowController.window;
726 m_glView = m_appWindow.contentView;
727 bounds = m_appWindow.contentView.bounds;
732 NSScreen* currentScreen = [NSScreen mainScreen];
733 dispatch_sync(dispatch_get_main_queue(), ^{
734 // NSWindowController does not track the last used screen so set the frame coordinates
735 // to the center of the screen in that case
736 if (screen && currentScreen != screen)
738 [m_appWindow setFrameOrigin:NSMakePoint(NSMidX(screen.frame) - m_nWidth / 2,
739 NSMidY(screen.frame) - m_nHeight / 2)];
741 [m_appWindowController showWindow:m_appWindow];
744 m_bWindowCreated = true;
746 CheckAndUpdateCurrentMonitor(m_lastDisplayNr);
748 // warning, we can order front but not become
749 // key window or risk starting up with bad flicker
750 // becoming key window must happen in completion block.
751 [(NSWindow*)m_appWindow performSelectorOnMainThread:@selector(orderFront:)
755 [NSAnimationContext endGrouping];
757 // get screen refreshrate - this is needed
758 // when we startup in windowed mode and don't run through SetFullScreen
759 auto screenResolution = GetScreenResolution(m_lastDisplayNr);
760 m_refreshRate = screenResolution.refreshrate;
762 // NSWindowController decides what is the best size for the window, so make sure to
763 // update the stored resolution right after the window creation (it's used for example by the splash screen)
764 // with the actual size of the window.
765 // Make sure the window frame rect is converted to backing units ONLY after moving it to the display screen
766 // (as it might be moving to another non-HiDPI screen).
767 dispatch_sync(dispatch_get_main_queue(), ^{
768 bounds = [m_appWindow convertRectToBacking:bounds];
770 SetWindowResolution(bounds.size.width, bounds.size.height);
772 // register platform dependent objects
773 CDVDFactoryCodec::ClearHWAccels();
774 VTB::CDecoder::Register();
775 VIDEOPLAYER::CRendererFactory::ClearRenderer();
776 CLinuxRendererGL::Register();
777 CRendererVTB::Register();
778 VIDEOPLAYER::CProcessInfoOSX::Register();
779 RETRO::CRPProcessInfoOSX::Register();
780 RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
781 CScreenshotSurfaceGL::Register();
786 bool CWinSystemOSX::DestroyWindowInternal()
788 // set this 1st, we should really mutex protext m_appWindow in this class
789 m_bWindowCreated = false;
792 dispatch_sync(dispatch_get_main_queue(), ^{
793 [m_appWindow setContentView:nil];
794 [[m_appWindowController window] close];
798 m_appWindowController = nil;
804 bool CWinSystemOSX::DestroyWindow()
809 bool CWinSystemOSX::Minimize()
813 dispatch_sync(dispatch_get_main_queue(), ^{
814 [NSApplication.sharedApplication miniaturizeAll:nil];
820 bool CWinSystemOSX::Restore()
824 dispatch_sync(dispatch_get_main_queue(), ^{
825 [NSApplication.sharedApplication unhide:nil];
831 bool CWinSystemOSX::Show(bool raise)
835 auto app = NSApplication.sharedApplication;
839 [app activateIgnoringOtherApps:YES];
840 [app arrangeInFront:nil];
844 [app unhideWithoutActivation];
850 bool CWinSystemOSX::Hide()
854 dispatch_sync(dispatch_get_main_queue(), ^{
855 [NSApplication.sharedApplication hide:nil];
861 NSRect CWinSystemOSX::GetWindowDimensions()
863 NSRect frame = NSZeroRect;
866 NSWindow* win = (NSWindow*)m_appWindow;
867 frame = win.contentView.frame;
872 #pragma mark - Resize Window
874 bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
879 __block OSXGLView* view;
880 dispatch_sync(dispatch_get_main_queue(), ^{
881 view = m_appWindow.contentView;
886 newWidth = [(NSWindow*)m_appWindow minSize].width;
891 newHeight = [(NSWindow*)m_appWindow minSize].height;
896 dispatch_sync(dispatch_get_main_queue(), ^{
902 m_nHeight = newHeight;
907 bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
909 std::unique_lock<CCriticalSection> lock(m_critSection);
910 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
911 m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
912 m_nWidth = res.iWidth;
913 m_nHeight = res.iHeight;
914 const bool fullScreenState = m_bFullScreen;
915 m_bFullScreen = fullScreen;
917 // handle resolution/refreshrate switching early here
921 SwitchToVideoMode(res);
924 if (m_fullscreenWillToggle)
926 ResizeWindow(m_nWidth, m_nHeight, -1, -1);
927 m_fullscreenWillToggle = false;
929 // Blank other displays if requested.
930 if (blankOtherDisplays)
932 BlankOtherDisplays(m_lastDisplayNr);
936 UnblankDisplays(m_lastDisplayNr);
944 // Blank/Unblank other displays if requested.
945 if (blankOtherDisplays)
947 BlankOtherDisplays(m_lastDisplayNr);
951 UnblankDisplays(m_lastDisplayNr);
957 dispatch_sync(dispatch_get_main_queue(), ^{
958 [NSApplication.sharedApplication setPresentationOptions:NSApplicationPresentationDefault];
962 // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
963 UnblankDisplays(m_lastDisplayNr);
966 // toggle cocoa fullscreen mode
967 if (fullScreenState != m_bFullScreen)
969 m_fullscreenWillToggle = true;
970 [m_appWindow performSelectorOnMainThread:@selector(toggleFullScreen:)
975 ResizeWindow(m_nWidth, m_nHeight, -1, -1);
980 void CWinSystemOSX::SignalFullScreenStateChanged(bool fullscreenState)
982 if (!m_fullScreenMovingToScreen.has_value())
987 if (!fullscreenState)
989 // check if we are already on the target screen (e.g. due to a display lost)
990 if (m_lastDisplayNr != m_fullScreenMovingToScreen.value())
992 CServiceBroker::GetAppMessenger()->PostMsg(
993 TMSG_MOVETOSCREEN, static_cast<int>(m_fullScreenMovingToScreen.value()));
997 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
1000 else if (fullscreenState)
1002 // fullscreen move of the window has been finished
1003 m_fullScreenMovingToScreen.reset();
1007 #pragma mark - Video Modes
1009 bool CWinSystemOSX::SwitchToVideoMode(RESOLUTION_INFO& res)
1011 CGDisplayModeRef dispMode = nullptr;
1013 const NSUInteger screenIdx =
1014 GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
1015 CSettings::SETTING_VIDEOSCREEN_MONITOR));
1017 // Figure out the screen size. (default to main screen)
1018 const CGDirectDisplayID display_id = GetDisplayID(screenIdx);
1020 // find mode that matches the desired size, refreshrate non interlaced, nonstretched, safe for hardware
1022 // try to find an exact match first (by using the unique ids we assign to resolution infos)
1023 dispMode = CreateModeById(res.strId, screenIdx);
1027 CreateMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate), screenIdx);
1030 // not found - fallback to bestmodeforparameters
1033 dispMode = BestMatchForMode(display_id, 32, res.iWidth, res.iHeight);
1037 dispMode = BestMatchForMode(display_id, 16, res.iWidth, res.iHeight);
1039 // still no match? fallback to current resolution of the display which HAS to work [tm]
1042 auto screenResolution = GetScreenResolution(screenIdx);
1043 dispMode = CreateMode(screenResolution.pixelWidth, screenResolution.pixelHeight,
1044 screenResolution.refreshrate, screenIdx);
1046 // no way to get a resolution set
1052 // switch mode and return success
1053 CGDisplayCapture(display_id);
1054 CGDisplayConfigRef cfg;
1055 CGBeginDisplayConfiguration(&cfg);
1056 CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr);
1057 CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly);
1058 CGDisplayRelease(display_id);
1060 m_refreshRate = CGDisplayModeGetRefreshRate(dispMode);
1062 Cocoa_CVDisplayLinkUpdate();
1063 CFRelease(dispMode);
1064 return (err == kCGErrorSuccess);
1067 void CWinSystemOSX::FillInVideoModes()
1069 const NSUInteger dispIdx =
1070 GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
1071 CSettings::SETTING_VIDEOSCREEN_MONITOR));
1073 for (NSUInteger disp = 0; disp <= NSScreen.screens.count - 1; disp++)
1077 bool safeForHardware;
1082 size_t bitsperpixel;
1084 RESOLUTION_INFO res;
1086 CFArrayRef displayModes = CopyAllDisplayModes(GetDisplayID(disp));
1087 NSString* const dispName = screenNameForDisplay(disp);
1088 res.guiInsets = GetScreenEdgeInsets(disp);
1090 CLog::LogF(LOGINFO, "Display {} has name {}", disp, dispName.UTF8String);
1095 for (int i = 0; i < CFArrayGetCount(displayModes); ++i)
1097 CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
1099 uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
1100 stretched = (flags & kDisplayModeStretchedFlag) != 0;
1101 interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
1102 bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
1103 safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
1105 if (bitsperpixel == 32 && safeForHardware && !stretched && !interlaced)
1107 resWidth = CGDisplayModeGetWidth(displayMode);
1108 resHeight = CGDisplayModeGetHeight(displayMode);
1109 pixelWidth = CGDisplayModeGetPixelWidth(displayMode);
1110 pixelHeight = CGDisplayModeGetPixelHeight(displayMode);
1111 refreshrate = CGDisplayModeGetRefreshRate(displayMode);
1112 if (static_cast<int>(refreshrate) == 0) // LCD display?
1114 // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
1117 const std::string modeId =
1118 ComputeVideoModeId(resWidth, resHeight, pixelWidth, pixelHeight, interlaced);
1121 "Found possible resolution for display {} ({}) with {} x {} @ {} Hz (pixel size: "
1122 "{} x {}{}) (id:{})",
1123 disp, dispName.UTF8String, resWidth, resHeight, refreshrate, pixelWidth, pixelHeight,
1124 pixelWidth > resWidth && pixelHeight > resHeight ? " - HiDPI" : "", modeId);
1126 // only add the resolution if it belongs to "our" screen
1127 // all others are only logged above...
1128 if (disp == dispIdx)
1131 UpdateDesktopResolution(res, (dispName != nil) ? dispName.UTF8String : "Unknown",
1132 static_cast<int>(pixelWidth), static_cast<int>(pixelHeight),
1133 static_cast<int>(resWidth), static_cast<int>(resHeight),
1135 m_gfxContext->ResetOverscan(res);
1136 CDisplaySettings::GetInstance().AddResolutionInfo(res);
1140 CFRelease(displayModes);
1144 #pragma mark - Resolution
1146 void CWinSystemOSX::UpdateResolutions()
1148 CWinSystemBase::UpdateResolutions();
1149 const NSUInteger dispIdx =
1150 GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
1151 CSettings::SETTING_VIDEOSCREEN_MONITOR));
1153 auto screenResolution = GetScreenResolution(dispIdx);
1154 NSString* const dispName = screenNameForDisplay(dispIdx);
1155 RESOLUTION_INFO& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
1156 resInfo.guiInsets = GetScreenEdgeInsets(dispIdx);
1157 resInfo.strId = ComputeVideoModeId(screenResolution.resWidth, screenResolution.resHeight,
1158 screenResolution.pixelWidth, screenResolution.pixelHeight,
1159 screenResolution.interlaced);
1160 UpdateDesktopResolution(
1161 resInfo, dispName.UTF8String, static_cast<int>(screenResolution.pixelWidth),
1162 static_cast<int>(screenResolution.pixelHeight), static_cast<int>(screenResolution.resWidth),
1163 static_cast<int>(screenResolution.resHeight), screenResolution.refreshrate, 0);
1165 CDisplaySettings::GetInstance().ClearCustomResolutions();
1167 // now just fill in the possible resolutions for the attached screens
1168 // and push to the resolution info vector
1170 CDisplaySettings::GetInstance().ApplyCalibrations();
1173 CWinSystemOSX::ScreenResolution CWinSystemOSX::GetScreenResolution(unsigned long screenIdx)
1175 ScreenResolution screenResolution;
1176 CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx);
1177 CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
1178 uint32_t flags = CGDisplayModeGetIOFlags(mode);
1179 screenResolution.interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
1180 screenResolution.resWidth = CGDisplayModeGetWidth(mode);
1181 screenResolution.resHeight = CGDisplayModeGetHeight(mode);
1182 screenResolution.pixelWidth = CGDisplayModeGetPixelWidth(mode);
1183 screenResolution.pixelHeight = CGDisplayModeGetPixelHeight(mode);
1184 screenResolution.refreshrate = CGDisplayModeGetRefreshRate(mode);
1185 CGDisplayModeRelease(mode);
1186 if (static_cast<int>(screenResolution.refreshrate) == 0)
1188 // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
1189 screenResolution.refreshrate = 60.0;
1191 return screenResolution;
1194 bool CWinSystemOSX::HasValidResolution() const
1196 return m_gfxContext->GetVideoResolution() != RES_INVALID;
1199 #pragma mark - Window Move
1201 void CWinSystemOSX::OnMove(int x, int y)
1203 // check if the current screen/monitor settings needs to be updated
1204 CheckAndUpdateCurrentMonitor(m_lastDisplayNr);
1206 // check if refresh rate needs to be updated
1207 static double oldRefreshRate = m_refreshRate;
1208 Cocoa_CVDisplayLinkUpdate();
1210 auto screenResolution = GetScreenResolution(m_lastDisplayNr);
1211 m_refreshRate = screenResolution.refreshrate;
1213 if (oldRefreshRate != m_refreshRate)
1215 oldRefreshRate = m_refreshRate;
1217 // send a message so that videoresolution (and refreshrate) is changed
1218 dispatch_sync(dispatch_get_main_queue(), ^{
1219 NSRect rect = [m_appWindow.contentView
1220 convertRectToBacking:[m_appWindow contentRectForFrameRect:m_appWindow.frame]];
1221 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, rect.size.width,
1225 if (m_fullScreenMovingToScreen.has_value())
1227 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
1231 void CWinSystemOSX::OnChangeScreen(unsigned int screenIdx)
1233 const NSUInteger lastDisplay = m_lastDisplayNr;
1236 dispatch_sync(dispatch_get_main_queue(), ^{
1237 m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen(m_appWindow.screen));
1240 // force unblank the current display
1241 if (lastDisplay != m_lastDisplayNr && m_bFullScreen)
1243 UnblankDisplay(m_lastDisplayNr);
1244 CheckAndUpdateCurrentMonitor(m_lastDisplayNr);
1248 unsigned int CWinSystemOSX::GetScreenId(const std::string& screen)
1250 return static_cast<int>(GetDisplayIndex(screen));
1253 void CWinSystemOSX::MoveToScreen(unsigned int screenIdx)
1255 // find the future displayId and the screen object
1258 // macOS doesn't allow moving fullscreen windows directly to another screen
1259 // toggle fullscreen first
1260 if (screenIdx < NSScreen.screens.count)
1262 m_fullScreenMovingToScreen.emplace(screenIdx);
1263 m_fullscreenWillToggle = true;
1264 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
1269 NSScreen* currentScreen;
1270 NSScreen* targetScreen;
1271 if (screenIdx < NSScreen.screens.count && m_lastDisplayNr < NSScreen.screens.count)
1273 currentScreen = NSScreen.screens[m_lastDisplayNr];
1274 targetScreen = NSScreen.screens[screenIdx];
1276 // move the window to the center of the new screen
1277 if (screenIdx != m_lastDisplayNr && targetScreen && currentScreen)
1279 // moving from a HiDPI screen to a non-HiDPI screen requires that we scale the dimensions of
1280 // the window properly. m_nWidth and m_nHeight store pixels (not points) after resize callbacks
1281 const double backingFactor =
1282 currentScreen.backingScaleFactor > targetScreen.backingScaleFactor
1283 ? currentScreen.backingScaleFactor / targetScreen.backingScaleFactor
1284 : currentScreen.backingScaleFactor;
1285 NSPoint windowPos = NSMakePoint(NSMidX(targetScreen.frame) - (m_nWidth / backingFactor) / 2.0,
1286 NSMidY(targetScreen.frame) - (m_nHeight / backingFactor) / 2.0);
1287 dispatch_sync(dispatch_get_main_queue(), ^{
1288 [m_appWindow setFrameOrigin:windowPos];
1290 m_lastDisplayNr = screenIdx;
1294 CGLContextObj CWinSystemOSX::GetCGLContextObj()
1296 __block CGLContextObj cglcontex = nullptr;
1299 dispatch_sync(dispatch_get_main_queue(), ^{
1300 cglcontex = [(OSXGLView*)m_appWindow.contentView getGLContextObj];
1307 bool CWinSystemOSX::FlushBuffer()
1311 dispatch_sync(dispatch_get_main_queue(), ^{
1312 [m_appWindow.contentView FlushBuffer];
1319 #pragma mark - Vsync
1321 void CWinSystemOSX::EnableVSync(bool enable)
1323 // OpenGL Flush synchronised with vertical retrace
1324 GLint swapInterval = enable ? 1 : 0;
1325 [NSOpenGLContext.currentContext setValues:&swapInterval
1326 forParameter:NSOpenGLContextParameterSwapInterval];
1329 std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(CVideoReferenceClock* clock)
1331 return std::make_unique<CVideoSyncOsx>(clock);
1334 std::vector<std::string> CWinSystemOSX::GetConnectedOutputs()
1336 std::vector<std::string> outputs;
1337 outputs.emplace_back(DEFAULT_SCREEN_NAME);
1339 // screen 0 is always the "Default" setting, avoid duplicating the available
1341 const NSUInteger numDisplays = NSScreen.screens.count;
1342 if (numDisplays > 1)
1344 for (NSUInteger disp = 1; disp <= numDisplays - 1; disp++)
1346 NSString* const dispName = screenNameForDisplay(disp);
1347 outputs.emplace_back(dispName.UTF8String);
1354 #pragma mark - OSScreenSaver
1356 std::unique_ptr<IOSScreenSaver> CWinSystemOSX::GetOSScreenSaverImpl()
1358 return std::make_unique<COSScreenSaverOSX>();
1361 #pragma mark - Input
1363 bool CWinSystemOSX::MessagePump()
1365 return m_winEvents->MessagePump();
1368 void CWinSystemOSX::enableInputEvents()
1370 m_winEvents->enableInputEvents();
1371 signalMouseEntered();
1374 void CWinSystemOSX::disableInputEvents()
1376 m_winEvents->disableInputEvents();
1377 signalMouseExited();
1380 std::string CWinSystemOSX::GetClipboardText()
1382 std::string utf8_text;
1384 const char* szStr = Cocoa_Paste();
1391 bool CWinSystemOSX::HasCursor()
1396 void CWinSystemOSX::signalMouseEntered()
1398 if (m_appWindow.keyWindow)
1401 m_winEvents->signalMouseEntered();
1405 void CWinSystemOSX::signalMouseExited()
1407 if (m_appWindow.keyWindow)
1409 m_hasCursor = false;
1410 m_winEvents->signalMouseExited();
1414 void CWinSystemOSX::SendInputEvent(NSEvent* nsEvent)
1416 m_winEvents->SendInputEvent(nsEvent);