Ed asked back on 2026-07-16 to drop "send all applications" from the app AND the
service - applications mode = pick specific apps only. The main app was done; the
service kept it. Worse, a code comment had pinned it in place as a DELIBERATE
divergence ("the headless service streams system audio, so send-all stays"), which
is exactly why the earlier removal skipped the service. Standing rule bit us: the
service silently diverged from the main app.
Removed across the whole service path, not just the checkbox:
- ServiceProfileDialog: dropped the "Send all applications" AccessibleCheckBox, its
event wiring and visibility branch; applications mode now always shows the specific-
apps list. Renumbered the tab's Alt hints (apps Alt+3, inputs Alt+4) now the box is
gone. Save forces SendAllApplications=false so a stale profile can't resurrect it.
- ServiceSendHost.BuildSendSpecs: deleted the SendAllApplications branch (and the now-
dead ResolveDefaultRenderId helper). Apps mode builds one process-loopback spec per
ticked app - identical to the main app.
- Profile.SendAllApplications: marked vestigial (kept only so old profiles deserialize;
nothing reads it now).
Guard so it can't drift again: the Service-sender-parity self-test now asserts apps
mode produces only per-application specs and NEVER a whole-system loopback spec, even
with SendAllApplications=true. (The old parity test only exercised devices mode, which
is why it missed this.)
Service UI audit (Ed's question): the whole service UI is house-controls throughout -
QuietTabControl, AccessibleCheckBox, MnemonicLabel, Theme.SectionHeader/AppIcon,
FormLayoutRows, CheckedListAccessibility. No raw CheckBox/TabControl (which are silent
to NVDA on .NET 10) anywhere in Service*.cs.
Gate 43/43.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ed, 2026-07-17. The bigger half of the service crackle fix: raw PCM sounded hideous;
Opus at the live-jamming frame with Small packets and lock-to-clock sounds right.
Lock to audio clock (TightLatencyMode) is now ALWAYS ON and no longer a user option:
- Removed the checkbox from the main window and the service dialog. Nobody ever runs
with it off -- off just adds delay.
- Main app forces the sender tight at startup; Profile.TightLatencyMode defaults true
and is kept only for file compatibility. Load/SaveTightLatencyMode accessors removed.
Service audio profile is now fixed and unconfigurable:
- Removed the "Audio profile" tab from the service config dialog entirely (two tabs
left: Connectivity, Audio send).
- The service FORCES Opus + 2.5 ms frame (120 samples) + Small packets + lock-to-clock
at runtime in ServiceSendHost.ApplyProfile, ignoring whatever the profile carries, so
a stale/hand-edited profile can never put it back on a bad codec. SaveToProfile writes
the same fixed values.
Robustness: ApplyProfile now swallows a capture Start() failure (with lock-to-clock
always on the WASAPI lane is push-mode, which opens the device synchronously and throws
if it is invalid/gone) -- presence stays up and the device-watcher/self-heal re-open when
a device is available, instead of the exception crashing the service loop.
Self-test "Service sender parity" rewritten to prove the service overrides a PCM profile
to Opus/120. Gate: 39/39.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-app capture (the "foobar alone = no sound" report):
- ProcessLoopbackCapture: take ActivateAudioInterfaceAsync's out operation as a raw
IntPtr (released after completion) instead of a typed interface. The eager RCW cast
of the not-yet-realised operation object threw InvalidCastException / E_NOINTERFACE
and killed EVERY specific-app capture at start.
- CompositeCaptureBackend: single IsPushEligible authority shared by Start,
UpdateSources and the coalesced rebuild. The update paths were missing the
ProcessLoopback exclusion, so switching whole-device -> one app kept the push
backend and fed it "proc:<pid>" (GetDevice ArgumentException).
- PushModeWasapiBackend: loud backstop rejecting process-loopback specs.
- Self-test: lifecycle test now FAILS on an activation error (it previously passed
green with the feature completely dead) + pure routing-rule checks.
Apps-mode UI (Ed 2026-07-16): the "Send all applications" checkbox is GONE from the
main window - applications mode always means picking specific apps; whole-system
audio is devices mode's job. Active list = running apps + any ticked app that is not
running ("(not running)" so it can be unticked); Remembered list = global address
book minus whatever is ticked. In-place list reconcile (no Clear+rebuild) kills the
NVDA double-read of the toggled row. Profile.SendAllApplications stays for the
SERVICE (deliberate divergence - a headless lock-screen sender wants system audio).
Remembered lists now genuinely machine-wide (AppConfig-backed): the settings store is
an intra-process cache, so remembered applications were forgotten on every exit and
remembered peers were per-profile in practice. Both books moved to AppConfig; legacy
per-profile peers are unioned in on profile load; profile save snapshots the global
book back for old-build compat. Cross-instance persistence pinned by self-test.
Service (issue #23): 15s capture pulse (callbacks/bytes/pre-encode peak/frames sent)
while sending - distinguishes "endpoint mix is genuinely silent at the lock screen"
from a pipeline fault, which callbacks alone cannot.
Gate: 38/38.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local checkpoint - NOT for public release. Ed: the service profile must never be
reachable except through the Service menu.
Also fixes a real bug: the service runs as SYSTEM, whose per-user data folder is NOT the
interactive user's - so a profile saved in the user's profiles folder (or AppConfig, both
per-user) was invisible to the service. It would have idled, never streaming.
- New RemSound.Core.ServiceStore: the service profile + its settings (logging) live in a
MACHINE-WIDE ProgramData\RemSound\service location - same absolute path for the user
(config dialog) and SYSTEM (service). Moved ServiceProfileName/ServiceLoggingEnabled off
AppConfig (per-user) onto this store.
- ServiceSendHost.FromConfig + RemSoundService now read ServiceStore; ConfigureServiceProfile
saves there (and migrates + deletes any profile left in the old user-folder location).
- Because it's no longer in the user's profiles folder, it can't appear in the startup
picker, File->Open, Recent profiles, or the password manager (all of which read the user
ProfileStore); the reserved-title filter in ListProfileTitles stays as belt-and-braces.
- Password button renamed "Set service profile password".
- New self-test "Service profile isolation": store is under ProgramData, the reserved title
is filtered from the listing, and it round-trips through the machine-wide store.
Gate 27/27.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local checkpoint - NOT for public release. Ed: the connectivity box was totally
different from the main window's - I'd rolled a bespoke listbox + textbox + buttons
instead of copying the real tab.
Now built like the main window's Connectivity tab:
- Theme.SectionHeader("Peers") header.
- Peers list is a wired CheckedListBox ("Peers to send to", Alt+C) with the shared
CheckedListAccessibility so it announces state like every other list; a ticked peer
is one the service sends to, Delete removes it.
- "Add peer by IP (Alt+A)" button uses the SAME ManualPeerPrompt the main window uses,
not an inline textbox.
- Set password button kept.
Checked peers persist as SelectedConnectedPeers (the send-to set); every listed peer is
kept as RememberedPeers. Gate 25/25.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local checkpoint - NOT for public release. Ed flagged the dialog as sloppy - lists
not announcing, wrong tab order, leftover Alt+4/5/6, "Standard/Tight" instead of the
real labels, and not looking like the main window.
- Screen-reader parity: new CheckedListAccessibility.Wire (factored from the main
window's WireCheckedListAccessibility) drives every checked list - announces
"checked, <item>. Item N of M. Press Space to toggle." on focus and arrow, plus
first-letter nav that never toggles. This was the core miss (lists not announcing).
- Layout parity: uses the house FormLayoutRows rows + status labels, QuietTabControl,
AccessibleCheckBox, MnemonicLabel, and the app icon - so it reads/looks like a real tab.
- Tab order now mirrors the main window: Connectivity, then Audio send, then Audio profile.
- Alt keys renumbered for the dialog (were lifted verbatim from the I/O tab): send tab
1-5, connectivity 1-2, profile keeps the main window's C/P/D.
- Exact main-window labels copied verbatim: codec ("PCM 48K 24 bit - uncompressed", etc.),
packet size ("Standard (5 ms PCM, 10/20 ms Opus)" / "Small (2.5 ms ...)"), and the
lock-to-audio-clock text + accessible description.
Gate 25/25 (dialog passes the accessibility audit: names + unique mnemonics + tab order).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local checkpoint - NOT for public release.
- ServiceProfileDialog: modal 3-tab editor (Audio send / Audio profile / Connectivity)
reusing the house controls, with a Save and Close / Cancel / Additional options
button row. Send-only: send-mode chooser + WASAPI outputs/apps + inputs (no "Send
my audio" toggle, no receive, no ASIO); codec/rate/lock-to-clock; peers add/remove +
a Set password button. Additional options sub-dialog = play connect/disconnect sound
+ enable service logging. Edits a Profile clone; returns it on OK.
- Service menu in MainForm: status line + Configure / Install / Uninstall / Start / Stop,
items enabled per live state on drop-down. Install/uninstall confirm then elevate.
Configure saves the reserved service profile, points AppConfig at it, stores the
machine-wide logging choice, and restarts a running service to pick up edits.
- ProfileStore.ReservedServiceProfileTitle ("RemSound service"): the service profile
lives with normal profiles (so the service Loads it) but ListProfileTitles hides it
from every picker. ServiceControl reuses that single constant.
- Self-test: the service dialog is now in the accessibility audit (constructs cleanly,
unique mnemonics, all controls named). Gate 20/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>