aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfuzzard <fuzzard@kodi.tv>2021-09-13 12:31:19 +1000
committerfuzzard <fuzzard@kodi.tv>2022-04-12 16:01:13 +0200
commit6ba89e19445909a8da54f48f36fc6f2601cfe8ef (patch)
tree41c6142969b31fae5366e79d4ec7b73fe0bf9755
parent83694727a263c1365a31580cfd88b1d3d589aa68 (diff)
[OSX] native windowing implementation
-rw-r--r--cmake/platform/osx/osx.cmake5
-rw-r--r--docs/README.macOS.md10
-rw-r--r--tools/depends/Makefile.include.in1
-rw-r--r--tools/depends/configure.ac17
-rw-r--r--tools/depends/target/Makefile4
-rw-r--r--tools/depends/target/cmakebuildsys/Makefile10
-rw-r--r--xbmc/Application.cpp2
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp2
-rw-r--r--xbmc/platform/darwin/osx/CMakeLists.txt7
-rw-r--r--xbmc/platform/darwin/osx/CocoaInterface.mm6
-rw-r--r--xbmc/platform/darwin/osx/OSXGLView.h27
-rw-r--r--xbmc/platform/darwin/osx/OSXGLView.mm107
-rw-r--r--xbmc/platform/darwin/osx/OSXGLWindow.h38
-rw-r--r--xbmc/platform/darwin/osx/OSXGLWindow.mm216
-rw-r--r--xbmc/platform/darwin/osx/XBMCApplication.mm444
-rw-r--r--xbmc/windowing/osx/CMakeLists.txt6
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.h120
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.mm1369
-rw-r--r--xbmc/windowing/osx/WinSystemOSXGL.h2
19 files changed, 2388 insertions, 5 deletions
diff --git a/cmake/platform/osx/osx.cmake b/cmake/platform/osx/osx.cmake
index 5f911d121e..72977de318 100644
--- a/cmake/platform/osx/osx.cmake
+++ b/cmake/platform/osx/osx.cmake
@@ -11,6 +11,9 @@ if(NOT APP_WINDOW_SYSTEM OR APP_WINDOW_SYSTEM STREQUAL sdl)
list(APPEND PLATFORM_REQUIRED_DEPS Sdl)
list(APPEND CORE_MAIN_SOURCE ${CMAKE_SOURCE_DIR}/xbmc/platform/darwin/osx/SDL/SDLMain.mm
${CMAKE_SOURCE_DIR}/xbmc/platform/posix/main.cpp)
+elseif(APP_WINDOW_SYSTEM STREQUAL native)
+ # native windowing and input
+ list(APPEND CORE_MAIN_SOURCE ${CMAKE_SOURCE_DIR}/xbmc/platform/darwin/osx/XBMCApplication.mm)
else()
- message(SEND_ERROR "Currently only SDL windowing is supported. Please set APP_WINDOW_SYSTEM to \"sdl\"")
+ message(SEND_ERROR "Only SDL or native windowing options are supported.")
endif()
diff --git a/docs/README.macOS.md b/docs/README.macOS.md
index 8d383cb9ca..7aac497e9e 100644
--- a/docs/README.macOS.md
+++ b/docs/README.macOS.md
@@ -111,6 +111,11 @@ make -j$(getconf _NPROCESSORS_ONLN)
./configure --host=x86_64-apple-darwin --with-platform=macos --with-sdk=10.13
```
+Developers can also select native windowing/input handling with the following
+```
+./configure --host=x86_64-apple-darwin --with-platform=macos --with-windowsystem=native
+```
+
**[back to top](#table-of-contents)** | **[back to section top](#4-configure-and-build-tools-and-dependencies)**
## 5. Build binary add-ons
@@ -156,6 +161,11 @@ Generate Xcode project as per configure command in **[Configure and build tools
make -C tools/depends/target/cmakebuildsys BUILD_DIR=$HOME/kodi-build GEN=Xcode
```
+To explicitly select the windowing/input system to use do the following (default is to use SDL if not provided)
+```
+make -C tools/depends/target/cmakebuildsys BUILD_DIR=$HOME/kodi-build GEN=Xcode APP_WINDOW_SYSTEM=native
+```
+
**TIP:** BUILD_DIR can be omitted, and project will be created in $HOME/kodi/build
Change all relevant paths onwards if omitted.
diff --git a/tools/depends/Makefile.include.in b/tools/depends/Makefile.include.in
index 81184b61fb..db73f05afb 100644
--- a/tools/depends/Makefile.include.in
+++ b/tools/depends/Makefile.include.in
@@ -26,6 +26,7 @@ ARCH_DEFINES=@ARCH_DEFINES@
NATIVE_ARCH_DEFINES=@NATIVE_ARCH_DEFINES@
TARGET_PLATFORM=@target_platform@
RENDER_SYSTEM=@app_rendersystem@
+WINDOW_SYSTEM=@app_winsystem@
AAPT=@AAPT@
DX=@DX@
D8=@D8@
diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac
index 934a1c16e4..d4e9172922 100644
--- a/tools/depends/configure.ac
+++ b/tools/depends/configure.ac
@@ -95,6 +95,11 @@ AC_ARG_WITH([rendersystem],
[render system to use])],
[app_rendersystem=$withval])
+AC_ARG_WITH([windowsystem],
+ [AS_HELP_STRING([--with-windowsystem],
+ [Windowing system to use])],
+ [app_winsystem=$withval])
+
AC_ARG_WITH([target-cflags],
[AS_HELP_STRING([--with-target-cflags],
[C compiler flags (target)])],
@@ -423,6 +428,16 @@ case $host in
fi
target_minver="10.13"
+
+ # check provided window system is valid_sdk
+ # if no window system supplied, default to SDL for now.
+ if test -n "$app_winsystem"; then
+ if test "$app_winsystem" != "native" && test "$app_winsystem" != "sdl"; then
+ AC_MSG_ERROR(Window system must be native or sdl)
+ fi
+ else
+ app_winsystem=sdl
+ fi
;;
aarch64-apple-darwin*)
MC_CHECK_NOT_CPU([$use_cpu], "*86")
@@ -433,6 +448,7 @@ case $host in
;;
osx)
target_minver="11.0"
+ app_winsystem=native
;;
*)
AC_MSG_ERROR(invalid platform for architecture ($host))
@@ -702,6 +718,7 @@ AC_SUBST(use_gplv3)
AC_SUBST(use_ccache)
AC_SUBST(host_includes)
AC_SUBST(app_rendersystem)
+AC_SUBST(app_winsystem)
AC_SUBST(ffmpeg_options)
AC_OUTPUT
diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile
index 1695a975e1..95368afca9 100644
--- a/tools/depends/target/Makefile
+++ b/tools/depends/target/Makefile
@@ -77,7 +77,9 @@ endif
ifeq ($(OS),osx)
EXCLUDED_DEPENDS = libusb
ifneq ($(CPU),arm64)
- DEPENDS += libsdl
+ ifeq ($(WINDOW_SYSTEM),sdl)
+ DEPENDS += libsdl
+ endif
endif
endif
diff --git a/tools/depends/target/cmakebuildsys/Makefile b/tools/depends/target/cmakebuildsys/Makefile
index 7da32df26e..953dba759e 100644
--- a/tools/depends/target/cmakebuildsys/Makefile
+++ b/tools/depends/target/cmakebuildsys/Makefile
@@ -21,13 +21,21 @@ else
CMAKE_BUILD_ARGUMENTS = -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(Configuration)
endif
+ifeq ($(OS),osx)
+ ifeq ($(APP_WINDOW_SYSTEM),native)
+ WINDOWSYSTEM=-DAPP_WINDOW_SYSTEM=native
+ else
+ WINDOWSYSTEM=-DAPP_WINDOW_SYSTEM=sdl
+ endif
+endif
+
ifeq ($(BUILD_DIR),)
BUILD_DIR=$(CMAKE_SOURCE_DIR)/build
endif
all:
mkdir -p $(BUILD_DIR)
- cd $(BUILD_DIR); $(CMAKE) $(CMAKE_BUILD_ARGUMENTS) -DENABLE_INTERNAL_CROSSGUID=ON -DENABLE_INTERNAL_FFMPEG=OFF $(CMAKE_EXTRA_ARGUMENTS) $(CMAKE_SOURCE_DIR)
+ cd $(BUILD_DIR); $(CMAKE) $(CMAKE_BUILD_ARGUMENTS) $(WINDOWSYSTEM) -DENABLE_INTERNAL_CROSSGUID=ON -DENABLE_INTERNAL_FFMPEG=OFF $(CMAKE_EXTRA_ARGUMENTS) $(CMAKE_SOURCE_DIR)
kodi:
$(MAKE) -C $(BUILD_DIR)
diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp
index 21e71e309b..8309414741 100644
--- a/xbmc/Application.cpp
+++ b/xbmc/Application.cpp
@@ -4243,6 +4243,7 @@ void CApplication::ProcessSlow()
CServiceBroker::GetPowerManager().ProcessEvents();
#if defined(TARGET_DARWIN_OSX)
+#if defined(SDL_FOUND)
// There is an issue on OS X that several system services ask the cursor to become visible
// during their startup routines. Given that we can't control this, we hack it in by
// forcing the
@@ -4251,6 +4252,7 @@ void CApplication::ProcessSlow()
Cocoa_HideMouse();
}
#endif
+#endif
// Temporarily pause pausable jobs when viewing video/picture
int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp
index 5802e61838..0069425afb 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp
@@ -16,6 +16,8 @@
#include "windowing/WinSystem.h"
#if defined(HAS_SDL)
#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "windowing/osx/WinSystemOSX.h"
#endif
#include "platform/darwin/osx/CocoaInterface.h"
diff --git a/xbmc/platform/darwin/osx/CMakeLists.txt b/xbmc/platform/darwin/osx/CMakeLists.txt
index 125135b610..b26f9d1559 100644
--- a/xbmc/platform/darwin/osx/CMakeLists.txt
+++ b/xbmc/platform/darwin/osx/CMakeLists.txt
@@ -12,4 +12,11 @@ set(HEADERS CocoaInterface.h
smc.h
XBMCHelper.h)
+if(NOT SDL_FOUND)
+ list(APPEND SOURCES OSXGLView.mm
+ OSXGLWindow.mm)
+ list(APPEND HEADERS OSXGLView.h
+ OSXGLWindow.h)
+endif()
+
core_add_library(platform_osx)
diff --git a/xbmc/platform/darwin/osx/CocoaInterface.mm b/xbmc/platform/darwin/osx/CocoaInterface.mm
index 870f927ace..0712121932 100644
--- a/xbmc/platform/darwin/osx/CocoaInterface.mm
+++ b/xbmc/platform/darwin/osx/CocoaInterface.mm
@@ -13,6 +13,8 @@
#include "utils/log.h"
#if defined(HAS_SDL)
#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "windowing/osx/WinSystemOSX.h"
#endif
#import <AudioToolbox/AudioToolbox.h>
@@ -33,8 +35,12 @@ CGDirectDisplayID Cocoa_GetDisplayIDFromScreen(NSScreen *screen);
NSOpenGLContext* Cocoa_GL_GetCurrentContext(void)
{
+#if defined(HAS_SDL)
CWinSystemOSX *winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
return winSystem->GetNSOpenGLContext();
+#else
+ return [NSOpenGLContext currentContext];
+#endif
}
uint32_t Cocoa_GL_GetCurrentDisplayID(void)
diff --git a/xbmc/platform/darwin/osx/OSXGLView.h b/xbmc/platform/darwin/osx/OSXGLView.h
new file mode 100644
index 0000000000..7849cea24d
--- /dev/null
+++ b/xbmc/platform/darwin/osx/OSXGLView.h
@@ -0,0 +1,27 @@
+#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
+{
+ NSOpenGLContext* m_glcontext;
+ NSOpenGLPixelFormat* m_pixFmt;
+ NSTrackingArea* m_trackingArea;
+ BOOL pause;
+}
+
+@property(readonly, getter=getCurrentNSContext) NSOpenGLContext* context;
+
+- (id)initWithFrame:(NSRect)frameRect;
+- (void)dealloc;
+- (NSOpenGLContext*)getGLContext;
+
+@end
diff --git a/xbmc/platform/darwin/osx/OSXGLView.mm b/xbmc/platform/darwin/osx/OSXGLView.mm
new file mode 100644
index 0000000000..c7fa7b67c8
--- /dev/null
+++ b/xbmc/platform/darwin/osx/OSXGLView.mm
@@ -0,0 +1,107 @@
+/*
+ * 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 "AppInboundProtocol.h"
+#include "AppParamParser.h"
+#include "Application.h"
+#include "ServiceBroker.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include "system_gl.h"
+
+@implementation OSXGLView
+
+- (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 BOOL firstRender = YES;
+ if (firstRender)
+ {
+ [m_glcontext setView:self];
+ firstRender = NO;
+
+ // 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/platform/darwin/osx/OSXGLWindow.h b/xbmc/platform/darwin/osx/OSXGLWindow.h
new file mode 100644
index 0000000000..d993f018da
--- /dev/null
+++ b/xbmc/platform/darwin/osx/OSXGLWindow.h
@@ -0,0 +1,38 @@
+#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;
+- (void)dealloc;
+
+- (BOOL)windowShouldClose:(id)sender;
+- (void)windowWillEnterFullScreen:(NSNotification*)pNotification;
+- (void)windowDidExitFullScreen:(NSNotification*)pNotification;
+
+- (void)windowDidChangeScreen:(NSNotification*)notification;
+- (void)windowDidExpose:(NSNotification*)aNotification;
+- (void)windowDidMove:(NSNotification*)aNotification;
+- (void)windowDidMiniaturize:(NSNotification*)aNotification;
+- (void)windowDidDeminiaturize:(NSNotification*)aNotification;
+
+- (void)windowDidBecomeKey:(NSNotification*)aNotification;
+- (void)windowDidResignKey:(NSNotification*)aNotification;
+
+- (void)windowDidResize:(NSNotification*)aNotification;
+- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize;
+- (void)windowWillStartLiveResize:(NSNotification*)notification;
+- (void)windowDidEndLiveResize:(NSNotification*)notification;
+
+@end
diff --git a/xbmc/platform/darwin/osx/OSXGLWindow.mm b/xbmc/platform/darwin/osx/OSXGLWindow.mm
new file mode 100644
index 0000000000..a550301259
--- /dev/null
+++ b/xbmc/platform/darwin/osx/OSXGLWindow.mm
@@ -0,0 +1,216 @@
+/*
+ * 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 "AppInboundProtocol.h"
+#include "AppParamParser.h"
+#include "Application.h"
+#include "ServiceBroker.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"
+#include "windowing/osx/WinEventsOSX.h"
+#import "windowing/osx/WinSystemOSX.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+#import "platform/darwin/osx/OSXGLView.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)
+ KODI::MESSAGING::CApplicationMessenger::GetInstance().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;
+}
+
+- (void)windowDidResize:(NSNotification*)aNotification
+{
+ if (!m_resizeState)
+ {
+ NSRect rect = [self contentRectForFrameRect:self.frame];
+
+ if (!CServiceBroker::GetWinSystem()->IsFullScreen())
+ {
+ RESOLUTION res_index = RES_DESKTOP;
+ if ((static_cast<int>(rect.size.width) ==
+ CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iWidth) &&
+ (static_cast<int>(rect.size.height) ==
+ CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iHeight))
+ return;
+ }
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = static_cast<int>(rect.size.width);
+ newEvent.resize.h = static_cast<int>(rect.size.height);
+
+ // check for valid sizes cause in some cases
+ // we are hit during fullscreen transition from osx
+ // 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);
+ }
+ //CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ }
+}
+
+- (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 osx is the issuer of the toggle
+ // call XBMCs toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from XBMCs gui thread
+ winSystem->SetFullscreenWillToggle(true);
+
+ KODI::MESSAGING::CApplicationMessenger::GetInstance().PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of xbmc 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 osx is the issuer of the toggle
+ // call XBMCs toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from XBMCs gui thread
+ winSystem->SetFullscreenWillToggle(true);
+ KODI::MESSAGING::CApplicationMessenger::GetInstance().PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of xbmc did a toggle - just reset the flag
+ // we don't need to do anything else
+ winSystem->SetFullscreenWillToggle(false);
+ }
+}
+
+- (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/platform/darwin/osx/XBMCApplication.mm b/xbmc/platform/darwin/osx/XBMCApplication.mm
new file mode 100644
index 0000000000..a1c27a24f7
--- /dev/null
+++ b/xbmc/platform/darwin/osx/XBMCApplication.mm
@@ -0,0 +1,444 @@
+/*
+ * SDLMain.mm - main entry point for our Cocoa-ized SDL app
+ * Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
+ * Non-NIB-Code & other changes: Max Horn <max@quendi.de>
+ *
+ * SPDX-License-Identifier: Unlicense
+ * See LICENSES/README.md for more information.
+ */
+
+#import "XBMCApplication.h"
+
+#include "AppInboundProtocol.h"
+#include "AppParamParser.h"
+#include "ServiceBroker.h"
+#include "messaging/ApplicationMessenger.h"
+#include "platform/xbmc.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+#import "windowing/osx/WinSystemOSX.h"
+
+#import "platform/darwin/osx/storage/OSXStorageProvider.h"
+
+#import <sys/param.h> /* for MAXPATHLEN */
+#import <unistd.h>
+
+// For some reason, Apple removed setAppleMenu from the headers in 10.4,
+// but the method still is there and works. To avoid warnings, we declare
+// it ourselves here.
+@interface NSApplication (Missing_Methods)
+- (void)setAppleMenu:(NSMenu*)menu;
+@end
+
+// Portions of CPS.h
+typedef struct CPSProcessSerNum
+{
+ UInt32 lo;
+ UInt32 hi;
+} CPSProcessSerNum;
+
+extern "C"
+{
+ extern OSErr CPSGetCurrentProcess(CPSProcessSerNum* psn);
+ extern OSErr CPSEnableForegroundOperation(
+ CPSProcessSerNum* psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
+ extern OSErr CPSSetFrontProcess(CPSProcessSerNum* psn);
+}
+
+static int gArgc;
+static char** gArgv;
+static BOOL gCalledAppMainline = FALSE;
+
+static NSString* getApplicationName(void)
+{
+ NSString* appName = 0;
+
+ // Determine the application name
+ NSDictionary* dict = (NSDictionary*)CFBundleGetInfoDictionary(CFBundleGetMainBundle());
+ if (dict)
+ appName = [dict objectForKey:@"CFBundleName"];
+
+ if (![appName length])
+ appName = NSProcessInfo.processInfo.processName;
+
+ return appName;
+}
+static void setupApplicationMenu(void)
+{
+ // warning: this code is very odd
+ NSString* appName = getApplicationName();
+ NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""];
+
+ // Add menu items
+ NSString* title = [@"About " stringByAppendingString:appName];
+ [appleMenu addItemWithTitle:title
+ action:@selector(orderFrontStandardAboutPanel:)
+ keyEquivalent:@""];
+
+ [appleMenu addItem:[NSMenuItem separatorItem]];
+
+ title = [@"Quit " stringByAppendingString:appName];
+ [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+ // Put menu into the menubar
+ NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+ [menuItem setSubmenu:appleMenu];
+ [NSApplication.sharedApplication.mainMenu addItem:menuItem];
+
+ // Tell the application object that this is now the application menu
+ [NSApplication.sharedApplication setAppleMenu:appleMenu];
+}
+
+// Create a window menu
+static void setupWindowMenu(void)
+{
+ NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
+
+ // "Full/Windowed Toggle" item
+ NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"Full/Windowed Toggle"
+ action:@selector(fullScreenToggle:)
+ keyEquivalent:@"f"];
+ // this is just for display purposes, key handling is in CWinEventsOSX::ProcessOSXShortcuts()
+ menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
+ [windowMenu addItem:menuItem];
+
+ // "Full/Windowed Toggle" item
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Float on Top"
+ action:@selector(floatOnTopToggle:)
+ keyEquivalent:@"t"];
+ menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
+ [windowMenu addItem:menuItem];
+
+ // "Minimize" item
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize"
+ action:@selector(performMiniaturize:)
+ keyEquivalent:@"m"];
+ menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
+ [windowMenu addItem:menuItem];
+
+ // Put menu into the menubar
+ NSMenuItem* windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window"
+ action:nil
+ keyEquivalent:@""];
+ [windowMenuItem setSubmenu:windowMenu];
+ [NSApplication.sharedApplication.mainMenu addItem:windowMenuItem];
+
+ // Tell the application object that this is now the window menu
+ [NSApplication.sharedApplication setWindowsMenu:windowMenu];
+}
+
+// The main class of the application, the application's delegate
+@implementation XBMCDelegate
+
+// Set the working directory to the .app's parent directory
+- (void)setupWorkingDirectory
+{
+ char parentdir[MAXPATHLEN];
+ CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
+ CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url);
+ if (CFURLGetFileSystemRepresentation(url2, true, (UInt8*)parentdir, MAXPATHLEN))
+ {
+ assert(chdir(parentdir) == 0); /* chdir to the binary app's parent */
+ }
+ CFRelease(url);
+ CFRelease(url2);
+}
+
+- (void)stopRunLoop
+{
+ // to get applicationShouldTerminate and
+ // applicationWillTerminate notifications.
+ [NSApplication.sharedApplication terminate:nil];
+ // to flag a stop on next event.
+ [NSApplication.sharedApplication stop:nil];
+
+ //post a NOP event, so the run loop actually stops
+ //see http://www.cocoabuilder.com/archive/cocoa/219842-nsapp-stop.html
+ NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0.0
+ windowNumber:0
+ context:nil
+ subtype:0
+ data1:0
+ data2:0];
+
+ [NSApplication.sharedApplication postEvent:event atStart:true];
+}
+
+// To use Cocoa on secondary POSIX threads, your application must first detach
+// at least one NSThread object, which can immediately exit. Some info says this
+// is not required anymore, who knows ?
+- (void)kickstartMultiThreaded:(id)arg
+{
+ @autoreleasepool
+ {
+ // empty
+ }
+}
+
+- (void)mainLoopThread:(id)arg
+{
+
+#if defined(DEBUG)
+ struct rlimit rlim;
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_CORE, &rlim) == -1)
+ CLog::Log(LOGDEBUG, "Failed to set core size limit (%s)", strerror(errno));
+#endif
+
+ setlocale(LC_NUMERIC, "C");
+
+ CAppParamParser appParamParser;
+ appParamParser.Parse((const char**)gArgv, (int)gArgc);
+
+ XBMC_Run(true, appParamParser);
+
+#ifdef _DEBUG
+ CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_DEBUG);
+#else
+ // CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_NORMAL);
+ CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_DEBUG);
+#endif
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(false);
+
+ [self performSelectorOnMainThread:@selector(stopRunLoop) withObject:nil waitUntilDone:false];
+}
+
+- (void)applicationDidChangeOcclusionState:(NSNotification*)notification
+{
+ bool occluded = true;
+ if (NSApp.occlusionState & NSApplicationOcclusionStateVisible)
+ occluded = false;
+
+ CWinSystemOSX* winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ winSystem->SetOcclusionState(occluded);
+}
+
+// Called after the internal event loop has started running.
+- (void)applicationDidFinishLaunching:(NSNotification*)note
+{
+ // enable multithreading, we should NOT have to do this but as we are mixing NSThreads/pthreads...
+ if (!NSThread.isMultiThreaded)
+ [NSThread detachNewThreadSelector:@selector(kickstartMultiThreaded:)
+ toTarget:self
+ withObject:nil];
+
+ // Set the working directory to the .app's parent directory
+ [self setupWorkingDirectory];
+
+ [NSWorkspace.sharedWorkspace.notificationCenter addObserver:self
+ selector:@selector(deviceDidMountNotification:)
+ name:NSWorkspaceDidMountNotification
+ object:nil];
+
+ [NSWorkspace.sharedWorkspace.notificationCenter
+ addObserver:self
+ selector:@selector(deviceDidUnMountNotification:)
+ name:NSWorkspaceDidUnmountNotification
+ object:nil];
+
+ // Hand off to main application code
+ gCalledAppMainline = TRUE;
+
+ //window.acceptsMouseMovedEvents = TRUE;
+
+ // kick our mainloop into an extra thread
+ [NSThread detachNewThreadSelector:@selector(mainLoopThread:) toTarget:self withObject:nil];
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
+{
+ return NSTerminateNow;
+}
+
+- (void)applicationWillTerminate:(NSNotification*)note
+{
+ [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self
+ name:NSWorkspaceDidMountNotification
+ object:nil];
+
+ [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self
+ name:NSWorkspaceDidUnmountNotification
+ object:nil];
+}
+
+- (void)applicationWillResignActive:(NSNotification*)note
+{
+ // when app moves to background
+}
+
+- (void)applicationWillBecomeActive:(NSNotification*)note
+{
+ // when app moves to front
+}
+
+/*
+ * Catch document open requests...this lets us notice files when the app
+ * was launched by double-clicking a document, or when a document was
+ * dragged/dropped on the app's icon. You need to have a
+ * CFBundleDocumentsType section in your Info.plist to get this message,
+ * apparently.
+ *
+ * Files are added to gArgv, so to the app, they'll look like command line
+ * arguments. Previously, apps launched from the finder had nothing but
+ * an argv[0].
+ *
+ * This message may be received multiple times to open several docs on launch.
+ *
+ * This message is ignored once the app's mainline has been called.
+ */
+- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
+{
+ const char* temparg;
+ size_t arglen;
+ char* arg;
+ char** newargv;
+
+ // app has started, ignore this document.
+ if (gCalledAppMainline)
+ return FALSE;
+
+ temparg = [filename UTF8String];
+ arglen = strlen(temparg) + 1;
+ arg = (char*)malloc(arglen);
+ if (arg == NULL)
+ return FALSE;
+
+ newargv = (char**)realloc(gArgv, sizeof(char*) * (gArgc + 2));
+ if (newargv == NULL)
+ {
+ free(arg);
+ return FALSE;
+ }
+ gArgv = newargv;
+
+ strlcpy(arg, temparg, arglen);
+ gArgv[gArgc++] = arg;
+ gArgv[gArgc] = NULL;
+
+ return TRUE;
+}
+
+- (void)deviceDidMountNotification:(NSNotification*)note
+{
+ // calling into c++ code, need to use autorelease pools
+ @autoreleasepool
+ {
+ NSString* volumeLabel = [note.userInfo objectForKey:@"NSWorkspaceVolumeLocalizedNameKey"];
+ const char* label = volumeLabel.UTF8String;
+
+ NSString* volumePath = [note.userInfo objectForKey:@"NSDevicePath"];
+ const char* path = volumePath.UTF8String;
+
+ COSXStorageProvider::VolumeMountNotification(label, path);
+ }
+}
+
+- (void)deviceDidUnMountNotification:(NSNotification*)note
+{
+ // calling into c++ code, need to use autorelease pools
+ @autoreleasepool
+ {
+ NSString* volumeLabel = [note.userInfo objectForKey:@"NSWorkspaceVolumeLocalizedNameKey"];
+ const char* label = [volumeLabel UTF8String];
+
+ NSString* volumePath = [note.userInfo objectForKey:@"NSDevicePath"];
+ const char* path = [volumePath UTF8String];
+
+ COSXStorageProvider::VolumeUnmountNotification(label, path);
+ }
+}
+
+// Invoked from the Quit menu item
+- (void)terminate:(id)sender
+{
+ // remove any notification handlers
+ [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self];
+ [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)fullScreenToggle:(id)sender
+{
+}
+
+- (void)floatOnTopToggle:(id)sender
+{
+ // ToDo!: non functional, test further
+ NSWindow* window = NSOpenGLContext.currentContext.view.window;
+ if (window.level == NSFloatingWindowLevel)
+ {
+ [window setLevel:NSNormalWindowLevel];
+ [sender setState:NSOffState];
+ }
+ else
+ {
+ [window setLevel:NSFloatingWindowLevel];
+ [sender setState:NSOnState];
+ }
+}
+
+@end
+
+int main(int argc, char* argv[])
+{
+ @autoreleasepool
+ {
+ XBMCDelegate* xbmc_delegate;
+
+ // Block SIGPIPE
+ // SIGPIPE repeatably kills us, turn it off
+ {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGPIPE);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ }
+
+ /* Copy the arguments into a global variable */
+ /* This is passed if we are launched by double-clicking */
+ if (argc >= 2 && strncmp(argv[1], "-psn", 4) == 0)
+ {
+ gArgv = (char**)malloc(sizeof(char*) * 2);
+ gArgv[0] = argv[0];
+ gArgv[1] = NULL;
+ gArgc = 1;
+ }
+ else
+ {
+ gArgc = argc;
+ gArgv = (char**)malloc(sizeof(char*) * (argc + 1));
+ for (int i = 0; i <= argc; i++)
+ gArgv[i] = argv[i];
+ }
+
+ // Ensure the application object is initialised
+ [NSApplication sharedApplication];
+
+ CPSProcessSerNum PSN;
+ /* Tell the dock about us */
+ if (!CPSGetCurrentProcess(&PSN))
+ if (!CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103))
+ if (!CPSSetFrontProcess(&PSN))
+ [NSApplication sharedApplication];
+
+ // Set up the menubars
+ [NSApplication.sharedApplication setMainMenu:[[NSMenu alloc] init]];
+ setupApplicationMenu();
+ setupWindowMenu();
+
+ // Create XBMCDelegate and make it the app delegate
+ xbmc_delegate = [[XBMCDelegate alloc] init];
+ [NSApplication.sharedApplication setDelegate:xbmc_delegate];
+
+ // Start the main event loop
+ [NSApplication.sharedApplication run];
+
+ return 1;
+ }
+}
diff --git a/xbmc/windowing/osx/CMakeLists.txt b/xbmc/windowing/osx/CMakeLists.txt
index ab223d96de..20af0813fd 100644
--- a/xbmc/windowing/osx/CMakeLists.txt
+++ b/xbmc/windowing/osx/CMakeLists.txt
@@ -7,9 +7,11 @@ set(HEADERS CocoaDPMSSupport.h
if(NOT SDL_FOUND)
list(APPEND SOURCES WinEventsOSX.mm
- WinEventsOSXImpl.mm)
+ WinEventsOSXImpl.mm
+ WinSystemOSX.mm)
list(APPEND HEADERS WinEventsOSX.h
- WinEventsOSXImpl.h)
+ WinEventsOSXImpl.h
+ WinSystemOSX.h)
endif()
if(OPENGL_FOUND)
diff --git a/xbmc/windowing/osx/WinSystemOSX.h b/xbmc/windowing/osx/WinSystemOSX.h
new file mode 100644
index 0000000000..d527b7b60f
--- /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
+class NSWindow;
+class 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(void);
+ bool IsObscured(void);
+
+ 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 0000000000..5ab150aafc
--- /dev/null
+++ b/xbmc/windowing/osx/WinSystemOSX.mm
@@ -0,0 +1,1369 @@
+/*
+ * 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 "AppInboundProtocol.h"
+#include "ServiceBroker.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/SingleLock.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/osx/CocoaDPMSSupport.h"
+#include "windowing/osx/OSScreenSaverOSX.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"
+#import "platform/darwin/osx/OSXGLView.h"
+#import "platform/darwin/osx/OSXGLWindow.h"
+#include "platform/darwin/osx/powermanagement/CocoaPowerSyscall.h"
+
+#include <chrono>
+#include <cstdlib>
+#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;
+
+#define MAX_DISPLAYS 32
+static NSWindow* blankingWindows[MAX_DISPLAYS];
+
+//---------------------------------------------------------------------------------
+void SetMenuBarVisible(bool visible)
+{
+ if (visible)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSApplicationPresentationOptions options = NSApplicationPresentationDefault;
+ [NSApplication.sharedApplication setPresentationOptions:options];
+ });
+ }
+ else
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSApplicationPresentationOptions options =
+ NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock;
+ [NSApplication.sharedApplication setPresentationOptions:options];
+ });
+ }
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+// No replacement for CGDisplayModeCopyPixelEncoding
+// Disable warning for now
+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;
+}
+#pragma GCC diagnostic pop
+
+#pragma mark - GetScreenName
+
+#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
+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;
+}
+#pragma GCC diagnostic pop
+
+#pragma mark - GetDisplay
+
+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 < 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 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;
+}
+
+// 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;
+
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ bool televisionoutput;
+ int w, h, bitsperpixel;
+ double rate;
+ RESOLUTION_INFO res;
+
+ CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with %d x %d @ %f Hz on display %d", 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 == true) && (stretched == false) &&
+ (interlaced == false) && (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;
+}
+
+// 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;
+}
+
+#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)
+{
+ 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;
+ }
+ }
+}
+
+#pragma mark - Fade Display
+
+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 %d", 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)
+{
+ CSingleLock lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemOSX::Unregister(IDispResource* resource)
+{
+ CSingleLock 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()
+{
+ CSingleLock 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()
+{
+
+ int delay = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange");
+ if (delay > 0)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay * 100);
+ }
+ 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);
+
+ CSingleLock 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();
+}
+
+#pragma mark - Timers
+
+void CWinSystemOSX::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(std::chrono::milliseconds(3000), 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 = NULL;
+ }
+
+ 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];
+ }];
+
+ // for native fullscreen we always want to set the
+ // same windowed flags
+ __block NSUInteger windowStyleMask;
+ if (fullScreen)
+ windowStyleMask = NSWindowStyleMaskBorderless;
+ else
+ 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;
+ [appWindow setOneShot:NO];
+
+ 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];
+
+ if (fullScreen)
+ {
+ m_fullscreenWillToggle = true;
+ [appWindow performSelectorOnMainThread:@selector(toggleFullScreen:)
+ withObject:nil
+ waitUntilDone:YES];
+ }
+
+ // 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 = NULL;
+ 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)
+{
+ CSingleLock lock(m_critSection);
+
+ static NSPoint last_window_origin;
+
+ static NSSize last_view_size;
+ static NSPoint last_view_origin;
+
+ // if (m_lastDisplayNr == -1)
+ // m_lastDisplayNr = res.iScreen;
+
+ __block NSWindow* window = m_appWindow;
+ __block OSXGLView* view;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view = window.contentView;
+ });
+
+ 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)
+ {
+ // Save info about the windowed context so we can restore it when returning to windowed.
+ __block NSPoint block_last_window_origin;
+ __block NSSize block_last_view_size;
+ __block NSPoint block_last_view_origin;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ block_last_view_size = view.frame.size;
+ block_last_view_origin = view.frame.origin;
+ block_last_window_origin = window.frame.origin;
+ });
+
+ last_view_size = block_last_view_size;
+ last_view_origin = block_last_view_origin;
+ last_window_origin = block_last_window_origin;
+
+ // 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;
+
+ window = m_appWindow;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view = [window contentView];
+ [view setFrameSize:NSMakeSize(m_nWidth, m_nHeight)];
+
+ NSString* title = [NSString stringWithFormat:@"%s", ""];
+ window.title = title;
+ });
+
+ // Hide the menu bar.
+ SetMenuBarVisible(false);
+
+ // Blank other displays if requested.
+ if (blankOtherDisplays)
+ BlankOtherDisplays(m_lastDisplayNr);
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [window setAllowsConcurrentViewDrawing:YES];
+ });
+ }
+ else
+ {
+ // Show menubar.
+ SetMenuBarVisible(true);
+
+ // 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, 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 ((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)
+{
+ 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++)
+ {
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ bool televisionoutput;
+ int w, h, bitsperpixel;
+ double refreshrate;
+ RESOLUTION_INFO res;
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp));
+ NSString* dispName = screenNameForDisplay(GetDisplayID(disp));
+
+ CLog::Log(LOGINFO, "Display %i has name %s", disp, [dispName UTF8String]);
+
+ if (nullptr == 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 == 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 %d with %d x %d @ %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);
+ }
+}
+
+#pragma mark - Occlusion
+
+bool CWinSystemOSX::IsObscured(void)
+{
+ if (m_obscured)
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::IsObscured(void) - TRUE");
+ return m_obscured;
+}
+
+void CWinSystemOSX::SetOcclusionState(bool occluded)
+{
+ // m_obscured = occluded;
+ // CLog::Log(LOGDEBUG, "CWinSystemOSX::SetOcclusionState(bool occluded) - %s", 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)
+ {
+ SetMenuBarVisible(false);
+ [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;
+ KODI::MESSAGING::CApplicationMessenger::GetInstance().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()
+{
+ CGLContextObj cglcontex = nullptr;
+ if (m_appWindow)
+ {
+ OSXGLView* contentView = m_appWindow.contentView;
+ cglcontex = contentView.getGLContext.CGLContextObj;
+ }
+
+ return cglcontex;
+}
+
+bool CWinSystemOSX::FlushBuffer(void)
+{
+ 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:NSOpenGLCPSwapInterval];
+}
+
+std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(void* clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncOsx(clock));
+ return pVSync;
+}
+
+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::unique_ptr<IOSScreenSaver>(new 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(void)
+{
+ 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);
+ }
+}
diff --git a/xbmc/windowing/osx/WinSystemOSXGL.h b/xbmc/windowing/osx/WinSystemOSXGL.h
index 9b5ee5fdca..750d2ad9c7 100644
--- a/xbmc/windowing/osx/WinSystemOSXGL.h
+++ b/xbmc/windowing/osx/WinSystemOSXGL.h
@@ -10,6 +10,8 @@
#if defined(HAS_SDL)
#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "WinSystemOSX.h"
#endif
#include "rendering/gl/RenderSystemGL.h"