Release v4.6: follow the Windows default audio device (output + input)

- New "Use Windows default audio device, follows Windows changes" entry at the top of the
  received-output and send-input WASAPI lists. Resolves the current Windows default live,
  re-routes automatically when the default device changes, works alongside specific devices,
  and persists across launches (a follower can't go stale). Optional "untick the others?"
  prompt with a remembered "don't ask again", reset via a new Options item "Reset the default
  audio device prompt".
- Manual updated for the feature, plus a sweep that corrected the now-stale Options-menu
  section (retired Startup-behaviour item, four->five Preferences tabs, missing entries).
  About changelog and RELEASE_NOTES updated; version 4.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-23 11:41:58 +01:00
co-authored by Claude Opus 4.8
parent 962f35ab80
commit 36431d6d39
8 changed files with 355 additions and 42 deletions
+18 -7
View File
@@ -262,14 +262,16 @@ Item| Shortcut| What it does
### Options menu ### Options menu
The Options menu gathers everything you might want to configure about the app — recording settings, keyboard shortcuts, startup behaviour, and general preferences. The Options menu gathers everything you might want to configure about the app — recording settings, keyboard shortcuts, profile passwords, and general preferences.
Item| Shortcut| What it does Item| Shortcut| What it does
---|---|--- ---|---|---
**Recording settings …**| Alt+O, S| Opens the Recording settings dialog. Up to five lists: _Recording source_ (Alt+S), _File format_ (Alt+F), _Audio format attributes_ (Alt+A), _FLAC compression level_ (Alt+L — only shown when FLAC is chosen), and _Channels_ (Alt+C). The attributes list changes to match the format you pick. OK saves to the current profile; Cancel discards. **Recording settings …**| Alt+O, S| Opens the Recording settings dialog. Up to five lists: _Recording source_ (Alt+S), _File format_ (Alt+F), _Audio format attributes_ (Alt+A), _FLAC compression level_ (Alt+L — only shown when FLAC is chosen), and _Channels_ (Alt+C). The attributes list changes to match the format you pick. OK saves to the current profile; Cancel discards.
**Keyboard shortcuts …**| Ctrl+K, or Alt+O, K| Opens the global hotkey dialog (mute, volume, show/hide window, start/stop recording, remote-control commands). **Keyboard shortcuts …**| Ctrl+K, or Alt+O, K| Opens the global hotkey dialog (mute, volume, show/hide window, start/stop recording, remote-control commands, speak the status line).
**Startup behaviour**| Alt+O, T| Opens the Startup behaviour dialog. Choose whether to launch automatically with Windows, which profile to load by default, and whether to start hidden in the tray. **Profile passwords**| Alt+O, W| Lists every profile alongside its password, so you can view or change any of them in one place.
**Preferences …**| Ctrl+P, or Alt+O, P| Opens the Preferences dialog, organised into four tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): **General** — the profiles folder, accept remote volume commands, UPnP router opening, and keep-logs / write-logs-now; **Audio cues** — the cue list and its sounds (see Audio cue sounds); **Startup behaviour** — start minimised / with Windows / with a specific profile; and **Update settings** — the update checks and install options. Esc or the Close button dismisses it. **Reset the default audio device prompt**| Alt+O, R| Brings back the “use only the default audio device?” question if you previously ticked “Don't ask me this again” on it. See Following the Windows default audio device.
**Enable / Disable Realtek ASIO**| —| Only shown if a Realtek ASIO driver is installed. Lets you reverse the choice RemSound offered about disabling that driver (Realtek's generic ASIO driver tends to grab the wrong device and clash with your screen reader).
**Preferences …**| Ctrl+P, or Alt+O, P| Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): **General** — the profiles folder, accept remote volume commands, and UPnP router opening; **Audio cues** — the cue list and its sounds (see Audio cue sounds); **Startup behaviour** — start minimised / with Windows / with a specific profile; **Update settings** — the update checks and install options; and **Logging** — enable logs, write logs now, and the log-folder housekeeping (see Logs and diagnostics). Esc or the Close button dismisses it.
### Help menu ### Help menu
@@ -311,6 +313,14 @@ Control| Shortcut| What it does
All the device lists are checkable lists — tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked. All the device lists are checkable lists — tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked.
### Following the Windows default audio device
At the very top of the **WASAPI outputs for received sound** list and the **WASAPI inputs to send** list there's a special entry: **Use Windows default audio device, follows Windows changes**. Tick it and RemSound uses whatever Windows currently treats as the default — the default speakers for received sound, the default microphone for sending — and, the useful part, it **follows that default on its own**. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.
It works alongside the specific devices: tick the default entry _and_ particular cards, and the sound plays out of (or is captured from) all of them at once. When you first tick it, RemSound asks whether you'd like to untick the other devices in that list, so you can choose to use only the default — with a “Don't ask me this again” option. If you tick that and later want the question back, use **Options → Reset the default audio device prompt**.
Unlike the specific device ticks (which start fresh each session), the “use default audio device” choice is remembered between launches. It's safe to remember because it can never point at the wrong card — it always resolves to whatever Windows is using right now.
### Receiving ### Receiving
To receive sound you need two things: **Receive audio** ticked, and at least one output device ticked. Without an output device, even when sound arrives there is nowhere for it to go. To receive sound you need two things: **Receive audio** ticked, and at least one output device ticked. Without an output device, even when sound arrives there is nowhere for it to go.
@@ -751,7 +761,8 @@ Alt+F, M| Rename the current profile
Alt+F, N| Minimise to tray Alt+F, N| Minimise to tray
Alt+O, S| Recording settings Alt+O, S| Recording settings
Alt+O, K| Keyboard shortcuts Alt+O, K| Keyboard shortcuts
Alt+O, T| Startup behaviour Alt+O, W| Profile passwords
Alt+O, R| Reset the default audio device prompt
Alt+O, P| Preferences Alt+O, P| Preferences
Alt+F, X| Exit Alt+F, X| Exit
@@ -903,7 +914,7 @@ All of these cues play through your default Windows sound output, which is separ
### The Audio cues tab ### The Audio cues tab
Open **File → Preferences** (or Ctrl+P) and go to the **Audio cues** tab. (Preferences is now organised into four tabs — General, Audio cues, Startup behaviour and Update settings — which you move between with Ctrl+Tab, or with the arrow keys when the row of tab names has focus.) Open **File → Preferences** (or Ctrl+P) and go to the **Audio cues** tab. (Preferences is organised into five tabs — General, Audio cues, Startup behaviour, Update settings and Logging — which you move between with Ctrl+Tab, or with the arrow keys when the row of tab names has focus.)
The **Audio cue sounds (Alt+N)** list shows every cue by name. Use the up and down arrow keys to move between them — as you land on each cue, RemSound plays its current sound, so you can hear what's set just by arrowing through. The **Audio cue sounds (Alt+N)** list shows every cue by name. Use the up and down arrow keys to move between them — as you land on each cue, RemSound plays its current sound, so you can hear what's set just by arrowing through.
@@ -1004,7 +1015,7 @@ You'll see the window close, then reopen on the new version within a second or t
When the install finishes and RemSound reopens, it loads the same profile that was running just before the update — you don't see the profile picker, and your devices, peer list, codec and latency settings all come back exactly as they were. This means a silent update in the middle of a session drops the audio briefly while the install finishes, then your session reconnects on its own. You don't have to be at the computer when it happens. When the install finishes and RemSound reopens, it loads the same profile that was running just before the update — you don't see the profile picker, and your devices, peer list, codec and latency settings all come back exactly as they were. This means a silent update in the middle of a session drops the audio briefly while the install finishes, then your session reconnects on its own. You don't have to be at the computer when it happens.
This is a one-shot, just-after-the-update behaviour. The very next time you launch RemSound manually (from the desktop, the Start menu, or the tray icon), it follows your normal startup choice — the picker if that's how you've set it, or your chosen startup profile if you've picked one in Options → Startup behaviour. This is a one-shot, just-after-the-update behaviour. The very next time you launch RemSound manually (from the desktop, the Start menu, or the tray icon), it follows your normal startup choice — the picker if that's how you've set it, or your chosen startup profile if you've picked one in Preferences → Startup behaviour.
If the profile that was running can't be found after the update (you'd renamed or moved it during the session, for example), RemSound falls back to your normal startup behaviour rather than getting stuck. If the profile that was running can't be found after the update (you'd renamed or moved it during the session, for example), RemSound falls back to your normal startup behaviour rather than getting stuck.
+9 -14
View File
@@ -1,34 +1,29 @@
# RemSound v4.5 # RemSound v4.6
Keyboard shortcuts get easier to manage — and people upgrading from before 4.4 no longer lose theirs. Follow your Windows default audio device — for both the sound you receive and the mic you send.
## Bring your shortcuts across when you upgrade ## Use the Windows default audio device
In version 4.4, keyboard shortcuts moved from being saved per-profile to one shared set for the whole app. If you're updating **from before 4.4**, RemSound now offers — the first time you run it — to **bring your shortcuts across** from one of your existing profiles: it lists your profiles, you pick the one you set your shortcuts up in, or choose to start fresh. (If you already updated to 4.4, nothing changes for you here.) Both WASAPI device lists now have a new entry at the very top — **"Use Windows default audio device, follows Windows changes"** — in the outputs for received sound, and in the inputs to send.
## A clearer Keyboard shortcuts dialog Tick it and RemSound uses whatever Windows currently treats as the default (the default speakers for received sound, the default microphone for sending), and — the useful part — it **follows that default automatically**. Make a headset your default and RemSound switches to it on its own; unplug it and it moves back, all without touching the list.
- A new **Clear this shortcut** button next to Close, so clearing a shortcut is discoverable — you no longer have to know the Del key. It works alongside the specific devices: tick the default entry *and* particular cards, and the sound plays out of (or is captured from) all of them at once. When you first turn it on, RemSound offers to untick the other devices so you use only the default — with a "Don't ask me this again" option. If you hide that question and later want it back, use **Options → Reset the default audio device prompt**.
- When you're setting a shortcut, pressing **Delete** in that box now leaves it unassigned.
## Clearer remote-control labels
The three remote-control rows that adjust a peer's **RemSound** volume now say so — "Send remote RemSound volume up / down to peers" and "Send remote RemSound receive mute toggle to peers" — so they're easy to tell apart from the "Windows global volume" rows.
## Compatibility ## Compatibility
**v4.5 talks to v3.3 through v4.4 with no trouble** — the over-the-network format is unchanged, so you don't have to update both ends at once. (Everyone still needs **v3.3 or newer**, where end-to-end encryption came in.) **v4.6 talks to v3.3 through v4.5 with no trouble** — the over-the-network format is unchanged, so you don't have to update both ends at once. (Everyone still needs **v3.3 or newer**, where end-to-end encryption came in.)
## Install ## Install
1. Download `RemSound-v4.5.zip` from this release. 1. Download `RemSound-v4.6.zip` from this release.
2. Close RemSound. 2. Close RemSound.
3. Extract the zip **over your existing RemSound folder**, overwriting program files when prompted. The zip is program files only — it won't touch your settings, profiles, logs or recordings. 3. Extract the zip **over your existing RemSound folder**, overwriting program files when prompted. The zip is program files only — it won't touch your settings, profiles, logs or recordings.
4. Run `RemSound.exe`. 4. Run `RemSound.exe`.
## Upgrading ## Upgrading
**From v3.6 or newer:** Help → Check for updates installs v4.5 with the in-app updater — and if it can't finish, it puts your old version back exactly as it was. **From v3.6 or newer:** Help → Check for updates installs v4.6 with the in-app updater — and if it can't finish, it puts your old version back exactly as it was.
**From v1.9v3.5:** Check for updates works, but uses your current version's older updater for this one hop. If auto-update has been failing on your machine, install by hand using the steps above. **From v1.9v3.5:** Check for updates works, but uses your current version's older updater for this one hop. If auto-update has been failing on your machine, install by hand using the steps above.
+18 -7
View File
@@ -296,14 +296,16 @@ ul, ol { padding-left: 1.4em; }
<h3>Options menu</h3> <h3>Options menu</h3>
<p>The Options menu gathers everything you might want to configure about the app &mdash; recording settings, keyboard shortcuts, startup behaviour, and general preferences.</p> <p>The Options menu gathers everything you might want to configure about the app &mdash; recording settings, keyboard shortcuts, profile passwords, and general preferences.</p>
<table> <table>
<tr><th>Item</th><th>Shortcut</th><th>What it does</th></tr> <tr><th>Item</th><th>Shortcut</th><th>What it does</th></tr>
<tr><td><strong>Recording settings&hellip;</strong></td><td>Alt+O, S</td><td>Opens the Recording settings dialog. Up to five lists: <em>Recording source</em> (Alt+S), <em>File format</em> (Alt+F), <em>Audio format attributes</em> (Alt+A), <em>FLAC compression level</em> (Alt+L &mdash; only shown when FLAC is chosen), and <em>Channels</em> (Alt+C). The attributes list changes to match the format you pick. OK saves to the current profile; Cancel discards.</td></tr> <tr><td><strong>Recording settings&hellip;</strong></td><td>Alt+O, S</td><td>Opens the Recording settings dialog. Up to five lists: <em>Recording source</em> (Alt+S), <em>File format</em> (Alt+F), <em>Audio format attributes</em> (Alt+A), <em>FLAC compression level</em> (Alt+L &mdash; only shown when FLAC is chosen), and <em>Channels</em> (Alt+C). The attributes list changes to match the format you pick. OK saves to the current profile; Cancel discards.</td></tr>
<tr><td><strong>Keyboard shortcuts&hellip;</strong></td><td>Ctrl+K, or Alt+O, K</td><td>Opens the global hotkey dialog (mute, volume, show/hide window, start/stop recording, remote-control commands).</td></tr> <tr><td><strong>Keyboard shortcuts&hellip;</strong></td><td>Ctrl+K, or Alt+O, K</td><td>Opens the global hotkey dialog (mute, volume, show/hide window, start/stop recording, remote-control commands, speak the status line).</td></tr>
<tr><td><strong>Startup behaviour&hellip;</strong></td><td>Alt+O, T</td><td>Opens the Startup behaviour dialog. Choose whether to launch automatically with Windows, which profile to load by default, and whether to start hidden in the tray.</td></tr> <tr><td><strong>Profile passwords&hellip;</strong></td><td>Alt+O, W</td><td>Lists every profile alongside its password, so you can view or change any of them in one place.</td></tr>
<tr><td><strong>Preferences&hellip;</strong></td><td>Ctrl+P, or Alt+O, P</td><td>Opens the Preferences dialog, organised into four tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): <strong>General</strong> &mdash; the profiles folder, accept remote volume commands, UPnP router opening, and keep-logs / write-logs-now; <strong>Audio cues</strong> &mdash; the cue list and its sounds (see <a href="#audio-cues">Audio cue sounds</a>); <strong>Startup behaviour</strong> &mdash; start minimised / with Windows / with a specific profile; and <strong>Update settings</strong> &mdash; the update checks and install options. Esc or the Close button dismisses it.</td></tr> <tr><td><strong>Reset the default audio device prompt</strong></td><td>Alt+O, R</td><td>Brings back the &ldquo;use only the default audio device?&rdquo; question if you previously ticked &ldquo;Don't ask me this again&rdquo; on it. See <a href="#tab-io">Following the Windows default audio device</a>.</td></tr>
<tr><td><strong>Enable / Disable Realtek ASIO</strong></td><td>&mdash;</td><td>Only shown if a Realtek ASIO driver is installed. Lets you reverse the choice RemSound offered about disabling that driver (Realtek's generic ASIO driver tends to grab the wrong device and clash with your screen reader).</td></tr>
<tr><td><strong>Preferences&hellip;</strong></td><td>Ctrl+P, or Alt+O, P</td><td>Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): <strong>General</strong> &mdash; the profiles folder, accept remote volume commands, and UPnP router opening; <strong>Audio cues</strong> &mdash; the cue list and its sounds (see <a href="#audio-cues">Audio cue sounds</a>); <strong>Startup behaviour</strong> &mdash; start minimised / with Windows / with a specific profile; <strong>Update settings</strong> &mdash; the update checks and install options; and <strong>Logging</strong> &mdash; enable logs, write logs now, and the log-folder housekeeping (see <a href="#logs">Logs and diagnostics</a>). Esc or the Close button dismisses it.</td></tr>
</table> </table>
<h3>Help menu</h3> <h3>Help menu</h3>
@@ -349,6 +351,14 @@ ul, ol { padding-left: 1.4em; }
<p>All the device lists are checkable lists &mdash; tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked.</p> <p>All the device lists are checkable lists &mdash; tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked.</p>
<h3>Following the Windows default audio device</h3>
<p>At the very top of the <strong>WASAPI outputs for received sound</strong> list and the <strong>WASAPI inputs to send</strong> list there's a special entry: <strong>Use Windows default audio device, follows Windows changes</strong>. Tick it and RemSound uses whatever Windows currently treats as the default &mdash; the default speakers for received sound, the default microphone for sending &mdash; and, the useful part, it <strong>follows that default on its own</strong>. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.</p>
<p>It works alongside the specific devices: tick the default entry <em>and</em> particular cards, and the sound plays out of (or is captured from) all of them at once. When you first tick it, RemSound asks whether you'd like to untick the other devices in that list, so you can choose to use only the default &mdash; with a &ldquo;Don't ask me this again&rdquo; option. If you tick that and later want the question back, use <strong>Options &rarr; Reset the default audio device prompt</strong>.</p>
<p>Unlike the specific device ticks (which start fresh each session), the &ldquo;use default audio device&rdquo; choice is remembered between launches. It's safe to remember because it can never point at the wrong card &mdash; it always resolves to whatever Windows is using right now.</p>
<h3>Receiving</h3> <h3>Receiving</h3>
<p>To receive sound you need two things: <strong>Receive audio</strong> ticked, and at least one output device ticked. Without an output device, even when sound arrives there is nowhere for it to go.</p> <p>To receive sound you need two things: <strong>Receive audio</strong> ticked, and at least one output device ticked. Without an output device, even when sound arrives there is nowhere for it to go.</p>
@@ -792,7 +802,8 @@ Audient USB Audio ASIO Driver &mdash; Pair 3 (channels 5/6): Loop-back 1 (L) / L
<tr><td>Alt+F, N</td><td>Minimise to tray</td></tr> <tr><td>Alt+F, N</td><td>Minimise to tray</td></tr>
<tr><td>Alt+O, S</td><td>Recording settings</td></tr> <tr><td>Alt+O, S</td><td>Recording settings</td></tr>
<tr><td>Alt+O, K</td><td>Keyboard shortcuts</td></tr> <tr><td>Alt+O, K</td><td>Keyboard shortcuts</td></tr>
<tr><td>Alt+O, T</td><td>Startup behaviour</td></tr> <tr><td>Alt+O, W</td><td>Profile passwords</td></tr>
<tr><td>Alt+O, R</td><td>Reset the default audio device prompt</td></tr>
<tr><td>Alt+O, P</td><td>Preferences</td></tr> <tr><td>Alt+O, P</td><td>Preferences</td></tr>
<tr><td>Alt+F, X</td><td>Exit</td></tr> <tr><td>Alt+F, X</td><td>Exit</td></tr>
</table> </table>
@@ -957,7 +968,7 @@ Use whatever key combinations you prefer (for example Ctrl+Shift+Up / Ctrl+Shift
<h3>The Audio cues tab</h3> <h3>The Audio cues tab</h3>
<p>Open <strong>File &rarr; Preferences</strong> (or Ctrl+P) and go to the <strong>Audio cues</strong> tab. (Preferences is now organised into four tabs &mdash; General, Audio cues, Startup behaviour and Update settings &mdash; which you move between with Ctrl+Tab, or with the arrow keys when the row of tab names has focus.)</p> <p>Open <strong>File &rarr; Preferences</strong> (or Ctrl+P) and go to the <strong>Audio cues</strong> tab. (Preferences is organised into five tabs &mdash; General, Audio cues, Startup behaviour, Update settings and Logging &mdash; which you move between with Ctrl+Tab, or with the arrow keys when the row of tab names has focus.)</p>
<p>The <strong>Audio cue sounds (Alt+N)</strong> list shows every cue by name. Use the up and down arrow keys to move between them &mdash; as you land on each cue, RemSound plays its current sound, so you can hear what's set just by arrowing through.</p> <p>The <strong>Audio cue sounds (Alt+N)</strong> list shows every cue by name. Use the up and down arrow keys to move between them &mdash; as you land on each cue, RemSound plays its current sound, so you can hear what's set just by arrowing through.</p>
@@ -1061,7 +1072,7 @@ Use whatever key combinations you prefer (for example Ctrl+Shift+Up / Ctrl+Shift
<p>When the install finishes and RemSound reopens, it loads the same profile that was running just before the update &mdash; you don't see the profile picker, and your devices, peer list, codec and latency settings all come back exactly as they were. This means a silent update in the middle of a session drops the audio briefly while the install finishes, then your session reconnects on its own. You don't have to be at the computer when it happens.</p> <p>When the install finishes and RemSound reopens, it loads the same profile that was running just before the update &mdash; you don't see the profile picker, and your devices, peer list, codec and latency settings all come back exactly as they were. This means a silent update in the middle of a session drops the audio briefly while the install finishes, then your session reconnects on its own. You don't have to be at the computer when it happens.</p>
<p>This is a one-shot, just-after-the-update behaviour. The very next time you launch RemSound manually (from the desktop, the Start menu, or the tray icon), it follows your normal startup choice &mdash; the picker if that's how you've set it, or your chosen startup profile if you've picked one in Options &rarr; Startup behaviour.</p> <p>This is a one-shot, just-after-the-update behaviour. The very next time you launch RemSound manually (from the desktop, the Start menu, or the tray icon), it follows your normal startup choice &mdash; the picker if that's how you've set it, or your chosen startup profile if you've picked one in Preferences &rarr; Startup behaviour.</p>
<p>If the profile that was running can't be found after the update (you'd renamed or moved it during the session, for example), RemSound falls back to your normal startup behaviour rather than getting stuck.</p> <p>If the profile that was running can't be found after the update (you'd renamed or moved it during the session, for example), RemSound falls back to your normal startup behaviour rather than getting stuck.</p>
+19
View File
@@ -20,6 +20,25 @@ internal sealed class AboutDialog : Form
/// updates" path.</summary> /// updates" path.</summary>
private const string ReleaseNotes = private const string ReleaseNotes =
""" """
RemSound v4.6
Follow your Windows default audio device.
Both device lists the outputs for received
sound and the inputs to send now have a "Use
Windows default audio device, follows Windows
changes" entry at the top. Tick it and RemSound
uses whatever Windows is currently using, and
switches with it on its own: make a headset your
default and RemSound follows, no clicking. You
can still tick specific devices alongside it.
When you turn it on, RemSound offers to untick
the other devices so you use only the default;
if you hide that question with "Don't ask
again", Options → Reset the default audio device
prompt brings it back.
RemSound v4.5 RemSound v4.5
Keyboard shortcuts get easier to manage. Keyboard shortcuts get easier to manage.
+268 -13
View File
@@ -400,6 +400,20 @@ public sealed class MainForm : Form
// wired in the constructor; disposed in FormClosing (which unregisters the COM callback). // wired in the constructor; disposed in FormClosing (which unregisters the COM callback).
private AudioDeviceChangeNotifier? deviceChangeNotifier; private AudioDeviceChangeNotifier? deviceChangeNotifier;
// The "Use Windows default ..." follower entries sit at the top of the receive-output and
// send-input lists. Ticked, the current Windows default device is resolved live (and re-resolved
// when Windows' default changes). A synthetic sentinel DeviceId that can never collide
// with a real endpoint id (which looks like "{0.0.0.00000000}.{guid}").
private static readonly AudioDeviceChoice DefaultOutputFollower =
new("Use Windows default audio device, follows Windows changes", "__use-default-output__", CaptureKind.Loopback) { IsDefaultFollower = true };
private static readonly AudioDeviceChoice DefaultInputFollower =
new("Use Windows default audio device, follows Windows changes", "__use-default-input__", CaptureKind.Input) { IsDefaultFollower = true };
// The Windows-default device id we last routed to while following, per direction. A default-device
// change doesn't change the device SET, so the list-sync wouldn't catch it — we compare against
// these to spot it and re-route (see ReapplyIfFollowedDefaultChanged).
private string? lastFollowedDefaultOutputId;
private string? lastFollowedDefaultInputId;
// Receive-output device IDs the user/profile selected — kept even while a device is unplugged, // Receive-output device IDs the user/profile selected — kept even while a device is unplugged,
// so a card that returns is silently re-ticked and re-opened (issue #5: recover after USB // so a card that returns is silently re-ticked and re-opened (issue #5: recover after USB
// unplug). Receive-only: the send lists deliberately don't persist selection (AudioDeviceCatalog). // unplug). Receive-only: the send lists deliberately don't persist selection (AudioDeviceCatalog).
@@ -1042,15 +1056,25 @@ public sealed class MainForm : Form
receiveOutputDevicesList.ItemCheck += (_, e) => receiveOutputDevicesList.ItemCheck += (_, e) =>
{ {
if (suppressDeviceCheckChange) return; if (suppressDeviceCheckChange) return;
// Track the user's intent so a card that's later unplugged is re-ticked + re-opened when if (receiveOutputDevicesList.Items[e.Index] is AudioDeviceChoice c)
// it returns (issue #5). See ReapplyRememberedReceiveOutputs.
if (receiveOutputDevicesList.Items[e.Index] is AudioDeviceChoice c && c.DeviceId is { } rid)
{ {
if (e.NewValue == CheckState.Checked) rememberedReceiveOutputIds.Add(rid); if (c.IsDefaultFollower)
else rememberedReceiveOutputIds.Remove(rid); {
// Machine-wide preference (AppConfig), not part of the profile.
var on = e.NewValue == CheckState.Checked;
PersistUseDefaultDevice(output: true, on);
if (on) BeginInvoke(new Action(() => MaybeUntickOthersForDefault(receiveOutputDevicesList, output: true)));
}
else if (c.DeviceId is { } rid)
{
// Track the user's intent so a card that's later unplugged is re-ticked + re-opened
// when it returns (issue #5). See ReapplyRememberedReceiveOutputs.
if (e.NewValue == CheckState.Checked) rememberedReceiveOutputIds.Add(rid);
else rememberedReceiveOutputIds.Remove(rid);
MarkProfileDirty();
}
} }
BeginInvoke(ApplyReceiveDevices); BeginInvoke(ApplyReceiveDevices);
MarkProfileDirty();
}; };
WireCheckedListAccessibility(sendOutputDevicesList, sendOutputDevicesStatusLabel, "output device"); WireCheckedListAccessibility(sendOutputDevicesList, sendOutputDevicesStatusLabel, "output device");
WireCheckedListAccessibility(sendInputDevicesList, sendInputDevicesStatusLabel, "input device"); WireCheckedListAccessibility(sendInputDevicesList, sendInputDevicesStatusLabel, "input device");
@@ -1060,6 +1084,15 @@ public sealed class MainForm : Form
if (suppressDeviceCheckChange) return; if (suppressDeviceCheckChange) return;
logFile.Event($"ui: capture (WASAPI mic) '{sendInputDevicesList.Items[args.Index]}' {(args.NewValue == CheckState.Checked ? "ticked" : "unticked")}"); logFile.Event($"ui: capture (WASAPI mic) '{sendInputDevicesList.Items[args.Index]}' {(args.NewValue == CheckState.Checked ? "ticked" : "unticked")}");
BeginInvoke(ApplyAudioRuntime); BeginInvoke(ApplyAudioRuntime);
if (sendInputDevicesList.Items[args.Index] is AudioDeviceChoice { IsDefaultFollower: true })
{
// Machine-wide preference (AppConfig), not part of the profile — and no mic-block check
// (the default mic could be anything; the runtime apply handles a blocked default).
var on = args.NewValue == CheckState.Checked;
PersistUseDefaultDevice(output: false, on);
if (on) BeginInvoke(new Action(() => MaybeUntickOthersForDefault(sendInputDevicesList, output: false)));
return;
}
MarkProfileDirty(); MarkProfileDirty();
// Heads-up when the user ticks a WASAPI mic ON but Windows is blocking desktop-app // Heads-up when the user ticks a WASAPI mic ON but Windows is blocking desktop-app
// microphone access — capture would open but silently send nothing. Deferred so the // microphone access — capture would open but silently send nothing. Deferred so the
@@ -1826,6 +1859,15 @@ public sealed class MainForm : Form
}; };
profilePasswordsItem.Click += (_, _) => OpenProfilePasswordManager(); profilePasswordsItem.Click += (_, _) => OpenProfilePasswordManager();
// Reset the one-time "untick the other devices?" prompt that appears when you turn on a
// "Use Windows default audio device" box — clears the remembered Yes/No so it asks again.
// Mirrors the Realtek toggle's "let the user reverse a one-time decision" pattern.
var resetDefaultDeviceItem = new ToolStripMenuItem("&Reset the default audio device prompt")
{
AccessibleName = "Reset the default audio device prompt",
};
resetDefaultDeviceItem.Click += (_, _) => ResetDefaultAudioDevicePrompt();
// Realtek ASIO enable/disable toggle — only present when a Realtek ASIO driver is actually // Realtek ASIO enable/disable toggle — only present when a Realtek ASIO driver is actually
// installed. Lets the user reverse the disable decision (or disable a driver they kept). // installed. Lets the user reverse the disable decision (or disable a driver they kept).
ToolStripMenuItem? realtekToggle = null; ToolStripMenuItem? realtekToggle = null;
@@ -1844,6 +1886,7 @@ public sealed class MainForm : Form
recordingSettingsItem, recordingSettingsItem,
keyboardItem, keyboardItem,
profilePasswordsItem, profilePasswordsItem,
resetDefaultDeviceItem,
}; };
if (realtekToggle is not null) optionItems.Add(realtekToggle); if (realtekToggle is not null) optionItems.Add(realtekToggle);
optionItems.Add(new ToolStripSeparator()); optionItems.Add(new ToolStripSeparator());
@@ -3932,10 +3975,25 @@ public sealed class MainForm : Form
{ {
if (item.DeviceId is { } id) specs.Add(new CaptureSourceSpec(id, CaptureKind.Loopback, item.Name)); if (item.DeviceId is { } id) specs.Add(new CaptureSourceSpec(id, CaptureKind.Loopback, item.Name));
} }
var addedInputIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string? followedDefaultInput = null;
foreach (var item in sendInputDevicesList.CheckedItems.OfType<AudioDeviceChoice>()) foreach (var item in sendInputDevicesList.CheckedItems.OfType<AudioDeviceChoice>())
{ {
if (item.DeviceId is { } id) specs.Add(new CaptureSourceSpec(id, CaptureKind.Input, item.Name)); if (item.IsDefaultFollower)
{
// Resolve the current Windows default capture device live.
followedDefaultInput = ResolveDefaultDeviceId(NAudio.CoreAudioApi.DataFlow.Capture);
if (!string.IsNullOrEmpty(followedDefaultInput) && addedInputIds.Add(followedDefaultInput))
{
specs.Add(new CaptureSourceSpec(followedDefaultInput, CaptureKind.Input, "Windows default audio device"));
}
}
else if (item.DeviceId is { } id && addedInputIds.Add(id))
{
specs.Add(new CaptureSourceSpec(id, CaptureKind.Input, item.Name));
}
} }
lastFollowedDefaultInputId = followedDefaultInput;
foreach (var item in asioSendDevicesList.CheckedItems.OfType<AudioDeviceChoice>()) foreach (var item in asioSendDevicesList.CheckedItems.OfType<AudioDeviceChoice>())
{ {
// ASIO channels have no Loopback/Input distinction — Kind is irrelevant for ASIO // ASIO channels have no Loopback/Input distinction — Kind is irrelevant for ASIO
@@ -3963,8 +4021,12 @@ public sealed class MainForm : Form
// The user re-ticks once per session, avoiding the "wrong-device-still-selected" // The user re-ticks once per session, avoiding the "wrong-device-still-selected"
// failure mode after a card unplug or ID change. // failure mode after a card unplug or ID change.
sendOutputDevicesSignature = SyncDeviceCheckedListBox(sendOutputDevicesList, outputs); sendOutputDevicesSignature = SyncDeviceCheckedListBox(sendOutputDevicesList, outputs);
sendInputDevicesSignature = SyncDeviceCheckedListBox(sendInputDevicesList, inputs); sendInputDevicesSignature = SyncDeviceCheckedListBox(sendInputDevicesList, WithDefaultFollower(inputs, DefaultInputFollower));
receiveOutputDevicesSignature = SyncDeviceCheckedListBox(receiveOutputDevicesList, outputs); receiveOutputDevicesSignature = SyncDeviceCheckedListBox(receiveOutputDevicesList, WithDefaultFollower(outputs, DefaultOutputFollower));
// Re-tick the "Use Windows default" followers from the saved preference. They don't ride the
// per-session "all unticked" rule above — a follower can never go stale (it always resolves
// to the current default), so persisting it is the whole point of the feature.
RestoreDefaultFollowerChecks();
// Ground-truth log so we can definitively see the device list and initial check state // Ground-truth log so we can definitively see the device list and initial check state
// each launch — diagnoses any "device was checked at startup" mystery. // each launch — diagnoses any "device was checked at startup" mystery.
@@ -4066,8 +4128,8 @@ public sealed class MainForm : Form
} }
var sendOutputChanged = MaybeSyncList(sendOutputDevicesList, wasapiOutputs, ref sendOutputDevicesSignature); var sendOutputChanged = MaybeSyncList(sendOutputDevicesList, wasapiOutputs, ref sendOutputDevicesSignature);
var sendInputChanged = MaybeSyncList(sendInputDevicesList, wasapiInputs, ref sendInputDevicesSignature); var sendInputChanged = MaybeSyncList(sendInputDevicesList, WithDefaultFollower(wasapiInputs, DefaultInputFollower), ref sendInputDevicesSignature);
var receiveOutputChanged = MaybeSyncList(receiveOutputDevicesList, wasapiOutputs, ref receiveOutputDevicesSignature); var receiveOutputChanged = MaybeSyncList(receiveOutputDevicesList, WithDefaultFollower(wasapiOutputs, DefaultOutputFollower), ref receiveOutputDevicesSignature);
bool asioSendChanged; bool asioSendChanged;
bool asioReceiveChanged; bool asioReceiveChanged;
if (asioProbeAttemptedAndFailed) if (asioProbeAttemptedAndFailed)
@@ -4094,6 +4156,9 @@ public sealed class MainForm : Form
{ {
ApplyReceiveDevices(); ApplyReceiveDevices();
} }
// A default-device change leaves the device SET unchanged, so the syncs above don't fire — but
// if we're following the Windows default, the target device just moved. Catch that and re-route.
ReapplyIfFollowedDefaultChanged();
} }
private void ClearAsioProbeCache() private void ClearAsioProbeCache()
@@ -4494,17 +4559,207 @@ public sealed class MainForm : Form
// Combine WASAPI device-ids and ASIO synthetic-ids into one list. The // Combine WASAPI device-ids and ASIO synthetic-ids into one list. The
// CompositeRenderBackend splits them internally and feeds each child the right subset. // CompositeRenderBackend splits them internally and feeds each child the right subset.
var ids = new List<string>(); var ids = new List<string>();
var added = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string? followedDefault = null;
foreach (var c in receiveOutputDevicesList.CheckedItems.OfType<AudioDeviceChoice>()) foreach (var c in receiveOutputDevicesList.CheckedItems.OfType<AudioDeviceChoice>())
{ {
if (!string.IsNullOrEmpty(c.DeviceId)) ids.Add(c.DeviceId); if (c.IsDefaultFollower)
{
// Resolve the current Windows default render device live.
followedDefault = ResolveDefaultDeviceId(NAudio.CoreAudioApi.DataFlow.Render);
if (!string.IsNullOrEmpty(followedDefault) && added.Add(followedDefault)) ids.Add(followedDefault);
}
else if (!string.IsNullOrEmpty(c.DeviceId) && added.Add(c.DeviceId))
{
ids.Add(c.DeviceId);
}
} }
lastFollowedDefaultOutputId = followedDefault;
foreach (var c in asioReceiveOutputDevicesList.CheckedItems.OfType<AudioDeviceChoice>()) foreach (var c in asioReceiveOutputDevicesList.CheckedItems.OfType<AudioDeviceChoice>())
{ {
if (!string.IsNullOrEmpty(c.DeviceId)) ids.Add(c.DeviceId); if (!string.IsNullOrEmpty(c.DeviceId) && added.Add(c.DeviceId)) ids.Add(c.DeviceId);
} }
receiver.SetOutputDevices(ids); receiver.SetOutputDevices(ids);
} }
// ===================== "Use Windows default device" follower support =====================
/// <summary>Returns a new list with <paramref name="follower"/> at the top, followed by
/// <paramref name="devices"/>. Used to put the "Use Windows default ..." entry first in the
/// receive-output and send-input lists.</summary>
private static IReadOnlyList<AudioDeviceChoice> WithDefaultFollower(IReadOnlyList<AudioDeviceChoice> devices, AudioDeviceChoice follower)
{
var list = new List<AudioDeviceChoice>(devices.Count + 1) { follower };
list.AddRange(devices);
return list;
}
/// <summary>The id of the current Windows default endpoint for the given direction, or null if
/// there isn't one / it can't be read. Best-effort.</summary>
private static string? ResolveDefaultDeviceId(NAudio.CoreAudioApi.DataFlow flow)
{
try
{
using var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator();
if (!enumerator.HasDefaultAudioEndpoint(flow, NAudio.CoreAudioApi.Role.Multimedia)) return null;
using var device = enumerator.GetDefaultAudioEndpoint(flow, NAudio.CoreAudioApi.Role.Multimedia);
return device.ID;
}
catch { return null; }
}
private static bool IsFollowerChecked(CheckedListBox list) =>
list.CheckedItems.OfType<AudioDeviceChoice>().Any(c => c.IsDefaultFollower);
/// <summary>When following the Windows default and that default device changes, the device SET is
/// unchanged so the list-sync doesn't fire — so re-route here. Called from OnAudioEndpointsChanged,
/// which the change-notifier raises (among other things) on a default-device change.</summary>
private void ReapplyIfFollowedDefaultChanged()
{
if (IsFollowerChecked(receiveOutputDevicesList)
&& ResolveDefaultDeviceId(NAudio.CoreAudioApi.DataFlow.Render) != lastFollowedDefaultOutputId)
{
ApplyReceiveDevices();
}
if (IsFollowerChecked(sendInputDevicesList)
&& ResolveDefaultDeviceId(NAudio.CoreAudioApi.DataFlow.Capture) != lastFollowedDefaultInputId)
{
ApplySendSources();
}
}
private static void PersistUseDefaultDevice(bool output, bool on)
{
try
{
var c = AppConfig.Load();
if (output) c.UseDefaultOutputDevice = on; else c.UseDefaultInputDevice = on;
c.Save();
}
catch { /* harmless — choice just won't survive a restart */ }
}
private void RestoreDefaultFollowerChecks()
{
AppConfig cfg;
try { cfg = AppConfig.Load(); }
catch { return; }
SetFollowerChecked(receiveOutputDevicesList, cfg.UseDefaultOutputDevice);
SetFollowerChecked(sendInputDevicesList, cfg.UseDefaultInputDevice);
}
private void SetFollowerChecked(CheckedListBox list, bool on)
{
if (!on) return;
suppressDeviceCheckChange = true;
try
{
for (var i = 0; i < list.Items.Count; i++)
{
if (list.Items[i] is AudioDeviceChoice { IsDefaultFollower: true })
{
list.SetItemChecked(i, true);
break;
}
}
}
finally { suppressDeviceCheckChange = false; }
}
/// <summary>Offered when the user ticks a "Use Windows default" follower: optionally untick the
/// other devices in that list so only the default is used. Remembers the answer if the user ticks
/// "Don't ask again".</summary>
private void MaybeUntickOthersForDefault(CheckedListBox list, bool output)
{
if (IsDisposed) return;
AppConfig cfg;
try { cfg = AppConfig.Load(); }
catch { return; }
var remembered = output ? cfg.UntickOthersWhenUsingDefaultOutput : cfg.UntickOthersWhenUsingDefaultInput;
bool untick;
if (remembered is bool r)
{
untick = r;
}
else
{
var verification = new TaskDialogVerificationCheckBox("Don't ask me this again");
var page = new TaskDialogPage
{
Caption = "RemSound",
Heading = "Use only the default audio device?",
Text = output
? "You've turned on “Use Windows default audio device”. Do you want to untick the other outputs in this list, so received sound plays only to whatever Windows is using?\n\nChoose No to keep playing to those outputs as well as the default audio device."
: "You've turned on “Use Windows default audio device”. Do you want to untick the other inputs in this list, so only the Windows default audio device is captured?\n\nChoose No to keep capturing those inputs as well as the default audio device.",
Icon = TaskDialogIcon.Information,
Buttons = { TaskDialogButton.Yes, TaskDialogButton.No },
DefaultButton = TaskDialogButton.No,
Verification = verification,
};
untick = TaskDialog.ShowDialog(this, page) == TaskDialogButton.Yes;
if (verification.Checked)
{
try
{
var c2 = AppConfig.Load();
if (output) c2.UntickOthersWhenUsingDefaultOutput = untick; else c2.UntickOthersWhenUsingDefaultInput = untick;
c2.Save();
}
catch { /* harmless */ }
}
}
if (untick) UntickAllExceptDefaultFollower(list, output);
}
private void UntickAllExceptDefaultFollower(CheckedListBox list, bool output)
{
var anyChanged = false;
suppressDeviceCheckChange = true;
try
{
for (var i = 0; i < list.Items.Count; i++)
{
if (list.Items[i] is AudioDeviceChoice c && !c.IsDefaultFollower && list.GetItemChecked(i))
{
list.SetItemChecked(i, false);
if (output && c.DeviceId is { } id) rememberedReceiveOutputIds.Remove(id);
anyChanged = true;
}
}
}
finally { suppressDeviceCheckChange = false; }
if (!anyChanged) return;
if (output) ApplyReceiveDevices(); else ApplyAudioRuntime();
}
/// <summary>Options-menu action: clear the remembered answer to the "untick the other devices?"
/// prompt (for both output and input), so it asks again next time the user turns on a "Use Windows
/// default audio device" box. The discoverable way back from the prompt's "Don't ask again".</summary>
private void ResetDefaultAudioDevicePrompt()
{
try
{
var c = AppConfig.Load();
c.UntickOthersWhenUsingDefaultOutput = null;
c.UntickOthersWhenUsingDefaultInput = null;
c.Save();
logFile.Event("default audio device: reset the untick-others prompt (will ask again)");
}
catch (Exception ex)
{
logFile.Event($"default audio device: reset prompt failed: {ex.GetType().Name}: {ex.Message}");
}
var page = new TaskDialogPage
{
Caption = "RemSound",
Heading = "Default audio device prompt reset",
Text = "RemSound will ask again next time you turn on a “Use Windows default audio device” option.",
Icon = TaskDialogIcon.Information,
Buttons = { TaskDialogButton.OK },
};
TaskDialog.ShowDialog(this, page);
}
/// <summary>A receive-output card that was unplugged drops out of the WASAPI list (and its tick /// <summary>A receive-output card that was unplugged drops out of the WASAPI list (and its tick
/// with it). When it returns, re-tick it from <see cref="rememberedReceiveOutputIds"/> so audio /// with it). When it returns, re-tick it from <see cref="rememberedReceiveOutputIds"/> so audio
/// resumes automatically (issue #5). Only RE-ticks present-but-unticked remembered devices; it /// resumes automatically (issue #5). Only RE-ticks present-but-unticked remembered devices; it
+7
View File
@@ -27,6 +27,13 @@ internal sealed record CodecChoice(string Label, AudioTransportCodec Codec, int
internal sealed record AudioDeviceChoice(string Name, string? DeviceId, CaptureKind Kind = CaptureKind.Loopback) internal sealed record AudioDeviceChoice(string Name, string? DeviceId, CaptureKind Kind = CaptureKind.Loopback)
{ {
/// <summary>True for the special "Use Windows default" pseudo-entry that sits at the top of the
/// receive-output and send-input lists. When ticked, the current Windows default device is
/// resolved live (Render for an output follower, Capture for an input follower — derived from
/// <see cref="Kind"/>) and re-resolved whenever Windows' default changes. Its <see cref="DeviceId"/>
/// is a sentinel, never a real endpoint id.</summary>
public bool IsDefaultFollower { get; init; }
public override string ToString() => Name; public override string ToString() => Name;
} }
+1 -1
View File
@@ -14,7 +14,7 @@
tag_name on the latest GitHub release; bump it on every public release. The tag_name on the latest GitHub release; bump it on every public release. The
AssemblyVersion / FileVersion default to this value, and Assembly.GetName().Version AssemblyVersion / FileVersion default to this value, and Assembly.GetName().Version
is what the About dialog and the updater both read. --> is what the About dialog and the updater both read. -->
<Version>4.5</Version> <Version>4.6</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+15
View File
@@ -181,6 +181,21 @@ public sealed class AppConfig
/// readable in their profile files.</summary> /// readable in their profile files.</summary>
public bool KeyboardShortcutsImportOffered { get; set; } public bool KeyboardShortcutsImportOffered { get; set; }
/// <summary>If true, the "Use Windows default output" follower at the top of the received-sound
/// output list is ticked at startup, so received sound plays to whatever Windows currently calls
/// the default output and re-routes when that changes (plug in headphones, it follows). Unlike a
/// specific device tick — which can go stale and play to the wrong card — a follower can never be
/// wrong, so it's safe to persist. Default false (opt-in). <see cref="UseDefaultInputDevice"/> is
/// the same idea for the send-input list.</summary>
public bool UseDefaultOutputDevice { get; set; }
public bool UseDefaultInputDevice { get; set; }
/// <summary>Remembered answer to the "untick the other outputs/inputs when you turn on Use Windows
/// default?" prompt. Null = ask each time; true = always untick; false = never untick. Set when the
/// user ticks "Don't ask again" on that prompt. Machine-wide.</summary>
public bool? UntickOthersWhenUsingDefaultOutput { get; set; }
public bool? UntickOthersWhenUsingDefaultInput { get; set; }
/// <summary>If non-null and a profile with this title exists, RemSound skips the /// <summary>If non-null and a profile with this title exists, RemSound skips the
/// startup profile picker and loads this profile directly. Combine with /// startup profile picker and loads this profile directly. Combine with
/// <see cref="StartMinimised"/> + the Windows auto-start registry entry /// <see cref="StartMinimised"/> + the Windows auto-start registry entry