Cues: front-most "missing sound file" warning + auto-disable

When a cue is switched on but its sound file can't be found (the player resolved to null - e.g.
a custom WAV was deleted, or a chosen sound is gone), RemSound now turns that cue off and tells
the user, front-most even when minimised (RestoreFromTray first), once per cue per session:
"RemSound was unable to find the <cue> sound file used when <event> happens. RemSound has set
this particular audio cue to not play for now, until a new sound file is specified."

CheckForMissingEnabledCues runs on first show (Shown) and after every cue reload (so it catches
a just-deleted custom WAV when the user closes Preferences). Per-profile cues are turned off via
the settings store, machine-wide cues via AppConfig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-13 06:43:48 +01:00
co-authored by Claude Opus 4.8
parent 75e7432fcb
commit e8dcf724d8
+67
View File
@@ -1236,6 +1236,10 @@ public sealed class MainForm : Form
// auto-jumping when the user arrows between tabs, only on first show. // auto-jumping when the user arrows between tabs, only on first show.
BeginInvoke(() => FocusListControl(connectedPeersList)); BeginInvoke(() => FocusListControl(connectedPeersList));
// First-launch pass: warn about any cue that's switched on but whose sound file is
// missing (deferred so it lands after the window is fully up and can surface).
BeginInvoke(CheckForMissingEnabledCues);
// Honour AppConfig.StartMinimised — drop straight to the tray after the // Honour AppConfig.StartMinimised — drop straight to the tray after the
// window finishes loading. Wrapped in BeginInvoke so the minimise happens // window finishes loading. Wrapped in BeginInvoke so the minimise happens
// *after* Shown completes (otherwise the form-show + form-hide collide and // *after* Shown completes (otherwise the form-show + form-hide collide and
@@ -6589,6 +6593,69 @@ public sealed class MainForm : Form
TryLoadCueSound(CueId.Show, "maximise.wav", out showSound); TryLoadCueSound(CueId.Show, "maximise.wav", out showSound);
// The app-wide checkbox tick/untick sounds live in their own service; keep them in step. // The app-wide checkbox tick/untick sounds live in their own service; keep them in step.
CheckSoundService.Reload(); CheckSoundService.Reload();
// After a reload (e.g. the user changed a cue in Preferences), warn about any cue that's
// switched on but whose sound file is missing. Skipped during construction (no window yet);
// OnShown does the first-launch pass.
CheckForMissingEnabledCues();
}
private readonly HashSet<string> reportedMissingCues = new(StringComparer.OrdinalIgnoreCase);
/// <summary>For every cue that's switched ON but whose sound file couldn't be found (player is
/// null - e.g. a custom WAV was deleted, or a chosen sound is gone), turn the cue off and tell
/// the user, front-most, once per cue. They re-enable it by choosing a sound in Preferences.</summary>
private void CheckForMissingEnabledCues()
{
if (!IsHandleCreated) return; // need a window so the warning can surface
var cfg = AppConfig.Load();
(CuePlayer? Player, bool Enabled, Action Disable, string Name, string When)[] cues =
{
(connectSound, settings.LoadEnableConnectCue(), () => settings.SaveEnableConnectCue(false), "connect", "a peer connects"),
(disconnectSound, settings.LoadEnableDisconnectCue(), () => settings.SaveEnableDisconnectCue(false), "disconnect", "a peer disconnects"),
(recordStartSound, settings.LoadEnableRecordStartCue(), () => settings.SaveEnableRecordStartCue(false), "recording start", "you start recording"),
(recordStopSound, settings.LoadEnableRecordStopCue(), () => settings.SaveEnableRecordStopCue(false), "recording stop", "you stop recording"),
(saveSound, settings.LoadEnableSaveCue(), () => settings.SaveEnableSaveCue(false), "profile saved", "you save a profile"),
(profileSwitchSound, settings.LoadEnableProfileSwitchCue(), () => settings.SaveEnableProfileSwitchCue(false), "profile switched", "you switch profile"),
(profileMenuOpenSound, settings.LoadEnableProfileMenuOpenCue(), () => settings.SaveEnableProfileMenuOpenCue(false), "profile menu open", "the quick profile menu opens"),
(updateSound, settings.LoadEnableUpdateCue(), () => settings.SaveEnableUpdateCue(false), "update", "an update is about to install"),
(sendOnSound, cfg.EnableSendOnCue, () => SetMachineCueEnabled(c => c.EnableSendOnCue = false), "send turned on", "you turn sending on"),
(sendOffSound, cfg.EnableSendOffCue, () => SetMachineCueEnabled(c => c.EnableSendOffCue = false), "send turned off", "you turn sending off"),
(receiveOnSound, cfg.EnableReceiveOnCue, () => SetMachineCueEnabled(c => c.EnableReceiveOnCue = false), "receive turned on", "you turn receiving on"),
(receiveOffSound, cfg.EnableReceiveOffCue, () => SetMachineCueEnabled(c => c.EnableReceiveOffCue = false), "receive turned off", "you turn receiving off"),
(hideSound, cfg.EnableHideCue, () => SetMachineCueEnabled(c => c.EnableHideCue = false), "minimise", "RemSound minimises to the tray"),
(showSound, cfg.EnableShowCue, () => SetMachineCueEnabled(c => c.EnableShowCue = false), "restore", "RemSound is restored from the tray"),
};
foreach (var c in cues)
{
if (c.Enabled && c.Player is null)
{
c.Disable();
ReportMissingCueSound(c.Name, c.When);
}
}
}
private static void SetMachineCueEnabled(Action<AppConfig> set)
{
var c = AppConfig.Load();
set(c);
try { c.Save(); } catch { /* harmless — the cue is silent regardless this session */ }
}
/// <summary>Surface the "couldn't find a cue's sound file" message, front-most even when RemSound
/// is minimised, once per cue per session. The cue has already been turned off by the caller.</summary>
private void ReportMissingCueSound(string name, string when)
{
if (!reportedMissingCues.Add(name)) return;
logFile.Event($"cue sound '{name}': enabled but file missing — cue turned off, informing the user");
BeginInvoke(() =>
{
try { RestoreFromTray(); } catch { /* surfacing is best-effort */ }
MessageBox.Show(this,
$"RemSound was unable to find the {name} sound file used when {when}. " +
"RemSound has set this particular audio cue to not play for now, until a new sound file is specified.",
"RemSound — missing sound file", MessageBoxButtons.OK, MessageBoxIcon.Warning);
});
} }
/// <summary> /// <summary>