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:
co-authored by
Claude Opus 4.8
parent
9ab0a1a20a
commit
cdcac859c4
@@ -1,5 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RemSound.App;
|
||||
|
||||
@@ -113,10 +114,31 @@ internal sealed class SingleInstanceCoordinator : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AllowSetForegroundWindow(int dwProcessId);
|
||||
|
||||
/// <summary>Signal whichever copy currently owns the lock to bring itself to the front.
|
||||
/// Called by a SECOND copy that chose "switch to the running copy".</summary>
|
||||
/// Called by a SECOND copy that chose "switch to the running copy".
|
||||
///
|
||||
/// Windows blocks <c>SetForegroundWindow</c> from a process that didn't receive the most
|
||||
/// recent user input. When the user launches this second copy from (say) an Explorer
|
||||
/// window and clicks "switch", the input belongs to US, not the running copy — so the
|
||||
/// running copy's own SetForegroundWindow is denied and its window only flashes in the
|
||||
/// taskbar instead of coming forward (the bug Ed reported). The documented fix is for the
|
||||
/// process that currently holds the foreground right — us, right now — to hand it to the
|
||||
/// running copy via <see cref="AllowSetForegroundWindow"/> BEFORE signalling. We then
|
||||
/// linger briefly so the running copy can raise itself while the grant is fresh and the
|
||||
/// foreground hasn't churned from us exiting.</summary>
|
||||
public static void SignalExistingToActivate()
|
||||
{
|
||||
// Grant every other RemSound process the one-shot right to pull itself to the front.
|
||||
foreach (var p in OtherInstances(Environment.ProcessId))
|
||||
{
|
||||
try { AllowSetForegroundWindow(p.Id); } catch { /* best-effort */ }
|
||||
p.Dispose();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (EventWaitHandle.TryOpenExisting(ActivateEventName, out var ev))
|
||||
@@ -128,6 +150,12 @@ internal sealed class SingleInstanceCoordinator : IDisposable
|
||||
{
|
||||
// Best-effort — if the signal can't be delivered the user can click the tray icon.
|
||||
}
|
||||
|
||||
// Stay alive a beat so the running copy can raise itself while we're still the
|
||||
// foreground process and the grant is fresh. If we vanished instantly the foreground
|
||||
// would churn and the grant could be consumed before it's used. Invisible to the user —
|
||||
// our dialog has already closed and we have no window.
|
||||
try { Thread.Sleep(600); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
/// <summary>Force every OTHER RemSound process to terminate. Returns true if no other
|
||||
|
||||
Reference in New Issue
Block a user