Named-peers registry + Options → Manage named peers dialog
Turns the flat friendly-name map into a proper machine-wide book (AppConfig.NamedPeers), and adds a management dialog. Held for next release. * New NamedPeer record (machine name, friendly name, last address, last-seen UTC). AppConfig gains NamedPeers; the legacy PeerFriendlyNames map is migrated into it once on load, then no longer written. The book stays machine-wide and profile-independent (it always was — this just enriches it). * Only deliberately-renamed peers are recorded (per Ed) so the list can't balloon. A named peer's last address / last-seen are updated in memory each tick while connected; persisted on address change and on app close (timestamp-only changes don't thrash the disk). * Options → Manage named peers (Alt+O, N): lists each named peer as "friendly — machine — last seen date, address"; Rename (Alt+R / F2 / double-click) reuses RenamePeerDialog; Delete (Alt+D / Del) forgets the name. Edits refresh the connected/discovered lists, the volume/pan/EQ list and details box live. * ApplyFriendlyName now records machine name + last address alongside the name. Manual updated (readme.html + MANUAL.md). Build clean; --selftest passes; deployed to both test folders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
289bb2b15c
commit
3c449d78b3
@@ -273,6 +273,7 @@ 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, speak the status line).
|
**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).
|
||||||
**Profile passwords …**| Alt+O, W| Lists every profile alongside its password, so you can view or change any of them in one place.
|
**Profile passwords …**| Alt+O, W| Lists every profile alongside its password, so you can view or change any of them in one place.
|
||||||
|
**Manage named peers …**| Alt+O, N| Opens a list of every peer you've given a friendly name to, showing each one's machine name and where and when you last connected to it. Pick one and **Rename (Alt+R** , or F2) to change its name, or **Delete (Alt+D** , or the Del key) to forget it — deleting only drops the name, the peer still connects as normal under its machine name. See the peer-naming notes on the Connectivity tab.
|
||||||
**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.
|
**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).
|
**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, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it); **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.
|
**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, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it); **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.
|
||||||
@@ -308,6 +309,8 @@ Highlight a peer in the **Connected peers** list and press **Rename peer (Alt+M)
|
|||||||
|
|
||||||
In the rename box, type the name and press OK, or press **Clear custom name (Alt+C)** to drop back to the machine name. (Leaving the box empty and pressing OK does the same.) The names are kept per machine you're using RemSound on, and apply in every profile. One case to know about: a peer you added purely by address that never announces a name has no machine name to pin to, so its friendly name is tied to the address instead and would need re-setting if that address changes.
|
In the rename box, type the name and press OK, or press **Clear custom name (Alt+C)** to drop back to the machine name. (Leaving the box empty and pressing OK does the same.) The names are kept per machine you're using RemSound on, and apply in every profile. One case to know about: a peer you added purely by address that never announces a name has no machine name to pin to, so its friendly name is tied to the address instead and would need re-setting if that address changes.
|
||||||
|
|
||||||
|
To see and tidy up all your named peers in one place — including ones that are currently offline — use **Options → Manage named peers**. It lists each one with its machine name and when you last connected, and lets you rename or delete any of them.
|
||||||
|
|
||||||
## 7. Audio inputs and outputs tab
|
## 7. Audio inputs and outputs tab
|
||||||
|
|
||||||
This tab controls everything to do with which sound devices are involved. The ASIO driver picker at the top decides whether ASIO is being used at all. The Receive side and the Send side each have their own master checkbox and their own device lists.
|
This tab controls everything to do with which sound devices are involved. The ASIO driver picker at the top decides whether ASIO is being used at all. The Receive side and the Send side each have their own master checkbox and their own device lists.
|
||||||
|
|||||||
@@ -309,6 +309,7 @@ ul, ol { padding-left: 1.4em; }
|
|||||||
<tr><td><strong>Recording settings…</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 — 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…</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 — 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…</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>Keyboard shortcuts…</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>Profile passwords…</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>Profile passwords…</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>Manage named peers…</strong></td><td>Alt+O, N</td><td>Opens a list of every peer you've given a friendly name to, showing each one's machine name and where and when you last connected to it. Pick one and <strong>Rename (Alt+R</strong>, or F2) to change its name, or <strong>Delete (Alt+D</strong>, or the Del key) to forget it — deleting only drops the name, the peer still connects as normal under its machine name. See <a href="#tab-connectivity">the peer-naming notes on the Connectivity tab</a>.</td></tr>
|
||||||
<tr><td><strong>Reset the default audio device prompt</strong></td><td>Alt+O, R</td><td>Brings back the “use only the default audio device?” question if you previously ticked “Don't ask me this again” on it. See <a href="#tab-io">Following the Windows default audio device</a>.</td></tr>
|
<tr><td><strong>Reset the default audio device prompt</strong></td><td>Alt+O, R</td><td>Brings back the “use only the default audio device?” question if you previously ticked “Don't ask me this again” 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>—</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>Enable / Disable Realtek ASIO</strong></td><td>—</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…</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> — the profiles folder, accept remote volume commands, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the <a href="#pan-eq">Volume, pan and EQ for peers tab</a> if you untick it); <strong>Audio cues</strong> — the cue list and its sounds (see <a href="#audio-cues">Audio cue sounds</a>); <strong>Startup behaviour</strong> — start minimised / with Windows / with a specific profile; <strong>Update settings</strong> — the update checks and install options; and <strong>Logging</strong> — 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>
|
<tr><td><strong>Preferences…</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> — the profiles folder, accept remote volume commands, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the <a href="#pan-eq">Volume, pan and EQ for peers tab</a> if you untick it); <strong>Audio cues</strong> — the cue list and its sounds (see <a href="#audio-cues">Audio cue sounds</a>); <strong>Startup behaviour</strong> — start minimised / with Windows / with a specific profile; <strong>Update settings</strong> — the update checks and install options; and <strong>Logging</strong> — 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>
|
||||||
@@ -347,6 +348,8 @@ ul, ol { padding-left: 1.4em; }
|
|||||||
|
|
||||||
<p>In the rename box, type the name and press OK, or press <strong>Clear custom name (Alt+C)</strong> to drop back to the machine name. (Leaving the box empty and pressing OK does the same.) The names are kept per machine you're using RemSound on, and apply in every profile. One case to know about: a peer you added purely by address that never announces a name has no machine name to pin to, so its friendly name is tied to the address instead and would need re-setting if that address changes.</p>
|
<p>In the rename box, type the name and press OK, or press <strong>Clear custom name (Alt+C)</strong> to drop back to the machine name. (Leaving the box empty and pressing OK does the same.) The names are kept per machine you're using RemSound on, and apply in every profile. One case to know about: a peer you added purely by address that never announces a name has no machine name to pin to, so its friendly name is tied to the address instead and would need re-setting if that address changes.</p>
|
||||||
|
|
||||||
|
<p>To see and tidy up all your named peers in one place — including ones that are currently offline — use <strong>Options → Manage named peers</strong>. It lists each one with its machine name and when you last connected, and lets you rename or delete any of them.</p>
|
||||||
|
|
||||||
<h2 id="tab-io">7. Audio inputs and outputs tab</h2>
|
<h2 id="tab-io">7. Audio inputs and outputs tab</h2>
|
||||||
|
|
||||||
<p>This tab controls everything to do with which sound devices are involved. The ASIO driver picker at the top decides whether ASIO is being used at all. The Receive side and the Send side each have their own master checkbox and their own device lists.</p>
|
<p>This tab controls everything to do with which sound devices are involved. The ASIO driver picker at the top decides whether ASIO is being used at all. The Receive side and the Send side each have their own master checkbox and their own device lists.</p>
|
||||||
|
|||||||
+188
-21
@@ -422,10 +422,12 @@ public sealed class MainForm : Form
|
|||||||
// Display labels for selected peers so we can render them in the dialog list even when
|
// Display labels for selected peers so we can render them in the dialog list even when
|
||||||
// discovery has temporarily lost sight of them ("Foo (192.168.1.5) — offline").
|
// discovery has temporarily lost sight of them ("Foo (192.168.1.5) — offline").
|
||||||
private readonly Dictionary<Guid, string> selectedPeerLabels = [];
|
private readonly Dictionary<Guid, string> selectedPeerLabels = [];
|
||||||
// Custom friendly names, keyed by peer identity (machine name, else address). Loaded from AppConfig
|
// The "named peers" book, keyed by peer identity (machine name, else address). Loaded from AppConfig
|
||||||
// (machine-wide) at startup and mirrored back on rename. Resolved to display names everywhere a peer
|
// (machine-wide) at startup and mirrored back on change. Resolved to display names everywhere a peer
|
||||||
// shows — connected/discovered lists, the volume/pan/EQ list, the status line, split recordings.
|
// shows — connected/discovered lists, the volume/pan/EQ list, the status line, split recordings.
|
||||||
private Dictionary<string, string> peerFriendlyNames = new(StringComparer.OrdinalIgnoreCase);
|
// Only deliberately-renamed peers live here. Last address / last-seen updated as they connect.
|
||||||
|
private Dictionary<string, NamedPeer> namedPeers = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private bool namedPeersDirty; // an address changed this session; flush on the next tick
|
||||||
// When each connected peer (by per-run InstanceId) first went healthy — for the "connected for" line.
|
// When each connected peer (by per-run InstanceId) first went healthy — for the "connected for" line.
|
||||||
private readonly Dictionary<Guid, DateTime> peerConnectedSinceUtc = [];
|
private readonly Dictionary<Guid, DateTime> peerConnectedSinceUtc = [];
|
||||||
|
|
||||||
@@ -851,9 +853,17 @@ public sealed class MainForm : Form
|
|||||||
.Select(kv => (kv.Value.Address, selectedPeerLabels.GetValueOrDefault(kv.Key, kv.Value.Address.ToString())))
|
.Select(kv => (kv.Value.Address, selectedPeerLabels.GetValueOrDefault(kv.Key, kv.Value.Address.ToString())))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Load the machine-wide friendly-name book and make every peer list resolve display names
|
// Load the machine-wide named-peers book and make every peer list resolve display names through
|
||||||
// through it. Set before any list is built so names show from the first render.
|
// it. Set before any list is built so names show from the first render.
|
||||||
peerFriendlyNames = new Dictionary<string, string>(AppConfig.Load().PeerFriendlyNames ?? new(), StringComparer.OrdinalIgnoreCase);
|
var startupCfg = AppConfig.Load();
|
||||||
|
namedPeers = new Dictionary<string, NamedPeer>(startupCfg.NamedPeers ?? new(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
// Migrate the legacy flat friendly-name map (pre-registry configs) into the book, once.
|
||||||
|
if (namedPeers.Count == 0 && startupCfg.PeerFriendlyNames is { Count: > 0 } legacy)
|
||||||
|
{
|
||||||
|
foreach (var (k, v) in legacy)
|
||||||
|
if (!string.IsNullOrWhiteSpace(v)) namedPeers[k] = new NamedPeer { MachineName = k, FriendlyName = v };
|
||||||
|
if (namedPeers.Count > 0) SaveNamedPeers();
|
||||||
|
}
|
||||||
PeerListItem.DisplayNameProvider = ResolvePeerDisplayName;
|
PeerListItem.DisplayNameProvider = ResolvePeerDisplayName;
|
||||||
|
|
||||||
// --- Set accessibility names ---
|
// --- Set accessibility names ---
|
||||||
@@ -1942,6 +1952,13 @@ public sealed class MainForm : Form
|
|||||||
};
|
};
|
||||||
profilePasswordsItem.Click += (_, _) => OpenProfilePasswordManager();
|
profilePasswordsItem.Click += (_, _) => OpenProfilePasswordManager();
|
||||||
|
|
||||||
|
// Manage the machine-wide named-peers book. 'n' mnemonic (ma&nage/&named) is free in Options.
|
||||||
|
var manageNamedPeersItem = new ToolStripMenuItem("Manage &named peers...")
|
||||||
|
{
|
||||||
|
AccessibleName = "Manage named peers",
|
||||||
|
};
|
||||||
|
manageNamedPeersItem.Click += (_, _) => ShowManageNamedPeersDialog();
|
||||||
|
|
||||||
// Reset the one-time "untick the other devices?" prompt that appears when you turn on a
|
// 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.
|
// "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.
|
// Mirrors the Realtek toggle's "let the user reverse a one-time decision" pattern.
|
||||||
@@ -1969,6 +1986,7 @@ public sealed class MainForm : Form
|
|||||||
recordingSettingsItem,
|
recordingSettingsItem,
|
||||||
keyboardItem,
|
keyboardItem,
|
||||||
profilePasswordsItem,
|
profilePasswordsItem,
|
||||||
|
manageNamedPeersItem,
|
||||||
resetDefaultDeviceItem,
|
resetDefaultDeviceItem,
|
||||||
};
|
};
|
||||||
if (realtekToggle is not null) optionItems.Add(realtekToggle);
|
if (realtekToggle is not null) optionItems.Add(realtekToggle);
|
||||||
@@ -4235,11 +4253,21 @@ public sealed class MainForm : Form
|
|||||||
if (isHealthy) { if (!peerConnectedSinceUtc.ContainsKey(item.Peer.InstanceId)) peerConnectedSinceUtc[item.Peer.InstanceId] = DateTime.UtcNow; }
|
if (isHealthy) { if (!peerConnectedSinceUtc.ContainsKey(item.Peer.InstanceId)) peerConnectedSinceUtc[item.Peer.InstanceId] = DateTime.UtcNow; }
|
||||||
else peerConnectedSinceUtc.Remove(item.Peer.InstanceId);
|
else peerConnectedSinceUtc.Remove(item.Peer.InstanceId);
|
||||||
|
|
||||||
|
// For a NAMED peer, record where and when we last saw it (for Manage named peers). Only
|
||||||
|
// an address change forces a disk write; the timestamp is flushed on close.
|
||||||
|
if (isHealthy && namedPeers.TryGetValue(PeerIdentityKey(item.Peer), out var np))
|
||||||
|
{
|
||||||
|
var addr = item.Peer.Address.ToString();
|
||||||
|
if (np.LastAddress != addr) { np.LastAddress = addr; namedPeersDirty = true; }
|
||||||
|
np.LastSeenUtc = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.ToString() != prevText)
|
if (item.ToString() != prevText)
|
||||||
{
|
{
|
||||||
connectedPeersList.RefreshItemPublic(i);
|
connectedPeersList.RefreshItemPublic(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (namedPeersDirty) { namedPeersDirty = false; SaveNamedPeers(); }
|
||||||
UpdatePeerDetails();
|
UpdatePeerDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4257,8 +4285,8 @@ public sealed class MainForm : Form
|
|||||||
/// (which for a manual-by-IP peer is the address). Used by every peer list via
|
/// (which for a manual-by-IP peer is the address). Used by every peer list via
|
||||||
/// <see cref="PeerListItem.DisplayNameProvider"/>.</summary>
|
/// <see cref="PeerListItem.DisplayNameProvider"/>.</summary>
|
||||||
private string ResolvePeerDisplayName(PeerAnnouncement peer)
|
private string ResolvePeerDisplayName(PeerAnnouncement peer)
|
||||||
=> peerFriendlyNames.TryGetValue(PeerIdentityKey(peer), out var custom) && !string.IsNullOrWhiteSpace(custom)
|
=> namedPeers.TryGetValue(PeerIdentityKey(peer), out var np) && !string.IsNullOrWhiteSpace(np.FriendlyName)
|
||||||
? custom
|
? np.FriendlyName
|
||||||
: peer.Name;
|
: peer.Name;
|
||||||
|
|
||||||
private PeerListItem? SelectedConnectedPeer() => SafeSelectedItem(connectedPeersList) as PeerListItem;
|
private PeerListItem? SelectedConnectedPeer() => SafeSelectedItem(connectedPeersList) as PeerListItem;
|
||||||
@@ -4340,27 +4368,29 @@ public sealed class MainForm : Form
|
|||||||
var machineForDisplay = string.IsNullOrWhiteSpace(peer.Name) || peer.Name == peer.Address.ToString()
|
var machineForDisplay = string.IsNullOrWhiteSpace(peer.Name) || peer.Name == peer.Address.ToString()
|
||||||
? peer.Address.ToString()
|
? peer.Address.ToString()
|
||||||
: peer.Name;
|
: peer.Name;
|
||||||
peerFriendlyNames.TryGetValue(key, out var current);
|
namedPeers.TryGetValue(key, out var current);
|
||||||
|
|
||||||
using var dlg = new RenamePeerDialog(machineForDisplay, current);
|
using var dlg = new RenamePeerDialog(machineForDisplay, current?.FriendlyName);
|
||||||
if (dlg.ShowDialog(this) != DialogResult.OK) return;
|
if (dlg.ShowDialog(this) != DialogResult.OK) return;
|
||||||
ApplyFriendlyName(key, dlg.FriendlyName);
|
ApplyFriendlyName(key, machineForDisplay, peer.Address.ToString(), dlg.FriendlyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Store (or clear) a peer's friendly name in the machine-wide book, persist it, and
|
/// <summary>Store (or clear) a peer's friendly name in the machine-wide book, persist it, and
|
||||||
/// refresh every place a peer name shows.</summary>
|
/// refresh every place a peer name shows. A blank/cleared name removes the entry entirely.</summary>
|
||||||
private void ApplyFriendlyName(string identityKey, string? name)
|
private void ApplyFriendlyName(string identityKey, string machineName, string? address, string? name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name)) peerFriendlyNames.Remove(identityKey);
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
else peerFriendlyNames[identityKey] = name.Trim();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var cfg = AppConfig.Load();
|
namedPeers.Remove(identityKey);
|
||||||
cfg.PeerFriendlyNames = new Dictionary<string, string>(peerFriendlyNames, StringComparer.OrdinalIgnoreCase);
|
|
||||||
cfg.Save();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) { logFile.Event($"peer rename: failed to save friendly name: {ex.Message}"); }
|
else
|
||||||
|
{
|
||||||
|
if (!namedPeers.TryGetValue(identityKey, out var np)) { np = new NamedPeer(); namedPeers[identityKey] = np; }
|
||||||
|
np.MachineName = string.IsNullOrWhiteSpace(machineName) ? identityKey : machineName;
|
||||||
|
np.FriendlyName = name.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(address)) { np.LastAddress = address; np.LastSeenUtc = DateTime.UtcNow; }
|
||||||
|
}
|
||||||
|
SaveNamedPeers();
|
||||||
|
|
||||||
// Refresh everywhere: connected/discovered lists rebuild their labels, the pan/EQ list re-reads
|
// Refresh everywhere: connected/discovered lists rebuild their labels, the pan/EQ list re-reads
|
||||||
// its peer names, and the details box updates.
|
// its peer names, and the details box updates.
|
||||||
@@ -4370,6 +4400,139 @@ public sealed class MainForm : Form
|
|||||||
UpdatePeerDetails();
|
UpdatePeerDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes the in-memory named-peers book to the machine-wide config, and clears the legacy
|
||||||
|
/// flat map so it isn't written back.</summary>
|
||||||
|
private void SaveNamedPeers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cfg = AppConfig.Load();
|
||||||
|
cfg.NamedPeers = new Dictionary<string, NamedPeer>(namedPeers, StringComparer.OrdinalIgnoreCase);
|
||||||
|
cfg.PeerFriendlyNames = new();
|
||||||
|
cfg.Save();
|
||||||
|
}
|
||||||
|
catch (Exception ex) { logFile.Event($"named peers: failed to save: {ex.Message}"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Options → Manage named peers. Lists the peers the user has renamed, with their machine
|
||||||
|
/// name and where/when last seen, and lets them rename (F2 / button) or delete (Del / button) any.</summary>
|
||||||
|
private void ShowManageNamedPeersDialog()
|
||||||
|
{
|
||||||
|
using var dialog = new Form
|
||||||
|
{
|
||||||
|
Text = "Manage named peers",
|
||||||
|
StartPosition = FormStartPosition.CenterParent,
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||||
|
MinimizeBox = false,
|
||||||
|
MaximizeBox = false,
|
||||||
|
ShowInTaskbar = false,
|
||||||
|
KeyPreview = true,
|
||||||
|
ClientSize = new Size(600, 420),
|
||||||
|
};
|
||||||
|
|
||||||
|
var root = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), ColumnCount = 1, RowCount = 4 };
|
||||||
|
root.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||||
|
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||||
|
|
||||||
|
var intro = new Label
|
||||||
|
{
|
||||||
|
Text = "The peers you've given a name to. Rename or delete any of them. Deleting forgets the "
|
||||||
|
+ "name only — the peer still connects as normal, under its machine name.",
|
||||||
|
AutoSize = true,
|
||||||
|
MaximumSize = new Size(560, 0),
|
||||||
|
Margin = new Padding(0, 0, 0, 8),
|
||||||
|
};
|
||||||
|
root.Controls.Add(intro, 0, 0);
|
||||||
|
|
||||||
|
// Plain label with '&' focuses the next control in tab order (the list).
|
||||||
|
var peersLabel = new Label { Text = "&Peers", AutoSize = true, Anchor = AnchorStyles.Left };
|
||||||
|
root.Controls.Add(peersLabel, 0, 1);
|
||||||
|
|
||||||
|
var list = new ListBox { Dock = DockStyle.Fill, IntegralHeight = false, AccessibleName = "Peers (Alt+P)", TabIndex = 0 };
|
||||||
|
root.Controls.Add(list, 0, 2);
|
||||||
|
|
||||||
|
var renameBtn = new Button { Text = "&Rename (Alt+R)", AutoSize = true, AccessibleName = "Rename", TabIndex = 1 };
|
||||||
|
var deleteBtn = new Button { Text = "&Delete (Alt+D)", AutoSize = true, AccessibleName = "Delete", TabIndex = 2 };
|
||||||
|
var closeBtn = new Button { Text = "Close", AutoSize = true, DialogResult = DialogResult.OK, TabIndex = 3 };
|
||||||
|
var buttonRow = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, AutoSize = true, Padding = new Padding(0, 8, 0, 0) };
|
||||||
|
buttonRow.Controls.Add(renameBtn);
|
||||||
|
buttonRow.Controls.Add(deleteBtn);
|
||||||
|
buttonRow.Controls.Add(closeBtn);
|
||||||
|
root.Controls.Add(buttonRow, 0, 3);
|
||||||
|
|
||||||
|
dialog.Controls.Add(root);
|
||||||
|
dialog.AcceptButton = closeBtn;
|
||||||
|
dialog.CancelButton = closeBtn;
|
||||||
|
|
||||||
|
void Refresh()
|
||||||
|
{
|
||||||
|
var prev = list.SelectedIndex;
|
||||||
|
list.BeginUpdate();
|
||||||
|
list.Items.Clear();
|
||||||
|
foreach (var kv in namedPeers.OrderBy(k => k.Value.FriendlyName, StringComparer.CurrentCultureIgnoreCase))
|
||||||
|
list.Items.Add(new NamedPeerItem(kv.Key, kv.Value));
|
||||||
|
list.EndUpdate();
|
||||||
|
if (list.Items.Count > 0) list.SelectedIndex = Math.Clamp(prev < 0 ? 0 : prev, 0, list.Items.Count - 1);
|
||||||
|
renameBtn.Enabled = deleteBtn.Enabled = list.Items.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSelected()
|
||||||
|
{
|
||||||
|
if (list.SelectedItem is not NamedPeerItem it) return;
|
||||||
|
using var rename = new RenamePeerDialog(it.Peer.MachineName, it.Peer.FriendlyName);
|
||||||
|
if (rename.ShowDialog(dialog) != DialogResult.OK) return;
|
||||||
|
ApplyFriendlyName(it.Key, it.Peer.MachineName, it.Peer.LastAddress, rename.FriendlyName);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteSelected()
|
||||||
|
{
|
||||||
|
if (list.SelectedItem is not NamedPeerItem it) return;
|
||||||
|
namedPeers.Remove(it.Key);
|
||||||
|
SaveNamedPeers();
|
||||||
|
lastPanEqPeerSignature = "";
|
||||||
|
SyncAllPeerLists();
|
||||||
|
RefreshPanEqPeerList();
|
||||||
|
UpdatePeerDetails();
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
renameBtn.Click += (_, _) => RenameSelected();
|
||||||
|
deleteBtn.Click += (_, _) => DeleteSelected();
|
||||||
|
list.DoubleClick += (_, _) => RenameSelected();
|
||||||
|
list.KeyDown += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.KeyCode == Keys.F2) { RenameSelected(); e.Handled = e.SuppressKeyPress = true; }
|
||||||
|
else if (e.KeyCode == Keys.Delete) { DeleteSelected(); e.Handled = e.SuppressKeyPress = true; }
|
||||||
|
};
|
||||||
|
dialog.KeyDown += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.KeyCode == Keys.Escape) { dialog.DialogResult = DialogResult.OK; dialog.Close(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
Refresh();
|
||||||
|
dialog.Load += (_, _) => list.Focus();
|
||||||
|
dialog.ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One row in the Manage named peers list: "Andre's desktop — ANDRE-DESKTOP — last seen 8 Jul 2026, 100.72.4.13".
|
||||||
|
private sealed class NamedPeerItem(string key, NamedPeer peer)
|
||||||
|
{
|
||||||
|
public string Key { get; } = key;
|
||||||
|
public NamedPeer Peer { get; } = peer;
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string seen = Peer.LastSeenUtc == default
|
||||||
|
? "not seen yet"
|
||||||
|
: $"last seen {Peer.LastSeenUtc.ToLocalTime():d MMM yyyy}"
|
||||||
|
+ (string.IsNullOrWhiteSpace(Peer.LastAddress) ? "" : $", {Peer.LastAddress}");
|
||||||
|
return $"{Peer.FriendlyName} — {Peer.MachineName} — {seen}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SyncDiscoveredList()
|
private void SyncDiscoveredList()
|
||||||
{
|
{
|
||||||
// Discovered = peers seen by discovery NOT currently connected, AND NOT manual peers
|
// Discovered = peers seen by discovery NOT currently connected, AND NOT manual peers
|
||||||
@@ -8885,6 +9048,10 @@ public sealed class MainForm : Form
|
|||||||
// recordings folder right after exit shows the file at its full size.
|
// recordings folder right after exit shows the file at its full size.
|
||||||
try { recordingController.Stop(); } catch { /* recording cleanup is best-effort */ }
|
try { recordingController.Stop(); } catch { /* recording cleanup is best-effort */ }
|
||||||
|
|
||||||
|
// Flush named-peers last-seen timestamps (updated in memory each tick, only saved to disk on
|
||||||
|
// address change during the session) so the Manage named peers dialog shows fresh times next run.
|
||||||
|
if (namedPeers.Count > 0) SaveNamedPeers();
|
||||||
|
|
||||||
base.OnFormClosing(e);
|
base.OnFormClosing(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,9 +75,14 @@ public sealed class AppConfig
|
|||||||
/// <see cref="Profile.PeerShaping"/>.</summary>
|
/// <see cref="Profile.PeerShaping"/>.</summary>
|
||||||
public bool ShowPanEqTab { get; set; } = true;
|
public bool ShowPanEqTab { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>Custom friendly names the user has given to peers, keyed by the peer's stable identity —
|
/// <summary>The machine-wide "named peers" book — peers the user has deliberately renamed, keyed by
|
||||||
/// its machine name when known, otherwise its address. Machine-wide (a name applies in every profile)
|
/// the peer's stable identity (machine name, or address for a nameless manual peer). A name applies in
|
||||||
/// and shown wherever that peer appears. Empty by default. See the Connectivity tab's Rename peer.</summary>
|
/// every profile and shows wherever that peer appears. Managed via the Connectivity tab's Rename peer
|
||||||
|
/// and the Options → Manage named peers dialog. Empty by default.</summary>
|
||||||
|
public Dictionary<string, NamedPeer> NamedPeers { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>Legacy flat name map (friendly name only). Kept so pre-registry configs still deserialise;
|
||||||
|
/// migrated into <see cref="NamedPeers"/> on load, then no longer written.</summary>
|
||||||
public Dictionary<string, string> PeerFriendlyNames { get; set; } = new();
|
public Dictionary<string, string> PeerFriendlyNames { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>If true (the default), RemSound plays the startup cue once, right after this
|
/// <summary>If true (the default), RemSound plays the startup cue once, right after this
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace RemSound.Core;
|
||||||
|
|
||||||
|
/// <summary>One entry in the machine-wide "named peers" book — a peer the user has deliberately given
|
||||||
|
/// a friendly name. Keyed in <see cref="AppConfig.NamedPeers"/> by the peer's stable identity (its
|
||||||
|
/// machine name, or its address for a nameless manual peer). Only renamed peers are recorded; unnamed
|
||||||
|
/// machines are never added, so the book stays small. Last address / last-seen are updated whenever the
|
||||||
|
/// peer connects, for the Manage named peers dialog.</summary>
|
||||||
|
public sealed class NamedPeer
|
||||||
|
{
|
||||||
|
/// <summary>The peer's machine name (for display). May equal the address for a manual-by-IP peer.</summary>
|
||||||
|
public string MachineName { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>The user's chosen friendly name. Never blank for a stored entry.</summary>
|
||||||
|
public string FriendlyName { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>The address this peer last connected from, or null if not seen since it was named.</summary>
|
||||||
|
public string? LastAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>When this peer was last seen connected (UTC), or default if not seen since named.</summary>
|
||||||
|
public DateTime LastSeenUtc { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user