"Use Windows default" is now exclusive: it clears + locks out the specific cards

Ed: ticking "Use Windows default audio device" should uncheck the other cards and stop
them being ticked again until the default is turned back off - in both the app and the
service. This replaces the old optional "shall I untick the others?" prompt with a hard
rule.

Behaviour (all follower lists: app received-outputs, outputs-to-send and inputs-to-send;
service outputs):
- Ticking the follower unticks every specific card in that list.
- While the follower is on, trying to tick a specific card is refused (the ItemCheck is
  vetoed straight back to unticked) - you're either following the Windows default or
  picking cards, never both.
- Unticking the follower frees the cards again.
- Enforced on load too, so a hand-edited/legacy profile carrying both comes up clean.

Shared, not duplicated: the veto + clear + follower-checked helpers live in
AudioDefaultFollower and are used by MainForm and the service dialog alike.

Removed the now-obsolete soft prompt and everything that hung off it:
- MainForm.MaybeUntickOthersForDefault + ResetDefaultAudioDevicePrompt.
- The "Reset the default audio device prompt" Options item (and its Alt+O,R).
- AppConfig.UntickOthersWhenUsingDefaultOutput/Input.
- Manual + About-box references to the prompt / reset item; the manual now documents
  the exclusive rule and lists all three follower lists (the outputs-to-send follower
  had been undocumented).

Test: "Default follower exclusivity" self-test - follower on clears + locks specific
cards, follower off frees them, unticking and the follower entry itself are never
vetoed. Gate 45/45.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-17 23:26:38 +01:00
co-authored by Claude Opus 4.8
parent 4a86a82ec8
commit 5fc1b5bd75
7 changed files with 118 additions and 107 deletions
+38
View File
@@ -65,6 +65,7 @@ internal static class SelfTest
RunStep(results, "Service app-yield token", ServiceInteractivePresence);
RunStep(results, "Service sender parity (crypto + Opus frame)", ServiceSenderParity);
RunStep(results, "Default-output follower (service follows Windows default)", DefaultOutputFollower);
RunStep(results, "Default follower exclusivity (locks out specific cards)", DefaultFollowerExclusivity);
RunStep(results, "Service profile isolation (location + hidden from pickers)", ServiceProfileIsolation);
RunStep(results, "Service send host (headless stream + yield)", ServiceSendHostStream);
RunStep(results, "Service network presence (reachable + shell teardown)", ServiceNetworkPresenceReachable);
@@ -883,6 +884,43 @@ internal static class SelfTest
/// AND the fingerprint from the password (a missing fingerprint gets the encrypted stream rejected at
/// the peer), and apply the send-rate-adjusted Opus frame (the "Small" rate halves it). Guards the
/// divergences found auditing the service against the main app.</summary>
/// <summary>Ticking a "Use Windows default" follower must be EXCLUSIVE: it clears the specific cards in
/// its list and locks them out (a check attempt is vetoed) until the follower is turned off. Unticking
/// a card, and the follower entry itself, are never vetoed. (Ed, 2026-07-17.)</summary>
private static string? DefaultFollowerExclusivity()
{
using var list = new System.Windows.Forms.CheckedListBox();
list.Items.Add(AudioDefaultFollower.LoopbackSendChoice()); // 0 = follower
list.Items.Add(new AudioDeviceChoice("Card A", "id-a", CaptureKind.Loopback)); // 1
list.Items.Add(new AudioDeviceChoice("Card B", "id-b", CaptureKind.Loopback)); // 2
// Follower OFF: a specific card may be ticked (no veto).
var offCheck = new System.Windows.Forms.ItemCheckEventArgs(1, System.Windows.Forms.CheckState.Checked, System.Windows.Forms.CheckState.Unchecked);
Check(!AudioDefaultFollower.VetoRealDeviceCheck(list, offCheck), "with the follower off, a specific card must be checkable");
Check(offCheck.NewValue == System.Windows.Forms.CheckState.Checked, "no veto must leave the pending check intact");
// Tick the follower plus both cards, then clearing must leave ONLY the follower.
list.SetItemChecked(0, true);
list.SetItemChecked(1, true);
list.SetItemChecked(2, true);
Check(AudioDefaultFollower.IsFollowerChecked(list), "the follower must read as checked");
Check(AudioDefaultFollower.UncheckRealDevices(list), "clearing must report a change when cards were ticked");
Check(list.GetItemChecked(0), "the follower must stay ticked");
Check(!list.GetItemChecked(1) && !list.GetItemChecked(2), "every specific card must be cleared");
// With the follower ON, a fresh attempt to tick a specific card is vetoed back to unticked.
var onCheck = new System.Windows.Forms.ItemCheckEventArgs(1, System.Windows.Forms.CheckState.Checked, System.Windows.Forms.CheckState.Unchecked);
Check(AudioDefaultFollower.VetoRealDeviceCheck(list, onCheck), "with the follower on, ticking a specific card must be vetoed");
Check(onCheck.NewValue == System.Windows.Forms.CheckState.Unchecked, "the vetoed check must be forced back to unticked");
// Unticking a card, and the follower entry itself, are never vetoed.
var untick = new System.Windows.Forms.ItemCheckEventArgs(1, System.Windows.Forms.CheckState.Unchecked, System.Windows.Forms.CheckState.Checked);
Check(!AudioDefaultFollower.VetoRealDeviceCheck(list, untick), "unticking a card must never be vetoed");
var followerToggle = new System.Windows.Forms.ItemCheckEventArgs(0, System.Windows.Forms.CheckState.Checked, System.Windows.Forms.CheckState.Unchecked);
Check(!AudioDefaultFollower.VetoRealDeviceCheck(list, followerToggle), "the follower entry itself must never be vetoed");
return "follower on clears + locks specific cards; off frees them; follower/untick never vetoed";
}
/// <summary>The "Use Windows default output" follower (Christopher's request) must be the SAME shared
/// sentinel + resolver the main app uses, and the service must resolve it to the LIVE default render
/// endpoint — never pass the raw sentinel through as a device id.</summary>