InputHook.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 // <summary> 6 // Keyboard/Mouse hook callbacks, pre-process before calling to routines in Common.Event. 7 // </summary> 8 // <history> 9 // 2008 created by Truong Do (ductdo). 10 // 2009-... modified by Truong Do (TruongDo). 11 // 2023- Included in PowerToys. 12 // </history> 13 using System; 14 using System.Collections.Generic; 15 using System.Diagnostics.CodeAnalysis; 16 using System.Globalization; 17 using System.Reflection; 18 using System.Runtime.InteropServices; 19 using System.Windows.Forms; 20 21 using Microsoft.PowerToys.Settings.UI.Library; 22 using MouseWithoutBorders.Core; 23 24 [module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.InputHook.#MouseHookProc(System.Int32,System.Int32,System.IntPtr)", Justification = "Dotnet port with style preservation")] 25 [module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.InputHook.#ProcessKeyEx(System.Int32,System.Int32)", Justification = "Dotnet port with style preservation")] 26 [module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.InputHook.#Start()", Justification = "Dotnet port with style preservation")] 27 28 namespace MouseWithoutBorders.Class 29 { 30 internal class InputHook 31 { 32 internal delegate void MouseEvHandler(MOUSEDATA e, int dx, int dy); 33 34 internal delegate void KeybdEvHandler(KEYBDDATA e); 35 36 [StructLayout(LayoutKind.Sequential)] 37 private struct MouseHookStruct 38 { 39 internal NativeMethods.POINT Pt; 40 internal int Hwnd; 41 internal int WHitTestCode; 42 internal int DwExtraInfo; 43 } 44 45 // http://msdn.microsoft.com/en-us/library/ms644970(VS.85).aspx 46 [StructLayout(LayoutKind.Sequential)] 47 private struct MouseLLHookStruct 48 { 49 internal NativeMethods.POINT Pt; 50 internal int MouseData; 51 internal int Flags; 52 internal int Time; 53 internal int DwExtraInfo; 54 } 55 56 [StructLayout(LayoutKind.Sequential)] 57 private struct KeyboardHookStruct 58 { 59 internal int VkCode; 60 internal int ScanCode; 61 internal int Flags; 62 internal int Time; 63 internal int DwExtraInfo; 64 } 65 66 internal event MouseEvHandler MouseEvent; 67 68 internal event KeybdEvHandler KeyboardEvent; 69 70 private int hMouseHook; 71 private int hKeyboardHook; 72 private static NativeMethods.HookProc mouseHookProcedure; 73 private static NativeMethods.HookProc keyboardHookProcedure; 74 75 private static MouseLLHookStruct mouseHookStruct; 76 private static KeyboardHookStruct keyboardHookStruct; 77 private static MOUSEDATA hookCallbackMouseData; 78 private static KEYBDDATA hookCallbackKeybdData; 79 80 private static bool winDown; 81 private static bool altDown; 82 private static bool shiftDown; 83 84 internal static bool RealData { get; set; } = true; 85 86 internal static int SkipMouseUpCount { get; set; } 87 88 internal static bool SkipMouseUpDown { get; set; } 89 90 internal static bool CtrlDown { get; private set; } 91 92 internal static bool EasyMouseKeyDown { get; set; } 93 94 internal InputHook() 95 { 96 Start(); 97 } 98 99 ~InputHook() 100 { 101 Stop(); 102 } 103 104 internal void Start() 105 { 106 int le; 107 bool er = false; 108 109 // Install Mouse Hook 110 mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc); 111 hMouseHook = NativeMethods.SetWindowsHookEx( 112 WM.WH_MOUSE_LL, 113 mouseHookProcedure, 114 Marshal.GetHINSTANCE( 115 Assembly.GetExecutingAssembly().GetModules()[0]), 116 0); 117 118 if (hMouseHook == 0) 119 { 120 le = Marshal.GetLastWin32Error(); 121 Logger.Log("Error installing Mouse hook: " + le.ToString(CultureInfo.CurrentCulture)); 122 er = true; 123 Stop(); 124 } 125 126 // Install Keyboard Hook 127 keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc); 128 hKeyboardHook = NativeMethods.SetWindowsHookEx( 129 WM.WH_KEYBOARD_LL, 130 keyboardHookProcedure, 131 Marshal.GetHINSTANCE( 132 Assembly.GetExecutingAssembly().GetModules()[0]), 133 0); 134 if (hKeyboardHook == 0) 135 { 136 le = Marshal.GetLastWin32Error(); 137 Logger.Log("Error installing keyboard hook: " + le.ToString(CultureInfo.CurrentCulture)); 138 er = true; 139 Stop(); 140 } 141 142 if (er) 143 { 144 if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) 145 { 146 _ = MessageBox.Show( 147 "Error installing keyboard/Mouse hook!", 148 Application.ProductName, 149 MessageBoxButtons.OK, 150 MessageBoxIcon.Error); 151 } 152 } 153 else 154 { 155 Common.InitLastInputEventCount(); 156 } 157 } 158 159 internal void Stop() 160 { 161 if (hMouseHook != 0) 162 { 163 int retMouse = NativeMethods.UnhookWindowsHookEx(hMouseHook); 164 hMouseHook = 0; 165 if (retMouse == 0) 166 { 167 int errorCode = Marshal.GetLastWin32Error(); 168 169 // throw new Win32Exception(errorCode); 170 Logger.Log("Exception uninstalling Mouse hook, error code: " + errorCode.ToString(CultureInfo.CurrentCulture)); 171 } 172 } 173 174 if (hKeyboardHook != 0) 175 { 176 int retKeyboard = NativeMethods.UnhookWindowsHookEx(hKeyboardHook); 177 hKeyboardHook = 0; 178 if (retKeyboard == 0) 179 { 180 int errorCode = Marshal.GetLastWin32Error(); 181 182 // throw new Win32Exception(errorCode); 183 Logger.Log("Exception uninstalling keyboard hook, error code: " + errorCode.ToString(CultureInfo.CurrentCulture)); 184 } 185 } 186 } 187 188 // Better performance, compared to Marshal.PtrToStructure. 189 private static MouseLLHookStruct LParamToMouseLLHookStruct(IntPtr lParam) 190 { 191 unsafe 192 { 193 return *(MouseLLHookStruct*)lParam; 194 } 195 } 196 197 private static KeyboardHookStruct LParamToKeyboardHookStruct(IntPtr lParam) 198 { 199 unsafe 200 { 201 return *(KeyboardHookStruct*)lParam; 202 } 203 } 204 205 private int MouseHookProc(int nCode, int wParam, IntPtr lParam) 206 { 207 int rv = 1, dx = 0, dy = 0; 208 bool local = false; 209 Event.InputEventCount++; 210 211 try 212 { 213 if (!RealData) 214 { 215 RealData = true; 216 217 // Common.Log("MouseHookProc: Not real data!"); 218 // return rv; 219 rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); 220 } 221 else 222 { 223 Event.RealInputEventCount++; 224 225 if (MachineStuff.NewDesMachineID == Common.MachineID || MachineStuff.NewDesMachineID == ID.ALL) 226 { 227 local = true; 228 if (Common.MainFormVisible && !DragDrop.IsDropping) 229 { 230 Helper.MainFormDot(); 231 } 232 } 233 234 if (nCode >= 0 && MouseEvent != null) 235 { 236 if (wParam == WM.WM_LBUTTONUP && SkipMouseUpCount > 0) 237 { 238 Logger.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}."); 239 SkipMouseUpCount--; 240 rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); 241 return rv; 242 } 243 244 if ((wParam == WM.WM_LBUTTONUP || wParam == WM.WM_LBUTTONDOWN) && SkipMouseUpDown) 245 { 246 rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); 247 return rv; 248 } 249 250 mouseHookStruct = LParamToMouseLLHookStruct(lParam); 251 hookCallbackMouseData.dwFlags = wParam; 252 253 // Use WheelDelta to store XBUTTON1/XBUTTON2 data. 254 hookCallbackMouseData.WheelDelta = (short)((mouseHookStruct.MouseData >> 16) & 0xffff); 255 256 if (local) 257 { 258 hookCallbackMouseData.X = mouseHookStruct.Pt.x; 259 hookCallbackMouseData.Y = mouseHookStruct.Pt.y; 260 261 if (Setting.Values.DrawMouse && Common.MouseCursorForm != null) 262 { 263 CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue); 264 } 265 } 266 else 267 { 268 if (MachineStuff.SwitchLocation.Count > 0 && MachineStuff.NewDesMachineID != Common.MachineID && MachineStuff.NewDesMachineID != ID.ALL) 269 { 270 MachineStuff.SwitchLocation.Count--; 271 272 if (MachineStuff.SwitchLocation.X > Event.XY_BY_PIXEL - 100000 || MachineStuff.SwitchLocation.Y > Event.XY_BY_PIXEL - 100000) 273 { 274 hookCallbackMouseData.X = MachineStuff.SwitchLocation.X - Event.XY_BY_PIXEL; 275 hookCallbackMouseData.Y = MachineStuff.SwitchLocation.Y - Event.XY_BY_PIXEL; 276 } 277 else 278 { 279 hookCallbackMouseData.X = (MachineStuff.SwitchLocation.X * Common.ScreenWidth / 65535) + MachineStuff.PrimaryScreenBounds.Left; 280 hookCallbackMouseData.Y = (MachineStuff.SwitchLocation.Y * Common.ScreenHeight / 65535) + MachineStuff.PrimaryScreenBounds.Top; 281 } 282 283 Common.HideMouseCursor(false); 284 } 285 else 286 { 287 dx = mouseHookStruct.Pt.x - Common.LastPos.X; 288 dy = mouseHookStruct.Pt.y - Common.LastPos.Y; 289 290 hookCallbackMouseData.X += dx; 291 hookCallbackMouseData.Y += dy; 292 293 if (hookCallbackMouseData.X < MachineStuff.PrimaryScreenBounds.Left) 294 { 295 hookCallbackMouseData.X = MachineStuff.PrimaryScreenBounds.Left - 1; 296 } 297 else if (hookCallbackMouseData.X > MachineStuff.PrimaryScreenBounds.Right) 298 { 299 hookCallbackMouseData.X = MachineStuff.PrimaryScreenBounds.Right + 1; 300 } 301 302 if (hookCallbackMouseData.Y < MachineStuff.PrimaryScreenBounds.Top) 303 { 304 hookCallbackMouseData.Y = MachineStuff.PrimaryScreenBounds.Top - 1; 305 } 306 else if (hookCallbackMouseData.Y > MachineStuff.PrimaryScreenBounds.Bottom) 307 { 308 hookCallbackMouseData.Y = MachineStuff.PrimaryScreenBounds.Bottom + 1; 309 } 310 311 dx += dx < 0 ? -Event.MOVE_MOUSE_RELATIVE : Event.MOVE_MOUSE_RELATIVE; 312 dy += dy < 0 ? -Event.MOVE_MOUSE_RELATIVE : Event.MOVE_MOUSE_RELATIVE; 313 } 314 } 315 316 MouseEvent(hookCallbackMouseData, dx, dy); 317 318 DragDrop.DragDropStep01(wParam); 319 DragDrop.DragDropStep09(wParam); 320 } 321 322 if (local) 323 { 324 rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); 325 } 326 } 327 } 328 catch (Exception e) 329 { 330 Logger.Log(e); 331 rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); 332 } 333 334 return rv; 335 } 336 337 private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam) 338 { 339 Event.InputEventCount++; 340 if (!RealData) 341 { 342 return NativeMethods.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); 343 } 344 345 Event.RealInputEventCount++; 346 347 keyboardHookStruct = LParamToKeyboardHookStruct(lParam); 348 hookCallbackKeybdData.dwFlags = keyboardHookStruct.Flags; 349 hookCallbackKeybdData.wVk = (short)keyboardHookStruct.VkCode; 350 351 if (nCode >= 0 && KeyboardEvent != null) 352 { 353 if (!ProcessKeyEx(keyboardHookStruct.VkCode, keyboardHookStruct.Flags, hookCallbackKeybdData)) 354 { 355 return 1; 356 } 357 358 KeyboardEvent(hookCallbackKeybdData); 359 } 360 361 if (Common.DesMachineID == ID.NONE || Common.DesMachineID == ID.ALL || Common.DesMachineID == Common.MachineID) 362 { 363 return NativeMethods.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); 364 } 365 else 366 { 367 return 1; 368 } 369 } 370 371 private bool ProcessKeyEx(int vkCode, int flags, KEYBDDATA hookCallbackKeybdData) 372 { 373 if ((flags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP) 374 { 375 EasyMouseKeyDown = false; 376 377 switch ((VK)vkCode) 378 { 379 case VK.LWIN: 380 case VK.RWIN: 381 winDown = false; 382 break; 383 384 case VK.LCONTROL: 385 case VK.RCONTROL: 386 CtrlDown = false; 387 break; 388 389 case VK.LMENU: 390 case VK.RMENU: 391 altDown = false; 392 break; 393 394 case VK.LSHIFT: 395 shiftDown = false; 396 break; 397 398 default: 399 break; 400 } 401 } 402 else 403 { 404 UpdateEasyMouseKeyDown((VK)vkCode); 405 406 switch ((VK)vkCode) 407 { 408 case VK.LWIN: 409 case VK.RWIN: 410 winDown = true; 411 break; 412 413 case VK.LCONTROL: 414 case VK.RCONTROL: 415 CtrlDown = true; 416 break; 417 418 case VK.LMENU: 419 case VK.RMENU: 420 altDown = true; 421 break; 422 423 case VK.LSHIFT: 424 shiftDown = true; 425 break; 426 427 case VK.DELETE: 428 if (CtrlDown && altDown) 429 { 430 CtrlDown = altDown = false; 431 KeyboardEvent(hookCallbackKeybdData); 432 433 if (Common.DesMachineID != ID.ALL) 434 { 435 MachineStuff.SwitchToMachine(Common.MachineName.Trim()); 436 } 437 438 /* 439 #if CUSTOMIZE_LOGON_SCREEN 440 Common.DoSomethingInUIThread(delegate() 441 { 442 Common.MainForm.LoadNewLogonBackground(); 443 }); 444 #endif 445 * */ 446 } 447 448 break; 449 450 case VK.ESCAPE: 451 if (Common.IsTopMostMessageNotNull()) 452 { 453 Common.HideTopMostMessage(); 454 } 455 456 break; 457 458 default: 459 Logger.LogDebug("X"); 460 return ProcessHotKeys(vkCode, hookCallbackKeybdData); 461 } 462 } 463 464 return true; 465 } 466 467 private void UpdateEasyMouseKeyDown(VK vkCode) 468 { 469 EasyMouseOption easyMouseOption = (EasyMouseOption)Setting.Values.EasyMouse; 470 471 EasyMouseKeyDown = (easyMouseOption == EasyMouseOption.Ctrl && (vkCode == VK.LCONTROL || vkCode == VK.RCONTROL)) 472 || (easyMouseOption == EasyMouseOption.Shift && (vkCode == VK.LSHIFT || vkCode == VK.RSHIFT)); 473 } 474 475 private static long lastHotKeyLockMachine; 476 477 private void ResetModifiersState(HotkeySettings matchingHotkey) 478 { 479 CtrlDown = CtrlDown && matchingHotkey.Ctrl; 480 altDown = altDown && matchingHotkey.Alt; 481 shiftDown = shiftDown && matchingHotkey.Shift; 482 winDown = winDown && matchingHotkey.Win; 483 } 484 485 private List<short> GetVkCodesList(HotkeySettings hotkey) 486 { 487 var list = new List<short>(); 488 if (hotkey.Alt) 489 { 490 list.Add((short)VK.MENU); 491 } 492 493 if (hotkey.Shift) 494 { 495 list.Add((short)VK.SHIFT); 496 } 497 498 if (hotkey.Win) 499 { 500 list.Add((short)VK.LWIN); 501 } 502 503 if (hotkey.Ctrl) 504 { 505 list.Add((short)VK.CONTROL); 506 } 507 508 if (hotkey.Code != 0) 509 { 510 list.Add((short)hotkey.Code); 511 } 512 513 return list; 514 } 515 516 private bool ProcessHotKeys(int vkCode, KEYBDDATA hookCallbackKeybdData) 517 { 518 if (Common.HotkeyMatched(vkCode, winDown, CtrlDown, altDown, shiftDown, Setting.Values.HotKeySwitch2AllPC)) 519 { 520 ResetLastSwitchKeys(); 521 MachineStuff.SwitchToMultipleMode(Common.DesMachineID != ID.ALL, true); 522 } 523 524 if (Common.HotkeyMatched(vkCode, winDown, CtrlDown, altDown, shiftDown, Setting.Values.HotKeyToggleEasyMouse)) 525 { 526 if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) 527 { 528 EasyMouseOption easyMouseOption = (EasyMouseOption)Setting.Values.EasyMouse; 529 530 if (easyMouseOption is EasyMouseOption.Disable or EasyMouseOption.Enable) 531 { 532 Setting.Values.EasyMouse = (int)(easyMouseOption == EasyMouseOption.Disable ? EasyMouseOption.Enable : EasyMouseOption.Disable); 533 534 Common.ShowToolTip($"Easy Mouse has been toggled to [{(EasyMouseOption)Setting.Values.EasyMouse}] by a hotkey. You can change the hotkey in the Settings form.", 5000); 535 return false; 536 } 537 } 538 } 539 else if (Common.HotkeyMatched(vkCode, winDown, CtrlDown, altDown, shiftDown, Setting.Values.HotKeyLockMachine)) 540 { 541 if (!Common.RunOnLogonDesktop 542 && !Common.RunOnScrSaverDesktop) 543 { 544 if (Common.GetTick() - lastHotKeyLockMachine < 500) 545 { 546 MachineStuff.SwitchToMultipleMode(true, true); 547 548 var codes = GetVkCodesList(Setting.Values.HotKeyLockMachine); 549 550 foreach (var code in codes) 551 { 552 hookCallbackKeybdData.wVk = code; 553 KeyboardEvent(hookCallbackKeybdData); 554 } 555 556 hookCallbackKeybdData.dwFlags |= (int)WM.LLKHF.UP; 557 558 foreach (var code in codes) 559 { 560 hookCallbackKeybdData.wVk = code; 561 KeyboardEvent(hookCallbackKeybdData); 562 } 563 564 MachineStuff.SwitchToMultipleMode(false, true); 565 566 _ = NativeMethods.LockWorkStation(); 567 } 568 else 569 { 570 KeyboardEvent(hookCallbackKeybdData); 571 } 572 573 lastHotKeyLockMachine = Common.GetTick(); 574 575 return false; 576 } 577 } 578 else if (Common.HotkeyMatched(vkCode, winDown, CtrlDown, altDown, shiftDown, Setting.Values.HotKeyReconnect)) 579 { 580 Common.ShowToolTip("Reconnecting...", 2000); 581 Common.LastReconnectByHotKeyTime = Common.GetTick(); 582 InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY; 583 return false; 584 } 585 586 if (CtrlDown && altDown) 587 { 588 if (shiftDown && vkCode == Setting.Values.HotKeyExitMM && 589 (Common.DesMachineID == Common.MachineID || Common.DesMachineID == ID.ALL)) 590 { 591 Common.DoSomethingInUIThread(() => 592 { 593 Common.MainForm.NotifyIcon.Visible = false; 594 595 for (int i = 1; i < 10; i++) 596 { 597 Application.DoEvents(); 598 Thread.Sleep(20); 599 } 600 601 Common.MainForm.Quit(false, false); 602 }); 603 } 604 else if (shiftDown || winDown) 605 { 606 // The following else cases should work if control and alt modifiers are pressed. The hotkeys should still be captured. 607 // But if any of the other 2 modifiers (shift or win) are pressed, they hotkeys should not be activated. 608 // Issue #26597 609 return true; 610 } 611 else if (vkCode == Setting.Values.HotKeySwitchMachine || 612 vkCode == Setting.Values.HotKeySwitchMachine + 1 || 613 vkCode == Setting.Values.HotKeySwitchMachine + 2 || 614 vkCode == Setting.Values.HotKeySwitchMachine + 3) 615 { 616 if (Switch2(vkCode - Setting.Values.HotKeySwitchMachine)) 617 { 618 return false; 619 } 620 } 621 } 622 623 return true; 624 } 625 626 private static bool Switch2(int index) 627 { 628 if (MachineStuff.MachineMatrix != null && MachineStuff.MachineMatrix.Length > index) 629 { 630 string mcName = MachineStuff.MachineMatrix[index].Trim(); 631 if (!string.IsNullOrEmpty(mcName)) 632 { 633 // Common.DoSomethingInUIThread(delegate() 634 { 635 InitAndCleanup.ReleaseAllKeys(); 636 } 637 638 // ); 639 MachineStuff.SwitchToMachine(mcName); 640 641 if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) 642 { 643 Common.ShowToolTip( 644 string.Format( 645 CultureInfo.CurrentCulture, 646 "Control has been switched to {0} by the hotkey Ctrl+Alt+{1}{2}", 647 mcName, 648 Setting.Values.HotKeySwitchMachine == (int)VK.F1 ? "F" : string.Empty, 649 index + 1), 650 3000); 651 } 652 653 return true; 654 } 655 } 656 657 return false; 658 } 659 660 internal void ResetLastSwitchKeys() 661 { 662 CtrlDown = winDown = altDown = false; 663 } 664 } 665 }