From c646de7e3f851b1fdd76acefce5730ca17f6a2c4 Mon Sep 17 00:00:00 2001 From: Andrey Semjonov <50518855+AndreySemjonov@users.noreply.github.com> Date: Fri, 22 May 2026 01:47:00 +0300 Subject: [PATCH 1/2] Fix FilePilot preview toggle after copy path --- QuickLook.Common/NativeMethods/User32.cs | 1 + QuickLook.Native/QuickLook.Native32/FilePilot.cpp | 7 +++++++ QuickLook/Helpers/GlobalKeyboardHook.cs | 3 +++ 3 files changed, 11 insertions(+) diff --git a/QuickLook.Common/NativeMethods/User32.cs b/QuickLook.Common/NativeMethods/User32.cs index 81271a8f4..dd9003b8c 100644 --- a/QuickLook.Common/NativeMethods/User32.cs +++ b/QuickLook.Common/NativeMethods/User32.cs @@ -203,6 +203,7 @@ public struct DISPLAYDEVICE public const int WM_KEYUP = 0x101; public const int WM_SYSKEYDOWN = 0x104; public const int WM_SYSKEYUP = 0x105; + public const int QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO = 0x514C4649; public const int GWL_STYLE = -16; public const int GWL_EXSTYLE = -20; diff --git a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp index 8b2e97583..357daf9f3 100644 --- a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp +++ b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp @@ -26,6 +26,7 @@ namespace constexpr auto CLIPBOARD_TIMEOUT_MS = 250; constexpr auto CLIPBOARD_POLL_INTERVAL_MS = 5; constexpr auto MAX_CLASS_NAME_LENGTH = 256; + constexpr ULONG_PTR QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO = 0x514C4649; bool StartsWith(PCWSTR value, PCWSTR prefix) { @@ -183,24 +184,30 @@ namespace inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = VK_CONTROL; + inputs[0].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.wVk = VK_SHIFT; + inputs[1].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; inputs[2].type = INPUT_KEYBOARD; inputs[2].ki.wVk = L'C'; + inputs[2].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; inputs[3].type = INPUT_KEYBOARD; inputs[3].ki.wVk = L'C'; inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; inputs[4].type = INPUT_KEYBOARD; inputs[4].ki.wVk = VK_SHIFT; inputs[4].ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; inputs[5].type = INPUT_KEYBOARD; inputs[5].ki.wVk = VK_CONTROL; inputs[5].ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; SendInput(_countof(inputs), inputs, sizeof(INPUT)); } diff --git a/QuickLook/Helpers/GlobalKeyboardHook.cs b/QuickLook/Helpers/GlobalKeyboardHook.cs index c62ace642..aa93cfec0 100644 --- a/QuickLook/Helpers/GlobalKeyboardHook.cs +++ b/QuickLook/Helpers/GlobalKeyboardHook.cs @@ -77,6 +77,9 @@ private int HookProc(int code, int wParam, ref User32.KeyboardHookStruct lParam) if (IsWindowsKeyPressed()) return User32.CallNextHookEx(_hHook, code, wParam, ref lParam); + if (lParam.dwExtraInfo == User32.QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO) + return User32.CallNextHookEx(_hHook, code, wParam, ref lParam); + var key = (Keys)lParam.vkCode; key = AddModifiers(key); From ab58115ecb629c7475a327ce7bf9e3a0bb4ac802 Mon Sep 17 00:00:00 2001 From: ema Date: Mon, 25 May 2026 22:21:06 +0800 Subject: [PATCH 2/2] Use QLTR marker for replayed hotkeys --- QuickLook.Common/NativeMethods/User32.cs | 1 - .../QuickLook.Native32/FilePilot.cpp | 19 ++++++++++++------- QuickLook/Helpers/GlobalKeyboardHook.cs | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/QuickLook.Common/NativeMethods/User32.cs b/QuickLook.Common/NativeMethods/User32.cs index dd9003b8c..81271a8f4 100644 --- a/QuickLook.Common/NativeMethods/User32.cs +++ b/QuickLook.Common/NativeMethods/User32.cs @@ -203,7 +203,6 @@ public struct DISPLAYDEVICE public const int WM_KEYUP = 0x101; public const int WM_SYSKEYDOWN = 0x104; public const int WM_SYSKEYUP = 0x105; - public const int QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO = 0x514C4649; public const int GWL_STYLE = -16; public const int GWL_EXSTYLE = -20; diff --git a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp index 357daf9f3..aa042b354 100644 --- a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp +++ b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp @@ -26,7 +26,12 @@ namespace constexpr auto CLIPBOARD_TIMEOUT_MS = 250; constexpr auto CLIPBOARD_POLL_INTERVAL_MS = 5; constexpr auto MAX_CLASS_NAME_LENGTH = 256; - constexpr ULONG_PTR QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO = 0x514C4649; + + // Tag synthetic key events generated by QuickLook (SendInput path) for + // third-party file manager interoperability. The managed low-level hook uses + // this marker to recognize and bypass these replayed keys. + // 0x514C5452 == 'QLTR' (QuickLook Third-party Replay) + constexpr ULONG_PTR QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO = 0x514C5452; bool StartsWith(PCWSTR value, PCWSTR prefix) { @@ -184,30 +189,30 @@ namespace inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = VK_CONTROL; - inputs[0].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[0].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.wVk = VK_SHIFT; - inputs[1].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[1].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[2].type = INPUT_KEYBOARD; inputs[2].ki.wVk = L'C'; - inputs[2].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[2].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[3].type = INPUT_KEYBOARD; inputs[3].ki.wVk = L'C'; inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; - inputs[3].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[3].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[4].type = INPUT_KEYBOARD; inputs[4].ki.wVk = VK_SHIFT; inputs[4].ki.dwFlags = KEYEVENTF_KEYUP; - inputs[4].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[4].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[5].type = INPUT_KEYBOARD; inputs[5].ki.wVk = VK_CONTROL; inputs[5].ki.dwFlags = KEYEVENTF_KEYUP; - inputs[5].ki.dwExtraInfo = QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO; + inputs[5].ki.dwExtraInfo = QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; SendInput(_countof(inputs), inputs, sizeof(INPUT)); } diff --git a/QuickLook/Helpers/GlobalKeyboardHook.cs b/QuickLook/Helpers/GlobalKeyboardHook.cs index aa93cfec0..378b16072 100644 --- a/QuickLook/Helpers/GlobalKeyboardHook.cs +++ b/QuickLook/Helpers/GlobalKeyboardHook.cs @@ -28,6 +28,15 @@ internal class GlobalKeyboardHook : IDisposable { private static GlobalKeyboardHook _instance; + // Marker written to INPUT::dwExtraInfo for synthetic hotkeys replayed by QuickLook + // through SendInput when integrating with third-party file managers. + // + // The low-level keyboard hook checks this value and immediately forwards the event, + // so QuickLook does not treat its own replayed keystrokes as real user input. + // This prevents false invalid-key suppression and accidental preview state changes. + // 0x514C5452 == 'QLTR' (QuickLook Third-party Replay) + public const int QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO = 0x514C5452; + private User32.KeyboardHookProc _callback; private nint _hHook = IntPtr.Zero; @@ -77,7 +86,11 @@ private int HookProc(int code, int wParam, ref User32.KeyboardHookStruct lParam) if (IsWindowsKeyPressed()) return User32.CallNextHookEx(_hHook, code, wParam, ref lParam); - if (lParam.dwExtraInfo == User32.QUICKLOOK_FILEPILOT_COPY_EXTRA_INFO) + // QuickLook itself may replay third-party hotkeys through SendInput. + // These events are tagged in dwExtraInfo and must be forwarded without + // entering QuickLook's hotkey pipeline, otherwise they can pollute state + // (for example invalid-key timing windows) and cause false toggles. + if (lParam.dwExtraInfo == User32.QUICKLOOK_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO) return User32.CallNextHookEx(_hHook, code, wParam, ref lParam); var key = (Keys)lParam.vkCode;