Skip to content

Add macOS borderless fullscreen support for Adventure#11347

Open
liminalwarmth wants to merge 2 commits into
Card-Forge:masterfrom
liminalwarmth:agent/macos-borderless-fullscreen
Open

Add macOS borderless fullscreen support for Adventure#11347
liminalwarmth wants to merge 2 commits into
Card-Forge:masterfrom
liminalwarmth:agent/macos-borderless-fullscreen

Conversation

@liminalwarmth

@liminalwarmth liminalwarmth commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

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:

  • Adventure opens as a monitor-sized, undecorated normal window instead of an exclusive-fullscreen window.
  • Cmd-Tab and applications on a second monitor work without minimizing or resizing Adventure.
  • The macOS menu bar and Dock stay hidden while the borderless window is visible.
  • Cmd-M minimizes normally, and restoring the window preserves its full-monitor geometry.

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, and en-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=false passes (3 tests).
  • mvn -q -pl forge-gui-mobile-dev -am package -DskipTests passes.
  • Checkstyle reports 0 violations.
  • Validated in play on macOS: menu bar and Dock hiding, Cmd-Tab, second-monitor application use, Cmd-M minimize and restore, stable full-monitor geometry, and mouse release after focus changes.

@Jetz72 Jetz72 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted; I'll open a separate PR for that as requested.

Comment on lines +217 to +223
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);
}

@Jetz72 Jetz72 Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)."

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:

  1. 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.
  2. 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.
  3. 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@liminalwarmth

liminalwarmth commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

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.

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.

@liminalwarmth

Copy link
Copy Markdown
Contributor Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adventure Mode Borderless Full Screen Support for 2nd Monitor

3 participants