Skip to content

Commit 1b7a475

Browse files
authored
Merge pull request #4024 from Flow-Launcher/power_enhancement
Use sleep mode listener to fix modern standby sleep mode issue
2 parents 7f0851b + c27817e commit 1b7a475

File tree

3 files changed

+177
-33
lines changed

3 files changed

+177
-33
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?.Invoke();
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?.Invoke();
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: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.ComponentModel;
33
using System.Linq;
44
using System.Media;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using System.Windows;
78
using System.Windows.Controls;
@@ -61,8 +62,9 @@ public partial class MainWindow : IDisposable
6162
private bool _isArrowKeyPressed = false;
6263

6364
// Window Sound Effects
64-
private MediaPlayer animationSoundWMP;
65-
private SoundPlayer animationSoundWPF;
65+
private MediaPlayer _animationSoundWMP;
66+
private SoundPlayer _animationSoundWPF;
67+
private readonly Lock _soundLock = new();
6668

6769
// Window WndProc
6870
private HwndSource _hwndSource;
@@ -93,6 +95,7 @@ public MainWindow()
9395
UpdatePosition();
9496

9597
InitSoundEffects();
98+
RegisterSoundEffectsEvent();
9699
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
97100
_viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged;
98101
}
@@ -666,16 +669,6 @@ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref b
666669
handled = true;
667670
}
668671
break;
669-
case Win32Helper.WM_POWERBROADCAST: // Handle power broadcast messages
670-
// https://learn.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast
671-
if (wParam.ToInt32() == Win32Helper.PBT_APMRESUMEAUTOMATIC)
672-
{
673-
// Fix for sound not playing after sleep / hibernate
674-
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
675-
InitSoundEffects();
676-
}
677-
handled = true;
678-
break;
679672
}
680673

681674
return IntPtr.Zero;
@@ -687,31 +680,78 @@ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref b
687680

688681
private void InitSoundEffects()
689682
{
690-
if (_settings.WMPInstalled)
683+
lock (_soundLock)
691684
{
692-
animationSoundWMP?.Close();
693-
animationSoundWMP = new MediaPlayer();
694-
animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
685+
if (_settings.WMPInstalled)
686+
{
687+
_animationSoundWMP?.Close();
688+
_animationSoundWMP = new MediaPlayer();
689+
_animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
690+
}
691+
else
692+
{
693+
_animationSoundWPF?.Dispose();
694+
_animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
695+
_animationSoundWPF.Load();
696+
}
695697
}
696-
else
698+
}
699+
700+
private void SoundPlay()
701+
{
702+
lock (_soundLock)
697703
{
698-
animationSoundWPF?.Dispose();
699-
animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
700-
animationSoundWPF.Load();
704+
if (_settings.WMPInstalled)
705+
{
706+
_animationSoundWMP.Position = TimeSpan.Zero;
707+
_animationSoundWMP.Volume = _settings.SoundVolume / 100.0;
708+
_animationSoundWMP.Play();
709+
}
710+
else
711+
{
712+
_animationSoundWPF.Play();
713+
}
701714
}
702715
}
703716

704-
private void SoundPlay()
717+
private void RegisterSoundEffectsEvent()
705718
{
706-
if (_settings.WMPInstalled)
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
707722
{
708-
animationSoundWMP.Position = TimeSpan.Zero;
709-
animationSoundWMP.Volume = _settings.SoundVolume / 100.0;
710-
animationSoundWMP.Play();
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+
});
711739
}
712-
else
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)
713753
{
714-
animationSoundWPF.Play();
754+
App.API.LogException(ClassName, "Failed to unregister sound effect event", e);
715755
}
716756
}
717757

@@ -1436,9 +1476,10 @@ protected virtual void Dispose(bool disposing)
14361476
{
14371477
_hwndSource?.Dispose();
14381478
_notifyIcon?.Dispose();
1439-
animationSoundWMP?.Close();
1440-
animationSoundWPF?.Dispose();
1479+
_animationSoundWMP?.Close();
1480+
_animationSoundWPF?.Dispose();
14411481
_viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged;
1482+
UnregisterSoundEffectsEvent();
14421483
}
14431484

14441485
_disposed = true;

0 commit comments

Comments
 (0)