v3.4: handle-leak fix, Realtek ASIO block, device notifications, quick profile switch, hotkey announcements
Freezes the v3.4 feature set (everything since the v3.3 public release): - Fix the receiver handle leak: the 3-second device-refresh timer reopened the configured ASIO driver every tick, and Realtek's ASIO driver leaks Event+Mutant handles on every open. Cache the ASIO probe per driver so it is opened once. - Realtek ASIO block: detect a Realtek ASIO driver, offer once to disable it, and never touch it again if disabled; Options-menu toggle to reverse. Global config. - Device hot-plug is event-driven (AudioDeviceChangeNotifier) instead of a 3s poll; debounced refresh, falls back to polling if registration fails. - Quick profile switch: new global hotkey opens an NVDA-friendly popup of all profiles (current marked); Enter/click switches; plays a new "profile menu open" cue with Preferences mute + custom-sound. - Announce assigned global hotkeys on the controls/menu items they drive (NVDA reads "press X anywhere"). File > Open already had Ctrl+O. - Held-back changes folded in: config-folder migration, codec-column fix, Tailscale endpoint network-prune, empty-password guard, and the handle-leak diagnostics (ProcessSelfMeter, HandleTypeProbe). Version bumped to 3.4.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fc451ba3e3
commit
dd70613017
@@ -13,7 +13,7 @@ namespace RemSound.App;
|
||||
/// </summary>
|
||||
internal static class ProfilePasswordDialog
|
||||
{
|
||||
public static string? Show(IWin32Window owner, string profileTitle, string currentPassword)
|
||||
public static string? Show(IWin32Window owner, string profileTitle, string currentPassword, bool requireNonEmpty = false)
|
||||
{
|
||||
using var dialog = new Form
|
||||
{
|
||||
@@ -45,16 +45,45 @@ internal static class ProfilePasswordDialog
|
||||
AutoSize = true,
|
||||
};
|
||||
|
||||
var okButton = new Button { Text = "OK", AutoSize = true, DialogResult = DialogResult.OK };
|
||||
var okButton = new Button { Text = "OK", AutoSize = true };
|
||||
var cancelButton = new Button { Text = "Cancel", AutoSize = true, DialogResult = DialogResult.Cancel };
|
||||
|
||||
// OK is validated by hand (no auto-close DialogResult) so we can block an empty entry when
|
||||
// a password is being REQUIRED. The trigger is the OK/Enter action, not typing — and
|
||||
// deliberately clearing an already-set password to blank is still allowed, because that
|
||||
// path passes requireNonEmpty: false. So this only catches "was asked for a password,
|
||||
// entered nothing, pressed OK", which used to silently leave audio dead.
|
||||
void TryAccept()
|
||||
{
|
||||
if (requireNonEmpty && textBox.Text.Trim().Length == 0)
|
||||
{
|
||||
var page = new TaskDialogPage
|
||||
{
|
||||
Caption = "Password required",
|
||||
Heading = "Enter a password",
|
||||
Text = "A password is required before audio can flow. You and the person you're connecting to must use the same one.",
|
||||
Icon = TaskDialogIcon.Warning,
|
||||
Buttons = { TaskDialogButton.OK },
|
||||
DefaultButton = TaskDialogButton.OK,
|
||||
AllowCancel = true,
|
||||
};
|
||||
TaskDialog.ShowDialog(dialog, page);
|
||||
textBox.Focus();
|
||||
textBox.SelectAll();
|
||||
return;
|
||||
}
|
||||
dialog.DialogResult = DialogResult.OK;
|
||||
dialog.Close();
|
||||
}
|
||||
|
||||
okButton.Click += (_, _) => TryAccept();
|
||||
textBox.KeyDown += (_, args) =>
|
||||
{
|
||||
if (args.KeyCode == Keys.Enter)
|
||||
{
|
||||
dialog.DialogResult = DialogResult.OK;
|
||||
dialog.Close();
|
||||
args.Handled = true;
|
||||
args.SuppressKeyPress = true;
|
||||
TryAccept();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user