Dialog-focus sweep: route every background-reachable dialog through ForegroundDialog

Ed flagged this as recurring. Swept all ShowDialog call sites and fixed the ones that
can appear while RemSound is minimised in the tray (a plain ShowDialog opens BEHIND
everything there — no foreground focus, NVDA can't find it):

- Quick profile switch (QuickProfileSwitchDialog): opened by a GLOBAL hotkey, so the
  app is usually minimised when it fires. Was using an ad-hoc SetForegroundWindow that
  Windows blocks from a background process (only flashes the taskbar). Now via
  ForegroundDialog; deleted the weak BringToForeground + its P/Invoke.
- Close-time unsaved-changes prompt (OnFormClosing): fires from tray -> Exit and OS
  shutdown while minimised. Now via ForegroundDialog. (Also covers the tray profile-
  switch, which routes through the same form-close path.)
- (Service-profile dialog was fixed in the previous commit.)

Verified the rest are safe: startup notices, update notices, the service/mic/Realtek/
About warnings and the password dialogs already use ForegroundDialog; every other
ShowDialog(this) is menu- or button-driven with the app already focused (Preferences,
EQ band, rename, recording settings, profile picker, hotkey capture nested in the
menu-driven shortcuts dialog). Recorded the rule in memory so it stops recurring.

Gate 71/71 + 7 relay tests; 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-27 12:40:34 +01:00
co-authored by Claude Fable 5
parent 6a1b4075b9
commit d09c47432f
2 changed files with 12 additions and 24 deletions
+5 -2
View File
@@ -9911,13 +9911,16 @@ public sealed partial class MainForm : Form
// hook (false-NEGATIVE — user changes something via an unhooked path, no prompt // hook (false-NEGATIVE — user changes something via an unhooked path, no prompt
// on close) is acceptable; the previous false-POSITIVE behaviour was nagging. // on close) is acceptable; the previous false-POSITIVE behaviour was nagging.
{ {
var result = MessageBox.Show(this, // Via ForegroundDialog: OnFormClosing fires from tray → Exit (RemSound minimised) and
// OS shutdown, so a plain MessageBox(this) would open behind everything and a blind
// user couldn't reach it to answer (Ed, 2026-07-27 dialog-focus sweep).
var result = ForegroundDialog.Show(owner => MessageBox.Show(owner,
"You have unsaved changes to your profile. Save them before exiting?\n\n" + "You have unsaved changes to your profile. Save them before exiting?\n\n" +
"Yes — save and exit.\nNo — exit without saving.\nCancel — keep RemSound open.", "Yes — save and exit.\nNo — exit without saving.\nCancel — keep RemSound open.",
"RemSound — unsaved changes", "RemSound — unsaved changes",
MessageBoxButtons.YesNoCancel, MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button3); MessageBoxDefaultButton.Button3));
if (result == DialogResult.Cancel) if (result == DialogResult.Cancel)
{ {
+7 -22
View File
@@ -22,12 +22,13 @@ internal sealed class QuickProfileSwitchDialog
var (dialog, list, chosenPath) = Build(profiles); var (dialog, list, chosenPath) = Build(profiles);
using (dialog) using (dialog)
{ {
dialog.Shown += (_, _) => dialog.Shown += (_, _) => list.Focus();
{ // Via ForegroundDialog: this is opened by a GLOBAL hotkey, so RemSound is usually
BringToForeground(dialog); // minimised in the tray when it fires — the old ad-hoc SetForegroundWindow only flashed
list.Focus(); // the taskbar there (Windows blocks foreground from a background process), leaving a blind
}; // user Alt-Tabbing for it. ForegroundDialog's 1×1-owner + foreground-lock dance actually
return dialog.ShowDialog() == DialogResult.OK ? chosenPath() : null; // surfaces it with focus wherever RemSound is sitting (Ed, 2026-07-27 dialog-focus sweep).
return ForegroundDialog.Show(owner => dialog.ShowDialog(owner)) == DialogResult.OK ? chosenPath() : null;
} }
} }
@@ -142,20 +143,4 @@ internal sealed class QuickProfileSwitchDialog
return (dialog, list, () => chosenPath); return (dialog, list, () => chosenPath);
} }
private static void BringToForeground(Form form)
{
try
{
form.Activate();
// The hotkey press is recent user input, so the foreground lock lets us call this
// (same handshake the tray "restore" uses).
SetForegroundWindow(form.Handle);
}
catch { /* foreground-lock race — best effort, the window is still TopMost */ }
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
} }