summaryrefslogtreecommitdiffstats
path: root/xbmc/windowing/osx
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/windowing/osx')
-rw-r--r--xbmc/windowing/osx/CMakeLists.txt17
-rw-r--r--xbmc/windowing/osx/CocoaDPMSSupport.cpp55
-rw-r--r--xbmc/windowing/osx/CocoaDPMSSupport.h20
-rw-r--r--xbmc/windowing/osx/OSScreenSaverOSX.cpp31
-rw-r--r--xbmc/windowing/osx/OSScreenSaverOSX.h24
-rw-r--r--xbmc/windowing/osx/OpenGL/CMakeLists.txt14
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLView.h18
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLView.mm111
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLWindow.h19
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLWindow.mm245
-rw-r--r--xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h35
-rw-r--r--xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm79
-rw-r--r--xbmc/windowing/osx/SDL/CMakeLists.txt10
-rw-r--r--xbmc/windowing/osx/SDL/WinEventsSDL.cpp242
-rw-r--r--xbmc/windowing/osx/SDL/WinEventsSDL.h24
-rw-r--r--xbmc/windowing/osx/SDL/WinSystemOSXSDL.h111
-rw-r--r--xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm1852
-rw-r--r--xbmc/windowing/osx/VideoSyncOsx.h46
-rw-r--r--xbmc/windowing/osx/VideoSyncOsx.mm148
-rw-r--r--xbmc/windowing/osx/WinEventsOSX.h34
-rw-r--r--xbmc/windowing/osx/WinEventsOSX.mm54
-rw-r--r--xbmc/windowing/osx/WinEventsOSXImpl.h25
-rw-r--r--xbmc/windowing/osx/WinEventsOSXImpl.mm364
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.h120
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.mm1305
25 files changed, 5003 insertions, 0 deletions
diff --git a/xbmc/windowing/osx/CMakeLists.txt b/xbmc/windowing/osx/CMakeLists.txt
new file mode 100644
index 0000000..433f5cf
--- /dev/null
+++ b/xbmc/windowing/osx/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(SOURCES CocoaDPMSSupport.cpp
+ OSScreenSaverOSX.cpp
+ VideoSyncOsx.mm)
+set(HEADERS CocoaDPMSSupport.h
+ OSScreenSaverOSX.h
+ VideoSyncOsx.h)
+
+if(NOT SDL_FOUND)
+ list(APPEND SOURCES WinEventsOSX.mm
+ WinEventsOSXImpl.mm
+ WinSystemOSX.mm)
+ list(APPEND HEADERS WinEventsOSX.h
+ WinEventsOSXImpl.h
+ WinSystemOSX.h)
+endif()
+
+core_add_library(windowing_osx)
diff --git a/xbmc/windowing/osx/CocoaDPMSSupport.cpp b/xbmc/windowing/osx/CocoaDPMSSupport.cpp
new file mode 100644
index 0000000..bc86220
--- /dev/null
+++ b/xbmc/windowing/osx/CocoaDPMSSupport.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CocoaDPMSSupport.h"
+
+#include "ServiceBroker.h"
+#include "utils/log.h"
+
+#include <CoreFoundation/CFNumber.h>
+#include <IOKit/IOKitLib.h>
+
+CCocoaDPMSSupport::CCocoaDPMSSupport()
+{
+ m_supportedModes.push_back(OFF);
+ m_supportedModes.push_back(STANDBY);
+}
+
+bool CCocoaDPMSSupport::EnablePowerSaving(PowerSavingMode mode)
+{
+ // http://lists.apple.com/archives/Cocoa-dev/2007/Nov/msg00267.html
+ // This is an unsupported system call that might kernel panic on PPC boxes
+ // The reported OSX-PPC panic is unverified so we are going to enable this until
+ // we find out which OSX-PPC boxes have problems, then add detect/disable for those boxes.
+ io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
+ if (!r)
+ return false;
+
+ switch (mode)
+ {
+ case OFF:
+ case STANDBY:
+ return (IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), kCFBooleanTrue) == 0);
+ default:
+ return false;
+ }
+}
+
+bool CCocoaDPMSSupport::DisablePowerSaving()
+{
+ // http://lists.apple.com/archives/Cocoa-dev/2007/Nov/msg00267.html
+ // This is an unsupported system call that might kernel panic on PPC boxes
+ // The reported OSX-PPC panic is unverified so we are going to enable this until
+ // we find out which OSX-PPC boxes have problems, then add detect/disable for those boxes.
+ io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
+ if (!r)
+ return false;
+
+ // Turn display on
+ return (IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), kCFBooleanFalse) == 0);
+}
diff --git a/xbmc/windowing/osx/CocoaDPMSSupport.h b/xbmc/windowing/osx/CocoaDPMSSupport.h
new file mode 100644
index 0000000..6a54f42
--- /dev/null
+++ b/xbmc/windowing/osx/CocoaDPMSSupport.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "xbmc/powermanagement/DPMSSupport.h"
+
+class CCocoaDPMSSupport : public CDPMSSupport
+{
+public:
+ CCocoaDPMSSupport();
+ ~CCocoaDPMSSupport() override = default;
+
+protected:
+ bool EnablePowerSaving(PowerSavingMode mode) override;
+ bool DisablePowerSaving() override;
+};
diff --git a/xbmc/windowing/osx/OSScreenSaverOSX.cpp b/xbmc/windowing/osx/OSScreenSaverOSX.cpp
new file mode 100644
index 0000000..019f86c
--- /dev/null
+++ b/xbmc/windowing/osx/OSScreenSaverOSX.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OSScreenSaverOSX.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+void COSScreenSaverOSX::Inhibit()
+{
+ // see Technical Q&A QA1340
+ if (m_assertionID == 0)
+ {
+ CFStringRef reasonForActivity= CFSTR("XBMC requested disable system screen saver");
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn, reasonForActivity, &m_assertionID);
+ }
+}
+
+void COSScreenSaverOSX::Uninhibit()
+{
+ if (m_assertionID != 0)
+ {
+ IOPMAssertionRelease(m_assertionID);
+ m_assertionID = 0;
+ }
+} \ No newline at end of file
diff --git a/xbmc/windowing/osx/OSScreenSaverOSX.h b/xbmc/windowing/osx/OSScreenSaverOSX.h
new file mode 100644
index 0000000..c79367a
--- /dev/null
+++ b/xbmc/windowing/osx/OSScreenSaverOSX.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "../OSScreenSaver.h"
+
+#import <IOKit/pwr_mgt/IOPMLib.h>
+
+class COSScreenSaverOSX : public KODI::WINDOWING::IOSScreenSaver
+{
+public:
+ COSScreenSaverOSX() = default;
+ void Inhibit() override;
+ void Uninhibit() override;
+
+private:
+ IOPMAssertionID m_assertionID = 0;
+};
diff --git a/xbmc/windowing/osx/OpenGL/CMakeLists.txt b/xbmc/windowing/osx/OpenGL/CMakeLists.txt
new file mode 100644
index 0000000..fd250d7
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/CMakeLists.txt
@@ -0,0 +1,14 @@
+if(OPENGL_FOUND)
+ set(SOURCES WinSystemOSXGL.mm)
+ set(HEADERS WinSystemOSXGL.h)
+
+ if(NOT SDL_FOUND)
+ list(APPEND SOURCES OSXGLView.mm
+ OSXGLWindow.mm)
+ list(APPEND HEADERS OSXGLView.h
+ OSXGLWindow.h)
+ endif()
+
+ core_add_library(windowing_osx_opengl)
+
+endif()
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLView.h b/xbmc/windowing/osx/OpenGL/OSXGLView.h
new file mode 100644
index 0000000..577642d
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLView.h
@@ -0,0 +1,18 @@
+#pragma once
+
+/*
+ * Copyright (C) 2021- Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface OSXGLView : NSOpenGLView
+
+- (id)initWithFrame:(NSRect)frameRect;
+- (NSOpenGLContext*)getGLContext;
+
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLView.mm b/xbmc/windowing/osx/OpenGL/OSXGLView.mm
new file mode 100644
index 0000000..6c0a8b5
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLView.mm
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021- Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#import "OSXGLView.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/AppParamParser.h"
+#include "application/Application.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include "system_gl.h"
+
+@implementation OSXGLView
+{
+ NSOpenGLContext* m_glcontext;
+ NSOpenGLPixelFormat* m_pixFmt;
+ NSTrackingArea* m_trackingArea;
+ BOOL pause;
+}
+
+- (id)initWithFrame:(NSRect)frameRect
+{
+ NSOpenGLPixelFormatAttribute wattrs[] = {
+ NSOpenGLPFANoRecovery, NSOpenGLPFAAccelerated,
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)32,
+ NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8,
+ NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24,
+ NSOpenGLPFADoubleBuffer, (NSOpenGLPixelFormatAttribute)0};
+
+ self = [super initWithFrame:frameRect];
+ if (self)
+ {
+ m_pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs];
+ m_glcontext = [[NSOpenGLContext alloc] initWithFormat:m_pixFmt shareContext:nil];
+ }
+
+ [self updateTrackingAreas];
+
+ GLint swapInterval = 1;
+ [m_glcontext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+ [m_glcontext makeCurrentContext];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [NSOpenGLContext clearCurrentContext];
+ [m_glcontext clearDrawable];
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ [m_glcontext setView:self];
+
+ // clear screen on first render
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0, 0, 0, 0);
+
+ [m_glcontext update];
+ });
+}
+
+- (void)updateTrackingAreas
+{
+ if (m_trackingArea != nil)
+ {
+ [self removeTrackingArea:m_trackingArea];
+ }
+
+ const int opts =
+ (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways);
+ m_trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
+ options:opts
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:m_trackingArea];
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent
+{
+ [NSCursor hide];
+}
+
+- (void)mouseMoved:(NSEvent*)theEvent
+{
+}
+
+- (void)mouseExited:(NSEvent*)theEvent
+{
+ [NSCursor unhide];
+}
+
+- (NSOpenGLContext*)getGLContext
+{
+ return m_glcontext;
+}
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLWindow.h b/xbmc/windowing/osx/OpenGL/OSXGLWindow.h
new file mode 100644
index 0000000..a722804
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLWindow.h
@@ -0,0 +1,19 @@
+#pragma once
+
+/*
+ * Copyright (C) 2021- Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface OSXGLWindow : NSWindow <NSWindowDelegate>
+
+@property(atomic) bool resizeState;
+
+- (id)initWithContentRect:(NSRect)box styleMask:(uint)style;
+
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm b/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm
new file mode 100644
index 0000000..8ee26fc
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2021- Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#import "OSXGLWindow.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/AppParamParser.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#import "windowing/osx/OpenGL/OSXGLView.h"
+#include "windowing/osx/WinEventsOSX.h"
+#import "windowing/osx/WinSystemOSX.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+//------------------------------------------------------------------------------------------
+@implementation OSXGLWindow
+
+@synthesize resizeState = m_resizeState;
+
+- (id)initWithContentRect:(NSRect)box styleMask:(uint)style
+{
+ self = [super initWithContentRect:box styleMask:style backing:NSBackingStoreBuffered defer:YES];
+ [self setDelegate:self];
+ [self setAcceptsMouseMovedEvents:YES];
+ // autosave the window position/size
+ // Tell the controller to not cascade its windows.
+ [self.windowController setShouldCascadeWindows:NO];
+ [self setFrameAutosaveName:@"OSXGLWindowPositionHeightWidth"];
+
+ g_application.m_AppFocused = true;
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self setDelegate:nil];
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+
+ return NO;
+}
+
+- (void)windowDidExpose:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+}
+
+- (void)windowDidMove:(NSNotification*)aNotification
+{
+ NSOpenGLContext* context = NSOpenGLContext.currentContext;
+ if (context)
+ {
+ if (context.view)
+ {
+ NSPoint window_origin = [[[context view] window] frame].origin;
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = window_origin.x;
+ newEvent.move.y = window_origin.y;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+
+- (void)windowWillStartLiveResize:(NSNotification*)notification
+{
+ m_resizeState = true;
+}
+
+- (void)windowDidEndLiveResize:(NSNotification*)notification
+{
+ m_resizeState = false;
+ [self windowDidResize:notification];
+}
+
+- (void)windowDidResize:(NSNotification*)aNotification
+{
+ if (!m_resizeState)
+ {
+ NSRect rect = [self contentRectForFrameRect:self.frame];
+ int width = static_cast<int>(rect.size.width);
+ int height = static_cast<int>(rect.size.height);
+
+ XBMC_Event newEvent = {};
+
+ if (!CServiceBroker::GetWinSystem()->IsFullScreen())
+ {
+ RESOLUTION res_index = RES_DESKTOP;
+ if ((width == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iWidth) &&
+ (height == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iHeight))
+ return;
+
+ newEvent.type = XBMC_VIDEORESIZE;
+ }
+ else
+ {
+ // macos may trigger a resize/rescale event just after Kodi has entered fullscreen
+ // (from windowDidEndLiveResize). Kodi needs to rescale the UI - use a different event
+ // type since XBMC_VIDEORESIZE is supposed to only be used in windowed mode
+ newEvent.type = XBMC_FULLSCREEN_UPDATE;
+ newEvent.move.x = -1;
+ newEvent.move.y = -1;
+ }
+
+ newEvent.resize.w = width;
+ newEvent.resize.h = height;
+
+ // check for valid sizes cause in some cases
+ // we are hit during fullscreen transition from macos
+ // and might be technically "zero" sized
+ if (newEvent.resize.w != 0 && newEvent.resize.h != 0)
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+
+- (void)windowDidChangeScreen:(NSNotification*)notification
+{
+ // user has moved the window to a
+ // different screen
+ // if (CServiceBroker::GetWinSystem()->IsFullScreen())
+ // CServiceBroker::GetWinSystem()->SetMovedToOtherScreen(true);
+}
+
+- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
+{
+ return frameSize;
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification*)pNotification
+{
+ CWinSystemOSX* winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return;
+
+ // if osx is the issuer of the toggle
+ // call Kodi's toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from Kodi's gui thread
+ winSystem->SetFullscreenWillToggle(true);
+
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of Kodi did a toggle - just reset the flag
+ // we don't need to do anything else
+ winSystem->SetFullscreenWillToggle(false);
+ }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification*)pNotification
+{
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return;
+
+ // if osx is the issuer of the toggle
+ // call Kodi's toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from Kodi's gui thread
+ winSystem->SetFullscreenWillToggle(true);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of Kodi did a toggle - just reset the flag
+ // we don't need to do anything else
+ winSystem->SetFullscreenWillToggle(false);
+ }
+}
+
+- (NSApplicationPresentationOptions)window:(NSWindow*)window
+ willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
+{
+ // customize our appearance when entering full screen:
+ // we don't want the dock to appear but we want the menubar to hide/show automatically
+ //
+ return (NSApplicationPresentationFullScreen | // support full screen for this window (required)
+ NSApplicationPresentationHideDock | // completely hide the dock
+ NSApplicationPresentationAutoHideMenuBar); // yes we want the menu bar to show/hide
+}
+
+- (void)windowDidMiniaturize:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = false;
+}
+
+- (void)windowDidDeminiaturize:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+}
+
+- (void)windowDidBecomeKey:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (winSystem)
+ {
+ winSystem->enableInputEvents();
+ }
+}
+
+- (void)windowDidResignKey:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = false;
+
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (winSystem)
+ {
+ winSystem->disableInputEvents();
+ }
+}
+@end
diff --git a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h
new file mode 100644
index 0000000..c38e5e3
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#if defined(HAS_SDL)
+#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "windowing/osx/WinSystemOSX.h"
+#endif
+#include "rendering/gl/RenderSystemGL.h"
+
+class CWinSystemOSXGL : public CWinSystemOSX, public CRenderSystemGL
+{
+public:
+ CWinSystemOSXGL() = default;
+ ~CWinSystemOSXGL() override = default;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemOSX
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+
+protected:
+ void PresentRenderImpl(bool rendered) override;
+ void SetVSyncImpl(bool enable) override;
+};
diff --git a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm
new file mode 100644
index 0000000..30e44f2
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinSystemOSXGL.h"
+
+#include "guilib/Texture.h"
+#include "rendering/gl/RenderSystemGL.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <unistd.h>
+
+void CWinSystemOSXGL::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemOSXGL::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemOSXGL>();
+}
+
+void CWinSystemOSXGL::PresentRenderImpl(bool rendered)
+{
+ if (rendered)
+ FlushBuffer();
+
+ // FlushBuffer does not block if window is obscured
+ // in this case we need to throttle the render loop
+ if (IsObscured())
+ usleep(10000);
+
+ if (m_delayDispReset && m_dispResetTimer.IsTimePast())
+ {
+ m_delayDispReset = false;
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSXGL::SetVSyncImpl(bool enable)
+{
+ EnableVSync(false);
+
+ if (enable)
+ {
+ EnableVSync(true);
+ }
+}
+
+bool CWinSystemOSXGL::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ CWinSystemOSX::ResizeWindow(newWidth, newHeight, newLeft, newTop);
+ CRenderSystemGL::ResetRenderSystem(newWidth, newHeight);
+
+ if (m_bVSync)
+ {
+ EnableVSync(m_bVSync);
+ }
+
+ return true;
+}
+
+bool CWinSystemOSXGL::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ CWinSystemOSX::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemGL::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ if (m_bVSync)
+ {
+ EnableVSync(m_bVSync);
+ }
+
+ return true;
+}
+
diff --git a/xbmc/windowing/osx/SDL/CMakeLists.txt b/xbmc/windowing/osx/SDL/CMakeLists.txt
new file mode 100644
index 0000000..33e25d8
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/CMakeLists.txt
@@ -0,0 +1,10 @@
+if(SDL_FOUND)
+ set(SOURCES WinEventsSDL.cpp
+ WinSystemOSXSDL.mm)
+ set(HEADERS WinEventsSDL.h
+ WinSystemOSXSDL.h)
+endif()
+
+if(SOURCES)
+ core_add_library(windowing_osx_SDL)
+endif()
diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.cpp b/xbmc/windowing/osx/SDL/WinEventsSDL.cpp
new file mode 100644
index 0000000..31b2506
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinEventsSDL.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinEventsSDL.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/DisplaySettings.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+bool CWinEventsOSX::MessagePump()
+{
+ SDL_Event event;
+ bool ret = false;
+
+ while (SDL_PollEvent(&event))
+ {
+ switch(event.type)
+ {
+ case SDL_QUIT:
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+
+ case SDL_ACTIVEEVENT:
+ //If the window was inconified or restored
+ if( event.active.state & SDL_APPACTIVE )
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(event.active.gain != 0);
+ CServiceBroker::GetWinSystem()->NotifyAppActiveChange(g_application.GetRenderGUI());
+ }
+ else if (event.active.state & SDL_APPINPUTFOCUS)
+ {
+ g_application.m_AppFocused = event.active.gain != 0;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort && g_application.m_AppFocused)
+ appPort->SetRenderGUI(g_application.m_AppFocused);
+ CServiceBroker::GetWinSystem()->NotifyAppFocusChange(g_application.m_AppFocused);
+ }
+ break;
+
+ case SDL_KEYDOWN:
+ {
+ // process any platform specific shortcuts before handing off to XBMC
+ if (ProcessOSXShortcuts(event))
+ {
+ ret = true;
+ break;
+ }
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_KEYDOWN;
+ newEvent.key.keysym.scancode = event.key.keysym.scancode;
+ newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
+ newEvent.key.keysym.unicode = event.key.keysym.unicode;
+
+ // Check if the Windows keys are down because SDL doesn't flag this.
+ uint16_t mod = event.key.keysym.mod;
+ uint8_t* keystate = SDL_GetKeyState(NULL);
+ if (keystate[SDLK_LSUPER] || keystate[SDLK_RSUPER])
+ mod |= XBMCKMOD_LSUPER;
+ newEvent.key.keysym.mod = (XBMCMod) mod;
+
+ // don't handle any more messages in the queue until we've handled keydown,
+ // if a keyup is in the queue it will reset the keypress before it is handled.
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_KEYUP:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_KEYUP;
+ newEvent.key.keysym.scancode = event.key.keysym.scancode;
+ newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
+ newEvent.key.keysym.mod =(XBMCMod) event.key.keysym.mod;
+ newEvent.key.keysym.unicode = event.key.keysym.unicode;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = event.button.button;
+ newEvent.button.x = event.button.x;
+ newEvent.button.y = event.button.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEBUTTONUP:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = event.button.button;
+ newEvent.button.x = event.button.x;
+ newEvent.button.y = event.button.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEMOTION:
+ {
+ if (0 == (SDL_GetAppState() & SDL_APPMOUSEFOCUS))
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ // See CApplication::ProcessSlow() for a description as to why we call Cocoa_HideMouse.
+ // this is here to restore the pointer when toggling back to window mode from fullscreen.
+ Cocoa_ShowMouse();
+ break;
+ }
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = event.motion.x;
+ newEvent.motion.y = event.motion.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+ case SDL_VIDEORESIZE:
+ {
+ // Under newer osx versions sdl is so fucked up that it even fires resize events
+ // that exceed the screen size (maybe some HiDP incompatibility in old SDL?)
+ // ensure to ignore those events because it will mess with windowed size
+ if((event.resize.w > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iWidth) ||
+ (event.resize.h > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iHeight))
+ {
+ break;
+ }
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = event.resize.w;
+ newEvent.resize.h = event.resize.h;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ break;
+ }
+ case SDL_USEREVENT:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_USEREVENT;
+ newEvent.user.code = event.user.code;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+ case SDL_VIDEOEXPOSE:
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ break;
+ }
+ memset(&event, 0, sizeof(SDL_Event));
+ }
+
+ return ret;
+}
+
+bool CWinEventsOSX::ProcessOSXShortcuts(SDL_Event& event)
+{
+ static bool shift = false, cmd = false;
+
+ cmd = !!(SDL_GetModState() & (KMOD_LMETA | KMOD_RMETA ));
+ shift = !!(SDL_GetModState() & (KMOD_LSHIFT | KMOD_RSHIFT));
+
+ if (cmd && event.key.type == SDL_KEYDOWN)
+ {
+ char keysymbol = event.key.keysym.sym;
+
+ // if the unicode is in the ascii range
+ // use this instead for getting the real
+ // character based on the used keyboard layout
+ // see http://lists.libsdl.org/pipermail/sdl-libsdl.org/2004-May/043716.html
+ bool isControl = (event.key.keysym.mod & KMOD_CTRL) != 0;
+ if (!isControl && !(event.key.keysym.unicode & 0xff80))
+ keysymbol = event.key.keysym.unicode;
+
+ switch(keysymbol)
+ {
+ case SDLK_q: // CMD-q to quit
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ return true;
+
+ case SDLK_f: // CMD-Ctrl-f to toggle fullscreen
+ if (!isControl)
+ return false;
+ g_application.OnAction(CAction(ACTION_TOGGLE_FULLSCREEN));
+ return true;
+
+ case SDLK_s: // CMD-3 to take a screenshot
+ g_application.OnAction(CAction(ACTION_TAKE_SCREENSHOT));
+ return true;
+
+ case SDLK_h: // CMD-h to hide
+ CServiceBroker::GetWinSystem()->Hide();
+ return true;
+
+ case SDLK_m: // CMD-m to minimize
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.h b/xbmc/windowing/osx/SDL/WinEventsSDL.h
new file mode 100644
index 0000000..06e6325
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinEventsSDL.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "windowing/WinEvents.h"
+
+#include <SDL/SDL_events.h>
+
+class CWinEventsOSX : public IWinEvents
+{
+public:
+ CWinEventsOSX() = default;
+ ~CWinEventsOSX() override = default;
+ bool MessagePump() override;
+
+private:
+ static bool ProcessOSXShortcuts(SDL_Event& event);
+};
diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h
new file mode 100644
index 0000000..ab19301
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Timer.h"
+#include "windowing/WinSystem.h"
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+typedef struct SDL_Surface SDL_Surface;
+
+class IDispResource;
+class CWinEventsOSX;
+class CWinSystemOSXImpl;
+#ifdef __OBJC__
+@class NSOpenGLContext;
+#endif
+
+class CWinSystemOSX : public CWinSystemBase, public ITimerCallback
+{
+public:
+
+ CWinSystemOSX();
+ ~CWinSystemOSX() override;
+
+ // ITimerCallback interface
+ void OnTimeout() override;
+
+ // CWinSystemBase
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+ bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override;
+ bool DestroyWindow() override;
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void OnMove(int x, int y) override;
+
+ std::string GetClipboardText() override;
+
+ void Register(IDispResource *resource) override;
+ void Unregister(IDispResource *resource) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ void WindowChangedScreen();
+
+ void AnnounceOnLostDevice();
+ void AnnounceOnResetDevice();
+ void HandleOnResetDevice();
+ void StartLostDeviceTimer();
+ void StopLostDeviceTimer();
+
+ void* GetCGLContextObj();
+#ifdef __OBJC__
+ NSOpenGLContext* GetNSOpenGLContext();
+#else
+ void* GetNSOpenGLContext();
+#endif
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+
+ void HandlePossibleRefreshrateChange();
+ void GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void EnableVSync(bool enable);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ void FillInVideoModes();
+ bool FlushBuffer(void);
+ bool IsObscured(void);
+ void StartTextInput();
+ void StopTextInput();
+
+ std::unique_ptr<CWinSystemOSXImpl> m_impl;
+ SDL_Surface* m_SDLSurface;
+ CWinEventsOSX *m_osx_events;
+ bool m_obscured;
+ std::chrono::time_point<std::chrono::steady_clock> m_obscured_timecheck;
+
+ bool m_movedToOtherScreen;
+ int m_lastDisplayNr;
+ double m_refreshRate;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ CTimer m_lostDeviceTimer;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+ int m_updateGLContext = 0;
+};
diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm
new file mode 100644
index 0000000..7a1b46b
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm
@@ -0,0 +1,1852 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinSystemOSXSDL.h"
+
+#include "CompileInfo.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
+#include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "guilib/DispResource.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/KeyboardStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+#include "windowing/osx/CocoaDPMSSupport.h"
+#include "windowing/osx/OSScreenSaverOSX.h"
+#include "windowing/osx/SDL/WinEventsSDL.h"
+#include "windowing/osx/VideoSyncOsx.h"
+
+#include "platform/darwin/DarwinUtils.h"
+#include "platform/darwin/DictionaryUtils.h"
+#include "platform/darwin/osx/CocoaInterface.h"
+#import "platform/darwin/osx/SDL/OSXTextInputResponder.h"
+#include "platform/darwin/osx/XBMCHelper.h"
+
+#include <cstdlib>
+#include <mutex>
+#include <signal.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/graphics/IOGraphicsLib.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <QuartzCore/QuartzCore.h>
+#import <SDL/SDL.h>
+
+// turn off deprecated warning spew.
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+using namespace KODI;
+using namespace WINDOWING;
+using namespace std::chrono_literals;
+
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidMoveNotification callback.
+@interface windowDidMoveNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidMoveNoteClass*) initWith: (void*) userdata;
+- (void) windowDidMoveNotification:(NSNotification*) note;
+@end
+
+@implementation windowDidMoveNoteClass
++ (windowDidMoveNoteClass*) initWith: (void*) userdata
+{
+ windowDidMoveNoteClass *windowDidMove = [windowDidMoveNoteClass new];
+ windowDidMove->m_userdata = userdata;
+ return windowDidMove;
+}
+- (void) windowDidMoveNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ if (context)
+ {
+ if ([context view])
+ {
+ NSPoint window_origin = [[[context view] window] frame].origin;
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = window_origin.x;
+ newEvent.move.y = window_origin.y;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+@end
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidReSizeNotification callback.
+@interface windowDidReSizeNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidReSizeNoteClass*) initWith: (void*) userdata;
+- (void) windowDidReSizeNotification:(NSNotification*) note;
+@end
+@implementation windowDidReSizeNoteClass
++ (windowDidReSizeNoteClass*) initWith: (void*) userdata
+{
+ windowDidReSizeNoteClass *windowDidReSize = [windowDidReSizeNoteClass new];
+ windowDidReSize->m_userdata = userdata;
+ return windowDidReSize;
+}
+- (void) windowDidReSizeNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+
+}
+@end
+
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidChangeScreenNotification callback.
+@interface windowDidChangeScreenNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata;
+- (void) windowDidChangeScreenNotification:(NSNotification*) note;
+@end
+@implementation windowDidChangeScreenNoteClass
++ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata
+{
+ windowDidChangeScreenNoteClass *windowDidChangeScreen = [windowDidChangeScreenNoteClass new];
+ windowDidChangeScreen->m_userdata = userdata;
+ return windowDidChangeScreen;
+}
+- (void) windowDidChangeScreenNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+ winsys->WindowChangedScreen();
+}
+@end
+//------------------------------------------------------------------------------------------
+
+class CWinSystemOSXImpl
+{
+public:
+ NSOpenGLContext* m_glContext;
+ static NSOpenGLContext* m_lastOwnedContext;
+
+ windowDidMoveNoteClass* m_windowDidMove;
+ windowDidReSizeNoteClass* m_windowDidReSize;
+ windowDidChangeScreenNoteClass* m_windowChangedScreen;
+};
+
+NSOpenGLContext* CWinSystemOSXImpl::m_lastOwnedContext = nil;
+
+
+#define MAX_DISPLAYS 32
+// if there was a devicelost callback
+// but no device reset for 3 secs
+// a timeout fires the reset callback
+// (for ensuring that e.x. AE isn't stuck)
+constexpr auto LOST_DEVICE_TIMEOUT_MS = 3000ms;
+static NSWindow* blankingWindows[MAX_DISPLAYS];
+
+//------------------------------------------------------------------------------------------
+CRect CGRectToCRect(CGRect cgrect)
+{
+ CRect crect = CRect(
+ cgrect.origin.x,
+ cgrect.origin.y,
+ cgrect.origin.x + cgrect.size.width,
+ cgrect.origin.y + cgrect.size.height);
+ return crect;
+}
+//---------------------------------------------------------------------------------
+void SetMenuBarVisible(bool visible)
+{
+ if(visible)
+ {
+ [[NSApplication sharedApplication]
+ setPresentationOptions: NSApplicationPresentationDefault];
+ }
+ else
+ {
+ [[NSApplication sharedApplication]
+ setPresentationOptions: NSApplicationPresentationHideMenuBar |
+ NSApplicationPresentationHideDock];
+ }
+}
+//---------------------------------------------------------------------------------
+CGDirectDisplayID GetDisplayID(int screen_index)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ if (screen_index >= 0 && screen_index < static_cast<int>(numDisplays))
+ return(displayArray[screen_index]);
+ else
+ return(displayArray[0]);
+}
+
+size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode)
+{
+ size_t bitsPerPixel = 0;
+
+ CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode);
+ if(CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 32;
+ }
+ else if(CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 16;
+ }
+ else if(CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 8;
+ }
+
+ CFRelease(pixEnc);
+
+ return bitsPerPixel;
+}
+
+CFArrayRef GetAllDisplayModes(CGDirectDisplayID display)
+{
+ int value = 1;
+
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
+ if (!number)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Number!");
+ return NULL;
+ }
+
+ CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
+ CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&key, (const void **)&number, 1, NULL, NULL);
+ CFRelease(number);
+
+ if (!options)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Dictionary!");
+ return NULL;
+ }
+
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
+ CFRelease(options);
+
+ if (!displayModes)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - no displaymodes found!");
+ return NULL;
+ }
+
+ return displayModes;
+}
+
+// mimic former behavior of deprecated CGDisplayBestModeForParameters
+CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display, size_t bitsPerPixel, size_t width, size_t height, boolean_t &match)
+{
+
+ // Get a copy of the current display mode
+ CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(display);
+
+ // Loop through all display modes to determine the closest match.
+ // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior
+ // Try to find a mode with the requested depth and equal or greater dimensions first.
+ // If no match is found, try to find a mode with greater depth and same or greater dimensions.
+ // If still no match is found, just use the current mode.
+ CFArrayRef allModes = GetAllDisplayModes(display);
+
+ for(int i = 0; i < CFArrayGetCount(allModes); i++) {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if(DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
+ continue;
+
+ if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ CGDisplayModeRelease(displayMode); // release the copy we got before ...
+ displayMode = mode;
+ match = true;
+ break;
+ }
+ }
+
+ // No depth match was found
+ if(!match)
+ {
+ for(int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+ if(DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
+ continue;
+
+ if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ match = true;
+ break;
+ }
+ }
+ }
+
+ CFRelease(allModes);
+
+ return displayMode;
+}
+
+CGDirectDisplayID GetDisplayIDFromScreen(NSScreen *screen)
+{
+ NSDictionary* screenInfo = [screen deviceDescription];
+ NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
+
+ return (CGDirectDisplayID)[screenID longValue];
+}
+
+int GetDisplayIndex(CGDirectDisplayID display)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ while (numDisplays > 0)
+ {
+ if (display == displayArray[--numDisplays])
+ return numDisplays;
+ }
+ return -1;
+}
+
+void BlankOtherDisplays(int screen_index)
+{
+ int i;
+ int numDisplays = [[NSScreen screens] count];
+
+ // zero out blankingWindows for debugging
+ for (i=0; i<MAX_DISPLAYS; i++)
+ {
+ blankingWindows[i] = 0;
+ }
+
+ // Blank.
+ for (i=0; i<numDisplays; i++)
+ {
+ if (i != screen_index)
+ {
+ // Get the size.
+ NSScreen* pScreen = [[NSScreen screens] objectAtIndex:i];
+ NSRect screenRect = [pScreen frame];
+
+ // Build a blanking window.
+ screenRect.origin = NSZeroPoint;
+ blankingWindows[i] = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+
+ [blankingWindows[i] setBackgroundColor:[NSColor blackColor]];
+ [blankingWindows[i] setLevel:CGShieldingWindowLevel()];
+ [blankingWindows[i] makeKeyAndOrderFront:nil];
+ }
+ }
+}
+
+void UnblankDisplays(void)
+{
+ int numDisplays = [[NSScreen screens] count];
+ int i = 0;
+
+ for (i=0; i<numDisplays; i++)
+ {
+ if (blankingWindows[i] != 0)
+ {
+ // Get rid of the blanking windows we created.
+ [blankingWindows[i] close];
+ blankingWindows[i] = 0;
+ }
+ }
+}
+
+CGDisplayFadeReservationToken DisplayFadeToBlack(bool fade)
+{
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
+ if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess && fade)
+ CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
+
+ return(fade_token);
+}
+
+void DisplayFadeFromBlack(CGDisplayFadeReservationToken fade_token, bool fade)
+{
+ if (fade_token != kCGDisplayFadeReservationInvalidToken)
+ {
+ if (fade)
+ CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
+ CGReleaseDisplayFadeReservation(fade_token);
+ }
+}
+
+NSString* screenNameForDisplay(CGDirectDisplayID displayID)
+{
+ NSString* screenName;
+ @autoreleasepool
+ {
+ NSDictionary* deviceInfo = (__bridge_transfer NSDictionary*)IODisplayCreateInfoDictionary(
+ CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
+ NSDictionary* localizedNames =
+ [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
+
+ if ([localizedNames count] > 0)
+ {
+ screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
+ }
+ }
+
+ if (screenName == nil)
+ {
+ screenName = [[NSString alloc] initWithFormat:@"%i", displayID];
+ }
+ else
+ {
+ // ensure screen name is unique by appending displayid
+ screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID) stringValue]];
+ }
+
+ return screenName;
+}
+
+int GetDisplayIndex(const std::string& dispName)
+{
+ int ret = 0;
+
+ // Add full screen settings for additional monitors
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString *name = screenNameForDisplay(GetDisplayID(disp));
+ if ([name UTF8String] == dispName)
+ {
+ ret = disp;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void ShowHideNSWindow(NSWindow *wind, bool show)
+{
+ if (show)
+ [wind orderFront:nil];
+ else
+ [wind orderOut:nil];
+}
+
+static NSWindow *curtainWindow;
+void fadeInDisplay(NSScreen *theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double) fadeSteps);
+
+ if (curtainWindow != nil)
+ {
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = 1.0 - (step * fadeInterval);
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
+ }
+ }
+ [curtainWindow close];
+ curtainWindow = nil;
+
+ [NSCursor unhide];
+}
+
+void fadeOutDisplay(NSScreen *theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double) fadeSteps);
+
+ [NSCursor hide];
+
+ curtainWindow = [[NSWindow alloc]
+ initWithContentRect:[theScreen frame]
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES
+ screen:theScreen];
+
+ [curtainWindow setAlphaValue:0.0];
+ [curtainWindow setBackgroundColor:[NSColor blackColor]];
+ [curtainWindow setLevel:NSScreenSaverWindowLevel];
+
+ [curtainWindow makeKeyAndOrderFront:nil];
+ [curtainWindow setFrame:[curtainWindow
+ frameRectForContentRect:[theScreen frame]]
+ display:YES
+ animate:NO];
+
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = step * fadeInterval;
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
+ }
+}
+
+// try to find mode that matches the desired size, refreshrate
+// non interlaced, nonstretched, safe for hardware
+CGDisplayModeRef GetMode(int width, int height, double refreshrate, int screenIdx)
+{
+ if ( screenIdx >= (signed)[[NSScreen screens] count])
+ return NULL;
+
+ Boolean stretched;
+ Boolean interlaced;
+ Boolean safeForHardware;
+ Boolean televisionoutput;
+ int w, h, bitsperpixel;
+ double rate;
+ RESOLUTION_INFO res;
+
+ CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with {} x {} @ {:f} Hz on display {}",
+ width, height, refreshrate, screenIdx);
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(screenIdx));
+
+ if (!displayModes)
+ return NULL;
+
+ for (int i=0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = flags & kDisplayModeStretchedFlag ? true : false;
+ interlaced = flags & kDisplayModeInterlacedFlag ? true : false;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = flags & kDisplayModeSafetyFlags ? true : false;
+ televisionoutput = flags & kDisplayModeTelevisionFlag ? true : false;
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ rate = CGDisplayModeGetRefreshRate(displayMode);
+
+
+ if ((bitsperpixel == 32) &&
+ (safeForHardware == YES) &&
+ (stretched == NO) &&
+ (interlaced == NO) &&
+ (w == width) &&
+ (h == height) &&
+ (rate == refreshrate || rate == 0))
+ {
+ CLog::Log(LOGDEBUG, "GetMode found a match!");
+ return displayMode;
+ }
+ }
+
+ CFRelease(displayModes);
+ CLog::Log(LOGERROR, "GetMode - no match found!");
+ return NULL;
+}
+
+//---------------------------------------------------------------------------------
+static void DisplayReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags, void* userData)
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)userData;
+ if (!winsys)
+ return;
+
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags);
+
+ // we fire the callbacks on start of configuration
+ // or when the mode set was finished
+ // or when we are called with flags == 0 (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+
+ // first check if we need to call OnLostDevice
+ if (flags & kCGDisplayBeginConfigurationFlag)
+ {
+ // pre/post-reconfiguration changes
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ if (res == RES_INVALID)
+ return;
+
+ NSScreen* pScreen = nil;
+ unsigned int screenIdx = 0;
+
+ if ( screenIdx < [[NSScreen screens] count] )
+ {
+ pScreen = [[NSScreen screens] objectAtIndex:screenIdx];
+ }
+
+ // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
+ // valid
+ if (pScreen)
+ {
+ CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
+ if (xbmc_display == display)
+ {
+ // we only respond to changes on the display we are running on.
+ winsys->AnnounceOnLostDevice();
+ winsys->StartLostDeviceTimer();
+ }
+ }
+ }
+ else // the else case checks if we need to call OnResetDevice
+ {
+ // we fire if kCGDisplaySetModeFlag is set or if flags == 0
+ // (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+ // we also don't check the screen here as we might not even have
+ // one anymore (e.x. when tv is turned off)
+ if (flags & kCGDisplaySetModeFlag || flags == 0)
+ {
+ winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
+ winsys->HandleOnResetDevice();
+ }
+ }
+
+ if ((flags & kCGDisplayAddFlag) || (flags & kCGDisplayRemoveFlag))
+ winsys->UpdateResolutions();
+}
+
+//------------------------------------------------------------------------------
+NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx);
+void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view);
+
+//------------------------------------------------------------------------------
+CWinSystemOSX::CWinSystemOSX()
+ : CWinSystemBase()
+ , m_impl{new CWinSystemOSXImpl}
+ , m_lostDeviceTimer(this)
+{
+ m_SDLSurface = NULL;
+ m_osx_events = NULL;
+ m_obscured = false;
+ m_obscured_timecheck = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
+ m_lastDisplayNr = -1;
+ m_movedToOtherScreen = false;
+ m_refreshRate = 0.0;
+ m_delayDispReset = false;
+
+ m_winEvents.reset(new CWinEventsOSX());
+
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkDARWINOSX::Register();
+ m_dpms = std::make_shared<CCocoaDPMSSupport>();
+}
+
+CWinSystemOSX::~CWinSystemOSX() = default;
+
+void CWinSystemOSX::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(LOST_DEVICE_TIMEOUT_MS, false);
+}
+
+void CWinSystemOSX::StopLostDeviceTimer()
+{
+ m_lostDeviceTimer.Stop();
+}
+
+void CWinSystemOSX::OnTimeout()
+{
+ HandleOnResetDevice();
+}
+
+bool CWinSystemOSX::InitWindowSystem()
+{
+ CLog::LogF(LOGINFO, "Setup SDL");
+
+ /* Clean up on exit, exit on window close and interrupt */
+ std::atexit(SDL_Quit);
+
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ CLog::LogF(LOGFATAL, "Unable to initialize SDL: {}", SDL_GetError());
+ return false;
+ }
+ // SDL_Init will install a handler for segfaults, restore the default handler.
+ signal(SIGSEGV, SIG_DFL);
+
+ SDL_EnableUNICODE(1);
+
+ // set repeat to 10ms to ensure repeat time < frame time
+ // so that hold times can be reliably detected
+ SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, 10);
+
+ if (!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ m_osx_events = new CWinEventsOSX();
+
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ auto windowDidMove = [windowDidMoveNoteClass initWith:this];
+ [center addObserver:windowDidMove
+ selector:@selector(windowDidMoveNotification:)
+ name:NSWindowDidMoveNotification object:nil];
+ m_impl->m_windowDidMove = windowDidMove;
+
+ auto windowDidReSize = [windowDidReSizeNoteClass initWith:this];
+ [center addObserver:windowDidReSize
+ selector:@selector(windowDidReSizeNotification:)
+ name:NSWindowDidResizeNotification object:nil];
+ m_impl->m_windowDidReSize = windowDidReSize;
+
+ auto windowDidChangeScreen = [windowDidChangeScreenNoteClass initWith:this];
+ [center addObserver:windowDidChangeScreen
+ selector:@selector(windowDidChangeScreenNotification:)
+ name:NSWindowDidChangeScreenNotification object:nil];
+ m_impl->m_windowChangedScreen = windowDidChangeScreen;
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowSystem()
+{
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center removeObserver:m_impl->m_windowDidMove name:NSWindowDidMoveNotification object:nil];
+ [center removeObserver:m_impl->m_windowDidReSize name:NSWindowDidResizeNotification object:nil];
+ [center removeObserver:m_impl->m_windowChangedScreen
+ name:NSWindowDidChangeScreenNotification
+ object:nil];
+
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ delete m_osx_events;
+ m_osx_events = NULL;
+
+ UnblankDisplays();
+ m_impl->m_glContext = nil;
+ return true;
+}
+
+bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ // force initial window creation to be windowed, if fullscreen, it will switch to it below
+ // fixes the white screen of death if starting fullscreen and switching to windowed.
+ RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ m_nWidth = resInfo.iWidth;
+ m_nHeight = resInfo.iHeight;
+ m_bFullScreen = false;
+
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ // Enable vertical sync to avoid any tearing.
+ SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
+
+ m_SDLSurface = SDL_SetVideoMode(m_nWidth, m_nHeight, 0, SDL_OPENGL | SDL_RESIZABLE);
+ if (!m_SDLSurface)
+ return false;
+
+ // the context SDL creates isn't full screen compatible, so we create new one
+ // first, find the current contect and make sure a view is attached
+ NSOpenGLContext* cur_context = [NSOpenGLContext currentContext];
+ NSView* view = [cur_context view];
+ if (!view)
+ return false;
+
+ if (CDisplaySettings::GetInstance().GetCurrentResolution() != RES_WINDOW)
+ {
+ // If we are not starting up windowed, then hide the initial SDL window
+ // so we do not see it flash before the fade-out and switch to fullscreen.
+ ShowHideNSWindow([view window], false);
+ }
+
+ // disassociate view from context
+ [cur_context clearDrawable];
+
+ // release the context
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [ NSOpenGLContext clearCurrentContext ];
+ [ cur_context clearDrawable ];
+ cur_context = nil;
+ }
+
+ // create a new context
+ auto new_context = CreateWindowedContext(nil);
+ if (!new_context)
+ return false;
+
+ // associate with current view
+ [new_context setView:view];
+ [new_context makeCurrentContext];
+
+ // set the window title
+ [[[new_context view] window]
+ setTitle:[NSString stringWithFormat:@"%s Media Center", CCompileInfo::GetAppName()]];
+
+ m_impl->m_glContext = new_context;
+ CWinSystemOSXImpl::m_lastOwnedContext = new_context;
+ m_bWindowCreated = true;
+
+ // get screen refreshrate - this is needed
+ // when we startup in windowed mode and don't run through SetFullScreen
+ int dummy;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGL::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+ CScreenshotSurfaceGL::Register();
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindow()
+{
+ return true;
+}
+
+void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view)
+{
+ if (last_view && [last_view window])
+ {
+ auto size = NSMakeSize(newWidth, newHeight);
+ NSWindow* lastWindow = [last_view window];
+ [lastWindow setContentSize:size];
+ [lastWindow update];
+ [last_view setFrameSize:size];
+ }
+}
+bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ if (!m_impl->m_glContext)
+ return false;
+
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ NSView* view;
+ NSWindow* window;
+
+ view = [context view];
+
+ if (view)
+ {
+ // It seems, that in macOS 10.15 this defaults to YES, but we currently do not support
+ // Retina resolutions properly. Ensure that the view uses a 1 pixel per point framebuffer.
+ view.wantsBestResolutionOpenGLSurface = NO;
+ }
+
+ if (view && (newWidth > 0) && (newHeight > 0))
+ {
+ window = [view window];
+ if (window)
+ {
+ [window setContentSize:NSMakeSize(newWidth, newHeight)];
+ [window update];
+ [view setFrameSize:NSMakeSize(newWidth, newHeight)];
+ [context update];
+ // this is needed in case we traverse from fullscreen screen 2
+ // to windowed on screen 1 directly where in ScreenChangedNotification
+ // we don't have a window to get the current screen on
+ // in that case ResizeWindow is called at a later stage from SetFullScreen(false)
+ // and we can grab the correct display number here then
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen( [window screen] ));
+ }
+ }
+
+ // HACK: resize SDL's view manually so that mouse bounds are correctly updated.
+ // there are two parts to this, the internal SDL (current_video->screen) and
+ // the cocoa view ( handled in SetFullScreen).
+ SDL_SetWidthHeight(newWidth, newHeight);
+
+ [context makeCurrentContext];
+
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+ m_impl->m_glContext = context;
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(m_refreshRate);
+
+ return true;
+}
+
+static bool needtoshowme = true;
+
+bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ static NSWindow* windowedFullScreenwindow = nil;
+ static NSPoint last_window_origin;
+ static NSView* last_view = nil;
+ static NSSize last_view_size;
+ static NSPoint last_view_origin;
+ static NSInteger last_window_level = NSNormalWindowLevel;
+ bool was_fullscreen = m_bFullScreen;
+ NSOpenGLContext* cur_context;
+
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = DisplayFadeToBlack(needtoshowme);
+
+ // If we're already fullscreen then we must be moving to a different display.
+ // or if we are still on the same display - it might be only a refreshrate/resolution
+ // change request.
+ // Recurse to reset fullscreen mode and then continue.
+ if (was_fullscreen && fullScreen)
+ {
+ needtoshowme = false;
+ ShowHideNSWindow([last_view window], needtoshowme);
+ RESOLUTION_INFO& window = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ CWinSystemOSX::SetFullScreen(false, window, blankOtherDisplays);
+ needtoshowme = true;
+ }
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ cur_context = [NSOpenGLContext currentContext];
+
+ //handle resolution/refreshrate switching early here
+ if (m_bFullScreen)
+ {
+ // switch videomode
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ }
+
+ //no context? done.
+ if (!cur_context)
+ {
+ DisplayFadeFromBlack(fade_token, needtoshowme);
+ return false;
+ }
+
+ if (windowedFullScreenwindow != nil)
+ {
+ [windowedFullScreenwindow close];
+ windowedFullScreenwindow = nil;
+ }
+
+ if (m_bFullScreen)
+ {
+ // FullScreen Mode
+ NSOpenGLContext* newContext = nil;
+
+ // Save info about the windowed context so we can restore it when returning to windowed.
+ last_view = [cur_context view];
+ last_view_size = [last_view frame].size;
+ last_view_origin = [last_view frame].origin;
+ last_window_origin = [[last_view window] frame].origin;
+ last_window_level = [[last_view window] level];
+
+ // This is Cocoa Windowed FullScreen Mode
+ // Get the screen rect of our current display
+ NSScreen* pScreen = [[NSScreen screens] objectAtIndex:m_lastDisplayNr];
+ NSRect screenRect = [pScreen frame];
+
+ // remove frame origin offset of original display
+ screenRect.origin = NSZeroPoint;
+
+ // make a new window to act as the windowedFullScreen
+ windowedFullScreenwindow = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+ windowedFullScreenwindow.releasedWhenClosed = NO;
+
+ [windowedFullScreenwindow setBackgroundColor:[NSColor blackColor]];
+ [windowedFullScreenwindow makeKeyAndOrderFront:nil];
+
+ // make our window the same level as the rest to enable cmd+tab switching
+ [windowedFullScreenwindow setLevel:NSNormalWindowLevel];
+ // this will make our window topmost and hide all system messages
+ //[windowedFullScreenwindow setLevel:CGShieldingWindowLevel()];
+
+ // ...and the original one beneath it and on the same screen.
+ [[last_view window] setLevel:NSNormalWindowLevel-1];
+ [[last_view window] setFrameOrigin:[pScreen frame].origin];
+ // expand the mouse bounds in SDL view to fullscreen
+ [ last_view setFrameOrigin:NSMakePoint(0.0, 0.0)];
+ [ last_view setFrameSize:NSMakeSize(m_nWidth, m_nHeight) ];
+
+ NSView* blankView = [[NSView alloc] init];
+ [windowedFullScreenwindow setContentView:blankView];
+ [windowedFullScreenwindow setContentSize:NSMakeSize(m_nWidth, m_nHeight)];
+ [windowedFullScreenwindow update];
+ [blankView setFrameSize:NSMakeSize(m_nWidth, m_nHeight)];
+
+ // force SDL's window origin to be at zero:
+ // on Monterey, X coordinate becomes non-zero somewhere after this method returns
+ const auto twoSeconds = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC);
+ dispatch_after(twoSeconds, dispatch_get_main_queue(), ^{
+ for (NSWindow* w in NSApp.windows)
+ {
+ if (w == windowedFullScreenwindow)
+ continue;
+ [w setFrameOrigin:w.screen.frame.origin];
+ break;
+ }
+ });
+
+ // Obtain windowed pixel format and create a new context.
+ newContext = CreateWindowedContext(cur_context);
+ [newContext setView:blankView];
+
+ // Hide the menu bar.
+ SetMenuBarVisible(false);
+
+ // Blank other displays if requested.
+ if (blankOtherDisplays)
+ BlankOtherDisplays(m_lastDisplayNr);
+
+ // Hide the mouse.
+ [NSCursor hide];
+
+ // Release old context if we created it.
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [cur_context clearDrawable];
+ }
+
+ // activate context
+ [newContext makeCurrentContext];
+ CWinSystemOSXImpl::m_lastOwnedContext = newContext;
+ }
+ else
+ {
+ // Windowed Mode
+ // exit fullscreen
+ [cur_context clearDrawable];
+
+ [NSCursor unhide];
+
+ // Show menubar.
+ SetMenuBarVisible(true);
+
+ // restore the windowed window level
+ [[last_view window] setLevel:last_window_level];
+
+ // Unblank.
+ // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
+ //if (blankOtherDisplays)
+ UnblankDisplays();
+
+ // create our new context (sharing with the current one)
+ auto newContext = CreateWindowedContext(cur_context);
+ if (!newContext)
+ return false;
+
+ // Assign view from old context, move back to original screen.
+ [newContext setView:last_view];
+ [[last_view window] setFrameOrigin:last_window_origin];
+ // return the mouse bounds in SDL view to previous size
+ [last_view setFrameSize:last_view_size];
+ [last_view setFrameOrigin:last_view_origin];
+
+ // Release the fullscreen context.
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [cur_context clearDrawable];
+ }
+
+ // Activate context.
+ [newContext makeCurrentContext];
+ CWinSystemOSXImpl::m_lastOwnedContext = newContext;
+ }
+
+ DisplayFadeFromBlack(fade_token, needtoshowme);
+
+ ShowHideNSWindow([last_view window], needtoshowme);
+ // need to make sure SDL tracks any window size changes
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+ ResizeWindowInternal(m_nWidth, m_nHeight, -1, -1, last_view);
+ // restore origin once again when going to windowed mode
+ if (!fullScreen)
+ {
+ [[last_view window] setFrameOrigin:last_window_origin];
+ }
+ HandlePossibleRefreshrateChange();
+
+ m_updateGLContext = 0;
+ return true;
+}
+
+void CWinSystemOSX::UpdateResolutions()
+{
+ CWinSystemBase::UpdateResolutions();
+
+ // Add desktop resolution
+ int w, h;
+ double fps;
+
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ GetScreenResolution(&w, &h, &fps, dispIdx);
+ NSString* dispName = screenNameForDisplay(GetDisplayID(dispIdx));
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), [dispName UTF8String], w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ // now just fill in the possible resolutions for the attached screens
+ // and push to the resolution info vector
+ FillInVideoModes();
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+/*
+void* Cocoa_GL_CreateContext(void* pixFmt, void* shareCtx)
+{
+ if (!pixFmt)
+ return nil;
+
+ NSOpenGLContext* newContext = [[NSOpenGLContext alloc] initWithFormat:(NSOpenGLPixelFormat*)pixFmt
+ shareContext:(NSOpenGLContext*)shareCtx];
+
+ // snipit from SDL_cocoaopengl.m
+ //
+ // Wisdom from Apple engineer in reference to UT2003's OpenGL performance:
+ // "You are blowing a couple of the internal OpenGL function caches. This
+ // appears to be happening in the VAO case. You can tell OpenGL to up
+ // the cache size by issuing the following calls right after you create
+ // the OpenGL context. The default cache size is 16." --ryan.
+ //
+
+ #ifndef GLI_ARRAY_FUNC_CACHE_MAX
+ #define GLI_ARRAY_FUNC_CACHE_MAX 284
+ #endif
+
+ #ifndef GLI_SUBMIT_FUNC_CACHE_MAX
+ #define GLI_SUBMIT_FUNC_CACHE_MAX 280
+ #endif
+
+ {
+ long cache_max = 64;
+ CGLContextObj ctx = (CGLContextObj)[newContext CGLContextObj];
+ CGLSetParameter(ctx, (CGLContextParameter)GLI_SUBMIT_FUNC_CACHE_MAX, &cache_max);
+ CGLSetParameter(ctx, (CGLContextParameter)GLI_ARRAY_FUNC_CACHE_MAX, &cache_max);
+ }
+
+ // End Wisdom from Apple Engineer section. --ryan.
+ return newContext;
+}
+*/
+
+NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx)
+{
+ NSOpenGLPixelFormat* pixFmt;
+ if (getenv("KODI_GL_PROFILE_LEGACY"))
+ {
+ NSOpenGLPixelFormatAttribute wattrs[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(8),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs];
+ }
+ else
+ {
+ NSOpenGLPixelFormatAttribute wattrs_gl3[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAOpenGLProfile,
+ NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(24),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs_gl3];
+ }
+
+ auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx];
+
+ if (!newContext)
+ {
+ // bah, try again for non-accelerated renderer
+ NSOpenGLPixelFormatAttribute wattrs2[] =
+ {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)8,
+ (NSOpenGLPixelFormatAttribute)0
+ };
+ newContext = [[NSOpenGLContext alloc]
+ initWithFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs2]
+ shareContext:shareCtx];
+ }
+
+ return newContext;
+}
+
+NSOpenGLContext* CreateFullScreenContext(int screen_index, NSOpenGLContext* shareCtx)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+ CGDirectDisplayID displayID;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ displayID = displayArray[screen_index];
+
+ NSOpenGLPixelFormat* pixFmt;
+ if (getenv("KODI_GL_PROFILE_LEGACY"))
+ {
+ NSOpenGLPixelFormatAttribute fsattrs[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(8),
+ NSOpenGLPFAScreenMask,
+ static_cast<NSOpenGLPixelFormatAttribute>(CGDisplayIDToOpenGLDisplayMask(displayID)),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs];
+ }
+ else
+ {
+ NSOpenGLPixelFormatAttribute fsattrs_gl3[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(24),
+ NSOpenGLPFAOpenGLProfile,
+ NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFAScreenMask,
+ static_cast<NSOpenGLPixelFormatAttribute>(CGDisplayIDToOpenGLDisplayMask(displayID)),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs_gl3];
+ }
+
+ if (!pixFmt)
+ return nil;
+
+ auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx];
+
+ return newContext;
+}
+
+void CWinSystemOSX::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx);
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+ *w = CGDisplayModeGetWidth(mode);
+ *h = CGDisplayModeGetHeight(mode);
+ *fps = CGDisplayModeGetRefreshRate(mode);
+ CGDisplayModeRelease(mode);
+ if ((int)*fps == 0)
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ *fps = 60.0;
+ }
+}
+
+void CWinSystemOSX::EnableVSync(bool enable)
+{
+ // OpenGL Flush synchronised with vertical retrace
+ GLint swapInterval = enable ? 1 : 0;
+ [[NSOpenGLContext currentContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+}
+
+bool CWinSystemOSX::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ boolean_t match = false;
+ CGDisplayModeRef dispMode = NULL;
+
+ int screenIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Figure out the screen size. (default to main screen)
+ CGDirectDisplayID display_id = GetDisplayID(screenIdx);
+
+ // find mode that matches the desired size, refreshrate
+ // non interlaced, nonstretched, safe for hardware
+ dispMode = GetMode(width, height, refreshrate, screenIdx);
+
+ //not found - fallback to bestemdeforparameters
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 32, width, height, match);
+
+ if (!match)
+ dispMode = BestMatchForMode(display_id, 16, width, height, match);
+
+ // still no match? fallback to current resolution of the display which HAS to work [tm]
+ if (!match)
+ {
+ int tmpWidth;
+ int tmpHeight;
+ double tmpRefresh;
+
+ GetScreenResolution(&tmpWidth, &tmpHeight, &tmpRefresh, screenIdx);
+ dispMode = GetMode(tmpWidth, tmpHeight, tmpRefresh, screenIdx);
+
+ // no way to get a resolution set
+ if (!dispMode)
+ return false;
+ }
+
+ if (!match)
+ return false;
+ }
+
+ // switch mode and return success
+ CGDisplayCapture(display_id);
+ CGDisplayConfigRef cfg;
+ CGBeginDisplayConfiguration(&cfg);
+ CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr);
+ CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly);
+ CGDisplayRelease(display_id);
+
+ m_refreshRate = CGDisplayModeGetRefreshRate(dispMode);
+
+ Cocoa_CVDisplayLinkUpdate();
+
+ return (err == kCGErrorSuccess);
+}
+
+void CWinSystemOSX::FillInVideoModes()
+{
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Add full screen settings for additional monitors
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ Boolean stretched;
+ Boolean interlaced;
+ Boolean safeForHardware;
+ Boolean televisionoutput;
+ int w, h, bitsperpixel;
+ double refreshrate;
+ RESOLUTION_INFO res;
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp));
+ NSString *dispName = screenNameForDisplay(GetDisplayID(disp));
+
+ CLog::Log(LOGINFO, "Display {} has name {}", disp, [dispName UTF8String]);
+
+ if (NULL == displayModes)
+ continue;
+
+ for (int i=0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = flags & kDisplayModeStretchedFlag ? true : false;
+ interlaced = flags & kDisplayModeInterlacedFlag ? true : false;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = flags & kDisplayModeSafetyFlags ? true : false;
+ televisionoutput = flags & kDisplayModeTelevisionFlag ? true : false;
+
+ if ((bitsperpixel == 32) &&
+ (safeForHardware == YES) &&
+ (stretched == NO) &&
+ (interlaced == NO))
+ {
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ refreshrate = CGDisplayModeGetRefreshRate(displayMode);
+ if ((int)refreshrate == 0) // LCD display?
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ refreshrate = 60.0;
+ }
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} @ {:f} Hz", disp,
+ w, h, refreshrate);
+
+ // only add the resolution if it belongs to "our" screen
+ // all others are only logged above...
+ if (disp == dispIdx)
+ {
+ UpdateDesktopResolution(res, (dispName != nil) ? [dispName UTF8String] : "Unknown", w, h, refreshrate, 0);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+ }
+ }
+ CFRelease(displayModes);
+ }
+}
+
+bool CWinSystemOSX::FlushBuffer(void)
+{
+ if (m_updateGLContext < 5)
+ {
+ [m_impl->m_glContext update];
+ m_updateGLContext++;
+ }
+
+ [m_impl->m_glContext flushBuffer];
+
+ return true;
+}
+
+bool CWinSystemOSX::IsObscured(void)
+{
+ // check once a second if we are obscured.
+ auto now_time = std::chrono::steady_clock::now();
+ if (m_obscured_timecheck > now_time)
+ return m_obscured;
+ else
+ m_obscured_timecheck = now_time + std::chrono::milliseconds(1000);
+
+ NSOpenGLContext* cur_context = [NSOpenGLContext currentContext];
+ NSView* view = [cur_context view];
+ if (!view)
+ {
+ // sanity check, we should always have a view
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ NSWindow *window = [view window];
+ if (!window)
+ {
+ // sanity check, we should always have a window
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ if ([window isVisible] == NO)
+ {
+ // not visible means the window is not showing.
+ // this should never really happen as we are always visible
+ // even when minimized in dock.
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // check if we are minimized (to an icon in the Dock).
+ if ([window isMiniaturized] == YES)
+ {
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // check if we are showing on the active workspace.
+ if ([window isOnActiveSpace] == NO)
+ {
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // default to false before we start parsing though the windows.
+ // if we are are obscured by any windows, then set true.
+ m_obscured = false;
+ static bool obscureLogged = false;
+
+ CGWindowListOption opts;
+ opts = kCGWindowListOptionOnScreenAboveWindow | kCGWindowListExcludeDesktopElements;
+ CFArrayRef windowIDs =CGWindowListCreate(opts, (CGWindowID)[window windowNumber]);
+
+ if (!windowIDs)
+ return m_obscured;
+
+ CFArrayRef windowDescs = CGWindowListCreateDescriptionFromArray(windowIDs);
+ if (!windowDescs)
+ {
+ CFRelease(windowIDs);
+ return m_obscured;
+ }
+
+ CGRect bounds = NSRectToCGRect([window frame]);
+ // kCGWindowBounds measures the origin as the top-left corner of the rectangle
+ // relative to the top-left corner of the screen.
+ // NSWindow’s frame property measures the origin as the bottom-left corner
+ // of the rectangle relative to the bottom-left corner of the screen.
+ // convert bounds from NSWindow to CGWindowBounds here.
+ bounds.origin.y = [[window screen] frame].size.height - bounds.origin.y - bounds.size.height;
+
+ std::vector<CRect> partialOverlaps;
+ CRect ourBounds = CGRectToCRect(bounds);
+
+ for (CFIndex idx=0; idx < CFArrayGetCount(windowDescs); idx++)
+ {
+ // walk the window list of windows that are above us and are not desktop elements
+ CFDictionaryRef windowDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDescs, idx);
+
+ // skip the Dock window, it actually covers the entire screen.
+ CFStringRef ownerName = (CFStringRef)CFDictionaryGetValue(windowDictionary, kCGWindowOwnerName);
+ if (CFStringCompare(ownerName, CFSTR("Dock"), 0) == kCFCompareEqualTo)
+ continue;
+
+ // Ignore known brightness tools for dimming the screen. They claim to cover
+ // the whole XBMC window and therefore would make the framerate limiter
+ // kicking in. Unfortunately even the alpha of these windows is 1.0 so
+ // we have to check the ownerName.
+ if (CFStringCompare(ownerName, CFSTR("Shades"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("SmartSaver"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Brightness Slider"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Displaperture"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Dreamweaver"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Window Server"), 0) == kCFCompareEqualTo)
+ continue;
+
+ CFDictionaryRef rectDictionary = (CFDictionaryRef)CFDictionaryGetValue(windowDictionary, kCGWindowBounds);
+ if (!rectDictionary)
+ continue;
+
+ CGRect windowBounds;
+ if (CGRectMakeWithDictionaryRepresentation(rectDictionary, &windowBounds))
+ {
+ if (CGRectContainsRect(windowBounds, bounds))
+ {
+ // if the windowBounds completely encloses our bounds, we are obscured.
+ if (!obscureLogged)
+ {
+ std::string appName;
+ if (CDarwinUtils::CFStringRefToUTF8String(ownerName, appName))
+ CLog::Log(LOGDEBUG, "WinSystemOSX: Fullscreen window {} obscures Kodi!", appName);
+ obscureLogged = true;
+ }
+ m_obscured = true;
+ break;
+ }
+
+ // handle overlapping windows above us that combine
+ // to obscure by collecting any partial overlaps,
+ // then subtract them from our bounds and check
+ // for any remaining area.
+ CRect intersection = CGRectToCRect(windowBounds);
+ intersection.Intersect(ourBounds);
+ if (!intersection.IsEmpty())
+ partialOverlaps.push_back(intersection);
+ }
+ }
+
+ if (!m_obscured)
+ {
+ // if we are here we are not obscured by any fullscreen window - reset flag
+ // for allowing the logmessage above to show again if this changes.
+ if (obscureLogged)
+ obscureLogged = false;
+ std::vector<CRect> rects = ourBounds.SubtractRects(partialOverlaps);
+ // they got us covered
+ if (rects.empty())
+ m_obscured = true;
+ }
+
+ CFRelease(windowDescs);
+ CFRelease(windowIDs);
+
+ return m_obscured;
+}
+
+void CWinSystemOSX::NotifyAppFocusChange(bool bGaining)
+{
+ if (!(m_bFullScreen && bGaining))
+ return;
+ @autoreleasepool
+ {
+ // find the window
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ if (context)
+ {
+ NSView* view;
+
+ view = [context view];
+ if (view)
+ {
+ NSWindow* window;
+ window = [view window];
+ if (window)
+ {
+ SetMenuBarVisible(false);
+ [window orderFront:nil];
+ }
+ }
+ }
+ }
+}
+
+void CWinSystemOSX::ShowOSMouse(bool show)
+{
+ SDL_ShowCursor(show ? 1 : 0);
+}
+
+bool CWinSystemOSX::Minimize()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] miniaturizeAll:nil];
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Restore()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] unhide:nil];
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Hide()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] hide:nil];
+ }
+ return true;
+}
+
+void CWinSystemOSX::HandlePossibleRefreshrateChange()
+{
+ static double oldRefreshRate = m_refreshRate;
+ Cocoa_CVDisplayLinkUpdate();
+ int dummy = 0;
+
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ if (oldRefreshRate != m_refreshRate)
+ {
+ oldRefreshRate = m_refreshRate;
+ // send a message so that videoresolution (and refreshrate)
+ // is changed
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, m_SDLSurface->w, m_SDLSurface->h);
+ }
+}
+
+void CWinSystemOSX::OnMove(int x, int y)
+{
+ HandlePossibleRefreshrateChange();
+}
+
+std::unique_ptr<IOSScreenSaver> CWinSystemOSX::GetOSScreenSaverImpl()
+{
+ return std::unique_ptr<IOSScreenSaver> (new COSScreenSaverOSX);
+}
+
+OSXTextInputResponder *g_textInputResponder = nil;
+
+void CWinSystemOSX::StartTextInput()
+{
+ NSView *parentView = [[NSApp keyWindow] contentView];
+
+ /* We only keep one field editor per process, since only the front most
+ * window can receive text input events, so it make no sense to keep more
+ * than one copy. When we switched to another window and requesting for
+ * text input, simply remove the field editor from its superview then add
+ * it to the front most window's content view */
+ if (!g_textInputResponder) {
+ g_textInputResponder =
+ [[OSXTextInputResponder alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)];
+ }
+
+ if (![[g_textInputResponder superview] isEqual: parentView])
+ {
+// DLOG(@"add fieldEdit to window contentView");
+ [g_textInputResponder removeFromSuperview];
+ [parentView addSubview: g_textInputResponder];
+ [[NSApp keyWindow] makeFirstResponder: g_textInputResponder];
+ }
+}
+void CWinSystemOSX::StopTextInput()
+{
+ if (g_textInputResponder)
+ {
+ [g_textInputResponder removeFromSuperview];
+ g_textInputResponder = nil;
+ }
+}
+
+void CWinSystemOSX::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemOSX::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+bool CWinSystemOSX::Show(bool raise)
+{
+ @autoreleasepool
+ {
+ auto app = [NSApplication sharedApplication];
+ if (raise)
+ {
+ [app unhide:nil];
+ [app activateIgnoringOtherApps:YES];
+ [app arrangeInFront:nil];
+ }
+ else
+ {
+ [app unhideWithoutActivation];
+ }
+ }
+ return true;
+}
+
+void CWinSystemOSX::WindowChangedScreen()
+{
+ // user has moved the window to a
+ // different screen
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ m_lastDisplayNr = -1;
+
+ // if we are here the user dragged the window to a different
+ // screen and we return the screen of the window
+ if (context)
+ {
+ NSView* view;
+
+ view = [context view];
+ if (view)
+ {
+ NSWindow* window;
+ window = [view window];
+ if (window)
+ {
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen([window screen]));
+ }
+ }
+ }
+ if (m_lastDisplayNr == -1)
+ m_lastDisplayNr = 0;// default to main screen
+}
+
+void CWinSystemOSX::AnnounceOnLostDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnLostDevice");
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+}
+
+void CWinSystemOSX::HandleOnResetDevice()
+{
+
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ else
+ {
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSX::AnnounceOnResetDevice()
+{
+ double currentFps = m_refreshRate;
+ int w = 0;
+ int h = 0;
+ int currentScreenIdx = m_lastDisplayNr;
+ // ensure that graphics context knows about the current refreshrate before
+ // doing the callbacks
+ GetScreenResolution(&w, &h, &currentFps, currentScreenIdx);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(currentFps);
+
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnResetDevice");
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+}
+
+void* CWinSystemOSX::GetCGLContextObj()
+{
+ return [m_impl->m_glContext CGLContextObj];
+}
+
+NSOpenGLContext* CWinSystemOSX::GetNSOpenGLContext()
+{
+ return m_impl->m_glContext;
+}
+
+std::string CWinSystemOSX::GetClipboardText(void)
+{
+ std::string utf8_text;
+
+ const char *szStr = Cocoa_Paste();
+ if (szStr)
+ utf8_text = szStr;
+
+ return utf8_text;
+}
+
+std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncOsx(clock));
+ return pVSync;
+}
+
+bool CWinSystemOSX::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+std::vector<std::string> CWinSystemOSX::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.emplace_back("Default");
+
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString *dispName = screenNameForDisplay(GetDisplayID(disp));
+ outputs.emplace_back([dispName UTF8String]);
+ }
+
+ return outputs;
+}
diff --git a/xbmc/windowing/osx/VideoSyncOsx.h b/xbmc/windowing/osx/VideoSyncOsx.h
new file mode 100644
index 0000000..b3ba145
--- /dev/null
+++ b/xbmc/windowing/osx/VideoSyncOsx.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/DispResource.h"
+#include "threads/Event.h"
+#include "windowing/VideoSync.h"
+
+class CVideoSyncOsx : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncOsx(void* clock)
+ : CVideoSync(clock), m_LastVBlankTime(0), m_displayLost(false), m_displayReset(false)
+ {
+ }
+
+ // CVideoSync interface
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+ void RefreshChanged() override;
+
+ // IDispResource interface
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+
+ // used in the displaylink callback
+ void VblankHandler(int64_t nowtime, uint32_t timebase);
+
+private:
+ virtual bool InitDisplayLink();
+ virtual void DeinitDisplayLink();
+
+ int64_t m_LastVBlankTime; //timestamp of the last vblank, used for calculating how many vblanks happened
+ volatile bool m_displayLost;
+ volatile bool m_displayReset;
+ CEvent m_lostEvent;
+};
+
diff --git a/xbmc/windowing/osx/VideoSyncOsx.mm b/xbmc/windowing/osx/VideoSyncOsx.mm
new file mode 100644
index 0000000..d19e724
--- /dev/null
+++ b/xbmc/windowing/osx/VideoSyncOsx.mm
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoSyncOsx.h"
+
+#include "ServiceBroker.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+#include <CoreVideo/CVHostTime.h>
+#include <QuartzCore/CVDisplayLink.h>
+#include <unistd.h>
+
+using namespace std::chrono_literals;
+
+bool CVideoSyncOsx::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} setting up OSX", __FUNCTION__);
+
+ //init the vblank timestamp
+ m_LastVBlankTime = 0;
+ UpdateClock = func;
+ m_displayLost = false;
+ m_displayReset = false;
+ m_lostEvent.Reset();
+
+ CServiceBroker::GetWinSystem()->Register(this);
+
+ return true;
+}
+
+void CVideoSyncOsx::Run(CEvent& stopEvent)
+{
+ InitDisplayLink();
+
+ //because cocoa has a vblank callback, we just keep sleeping until we're asked to stop the thread
+ while(!stopEvent.Signaled() && !m_displayLost && !m_displayReset)
+ {
+ usleep(100000);
+ }
+
+ m_lostEvent.Set();
+
+ while(!stopEvent.Signaled() && m_displayLost && !m_displayReset)
+ {
+ usleep(10000);
+ }
+
+ DeinitDisplayLink();
+}
+
+void CVideoSyncOsx::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} cleaning up OSX", __FUNCTION__);
+ m_lostEvent.Set();
+ m_LastVBlankTime = 0;
+ CServiceBroker::GetWinSystem()->Unregister(this);
+}
+
+float CVideoSyncOsx::GetFps()
+{
+ m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} Detected refreshrate: {:f} hertz", __FUNCTION__, m_fps);
+ return m_fps;
+}
+
+void CVideoSyncOsx::RefreshChanged()
+{
+ m_displayReset = true;
+}
+
+void CVideoSyncOsx::OnLostDisplay()
+{
+ if (!m_displayLost)
+ {
+ m_displayLost = true;
+ m_lostEvent.Wait(1000ms);
+ }
+}
+
+void CVideoSyncOsx::OnResetDisplay()
+{
+ m_displayReset = true;
+}
+
+void CVideoSyncOsx::VblankHandler(int64_t nowtime, uint32_t timebase)
+{
+ int NrVBlanks;
+ double VBlankTime;
+ int64_t Now = CurrentHostCounter();
+
+ if (m_LastVBlankTime != 0)
+ {
+ VBlankTime = (double)(nowtime - m_LastVBlankTime) / (double)timebase;
+ NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps));
+
+ //update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, Now, m_refClock);
+ }
+
+ //save the timestamp of this vblank so we can calculate how many happened next time
+ m_LastVBlankTime = nowtime;
+}
+
+// Called by the Core Video Display Link whenever it's appropriate to render a frame.
+static CVReturn DisplayLinkCallBack(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
+{
+ @autoreleasepool
+ {
+ CVideoSyncOsx* VideoSyncOsx = reinterpret_cast<CVideoSyncOsx*>(displayLinkContext);
+
+ if (inOutputTime->flags & kCVTimeStampHostTimeValid)
+ VideoSyncOsx->VblankHandler(inOutputTime->hostTime, CVGetHostClockFrequency());
+ else
+ VideoSyncOsx->VblankHandler(CVGetCurrentHostTime(), CVGetHostClockFrequency());
+ }
+
+ return kCVReturnSuccess;
+}
+
+bool CVideoSyncOsx::InitDisplayLink()
+{
+ bool ret = true;
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} setting up displaylink", __FUNCTION__);
+
+ if (!Cocoa_CVDisplayLinkCreate((void*)DisplayLinkCallBack, reinterpret_cast<void*>(this)))
+ {
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} Cocoa_CVDisplayLinkCreate failed", __FUNCTION__);
+ ret = false;
+ }
+ return ret;
+}
+
+void CVideoSyncOsx::DeinitDisplayLink()
+{
+ Cocoa_CVDisplayLinkRelease();
+}
+
diff --git a/xbmc/windowing/osx/WinEventsOSX.h b/xbmc/windowing/osx/WinEventsOSX.h
new file mode 100644
index 0000000..7c4644f
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSX.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Thread.h"
+#include "windowing/WinEvents.h"
+#include "windowing/XBMC_events.h"
+
+#include <memory>
+
+struct CWinEventsOSXImplWrapper;
+
+class CWinEventsOSX : public IWinEvents, public CThread
+{
+public:
+ CWinEventsOSX();
+ ~CWinEventsOSX();
+
+ void MessagePush(XBMC_Event* newEvent);
+ bool MessagePump();
+ size_t GetQueueSize();
+
+ void enableInputEvents();
+ void disableInputEvents();
+
+private:
+ std::unique_ptr<CWinEventsOSXImplWrapper> m_eventsImplWrapper;
+};
diff --git a/xbmc/windowing/osx/WinEventsOSX.mm b/xbmc/windowing/osx/WinEventsOSX.mm
new file mode 100644
index 0000000..b2d07db
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSX.mm
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinEventsOSX.h"
+
+#import "WinEventsOSXImpl.h"
+
+struct CWinEventsOSXImplWrapper
+{
+ CWinEventsOSXImpl* callbackClass;
+};
+
+CWinEventsOSX::CWinEventsOSX() : CThread("CWinEventsOSX")
+{
+ m_eventsImplWrapper = std::make_unique<CWinEventsOSXImplWrapper>();
+ m_eventsImplWrapper->callbackClass = [CWinEventsOSXImpl new];
+ Create();
+}
+
+CWinEventsOSX::~CWinEventsOSX()
+{
+ m_bStop = true;
+ StopThread(true);
+}
+
+void CWinEventsOSX::MessagePush(XBMC_Event* newEvent)
+{
+ [m_eventsImplWrapper->callbackClass MessagePush:newEvent];
+}
+
+size_t CWinEventsOSX::GetQueueSize()
+{
+ return [m_eventsImplWrapper->callbackClass GetQueueSize];
+}
+
+bool CWinEventsOSX::MessagePump()
+{
+ return [m_eventsImplWrapper->callbackClass MessagePump];
+}
+
+void CWinEventsOSX::enableInputEvents()
+{
+ return [m_eventsImplWrapper->callbackClass enableInputEvents];
+}
+
+void CWinEventsOSX::disableInputEvents()
+{
+ return [m_eventsImplWrapper->callbackClass disableInputEvents];
+}
diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.h b/xbmc/windowing/osx/WinEventsOSXImpl.h
new file mode 100644
index 0000000..2dcacd4
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSXImpl.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "windowing/osx/WinEventsOSX.h"
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <Foundation/Foundation.h>
+
+@interface CWinEventsOSXImpl : NSObject
+
+- (void)MessagePush:(XBMC_Event*)newEvent;
+- (size_t)GetQueueSize;
+- (bool)MessagePump;
+- (void)enableInputEvents;
+- (void)disableInputEvents;
+- (XBMC_Event)keyPressEvent:(CGEventRef*)event;
+
+@end
diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm
new file mode 100644
index 0000000..48c4e37
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinEventsOSXImpl.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "input/XBMC_keysym.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "input/mouse/MouseStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "threads/CriticalSection.h"
+#include "utils/log.h"
+#include "windowing/osx/WinSystemOSX.h"
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#import <AppKit/AppKit.h>
+#import <Carbon/Carbon.h> // kvk_ANSI_ keycodes
+#import <CoreGraphics/CoreGraphics.h>
+#import <Foundation/Foundation.h>
+
+#pragma mark - objc implementation
+
+@implementation CWinEventsOSXImpl
+{
+ std::queue<XBMC_Event> events;
+ CCriticalSection m_inputlock;
+ id mLocalMonitorId;
+}
+
+#pragma mark - init
+
+- (instancetype)init
+{
+ self = [super init];
+
+ [self enableInputEvents];
+
+ return self;
+}
+
+- (void)MessagePush:(XBMC_Event*)newEvent
+{
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ events.emplace(*newEvent);
+}
+
+- (bool)MessagePump
+{
+
+ bool ret = false;
+
+ // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+ // events the loop won't finish then it will block xbmc main message loop.
+ for (size_t pumpEventCount = [self GetQueueSize]; pumpEventCount > 0; --pumpEventCount)
+ {
+ // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ XBMC_Event pumpEvent = {};
+ {
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ if (events.size() == 0)
+ return ret;
+ pumpEvent = events.front();
+ events.pop();
+ }
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(pumpEvent);
+ }
+ return ret;
+}
+
+- (size_t)GetQueueSize
+{
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ return events.size();
+}
+
+- (unichar)OsxKey2XbmcKey:(unichar)character
+{
+ switch (character)
+ {
+ case kVK_ANSI_8:
+ case NSLeftArrowFunctionKey:
+ return XBMCK_LEFT;
+ case kVK_ANSI_0:
+ case NSRightArrowFunctionKey:
+ return XBMCK_RIGHT;
+ case kVK_ANSI_RightBracket:
+ case NSUpArrowFunctionKey:
+ return XBMCK_UP;
+ case kVK_ANSI_O:
+ case NSDownArrowFunctionKey:
+ return XBMCK_DOWN;
+ case NSDeleteCharacter:
+ return XBMCK_BACKSPACE;
+ default:
+ return character;
+ }
+}
+
+- (XBMCMod)OsxMod2XbmcMod:(CGEventFlags)appleModifier
+{
+ unsigned int xbmcModifier = XBMCKMOD_NONE;
+ // shift left
+ if (appleModifier & kCGEventFlagMaskAlphaShift)
+ xbmcModifier |= XBMCKMOD_LSHIFT;
+ // shift right
+ if (appleModifier & kCGEventFlagMaskShift)
+ xbmcModifier |= XBMCKMOD_RSHIFT;
+ // left ctrl
+ if (appleModifier & kCGEventFlagMaskControl)
+ xbmcModifier |= XBMCKMOD_LCTRL;
+ // left alt/option
+ if (appleModifier & kCGEventFlagMaskAlternate)
+ xbmcModifier |= XBMCKMOD_LALT;
+ // left command
+ if (appleModifier & kCGEventFlagMaskCommand)
+ xbmcModifier |= XBMCKMOD_LMETA;
+
+ return static_cast<XBMCMod>(xbmcModifier);
+}
+
+- (bool)ProcessOSXShortcuts:(XBMC_Event&)event
+{
+ const auto cmd = (event.key.keysym.mod & (XBMCKMOD_LMETA | XBMCKMOD_RMETA)) != 0;
+ if (cmd && event.type == XBMC_KEYDOWN)
+ {
+ switch (event.key.keysym.sym)
+ {
+ case XBMCK_q: // CMD-q to quit
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ return true;
+
+ case XBMCK_CTRLF: // CMD-CTRL-f to toggle fullscreen
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ return true;
+
+ case XBMCK_s: // CMD-s to take a screenshot
+ {
+ CAction* action = new CAction(ACTION_TAKE_SCREENSHOT);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(action));
+ return true;
+ }
+ case XBMCK_h: // CMD-h to hide (but we minimize for now)
+ case XBMCK_m: // CMD-m to minimize
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+- (void)enableInputEvents
+{
+ [self disableInputEvents]; // allow only one registration at a time
+
+ // clang-format off
+ // Create an event tap. We are interested in mouse and keyboard events.
+ NSEventMask eventMask =
+ NSEventMaskKeyDown | NSEventMaskKeyUp |
+ NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp |
+ NSEventMaskRightMouseDown | NSEventMaskRightMouseUp |
+ NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp |
+ NSEventMaskScrollWheel |
+ NSEventMaskLeftMouseDragged |
+ NSEventMaskRightMouseDragged |
+ NSEventMaskOtherMouseDragged |
+ NSEventMaskMouseMoved;
+ // clang-format on
+
+ mLocalMonitorId = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask
+ handler:^(NSEvent* event) {
+ return [self InputEventHandler:event];
+ }];
+}
+
+- (void)disableInputEvents
+{
+ // Disable the local Monitor
+ if (mLocalMonitorId != nil)
+ [NSEvent removeMonitor:mLocalMonitorId];
+ mLocalMonitorId = nil;
+}
+
+- (NSEvent*)InputEventHandler:(NSEvent*)nsevent
+{
+ bool passEvent = true;
+ CGEventRef event = nsevent.CGEvent;
+ CGEventType type = CGEventGetType(event);
+
+ // The incoming mouse position.
+ NSPoint location = nsevent.locationInWindow;
+ if (location.x < 0 || location.y < 0)
+ return nsevent;
+
+ // cocoa world is upside down ...
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return nsevent;
+
+ NSRect frame = winSystem->GetWindowDimensions();
+ location.y = frame.size.height - location.y;
+
+ XBMC_Event newEvent = {};
+
+ switch (type)
+ {
+ // handle mouse events and transform them into the xbmc event world
+ case kCGEventLeftMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventLeftMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventRightMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventRightMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventOtherMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventOtherMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventMouseMoved:
+ case kCGEventLeftMouseDragged:
+ case kCGEventRightMouseDragged:
+ case kCGEventOtherMouseDragged:
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = location.x;
+ newEvent.motion.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventScrollWheel:
+ // very strange, real scrolls have non-zero deltaY followed by same number of events
+ // with a zero deltaY. This reverses our scroll which is WTF? anoying. Trap them out here.
+ if (nsevent.deltaY != 0.0)
+ {
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ newEvent.button.button =
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1) > 0
+ ? XBMC_BUTTON_WHEELUP
+ : XBMC_BUTTON_WHEELDOWN;
+ [self MessagePush:&newEvent];
+
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ [self MessagePush:&newEvent];
+ }
+ break;
+
+ // handle keyboard events and transform them into the xbmc event world
+ case kCGEventKeyUp:
+ newEvent = [self keyPressEvent:&event];
+ newEvent.type = XBMC_KEYUP;
+
+ [self MessagePush:&newEvent];
+ passEvent = false;
+ break;
+ case kCGEventKeyDown:
+ newEvent = [self keyPressEvent:&event];
+ newEvent.type = XBMC_KEYDOWN;
+
+ if (![self ProcessOSXShortcuts:newEvent])
+ [self MessagePush:&newEvent];
+ passEvent = false;
+
+ break;
+ default:
+ return nsevent;
+ }
+ // We must return the event for it to be useful if not already handled
+ if (passEvent)
+ return nsevent;
+ else
+ return nullptr;
+}
+
+- (XBMC_Event)keyPressEvent:(CGEventRef*)event
+{
+ UniCharCount actualStringLength = 0;
+ // Get stringlength of event
+ CGEventKeyboardGetUnicodeString(*event, 0, &actualStringLength, nullptr);
+
+ // Create array with size of event string
+ UniChar unicodeString[actualStringLength];
+ memset(unicodeString, 0, sizeof(unicodeString));
+
+ auto keycode =
+ static_cast<CGKeyCode>(CGEventGetIntegerValueField(*event, kCGKeyboardEventKeycode));
+ CGEventKeyboardGetUnicodeString(*event, sizeof(unicodeString) / sizeof(*unicodeString),
+ &actualStringLength, unicodeString);
+
+ XBMC_Event newEvent = {};
+
+ // May be possible for actualStringLength > 1. Havent been able to replicate anything
+ // larger than 1, but keep in mind for any regressions
+ if (actualStringLength == 0)
+ {
+ return newEvent;
+ }
+ else if (actualStringLength > 1)
+ {
+ CLog::Log(LOGERROR, "CWinEventsOSXImpl::keyPressEvent - event string > 1 - size: {}",
+ static_cast<int>(actualStringLength));
+ return newEvent;
+ }
+
+ unicodeString[0] = [self OsxKey2XbmcKey:unicodeString[0]];
+
+ newEvent.key.keysym.scancode = keycode;
+ newEvent.key.keysym.sym = static_cast<XBMCKey>(unicodeString[0]);
+ newEvent.key.keysym.unicode = unicodeString[0];
+ newEvent.key.keysym.mod = [self OsxMod2XbmcMod:CGEventGetFlags(*event)];
+
+ return newEvent;
+}
+
+@end
diff --git a/xbmc/windowing/osx/WinSystemOSX.h b/xbmc/windowing/osx/WinSystemOSX.h
new file mode 100644
index 0000000..0ac08d4
--- /dev/null
+++ b/xbmc/windowing/osx/WinSystemOSX.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Timer.h"
+#include "windowing/WinSystem.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+typedef struct _CGLContextObject* CGLContextObj;
+typedef struct CGRect NSRect;
+
+class IDispResource;
+class CWinEventsOSX;
+#ifdef __OBJC__
+@class NSWindow;
+@class OSXGLView;
+#else
+struct NSWindow;
+struct OSXGLView;
+#endif
+
+class CWinSystemOSX : public CWinSystemBase, public ITimerCallback
+{
+public:
+ CWinSystemOSX();
+ ~CWinSystemOSX() override;
+
+ // ITimerCallback interface
+ void OnTimeout() override;
+
+ // CWinSystemBase
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+ bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override;
+ bool DestroyWindow() override;
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void OnMove(int x, int y) override;
+
+ void SetOcclusionState(bool occluded);
+
+ std::string GetClipboardText() override;
+
+ void Register(IDispResource* resource) override;
+ void Unregister(IDispResource* resource) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ void WindowChangedScreen();
+
+ void AnnounceOnLostDevice();
+ void AnnounceOnResetDevice();
+ void HandleOnResetDevice();
+ void StartLostDeviceTimer();
+ void StopLostDeviceTimer();
+
+ void SetMovedToOtherScreen(bool moved) { m_movedToOtherScreen = moved; }
+ int CheckDisplayChanging(uint32_t flags);
+ void SetFullscreenWillToggle(bool toggle) { m_fullscreenWillToggle = toggle; }
+ bool GetFullscreenWillToggle() { return m_fullscreenWillToggle; }
+
+ CGLContextObj GetCGLContextObj();
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ // winevents override
+ bool MessagePump() override;
+
+ NSRect GetWindowDimensions();
+ void enableInputEvents();
+ void disableInputEvents();
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+
+ void GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void EnableVSync(bool enable);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ void FillInVideoModes();
+ bool FlushBuffer();
+ bool IsObscured();
+
+ bool DestroyWindowInternal();
+
+ std::unique_ptr<CWinEventsOSX> m_winEvents;
+
+ std::string m_name;
+ bool m_obscured;
+ NSWindow* m_appWindow;
+ OSXGLView* m_glView;
+ bool m_movedToOtherScreen;
+ int m_lastDisplayNr;
+ double m_refreshRate;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ CTimer m_lostDeviceTimer;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+ bool m_fullscreenWillToggle;
+ CCriticalSection m_critSection;
+};
diff --git a/xbmc/windowing/osx/WinSystemOSX.mm b/xbmc/windowing/osx/WinSystemOSX.mm
new file mode 100644
index 0000000..85dd4b7
--- /dev/null
+++ b/xbmc/windowing/osx/WinSystemOSX.mm
@@ -0,0 +1,1305 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WinSystemOSX.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
+#include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "guilib/DispResource.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/CriticalSection.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/osx/CocoaDPMSSupport.h"
+#include "windowing/osx/OSScreenSaverOSX.h"
+#import "windowing/osx/OpenGL/OSXGLView.h"
+#import "windowing/osx/OpenGL/OSXGLWindow.h"
+#include "windowing/osx/VideoSyncOsx.h"
+#include "windowing/osx/WinEventsOSX.h"
+
+#include "platform/darwin/DarwinUtils.h"
+#include "platform/darwin/DictionaryUtils.h"
+#include "platform/darwin/osx/CocoaInterface.h"
+#include "platform/darwin/osx/powermanagement/CocoaPowerSyscall.h"
+
+#include <chrono>
+#include <cstdlib>
+#include <mutex>
+#include <signal.h>
+
+#import <Cocoa/Cocoa.h>
+#import <Foundation/Foundation.h>
+#import <IOKit/graphics/IOGraphicsLib.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <QuartzCore/QuartzCore.h>
+
+using namespace KODI;
+using namespace MESSAGING;
+using namespace WINDOWING;
+using namespace std::chrono_literals;
+
+#define MAX_DISPLAYS 32
+static NSWindow* blankingWindows[MAX_DISPLAYS];
+
+size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode)
+{
+ size_t bitsPerPixel = 0;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ // No replacement for CGDisplayModeCopyPixelEncoding
+ // Disable warning for now
+ CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode);
+#pragma GCC diagnostic pop
+
+ if (CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 32;
+ }
+ else if (CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 16;
+ }
+ else if (CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 8;
+ }
+
+ CFRelease(pixEnc);
+
+ return bitsPerPixel;
+}
+
+#pragma mark - GetScreenName
+
+NSString* screenNameForDisplay(CGDirectDisplayID displayID)
+{
+ NSString* screenName;
+ @autoreleasepool
+ {
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ // No real replacement of CGDisplayIOServicePort
+ // Stackoverflow links to https://github.com/glfw/glfw/pull/192 as a possible replacement
+ // disable warning for now
+ NSDictionary* deviceInfo = (__bridge_transfer NSDictionary*)IODisplayCreateInfoDictionary(
+ CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
+
+#pragma GCC diagnostic pop
+
+ NSDictionary* localizedNames =
+ [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
+
+ if ([localizedNames count] > 0)
+ {
+ screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
+ }
+ }
+
+ if (screenName == nil)
+ {
+ screenName = [[NSString alloc] initWithFormat:@"%i", displayID];
+ }
+ else
+ {
+ // ensure screen name is unique by appending displayid
+ screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID) stringValue]];
+ }
+
+ return screenName;
+}
+
+#pragma mark - GetDisplay
+
+CGDirectDisplayID GetDisplayID(int screen_index)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ uint32_t numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ if (screen_index >= 0 && screen_index < static_cast<int>(numDisplays))
+ return (displayArray[screen_index]);
+ else
+ return (displayArray[0]);
+}
+
+CGDirectDisplayID GetDisplayIDFromScreen(NSScreen* screen)
+{
+ NSDictionary* screenInfo = screen.deviceDescription;
+ NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
+
+ return (CGDirectDisplayID)[screenID longValue];
+}
+
+int GetDisplayIndex(CGDirectDisplayID display)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ while (numDisplays > 0)
+ {
+ if (display == displayArray[--numDisplays])
+ return numDisplays;
+ }
+ return -1;
+}
+
+int GetDisplayIndex(const std::string& dispName)
+{
+ int ret = 0;
+
+ // Add full screen settings for additional monitors
+ int numDisplays = NSScreen.screens.count;
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString* name = screenNameForDisplay(GetDisplayID(disp));
+ if (name.UTF8String == dispName)
+ {
+ ret = disp;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+#pragma mark - Display Modes
+
+CFArrayRef GetAllDisplayModes(CGDirectDisplayID display)
+{
+ int value = 1;
+
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
+ if (!number)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Number!");
+ return nullptr;
+ }
+
+ CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
+ CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&key,
+ (const void**)&number, 1, nullptr, nullptr);
+ CFRelease(number);
+
+ if (!options)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Dictionary!");
+ return nullptr;
+ }
+
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
+ CFRelease(options);
+
+ if (!displayModes)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - no displaymodes found!");
+ return nullptr;
+ }
+
+ return displayModes;
+}
+
+// try to find mode that matches the desired size, refreshrate
+// non interlaced, nonstretched, safe for hardware
+CGDisplayModeRef GetMode(int width, int height, double refreshrate, int screenIdx)
+{
+ if (screenIdx >= (signed)[[NSScreen screens] count])
+ return nullptr;
+
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ int w, h, bitsperpixel;
+ double rate;
+ RESOLUTION_INFO res;
+
+ CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with {} x {} @ {} Hz on display {}", width,
+ height, refreshrate, screenIdx);
+
+ CFArrayRef allModes = GetAllDisplayModes(GetDisplayID(screenIdx));
+
+ if (!allModes)
+ return nullptr;
+
+ for (int i = 0; i < CFArrayGetCount(allModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = (flags & kDisplayModeStretchedFlag) != 0;
+ interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ rate = CGDisplayModeGetRefreshRate(displayMode);
+
+ if ((bitsperpixel == 32) && (safeForHardware == true) && (stretched == false) &&
+ (interlaced == false) && (w == width) && (h == height) &&
+ (rate == refreshrate || rate == 0))
+ {
+ CFRelease(allModes);
+ CLog::Log(LOGDEBUG, "GetMode found a match!");
+ return CGDisplayModeRetain(displayMode);
+ }
+ }
+
+ CFRelease(allModes);
+ CLog::Log(LOGERROR, "GetMode - no match found!");
+ return nullptr;
+}
+
+// mimic former behavior of deprecated CGDisplayBestModeForParameters
+CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display,
+ size_t bitsPerPixel,
+ size_t width,
+ size_t height)
+{
+ // Loop through all display modes to determine the closest match.
+ // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior
+ // Try to find a mode with the requested depth and equal or greater dimensions first.
+ // If no match is found, try to find a mode with greater depth and same or greater dimensions.
+ // If still no match is found, just use the current mode.
+ CFArrayRef allModes = GetAllDisplayModes(display);
+
+ if (!allModes)
+ return nullptr;
+
+ CGDisplayModeRef displayMode = nullptr;
+
+ for (int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if (!mode)
+ continue;
+
+ if (DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
+ continue;
+
+ if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ break;
+ }
+ }
+
+ // No depth match was found
+ if (!displayMode)
+ {
+ for (int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if (!mode)
+ continue;
+
+ if (DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
+ continue;
+
+ if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ break;
+ }
+ }
+ }
+
+ CFRelease(allModes);
+
+ return displayMode;
+}
+
+#pragma mark - Blank Displays
+
+void BlankOtherDisplays(int screen_index)
+{
+ int i;
+ int numDisplays = [[NSScreen screens] count];
+
+ // zero out blankingWindows for debugging
+ for (i = 0; i < MAX_DISPLAYS; i++)
+ {
+ blankingWindows[i] = 0;
+ }
+
+ // Blank.
+ for (i = 0; i < numDisplays; i++)
+ {
+ if (i != screen_index)
+ {
+ // Get the size.
+ NSScreen* pScreen = [NSScreen.screens objectAtIndex:i];
+ NSRect screenRect = pScreen.frame;
+
+ // Build a blanking window.
+ screenRect.origin = NSZeroPoint;
+ blankingWindows[i] = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+
+ [blankingWindows[i] setBackgroundColor:NSColor.blackColor];
+ [blankingWindows[i] setLevel:CGShieldingWindowLevel()];
+ [blankingWindows[i] makeKeyAndOrderFront:nil];
+ }
+ }
+}
+
+void UnblankDisplays(void)
+{
+ for (auto i = 0; i < static_cast<int>(NSScreen.screens.count); i++)
+ {
+ if (blankingWindows[i] != 0)
+ {
+ // Get rid of the blanking windows we created.
+ [blankingWindows[i] close];
+ blankingWindows[i] = 0;
+ }
+ }
+}
+
+#pragma mark - Fade Display
+//! @Todo Look to replace Fade with CABasicAnimation
+static NSWindow* curtainWindow;
+void fadeInDisplay(NSScreen* theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double)fadeSteps);
+
+ if (curtainWindow != nil)
+ {
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = 1.0 - (step * fadeInterval);
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate* nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [NSRunLoop.currentRunLoop runUntilDate:nextDate];
+ }
+ }
+ [curtainWindow close];
+ curtainWindow = nil;
+}
+
+void fadeOutDisplay(NSScreen* theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double)fadeSteps);
+
+ [NSCursor hide];
+
+ curtainWindow = [[NSWindow alloc] initWithContentRect:[theScreen frame]
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:YES
+ screen:theScreen];
+
+ [curtainWindow setAlphaValue:0.0];
+ [curtainWindow setBackgroundColor:NSColor.blackColor];
+ [curtainWindow setLevel:NSScreenSaverWindowLevel];
+
+ [curtainWindow makeKeyAndOrderFront:nil];
+ [curtainWindow setFrame:[curtainWindow frameRectForContentRect:[theScreen frame]]
+ display:YES
+ animate:NO];
+
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = step * fadeInterval;
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate* nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [NSRunLoop.currentRunLoop runUntilDate:nextDate];
+ }
+}
+
+//---------------------------------------------------------------------------------
+static void DisplayReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void* userData)
+{
+ CWinSystemOSX* winsys = (CWinSystemOSX*)userData;
+ if (!winsys)
+ return;
+
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags);
+
+ // we fire the callbacks on start of configuration
+ // or when the mode set was finished
+ // or when we are called with flags == 0 (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+
+ // first check if we need to call OnLostDevice
+ if (flags & kCGDisplayBeginConfigurationFlag)
+ {
+ // pre/post-reconfiguration changes
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ if (res == RES_INVALID)
+ return;
+
+ NSScreen* pScreen = nil;
+ unsigned int screenIdx = 0;
+
+ if (screenIdx < NSScreen.screens.count)
+ {
+ pScreen = [NSScreen.screens objectAtIndex:screenIdx];
+ }
+
+ // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
+ // valid
+ if (pScreen)
+ {
+ CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
+ if (xbmc_display == display)
+ {
+ // we only respond to changes on the display we are running on.
+ winsys->AnnounceOnLostDevice();
+ winsys->StartLostDeviceTimer();
+ }
+ }
+ }
+ else // the else case checks if we need to call OnResetDevice
+ {
+ // we fire if kCGDisplaySetModeFlag is set or if flags == 0
+ // (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+ // we also don't check the screen here as we might not even have
+ // one anymore (e.x. when tv is turned off)
+ if (flags & kCGDisplaySetModeFlag || flags == 0)
+ {
+ winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
+ winsys->HandleOnResetDevice();
+ }
+ }
+}
+
+#pragma mark - CWinSystemOSX
+//------------------------------------------------------------------------------
+CWinSystemOSX::CWinSystemOSX() : CWinSystemBase(), m_lostDeviceTimer(this)
+{
+ m_appWindow = nullptr;
+ m_glView = nullptr;
+ m_obscured = false;
+ m_lastDisplayNr = -1;
+ m_movedToOtherScreen = false;
+ m_refreshRate = 0.0;
+ m_delayDispReset = false;
+
+ m_winEvents.reset(new CWinEventsOSX());
+
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkDARWINOSX::Register();
+ CCocoaPowerSyscall::Register();
+ m_dpms = std::make_shared<CCocoaDPMSSupport>();
+}
+
+CWinSystemOSX::~CWinSystemOSX() = default;
+
+void CWinSystemOSX::Register(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemOSX::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemOSX::AnnounceOnLostDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::LogF(LOGDEBUG, "Lost Device Announce");
+ for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+}
+
+void CWinSystemOSX::HandleOnResetDevice()
+{
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ else
+ {
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSX::AnnounceOnResetDevice()
+{
+ double currentFps = m_refreshRate;
+ int w = 0;
+ int h = 0;
+ int currentScreenIdx = m_lastDisplayNr;
+ // ensure that graphics context knows about the current refreshrate before
+ // doing the callbacks
+ GetScreenResolution(&w, &h, &currentFps, currentScreenIdx);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(currentFps);
+
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::LogF(LOGDEBUG, "Reset Device Announce");
+ for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+}
+
+#pragma mark - Timers
+
+void CWinSystemOSX::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(3000ms, false);
+}
+
+void CWinSystemOSX::StopLostDeviceTimer()
+{
+ m_lostDeviceTimer.Stop();
+}
+
+void CWinSystemOSX::OnTimeout()
+{
+ HandleOnResetDevice();
+}
+
+#pragma mark - WindowSystem
+
+bool CWinSystemOSX::InitWindowSystem()
+{
+ if (!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowSystem()
+{
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ DestroyWindowInternal();
+
+ if (m_glView)
+ {
+ m_glView = nullptr;
+ }
+
+ UnblankDisplays();
+ return true;
+}
+
+bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ // force initial window creation to be windowed, if fullscreen, it will switch to it below
+ // fixes the white screen of death if starting fullscreen and switching to windowed.
+ RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ m_nWidth = resInfo.iWidth;
+ m_nHeight = resInfo.iHeight;
+ m_bFullScreen = false;
+ m_name = name;
+
+ __block NSWindow* appWindow;
+ // because we are not main thread, delay any updates
+ // and only become keyWindow after it finishes.
+ [NSAnimationContext beginGrouping];
+ [NSAnimationContext.currentContext setCompletionHandler:^{
+ [appWindow makeKeyWindow];
+ }];
+
+ const NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable |
+ NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+
+ if (m_appWindow == nullptr)
+ {
+ // create new content view
+ NSRect rect = [appWindow contentRectForFrameRect:appWindow.frame];
+
+ // create new view if we don't have one
+ if (!m_glView)
+ m_glView = [[OSXGLView alloc] initWithFrame:rect];
+
+ OSXGLView* view = (OSXGLView*)m_glView;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ appWindow = [[OSXGLWindow alloc] initWithContentRect:NSMakeRect(0, 0, m_nWidth, m_nHeight)
+ styleMask:windowStyleMask];
+ NSString* title = [NSString stringWithUTF8String:m_name.c_str()];
+ appWindow.backgroundColor = NSColor.blackColor;
+ appWindow.title = title;
+
+ NSWindowCollectionBehavior behavior = appWindow.collectionBehavior;
+ behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+ [appWindow setCollectionBehavior:behavior];
+
+ // associate with current window
+ [appWindow setContentView:view];
+ });
+
+ [view.getGLContext makeCurrentContext];
+ [view.getGLContext update];
+
+ m_appWindow = appWindow;
+ m_bWindowCreated = true;
+ }
+
+ // warning, we can order front but not become
+ // key window or risk starting up with bad flicker
+ // becoming key window must happen in completion block.
+ [(NSWindow*)m_appWindow performSelectorOnMainThread:@selector(orderFront:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ [NSAnimationContext endGrouping];
+
+ // get screen refreshrate - this is needed
+ // when we startup in windowed mode and don't run through SetFullScreen
+ int dummy;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGL::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+ CScreenshotSurfaceGL::Register();
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowInternal()
+{
+ // set this 1st, we should really mutex protext m_appWindow in this class
+ m_bWindowCreated = false;
+ if (m_appWindow)
+ {
+ NSWindow* oldAppWindow = m_appWindow;
+ m_appWindow = nullptr;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [oldAppWindow setContentView:nil];
+ });
+ }
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindow()
+{
+ return true;
+}
+
+bool CWinSystemOSX::Minimize()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication miniaturizeAll:nil];
+ });
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Restore()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication unhide:nil];
+ });
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Show(bool raise)
+{
+ @autoreleasepool
+ {
+ auto app = NSApplication.sharedApplication;
+ if (raise)
+ {
+ [app unhide:nil];
+ [app activateIgnoringOtherApps:YES];
+ [app arrangeInFront:nil];
+ }
+ else
+ {
+ [app unhideWithoutActivation];
+ }
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Hide()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication hide:nil];
+ });
+ }
+ return true;
+}
+
+NSRect CWinSystemOSX::GetWindowDimensions()
+{
+ if (m_appWindow)
+ {
+ NSWindow* win = (NSWindow*)m_appWindow;
+ NSRect frame = win.contentView.frame;
+ return frame;
+ }
+}
+
+#pragma mark - Resize Window
+
+bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ if (!m_appWindow)
+ return false;
+
+ [(OSXGLWindow*)m_appWindow setResizeState:true];
+
+ __block OSXGLView* view;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view = m_appWindow.contentView;
+ });
+
+ if (view)
+ {
+ // It seems, that in macOS 10.15 this defaults to YES, but we currently do not support
+ // Retina resolutions properly. Ensure that the view uses a 1 pixel per point framebuffer.
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view.wantsBestResolutionOpenGLSurface = NO;
+ });
+ }
+
+ if (newWidth < 0)
+ {
+ newWidth = [(NSWindow*)m_appWindow minSize].width;
+ }
+
+ if (newHeight < 0)
+ {
+ newHeight = [(NSWindow*)m_appWindow minSize].height;
+ }
+
+ if (view)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSOpenGLContext* context = [view getGLContext];
+ NSWindow* window = m_appWindow;
+
+ NSRect pos = window.frame;
+
+ NSRect myNewContentFrame = NSMakeRect(pos.origin.x, pos.origin.y, newWidth, newHeight);
+ NSRect myNewWindowRect = [window frameRectForContentRect:myNewContentFrame];
+ [window setFrame:myNewWindowRect display:TRUE];
+
+ [context update];
+ });
+ }
+
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+
+ [(OSXGLWindow*)m_appWindow setResizeState:false];
+
+ return true;
+}
+
+bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // if (m_lastDisplayNr == -1)
+ // m_lastDisplayNr = res.iScreen;
+
+ __block NSWindow* window = m_appWindow;
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ //handle resolution/refreshrate switching early here
+ if (m_bFullScreen)
+ {
+ // switch videomode
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ // hide the OS mouse
+ [NSCursor hide];
+ }
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [window setAllowsConcurrentViewDrawing:NO];
+ });
+
+ if (m_fullscreenWillToggle)
+ {
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+ m_fullscreenWillToggle = false;
+ return true;
+ }
+
+ if (m_bFullScreen)
+ {
+ // This is Cocoa Windowed FullScreen Mode
+ // Get the screen rect of our current display
+ NSScreen* pScreen = [NSScreen.screens objectAtIndex:m_lastDisplayNr];
+
+ // remove frame origin offset of original display
+ pScreen.frame.origin = NSZeroPoint;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [window.contentView setFrameSize:NSMakeSize(m_nWidth, m_nHeight)];
+ window.title = @"";
+ [window setAllowsConcurrentViewDrawing:YES];
+ });
+
+ // Blank other displays if requested.
+ if (blankOtherDisplays)
+ BlankOtherDisplays(m_lastDisplayNr);
+ }
+ else
+ {
+ // Show menubar.
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication setPresentationOptions:NSApplicationPresentationDefault];
+ });
+
+ // Unblank.
+ // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
+ //if (blankOtherDisplays)
+ UnblankDisplays();
+ }
+
+ //DisplayFadeFromBlack(fade_token, needtoshowme);
+
+ m_fullscreenWillToggle = true;
+ // toggle cocoa fullscreen mode
+ if ([m_appWindow respondsToSelector:@selector(toggleFullScreen:)])
+ [m_appWindow performSelectorOnMainThread:@selector(toggleFullScreen:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+
+ return true;
+}
+
+#pragma mark - Resolution
+
+void CWinSystemOSX::UpdateResolutions()
+{
+ CWinSystemBase::UpdateResolutions();
+
+ // Add desktop resolution
+ int w;
+ int h;
+ double fps;
+
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ GetScreenResolution(&w, &h, &fps, dispIdx);
+ NSString* dispName = screenNameForDisplay(GetDisplayID(dispIdx));
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP),
+ dispName.UTF8String, w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ // now just fill in the possible resolutions for the attached screens
+ // and push to the resolution info vector
+ FillInVideoModes();
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+void CWinSystemOSX::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx);
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+ *w = CGDisplayModeGetWidth(mode);
+ *h = CGDisplayModeGetHeight(mode);
+ *fps = CGDisplayModeGetRefreshRate(mode);
+ CGDisplayModeRelease(mode);
+ if (static_cast<int>(*fps) == 0)
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ *fps = 60.0;
+ }
+}
+
+#pragma mark - Video Modes
+
+bool CWinSystemOSX::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ CGDisplayModeRef dispMode = nullptr;
+
+ int screenIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Figure out the screen size. (default to main screen)
+ CGDirectDisplayID display_id = GetDisplayID(screenIdx);
+
+ // find mode that matches the desired size, refreshrate
+ // non interlaced, nonstretched, safe for hardware
+ dispMode = GetMode(width, height, refreshrate, screenIdx);
+
+ //not found - fallback to bestemdeforparameters
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 32, width, height);
+
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 16, width, height);
+
+ // still no match? fallback to current resolution of the display which HAS to work [tm]
+ if (!dispMode)
+ {
+ int currentWidth;
+ int currentHeight;
+ double currentRefresh;
+
+ GetScreenResolution(&currentWidth, &currentHeight, &currentRefresh, screenIdx);
+ dispMode = GetMode(currentWidth, currentHeight, currentRefresh, screenIdx);
+
+ // no way to get a resolution set
+ if (!dispMode)
+ return false;
+ }
+ }
+ }
+ // switch mode and return success
+ CGDisplayCapture(display_id);
+ CGDisplayConfigRef cfg;
+ CGBeginDisplayConfiguration(&cfg);
+ CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr);
+ CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly);
+ CGDisplayRelease(display_id);
+
+ m_refreshRate = CGDisplayModeGetRefreshRate(dispMode);
+
+ Cocoa_CVDisplayLinkUpdate();
+
+ return (err == kCGErrorSuccess);
+}
+
+void CWinSystemOSX::FillInVideoModes()
+{
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ for (int disp = 0; disp < static_cast<int>(NSScreen.screens.count); disp++)
+ {
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ int w, h, bitsperpixel;
+ double refreshrate;
+ RESOLUTION_INFO res;
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp));
+ NSString* dispName = screenNameForDisplay(GetDisplayID(disp));
+
+ CLog::LogF(LOGINFO, "Display {} has name {}", disp, [dispName UTF8String]);
+
+ if (!displayModes)
+ continue;
+
+ for (int i = 0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = (flags & kDisplayModeStretchedFlag) != 0;
+ interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
+
+ if ((bitsperpixel == 32) && (safeForHardware == true) && (stretched == false) &&
+ (interlaced == false))
+ {
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ refreshrate = CGDisplayModeGetRefreshRate(displayMode);
+ if (static_cast<int>(refreshrate) == 0) // LCD display?
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ refreshrate = 60.0;
+ }
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} @ {} Hz", disp, w,
+ h, refreshrate);
+
+ // only add the resolution if it belongs to "our" screen
+ // all others are only logged above...
+ if (disp == dispIdx)
+ {
+ UpdateDesktopResolution(res, (dispName != nil) ? [dispName UTF8String] : "Unknown", w, h,
+ refreshrate, 0);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+ }
+ }
+ CFRelease(displayModes);
+ }
+}
+
+#pragma mark - Occlusion
+
+bool CWinSystemOSX::IsObscured()
+{
+ if (m_obscured)
+ CLog::LogF(LOGDEBUG, "Obscured");
+ return m_obscured;
+}
+
+void CWinSystemOSX::SetOcclusionState(bool occluded)
+{
+ // m_obscured = occluded;
+ // CLog::LogF(LOGDEBUG, "{}", occluded ? "true":"false");
+}
+
+void CWinSystemOSX::NotifyAppFocusChange(bool bGaining)
+{
+ if (!(m_bFullScreen && bGaining))
+ return;
+ @autoreleasepool
+ {
+ // find the window
+ NSOpenGLContext* context = NSOpenGLContext.currentContext;
+ if (context)
+ {
+ NSView* view;
+
+ view = context.view;
+ if (view)
+ {
+ NSWindow* window;
+ window = view.window;
+ if (window)
+ {
+ [window orderFront:nil];
+ }
+ }
+ }
+ }
+}
+
+#pragma mark - Window Move
+
+void CWinSystemOSX::OnMove(int x, int y)
+{
+ static double oldRefreshRate = m_refreshRate;
+ Cocoa_CVDisplayLinkUpdate();
+
+ int dummy = 0;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ if (oldRefreshRate != m_refreshRate)
+ {
+ oldRefreshRate = m_refreshRate;
+
+ // send a message so that videoresolution (and refreshrate) is changed
+ NSWindow* win = m_appWindow;
+ NSRect frame = win.contentView.frame;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, frame.size.width,
+ frame.size.height);
+ }
+}
+
+void CWinSystemOSX::WindowChangedScreen()
+{
+ // user has moved the window to a
+ // different screen
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ m_lastDisplayNr = -1;
+
+ // if we are here the user dragged the window to a different
+ // screen and we return the screen of the window
+ if (context)
+ {
+ NSView* view;
+
+ view = context.view;
+ if (view)
+ {
+ NSWindow* window;
+ window = view.window;
+ if (window)
+ {
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen(window.screen));
+ }
+ }
+ }
+ if (m_lastDisplayNr == -1)
+ m_lastDisplayNr = 0; // default to main screen
+}
+
+CGLContextObj CWinSystemOSX::GetCGLContextObj()
+{
+ __block CGLContextObj cglcontex = nullptr;
+ if (m_appWindow)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ OSXGLView* contentView = m_appWindow.contentView;
+ cglcontex = contentView.getGLContext.CGLContextObj;
+ });
+ }
+
+ return cglcontex;
+}
+
+bool CWinSystemOSX::FlushBuffer()
+{
+ if (m_appWindow)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ OSXGLView* contentView = m_appWindow.contentView;
+ NSOpenGLContext* glcontex = contentView.getGLContext;
+ [glcontex flushBuffer];
+ });
+ }
+
+ return true;
+}
+
+#pragma mark - Vsync
+
+void CWinSystemOSX::EnableVSync(bool enable)
+{
+ // OpenGL Flush synchronised with vertical retrace
+ GLint swapInterval = enable ? 1 : 0;
+ [NSOpenGLContext.currentContext setValues:&swapInterval
+ forParameter:NSOpenGLContextParameterSwapInterval];
+}
+
+std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(void* clock)
+{
+ return std::make_unique<CVideoSyncOsx>(clock);
+}
+
+std::vector<std::string> CWinSystemOSX::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.push_back("Default");
+
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString* dispName = screenNameForDisplay(GetDisplayID(disp));
+ outputs.push_back(dispName.UTF8String);
+ }
+
+ return outputs;
+}
+
+#pragma mark - OSScreenSaver
+
+std::unique_ptr<IOSScreenSaver> CWinSystemOSX::GetOSScreenSaverImpl()
+{
+ return std::make_unique<COSScreenSaverOSX>();
+}
+
+#pragma mark - Input
+
+bool CWinSystemOSX::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+void CWinSystemOSX::enableInputEvents()
+{
+ m_winEvents->enableInputEvents();
+}
+
+void CWinSystemOSX::disableInputEvents()
+{
+ m_winEvents->disableInputEvents();
+}
+
+std::string CWinSystemOSX::GetClipboardText()
+{
+ std::string utf8_text;
+
+ const char* szStr = Cocoa_Paste();
+ if (szStr)
+ utf8_text = szStr;
+
+ return utf8_text;
+}
+
+void CWinSystemOSX::ShowOSMouse(bool show)
+{
+}
+
+#pragma mark - Unused
+
+CGDisplayFadeReservationToken DisplayFadeToBlack(bool fade)
+{
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
+ if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess && fade)
+ CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0,
+ TRUE);
+
+ return (fade_token);
+}
+
+void DisplayFadeFromBlack(CGDisplayFadeReservationToken fade_token, bool fade)
+{
+ if (fade_token != kCGDisplayFadeReservationInvalidToken)
+ {
+ if (fade)
+ CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0,
+ 0.0, FALSE);
+ CGReleaseDisplayFadeReservation(fade_token);
+ }
+}