diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs
index bd5e257..df8c9d2 100644
--- a/src/RemSound.App/SelfTest.cs
+++ b/src/RemSound.App/SelfTest.cs
@@ -914,7 +914,22 @@ internal static class SelfTest
$"the service must FORCE Opus regardless of the profile codec (got {cfg.Codec})");
Check(cfg.Frame == 120,
$"the service must force the 2.5 ms live Opus frame (120 samples), regardless of the profile (got {cfg.Frame})");
- return "service forces Opus + 2.5ms frame + lock-to-clock regardless of profile; crypto matches";
+
+ // Applications-mode parity with the main app: specific apps only. Even with the legacy
+ // SendAllApplications flag set true, the service must NOT emit a whole-system "all applications"
+ // loopback spec — it must build one process-loopback spec per ticked app. (Guards the drift where
+ // the service kept the removed "send all applications" checkbox + code path — Ed, 2026-07-17.)
+ if (RemSound.Sender.ProcessLoopbackCapture.IsSupported)
+ {
+ var appsProfile = new Profile { WasapiSendMode = "applications", SendAllApplications = true };
+ appsProfile.SelectedSendApplications.Add("nonexistent-proc-for-test");
+ var appSpecs = ServiceSendHost.BuildSendSpecs(appsProfile);
+ Check(!appSpecs.Any(s => s.Kind == CaptureKind.Loopback),
+ "applications mode must NOT produce a whole-system loopback spec, even with SendAllApplications=true");
+ Check(appSpecs.All(s => s.Kind is CaptureKind.ProcessLoopback or CaptureKind.Input),
+ "applications mode must build only per-application (or input) specs — the 'send all' path is gone");
+ }
+ return "service forces Opus + 2.5ms frame + lock-to-clock; apps mode is specific-apps-only; crypto matches";
}
/// The lock-screen service's app-yield token: while a hold is active the service must see an
diff --git a/src/RemSound.App/ServiceProfileDialog.cs b/src/RemSound.App/ServiceProfileDialog.cs
index 2fa2ead..b84bab1 100644
--- a/src/RemSound.App/ServiceProfileDialog.cs
+++ b/src/RemSound.App/ServiceProfileDialog.cs
@@ -28,13 +28,9 @@ internal sealed class ServiceProfileDialog : Form
private readonly ListBox sendModeList = new() { Width = 460, Height = 40, IntegralHeight = false, AccessibleName = "How to send WASAPI audio (Alt+1)" };
private readonly CheckedListBox outputsList = new() { CheckOnClick = true, Width = 460, Height = 110, AccessibleName = "WASAPI audio outputs to send (Alt+2)" };
private readonly Label outputsStatus = new() { AutoSize = true, Text = "No output device selected." };
- // DELIBERATE divergence from the main window (which dropped its send-all option 2026-07-16): the
- // headless service's whole point is streaming the machine's system audio from the lock screen, so
- // "send all applications" stays here as the sensible default.
- private readonly AccessibleCheckBox sendAllAppsBox = new() { Text = "Send all applications (Alt+&3)", AccessibleName = "Send all applications", AutoSize = true, Checked = true };
- private readonly CheckedListBox appsList = new() { CheckOnClick = true, Width = 460, Height = 110, AccessibleName = "Applications to send (Alt+4)" };
+ private readonly CheckedListBox appsList = new() { CheckOnClick = true, Width = 460, Height = 110, AccessibleName = "Applications to send (Alt+3)" };
private readonly Label appsStatus = new() { AutoSize = true, Text = "No application selected." };
- private readonly CheckedListBox inputsList = new() { CheckOnClick = true, Width = 460, Height = 90, AccessibleName = "WASAPI audio inputs to send (Alt+5)" };
+ private readonly CheckedListBox inputsList = new() { CheckOnClick = true, Width = 460, Height = 90, AccessibleName = "WASAPI audio inputs to send (Alt+4)" };
private readonly Label inputsStatus = new() { AutoSize = true, Text = "No input device selected." };
private MnemonicLabel? sendModeLabel, outputsLabel, appsLabel, inputsLabel;
@@ -154,15 +150,11 @@ internal sealed class ServiceProfileDialog : Form
sendModeList.Items.Add("Send specific applications");
sendModeList.SelectedIndex = 0;
sendModeList.SelectedIndexChanged += (_, _) => { if (!suppressAppEvents) ApplySendModeVisibility(); };
- sendAllAppsBox.CheckedChanged += (_, _) => { if (!suppressAppEvents) ApplySendModeVisibility(); };
sendModeLabel = AddListRow(panel, 0, "How to send WASAPI audio (Alt+&1)", sendModeList);
outputsLabel = FormLayoutRows.AddCheckedListRow(panel, 1, "WASAPI audio outputs to send (Alt+&2)", outputsList, outputsStatus, l => l.Focus());
- var allAppsWrap = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill };
- allAppsWrap.Controls.Add(sendAllAppsBox);
- panel.Controls.Add(allAppsWrap, 1, 2);
- appsLabel = FormLayoutRows.AddCheckedListRow(panel, 3, "Applications to send (Alt+&4)", appsList, appsStatus, l => l.Focus());
- inputsLabel = FormLayoutRows.AddCheckedListRow(panel, 4, "WASAPI audio inputs to send (Alt+&5)", inputsList, inputsStatus, l => l.Focus());
+ appsLabel = FormLayoutRows.AddCheckedListRow(panel, 2, "Applications to send (Alt+&3)", appsList, appsStatus, l => l.Focus());
+ inputsLabel = FormLayoutRows.AddCheckedListRow(panel, 3, "WASAPI audio inputs to send (Alt+&4)", inputsList, inputsStatus, l => l.Focus());
CheckedListAccessibility.Wire(outputsList, outputsStatus, "output device");
CheckedListAccessibility.Wire(appsList, appsStatus, "application");
@@ -211,7 +203,6 @@ internal sealed class ServiceProfileDialog : Form
var appsMode = ProcessLoopbackCapture.IsSupported
&& string.Equals(working.WasapiSendMode, "applications", StringComparison.OrdinalIgnoreCase);
sendModeList.SelectedIndex = appsMode ? 1 : 0;
- sendAllAppsBox.Checked = working.SendAllApplications;
PopulateAppsList();
peersList.Items.Clear();
@@ -233,7 +224,10 @@ internal sealed class ServiceProfileDialog : Form
working.SelectedWasapiSendOutputs = CheckedIds(outputsList);
working.SelectedWasapiSendInputs = CheckedIds(inputsList);
working.WasapiSendMode = sendModeList.SelectedIndex == 1 ? "applications" : "devices";
- working.SendAllApplications = sendAllAppsBox.Checked;
+ // Applications mode = pick specific apps only (matches the main app; the "send all applications"
+ // option was removed from both). Force the legacy flag off so a stale profile that still carries
+ // SendAllApplications=true can't make the service quietly capture the whole system.
+ working.SendAllApplications = false;
working.SelectedSendApplications = appsList.CheckedItems.OfType().Select(a => a.ProcessName).Distinct().ToList();
// Audio profile is fixed for the service (no tab): Opus live-jamming frame, Small packets, locked
@@ -289,10 +283,9 @@ internal sealed class ServiceProfileDialog : Form
var appsMode = supported && sendModeList.SelectedIndex == 1;
if (outputsLabel is not null) outputsLabel.Visible = !appsMode;
SetRowVisible(outputsList, !appsMode);
- SetRowVisible(sendAllAppsBox, appsMode);
- var showApps = appsMode && !sendAllAppsBox.Checked;
- if (appsLabel is not null) appsLabel.Visible = showApps;
- SetRowVisible(appsList, showApps);
+ // Applications mode always shows the specific-apps list (no "send all" option any more).
+ if (appsLabel is not null) appsLabel.Visible = appsMode;
+ SetRowVisible(appsList, appsMode);
}
private static void SetRowVisible(Control c, bool visible)
diff --git a/src/RemSound.App/ServiceSendHost.cs b/src/RemSound.App/ServiceSendHost.cs
index 1639955..2fb9e17 100644
--- a/src/RemSound.App/ServiceSendHost.cs
+++ b/src/RemSound.App/ServiceSendHost.cs
@@ -573,17 +573,12 @@ public sealed class ServiceSendHost : IDisposable
&& string.Equals(p.WasapiSendMode, "applications", StringComparison.OrdinalIgnoreCase);
if (appsMode)
{
- if (p.SendAllApplications)
- {
- var def = ResolveDefaultRenderId();
- if (def is not null) specs.Add(new CaptureSourceSpec(def, CaptureKind.Loopback, "All applications (system audio)"));
- }
- else
- {
- foreach (var name in p.SelectedSendApplications.Distinct(StringComparer.OrdinalIgnoreCase))
- foreach (var pid in AudioAppEnumerator.PidsForProcessName(name))
- specs.Add(new CaptureSourceSpec(ProcessLoopbackId.Format(pid), CaptureKind.ProcessLoopback, name));
- }
+ // Specific applications only — matches the main app. The old "send all applications"
+ // path (SendAllApplications) was removed from both the app and the service; the profile
+ // flag is ignored here so a stale profile can't resurrect whole-system capture.
+ foreach (var name in p.SelectedSendApplications.Distinct(StringComparer.OrdinalIgnoreCase))
+ foreach (var pid in AudioAppEnumerator.PidsForProcessName(name))
+ specs.Add(new CaptureSourceSpec(ProcessLoopbackId.Format(pid), CaptureKind.ProcessLoopback, name));
}
else
{
@@ -634,18 +629,6 @@ public sealed class ServiceSendHost : IDisposable
return int.TryParse(text[(colon + 1)..], out var port) && port is >= 1 and <= 65535 ? (host, port) : (text, null);
}
- private static string? ResolveDefaultRenderId()
- {
- try
- {
- using var en = new MMDeviceEnumerator();
- if (!en.HasDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)) return null;
- using var d = en.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
- return d.ID;
- }
- catch { return null; }
- }
-
public void Dispose()
{
lock (gate)
diff --git a/src/RemSound.Core/Profile.cs b/src/RemSound.Core/Profile.cs
index ec90ccb..a1f9c9d 100644
--- a/src/RemSound.Core/Profile.cs
+++ b/src/RemSound.Core/Profile.cs
@@ -79,11 +79,11 @@ public sealed class Profile
/// SERVICE profiles only (ServiceProfileDialog / ServiceSendHost): when true (the default)
/// in "applications" send mode every app's audio is sent — i.e. exactly the same result as sending
/// the whole system's default output. When false, only the apps named in
- /// are sent. The MAIN window no longer has a "send all
- /// applications" option (removed 2026-07-16): there, applications mode always means specific ticked
- /// apps, and whole-system audio is devices mode's job. The main window never READS this to drive
- /// behaviour; it does still write the default true when it saves a profile (harmless — main-app and
- /// service profiles live in separate stores and are never the same file).
+ /// are sent. VESTIGIAL as of 2026-07-17: neither the main
+ /// window NOR the service honours this any more — applications mode always means the specific ticked
+ /// apps in both, and whole-system audio is devices mode's job. The "send all applications" option was
+ /// removed from both UIs and both send-spec builders ignore this flag. Kept only so existing profile
+ /// files still deserialize; the service now writes it false on save. Do not add new reads.
public bool SendAllApplications { get; set; } = true;
/// In "applications" send mode: the process
/// names (lower-case, no path/extension, e.g. "vlc", "firefox") whose audio to send. Tracked by