using System.Windows.Forms; using RemSound.Core; namespace RemSound.App; /// /// CheckedListBox subclass that exposes the protected RefreshItem method publicly. Used /// for the connectivity dialog's "Connected peers" list, where each row's text is updated in /// place (live RTT, codec, direction) without re-adding items — that would destroy NVDA's row /// focus on every tick. /// internal sealed class LiveCheckedListBox : CheckedListBox { public void RefreshItemPublic(int index) => RefreshItem(index); } /// /// User-facing choice that maps a friendly label to a codec + Opus frame size pair. The frame /// size is in samples-per-channel at 48 kHz (v3.0 wire-format unit — see /// ). 120 = 2.5 ms, 240 = 5 ms, 480 = 10 ms, 960 = 20 ms. /// Only meaningful when Codec == Opus; for PCM it's ignored. /// internal sealed record CodecChoice(string Label, AudioTransportCodec Codec, int OpusFrameSamples) { public override string ToString() => Label; } internal sealed record AudioDeviceChoice(string Name, string? DeviceId, CaptureKind Kind = CaptureKind.Loopback) { public override string ToString() => Name; } internal sealed record RememberedPeerItem(string Entry) { public override string ToString() => Entry; } /// /// Live per-peer status surfaced in the connectivity dialog's listbox text. Mutated in place /// each tick by MainForm.SyncAllDialogPeerLists, then ListBox.RefreshItem(i) is called on the /// containing item so the visible label updates without rebuilding the listbox (which would /// destroy NVDA focus on the row). /// internal sealed class PeerLineStatus { public bool Connected; /// True when our sender is actively pushing audio at this peer. public bool Sending; /// True when audio is arriving from this peer's IP (a fresh receiver session exists). public bool Receiving; /// Codec label like "Opus 10ms", "Opus 20ms", "PCM". Null when not connected. public string? CodecLabel; /// Round-trip ping ms from heartbeat. Null when not connected or pending. public int? RttMs; } internal sealed class PeerListItem { public PeerAnnouncement Peer { get; } public PeerLineStatus Status { get; } = new(); public PeerListItem(PeerAnnouncement peer) { Peer = peer; } /// /// Stable identity for signature-based listbox-rebuild detection. Does NOT include live /// status — that gets updated in-place via RefreshItem so NVDA focus survives tick updates. /// public string StableKey() => $"{Peer.InstanceId}:{Peer.Name}:{Peer.Address}:{Peer.AudioPort}"; public override string ToString() { // Base label: "hostname (ip)" for discovered peers, just "ip" for manual-by-IP entries // (where hostname equals the IP address). Avoids "192.168.1.95 (192.168.1.95)" duplication. var addr = Peer.Address.ToString(); var basePart = Peer.Name == addr ? addr : $"{Peer.Name} ({addr})"; if (!Status.Connected) { return basePart; } // Connected line — extra metadata after a dash. Comma-separated so NVDA reads naturally: // "Andre's PC (1.2.3.4) — connected, Opus 10ms, send and receive, 32ms" var parts = new List { "connected" }; if (Status.CodecLabel is { Length: > 0 } codec) parts.Add(codec); var direction = (Status.Sending, Status.Receiving) switch { (true, true) => "send and receive", (true, false) => "send only", (false, true) => "receive only", _ => null, }; if (direction is not null) parts.Add(direction); if (Status.RttMs is { } rtt) parts.Add($"{rtt}ms"); return $"{basePart} — {string.Join(", ", parts)}"; } }