Add macOS borderless fullscreen support for Adventure#11347
Add macOS borderless fullscreen support for Adventure#11347liminalwarmth wants to merge 2 commits into
Conversation
Jetz72
left a comment
There was a problem hiding this comment.
Feels like the AI took a few nuances of circumstance and implementation that came up in the feature request and made them into fundamental parts of the design. As though the MacOS exclusivity was a core part of the feature and not a simple gating of something that hasn't been verified on other platforms.
| public void focusLost() { | ||
| super.focusLost(); | ||
| SoundSystem.instance.setWindowFocus(false); | ||
| SoundSystem.instance.setWindowFocus(macOSBorderlessFullScreen); |
There was a problem hiding this comment.
If you want to disable the behavior of stopping music when focus is lost, that should probably be a separate PR, and be gated behind a separate preference rather than tied to a window mode. It also probably should be handled in the SoundSystem and not on the window listener.
There was a problem hiding this comment.
Extracted; I'll open a separate PR for that as requested.
| private static void setPresentationOptions(long options) { | ||
| long application = JNI.invokePPJ(NS_APPLICATION, SHARED_APPLICATION, OBJC_MSG_SEND); | ||
| if (options != DEFAULT) { | ||
| ensureMinimizeMenu(application); | ||
| } | ||
| JNI.invokePPPV(application, SET_PRESENTATION_OPTIONS, options, OBJC_MSG_SEND); | ||
| } |
There was a problem hiding this comment.
This class of platform-specific constants and native interface invocations makes me think that whatever limitation you're aiming to overcome here would better be resolved in LWJGL or GLFW.
There was a problem hiding this comment.
I was originally using a GLFW approach, but on Mac OS I was running into issues sliding other windows over it for some reason and having it properly behave like a normal FS borderless window. GLFW was the Windows and Linux approach when I had those in, but I pulled them out for this cut since I couldn't test on those platforms. I'll take a look at LWJGL.
There was a problem hiding this comment.
ObjCRuntime, JNI, LibSystem are all LWJGL's own bundled macOS bindings — no new dependency was added. What GLFW lacks is any API for app-level NSApplication.presentationOptions (hide/auto-hide menu bar and Dock for a normal-level window). GLFW's ownborderless path on macOS puts the window at a level above the menu bar, which is exactly what you found broken: with auto-iconify off it floats above everything and you can't use a second monitor. Your history confirms GLFW's built-in behavior is the limitation, not a workaround you skipped.
There was a problem hiding this comment.
One of my design goals here was to support something like alt-tabbing to a web browser on a single screen behind the game window or using keyboard commands to minimize the app or show the desktop while still allowing it to keep focus, and not do things like pop the dock in the normal course of gameplay while the mouse is at the bottom of the screen. I had to ask it to change the approach we tried initially which was failing at those things. It says these are LWJGL's bundled AppKit bindings.
There was a problem hiding this comment.
I get the vision, I'm just surprised none of the underlying libraries support this properly. Looking at the division of responsibilities though, maybe libGDX would be the more appropriate home for a solution like this? Unless we're utilizing its window/application configurations incorrectly...
There was a problem hiding this comment.
Yeah, agreed. Unfortunately, I'm not personally familiar with any of the idiosyncrasies of these libraries, so I can only tell you the testing angles I came at it from, and I can tell you what Fable and Sol tell me when I relay that technical feedback and do a little digging. Here are the tight highlights from some research about this (edits mine, comments from Fable):
"Agreed on the division of responsibilities [Jetz suggested]; the blocker is that libGDX delegates windowing to GLFW, which has no presentation-options API (glfw#2246), and libGDX would need these same ObjC calls (see libgdx#6646/#7546)."
- You're (most likely) not misusing libGDX. libGDX's tracker confirms this is a gap: Convoke improvise delve #6646 — borderless fullscreen support request (Add support for borderless fullscreen mode on supporting desktop OSes libgdx/libgdx#6646) and Support for Lord Jyscal Guado #7546 — macOS Dock stays interactable over fullscreen (Mac OS fullscreen mode does not cover the Mac Dock completely, unless window is setResizable(false) libgdx/libgdx#7546) are this same class of problem.
- The issue is a layer below libGDX, because libGDX delegates all desktop windowing to GLFW (via LWJGL). So "the underlying libraries should support this" really points at GLFW, and there's a defensible reason they don't: presentationOptions is application-global state, not per-window state, and GLFW historically treats app-level policy as the application's business.
I asked Fable to check if there was an alternative approach that would still achieve the design goals on MacOS (no taskbar popup, minimizable window, etc) but work in a way more consistent with the directions you've proposed and got this:
"Short answer: no — for the full set of behaviors you want, NSApplication.presentationOptions is the macOS mechanism, and every path to it looks like the code you already have. The alternatives each sacrifice one of your stated goals."
That sounds reasonable to me based on what I saw when testing, but I'm not familiar enough with this problem space or these libraries to be able to personally validate that or suggest a different pattern. I think on Linux and Windows GLFW might handle this better since Fable had paths for both in the original code that kept that approach? So if you wanted to support it for all three, I think it's just macOS that would be the problem and need special handling. The only other alternatives that were feasible would be making corrections or suggesting additions to the libraries themselves, which feels like probably the correct long-term solution but is unlikely to help Forge support this anytime soon.
However, no worries if you want to reject this PR because the pattern feels wrong for the repo standards! I have it fixed in my local build, which solves my immediate problem. I'll paste the full explanation I got from Fable below as a starting point in case anyone wants to dig deeper on this.
Thanks for taking the time to review it and discuss regardless of your decision!
Fable's full response (click to expand)
Short answer: no — for the full set of behaviors you want,
NSApplication.presentationOptions is the macOS mechanism, and every path to it
looks like the code you already have. The alternatives each sacrifice one of
your stated goals. Here's the rundown:What you want is a specific combination: a screen-covering window at normal
window level (so Cmd-Tab layering, second monitor, and show-desktop all work),
with the menu bar and Dock auto-hidden so they don't pop during gameplay.
Going through the alternatives:
- macOS native fullscreen (toggleFullScreen, the green-button mode): hides
menu bar and Dock properly, but moves the game to its own Space. Cmd-Tabbing
to a browser swipes you to a different Space instead of layering windows on
one screen — fails your core goal.- GLFW's own borderless-fullscreen path: puts the window at a level above the
menu bar. That's the broken behavior you already hit — floats over everything,
second monitor unusable.- Accessory activation policy (the proposal in glfw#2246
(Hide taskbar/Dock icon and menu bar glfw/glfw#2246)): hides the menu bar and Dock icon
by making the app an "accessory" — but accessory apps are excluded from
Cmd-Tab entirely, which breaks app switching. And it's still just an open
feature request, not shipped API.- Window sized below the menu bar (no presentation options at all): menu bar
stays visible and the Dock still pops when your mouse hits the screen bottom —
fails the "don't interrupt gameplay" requirement.- Display capture / kiosk modes: strictly more exclusive than what you have;
fails everything about coexisting with other windows.So the design space isn't "which mechanism" — it's settled — it's which layer
makes the ObjC call. Three options, and this maps directly onto Jetz72's
concern:
- Keep it in Forge (current PR): the pragmatic choice. It's already isolated
in one MacOSPresentation class using LWJGL's bundled bindings, documented as
removable.- Contribute it to libGDX's Lwjgl3 backend: the right long-term home. libGDX
shipped generic borderless fullscreen in 1.11 (Convoke improvise delve #6646
(Add support for borderless fullscreen mode on supporting desktop OSes libgdx/libgdx#6646) is closed), but its macOS
behavior still has exactly these gaps (Support for Lord Jyscal Guado #7546
(Mac OS fullscreen mode does not cover the Mac Dock completely, unless window is setResizable(false) libgdx/libgdx#7546) — Dock stays interactable). A
small config option or macOS helper there would likely be welcomed, and
Forge's shim gets deleted when it lands. The catch is release latency — you'd
keep the shim until a libGDX release with the fix reaches Forge anyway.- Contribute it to GLFW: architecturally purest, but presentation options are
app-global state that GLFW has historically treated as out of scope, and the
pipeline (GLFW → LWJGL → libGDX → Forge) is years long.Sources: glfw#2246 — Hide taskbar/Dock icon and menu bar
(glfw/glfw#2246), libgdx#6646 — borderless
fullscreen support (libgdx/libgdx#6646), libgdx#7546
— macOS Dock over fullscreen (libgdx/libgdx#7546),
GLFW window guide (https://www.glfw.org/docs/3.3/window_guide.html),
NSApplication.PresentationOptions (https://developer.apple.com/documentation/a
ppkit/nsapplication/presentationoptions)
There was a problem hiding this comment.
If you'd be okay with this approach but would prefer for it to be solved as a universal option across operating systems (with a temporary stopgap solution for just MacOS), I'm happy to add the other two paths back in. Just let me know. As I said earlier, I just can't manually test them and would need help there.
That's probably Sol being extremely literal when I told it to focus on Mac OS because I wasn't able to test on Linux and Windows. Let me know if there are other places you're specifically seeing that and I can take another look. Edit: To clarify, that was actually me specifically requesting MacOS only to try to keep the PR smaller. I could add back in the other OS if desired? But can't verify them easily. |
|
Okay; extracted the sound piece for a separate PR, made it less MacOS-oriented in how things are named and presented (including the settings option), and explained the LWJGL binding approach. If you wanted this expanded to both Windows and Linux so it covers the multi-platform desktop cases, that's not too much trouble. I just can't test them directly. Happy to make the adjustment or let someone else try that! |
Resolves #11342
Implements the feature request in Card-Forge/forge#11342: a borderless fullscreen option for Adventure that remains full-monitor while allowing normal use of other applications.
Why
The existing GLFW fullscreen mode automatically iconifies when it loses focus, so using an application on a second monitor minimizes Adventure. Disabling auto-iconify on that exclusive-fullscreen window prevents the minimization, but leaves the game above other applications and does not provide normal macOS minimize behavior.
What
Adds a Borderless Fullscreen option (shown on macOS only until validated elsewhere) beside the existing Fullscreen setting:
Per review feedback, the change to keep music playing while unfocused has been removed from this PR; it will be proposed separately as a preference handled in SoundSystem.
The window setup itself is platform-neutral; the option and launcher behavior are gated with
OperatingSystem.isMac()because macOS is the only platform this has been validated on so far. The setting is hidden on other platforms, a stale preference value is ignored there, and the existing fullscreen path remains unchanged for Windows, Linux, and Android.A small, lazily initialized AppKit bridge applies the presentation options and native minimize action without adding a dependency or loading macOS symbols on other platforms.
Changes
forge-gui-mobile-dev/src/forge/app/GameLauncher.java: normal full-monitor window setup, macOS presentation lifecycle, and minimize action.forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java: macOS-gated Adventure setting, mutually exclusive with Fullscreen.forge-gui-mobile/src/forge/screens/settings/SettingsPage.java: matching macOS-gated option in the classic mobile settings page.SettingData,ForgePreferences, anden-US.properties: persist and label the new option.Primary feature was written with Codex 5.6 Sol with guidance to scope it as small as possible, code reviewed in several rounds by Sol and Claude Fable, and manually tested (by me on MacOS) before opening the PR.
Testing
mvn -pl forge-gui-mobile-dev -am test -DskipTests=falsepasses (3 tests).mvn -q -pl forge-gui-mobile-dev -am package -DskipTestspasses.