cocoa: Add a hint to control menu visibility in fullscreen spaces windows

Adds SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY to control whether or not the menu can be accessed when the cursor is moved to the top of the screen when a window is in fullscreen spaces mode.

The three values are true, false, and 'auto' (default), with auto resulting in a hidden menu if fullscreen was toggled programmatically, and the menu being accessible if fullscreen was toggled via the button on the window title bar, so the user has an easy way back out of fullscreen if the client app/game doesn't have a readily available option to toggle it.
This commit is contained in:
Frank Praznik 2025-01-11 14:07:05 -05:00
parent 611f132fd0
commit b4562c0243
4 changed files with 82 additions and 17 deletions

View file

@ -3330,6 +3330,23 @@ extern "C" {
*/ */
#define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" #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 * A variable controlling whether fullscreen windows are minimized when they
* lose focus. * lose focus.

View file

@ -218,6 +218,7 @@ static bool Cocoa_VideoInit(SDL_VideoDevice *_this)
data.allow_spaces = SDL_GetHintBoolean(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, true); 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); 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(); data.swaplock = SDL_CreateMutex();
if (!data.swaplock) { if (!data.swaplock) {

View file

@ -142,6 +142,7 @@ typedef enum
@property(nonatomic) NSView *sdlContentView; @property(nonatomic) NSView *sdlContentView;
@property(nonatomic) NSMutableArray *nscontexts; @property(nonatomic) NSMutableArray *nscontexts;
@property(nonatomic) BOOL in_blocking_transition; @property(nonatomic) BOOL in_blocking_transition;
@property(nonatomic) BOOL fullscreen_space_requested;
@property(nonatomic) BOOL was_zoomed; @property(nonatomic) BOOL was_zoomed;
@property(nonatomic) NSInteger window_number; @property(nonatomic) NSInteger window_number;
@property(nonatomic) NSInteger flash_request; @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_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent);
extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); 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_ #endif // SDL_cocoawindow_h_

View file

@ -404,6 +404,61 @@ static NSScreen *ScreenForPoint(const NSPoint *point)
return screen; 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) static NSScreen *ScreenForRect(const NSRect *rect)
{ {
NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect)); NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect));
@ -1195,7 +1250,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
Cocoa_CheckClipboardUpdate(_data.videodata); Cocoa_CheckClipboardUpdate(_data.videodata);
if (isFullscreenSpace && !window->fullscreen_exclusive) { if (isFullscreenSpace && !window->fullscreen_exclusive) {
[NSMenu setMenuBarVisible:NO]; Cocoa_ToggleFullscreenSpaceMenuVisibility(window);
} }
{ {
const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock; const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock;
@ -1307,9 +1362,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) { if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) {
[self setFullscreenSpace:NO]; [self setFullscreenSpace:NO];
} else { } else {
if (window->fullscreen_exclusive) { Cocoa_ToggleFullscreenSpaceMenuVisibility(window);
[NSMenu setMenuBarVisible:NO];
}
/* Don't recurse back into UpdateFullscreenMode() if this was hit in /* Don't recurse back into UpdateFullscreenMode() if this was hit in
* a blocking transition, as the caller is already waiting in * a blocking transition, as the caller is already waiting in
@ -1380,6 +1433,7 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
NSWindow *nswindow = _data.nswindow; NSWindow *nswindow = _data.nswindow;
inFullscreenTransition = NO; inFullscreenTransition = NO;
_data.fullscreen_space_requested = NO;
/* As of macOS 10.15, the window decorations can go missing sometimes after /* As of macOS 10.15, the window decorations can go missing sometimes after
certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows 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) bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking)
{ {
@autoreleasepool { @autoreleasepool {
bool succeeded = false; bool succeeded = false;
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (state) {
data.fullscreen_space_requested = YES;
}
data.in_blocking_transition = blocking; data.in_blocking_transition = blocking;
if ([data.listener setFullscreenSpace:(state ? YES : NO)]) { if ([data.listener setFullscreenSpace:(state ? YES : NO)]) {
if (blocking) { if (blocking) {