Files
voice-cat/clients/windows/VoiceCat.App/Forms/PerUserTuningDialog.cs
Talon 63b241cc2e feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
  for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
  until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
  it atomically so the audio RT path reads without a lock

C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
  password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
  after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)

Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
  Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
  Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
  ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
  per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
  Activity log ListBox as durable screen-reader record, AutomationNotification for
  curated live announcements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00

126 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using VoiceCat.Interop;
namespace VoiceCat.App.Forms;
/// <summary>
/// Per-remote-user gain, mute, and noise reduction settings.
/// Changes are applied in real-time as the user adjusts controls — no OK/Cancel round-trip
/// for gain and mute (the close button just dismisses). The settings live in the core and
/// are not persisted across sessions.
/// </summary>
public sealed class PerUserTuningDialog : Form
{
private readonly VoiceCatClient _client;
private readonly uint _userId;
private readonly TrackBar _trkGain;
private readonly Label _lblGainValue;
private readonly CheckBox _chkMute;
private readonly CheckBox _chkNr;
public PerUserTuningDialog(VoiceCatClient client, uint userId, string nickname)
{
_client = client;
_userId = userId;
// ── Controls ──────────────────────────────────────────────────────────
var lblTitle = new Label
{
Text = $"Settings for {nickname}",
AutoSize = true,
Font = new Font(Font, FontStyle.Bold),
Location = new Point(12, 12),
TabIndex = 0,
};
var lblGainLabel = new Label
{
Text = "&Gain:",
AutoSize = true,
Location = new Point(12, 46),
TabIndex = 1,
};
_trkGain = new TrackBar
{
AccessibleName = "Gain",
AccessibleDescription = "Volume level for this user. 100 is normal (1.0×), 200 is double.",
Location = new Point(55, 38),
Size = new Size(220, 45),
Minimum = 0,
Maximum = 200,
Value = 100,
TickFrequency = 25,
SmallChange = 5,
LargeChange = 25,
TabIndex = 2,
};
_lblGainValue = new Label
{
Text = "100% (1.0×)",
AutoSize = true,
Location = new Point(280, 46),
TabIndex = 3,
};
_chkMute = new CheckBox
{
Text = "&Mute this user",
AutoSize = true,
Location = new Point(12, 92),
TabIndex = 4,
};
_chkNr = new CheckBox
{
Text = "&Noise reduction (planned — currently passthrough)",
AutoSize = true,
Location = new Point(12, 118),
TabIndex = 5,
};
var btnClose = new Button
{
Text = "&Close",
DialogResult = DialogResult.OK,
Location = new Point(296, 152),
Size = new Size(75, 27),
TabIndex = 6,
};
// ── Wire events ───────────────────────────────────────────────────────
_trkGain.Scroll += (_, _) =>
{
float gain = _trkGain.Value / 100f;
_lblGainValue.Text = $"{_trkGain.Value}% ({gain:F1}×)";
ApplySettings();
};
_chkMute.CheckedChanged += (_, _) => ApplySettings();
_chkNr.CheckedChanged += (_, _) => ApplySettings();
// ── Form ──────────────────────────────────────────────────────────────
AcceptButton = btnClose;
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(384, 192);
Controls.AddRange([lblTitle, lblGainLabel, _trkGain, _lblGainValue,
_chkMute, _chkNr, btnClose]);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Text = $"User settings — {nickname}";
}
private void ApplySettings()
{
float gain = _trkGain.Value / 100f;
bool muted = _chkMute.Checked;
bool nr = _chkNr.Checked;
// Apply to all of this user's streams
var streams = _client.ListUserStreams(_userId);
foreach (var s in streams)
_client.SetRemoteStream(_userId, s.StreamId, gain, muted, nr);
}
}