From f239866c6811792f60da0fd243ffd003a1e609c4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 30 Sep 2025 21:11:03 +0800 Subject: [PATCH] Use sleep mode listener to fix modern standby sleep mode issue --- .../NativeMethods.txt | 7 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 104 +++++++++++++++++- Flow.Launcher/MainWindow.xaml.cs | 53 +++++++-- 3 files changed, 150 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index eb844dd7c..cd072f635 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -85,5 +85,10 @@ QueryFullProcessImageName EVENT_OBJECT_HIDE EVENT_SYSTEM_DIALOGEND +DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS WM_POWERBROADCAST -PBT_APMRESUMEAUTOMATIC \ No newline at end of file +PBT_APMRESUMEAUTOMATIC +PBT_APMRESUMESUSPEND +PowerRegisterSuspendResumeNotification +PowerUnregisterSuspendResumeNotification +DeviceNotifyCallbackRoutine \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 5d30b740d..c94008b03 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -19,6 +19,7 @@ using Microsoft.Win32.SafeHandles; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; +using Windows.Win32.System.Power; using Windows.Win32.System.Threading; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.Shell.Common; @@ -338,9 +339,6 @@ namespace Flow.Launcher.Infrastructure public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE; public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE; - public const int WM_POWERBROADCAST = (int)PInvoke.WM_POWERBROADCAST; - public const int PBT_APMRESUMEAUTOMATIC = (int)PInvoke.PBT_APMRESUMEAUTOMATIC; - #endregion #region Window Handle @@ -918,5 +916,105 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region Sleep Mode Listener + + private static Action _func; + private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null; + private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient; + private static SafeHandle _recipientHandle; + private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null; + + /// + /// Registers a listener for sleep mode events. + /// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit + /// https://blog.csdn.net/mochounv/article/details/114668594 + /// + /// + /// + public static unsafe void RegisterSleepModeListener(Action func) + { + if (_callback != null) + { + // Only register if not already registered + return; + } + + _func = func; + _callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback); + _recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS() + { + Callback = _callback, + Context = null + }; + + _recipientHandle = new StructSafeHandle(_recipient); + _handle = PInvoke.PowerRegisterSuspendResumeNotification( + REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK, + _recipientHandle, + out var handle) == WIN32_ERROR.ERROR_SUCCESS ? + new HPOWERNOTIFY(new IntPtr(handle)) : + HPOWERNOTIFY.Null; + if (_handle.IsNull) + { + throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error()); + } + } + + /// + /// Unregisters the sleep mode listener. + /// + public static void UnregisterSleepModeListener() + { + if (!_handle.IsNull) + { + PInvoke.PowerUnregisterSuspendResumeNotification(_handle); + _handle = HPOWERNOTIFY.Null; + _func = null; + _callback = null; + _recipientHandle = null; + } + } + + private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting) + { + switch (type) + { + case PInvoke.PBT_APMRESUMEAUTOMATIC: + // Operation is resuming automatically from a low-power state.This message is sent every time the system resumes + _func(); + break; + + case PInvoke.PBT_APMRESUMESUSPEND: + // Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key + _func(); + break; + } + + return 0; + } + + private sealed class StructSafeHandle : SafeHandle where T : struct + { + private readonly nint _ptr = nint.Zero; + + public StructSafeHandle(T recipient) : base(nint.Zero, true) + { + var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(recipient, pRecipient, false); + SetHandle(pRecipient); + _ptr = pRecipient; + } + + public override bool IsInvalid => handle == nint.Zero; + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(_ptr); + return true; + } + } + + #endregion } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index a9e03bc8c..01a7dc9bd 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -95,6 +95,7 @@ namespace Flow.Launcher UpdatePosition(); InitSoundEffects(); + RegisterSoundEffectsEvent(); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); _viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged; } @@ -668,16 +669,6 @@ namespace Flow.Launcher handled = true; } break; - case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages - // https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast - if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC) - { - // Fix for sound not playing after sleep / hibernate - // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps - InitSoundEffects(); - } - handled = true; - break; } return IntPtr.Zero; @@ -723,6 +714,47 @@ namespace Flow.Launcher } } + private void RegisterSoundEffectsEvent() + { + // Fix for sound not playing after sleep / hibernate for both modern standby and legacy standby + // https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps + try + { + Win32Helper.RegisterSleepModeListener(() => + { + if (Application.Current == null) + { + return; + } + + // We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.Invoke(InitSoundEffects); + return; + } + + InitSoundEffects(); + }); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to register sound effect event", e); + } + } + + private static void UnregisterSoundEffectsEvent() + { + try + { + Win32Helper.UnregisterSleepModeListener(); + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to unregister sound effect event", e); + } + } + #endregion #region Window Notify Icon @@ -1447,6 +1479,7 @@ namespace Flow.Launcher _animationSoundWMP?.Close(); _animationSoundWPF?.Dispose(); _viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged; + UnregisterSoundEffectsEvent(); } _disposed = true;