v4.1: two screen-reader fixes — silent start-in-tray, announce on restore

- Starting straight into the tray (StartMinimised / --minimized) no longer plays the "minimise"
  cue; only a genuine user minimise does (the startup path passes playCue:false)
- Restoring the window from the tray now lands focus on a real named control on the active tab so
  NVDA announces it, instead of resting on the role-less QuietTabControl and surfacing silently
- Shared the focus-a-leaf-for-announcement helper between the main window and Preferences

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-13 15:59:59 +01:00
co-authored by Claude Opus 4.8
parent a408d2b56e
commit 5abbeefe29
7 changed files with 73 additions and 54 deletions
+30
View File
@@ -27,6 +27,36 @@ internal static class WinEventNotifier
NotifyWinEvent(EVENT_OBJECT_FOCUS, control.Handle, OBJID_CLIENT, CHILDID_SELF);
}
}
/// <summary>Land NVDA on a named control so a freshly-shown window or dialog announces itself
/// instead of surfacing silently. Focuses the first focusable leaf inside <paramref name="page"/>
/// (falling back to <paramref name="fallback"/>), forcing a genuine focus CHANGE (ActiveControl=null
/// first) and re-firing the MSAA focus event. Used by the Preferences dialog on open and by the
/// main window when it's restored from the tray. The page sits inside a <see cref="QuietTabControl"/>
/// whose own accessible object is deliberately role-less and nameless, so focusing the tab control
/// itself would be silent — this finds a real leaf (the first interactive control on whatever tab
/// is showing) instead. Best run deferred (BeginInvoke), after the show/foreground settles.</summary>
public static void AnnounceByFocusingLeaf(ContainerControl form, Control? page, Control fallback)
{
var leaf = FirstFocusableLeaf(page) ?? fallback;
form.ActiveControl = null;
leaf.Focus();
if (leaf.IsHandleCreated) NotifyFocus(leaf);
}
/// <summary>The first visible, enabled, tab-stop control inside <paramref name="container"/>,
/// searched depth-first in child order (which matches the order controls were added). Returns a
/// real leaf (button / list / combo / checkbox) — never a layout panel or the role-less tab strip.</summary>
private static Control? FirstFocusableLeaf(Control? container)
{
if (container is null) return null;
foreach (Control c in container.Controls)
{
if (c is { CanSelect: true, TabStop: true, Visible: true, Enabled: true }) return c;
if (FirstFocusableLeaf(c) is { } nested) return nested;
}
return null;
}
}
/// <summary>