[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / windowing / osx / WinSystemOSX.mm
blob007fc14be3fd055e510a132ce622cc52b936c8aa
1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
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"
41 #include <array>
42 #include <chrono>
43 #include <memory>
44 #include <mutex>
46 #import <IOKit/graphics/IOGraphicsLib.h>
48 using namespace KODI;
49 using namespace MESSAGING;
50 using namespace WINDOWING;
51 using namespace std::chrono_literals;
53 namespace
55 constexpr int MAX_DISPLAYS = 32;
56 constexpr const char* DEFAULT_SCREEN_NAME = "Default";
57 } // namespace
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) ==
73       kCFCompareEqualTo)
74   {
75     bitsPerPixel = 32;
76   }
77   else if (CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) ==
78            kCFCompareEqualTo)
79   {
80     bitsPerPixel = 16;
81   }
82   else if (CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) ==
83            kCFCompareEqualTo)
84   {
85     bitsPerPixel = 8;
86   }
88   CFRelease(pixEnc);
90   return bitsPerPixel;
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];
104   else
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, *))
116   {
117     screenName = [NSScreen.screens objectAtIndex:screenIdx].localizedName;
118   }
119   else
120   {
121     //! TODO: Remove when 10.15 is the minimal target
122     @autoreleasepool
123     {
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)
139       {
140         screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
141       }
142     }
143   }
144   return screenName;
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])
156   {
157     NSEdgeInsets insets;
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);
169   }
170   return safeAreaInsets;
173 NSString* screenNameForDisplay(NSUInteger screenIdx)
175   // screen id 0 is always called "Default"
176   if (screenIdx == 0)
177   {
178     return @(DEFAULT_SCREEN_NAME);
179   }
181   const CGDirectDisplayID displayID = GetDisplayID(screenIdx);
182   NSString* screenName = GetScreenName(screenIdx);
184   if (screenName == nil)
185   {
186     screenName = [[NSString alloc] initWithFormat:@"%u", displayID];
187   }
188   else
189   {
190     // ensure screen name is unique by appending displayid
191     screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID - 1) stringValue]];
192   }
194   return screenName;
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)
203   {
204     CDisplaySettings::GetInstance().SetMonitor(currentScreenName);
205   }
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)
224   {
225     if (display == displayArray[--numDisplays])
226       return numDisplays;
227   }
228   return -1;
231 NSUInteger GetDisplayIndex(const std::string& dispName)
233   NSUInteger ret = 0;
235   // Add full screen settings for additional monitors
236   const NSUInteger numDisplays = NSScreen.screens.count;
237   for (NSUInteger disp = 0; disp <= numDisplays - 1; disp++)
238   {
239     NSString* name = screenNameForDisplay(disp);
240     if (name.UTF8String == dispName)
241     {
242       ret = disp;
243       break;
244     }
245   }
246   return ret;
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,
256                              pixelHeight);
259 CFArrayRef CopyAllDisplayModes(CGDirectDisplayID display)
261   int value = 1;
263   CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
264   if (!number)
265   {
266     CLog::LogF(LOGERROR, "Could not create Number!");
267     return nullptr;
268   }
270   CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
271   CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&key,
272                                                (const void**)&number, 1, nullptr, nullptr);
273   CFRelease(number);
275   if (!options)
276   {
277     CLog::LogF(LOGERROR, "Could not create Dictionary!");
278     return nullptr;
279   }
281   CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
282   CFRelease(options);
284   if (!displayModes)
285   {
286     CLog::LogF(LOGERROR, "No displaymodes found!");
287     return nullptr;
288   }
290   return displayModes;
293 CGDisplayModeRef CreateModeById(const std::string& modeId, NSUInteger screenIdx)
295   if (modeId.empty())
296     return nullptr;
298   bool stretched;
299   bool interlaced;
300   bool safeForHardware;
301   size_t resWidth;
302   size_t resHeight;
303   size_t pixelWidth;
304   size_t pixelHeight;
305   size_t bitsperpixel;
306   RESOLUTION_INFO res;
308   CLog::LogF(LOGDEBUG, "Looking for mode for screen {} with id {}", screenIdx, modeId);
310   CFArrayRef allModes = CopyAllDisplayModes(GetDisplayID(screenIdx));
312   if (!allModes)
313     return nullptr;
315   for (int i = 0; i < CFArrayGetCount(allModes); ++i)
316   {
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))
330     {
331       CGDisplayModeRetain(displayMode);
332       CFRelease(allModes);
333       CLog::LogF(LOGDEBUG, "Found a match!");
334       return displayMode;
335     }
336   }
338   CFRelease(allModes);
339   CLog::LogF(LOGERROR, "No match found!");
340   return nullptr;
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])
348     return nullptr;
350   bool stretched;
351   bool interlaced;
352   bool safeForHardware;
353   size_t w;
354   size_t h;
355   size_t bitsperpixel;
356   double rate;
357   RESOLUTION_INFO res;
359   CLog::LogF(LOGDEBUG, "Looking for suitable mode with {} x {} @ {} Hz on display {}", width,
360              height, refreshrate, screenIdx);
362   CFArrayRef allModes = CopyAllDisplayModes(GetDisplayID(screenIdx));
364   if (!allModes)
365     return nullptr;
367   for (int i = 0; i < CFArrayGetCount(allModes); ++i)
368   {
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))
382     {
383       CGDisplayModeRetain(displayMode);
384       CFRelease(allModes);
385       CLog::LogF(LOGDEBUG, "Found a match!");
386       return displayMode;
387     }
388   }
390   CFRelease(allModes);
391   CLog::LogF(LOGERROR, "No match found!");
392   return nullptr;
395 // mimic former behavior of deprecated CGDisplayBestModeForParameters
396 CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display,
397                                   size_t bitsPerPixel,
398                                   size_t width,
399                                   size_t height)
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);
408   if (!allModes)
409     return nullptr;
411   CGDisplayModeRef displayMode = nullptr;
413   for (int i = 0; i < CFArrayGetCount(allModes); i++)
414   {
415     CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
417     if (!mode)
418       continue;
420     if (DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
421       continue;
423     if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
424     {
425       displayMode = mode;
426       CGDisplayModeRetain(displayMode);
427       break;
428     }
429   }
431   // No depth match was found
432   if (!displayMode)
433   {
434     for (int i = 0; i < CFArrayGetCount(allModes); i++)
435     {
436       CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
438       if (!mode)
439         continue;
441       if (DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
442         continue;
444       if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
445       {
446         displayMode = mode;
447         CGDisplayModeRetain(displayMode);
448         break;
449       }
450     }
451   }
453   CFRelease(allModes);
455   return 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++)
466   {
467     if (i != screenBeingUsed)
468     {
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
479                                                               defer:NO
480                                                              screen:pScreen];
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];
488       });
489     }
490   }
493 void UnblankDisplay(NSUInteger screenToUnblank)
495   if (screenToUnblank < blankingWindowControllers.size() &&
496       blankingWindowControllers[screenToUnblank])
497   {
498     [[blankingWindowControllers[screenToUnblank] window] close];
499     blankingWindowControllers[screenToUnblank] = nil;
500   }
503 void UnblankDisplays(NSUInteger screenBeingUsed)
505   for (NSUInteger i = 0; i < NSScreen.screens.count; i++)
506   {
507     if (blankingWindowControllers[i] && i != screenBeingUsed)
508     {
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(), ^{
512         UnblankDisplay(i);
513       });
514     }
515   }
518 //---------------------------------------------------------------------------------
519 static void DisplayReconfigured(CGDirectDisplayID display,
520                                 CGDisplayChangeSummaryFlags flags,
521                                 void* userData)
523   CWinSystemOSX* winsys = (CWinSystemOSX*)userData;
524   if (!winsys)
525     return;
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)
536   {
537     // pre/post-reconfiguration changes
538     if (!winsys->HasValidResolution())
539       return;
541     NSScreen* pScreen = nil;
542     unsigned int screenIdx = 0;
544     if (screenIdx < NSScreen.screens.count)
545     {
546       pScreen = [NSScreen.screens objectAtIndex:screenIdx];
547     }
549     // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
550     // valid
551     if (pScreen)
552     {
553       CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
554       if (xbmc_display == display)
555       {
556         // we only respond to changes on the display we are running on.
557         winsys->AnnounceOnLostDevice();
558         winsys->StartLostDeviceTimer();
559       }
560     }
561   }
562   else // the else case checks if we need to call OnResetDevice
563   {
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)
570     {
571       winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
572       winsys->HandleOnResetDevice();
573     }
574   }
577 #pragma mark - CWinSystemOSX
578 //------------------------------------------------------------------------------
579 CWinSystemOSX::CWinSystemOSX() : CWinSystemBase(), m_lostDeviceTimer(this)
581   m_appWindow = nullptr;
582   m_glView = nullptr;
583   m_lastDisplayNr = -1;
584   m_refreshRate = 0.0;
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()
622   auto delay =
623       std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
624                                     "videoscreen.delayrefreshchange") *
625                                 100);
626   if (delay > 0ms)
627   {
628     m_delayDispReset = true;
629     m_dispResetTimer.Set(delay);
630   }
631   else
632   {
633     AnnounceOnResetDevice();
634   }
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();
656   else
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())
675     return false;
677   CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
679   return true;
682 bool CWinSystemOSX::DestroyWindowSystem()
684   CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
686   DestroyWindowInternal();
688   if (m_glView)
689   {
690     m_glView = nullptr;
691   }
693   UnblankDisplays(m_lastDisplayNr);
694   return true;
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)
705   {
706     screen = [NSScreen.screens objectAtIndex:m_lastDisplayNr];
707   }
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;
715   m_name = name;
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
723                                                                  defaultSize:size];
725     m_appWindow = m_appWindowController.window;
726     m_glView = m_appWindow.contentView;
727     bounds = m_appWindow.contentView.bounds;
728   });
730   [m_glView Update];
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)
737     {
738       [m_appWindow setFrameOrigin:NSMakePoint(NSMidX(screen.frame) - m_nWidth / 2,
739                                               NSMidY(screen.frame) - m_nHeight / 2)];
740     }
741     [m_appWindowController showWindow:m_appWindow];
742   });
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:)
752                                            withObject:nil
753                                         waitUntilDone:YES];
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];
769   });
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();
783   return true;
786 bool CWinSystemOSX::DestroyWindowInternal()
788   // set this 1st, we should really mutex protext m_appWindow in this class
789   m_bWindowCreated = false;
790   if (m_appWindow)
791   {
792     dispatch_sync(dispatch_get_main_queue(), ^{
793       [m_appWindow setContentView:nil];
794       [[m_appWindowController window] close];
795     });
797     m_appWindow = nil;
798     m_appWindowController = nil;
799   }
801   return true;
804 bool CWinSystemOSX::DestroyWindow()
806   return true;
809 bool CWinSystemOSX::Minimize()
811   @autoreleasepool
812   {
813     dispatch_sync(dispatch_get_main_queue(), ^{
814       [NSApplication.sharedApplication miniaturizeAll:nil];
815     });
816   }
817   return true;
820 bool CWinSystemOSX::Restore()
822   @autoreleasepool
823   {
824     dispatch_sync(dispatch_get_main_queue(), ^{
825       [NSApplication.sharedApplication unhide:nil];
826     });
827   }
828   return true;
831 bool CWinSystemOSX::Show(bool raise)
833   @autoreleasepool
834   {
835     auto app = NSApplication.sharedApplication;
836     if (raise)
837     {
838       [app unhide:nil];
839       [app activateIgnoringOtherApps:YES];
840       [app arrangeInFront:nil];
841     }
842     else
843     {
844       [app unhideWithoutActivation];
845     }
846   }
847   return true;
850 bool CWinSystemOSX::Hide()
852   @autoreleasepool
853   {
854     dispatch_sync(dispatch_get_main_queue(), ^{
855       [NSApplication.sharedApplication hide:nil];
856     });
857   }
858   return true;
861 NSRect CWinSystemOSX::GetWindowDimensions()
863   NSRect frame = NSZeroRect;
864   if (m_appWindow)
865   {
866     NSWindow* win = (NSWindow*)m_appWindow;
867     frame = win.contentView.frame;
868   }
869   return frame;
872 #pragma mark - Resize Window
874 bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
876   if (!m_appWindow)
877     return false;
879   __block OSXGLView* view;
880   dispatch_sync(dispatch_get_main_queue(), ^{
881     view = m_appWindow.contentView;
882   });
884   if (newWidth < 0)
885   {
886     newWidth = [(NSWindow*)m_appWindow minSize].width;
887   }
889   if (newHeight < 0)
890   {
891     newHeight = [(NSWindow*)m_appWindow minSize].height;
892   }
894   if (view)
895   {
896     dispatch_sync(dispatch_get_main_queue(), ^{
897       [view Update];
898     });
899   }
901   m_nWidth = newWidth;
902   m_nHeight = newHeight;
904   return true;
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
918   if (m_bFullScreen)
919   {
920     // switch videomode
921     SwitchToVideoMode(res);
922   }
924   if (m_fullscreenWillToggle)
925   {
926     ResizeWindow(m_nWidth, m_nHeight, -1, -1);
927     m_fullscreenWillToggle = false;
929     // Blank other displays if requested.
930     if (blankOtherDisplays)
931     {
932       BlankOtherDisplays(m_lastDisplayNr);
933     }
934     else
935     {
936       UnblankDisplays(m_lastDisplayNr);
937     }
939     return true;
940   }
942   if (m_bFullScreen)
943   {
944     // Blank/Unblank other displays if requested.
945     if (blankOtherDisplays)
946     {
947       BlankOtherDisplays(m_lastDisplayNr);
948     }
949     else
950     {
951       UnblankDisplays(m_lastDisplayNr);
952     }
953   }
954   else
955   {
956     // Show menubar.
957     dispatch_sync(dispatch_get_main_queue(), ^{
958       [NSApplication.sharedApplication setPresentationOptions:NSApplicationPresentationDefault];
959     });
961     // Unblank.
962     // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
963     UnblankDisplays(m_lastDisplayNr);
964   }
966   // toggle cocoa fullscreen mode
967   if (fullScreenState != m_bFullScreen)
968   {
969     m_fullscreenWillToggle = true;
970     [m_appWindow performSelectorOnMainThread:@selector(toggleFullScreen:)
971                                   withObject:nil
972                                waitUntilDone:YES];
973   }
975   ResizeWindow(m_nWidth, m_nHeight, -1, -1);
977   return true;
980 void CWinSystemOSX::SignalFullScreenStateChanged(bool fullscreenState)
982   if (!m_fullScreenMovingToScreen.has_value())
983   {
984     return;
985   }
987   if (!fullscreenState)
988   {
989     // check if we are already on the target screen (e.g. due to a display lost)
990     if (m_lastDisplayNr != m_fullScreenMovingToScreen.value())
991     {
992       CServiceBroker::GetAppMessenger()->PostMsg(
993           TMSG_MOVETOSCREEN, static_cast<int>(m_fullScreenMovingToScreen.value()));
994     }
995     else
996     {
997       CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
998     }
999   }
1000   else if (fullscreenState)
1001   {
1002     // fullscreen move of the window has been finished
1003     m_fullScreenMovingToScreen.reset();
1004   }
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);
1024   if (!dispMode)
1025   {
1026     dispMode =
1027         CreateMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate), screenIdx);
1028   }
1030   // not found - fallback to bestmodeforparameters
1031   if (!dispMode)
1032   {
1033     dispMode = BestMatchForMode(display_id, 32, res.iWidth, res.iHeight);
1035     if (!dispMode)
1036     {
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]
1040       if (!dispMode)
1041       {
1042         auto screenResolution = GetScreenResolution(screenIdx);
1043         dispMode = CreateMode(screenResolution.pixelWidth, screenResolution.pixelHeight,
1044                               screenResolution.refreshrate, screenIdx);
1046         // no way to get a resolution set
1047         if (!dispMode)
1048           return false;
1049       }
1050     }
1051   }
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++)
1074   {
1075     bool stretched;
1076     bool interlaced;
1077     bool safeForHardware;
1078     size_t resWidth;
1079     size_t resHeight;
1080     size_t pixelWidth;
1081     size_t pixelHeight;
1082     size_t bitsperpixel;
1083     double refreshrate;
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);
1092     if (!displayModes)
1093       continue;
1095     for (int i = 0; i < CFArrayGetCount(displayModes); ++i)
1096     {
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)
1106       {
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?
1113         {
1114           // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
1115           refreshrate = 60.0;
1116         }
1117         const std::string modeId =
1118             ComputeVideoModeId(resWidth, resHeight, pixelWidth, pixelHeight, interlaced);
1119         CLog::LogF(
1120             LOGINFO,
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)
1129         {
1130           res.strId = modeId;
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),
1134                                   refreshrate, 0);
1135           m_gfxContext->ResetOverscan(res);
1136           CDisplaySettings::GetInstance().AddResolutionInfo(res);
1137         }
1138       }
1139     }
1140     CFRelease(displayModes);
1141   }
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
1169   FillInVideoModes();
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)
1187   {
1188     // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
1189     screenResolution.refreshrate = 60.0;
1190   }
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)
1214   {
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,
1222                                                  rect.size.height);
1223     });
1224   }
1225   if (m_fullScreenMovingToScreen.has_value())
1226   {
1227     CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
1228   }
1231 void CWinSystemOSX::OnChangeScreen(unsigned int screenIdx)
1233   const NSUInteger lastDisplay = m_lastDisplayNr;
1234   if (m_appWindow)
1235   {
1236     dispatch_sync(dispatch_get_main_queue(), ^{
1237       m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen(m_appWindow.screen));
1238     });
1239   }
1240   // force unblank the current display
1241   if (lastDisplay != m_lastDisplayNr && m_bFullScreen)
1242   {
1243     UnblankDisplay(m_lastDisplayNr);
1244     CheckAndUpdateCurrentMonitor(m_lastDisplayNr);
1245   }
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
1256   if (m_bFullScreen)
1257   {
1258     // macOS doesn't allow moving fullscreen windows directly to another screen
1259     // toggle fullscreen first
1260     if (screenIdx < NSScreen.screens.count)
1261     {
1262       m_fullScreenMovingToScreen.emplace(screenIdx);
1263       m_fullscreenWillToggle = true;
1264       CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
1265     }
1266     return;
1267   }
1269   NSScreen* currentScreen;
1270   NSScreen* targetScreen;
1271   if (screenIdx < NSScreen.screens.count && m_lastDisplayNr < NSScreen.screens.count)
1272   {
1273     currentScreen = NSScreen.screens[m_lastDisplayNr];
1274     targetScreen = NSScreen.screens[screenIdx];
1275   }
1276   // move the window to the center of the new screen
1277   if (screenIdx != m_lastDisplayNr && targetScreen && currentScreen)
1278   {
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];
1289     });
1290     m_lastDisplayNr = screenIdx;
1291   }
1294 CGLContextObj CWinSystemOSX::GetCGLContextObj()
1296   __block CGLContextObj cglcontex = nullptr;
1297   if (m_appWindow)
1298   {
1299     dispatch_sync(dispatch_get_main_queue(), ^{
1300       cglcontex = [(OSXGLView*)m_appWindow.contentView getGLContextObj];
1301     });
1302   }
1304   return cglcontex;
1307 bool CWinSystemOSX::FlushBuffer()
1309   if (m_appWindow)
1310   {
1311     dispatch_sync(dispatch_get_main_queue(), ^{
1312       [m_appWindow.contentView FlushBuffer];
1313     });
1314   }
1316   return true;
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
1340   // screens here.
1341   const NSUInteger numDisplays = NSScreen.screens.count;
1342   if (numDisplays > 1)
1343   {
1344     for (NSUInteger disp = 1; disp <= numDisplays - 1; disp++)
1345     {
1346       NSString* const dispName = screenNameForDisplay(disp);
1347       outputs.emplace_back(dispName.UTF8String);
1348     }
1349   }
1351   return outputs;
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();
1385   if (szStr)
1386     utf8_text = szStr;
1388   return utf8_text;
1391 bool CWinSystemOSX::HasCursor()
1393   return m_hasCursor;
1396 void CWinSystemOSX::signalMouseEntered()
1398   if (m_appWindow.keyWindow)
1399   {
1400     m_hasCursor = true;
1401     m_winEvents->signalMouseEntered();
1402   }
1405 void CWinSystemOSX::signalMouseExited()
1407   if (m_appWindow.keyWindow)
1408   {
1409     m_hasCursor = false;
1410     m_winEvents->signalMouseExited();
1411   }
1414 void CWinSystemOSX::SendInputEvent(NSEvent* nsEvent)
1416   m_winEvents->SendInputEvent(nsEvent);