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 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -375,7 +375,7 @@ ul, ol { padding-left: 1.4em; }
|
||||
<tr><td><strong>Peer details</strong></td><td>Alt+E</td><td>A 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.</td></tr>
|
||||
<tr><td><strong>Rename peer</strong></td><td>Alt+M or F2</td><td>Give the highlighted peer a friendly name of your choosing. It opens a box with the name and a <strong>Clear custom name</strong> 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.</td></tr>
|
||||
<tr><td><strong>Discovered peers</strong></td><td>Alt+D</td><td>People RemSound has heard from in the last few seconds. Tick someone to connect to them.</td></tr>
|
||||
<tr><td><strong>Remembered peers</strong></td><td>Alt+R</td><td>People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect.</td></tr>
|
||||
<tr><td><strong>Remembered peers</strong></td><td>Alt+R</td><td>People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect. Press <strong>Delete</strong> on an entry to forget it.</td></tr>
|
||||
<tr><td><strong>Add peer by IP</strong></td><td>Alt+A</td><td>Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects.</td></tr>
|
||||
<tr><td><strong>Lock to these exact peer addresses</strong></td><td>Alt+L</td><td>When 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 <a href="#lock-addresses">Locking a profile to one exact address</a>.</td></tr>
|
||||
<tr><td><strong>Connection status</strong></td><td>Alt+S</td><td>A 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.</td></tr>
|
||||
@@ -466,7 +466,7 @@ ul, ol { padding-left: 1.4em; }
|
||||
<table>
|
||||
<tr><th>List</th><th>What it holds</th></tr>
|
||||
<tr><td><strong>Currently active applications (Alt+8)</strong></td><td>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 <em>(not running)</em>, so you can always find it and untick it; it starts being sent again the instant it reopens.</td></tr>
|
||||
<tr><td><strong>Remembered applications (Alt+9)</strong></td><td>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.</td></tr>
|
||||
<tr><td><strong>Remembered applications (Alt+9)</strong></td><td>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 <strong>Delete</strong> on an entry to forget it, just like the remembered peers list.</td></tr>
|
||||
</table>
|
||||
|
||||
<p>Because sending is by program <em>name</em>, 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 <em>Send whole audio devices</em> is for.</p>
|
||||
@@ -1411,7 +1411,7 @@ RemSound.exe --connect 192.168.1.42
|
||||
<h3>Setting it up</h3>
|
||||
<p>Everything lives in the <strong>Service</strong> menu on the menu bar:</p>
|
||||
<ol>
|
||||
<li><strong>Configure service profile…</strong> — 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 <strong>Use Windows default audio device, follows Windows changes</strong> — 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 <strong>Additional options</strong> button lets you turn the connect/disconnect sounds and the service's own log on or off.</li>
|
||||
<li><strong>Configure service profile…</strong> — 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 <strong>Use Windows default audio device, follows Windows changes</strong> — 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 <strong>Additional options</strong> 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.)</li>
|
||||
<li><strong>Install service</strong> — 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 <strong>start it now</strong> (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.)</li>
|
||||
<li><strong>Start service</strong> / <strong>Stop service</strong> — run or halt it now without waiting for a reboot.</li>
|
||||
<li><strong>Uninstall service</strong> — removes it entirely.</li>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/// <summary>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.</summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>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;
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user