diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
index eb844dd7ca0..cd072f635f6 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 5d30b740d78..8a41e12b43c 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -19,6 +19,7 @@
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 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni
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 @@ public static string SelectFile()
}
#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?.Invoke();
+ 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?.Invoke();
+ 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 c4ed73a0d5a..01a7dc9bd50 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Linq;
using System.Media;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -61,8 +62,9 @@ public partial class MainWindow : IDisposable
private bool _isArrowKeyPressed = false;
// Window Sound Effects
- private MediaPlayer animationSoundWMP;
- private SoundPlayer animationSoundWPF;
+ private MediaPlayer _animationSoundWMP;
+ private SoundPlayer _animationSoundWPF;
+ private readonly Lock _soundLock = new();
// Window WndProc
private HwndSource _hwndSource;
@@ -93,6 +95,7 @@ public MainWindow()
UpdatePosition();
InitSoundEffects();
+ RegisterSoundEffectsEvent();
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
_viewModel.ActualApplicationThemeChanged += ViewModel_ActualApplicationThemeChanged;
}
@@ -666,16 +669,6 @@ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref b
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;
@@ -687,31 +680,78 @@ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref b
private void InitSoundEffects()
{
- if (_settings.WMPInstalled)
+ lock (_soundLock)
{
- animationSoundWMP?.Close();
- animationSoundWMP = new MediaPlayer();
- animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
+ if (_settings.WMPInstalled)
+ {
+ _animationSoundWMP?.Close();
+ _animationSoundWMP = new MediaPlayer();
+ _animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
+ }
+ else
+ {
+ _animationSoundWPF?.Dispose();
+ _animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
+ _animationSoundWPF.Load();
+ }
}
- else
+ }
+
+ private void SoundPlay()
+ {
+ lock (_soundLock)
{
- animationSoundWPF?.Dispose();
- animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
- animationSoundWPF.Load();
+ if (_settings.WMPInstalled)
+ {
+ _animationSoundWMP.Position = TimeSpan.Zero;
+ _animationSoundWMP.Volume = _settings.SoundVolume / 100.0;
+ _animationSoundWMP.Play();
+ }
+ else
+ {
+ _animationSoundWPF.Play();
+ }
}
}
- private void SoundPlay()
+ private void RegisterSoundEffectsEvent()
{
- if (_settings.WMPInstalled)
+ // 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
{
- animationSoundWMP.Position = TimeSpan.Zero;
- animationSoundWMP.Volume = _settings.SoundVolume / 100.0;
- animationSoundWMP.Play();
+ 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();
+ });
}
- else
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to register sound effect event", e);
+ }
+ }
+
+ private static void UnregisterSoundEffectsEvent()
+ {
+ try
+ {
+ Win32Helper.UnregisterSleepModeListener();
+ }
+ catch (Exception e)
{
- animationSoundWPF.Play();
+ App.API.LogException(ClassName, "Failed to unregister sound effect event", e);
}
}
@@ -1436,9 +1476,10 @@ protected virtual void Dispose(bool disposing)
{
_hwndSource?.Dispose();
_notifyIcon?.Dispose();
- animationSoundWMP?.Close();
- animationSoundWPF?.Dispose();
+ _animationSoundWMP?.Close();
+ _animationSoundWPF?.Dispose();
_viewModel.ActualApplicationThemeChanged -= ViewModel_ActualApplicationThemeChanged;
+ UnregisterSoundEffectsEvent();
}
_disposed = true;