diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 622297c04..de5b5b407 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -3330,6 +3330,23 @@ extern "C" { */ #define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" +/** + * A variable that specifies the menu visibility when a window is fullscreen in Spaces on macOS. + * + * The variable can be set to the following values: + * + * - "0": The menu will be hidden when the window is in a fullscreen space, and not accessible by moving the mouse to the top of the screen. + * - "1": The menu will be accessible when the window is in a fullscreen space. + * - "auto": The menu will be hidden if fullscreen mode was toggled on programmatically via `SDL_SetWindowFullscreen()`, + * and accessible if fullscreen was entered via the "fullscreen" button on the window + * title bar. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.1.9. + */ +#define SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY" + /** * A variable controlling whether fullscreen windows are minimized when they * lose focus. diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 6f25de1e6..7d595e136 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -218,6 +218,7 @@ static bool Cocoa_VideoInit(SDL_VideoDevice *_this) data.allow_spaces = SDL_GetHintBoolean(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, true); data.trackpad_is_touch_only = SDL_GetHintBoolean(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, false); + SDL_AddHintCallback(SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY, Cocoa_MenuVisibilityCallback, NULL); data.swaplock = SDL_CreateMutex(); if (!data.swaplock) { diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index fa46cbb37..af567b8af 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -142,6 +142,7 @@ typedef enum @property(nonatomic) NSView *sdlContentView; @property(nonatomic) NSMutableArray *nscontexts; @property(nonatomic) BOOL in_blocking_transition; +@property(nonatomic) BOOL fullscreen_space_requested; @property(nonatomic) BOOL was_zoomed; @property(nonatomic) NSInteger window_number; @property(nonatomic) NSInteger flash_request; @@ -192,4 +193,6 @@ extern bool Cocoa_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, boo extern bool Cocoa_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue); + #endif // SDL_cocoawindow_h_ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 9ae7b1635..01fdc7b42 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -404,6 +404,61 @@ static NSScreen *ScreenForPoint(const NSPoint *point) return screen; } +bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) +{ + @autoreleasepool { + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + + if ([data.listener isInFullscreenSpace]) { + return true; + } else { + return false; + } + } +} + +typedef enum CocoaMenuVisibility +{ + COCOA_MENU_VISIBILITY_AUTO = 0, + COCOA_MENU_VISIBILITY_NEVER, + COCOA_MENU_VISIBILITY_ALWAYS +} CocoaMenuVisibility; + +static CocoaMenuVisibility menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; + +static void Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_Window *window) +{ + if (window && Cocoa_IsWindowInFullscreenSpace(window)) { + SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + + // 'Auto' sets the menu to visible if fullscreen wasn't explicitly entered via SDL_SetWindowFullscreen(). + if ((menu_visibility_hint == COCOA_MENU_VISIBILITY_AUTO && !data.fullscreen_space_requested) || + menu_visibility_hint == COCOA_MENU_VISIBILITY_ALWAYS) { + [NSMenu setMenuBarVisible:YES]; + } else { + [NSMenu setMenuBarVisible:NO]; + } + } +} + +void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + if (newValue) { + if (*newValue == '0' || SDL_strcasecmp(newValue, "false") == 0) { + menu_visibility_hint = COCOA_MENU_VISIBILITY_NEVER; + } else if (*newValue == '1' || SDL_strcasecmp(newValue, "true") == 0) { + menu_visibility_hint = COCOA_MENU_VISIBILITY_ALWAYS; + } else { + menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; + } + } else { + menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; + } + + // Update the current menu visibility. + Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_GetKeyboardFocus()); +} + static NSScreen *ScreenForRect(const NSRect *rect) { NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect)); @@ -1195,7 +1250,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) Cocoa_CheckClipboardUpdate(_data.videodata); if (isFullscreenSpace && !window->fullscreen_exclusive) { - [NSMenu setMenuBarVisible:NO]; + Cocoa_ToggleFullscreenSpaceMenuVisibility(window); } { const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock; @@ -1307,9 +1362,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) { [self setFullscreenSpace:NO]; } else { - if (window->fullscreen_exclusive) { - [NSMenu setMenuBarVisible:NO]; - } + Cocoa_ToggleFullscreenSpaceMenuVisibility(window); /* Don't recurse back into UpdateFullscreenMode() if this was hit in * a blocking transition, as the caller is already waiting in @@ -1380,6 +1433,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void) NSWindow *nswindow = _data.nswindow; inFullscreenTransition = NO; + _data.fullscreen_space_requested = NO; /* As of macOS 10.15, the window decorations can go missing sometimes after certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows @@ -3030,25 +3084,15 @@ void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } } -bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) -{ - @autoreleasepool { - SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; - - if ([data.listener isInFullscreenSpace]) { - return true; - } else { - return false; - } - } -} - bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking) { @autoreleasepool { bool succeeded = false; SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; + if (state) { + data.fullscreen_space_requested = YES; + } data.in_blocking_transition = blocking; if ([data.listener setFullscreenSpace:(state ? YES : NO)]) { if (blocking) {