Skip to content

Commit f239866

Browse files
committed
Use sleep mode listener to fix modern standby sleep mode issue
1 parent d56d85b commit f239866

File tree

3 files changed

+150
-14
lines changed

3 files changed

+150
-14
lines changed

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,10 @@ QueryFullProcessImageName
8585
EVENT_OBJECT_HIDE
8686
EVENT_SYSTEM_DIALOGEND
8787

88+
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
8889
WM_POWERBROADCAST
89-
PBT_APMRESUMEAUTOMATIC
90+
PBT_APMRESUMEAUTOMATIC
91+
PBT_APMRESUMESUSPEND
92+
PowerRegisterSuspendResumeNotification
93+
PowerUnregisterSuspendResumeNotification
94+
DeviceNotifyCallbackRoutine

Flow.Launcher.Infrastructure/Win32Helper.cs

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Windows.Win32;
2020
using Windows.Win32.Foundation;
2121
using Windows.Win32.Graphics.Dwm;
22+
using Windows.Win32.System.Power;
2223
using Windows.Win32.System.Threading;
2324
using Windows.Win32.UI.Input.KeyboardAndMouse;
2425
using Windows.Win32.UI.Shell.Common;
@@ -338,9 +339,6 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni
338339
public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE;
339340
public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE;
340341

341-
public const int WM_POWERBROADCAST = (int)PInvoke.WM_POWERBROADCAST;
342-
public const int PBT_APMRESUMEAUTOMATIC = (int)PInvoke.PBT_APMRESUMEAUTOMATIC;
343-
344342
#endregion
345343

346344
#region Window Handle
@@ -918,5 +916,105 @@ public static string SelectFile()
918916
}
919917

920918
#endregion
919+
920+
#region Sleep Mode Listener
921+
922+
private static Action _func;
923+
private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null;
924+
private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient;
925+
private static SafeHandle _recipientHandle;
926+
private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null;
927+
928+
/// <summary>
929+
/// Registers a listener for sleep mode events.
930+
/// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit
931+
/// https://blog.csdn.net/mochounv/article/details/114668594
932+
/// </summary>
933+
/// <param name="func"></param>
934+
/// <exception cref="Win32Exception"></exception>
935+
public static unsafe void RegisterSleepModeListener(Action func)
936+
{
937+
if (_callback != null)
938+
{
939+
// Only register if not already registered
940+
return;
941+
}
942+
943+
_func = func;
944+
_callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback);
945+
_recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS()
946+
{
947+
Callback = _callback,
948+
Context = null
949+
};
950+
951+
_recipientHandle = new StructSafeHandle<DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS>(_recipient);
952+
_handle = PInvoke.PowerRegisterSuspendResumeNotification(
953+
REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK,
954+
_recipientHandle,
955+
out var handle) == WIN32_ERROR.ERROR_SUCCESS ?
956+
new HPOWERNOTIFY(new IntPtr(handle)) :
957+
HPOWERNOTIFY.Null;
958+
if (_handle.IsNull)
959+
{
960+
throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error());
961+
}
962+
}
963+
964+
/// <summary>
965+
/// Unregisters the sleep mode listener.
966+
/// </summary>
967+
public static void UnregisterSleepModeListener()
968+
{
969+
if (!_handle.IsNull)
970+
{
971+
PInvoke.PowerUnregisterSuspendResumeNotification(_handle);
972+
_handle = HPOWERNOTIFY.Null;
973+
_func = null;
974+
_callback = null;
975+
_recipientHandle = null;
976+
}
977+
}
978+
979+
private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting)
980+
{
981+
switch (type)
982+
{
983+
case PInvoke.PBT_APMRESUMEAUTOMATIC:
984+
// Operation is resuming automatically from a low-power state.This message is sent every time the system resumes
985+
_func();
986+
break;
987+
988+
case PInvoke.PBT_APMRESUMESUSPEND:
989+
// 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
990+
_func();
991+
break;
992+
}
993+
994+
return 0;
995+
}
996+
997+
private sealed class StructSafeHandle<T> : SafeHandle where T : struct
998+
{
999+
private readonly nint _ptr = nint.Zero;
1000+
1001+
public StructSafeHandle(T recipient) : base(nint.Zero, true)
1002+
{
1003+
var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
1004+
Marshal.StructureToPtr(recipient, pRecipient, false);
1005+
SetHandle(pRecipient);
1006+
_ptr = pRecipient;
1007+
}
1008+
1009+
public override bool IsInvalid => handle == nint.Zero;
1010+
1011+
protected override bool ReleaseHandle()
1012+
{
1013+
Marshal.FreeHGlobal(_ptr);
1014+
return true;
1015+
}
1016+
}
1017+
1018+
#endregion
9211019
}
9221020
}

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public MainWindow()
9595
UpdatePosition();
9696

9797
InitSoundEffects();
98+
RegisterSoundEffectsEvent();
9899
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
99100
_viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged;
100101
}
@@ -668,16 +669,6 @@ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref b
668669
handled = true;
669670
}
670671
break;
671-
case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages
672-
// https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast
673-
if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC)
674-
{
675-
// Fix for sound not playing after sleep / hibernate
676-
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
677-
InitSoundEffects();
678-
}
679-
handled = true;
680-
break;
681672
}
682673

683674
return IntPtr.Zero;
@@ -723,6 +714,47 @@ private void SoundPlay()
723714
}
724715
}
725716

717+
private void RegisterSoundEffectsEvent()
718+
{
719+
// Fix for sound not playing after sleep / hibernate for both modern standby and legacy standby
720+
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
721+
try
722+
{
723+
Win32Helper.RegisterSleepModeListener(() =>
724+
{
725+
if (Application.Current == null)
726+
{
727+
return;
728+
}
729+
730+
// We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject
731+
if (!Application.Current.Dispatcher.CheckAccess())
732+
{
733+
Application.Current.Dispatcher.Invoke(InitSoundEffects);
734+
return;
735+
}
736+
737+
InitSoundEffects();
738+
});
739+
}
740+
catch (Exception e)
741+
{
742+
App.API.LogException(ClassName, "Failed to register sound effect event", e);
743+
}
744+
}
745+
746+
private static void UnregisterSoundEffectsEvent()
747+
{
748+
try
749+
{
750+
Win32Helper.UnregisterSleepModeListener();
751+
}
752+
catch (Exception e)
753+
{
754+
App.API.LogException(ClassName, "Failed to unregister sound effect event", e);
755+
}
756+
}
757+
726758
#endregion
727759

728760
#region Window Notify Icon
@@ -1447,6 +1479,7 @@ protected virtual void Dispose(bool disposing)
14471479
_animationSoundWMP?.Close();
14481480
_animationSoundWPF?.Dispose();
14491481
_viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged;
1482+
UnregisterSoundEffectsEvent();
14501483
}
14511484

14521485
_disposed = true;

0 commit comments

Comments
 (0)