Release v3.2: update sound cue + single-instance bring-to-front fix

- New "Update sound" cue (CueId.Update / update.wav). Plays just before an
  update starts installing, on every path (manual, background-silent,
  startup-silent), so a silent background update gives an audible heads-up
  before RemSound closes to restart. Per-profile mute + custom-sound override
  in Preferences, same infrastructure as the other cues:
    * Profile.EnableUpdateCue + RemSoundSettingsStore Load/Save + round-trip
    * MainForm updateSound field, TryLoadCueSound, play in InstallUpdateAsync
      gated by LoadEnableUpdateCue
    * PreferencesDialog "Update sound" CueRow + ResolveCueFilePath mapping
    * update.wav shipped in sounds\ via csproj Content
- Single-instance "switch to the running copy" now actually brings the window
  to the front. The second copy grants the running copy foreground rights via
  AllowSetForegroundWindow before signalling, and lingers briefly so it can
  raise itself before we exit — without this, Windows' foreground lock left
  the running window only flashing in the taskbar (Ed's report).
- Docs: About box v3.2 block, RELEASE_NOTES.md, manual cue section (now seven
  cues, with the update cue described), MANUAL.md regenerated.
- Version bumped 3.1.3 -> 3.2.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-05-31 17:58:25 +01:00
co-authored by Claude Opus 4.8
parent 9ab0a1a20a
commit cdcac859c4
11 changed files with 130 additions and 27 deletions
+5 -1
View File
@@ -107,10 +107,14 @@ public sealed class Profile
public bool? EnableRecordStopCue { get; set; }
public bool? EnableSaveCue { get; set; }
public bool? EnableProfileSwitchCue { get; set; }
/// <summary>Plays the update cue just before an update starts installing (manual or
/// silent). Null = unset → defaults to on, so a silent background update still gives an
/// audible heads-up. Added 2026-05-31.</summary>
public bool? EnableUpdateCue { get; set; }
/// <summary>Per-cue custom WAV file overrides, keyed by the well-known cue id (<c>connect</c>,
/// <c>disconnect</c>, <c>record-start</c>, <c>record-stop</c>, <c>save</c>,
/// <c>profile-switch</c>) and valued with the absolute filesystem path to the user's chosen
/// <c>profile-switch</c>, <c>update</c>) and valued with the absolute filesystem path to the user's chosen
/// WAV. Per-profile (moved here from AppConfig 2026-05-28) so a "live monitoring" profile
/// can have one set of custom sounds and a "recording" profile a different set. Missing
/// keys mean "use the default sound shipped in the sounds\ folder next to RemSound.exe".
@@ -428,6 +428,16 @@ public sealed class RemSoundSettingsStore
Save(s);
}
public bool LoadEnableUpdateCue() =>
Try(() => Load()?.EnableUpdateCue) ?? true;
public void SaveEnableUpdateCue(bool value)
{
var s = Load() ?? new Settings();
s.EnableUpdateCue = value;
Save(s);
}
/// <summary>The user's custom WAV path for a given cue, or null when they're using the
/// default. Per-profile (lives on <see cref="Profile.CustomCuePaths"/>) so different
/// profiles can carry different cue palettes.</summary>
@@ -587,6 +597,7 @@ public sealed class RemSoundSettingsStore
EnableRecordStopCue = profile.EnableRecordStopCue,
EnableSaveCue = profile.EnableSaveCue,
EnableProfileSwitchCue = profile.EnableProfileSwitchCue,
EnableUpdateCue = profile.EnableUpdateCue,
// Defensive copy so cache mutations don't leak into the in-memory Profile graph
// (and vice-versa). Profile is loaded once at startup; the cache evolves through
// the session and is written back via CopyTo on save.
@@ -643,6 +654,7 @@ public sealed class RemSoundSettingsStore
profile.EnableRecordStopCue = s.EnableRecordStopCue;
profile.EnableSaveCue = s.EnableSaveCue;
profile.EnableProfileSwitchCue = s.EnableProfileSwitchCue;
profile.EnableUpdateCue = s.EnableUpdateCue;
profile.CustomCuePaths = s.CustomCuePaths is null
? new Dictionary<string, string>()
: new Dictionary<string, string>(s.CustomCuePaths);
@@ -713,6 +725,7 @@ public sealed class RemSoundSettingsStore
public bool? EnableRecordStopCue { get; set; }
public bool? EnableSaveCue { get; set; }
public bool? EnableProfileSwitchCue { get; set; }
public bool? EnableUpdateCue { get; set; }
public Dictionary<string, string>? CustomCuePaths { get; set; }
public RecordingSettings? RecordingSettings { get; set; }
}