diff --git a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp index 8b2e97583..aa042b354 100644 --- a/QuickLook.Native/QuickLook.Native32/FilePilot.cpp +++ b/QuickLook.Native/QuickLook.Native32/FilePilot.cpp @@ -27,6 +27,12 @@ namespace constexpr auto CLIPBOARD_POLL_INTERVAL_MS = 5; constexpr auto MAX_CLASS_NAME_LENGTH = 256; + // 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) { return wcsncmp(value, prefix, wcslen(prefix)) == 0; @@ -183,24 +189,30 @@ namespace inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = VK_CONTROL; + 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_THIRD_PARTY_HOTKEY_REPLAY_EXTRA_INFO; inputs[2].type = INPUT_KEYBOARD; inputs[2].ki.wVk = L'C'; + 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_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_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_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 c62ace642..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,6 +86,13 @@ private int HookProc(int code, int wParam, ref User32.KeyboardHookStruct lParam) if (IsWindowsKeyPressed()) return User32.CallNextHookEx(_hHook, code, wParam, ref lParam); + // 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; key = AddModifiers(key);