diff options
author | fuzzard <fuzzard@kodi.tv> | 2021-09-13 12:31:19 +1000 |
---|---|---|
committer | fuzzard <fuzzard@kodi.tv> | 2022-04-12 16:01:13 +0200 |
commit | 6ba89e19445909a8da54f48f36fc6f2601cfe8ef (patch) | |
tree | 41c6142969b31fae5366e79d4ec7b73fe0bf9755 | |
parent | 83694727a263c1365a31580cfd88b1d3d589aa68 (diff) |
[OSX] native windowing implementation
-rw-r--r-- | cmake/platform/osx/osx.cmake | 5 | ||||
-rw-r--r-- | docs/README.macOS.md | 10 | ||||
-rw-r--r-- | tools/depends/Makefile.include.in | 1 | ||||
-rw-r--r-- | tools/depends/configure.ac | 17 | ||||
-rw-r--r-- | tools/depends/target/Makefile | 4 | ||||
-rw-r--r-- | tools/depends/target/cmakebuildsys/Makefile | 10 | ||||
-rw-r--r-- | xbmc/Application.cpp | 2 | ||||
-rw-r--r-- | xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp | 2 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/CMakeLists.txt | 7 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/CocoaInterface.mm | 6 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/OSXGLView.h | 27 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/OSXGLView.mm | 107 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/OSXGLWindow.h | 38 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/OSXGLWindow.mm | 216 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/XBMCApplication.mm | 444 | ||||
-rw-r--r-- | xbmc/windowing/osx/CMakeLists.txt | 6 | ||||
-rw-r--r-- | xbmc/windowing/osx/WinSystemOSX.h | 120 | ||||
-rw-r--r-- | xbmc/windowing/osx/WinSystemOSX.mm | 1369 | ||||
-rw-r--r-- | xbmc/windowing/osx/WinSystemOSXGL.h | 2 |
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, ¤tFps, 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" |