Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions FluentFlyoutWPF/Classes/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ public static partial class NativeMethods
internal const int WM_KEYUP = 0x0101;
internal const int WM_SETTINGCHANGE = 0x001A;

// Shell Hook Messages
internal const int HSHELL_APPCOMMAND = 12;

// App Command Messages
internal const int APPCOMMAND_VOLUME_MUTE = 8;
internal const int APPCOMMAND_VOLUME_DOWN = 9;
internal const int APPCOMMAND_VOLUME_UP = 10;
internal const int APPCOMMAND_MEDIA_NEXTTRACK = 11;
internal const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
internal const int APPCOMMAND_MEDIA_STOP = 13;
internal const int APPCOMMAND_MEDIA_PLAY_PAUSE = 14;
internal const int FAPPCOMMAND_KEY = 0x0000;

#endregion

#region Enums
Expand Down Expand Up @@ -274,6 +287,14 @@ internal struct WindowCompositionAttributeData
[LibraryImport("user32.dll")]
internal static partial void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo);

[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool RegisterShellHookWindow(IntPtr hWnd);

[LibraryImport("user32.dll")]
Comment on lines +290 to +294
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool DeregisterShellHookWindow(IntPtr hWnd);

[LibraryImport("user32.dll", EntryPoint = "RegisterWindowMessageW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
internal static partial int RegisterWindowMessage(string lpString);

Expand Down
79 changes: 69 additions & 10 deletions FluentFlyoutWPF/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public partial class MainWindow : MicaWindow
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

private int WM_TASKBARCREATED;
private int WM_TASKBARCREATED, WM_SHELLHOOK;

private IntPtr _hookId = IntPtr.Zero;
private LowLevelKeyboardProc _hookProc;
Expand Down Expand Up @@ -169,6 +169,8 @@ public MainWindow()
mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;

WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");
WM_SHELLHOOK = RegisterWindowMessage("SHELLHOOK");
RegisterShellHookWindow(new WindowInteropHelper(this).Handle);
Comment on lines 171 to +173

_positionTimer = new Timer(SeekbarUpdateUi, null, Timeout.Infinite, Timeout.Infinite);
if (_seekBarEnabled && mediaManager.GetFocusedSession() is { } session)
Expand Down Expand Up @@ -690,17 +692,12 @@ private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)

if (mediaKeysPressed || (!SettingsManager.Current.MediaFlyoutVolumeKeysExcluded && volumeKeysPressed))
{
long currentTime = Environment.TickCount64;
bool result = TryShowMediaFlyoutDebounced();

// debounce to prevent hangs with rapid key presses
if ((currentTime - _lastFlyoutTime) < 500) // 500ms debounce time
if (!result)
{
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}

_lastFlyoutTime = currentTime;

ShowMediaFlyout();
}

if (SettingsManager.Current.LockKeysEnabled && !FullscreenDetector.IsFullscreenApplicationRunning())
Expand Down Expand Up @@ -730,6 +727,20 @@ private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}

// show the media flyout with debounce
private bool TryShowMediaFlyoutDebounced()
{
long currentTime = Environment.TickCount64;
// debounce to prevent hangs with rapid key presses
if ((currentTime - _lastFlyoutTime) < 500) // 500ms debounce time
{
return false;
}
_lastFlyoutTime = currentTime;
ShowMediaFlyout();
return true;
}

public async void ShowMediaFlyout(bool toggleMode = false)
{
if (mediaManager.GetFocusedSession() == null ||
Expand Down Expand Up @@ -1256,13 +1267,15 @@ private void CleanupResources()

TaskbarVisualizerControl.DisposeVisualizer();

// unhook keyboard hook
// unhook hooks
if (_hookId != IntPtr.Zero)
{
UnhookWindowsHookEx(_hookId);
_hookId = IntPtr.Zero;
}

DeregisterShellHookWindow(new WindowInteropHelper(this).Handle);

// clean up other resources
if (lockWindow?.IsLoaded == true)
lockWindow.Close();
Expand Down Expand Up @@ -1337,7 +1350,53 @@ private async Task<bool> WaitForExplorerReadyAsync(int timeoutMs = 60000)

private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
{
if (msg == WM_TASKBARCREATED)
// detect key presses from both keyboard hook and shell hook to show flyouts
if (msg == WM_SHELLHOOK && wParam == HSHELL_APPCOMMAND)
{
int highWord = (int)(lParam >> 16);
int cmd = highWord & 0x0FFF;
int device = highWord & 0xF000;

bool isMediaCommand = cmd switch
{
APPCOMMAND_MEDIA_PLAY_PAUSE => true,
APPCOMMAND_MEDIA_NEXTTRACK => true,
APPCOMMAND_MEDIA_PREVIOUSTRACK => true,
APPCOMMAND_MEDIA_STOP => true,
_ => false
};

bool isVolumeCommand = false;

if (!isMediaCommand && !SettingsManager.Current.MediaFlyoutVolumeKeysExcluded)
{
isVolumeCommand = cmd switch
{
APPCOMMAND_VOLUME_MUTE => true,
APPCOMMAND_VOLUME_DOWN => true,
APPCOMMAND_VOLUME_UP => true,
_ => false
};
}

if (!isMediaCommand && !isVolumeCommand)
return 0;

bool isKeyCommand = device == FAPPCOMMAND_KEY;

if (!isKeyCommand)
return 0;

bool result = TryShowMediaFlyoutDebounced();

if (!result)
{
return 0;
}

handled = true;
}
else if (msg == WM_TASKBARCREATED)
{
Logger.Warn("Explorer restart detected (TaskbarCreated)");

Expand Down
Loading