/ src / modules / MouseWithoutBorders / App / Class / InputHook.cs
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  }