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:
co-authored by
Claude Opus 4.8
parent
0df2a576fc
commit
c7d422e3ba
@@ -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 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" };
|
||||
// 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
|
||||
// now live inside PreferencesDialog. The form-level logFile.Enabled gate is set
|
||||
// directly from the settings store at startup (see ApplyLoggingEnabled).
|
||||
@@ -2797,7 +2805,7 @@ public sealed class MainForm : Form
|
||||
Dock = DockStyle.Fill,
|
||||
Padding = new Padding(12),
|
||||
ColumnCount = 2,
|
||||
RowCount = 5,
|
||||
RowCount = 6,
|
||||
AutoScroll = true,
|
||||
};
|
||||
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.
|
||||
|
||||
// === Layout ===
|
||||
// 5 rows: 0–2 the three peer lists, 3 manual-add, 4 connection-status readout.
|
||||
panel.RowCount = 5;
|
||||
// 6 rows: 0–2 the three peer lists, 3 manual-add, 4 the lock-to-fixed-addresses toggle, 5 status.
|
||||
panel.RowCount = 6;
|
||||
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, 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(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.
|
||||
var statusLabel = new MnemonicLabel { Text = "Connection status (Alt+&S)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = statusReadout };
|
||||
statusLabel.Click += (_, _) => statusReadout.Focus();
|
||||
panel.Controls.Add(statusLabel, 0, 4);
|
||||
panel.Controls.Add(statusReadout, 1, 4);
|
||||
panel.Controls.Add(statusLabel, 0, 5);
|
||||
panel.Controls.Add(statusReadout, 1, 5);
|
||||
|
||||
// Initial render so the box has content the moment the user tabs into it.
|
||||
RefreshStatusReadout();
|
||||
@@ -5226,6 +5244,16 @@ public sealed class MainForm : Form
|
||||
foreach (var peer in discovery.Peers) 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
|
||||
// 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.
|
||||
|
||||
@@ -177,6 +177,12 @@ public sealed class Profile
|
||||
/// list at save time. On load, RemSound auto-connects to any of these that resolve.</summary>
|
||||
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 ===
|
||||
public HotkeyRecord? ReceiveMuteHotkey { get; set; }
|
||||
public HotkeyRecord? SendMuteHotkey { get; set; }
|
||||
|
||||
@@ -282,6 +282,19 @@ public sealed class RemSoundSettingsStore
|
||||
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
|
||||
/// 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.
|
||||
@@ -537,6 +550,7 @@ public sealed class RemSoundSettingsStore
|
||||
SendRate = profile.SendRate,
|
||||
TightLatencyMode = profile.TightLatencyMode,
|
||||
PriorityMode = profile.PriorityMode,
|
||||
LockPeerAddresses = profile.LockPeerAddresses,
|
||||
Smoothness = profile.Smoothness,
|
||||
ConcealmentArtifact = (ConcealmentArtifact)profile.ConcealmentArtifactRaw,
|
||||
MuteConnectionCues = profile.MuteConnectionCues,
|
||||
@@ -581,6 +595,7 @@ public sealed class RemSoundSettingsStore
|
||||
if (s.SendRate is SendRate sr) profile.SendRate = sr;
|
||||
if (s.TightLatencyMode is bool tl) profile.TightLatencyMode = tl;
|
||||
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.ConcealmentArtifact is ConcealmentArtifact ca) profile.ConcealmentArtifactRaw = (int)ca;
|
||||
if (s.MuteConnectionCues is bool mc) profile.MuteConnectionCues = mc;
|
||||
@@ -626,6 +641,7 @@ public sealed class RemSoundSettingsStore
|
||||
public SendRate? SendRate { get; set; }
|
||||
public bool? TightLatencyMode { get; set; }
|
||||
public bool? PriorityMode { get; set; }
|
||||
public bool? LockPeerAddresses { get; set; }
|
||||
public int? Smoothness { get; set; }
|
||||
public ConcealmentArtifact? ConcealmentArtifact { get; set; }
|
||||
public bool? MuteConnectionCues { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user