Files
voice-cat/clients/windows/VoiceCat.App/Forms/MainForm.Designer.cs
Talon a88656f2fa feat(windows): wire screen-audio sharing into the WinForms client
The core already supported SCREEN_AUDIO capture on Windows (post-M3 WASAPI
loopback via VOICECAT_HAS_LOOPBACK) and the C# Interop layer was complete
(VcStreamKind.ScreenAudio, StartStream/StopStream/SetRemoteStream). Only the
UI was missing -- no core, proto, or C ABI changes needed.

Adds a 'Share Screen Audio' toggle to the voice panel, independent of mic
voice (can share without joining voice). Disconnect/teardown now stops the
screen stream cleanly. New smoke test exercises the full StartStream ->
StreamStarted -> StopStream -> StreamStopped path through P/Invoke.
2026-06-17 22:47:28 +02:00

393 lines
17 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.

namespace VoiceCat.App.Forms;
partial class MainForm
{
private System.ComponentModel.IContainer components = null!;
// Menu bar
private MenuStrip menuStrip = null!;
// Status bar
private Label lblStatus = null!;
// Main left/right split
private SplitContainer splitMain = null!;
// Left panel: channel tree on top, user list on bottom
private SplitContainer splitLeft = null!;
private Label lblChannels = null!;
private TreeView tvChannels = null!;
private Label lblUsers = null!;
private ListBox lstUsers = null!;
// Right panel: chat transcript, compose row, activity log
private TableLayoutPanel tblRight = null!;
private Label lblChat = null!;
private RichTextBox rtbChat = null!;
private TableLayoutPanel tblCompose = null!;
private ComboBox cboScope = null!;
private TextBox txtCompose = null!;
private Button btnSend = null!;
private Label lblActivity = null!;
private ListBox lstActivity = null!;
// Voice control panel (docked Bottom)
private Panel pnlVoice = null!;
private FlowLayoutPanel flpVoiceTop = null!;
private FlowLayoutPanel flpVoiceBottom = null!;
private Button btnMicToggle = null!;
private Button btnScreenShareToggle = null!;
private CheckBox chkMute = null!;
private CheckBox chkDeafen = null!;
private RadioButton radioVad = null!;
private RadioButton radioPtt = null!;
private RadioButton radioAlwaysOn = null!;
private Label lblPttKey = null!;
private Button btnChangePtt = null!;
private Label lblInputDevice = null!;
private ComboBox cboInputDevice = null!;
private Button btnRefreshDevices = null!;
private Label lblLevel = null!;
private ProgressBar pbLevel = null!;
private Label lblVadThreshold = null!;
private TrackBar trkVadThreshold = null!;
protected override void Dispose(bool disposing)
{
if (disposing) components?.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
lblStatus = new Label();
splitMain = new SplitContainer();
splitLeft = new SplitContainer();
lblChannels = new Label();
tvChannels = new TreeView();
lblUsers = new Label();
lstUsers = new ListBox();
tblRight = new TableLayoutPanel();
lblChat = new Label();
rtbChat = new RichTextBox();
tblCompose = new TableLayoutPanel();
cboScope = new ComboBox();
txtCompose = new TextBox();
btnSend = new Button();
lblActivity = new Label();
lstActivity = new ListBox();
pnlVoice = new Panel();
flpVoiceTop = new FlowLayoutPanel();
flpVoiceBottom = new FlowLayoutPanel();
btnMicToggle = new Button();
btnScreenShareToggle = new Button();
chkMute = new CheckBox();
chkDeafen = new CheckBox();
radioVad = new RadioButton();
radioPtt = new RadioButton();
radioAlwaysOn = new RadioButton();
lblPttKey = new Label();
btnChangePtt = new Button();
lblInputDevice = new Label();
cboInputDevice = new ComboBox();
btnRefreshDevices = new Button();
lblLevel = new Label();
pbLevel = new ProgressBar();
lblVadThreshold = new Label();
trkVadThreshold = new TrackBar();
// ── Status label ──────────────────────────────────────────────────────
lblStatus.AccessibleName = "Connection status";
lblStatus.Dock = DockStyle.Top;
lblStatus.AutoSize = true;
lblStatus.Padding = new Padding(6, 4, 6, 4);
lblStatus.TabIndex = 0;
lblStatus.Text = "Connecting...";
// ── Channel tree ──────────────────────────────────────────────────────
lblChannels.Text = "Channels:";
lblChannels.Dock = DockStyle.Top;
lblChannels.AutoSize = true;
lblChannels.Padding = new Padding(4, 4, 4, 2);
tvChannels.AccessibleName = "Channel list";
tvChannels.AccessibleDescription =
"Double-click or press Enter to join a channel. " +
"Channels marked [password] require a password.";
tvChannels.Dock = DockStyle.Fill;
tvChannels.HideSelection = false;
tvChannels.TabIndex = 0;
// ── User list ─────────────────────────────────────────────────────────
lblUsers.Text = "Users in channel:";
lblUsers.Dock = DockStyle.Top;
lblUsers.AutoSize = true;
lblUsers.Padding = new Padding(4, 4, 4, 2);
lstUsers.AccessibleName = "Users in current channel";
lstUsers.AccessibleDescription =
"People in the same channel. Double-click or press Enter for per-user volume settings. " +
"Talking users are marked (talking).";
lstUsers.Dock = DockStyle.Fill;
lstUsers.TabIndex = 1;
// ── Left split (channels top, users bottom) ───────────────────────────
splitLeft.Orientation = Orientation.Horizontal;
splitLeft.Dock = DockStyle.Fill;
splitLeft.Panel1MinSize = 100;
splitLeft.Panel2MinSize = 80;
splitLeft.TabIndex = 0;
splitLeft.Panel1.Controls.Add(tvChannels);
splitLeft.Panel1.Controls.Add(lblChannels);
splitLeft.Panel2.Controls.Add(lstUsers);
splitLeft.Panel2.Controls.Add(lblUsers);
// ── Chat transcript ───────────────────────────────────────────────────
lblChat.Text = "Chat:";
lblChat.AutoSize = true;
lblChat.Padding = new Padding(2, 2, 2, 1);
rtbChat.AccessibleName = "Chat transcript";
rtbChat.AccessibleDescription = "History of channel and private messages.";
rtbChat.Dock = DockStyle.Fill;
rtbChat.ReadOnly = true;
rtbChat.ScrollBars = RichTextBoxScrollBars.Vertical;
rtbChat.BackColor = SystemColors.Window;
rtbChat.TabIndex = 0;
// ── Compose row ───────────────────────────────────────────────────────
cboScope.AccessibleName = "Send to";
cboScope.AccessibleDescription =
"Choose Channel to send to everyone, or a specific user for a private message.";
cboScope.DropDownStyle = ComboBoxStyle.DropDownList;
cboScope.Dock = DockStyle.Fill;
cboScope.TabIndex = 1;
txtCompose.AccessibleName = "Message text";
txtCompose.AccessibleDescription = "Type your message. Press Enter or click Send to send.";
txtCompose.Dock = DockStyle.Fill;
txtCompose.TabIndex = 2;
btnSend.Text = "&Send";
btnSend.Dock = DockStyle.Fill;
btnSend.TabIndex = 3;
tblCompose.ColumnCount = 3;
tblCompose.RowCount = 1;
tblCompose.Dock = DockStyle.Fill;
tblCompose.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 165F));
tblCompose.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tblCompose.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 68F));
tblCompose.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tblCompose.Padding = new Padding(0, 3, 0, 0);
tblCompose.Controls.Add(cboScope, 0, 0);
tblCompose.Controls.Add(txtCompose, 1, 0);
tblCompose.Controls.Add(btnSend, 2, 0);
// ── Activity log ──────────────────────────────────────────────────────
lblActivity.Text = "Activity:";
lblActivity.AutoSize = true;
lblActivity.Padding = new Padding(2, 4, 2, 1);
lstActivity.AccessibleName = "Activity log";
lstActivity.AccessibleDescription =
"Record of joins, leaves, talk-state changes, and server messages.";
lstActivity.Dock = DockStyle.Fill;
lstActivity.HorizontalScrollbar = true;
lstActivity.TabIndex = 4;
// ── Right table layout ────────────────────────────────────────────────
tblRight.ColumnCount = 1;
tblRight.RowCount = 5;
tblRight.Dock = DockStyle.Fill;
tblRight.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tblRight.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tblRight.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tblRight.RowStyles.Add(new RowStyle(SizeType.Absolute, 34F));
tblRight.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tblRight.RowStyles.Add(new RowStyle(SizeType.Absolute, 120F));
tblRight.Controls.Add(lblChat, 0, 0);
tblRight.Controls.Add(rtbChat, 0, 1);
tblRight.Controls.Add(tblCompose, 0, 2);
tblRight.Controls.Add(lblActivity, 0, 3);
tblRight.Controls.Add(lstActivity, 0, 4);
// ── Main split ────────────────────────────────────────────────────────
// SplitterDistance set in OnLoad — see MainForm.cs.
splitMain.Dock = DockStyle.Fill;
splitMain.Panel1MinSize = 150;
splitMain.TabIndex = 1;
splitMain.Panel1.Controls.Add(splitLeft);
splitMain.Panel2.Controls.Add(tblRight);
// ── Voice control panel ───────────────────────────────────────────────
// Top row: mic start/stop, mute/deafen, VAD/PTT
btnMicToggle.Text = "&Join Voice";
btnMicToggle.AutoSize = true;
btnMicToggle.Margin = new Padding(0, 2, 6, 0);
btnMicToggle.TabIndex = 0;
// Screen-audio share — independent of mic voice (can share without joining voice and
// vice versa). The core's WASAPI loopback path (VOICECAT_HAS_LOOPBACK, always on for
// the windows-client preset) captures the default render endpoint; see docs/voice.md
// §9. Whole-device loopback inherently re-captures this app's own incoming voice mix —
// an accepted self-echo characteristic, not a bug.
btnScreenShareToggle.Text = "Share Screen &Audio";
btnScreenShareToggle.AccessibleName = "Share screen audio";
btnScreenShareToggle.AccessibleDescription =
"Start or stop sharing your computer's audio (desktop/system audio) with the channel. " +
"Independent of the microphone. Captures everything playing through your default speakers.";
btnScreenShareToggle.AutoSize = true;
btnScreenShareToggle.Margin = new Padding(0, 2, 12, 0);
btnScreenShareToggle.TabIndex = 10;
chkMute.Text = "&Mute mic";
chkMute.AutoSize = true;
chkMute.Enabled = false;
chkMute.Margin = new Padding(0, 4, 6, 0);
chkMute.TabIndex = 1;
chkDeafen.Text = "&Deafen";
chkDeafen.AutoSize = true;
chkDeafen.Enabled = false;
chkDeafen.Margin = new Padding(0, 4, 12, 0);
chkDeafen.TabIndex = 2;
var lblMode = new Label { Text = "Mode:", AutoSize = true, Margin = new Padding(0, 5, 4, 0) };
radioVad.Text = "&Voice activation";
radioVad.AutoSize = true;
radioVad.Checked = true;
radioVad.Enabled = false;
radioVad.Margin = new Padding(0, 4, 6, 0);
radioVad.TabIndex = 3;
radioPtt.Text = "&Push to talk";
radioPtt.AutoSize = true;
radioPtt.Enabled = false;
radioPtt.Margin = new Padding(0, 4, 4, 0);
radioPtt.TabIndex = 4;
radioAlwaysOn.Text = "A&lways on";
radioAlwaysOn.AutoSize = true;
radioAlwaysOn.Enabled = false;
radioAlwaysOn.Margin = new Padding(0, 4, 12, 0);
radioAlwaysOn.TabIndex = 5;
lblPttKey.Text = "(F8)";
lblPttKey.AutoSize = true;
lblPttKey.Margin = new Padding(2, 5, 4, 0);
lblPttKey.Visible = false;
btnChangePtt.Text = "Change key...";
btnChangePtt.AutoSize = true;
btnChangePtt.Margin = new Padding(0, 2, 0, 0);
btnChangePtt.Visible = false;
btnChangePtt.TabIndex = 6;
flpVoiceTop.Dock = DockStyle.Top;
flpVoiceTop.Height = 34;
flpVoiceTop.AutoSize = false;
flpVoiceTop.Padding = new Padding(4, 2, 4, 0);
flpVoiceTop.Controls.Add(btnMicToggle);
flpVoiceTop.Controls.Add(btnScreenShareToggle);
flpVoiceTop.Controls.Add(chkMute);
flpVoiceTop.Controls.Add(chkDeafen);
flpVoiceTop.Controls.Add(lblMode);
flpVoiceTop.Controls.Add(radioVad);
flpVoiceTop.Controls.Add(radioPtt);
flpVoiceTop.Controls.Add(radioAlwaysOn);
flpVoiceTop.Controls.Add(lblPttKey);
flpVoiceTop.Controls.Add(btnChangePtt);
// Bottom row: device picker + level meter
lblInputDevice.Text = "Input:";
lblInputDevice.AutoSize = true;
lblInputDevice.Margin = new Padding(0, 5, 4, 0);
cboInputDevice.AccessibleName = "Input device";
cboInputDevice.AccessibleDescription = "Select which microphone or audio device to use.";
cboInputDevice.DropDownStyle = ComboBoxStyle.DropDownList;
cboInputDevice.Width = 200;
cboInputDevice.Margin = new Padding(0, 2, 4, 0);
cboInputDevice.TabIndex = 7;
btnRefreshDevices.Text = "Re&fresh";
btnRefreshDevices.AutoSize = true;
btnRefreshDevices.Margin = new Padding(0, 2, 12, 0);
btnRefreshDevices.TabIndex = 8;
lblLevel.Text = "Level:";
lblLevel.AutoSize = true;
lblLevel.Margin = new Padding(0, 5, 4, 0);
pbLevel.AccessibleName = "Microphone level";
pbLevel.AccessibleDescription = "Current input level from the microphone.";
pbLevel.Width = 120;
pbLevel.Height = 16;
pbLevel.Maximum = 100;
pbLevel.Margin = new Padding(0, 6, 0, 0);
pbLevel.Style = ProgressBarStyle.Continuous;
pbLevel.TabStop = false; // informational, not actionable
lblVadThreshold.Text = "Sensitivity:";
lblVadThreshold.AutoSize = true;
lblVadThreshold.Margin = new Padding(12, 5, 4, 0);
lblVadThreshold.Visible = true; // shown when VAD mode active
trkVadThreshold.AccessibleName = "VAD sensitivity";
trkVadThreshold.AccessibleDescription =
"Voice detection sensitivity. Higher = more sensitive (triggers on quieter sounds). " +
"Range 1100; default 25.";
trkVadThreshold.Minimum = 1;
trkVadThreshold.Maximum = 100;
trkVadThreshold.Value = 76; // maps to ~0.024 (≈ default 0.025 threshold)
trkVadThreshold.TickFrequency = 10;
trkVadThreshold.SmallChange = 1;
trkVadThreshold.LargeChange = 10;
trkVadThreshold.Width = 120;
trkVadThreshold.Margin = new Padding(0, 2, 0, 0);
trkVadThreshold.TabIndex = 9;
trkVadThreshold.Visible = true;
flpVoiceBottom.Dock = DockStyle.Fill;
flpVoiceBottom.Padding = new Padding(4, 0, 4, 2);
flpVoiceBottom.Controls.Add(lblInputDevice);
flpVoiceBottom.Controls.Add(cboInputDevice);
flpVoiceBottom.Controls.Add(btnRefreshDevices);
flpVoiceBottom.Controls.Add(lblLevel);
flpVoiceBottom.Controls.Add(pbLevel);
flpVoiceBottom.Controls.Add(lblVadThreshold);
flpVoiceBottom.Controls.Add(trkVadThreshold);
pnlVoice.Dock = DockStyle.Bottom;
pnlVoice.Height = 68;
pnlVoice.BorderStyle = BorderStyle.FixedSingle;
pnlVoice.Padding = new Padding(0);
pnlVoice.Controls.Add(flpVoiceBottom); // Fill — added first
pnlVoice.Controls.Add(flpVoiceTop); // Top — added last
// ── Menu strip ────────────────────────────────────────────────────────
menuStrip = new MenuStrip();
menuStrip.AccessibleName = "Main menu";
menuStrip.Dock = DockStyle.Top;
menuStrip.TabIndex = 0;
MainMenuStrip = menuStrip;
// ── Form ──────────────────────────────────────────────────────────────
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(960, 680);
MinimumSize = new Size(700, 540);
KeyPreview = true; // form sees KeyDown/KeyUp before focused control (needed for PTT)
// Controls added in reverse docking priority: Fill first, then Bottom, then Top.
Controls.Add(splitMain); // DockStyle.Fill
Controls.Add(pnlVoice); // DockStyle.Bottom
Controls.Add(lblStatus); // DockStyle.Top
Controls.Add(menuStrip); // DockStyle.Top (placed at the very top)
StartPosition = FormStartPosition.CenterScreen;
Text = "VoiceCat";
}
}