Skip to content

Bring desktop dialogs to the foreground#18

Merged
lanceewing merged 1 commit into
lanceewing:masterfrom
lxpollitt:fix/desktop-dialog-foreground
Jun 20, 2026
Merged

Bring desktop dialogs to the foreground#18
lanceewing merged 1 commit into
lanceewing:masterfrom
lxpollitt:fix/desktop-dialog-foreground

Conversation

@lxpollitt

Copy link
Copy Markdown
Contributor

Problem

Following the macOS dialog deadlock fix (#9), on (at least some versions of) Windows the Swing dialogs (file open, the settings/ROM chooser, the About box, and the quit/exit confirmations) can open behind the JOric main window instead of in front of it.

The cause is the macOS deadlock fix moved the Swing dialog calls onto the AWT event dispatch thread (EDT). On Windows, the OS only allows the thread that owns the current foreground window to bring a new window to the front. As the EDT does not own the GLFW main application window, so a dialog created by the EDT opens behind the main application window. Before the deadlock fix the dialogs ran on the main thread (which owns the GLFW window) and came to the front normally.

Candidate Fix

For each dialog we now create a temporary, effectively invisible, owner window that is brought to the foreground, and disposed of as soon as the dialog is closed. The owner window is a 1x1, undecorated, UTILITY-type frame (the UTILITY type is meant to keep it out of the taskbar and the alt-tab list), created on the EDT inside the existing dispatch so the deadlock fix is preserved.

By default the code uses a gentle toFront()/requestFocus() to bring the dialog to the front without pinning it above other windows (so you can still switch away to, say, check a filename). A single DIALOG_ALWAYS_ON_TOP static flag switches the owner window to always-on-top for all the dialogs, which would foreground more reliably but keeps the dialog pinned above everything while it's open, even when switching away to other applications. Which mode should be set as the default behaviour will depend on the outcome of Windows testing. (See Testing section below.)

Scope

Desktop (lwjgl3) only - one file (DesktopDialogHandler). No changes to core, web or Android. All the dialog methods route through a single shared owner-creating helper, so the behaviour and the toggle are uniform across them.

Testing

Tested on macOS only. All the reachable dialogs open in front and dismiss correctly, with both the default gentle approach and the always-on-top toggle, and no visible stray window appears. (Note that the promptForTextInput appears to be unused currently, so is untested.)

I don't have a Windows machine, so I can't verify the actual fix there - that's the part that needs checking to see if the candidate fix works as is, or if it needs toggling to use the always-on-top mode.

  • Do the dialogs now open in front of the application?
  • If the gentle default foregrounding behaviour still opens them behind (I think Windows might ignore toFront() from a thread that doesn't own the foreground window - the same root cause), then flip DIALOG_ALWAYS_ON_TOP to true which should force the dialogs to the front.
  • Does any stray 1x1 window appear in the taskbar or alt-tab while a dialog is open? (The UTILITY window type should prevent it, but I can't confirm that on Windows.)
  • If the always-on-top mode is required, a judgment on whether that behaviour is ok or not, since always on-top windows have the potential to be irritating if you do need to switch to another application while they are displayed. I think it's probably ok on balance - but it's subjective.

The desktop Swing dialogs (file open, settings, About, and the quit/exit confirmations) could open behind the application window on Windows. This was a behaviour regression introduced by the macOS dialog deadlock fix (lanceewing#9) which moved the dialog calls onto the AWT event dispatch thread. On Windows a thread that does not own the foreground window cannot bring a new window to the front, so a dialog created on the EDT opens behind
the application.

Each dialog is now given a temporary, effectively invisible, owner window that is brought to the foreground and disposed as soon as the dialog closes, so the dialog appears in front. By default the code uses a gentle toFront()/requestFocus() to bring this window to the foreground. But a DIALOG_ALWAYS_ON_TOP static flag is included to switch to setting the owner window to always-on-top, which foregrounds more reliably but pins the dialog above all other windows while open, even when switching to ther applications. The correct choise for this static flag needs to be chosen after testing on Windows.

The owner window is created on the EDT inside the existing dispatch, so the macOS deadlock fix is preserved.
@lxpollitt

Copy link
Copy Markdown
Contributor Author

By the way, if there's something you would like me to be doing differently relating to the CI failures, let me know. I'm happy to use whatever workflow you would prefer.

@lanceewing

Copy link
Copy Markdown
Owner

This is working for me when running the desktop version on Windows, so I will merge it in a few minutes.

@lanceewing lanceewing merged commit bdbf5ae into lanceewing:master Jun 20, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants