mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-07 19:09:43 +02:00
Compare commits
11 Commits
dev/crutka
...
issue/4827
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6786fcaa84 | ||
|
|
f8b0b40ebf | ||
|
|
7a7d1f0d40 | ||
|
|
e44eb8a61f | ||
|
|
3c4b93189b | ||
|
|
381c254d13 | ||
|
|
5883f8b665 | ||
|
|
a956a525c7 | ||
|
|
6a790f2177 | ||
|
|
eb6bf09024 | ||
|
|
08713f3a12 |
@@ -199,6 +199,12 @@ namespace Peek.FilePreviewer.Controls
|
||||
PreviewBrowser.CoreWebView2.Settings.IsScriptEnabled = IsDevFilePreview;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsWebMessageEnabled = false;
|
||||
|
||||
// Disable browser-level accelerator keys (Ctrl+W, Ctrl+T, F5, etc.) as a
|
||||
// defense-in-depth measure. This alone does not guarantee XAML KeyboardAccelerators
|
||||
// work when WebView2 has focus (a low-level keyboard hook in MainWindow handles
|
||||
// that), but it prevents WebView2 from consuming shortcuts like Ctrl+W internally.
|
||||
PreviewBrowser.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
|
||||
|
||||
if (IsDevFilePreview)
|
||||
{
|
||||
PreviewBrowser.CoreWebView2.SetVirtualHostNameToFolderMapping(Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.MonacoDirectory, CoreWebView2HostResourceAccessKind.Allow);
|
||||
|
||||
@@ -131,5 +131,36 @@ namespace Peek.UI.Native
|
||||
/// contain string paths.
|
||||
/// </summary>
|
||||
internal const uint SHCNF_PATH = 0x0001;
|
||||
|
||||
// Low-level keyboard hook support
|
||||
internal const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
internal delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct KBDLLHOOKSTRUCT
|
||||
{
|
||||
public uint vkCode;
|
||||
public uint scanCode;
|
||||
public uint flags;
|
||||
public uint time;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr GetModuleHandle(string? lpModuleName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ManagedCommon;
|
||||
@@ -20,6 +21,7 @@ using Peek.FilePreviewer.Previewers;
|
||||
using Peek.UI.Extensions;
|
||||
using Peek.UI.Helpers;
|
||||
using Peek.UI.Models;
|
||||
using Peek.UI.Native;
|
||||
using Peek.UI.Telemetry.Events;
|
||||
using Windows.Foundation;
|
||||
using WinUIEx;
|
||||
@@ -42,6 +44,10 @@ namespace Peek.UI
|
||||
private bool _isDeleteInProgress;
|
||||
private bool _exitAfterClose;
|
||||
|
||||
private IntPtr _keyboardHookHandle;
|
||||
private NativeMethods.LowLevelKeyboardProc? _keyboardHookProc;
|
||||
private Windows.Win32.Foundation.HWND _cachedWindowHandle;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -207,8 +213,12 @@ namespace Peek.UI
|
||||
}
|
||||
|
||||
ViewModel.ScalingFactor = this.GetMonitorScale();
|
||||
this.Content.KeyUp -= Content_KeyUp;
|
||||
this.Content.KeyUp += Content_KeyUp;
|
||||
|
||||
_cachedWindowHandle = new Windows.Win32.Foundation.HWND(this.GetWindowHandle());
|
||||
InstallKeyboardHook();
|
||||
|
||||
bootTime.Stop();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new OpenedEvent() { FileExtension = ViewModel.CurrentItem?.Extension ?? string.Empty, HotKeyToVisibleTimeMs = bootTime.ElapsedMilliseconds });
|
||||
@@ -216,6 +226,8 @@ namespace Peek.UI
|
||||
|
||||
private void Uninitialize()
|
||||
{
|
||||
UninstallKeyboardHook();
|
||||
|
||||
this.Restore();
|
||||
this.Hide();
|
||||
|
||||
@@ -309,8 +321,164 @@ namespace Peek.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InstallKeyboardHook()
|
||||
{
|
||||
if (_keyboardHookHandle != IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_keyboardHookProc = LowLevelKeyboardHookCallback;
|
||||
using var process = System.Diagnostics.Process.GetCurrentProcess();
|
||||
var moduleHandle = NativeMethods.GetModuleHandle(process.MainModule?.ModuleName);
|
||||
|
||||
_keyboardHookHandle = NativeMethods.SetWindowsHookEx(
|
||||
NativeMethods.WH_KEYBOARD_LL,
|
||||
_keyboardHookProc!,
|
||||
moduleHandle,
|
||||
0);
|
||||
|
||||
if (_keyboardHookHandle == IntPtr.Zero)
|
||||
{
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
_keyboardHookProc = null;
|
||||
Logger.LogError($"Failed to install keyboard hook for Peek window. Win32 error: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UninstallKeyboardHook()
|
||||
{
|
||||
if (_keyboardHookHandle != IntPtr.Zero)
|
||||
{
|
||||
if (NativeMethods.UnhookWindowsHookEx(_keyboardHookHandle))
|
||||
{
|
||||
_keyboardHookHandle = IntPtr.Zero;
|
||||
_keyboardHookProc = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
Logger.LogError($"Failed to uninstall keyboard hook for Peek window. Win32 error: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr LowLevelKeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
const int WM_KEYDOWN = 0x0100;
|
||||
const uint VK_W = 0x57;
|
||||
const uint VK_ESCAPE = 0x1B;
|
||||
const uint VK_LEFT = 0x25;
|
||||
const uint VK_UP = 0x26;
|
||||
const uint VK_RIGHT = 0x27;
|
||||
const uint VK_DOWN = 0x28;
|
||||
const int VK_CONTROL = 0x11;
|
||||
const int VK_ALT = 0x12;
|
||||
const int VK_SHIFT = 0x10;
|
||||
const int VK_LWIN = 0x5B;
|
||||
const int VK_RWIN = 0x5C;
|
||||
const int KEY_PRESSED_MASK = 0x8000;
|
||||
|
||||
try
|
||||
{
|
||||
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN && lParam != IntPtr.Zero)
|
||||
{
|
||||
// Only handle when our window is in the foreground (cheap check before marshaling)
|
||||
var foreground = Windows.Win32.PInvoke_PeekUI.GetForegroundWindow();
|
||||
if (foreground != _cachedWindowHandle)
|
||||
{
|
||||
return NativeMethods.CallNextHookEx(_keyboardHookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
var hookStruct = Marshal.PtrToStructure<NativeMethods.KBDLLHOOKSTRUCT>(lParam);
|
||||
|
||||
// Fast-path: skip keys we never handle
|
||||
if (hookStruct.vkCode != VK_W && hookStruct.vkCode != VK_ESCAPE &&
|
||||
hookStruct.vkCode != VK_LEFT && hookStruct.vkCode != VK_RIGHT &&
|
||||
hookStruct.vkCode != VK_UP && hookStruct.vkCode != VK_DOWN)
|
||||
{
|
||||
return NativeMethods.CallNextHookEx(_keyboardHookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
bool ctrlPressed = (NativeMethods.GetAsyncKeyState(VK_CONTROL) & KEY_PRESSED_MASK) != 0;
|
||||
bool altPressed = (NativeMethods.GetAsyncKeyState(VK_ALT) & KEY_PRESSED_MASK) != 0;
|
||||
bool shiftPressed = (NativeMethods.GetAsyncKeyState(VK_SHIFT) & KEY_PRESSED_MASK) != 0;
|
||||
bool winPressed = (NativeMethods.GetAsyncKeyState(VK_LWIN) & KEY_PRESSED_MASK) != 0 ||
|
||||
(NativeMethods.GetAsyncKeyState(VK_RWIN) & KEY_PRESSED_MASK) != 0;
|
||||
bool handled = false;
|
||||
|
||||
// Pass keys through while delete confirmation dialog is showing
|
||||
if (_isDeleteInProgress)
|
||||
{
|
||||
return NativeMethods.CallNextHookEx(_keyboardHookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
if (ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_W)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (!_isDeleteInProgress)
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_ESCAPE)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (!_isDeleteInProgress)
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_LEFT)
|
||||
{
|
||||
if (ViewModel.DisplayItemCount > 1)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() => ViewModel.AttemptPreviousNavigation());
|
||||
}
|
||||
}
|
||||
else if (!ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_RIGHT)
|
||||
{
|
||||
if (ViewModel.DisplayItemCount > 1)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() => ViewModel.AttemptNextNavigation());
|
||||
}
|
||||
}
|
||||
else if (!ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_UP)
|
||||
{
|
||||
if (ViewModel.DisplayItemCount > 1)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() => ViewModel.AttemptPreviousNavigation());
|
||||
}
|
||||
}
|
||||
else if (!ctrlPressed && !altPressed && !shiftPressed && !winPressed && hookStruct.vkCode == VK_DOWN)
|
||||
{
|
||||
if (ViewModel.DisplayItemCount > 1)
|
||||
{
|
||||
handled = DispatcherQueue.TryEnqueue(() => ViewModel.AttemptNextNavigation());
|
||||
}
|
||||
}
|
||||
|
||||
if (handled)
|
||||
{
|
||||
return (IntPtr)1;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unhandled exception in Peek keyboard hook.", ex);
|
||||
}
|
||||
|
||||
return NativeMethods.CallNextHookEx(_keyboardHookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UninstallKeyboardHook();
|
||||
themeListener?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user