From b010525bced02a748f767b639eac24619fd8a076 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:47:38 +0100 Subject: [PATCH] Review bug batch: Delete key on remembered apps (#26), phantom service cues, suppress-flag guards - Remembered applications list now supports the Delete key (issue #26): deleting an entry forgets it machine-wide, mirroring the remembered-peers list exactly (same focus-next-item behaviour). Logic lives in RemoveRememberedApplication so the self-test drives the real path; the send-app semantics test now covers deletion. Manual documents Delete on BOTH remembered lists (neither was documented). - Service dialog's Additional options no longer offers connect/disconnect cue checkboxes: the headless service never plays cues (nothing in it touches CuePlayer, and a logged-out session couldn't render them), so the switches did nothing. Manual sentence updated to match. - Three re-entrancy suppress flags (send-mode reset x2, streaming password gate) now clear in try/finally - a throw can no longer leave list events suppressed for the rest of the session. Gate 47/47. Co-Authored-By: Claude Fable 5 --- readme.html | 6 ++-- src/RemSound.App/MainForm.cs | 35 +++++++++++++++++++++--- src/RemSound.App/SelfTest.cs | 11 +++++++- src/RemSound.App/ServiceProfileDialog.cs | 19 ++++++++----- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/readme.html b/readme.html index f8d74af..80fa9a8 100644 --- a/readme.html +++ b/readme.html @@ -375,7 +375,7 @@ ul, ol { padding-left: 1.4em; } Peer detailsAlt+EA read-only box describing whichever connected peer you're on in the list above. Arrow through it to read: their name, their machine name, their IP address, how long you've been connected, the link health and ping, what they're sending (how many devices, on WASAPI or ASIO, at what sample rate and codec), and whether they're receiving your audio. The device and WASAPI/ASIO detail only shows while you're actually receiving that peer. Rename peerAlt+M or F2Give the highlighted peer a friendly name of your choosing. It opens a box with the name and a Clear custom name button. The name sticks to that machine for good — across restarts, IP changes and networks — and shows everywhere that peer appears: the lists here, the volume, pan and EQ for peers tab, the status line and split-recording filenames. See below. Discovered peersAlt+DPeople RemSound has heard from in the last few seconds. Tick someone to connect to them. -Remembered peersAlt+RPeople you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect. +Remembered peersAlt+RPeople you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect. Press Delete on an entry to forget it. Add peer by IPAlt+AOpens a small box where you type an address or computer name. It adds that peer to the remembered list and connects. Lock to these exact peer addressesAlt+LWhen ticked, this profile uses only the exact addresses you set and never follows the other computer by name or switches to a different address — even if the address stops working. Off by default, saved with the profile. See Locking a profile to one exact address. Connection statusAlt+SA read-only box of text that sums up everything happening right now — how long you've been connected, how many peers you have, how much sound is flowing each way, and the connection health of each peer. Open it to read the current connection status. @@ -466,7 +466,7 @@ ul, ol { padding-left: 1.4em; } - +
ListWhat it holds
Currently active applications (Alt+8)Every program making sound right now. Tick one and only that program's audio is captured and sent — its own private stream, separate from everything else on the machine. Tick several to send several. A program you've ticked that isn't running at the moment still shows here marked (not running), so you can always find it and untick it; it starts being sent again the instant it reopens.
Remembered applications (Alt+9)Your saved “apps I send” address book — shared across all your profiles, like the remembered peers list. Tick a program here and it moves up to the active list the moment it's running (and is captured from its very first sound). Untick a program in either list and it drops back here.
Remembered applications (Alt+9)Your saved “apps I send” address book — shared across all your profiles, like the remembered peers list. Tick a program here and it moves up to the active list the moment it's running (and is captured from its very first sound). Untick a program in either list and it drops back here. Press Delete on an entry to forget it, just like the remembered peers list.

Because sending is by program name, your choice survives that program being closed and reopened, or even the computer restarting. There is deliberately no “send everything” option in applications mode — if you want the whole machine's sound, that's what Send whole audio devices is for.

@@ -1411,7 +1411,7 @@ RemSound.exe --connect 192.168.1.42

Setting it up

Everything lives in the Service menu on the menu bar:

    -
  1. Configure service profile… — opens a small window with two tabs (Connectivity and Audio send) where you choose who to send to (plus a password) and what to send. On the Audio send tab, the first output choice is Use Windows default audio device, follows Windows changes — tick that to send whatever this machine is currently playing and keep following the Windows default if it later changes, rather than pinning one named card. (You can still pick specific devices, or a specific application, exactly as in the normal app.) There is no “send my audio” switch because the service always sends, and there is no audio-quality tab to fiddle with: the service always uses the settings that work best for live streaming (the Opus live-latency codec, small packets, locked to the audio clock), so it just sounds right. This is a separate profile from your normal ones and does not appear in the usual profile list. The Additional options button lets you turn the connect/disconnect sounds and the service's own log on or off.
  2. +
  3. Configure service profile… — opens a small window with two tabs (Connectivity and Audio send) where you choose who to send to (plus a password) and what to send. On the Audio send tab, the first output choice is Use Windows default audio device, follows Windows changes — tick that to send whatever this machine is currently playing and keep following the Windows default if it later changes, rather than pinning one named card. (You can still pick specific devices, or a specific application, exactly as in the normal app.) There is no “send my audio” switch because the service always sends, and there is no audio-quality tab to fiddle with: the service always uses the settings that work best for live streaming (the Opus live-latency codec, small packets, locked to the audio clock), so it just sounds right. This is a separate profile from your normal ones and does not appear in the usual profile list. The Additional options button lets you turn the service's own log on or off. (The service itself never plays sounds — it streams silently in the background — so there are no cue options here.)
  4. Install service — registers it with Windows so it starts automatically at every boot. Windows asks for administrator permission (one prompt). Do this once. Straight after installing, RemSound asks whether you'd like to start it now (otherwise it waits until the next reboot). (When you first install RemSound on a PC, the app installer also offers to set the service up — and start it — for you, so you may have done this already.)
  5. Start service / Stop service — run or halt it now without waiting for a reboot.
  6. Uninstall service — removes it entirely.
  7. diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs index 1b02a5a..1932de1 100644 --- a/src/RemSound.App/MainForm.cs +++ b/src/RemSound.App/MainForm.cs @@ -3706,6 +3706,21 @@ public sealed class MainForm : Form WireAppList(sendAppsList); WireAppList(rememberedAppsList); + // Delete on a remembered application forgets it — the same affordance the remembered-peers list + // has had all along (issue #26; the two remembered lists should feel identical). Items in this + // list are by definition not ticked (ticked apps show in the Active list), so deleting one only + // edits the machine-wide remembered set; no capture change is implied. + rememberedAppsList.KeyDown += (_, args) => + { + if (args.KeyCode != Keys.Delete) return; + args.Handled = true; + args.SuppressKeyPress = true; + if (rememberedAppsList.SelectedItem is not AudioAppChoice choice) return; + var prevIndex = rememberedAppsList.SelectedIndex; + RemoveRememberedApplication(choice.ProcessName); + FocusListItemAfterDelete(rememberedAppsList, prevIndex); + }; + // Reconcile the app list on a slow timer so entries appear/disappear as apps open and close, // without ever piling up (each pass releases every session object — see AudioAppEnumerator). // Only ticks while the applications list is actually visible. @@ -3742,8 +3757,8 @@ public sealed class MainForm : Form if (!supported && sendModeList.Items.Count > 0 && sendModeList.SelectedIndex != SendModeDevicesIndex) { suppressSendAppEvents = true; - sendModeList.SelectedIndex = SendModeDevicesIndex; - suppressSendAppEvents = false; + try { sendModeList.SelectedIndex = SendModeDevicesIndex; } + finally { suppressSendAppEvents = false; } // a throw must not leave events suppressed for good } var appsMode = supported && sendModeList.SelectedIndex == SendModeApplicationsIndex; @@ -3842,6 +3857,18 @@ public sealed class MainForm : Form UpdateCheckedListStatus(rememberedAppsList, rememberedAppsStatusLabel, "remembered application"); } + /// Forget one app from the machine-wide remembered-applications list and re-render both app + /// lists. Backs the Delete key on the remembered list (issue #26) — the peers list has the same + /// affordance. Internal so the self-test can drive the real removal path headlessly. + internal void RemoveRememberedApplication(string processName) + { + var remaining = settings.LoadRememberedApplications() + .Where(n => !string.Equals(n, processName, StringComparison.OrdinalIgnoreCase)); + settings.SaveRememberedApplications(remaining); + logFile.Event($"ui: remembered application '{processName}' deleted from the remembered list"); + ReconcileSendAppsList(); + } + /// Test seam: reconcile now and return the two send-app lists' rows — the active list's /// process names, which of them are ticked, and the remembered list's process names. Lets the /// self-test pin the list semantics (ticked apps leave Remembered; a ticked app that isn't running @@ -8682,8 +8709,8 @@ public sealed class MainForm : Form { // No password → can't stream. Put the box back without re-firing this gate. suppressStreamingPasswordGate = true; - box.Checked = false; - suppressStreamingPasswordGate = false; + try { box.Checked = false; } + finally { suppressStreamingPasswordGate = false; } // a throw must not disable the gate for good return false; } currentProfilePassword = entered; diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs index 718c825..0becffe 100644 --- a/src/RemSound.App/SelfTest.cs +++ b/src/RemSound.App/SelfTest.cs @@ -1957,8 +1957,17 @@ internal static class SelfTest "a ticked app must NOT appear in the Remembered list"); Check(rememberedRows.Contains(unticked, StringComparer.OrdinalIgnoreCase), "an unticked remembered app must stay in the Remembered list"); + + // Delete on the Remembered list (issue #26): removing forgets the app machine-wide and + // the list re-renders without it. Drives the real removal path the Delete key uses. + mf.RemoveRememberedApplication(unticked); + var (_, _, afterDelete) = mf.SnapshotAppListsForTest(); + Check(!afterDelete.Contains(unticked, StringComparer.OrdinalIgnoreCase), + "a deleted remembered app must leave the Remembered list"); + Check(!store.LoadRememberedApplications().Contains(unticked, StringComparer.OrdinalIgnoreCase), + "a deleted remembered app must leave the machine-wide remembered set"); } - return "ticked app: in Active (not running) + out of Remembered; unticked app: stays in Remembered"; + return "ticked app: in Active + out of Remembered; unticked stays; Delete forgets machine-wide"; } finally { store.SaveRememberedApplications(original); } } diff --git a/src/RemSound.App/ServiceProfileDialog.cs b/src/RemSound.App/ServiceProfileDialog.cs index 5b97830..06ffe0d 100644 --- a/src/RemSound.App/ServiceProfileDialog.cs +++ b/src/RemSound.App/ServiceProfileDialog.cs @@ -300,7 +300,12 @@ internal sealed class ServiceProfileDialog : Form var supported = ProcessLoopbackCapture.IsSupported; if (sendModeLabel is not null) sendModeLabel.Visible = supported; SetRowVisible(sendModeList, supported); - if (!supported && sendModeList.SelectedIndex != 0) { suppressAppEvents = true; sendModeList.SelectedIndex = 0; suppressAppEvents = false; } + if (!supported && sendModeList.SelectedIndex != 0) + { + suppressAppEvents = true; + try { sendModeList.SelectedIndex = 0; } + finally { suppressAppEvents = false; } // a throw must not leave events suppressed for good + } var appsMode = supported && sendModeList.SelectedIndex == 1; if (outputsLabel is not null) outputsLabel.Visible = !appsMode; @@ -338,23 +343,23 @@ internal sealed class ServiceProfileDialog : Form MaximizeBox = false, ShowInTaskbar = false, StartPosition = FormStartPosition.CenterParent, - ClientSize = new Size(460, 200), + ClientSize = new Size(460, 110), AccessibleName = "Additional service options", }; - var connect = new AccessibleCheckBox { Text = "Play a sound when a peer &connects (Alt+C)", AccessibleName = "Play connect sound", AutoSize = true, Checked = working.EnableConnectCue ?? true }; - var disconnect = new AccessibleCheckBox { Text = "Play a sound when a peer &disconnects (Alt+D)", AccessibleName = "Play disconnect sound", AutoSize = true, Checked = working.EnableDisconnectCue ?? true }; + // No connect/disconnect cue checkboxes here (removed 2026-07-19, review sweep): the dialog used + // to offer them, but the headless service NEVER plays cues — nothing in the service host touches + // CuePlayer, and a logged-out session couldn't render them anyway. Offering a switch that does + // nothing is worse than not offering it. The cue fields stay on Profile for the app's own use. var logging = new AccessibleCheckBox { Text = "Enable service &logging (Alt+L)", AccessibleName = "Enable service logging", AutoSize = true, Checked = ServiceLoggingEnabled }; var ok = new Button { Text = "&OK", AutoSize = true, DialogResult = DialogResult.OK }; var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, Padding = new Padding(12), AutoSize = true }; - foreach (var c in new Control[] { connect, disconnect, logging, ok }) { var w = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill }; w.Controls.Add(c); layout.Controls.Add(w); } + foreach (var c in new Control[] { logging, ok }) { var w = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill }; w.Controls.Add(c); layout.Controls.Add(w); } dlg.Controls.Add(layout); dlg.AcceptButton = ok; if (ForegroundDialog.Show(owner => dlg.ShowDialog(owner)) == DialogResult.OK) { - working.EnableConnectCue = connect.Checked; - working.EnableDisconnectCue = disconnect.Checked; ServiceLoggingEnabled = logging.Checked; } }