Add per-profile "Lock to exact peer addresses" setting (#17, held for next release)

Feature request from the same singer: a way to pin a profile to exact peer addresses
so RemSound never matches the other computer by its advertised name and never switches
to a different address it discovers — for a machine reachable at two addresses at once
(VPN + LAN), they want only the one they chose, and would rather the connection die than
wander. The logical end of the v4.7/v4.8 direction.

New per-profile Profile.LockPeerAddresses (default false), round-tripped through
RemSoundSettingsStore (ApplyProfile/CopyTo + Load/SaveLockPeerAddresses), mirroring the
PriorityMode pattern. New AccessibleCheckBox on the Connectivity tab ("Lock to these exact
peer addresses, no matter what — never follow names or switch", Alt+L), saved with the
profile, marks the profile dirty on change. When set, RefreshKnownPeers early-returns
before the discovered-peer merge and the address-follow, so the profile's peers stay
exactly as set (the allow-list is still pushed). Off = unchanged behaviour.

Manual: rewrote the "Connecting to one specific IP" section (which over-promised that
add-by-IP "can never drift" — the merge/follow could) into an unambiguous "Locking a
profile to one exact address" section, plus a Connectivity-tab control-table row.

Held for the next release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-28 15:31:50 +01:00
co-authored by Claude Opus 4.8
parent 0df2a576fc
commit c7d422e3ba
5 changed files with 86 additions and 17 deletions
+17 -6
View File
@@ -295,6 +295,7 @@ Control| Shortcut| What it does
**Discovered peers**| Alt+D| People RemSound has heard from in the last few seconds. Tick someone to connect to them. **Discovered peers**| Alt+D| People RemSound has heard from in the last few seconds. Tick someone to connect to them.
**Remembered peers**| Alt+R| People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect. **Remembered peers**| Alt+R| People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect.
**Add peer by IP**| Alt+A| Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects. **Add peer by IP**| Alt+A| Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects.
**Lock to these exact peer addresses**| Alt+L| 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 Locking a profile to one exact address.
**Connection status**| Alt+S| 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. **Connection status**| Alt+S| 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.
## 7. Audio inputs and outputs tab ## 7. Audio inputs and outputs tab
@@ -463,18 +464,28 @@ List| Contents| What ticking does
There's also the **Add peer by IP (Alt+A)** button, which opens a small box for a computer name or address. It's useful for a first connection over a VPN, where discovery hasn't reached the other computer yet. There's also the **Add peer by IP (Alt+A)** button, which opens a small box for a computer name or address. It's useful for a first connection over a VPN, where discovery hasn't reached the other computer yet.
### Connecting to one specific IP address (and only that one) ### Locking a profile to one exact address (and only that one)
RemSound can reach another computer two ways, and the difference matters if a computer has more than one IP address: There are two ways to reach another computer, and the difference matters if that computer has more than one address:
* **By name** — ticking someone in **Discovered peers**. RemSound found them from their announcement and the entry shows their computer name. This is the easy, automatic way on an ordinary network. * **By name** — ticking someone in **Discovered peers**. RemSound found them from their announcement, and the entry shows their computer name. This is the easy, automatic way on an ordinary network.
* **By a fixed IP** — **Add peer by IP (Alt+A)** , then type the exact address, for example `10.8.0.1`. RemSound talks to _that address and nothing else_ : it doesn't look up a name, so it can never drift to a different IP, and if that address isn't reachable, no connection is made at all. * **By a fixed address** — **Add peer by IP (Alt+A)** , then type the exact address, for example `10.8.0.1`.
So to force a connection to one specific IP — say a machine that appears under two addresses and you only want one of them — add it by that IP and tick only that entry. If the same machine also turns up in Discovered peers under its _other_ address, it shows as a separate entry that you simply leave unticked; RemSound keeps the two apart by their actual address, so they never merge. Normally RemSound is helpful about addresses, even one you typed: it can recognise that the address belongs to a computer it also hears announcing itself on the network, and if that computer turns up at a _different_ address — a new IP after a reboot, or a second address over a VPN — RemSound follows it there automatically so your sound keeps flowing. Most of the time that is exactly what you want.
Either way, **the profile remembers exactly what you ticked.** Tick a discovered name and the profile reconnects by that name next time; add and tick an IP and the profile reconnects to that exact IP. So the way to make a profile always use one specific IP is simply to add and tick it by that IP, then save the profile. Sometimes you want the opposite: connect to one specific address and nothing else, _ever_. For that, tick **Lock to these exact peer addresses, no matter what (Alt+L)** on the Connectivity tab. It is off by default and is saved with the profile, so you can lock one profile down while another keeps the automatic behaviour. When it is ticked, for that profile:
* RemSound uses **only the exact address you set** for each peer.
* It will **not** look the other computer up by the name it advertises on the network, and it will **not** switch to any other address it discovers — even if that same computer is reachable at a second address at the same moment.
* If the address you set stops working — the computer is off, the network is down, or it has moved to a new address — the connection simply **waits, or drops, until that exact address is reachable again**. RemSound will not go looking for another way through.
**When you use this, set your peers by their IP address** (Add peer by IP, then type the address). That is the setting for a machine that appears under two addresses when you only ever want one of them: add that one IP, tick the box, and save the profile — the connection will use that address and never wander.
With the box left unticked, RemSound behaves as it always has: the profile remembers exactly what you ticked, reconnects by that name or address next time, and quietly follows a peer that genuinely moves to a new address.
### You only hear peers you've ticked ### You only hear peers you've ticked
+14 -6
View File
@@ -333,6 +333,7 @@ ul, ol { padding-left: 1.4em; }
<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>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.</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>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 &mdash; 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 &mdash; 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> <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 &mdash; 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>
</table> </table>
@@ -510,14 +511,21 @@ Audient USB Audio ASIO Driver &mdash; Pair 3 (channels 5/6): Loop-back 1 (L) / L
<p>There's also the <strong>Add peer by IP (Alt+A)</strong> button, which opens a small box for a computer name or address. It's useful for a first connection over a VPN, where discovery hasn't reached the other computer yet.</p> <p>There's also the <strong>Add peer by IP (Alt+A)</strong> button, which opens a small box for a computer name or address. It's useful for a first connection over a VPN, where discovery hasn't reached the other computer yet.</p>
<h3>Connecting to one specific IP address (and only that one)</h3> <h3 id="lock-addresses">Locking a profile to one exact address (and only that one)</h3>
<p>RemSound can reach another computer two ways, and the difference matters if a computer has more than one IP address:</p> <p>There are two ways to reach another computer, and the difference matters if that computer has more than one address:</p>
<ul> <ul>
<li><strong>By name</strong> &mdash; ticking someone in <strong>Discovered peers</strong>. RemSound found them from their announcement and the entry shows their computer name. This is the easy, automatic way on an ordinary network.</li> <li><strong>By name</strong> &mdash; ticking someone in <strong>Discovered peers</strong>. RemSound found them from their announcement, and the entry shows their computer name. This is the easy, automatic way on an ordinary network.</li>
<li><strong>By a fixed IP</strong> &mdash; <strong>Add peer by IP (Alt+A)</strong>, then type the exact address, for example <code>10.8.0.1</code>. RemSound talks to <em>that address and nothing else</em>: it doesn't look up a name, so it can never drift to a different IP, and if that address isn't reachable, no connection is made at all.</li> <li><strong>By a fixed address</strong> &mdash; <strong>Add peer by IP (Alt+A)</strong>, then type the exact address, for example <code>10.8.0.1</code>.</li>
</ul> </ul>
<p>So to force a connection to one specific IP &mdash; say a machine that appears under two addresses and you only want one of them &mdash; add it by that IP and tick only that entry. If the same machine also turns up in Discovered peers under its <em>other</em> address, it shows as a separate entry that you simply leave unticked; RemSound keeps the two apart by their actual address, so they never merge.</p> <p>Normally RemSound is helpful about addresses, even one you typed: it can recognise that the address belongs to a computer it also hears announcing itself on the network, and if that computer turns up at a <em>different</em> address &mdash; a new IP after a reboot, or a second address over a VPN &mdash; RemSound follows it there automatically so your sound keeps flowing. Most of the time that is exactly what you want.</p>
<p>Either way, <strong>the profile remembers exactly what you ticked.</strong> Tick a discovered name and the profile reconnects by that name next time; add and tick an IP and the profile reconnects to that exact IP. So the way to make a profile always use one specific IP is simply to add and tick it by that IP, then save the profile.</p> <p>Sometimes you want the opposite: connect to one specific address and nothing else, <em>ever</em>. For that, tick <strong>Lock to these exact peer addresses, no matter what (Alt+L)</strong> on the Connectivity tab. It is off by default and is saved with the profile, so you can lock one profile down while another keeps the automatic behaviour. When it is ticked, for that profile:</p>
<ul>
<li>RemSound uses <strong>only the exact address you set</strong> for each peer.</li>
<li>It will <strong>not</strong> look the other computer up by the name it advertises on the network, and it will <strong>not</strong> switch to any other address it discovers &mdash; even if that same computer is reachable at a second address at the same moment.</li>
<li>If the address you set stops working &mdash; the computer is off, the network is down, or it has moved to a new address &mdash; the connection simply <strong>waits, or drops, until that exact address is reachable again</strong>. RemSound will not go looking for another way through.</li>
</ul>
<p><strong>When you use this, set your peers by their IP address</strong> (Add peer by IP, then type the address). That is the setting for a machine that appears under two addresses when you only ever want one of them: add that one IP, tick the box, and save the profile &mdash; the connection will use that address and never wander.</p>
<p>With the box left unticked, RemSound behaves as it always has: the profile remembers exactly what you ticked, reconnects by that name or address next time, and quietly follows a peer that genuinely moves to a new address.</p>
<h3>You only hear peers you've ticked</h3> <h3>You only hear peers you've ticked</h3>
<p>Even if a peer is sending sound your way, you won't hear it until you've ticked their checkbox. This is deliberate &mdash; connecting is a step where you give your consent. A peer's name appears in Discovered the moment they come online, but they can't make any sound on your speakers until you say yes.</p> <p>Even if a peer is sending sound your way, you won't hear it until you've ticked their checkbox. This is deliberate &mdash; connecting is a step where you give your consent. A peer's name appears in Discovered the moment they come online, but they can't make any sound on your speakers until you say yes.</p>
+33 -5
View File
@@ -238,6 +238,14 @@ public sealed class MainForm : Form
private readonly CheckedListBox rememberedPeersList = new() { CheckOnClick = true, Width = 430, Height = 90, AccessibleName = "Remembered peers (Alt+R)" }; private readonly CheckedListBox rememberedPeersList = new() { CheckOnClick = true, Width = 430, Height = 90, AccessibleName = "Remembered peers (Alt+R)" };
private readonly Label rememberedPeersStatus = new() { AutoSize = true, Text = "No remembered peer selected." }; private readonly Label rememberedPeersStatus = new() { AutoSize = true, Text = "No remembered peer selected." };
private readonly Button manualAddButton = new() { Text = "Add peer by IP (Alt+&A)", AutoSize = true, AccessibleName = "Add peer by IP" }; private readonly Button manualAddButton = new() { Text = "Add peer by IP (Alt+&A)", AutoSize = true, AccessibleName = "Add peer by IP" };
// Per-profile "pin to exact addresses" toggle (#17). When ticked, RefreshKnownPeers skips the
// discovered-peer merge and the address-follow, so the profile's peers stay exactly as the user set.
private readonly AccessibleCheckBox lockPeerAddressesBox = new()
{
Text = "Lock to these exact peer addresses, no matter what — never follow names or switch (Alt+&L)",
AccessibleName = "Lock to these exact peer addresses, no matter what; never follow names or switch address",
AutoSize = true,
};
// loggingBox + writeLogsNowButton field instances retired 2026-05-08 — both controls // loggingBox + writeLogsNowButton field instances retired 2026-05-08 — both controls
// now live inside PreferencesDialog. The form-level logFile.Enabled gate is set // now live inside PreferencesDialog. The form-level logFile.Enabled gate is set
// directly from the settings store at startup (see ApplyLoggingEnabled). // directly from the settings store at startup (see ApplyLoggingEnabled).
@@ -2797,7 +2805,7 @@ public sealed class MainForm : Form
Dock = DockStyle.Fill, Dock = DockStyle.Fill,
Padding = new Padding(12), Padding = new Padding(12),
ColumnCount = 2, ColumnCount = 2,
RowCount = 5, RowCount = 6,
AutoScroll = true, AutoScroll = true,
}; };
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
@@ -2921,19 +2929,29 @@ public sealed class MainForm : Form
// Preferences dialog (File → Preferences, Ctrl+P) as the last two items. // Preferences dialog (File → Preferences, Ctrl+P) as the last two items.
// === Layout === // === Layout ===
// 5 rows: 02 the three peer lists, 3 manual-add, 4 connection-status readout. // 6 rows: 02 the three peer lists, 3 manual-add, 4 the lock-to-fixed-addresses toggle, 5 status.
panel.RowCount = 5; panel.RowCount = 6;
FormLayoutRows.AddCheckedListRow(panel, 0, "Connected peers (Alt+&C)", connectedPeersList, connectedPeersStatus, FocusListControl); FormLayoutRows.AddCheckedListRow(panel, 0, "Connected peers (Alt+&C)", connectedPeersList, connectedPeersStatus, FocusListControl);
FormLayoutRows.AddCheckedListRow(panel, 1, "Discovered peers (Alt+&D)", discoveredPeersList, discoveredPeersStatus, FocusListControl); FormLayoutRows.AddCheckedListRow(panel, 1, "Discovered peers (Alt+&D)", discoveredPeersList, discoveredPeersStatus, FocusListControl);
FormLayoutRows.AddCheckedListRow(panel, 2, "Remembered peers (Alt+&R)", rememberedPeersList, rememberedPeersStatus, FocusListControl); FormLayoutRows.AddCheckedListRow(panel, 2, "Remembered peers (Alt+&R)", rememberedPeersList, rememberedPeersStatus, FocusListControl);
panel.Controls.Add(new Label { Text = "Manual peer", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 3); panel.Controls.Add(new Label { Text = "Manual peer", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 3);
panel.Controls.Add(manualAddButton, 1, 3); panel.Controls.Add(manualAddButton, 1, 3);
lockPeerAddressesBox.Checked = settings.LoadLockPeerAddresses();
lockPeerAddressesBox.CheckedChanged += (_, _) =>
{
settings.SaveLockPeerAddresses(lockPeerAddressesBox.Checked);
MarkProfileDirty();
logFile.Event($"lock peer addresses: {(lockPeerAddressesBox.Checked ? "on" : "off")}");
};
panel.Controls.Add(lockPeerAddressesBox, 0, 4);
panel.SetColumnSpan(lockPeerAddressesBox, 2);
// Connection status readout — last row, tab-into-able. // Connection status readout — last row, tab-into-able.
var statusLabel = new MnemonicLabel { Text = "Connection status (Alt+&S)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = statusReadout }; var statusLabel = new MnemonicLabel { Text = "Connection status (Alt+&S)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = statusReadout };
statusLabel.Click += (_, _) => statusReadout.Focus(); statusLabel.Click += (_, _) => statusReadout.Focus();
panel.Controls.Add(statusLabel, 0, 4); panel.Controls.Add(statusLabel, 0, 5);
panel.Controls.Add(statusReadout, 1, 4); panel.Controls.Add(statusReadout, 1, 5);
// Initial render so the box has content the moment the user tabs into it. // Initial render so the box has content the moment the user tabs into it.
RefreshStatusReadout(); RefreshStatusReadout();
@@ -5226,6 +5244,16 @@ public sealed class MainForm : Form
foreach (var peer in discovery.Peers) knownPeers[peer.InstanceId] = peer; foreach (var peer in discovery.Peers) knownPeers[peer.InstanceId] = peer;
foreach (var peer in manualPeers.Values) knownPeers[peer.InstanceId] = peer; foreach (var peer in manualPeers.Values) knownPeers[peer.InstanceId] = peer;
// Locked-to-fixed-addresses profiles (#17): the user wants RemSound to use exactly the peer
// addresses they set and never substitute one found on the network. Skip the discovered-peer
// merge (which would attach a computer name to their selection) and the address-follow below;
// the selection's allow-list is still pushed so audio flows to those exact addresses, unchanged.
if (settings.LoadLockPeerAddresses())
{
PushAllowedReceiveSenders();
return;
}
// Dedupe by endpoint (address:port). When a manual peer (typed by IP) and a discovered // Dedupe by endpoint (address:port). When a manual peer (typed by IP) and a discovered
// peer (broadcasting hostname) point to the same machine, drop the manual entry and // peer (broadcasting hostname) point to the same machine, drop the manual entry and
// forward any active selection to the discovered peer so the user doesn't lose it. // forward any active selection to the discovered peer so the user doesn't lose it.
+6
View File
@@ -177,6 +177,12 @@ public sealed class Profile
/// list at save time. On load, RemSound auto-connects to any of these that resolve.</summary> /// list at save time. On load, RemSound auto-connects to any of these that resolve.</summary>
public List<string> SelectedConnectedPeers { get; set; } = []; public List<string> SelectedConnectedPeers { get; set; } = [];
/// <summary>When true, this profile is LOCKED to the exact peer addresses set above. RemSound uses
/// only those addresses, never matches the other computer by the name it advertises on the network,
/// and never switches to a different address it discovers — even if the set address stops working
/// (the connection simply waits or drops rather than moving). Off by default. (#17)</summary>
public bool LockPeerAddresses { get; set; }
// === Hotkeys === // === Hotkeys ===
public HotkeyRecord? ReceiveMuteHotkey { get; set; } public HotkeyRecord? ReceiveMuteHotkey { get; set; }
public HotkeyRecord? SendMuteHotkey { get; set; } public HotkeyRecord? SendMuteHotkey { get; set; }
@@ -282,6 +282,19 @@ public sealed class RemSoundSettingsStore
Save(s); Save(s);
} }
/// <summary>When true, this profile is locked to the exact peer addresses the user set — RemSound
/// uses only those, never follows the other computer by its advertised name and never switches to a
/// discovered address. Off by default; opt in per profile. (#17)</summary>
public bool LoadLockPeerAddresses(bool defaultValue = false) =>
Try(() => Load()?.LockPeerAddresses) ?? defaultValue;
public void SaveLockPeerAddresses(bool value)
{
var s = Load() ?? new Settings();
s.LockPeerAddresses = value;
Save(s);
}
/// <summary>Suppresses the connect/disconnect sound cues that play when a peer's health /// <summary>Suppresses the connect/disconnect sound cues that play when a peer's health
/// transitions to/from Healthy. Off by default — cues are on. Saved per-profile so users /// transitions to/from Healthy. Off by default — cues are on. Saved per-profile so users
/// who don't want them in a given setup don't have to remember to mute every session. /// who don't want them in a given setup don't have to remember to mute every session.
@@ -537,6 +550,7 @@ public sealed class RemSoundSettingsStore
SendRate = profile.SendRate, SendRate = profile.SendRate,
TightLatencyMode = profile.TightLatencyMode, TightLatencyMode = profile.TightLatencyMode,
PriorityMode = profile.PriorityMode, PriorityMode = profile.PriorityMode,
LockPeerAddresses = profile.LockPeerAddresses,
Smoothness = profile.Smoothness, Smoothness = profile.Smoothness,
ConcealmentArtifact = (ConcealmentArtifact)profile.ConcealmentArtifactRaw, ConcealmentArtifact = (ConcealmentArtifact)profile.ConcealmentArtifactRaw,
MuteConnectionCues = profile.MuteConnectionCues, MuteConnectionCues = profile.MuteConnectionCues,
@@ -581,6 +595,7 @@ public sealed class RemSoundSettingsStore
if (s.SendRate is SendRate sr) profile.SendRate = sr; if (s.SendRate is SendRate sr) profile.SendRate = sr;
if (s.TightLatencyMode is bool tl) profile.TightLatencyMode = tl; if (s.TightLatencyMode is bool tl) profile.TightLatencyMode = tl;
if (s.PriorityMode is bool pm) profile.PriorityMode = pm; if (s.PriorityMode is bool pm) profile.PriorityMode = pm;
if (s.LockPeerAddresses is bool lpa) profile.LockPeerAddresses = lpa;
if (s.Smoothness is int sm) profile.Smoothness = sm; if (s.Smoothness is int sm) profile.Smoothness = sm;
if (s.ConcealmentArtifact is ConcealmentArtifact ca) profile.ConcealmentArtifactRaw = (int)ca; if (s.ConcealmentArtifact is ConcealmentArtifact ca) profile.ConcealmentArtifactRaw = (int)ca;
if (s.MuteConnectionCues is bool mc) profile.MuteConnectionCues = mc; if (s.MuteConnectionCues is bool mc) profile.MuteConnectionCues = mc;
@@ -626,6 +641,7 @@ public sealed class RemSoundSettingsStore
public SendRate? SendRate { get; set; } public SendRate? SendRate { get; set; }
public bool? TightLatencyMode { get; set; } public bool? TightLatencyMode { get; set; }
public bool? PriorityMode { get; set; } public bool? PriorityMode { get; set; }
public bool? LockPeerAddresses { get; set; }
public int? Smoothness { get; set; } public int? Smoothness { get; set; }
public ConcealmentArtifact? ConcealmentArtifact { get; set; } public ConcealmentArtifact? ConcealmentArtifact { get; set; }
public bool? MuteConnectionCues { get; set; } public bool? MuteConnectionCues { get; set; }