diff --git a/desktop-ui/settings/drivers.cpp b/desktop-ui/settings/drivers.cpp index 370bff6db..33f3690f3 100644 --- a/desktop-ui/settings/drivers.cpp +++ b/desktop-ui/settings/drivers.cpp @@ -27,10 +27,12 @@ auto DriverSettings::construct() -> void { program.videoFormatUpdate(); videoRefresh(); }); +#if !defined(PLATFORM_MACOS) videoExclusiveToggle.setText("Exclusive mode").onToggle([&] { settings.video.exclusive = videoExclusiveToggle.checked(); ruby::video.setExclusive(settings.video.exclusive); }); +#endif videoBlockingToggle.setText("Synchronize").onToggle([&] { settings.video.blocking = videoBlockingToggle.checked(); ruby::video.setBlocking(settings.video.blocking); @@ -48,6 +50,11 @@ auto DriverSettings::construct() -> void { settings.video.threadedRenderer = videoThreadedRendererToggle.checked(); ruby::video.setThreadedRenderer(settings.video.threadedRenderer); }); + videoNativeFullScreenToggle.setText("Use native fullscreen").onToggle([&] { + settings.video.nativeFullScreen = videoNativeFullScreenToggle.checked(); + ruby::video.setNativeFullScreen(settings.video.nativeFullScreen); + videoRefresh(); + }); #endif audioLabel.setText("Audio").setFont(Font().setBold()); @@ -153,13 +160,16 @@ auto DriverSettings::videoRefresh() -> void { item.setText(format); if(format == ruby::video.format()) item.setSelected(); } - videoMonitorList.setEnabled(videoMonitorList.itemCount() > 1); + videoMonitorList.setEnabled(videoMonitorList.itemCount() > 1 && ruby::video.hasMonitor()); videoFormatList.setEnabled(0 && videoFormatList.itemCount() > 1); +#if !defined(PLATFORM_MACOS) videoExclusiveToggle.setChecked(ruby::video.exclusive()).setEnabled(ruby::video.hasExclusive()); +#endif videoBlockingToggle.setChecked(ruby::video.blocking()).setEnabled(ruby::video.hasBlocking()); #if defined(PLATFORM_MACOS) videoColorSpaceToggle.setChecked(ruby::video.forceSRGB()).setEnabled(ruby::video.hasForceSRGB()); videoThreadedRendererToggle.setChecked(ruby::video.threadedRenderer()).setEnabled(ruby::video.hasThreadedRenderer()); + videoNativeFullScreenToggle.setChecked(ruby::video.nativeFullScreen()).setEnabled(ruby::video.hasNativeFullScreen()); #endif videoFlushToggle.setChecked(ruby::video.flush()).setEnabled(ruby::video.hasFlush()); VerticalLayout::resize(); diff --git a/desktop-ui/settings/settings.cpp b/desktop-ui/settings/settings.cpp index b0293d3fc..844820823 100644 --- a/desktop-ui/settings/settings.cpp +++ b/desktop-ui/settings/settings.cpp @@ -58,6 +58,7 @@ auto Settings::process(bool load) -> void { bind(boolean, "Video/Blocking", video.blocking); bind(boolean, "Video/PresentSRGB", video.forceSRGB); bind(boolean, "Video/ThreadedRenderer", video.threadedRenderer); + bind(boolean, "Video/NativeFullScreen", video.nativeFullScreen); bind(boolean, "Video/Flush", video.flush); bind(string, "Video/Shader", video.shader); bind(natural, "Video/Multiplier", video.multiplier); diff --git a/desktop-ui/settings/settings.hpp b/desktop-ui/settings/settings.hpp index ea12038ca..6651527b6 100644 --- a/desktop-ui/settings/settings.hpp +++ b/desktop-ui/settings/settings.hpp @@ -13,6 +13,7 @@ struct Settings : Markup::Node { bool blocking = false; bool forceSRGB = false; bool threadedRenderer = true; + bool nativeFullScreen = false; bool flush = false; string shader = "None"; u32 multiplier = 2; @@ -333,12 +334,15 @@ struct DriverSettings : VerticalLayout { Label videoFormatLabel{&videoPropertyLayout, Size{0, 0}}; ComboButton videoFormatList{&videoPropertyLayout, Size{0, 0}}; HorizontalLayout videoToggleLayout{this, Size{~0, 0}}; +#if !defined(PLATFORM_MACOS) CheckLabel videoExclusiveToggle{&videoToggleLayout, Size{0, 0}}; +#endif CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; #if defined(PLATFORM_MACOS) CheckLabel videoColorSpaceToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoThreadedRendererToggle{&videoToggleLayout, Size{0, 0}}; + CheckLabel videoNativeFullScreenToggle{&videoToggleLayout, Size{0, 0}}; #endif // Label audioLabel{this, Size{~0, 0}, 5}; diff --git a/ruby/video/metal/metal.cpp b/ruby/video/metal/metal.cpp index 94250321a..a264ce942 100644 --- a/ruby/video/metal/metal.cpp +++ b/ruby/video/metal/metal.cpp @@ -31,7 +31,9 @@ struct VideoMetal : VideoDriver, Metal { auto ready() -> bool override { return _ready; } auto hasFullScreen() -> bool override { return true; } + auto hasMonitor() -> bool override { return !_nativeFullScreen; } auto hasContext() -> bool override { return true; } + auto hasFlush() -> bool override { return true; } auto hasBlocking() -> bool override { if (@available(macOS 10.15.4, *)) { return true; @@ -41,31 +43,34 @@ struct VideoMetal : VideoDriver, Metal { } auto hasForceSRGB() -> bool override { return true; } auto hasThreadedRenderer() -> bool override { return true; } - auto hasFlush() -> bool override { return true; } + auto hasNativeFullScreen() -> bool override { return true; } auto hasShader() -> bool override { return true; } auto setFullScreen(bool fullScreen) -> bool override { - /// This function implements non-idiomatic macOS fullscreen behavior that sets the window frame equal to the display's - /// frame size and hides the cursor. Idiomatic fullscreen is still available via the normal stoplight window controls. This - /// version of fullscreen is desirable because it allows us to render around the camera housing on newer Macs - /// (important for bezel-style shaders), has snappier entrance/exit and tabbing behavior, and functions better with - /// recording and capture software such as OBS and screen recorders. Hiding the mouse cursor is also essential to - /// rendering with appropriate frame pacing in Metal's 'direct' presentation mode. - - // todo: unify with cursor auto-hide in hiro, ideally ares-wide fullscreen mode option - - if (fullScreen) { - frameBeforeFullScreen = view.window.frame; - [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)]; - [view.window setStyleMask:NSWindowStyleMaskBorderless]; - [view.window setFrame:view.window.screen.frame display:YES]; - [NSCursor setHiddenUntilMouseMoves:YES]; + // todo: fix/make consistent mouse cursor hide behavior + + if (_nativeFullScreen) { + [view.window toggleFullScreen:nil]; } else { - [NSApp setPresentationOptions:NSApplicationPresentationDefault]; - [view.window setStyleMask:NSWindowStyleMaskTitled]; - [view.window setFrame:frameBeforeFullScreen display:YES]; + /// This option implements non-idiomatic macOS fullscreen behavior that sets the window frame equal to the selected display's + /// frame size and hides the cursor. This version of fullscreen is desirable because it allows us to render around the camera + /// housing on newer Macs (important for bezel-style shaders), has snappier entrance/exit and tabbing behavior, and functions + /// better with recording and capture software such as OBS. + if (fullScreen) { + auto monitor = Video::monitor(self.monitor); + NSScreen *handle = (__bridge NSScreen *)(void *)monitor.nativeHandle; //eew + frameBeforeFullScreen = view.window.frame; + [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)]; + [view.window setStyleMask:NSWindowStyleMaskBorderless]; + [view.window setFrame:handle.frame display:YES]; + [NSCursor setHiddenUntilMouseMoves:YES]; + } else { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + [view.window setStyleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable)]; + [view.window setFrame:frameBeforeFullScreen display:YES]; + } + [view.window makeFirstResponder:view]; } - [view.window makeFirstResponder:view]; return true; } @@ -92,6 +97,18 @@ struct VideoMetal : VideoDriver, Metal { _threaded = threadedRenderer; return true; } + + auto setNativeFullScreen(bool nativeFullScreen) -> bool override { + _nativeFullScreen = nativeFullScreen; + if (nativeFullScreen) { + //maximize goes fullscreen + [view.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary]; + } else { + //maximize does not go fullscreen + [view.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary]; + } + return true; + } auto setFlush(bool flush) -> bool override { _flush = flush; @@ -562,15 +579,16 @@ private: bool forceSRGB = self.forceSRGB; self.setForceSRGB(forceSRGB); view.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable; + _threaded = self.threadedRenderer; + _blocking = self.blocking; + setNativeFullScreen(self.nativeFullScreen); _libra = librashader_load_instance(); if (!_libra.instance_loaded) { print("Metal: Failed to load librashader: shaders will be disabled\n"); } - _blocking = self.blocking; - initialized = true; return _ready = true; } diff --git a/ruby/video/metal/metal.hpp b/ruby/video/metal/metal.hpp index a89aff229..37d46e455 100644 --- a/ruby/video/metal/metal.hpp +++ b/ruby/video/metal/metal.hpp @@ -78,6 +78,7 @@ struct Metal { bool _flush = false; bool _vrrIsSupported = false; bool _threaded = true; + bool _nativeFullScreen = false; NSRect frameBeforeFullScreen = NSMakeRect(0,0,0,0); diff --git a/ruby/video/video.cpp b/ruby/video/video.cpp index 6ecb62264..2c24fdde8 100644 --- a/ruby/video/video.cpp +++ b/ruby/video/video.cpp @@ -76,6 +76,14 @@ auto Video::setThreadedRenderer(bool threadedRenderer) -> bool { return true; } +auto Video::setNativeFullScreen(bool nativeFullScreen) -> bool { + lock_guard lock(mutex); + if(instance->nativeFullScreen == nativeFullScreen) return true; + if(!instance->hasNativeFullScreen()) return false; + if(!instance->setNativeFullScreen(instance->nativeFullScreen = nativeFullScreen)) return false; + return true; +} + auto Video::setFlush(bool flush) -> bool { lock_guard lock(mutex); if(instance->flush == flush) return true; @@ -300,28 +308,33 @@ auto Video::hasMonitors() -> vector { monitor.y = rectangle.origin.y; monitor.width = rectangle.size.width; monitor.height = rectangle.size.height; - //getting the name of the monitor on macOS: "Think Different" - auto screenDictionary = [screen deviceDescription]; - auto screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; - auto displayID = [screenID unsignedIntValue]; - CFUUIDRef displayUUID = CGDisplayCreateUUIDFromDisplayID(displayID); - io_service_t displayPort = CGDisplayGetDisplayIDFromUUID(displayUUID); - auto dictionary = IODisplayCreateInfoDictionary(displayPort, 0); - CFRetain(dictionary); - if(auto names = (CFDictionaryRef)CFDictionaryGetValue(dictionary, CFSTR(kDisplayProductName))) { - auto languageKeys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - CFDictionaryApplyFunction(names, MonitorKeyArrayCallback, (void*)languageKeys); - auto orderLanguageKeys = CFBundleCopyPreferredLocalizationsFromArray(languageKeys); - CFRelease(languageKeys); - if(orderLanguageKeys && CFArrayGetCount(orderLanguageKeys)) { - auto languageKey = CFArrayGetValueAtIndex(orderLanguageKeys, 0); - auto localName = CFDictionaryGetValue(names, languageKey); - monitor.name = {1 + monitors.size(), ": ", [(__bridge NSString*)localName UTF8String]}; - CFRelease(localName); + monitor.nativeHandle = (uintptr)screen; + if (@available(macOS 10.15, *)) { + monitor.name = {1 + monitors.size(), ": ", screen.localizedName.UTF8String}; + } else { + //getting the name of the monitor on macOS: "Think Different" + auto screenDictionary = [screen deviceDescription]; + auto screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; + auto displayID = [screenID unsignedIntValue]; + CFUUIDRef displayUUID = CGDisplayCreateUUIDFromDisplayID(displayID); + io_service_t displayPort = CGDisplayGetDisplayIDFromUUID(displayUUID); + auto dictionary = IODisplayCreateInfoDictionary(displayPort, 0); + CFRetain(dictionary); + if(auto names = (CFDictionaryRef)CFDictionaryGetValue(dictionary, CFSTR(kDisplayProductName))) { + auto languageKeys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFDictionaryApplyFunction(names, MonitorKeyArrayCallback, (void*)languageKeys); + auto orderLanguageKeys = CFBundleCopyPreferredLocalizationsFromArray(languageKeys); + CFRelease(languageKeys); + if(orderLanguageKeys && CFArrayGetCount(orderLanguageKeys)) { + auto languageKey = CFArrayGetValueAtIndex(orderLanguageKeys, 0); + auto localName = CFDictionaryGetValue(names, languageKey); + monitor.name = {1 + monitors.size(), ": ", [(__bridge NSString*)localName UTF8String]}; + CFRelease(localName); + } + CFRelease(orderLanguageKeys); } - CFRelease(orderLanguageKeys); + CFRelease(dictionary); } - CFRelease(dictionary); monitors.append(monitor); } } diff --git a/ruby/video/video.hpp b/ruby/video/video.hpp index d4c6c40c9..73e922883 100644 --- a/ruby/video/video.hpp +++ b/ruby/video/video.hpp @@ -15,6 +15,7 @@ struct VideoDriver { virtual auto hasBlocking() -> bool { return false; } virtual auto hasForceSRGB() -> bool { return false; } virtual auto hasThreadedRenderer() -> bool { return false; } + virtual auto hasNativeFullScreen() -> bool { return false; } virtual auto hasFlush() -> bool { return false; } virtual auto hasFormats() -> vector { return {"ARGB24"}; } virtual auto hasShader() -> bool { return false; } @@ -28,6 +29,7 @@ struct VideoDriver { virtual auto setBlocking(bool blocking) -> bool { return true; } virtual auto setForceSRGB(bool forceSRGB) -> bool { return true; } virtual auto setThreadedRenderer(bool threadedRenderer) -> bool { return true; } + virtual auto setNativeFullScreen(bool nativeFullScreen) -> bool { return true; } virtual auto setFlush(bool flush) -> bool { return true; } virtual auto setFormat(string format) -> bool { return true; } virtual auto setShader(string shader) -> bool { return true; } @@ -52,6 +54,7 @@ protected: bool blocking = false; bool forceSRGB = false; bool threadedRenderer = true; + bool nativeFullScreen = false; bool flush = false; string format = "ARGB24"; string shader = "None"; @@ -70,6 +73,7 @@ struct Video { s32 y = 0; s32 width = 0; s32 height = 0; + uintptr_t nativeHandle = 0; }; static auto monitor(string name) -> Monitor; static auto hasMonitors() -> vector; @@ -94,6 +98,7 @@ struct Video { auto hasBlocking() -> bool { return instance->hasBlocking(); } auto hasForceSRGB() -> bool { return instance->hasForceSRGB(); } auto hasThreadedRenderer() -> bool { return instance->hasThreadedRenderer(); } + auto hasNativeFullScreen() -> bool { return instance->hasNativeFullScreen(); } auto hasFlush() -> bool { return instance->hasFlush(); } auto hasFormats() -> vector { return instance->hasFormats(); } auto hasShader() -> bool { return instance->hasShader(); } @@ -107,6 +112,7 @@ struct Video { auto blocking() -> bool { return instance->blocking; } auto forceSRGB() -> bool { return instance->forceSRGB; } auto threadedRenderer() -> bool { return instance->threadedRenderer; } + auto nativeFullScreen() -> bool { return instance->nativeFullScreen; } auto flush() -> bool { return instance->flush; } auto format() -> string { return instance->format; } auto shader() -> string { return instance->shader; } @@ -118,6 +124,7 @@ struct Video { auto setBlocking(bool blocking) -> bool; auto setForceSRGB(bool forceSRGB) -> bool; auto setThreadedRenderer(bool threadedRenderer) -> bool; + auto setNativeFullScreen(bool nativeFullScreen) -> bool; auto setFlush(bool flush) -> bool; auto setFormat(string format) -> bool; auto setShader(string shader) -> bool;