Skip to content

Commit a1ddfb1

Browse files
committed
cocoa: hand-rolled borderless fullscreen on non-Metal path
After 23a9456 unified Cocoa + Metal on -[RAWindow sendEvent:] as the event pump, fullscreen on the HAVE_COCOA path stopped responding to keyboard or mouse input. ROOT CAUSE: -[NSView enterFullScreenMode:withOptions:] internally calls CGCaptureAllDisplays and creates a new AppKit-manufactured NSWindow to host the view during fullscreen. That replacement window is a plain NSWindow, not RAWindow, so -[RAWindow sendEvent:] stops firing; keystrokes and mouse events get delivered to the fullscreen wrapper and die in CocoaView's default responder chain (CocoaView's -keyDown: is an empty beep-squelch stub). The Metal path is unaffected: it takes a completely different route via -[apple_platform setVideoMode:] -> native -[NSWindow toggle- FullScreen:] on 10.7+, which keeps the RAWindow as host throughout. FIX: replace enterFullScreenMode: on the non-Metal path with a hand- rolled borderless RAWindow. On fullscreen enter: - save the current windowed window + view frame - alloc a borderless RAWindow covering the chosen screen - hide the menu bar via +[NSMenu setMenuBarVisible:NO] (pure Cocoa, 10.0+, no Carbon framework dependency); only when fullscreening onto screen 0, since hiding the menu bar on a secondary screen would mangle the primary - move the CocoaView from windowed window to fullscreen window - makeKeyAndOrderFront + makeFirstResponder so keystrokes route On exit, reverse everything: move the view back, show menu bar, tear down the fullscreen window. Both the fullscreen RAWindow and the retained windowed-window reference are released at exit - net retain count balanced. ui_cocoa.m: - Add -[RAWindow canBecomeKeyWindow] { return YES; }. A border- less NSWindow (no NSWindowStyleMaskTitled) can't become the key window by default, so without this the new fullscreen RAWindow would be unable to receive keystrokes regardless of everything else. No-op for the titled windowed-mode RAWindow, which is key-eligible by default. cocoa_gl_ctx.m: - Replace the enterFullScreenMode: / exitFullScreenModeWithOptions: pair with alloc/move/show of a borderless RAWindow. RAWindow is looked up via NSClassFromString(@"RAWindow") rather than exposing its @interface from ui_cocoa.m into a shared header. Additional constraint worth calling out: on 10.5 Leopard, -[NSWindow setStyleMask:] doesn't exist, so we can't toggle the existing window between titled and borderless - the new-window approach is the only one that works on every macOS version we target. SDL, GLFW, and other cross-platform frontends use this same pattern for pre-Lion fullscreen on macOS. Windowed mode is untouched so there's no regression risk there; fullscreen on modern macOS via the Metal path is also untouched.
1 parent f0b5fd9 commit a1ddfb1

2 files changed

Lines changed: 104 additions & 5 deletions

File tree

gfx/drivers_context/cocoa_gl_ctx.m

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,21 +462,112 @@ static bool cocoa_gl_gfx_ctx_set_video_mode(void *data,
462462
[apple_platform setVideoMode:mode];
463463
cocoa_show_mouse(data, !fullscreen);
464464
#else
465-
/* TODO/FIXME: Screen mode support. */
465+
/* Hand-rolled fullscreen for the non-Metal path.
466+
*
467+
* The previous implementation called -[NSView enterFullScreenMode:
468+
* withOptions:], which internally captures all displays and moves
469+
* the view into an AppKit-manufactured NSWindow. That replacement
470+
* window is a plain NSWindow, not RAWindow, so -[RAWindow sendEvent:]
471+
* (the event-pump override that feeds cocoa_input, added in commit
472+
* 23a945639) stops firing while fullscreen, and keystrokes / mouse
473+
* clicks get dropped.
474+
*
475+
* Instead, create our own borderless RAWindow covering the chosen
476+
* screen, move the CocoaView into it, and show it above the menu
477+
* bar. Because the fullscreen window is itself an RAWindow, our
478+
* sendEvent: override keeps firing. SDL, GLFW, and similar
479+
* libraries use this same pattern for pre-Lion fullscreen on macOS.
480+
*
481+
* Extra constraint: on 10.5 Leopard, -[NSWindow setStyleMask:]
482+
* doesn't exist, so we can't toggle the existing window's style
483+
* between titled and borderless - the new-window approach is the
484+
* only option that works on every macOS version we target.
485+
*
486+
* HAVE_COCOA_METAL is unaffected: that path goes through
487+
* -[apple_platform setVideoMode:] above, which drives the native
488+
* -[NSWindow toggleFullScreen:] API on 10.7+. */
489+
static NSWindow *saved_windowed_window = NULL;
490+
static NSWindow *fullscreen_window = NULL;
491+
static NSRect saved_view_frame;
492+
466493
if (fullscreen)
467494
{
468495
if (!has_went_fullscreen)
469496
{
470-
[g_view enterFullScreenMode:(BRIDGE NSScreen *)cocoa_screen_get_chosen() withOptions:nil];
497+
NSScreen *screen = (BRIDGE NSScreen *)cocoa_screen_get_chosen();
498+
NSRect screen_frame = [screen frame];
499+
/* Look up RAWindow at runtime rather than pulling its
500+
* @interface out of ui_cocoa.m into a shared header. */
501+
Class ra_window_cls = NSClassFromString(@"RAWindow");
502+
503+
/* Remember where the view lived so we can put it back on exit. */
504+
saved_windowed_window = [[g_view window] retain];
505+
saved_view_frame = [g_view frame];
506+
507+
/* Build the fullscreen host window. NSBorderlessWindowMask is
508+
* 0 on every macOS version, identical 10.5 through modern.
509+
* Raising above NSMainMenuWindowLevel is belt-and-braces once
510+
* the menu bar is hidden below. */
511+
fullscreen_window = [[ra_window_cls alloc]
512+
initWithContentRect:screen_frame
513+
styleMask:NSBorderlessWindowMask
514+
backing:NSBackingStoreBuffered
515+
defer:NO];
516+
[fullscreen_window setLevel:NSMainMenuWindowLevel + 1];
517+
[fullscreen_window setOpaque:YES];
518+
[fullscreen_window setHidesOnDeactivate:YES];
519+
520+
/* Hide menu bar + Dock. Only valid when fullscreening onto
521+
* screen 0 (the screen that owns the menu bar); on a
522+
* secondary screen the menu bar stays put and hiding it would
523+
* mangle the primary screen. */
524+
if ([[NSScreen screens] count] > 0
525+
&& [screen isEqual:[[NSScreen screens] objectAtIndex:0]])
526+
[NSMenu setMenuBarVisible:NO];
527+
528+
/* Move the CocoaView from the windowed window into the
529+
* fullscreen window. Retain across the move so the view
530+
* isn't released by removeFromSuperview... if it happened
531+
* to hold the last reference. */
532+
[g_view retain];
533+
[g_view removeFromSuperviewWithoutNeedingDisplay];
534+
[[fullscreen_window contentView] addSubview:g_view];
535+
[g_view setFrame:[[fullscreen_window contentView] bounds]];
536+
[g_view release];
537+
538+
/* Order the windowed window out, bring the fullscreen window
539+
* up, and route keystrokes to the view. */
540+
[saved_windowed_window orderOut:nil];
541+
[fullscreen_window makeKeyAndOrderFront:nil];
542+
[fullscreen_window makeFirstResponder:g_view];
543+
471544
cocoa_show_mouse(data, false);
472545
}
473546
}
474547
else
475548
{
476-
if (has_went_fullscreen)
549+
if (has_went_fullscreen && fullscreen_window)
477550
{
478-
[g_view exitFullScreenModeWithOptions:nil];
479-
[[g_view window] makeFirstResponder:g_view];
551+
/* Put the view back in the windowed window. */
552+
[g_view retain];
553+
[g_view removeFromSuperviewWithoutNeedingDisplay];
554+
[[saved_windowed_window contentView] addSubview:g_view];
555+
[g_view setFrame:saved_view_frame];
556+
[g_view release];
557+
558+
/* Restore the menu bar, tear down the fullscreen window,
559+
* bring the windowed window back. */
560+
[NSMenu setMenuBarVisible:YES];
561+
562+
[fullscreen_window orderOut:nil];
563+
[fullscreen_window release];
564+
fullscreen_window = NULL;
565+
566+
[saved_windowed_window makeKeyAndOrderFront:nil];
567+
[saved_windowed_window makeFirstResponder:g_view];
568+
[saved_windowed_window release];
569+
saved_windowed_window = NULL;
570+
480571
cocoa_show_mouse(data, true);
481572
}
482573

ui/drivers/ui_cocoa.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ @interface RAWindow : NSWindow
383383

384384
@implementation RAWindow
385385

386+
/* A borderless NSWindow (no NSWindowStyleMaskTitled) cannot become
387+
* the key window by default - titled is an implicit prerequisite
388+
* unless this returns YES explicitly. The windowed-mode RAWindow
389+
* is titled so this has no effect there, but the borderless
390+
* fullscreen RAWindow created on the HAVE_COCOA path in
391+
* cocoa_gl_ctx.m needs it to receive keystrokes. */
392+
- (BOOL)canBecomeKeyWindow { return YES; }
393+
386394
#ifdef HAVE_COCOA_METAL
387395
#define CONVERT_POINT() [apple_platform.renderView convertPoint:[event locationInWindow] fromView:nil]
388396
#else

0 commit comments

Comments
 (0)