feat(windows): move audio settings to dedicated dialog, fix device ComboBox accessibility
Audio input settings (device picker, VAD/PTT mode, VAD sensitivity, mic gain, PTT key) move from the always-visible bottom panel into Settings > Audio..., matching the macOS/iOS pattern. The device ComboBox now uses DataSource + DisplayMember="Name" instead of Items.Add() with no DisplayMember — this fixes both the display bug (was showing the full DeviceInfo record ToString()) and the NVDA silence on dropdown open (DataSource binding exposes proper MSAA text per item). Changes apply live for immediate feedback; Cancel reverts. VoiceSettings gains InputDeviceId to persist the chosen device across sessions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -83,14 +83,6 @@ public partial class MainForm : Form
|
||||
// Voice controls
|
||||
chkMute.CheckedChanged += (_, _) => ApplySelfMute();
|
||||
chkDeafen.CheckedChanged += (_, _) => ApplySelfMute();
|
||||
radioVad.CheckedChanged += RadioVad_CheckedChanged;
|
||||
radioPtt.CheckedChanged += RadioPtt_CheckedChanged;
|
||||
radioAlwaysOn.CheckedChanged += RadioAlwaysOn_CheckedChanged;
|
||||
trkVadThreshold.Scroll += TrkVadThreshold_Scroll;
|
||||
trkMicGain.Scroll += TrkMicGain_Scroll;
|
||||
btnChangePtt.Click += BtnChangePtt_Click;
|
||||
btnRefreshDevices.Click += (_, _) => LoadInputDevices();
|
||||
cboInputDevice.SelectedIndexChanged += CboInputDevice_SelectedIndexChanged;
|
||||
|
||||
// PTT and global hotkeys (focus-scoped — work only while this form has focus)
|
||||
KeyDown += MainForm_KeyDown;
|
||||
@@ -105,29 +97,9 @@ public partial class MainForm : Form
|
||||
BootstrapFromServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore the saved transmission mode / VAD sensitivity / mic gain / PTT key into the UI so a
|
||||
/// relaunch keeps the user's input settings instead of resetting to the designer defaults. The
|
||||
/// values are pushed into the core when the mic stream starts (<see cref="BtnMicToggle_Click"/>).
|
||||
/// Setting the radio fires CheckedChanged, which only adjusts visibility while the mic is idle.
|
||||
/// </summary>
|
||||
private void ApplyPersistedVoiceSettings()
|
||||
{
|
||||
private void ApplyPersistedVoiceSettings() =>
|
||||
_pttKey = (Keys)_voiceSettings.PttKey;
|
||||
|
||||
trkVadThreshold.Value = Math.Clamp(_voiceSettings.VadThresholdSlider,
|
||||
trkVadThreshold.Minimum, trkVadThreshold.Maximum);
|
||||
trkMicGain.Value = Math.Clamp(_voiceSettings.MicGain,
|
||||
trkMicGain.Minimum, trkMicGain.Maximum);
|
||||
|
||||
switch ((VcInputMode)_voiceSettings.InputMode)
|
||||
{
|
||||
case VcInputMode.PushToTalk: radioPtt.Checked = true; break;
|
||||
case VcInputMode.AlwaysOn: radioAlwaysOn.Checked = true; break;
|
||||
default: radioVad.Checked = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Startup ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void BootstrapFromServer()
|
||||
@@ -157,7 +129,6 @@ public partial class MainForm : Form
|
||||
base.OnLoad(e);
|
||||
splitMain.SplitterDistance = Math.Min(220, splitMain.Width - 304);
|
||||
splitLeft.SplitterDistance = Math.Min(260, splitLeft.Height - 84);
|
||||
LoadInputDevices();
|
||||
}
|
||||
|
||||
// ── Menu / context menu builders ─────────────────────────────────────────
|
||||
@@ -195,6 +166,14 @@ public partial class MainForm : Form
|
||||
|
||||
// Settings menu — always visible
|
||||
var settingsMenu = new ToolStripMenuItem("&Settings");
|
||||
var miAudio = new ToolStripMenuItem("&Audio...");
|
||||
miAudio.Click += (_, _) =>
|
||||
{
|
||||
using var dlg = new AudioSettingsForm(_client, _voiceSettings, _micStreamId);
|
||||
dlg.ShowDialog(this);
|
||||
_pttKey = (Keys)_voiceSettings.PttKey;
|
||||
};
|
||||
settingsMenu.DropDownItems.Add(miAudio);
|
||||
var miNotifications = new ToolStripMenuItem("&Notifications...");
|
||||
miNotifications.Click += (_, _) =>
|
||||
{
|
||||
@@ -642,38 +621,6 @@ public partial class MainForm : Form
|
||||
lblStatus.Text = $"Connected as {_nickname}{suffix} — {chanName} ({count} user{(count == 1 ? "" : "s")})";
|
||||
}
|
||||
|
||||
// ── Device management ─────────────────────────────────────────────────────
|
||||
|
||||
private void LoadInputDevices()
|
||||
{
|
||||
var devices = _client.ListDevices(VcDeviceKind.Input);
|
||||
DeviceInfo? prevDevice = cboInputDevice.SelectedItem as DeviceInfo;
|
||||
|
||||
cboInputDevice.Items.Clear();
|
||||
foreach (var d in devices) cboInputDevice.Items.Add(d);
|
||||
|
||||
if (prevDevice is not null)
|
||||
{
|
||||
for (int i = 0; i < cboInputDevice.Items.Count; i++)
|
||||
{
|
||||
if (cboInputDevice.Items[i] is DeviceInfo d && d.Id == prevDevice.Id)
|
||||
{
|
||||
cboInputDevice.SelectedIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < cboInputDevice.Items.Count; i++)
|
||||
{
|
||||
if (cboInputDevice.Items[i] is DeviceInfo d && d.IsDefault)
|
||||
{
|
||||
cboInputDevice.SelectedIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cboInputDevice.Items.Count > 0) cboInputDevice.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
// ── Voice controls ────────────────────────────────────────────────────────
|
||||
|
||||
private void BtnMicToggle_Click(object? sender, EventArgs e)
|
||||
@@ -684,11 +631,13 @@ public partial class MainForm : Form
|
||||
if (result == VcResult.Ok)
|
||||
{
|
||||
_micStreamId = streamId;
|
||||
if (cboInputDevice.SelectedItem is DeviceInfo { IsDefault: false } dev)
|
||||
_client.SetInputDevice(streamId, dev.Id);
|
||||
_client.SetInputMode(CurrentInputMode());
|
||||
if (radioVad.Checked) _client.SetVadThreshold(VadThresholdFromSlider());
|
||||
_client.SetInputGain(trkMicGain.Value / 100f);
|
||||
var mode = (VcInputMode)_voiceSettings.InputMode;
|
||||
if (_voiceSettings.InputDeviceId is string devId)
|
||||
_client.SetInputDevice(streamId, devId);
|
||||
_client.SetInputMode(mode);
|
||||
if (mode == VcInputMode.VoiceActivation)
|
||||
_client.SetVadThreshold(VadThresholdFromSettings());
|
||||
_client.SetInputGain(_voiceSettings.MicGain / 100f);
|
||||
SetVoiceJoinedState(true);
|
||||
AddActivity("Joined voice — microphone active");
|
||||
_feedback.PlaySound(SoundEvent.VoiceOn);
|
||||
@@ -716,9 +665,6 @@ public partial class MainForm : Form
|
||||
_miJoinVoice.Text = joined ? "Leave &Voice" : "&Join Voice";
|
||||
chkMute.Enabled = joined;
|
||||
chkDeafen.Enabled = joined;
|
||||
radioVad.Enabled = joined;
|
||||
radioPtt.Enabled = joined;
|
||||
radioAlwaysOn.Enabled = joined;
|
||||
}
|
||||
|
||||
private void ApplySelfMute() =>
|
||||
@@ -800,104 +746,17 @@ public partial class MainForm : Form
|
||||
private void TrkOutputVolume_Scroll(object? sender, EventArgs e) =>
|
||||
_client.SetOutputVolume(trkOutputVolume.Value / 100f);
|
||||
|
||||
private void RadioVad_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (!radioVad.Checked) return;
|
||||
lblPttKey.Visible = false;
|
||||
btnChangePtt.Visible = false;
|
||||
lblVadThreshold.Visible = true;
|
||||
trkVadThreshold.Visible = true;
|
||||
SaveInputMode(VcInputMode.VoiceActivation);
|
||||
if (_micStreamId != 0)
|
||||
{
|
||||
_client.SetInputMode(VcInputMode.VoiceActivation);
|
||||
_client.SetVadThreshold(VadThresholdFromSlider());
|
||||
}
|
||||
}
|
||||
|
||||
private void RadioPtt_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (!radioPtt.Checked) return;
|
||||
lblPttKey.Text = $"({_pttKey})";
|
||||
lblPttKey.Visible = true;
|
||||
btnChangePtt.Visible = true;
|
||||
lblVadThreshold.Visible = false;
|
||||
trkVadThreshold.Visible = false;
|
||||
SaveInputMode(VcInputMode.PushToTalk);
|
||||
if (_micStreamId != 0)
|
||||
{
|
||||
_client.SetInputMode(VcInputMode.PushToTalk);
|
||||
_client.SetPushToTalk(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RadioAlwaysOn_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (!radioAlwaysOn.Checked) return;
|
||||
lblPttKey.Visible = false;
|
||||
btnChangePtt.Visible = false;
|
||||
lblVadThreshold.Visible = false;
|
||||
trkVadThreshold.Visible = false;
|
||||
SaveInputMode(VcInputMode.AlwaysOn);
|
||||
if (_micStreamId != 0) _client.SetInputMode(VcInputMode.AlwaysOn);
|
||||
}
|
||||
|
||||
private void TrkVadThreshold_Scroll(object? sender, EventArgs e)
|
||||
{
|
||||
_voiceSettings.VadThresholdSlider = trkVadThreshold.Value;
|
||||
_voiceSettings.Save();
|
||||
if (_micStreamId != 0 && radioVad.Checked)
|
||||
_client.SetVadThreshold(VadThresholdFromSlider());
|
||||
}
|
||||
|
||||
private void TrkMicGain_Scroll(object? sender, EventArgs e)
|
||||
{
|
||||
_voiceSettings.MicGain = trkMicGain.Value;
|
||||
_voiceSettings.Save();
|
||||
if (_micStreamId != 0) _client.SetInputGain(trkMicGain.Value / 100f);
|
||||
}
|
||||
|
||||
private void SaveInputMode(VcInputMode mode)
|
||||
{
|
||||
_voiceSettings.InputMode = (int)mode;
|
||||
_voiceSettings.Save();
|
||||
}
|
||||
|
||||
private float VadThresholdFromSlider() =>
|
||||
0.1f * (1f - (trkVadThreshold.Value - 1f) / 99f);
|
||||
|
||||
private VcInputMode CurrentInputMode() =>
|
||||
radioPtt.Checked ? VcInputMode.PushToTalk :
|
||||
radioAlwaysOn.Checked ? VcInputMode.AlwaysOn :
|
||||
VcInputMode.VoiceActivation;
|
||||
|
||||
private void BtnChangePtt_Click(object? sender, EventArgs e)
|
||||
{
|
||||
using var dlg = new PttKeyCaptureDialog(_pttKey);
|
||||
if (dlg.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
_pttKey = dlg.CapturedKey;
|
||||
lblPttKey.Text = $"({_pttKey})";
|
||||
_voiceSettings.PttKey = (int)_pttKey;
|
||||
_voiceSettings.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void CboInputDevice_SelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_micStreamId == 0) return;
|
||||
string? deviceId = (cboInputDevice.SelectedItem as DeviceInfo)?.Id;
|
||||
_client.SetInputDevice(_micStreamId, deviceId);
|
||||
}
|
||||
private float VadThresholdFromSettings() =>
|
||||
0.1f * (1f - (_voiceSettings.VadThresholdSlider - 1f) / 99f);
|
||||
|
||||
// ── PTT key handling (focus-scoped) ───────────────────────────────────────
|
||||
|
||||
private void MainForm_KeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!radioPtt.Checked || e.KeyCode != _pttKey || _micStreamId == 0) return;
|
||||
if ((VcInputMode)_voiceSettings.InputMode != VcInputMode.PushToTalk) return;
|
||||
if (e.KeyCode != _pttKey || _micStreamId == 0) return;
|
||||
if (ActiveControl is TextBox or RichTextBox) return;
|
||||
_client.SetPushToTalk(true);
|
||||
lblPttKey.Text = $"({_pttKey} ▶)";
|
||||
if (!_pttEngaged) // first key-down only, not auto-repeat
|
||||
{
|
||||
_pttEngaged = true;
|
||||
@@ -935,10 +794,10 @@ public partial class MainForm : Form
|
||||
|
||||
private void MainForm_KeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!radioPtt.Checked || e.KeyCode != _pttKey || _micStreamId == 0) return;
|
||||
if ((VcInputMode)_voiceSettings.InputMode != VcInputMode.PushToTalk) return;
|
||||
if (e.KeyCode != _pttKey || _micStreamId == 0) return;
|
||||
_client.SetPushToTalk(false);
|
||||
_pttEngaged = false;
|
||||
lblPttKey.Text = $"({_pttKey})";
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user