Use sleep mode listener to fix modern standby sleep mode issue

This commit is contained in:
Jack251970 2025-09-30 21:11:03 +08:00
parent d56d85b702
commit f239866c68
3 changed files with 150 additions and 14 deletions

View file

@ -85,5 +85,10 @@ QueryFullProcessImageName
EVENT_OBJECT_HIDE
EVENT_SYSTEM_DIALOGEND
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
WM_POWERBROADCAST
PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMESUSPEND
PowerRegisterSuspendResumeNotification
PowerUnregisterSuspendResumeNotification
DeviceNotifyCallbackRoutine

View file

@ -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;
/// <summary>
/// Registers a listener for sleep mode events.
/// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit
/// https://blog.csdn.net/mochounv/article/details/114668594
/// </summary>
/// <param name="func"></param>
/// <exception cref="Win32Exception"></exception>
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<DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS>(_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());
}
}
/// <summary>
/// Unregisters the sleep mode listener.
/// </summary>
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<T> : SafeHandle where T : struct
{
private readonly nint _ptr = nint.Zero;
public StructSafeHandle(T recipient) : base(nint.Zero, true)
{
var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
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
}
}

View file

@ -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;