// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Keyboard/Mouse hook callbacks, pre-process before calling to routines in Common.Event. // // // 2008 created by Truong Do (ductdo). // 2009-... modified by Truong Do (TruongDo). // 2023- Included in PowerToys. // using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Forms; [module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.InputHook.#MouseHookProc(System.Int32,System.Int32,System.IntPtr)", Justification = "Dotnet port with style preservation")] [module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.InputHook.#ProcessKeyEx(System.Int32,System.Int32)", Justification = "Dotnet port with style preservation")] [module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.InputHook.#Start()", Justification = "Dotnet port with style preservation")] namespace MouseWithoutBorders.Class { internal class InputHook { internal delegate void MouseEvHandler(MOUSEDATA e, int dx, int dy); internal delegate void KeybdEvHandler(KEYBDDATA e); [StructLayout(LayoutKind.Sequential)] private struct MouseHookStruct { internal NativeMethods.POINT Pt; internal int Hwnd; internal int WHitTestCode; internal int DwExtraInfo; } // http://msdn.microsoft.com/en-us/library/ms644970(VS.85).aspx [StructLayout(LayoutKind.Sequential)] private struct MouseLLHookStruct { internal NativeMethods.POINT Pt; internal int MouseData; internal int Flags; internal int Time; internal int DwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct KeyboardHookStruct { internal int VkCode; internal int ScanCode; internal int Flags; internal int Time; internal int DwExtraInfo; } internal event MouseEvHandler MouseEvent; internal event KeybdEvHandler KeyboardEvent; private int hMouseHook; private int hKeyboardHook; private static NativeMethods.HookProc mouseHookProcedure; private static NativeMethods.HookProc keyboardHookProcedure; private static MouseLLHookStruct mouseHookStruct; private static KeyboardHookStruct keyboardHookStruct; private static MOUSEDATA hookCallbackMouseData; private static KEYBDDATA hookCallbackKeybdData; private static bool winDown; private static bool altDown; private static bool shiftDown; internal static bool RealData { get; set; } = true; internal static int SkipMouseUpCount { get; set; } internal static bool SkipMouseUpDown { get; set; } internal static bool CtrlDown { get; private set; } internal static bool EasyMouseKeyDown { get; set; } internal InputHook() { Start(); } ~InputHook() { Stop(); } internal void Start() { int le; bool er = false; // Install Mouse Hook mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc); hMouseHook = NativeMethods.SetWindowsHookEx( Common.WH_MOUSE_LL, mouseHookProcedure, Marshal.GetHINSTANCE( Assembly.GetExecutingAssembly().GetModules()[0]), 0); if (hMouseHook == 0) { le = Marshal.GetLastWin32Error(); Common.Log("Error installing Mouse hook: " + le.ToString(CultureInfo.CurrentCulture)); er = true; Stop(); } // Install Keyboard Hook keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc); hKeyboardHook = NativeMethods.SetWindowsHookEx( Common.WH_KEYBOARD_LL, keyboardHookProcedure, Marshal.GetHINSTANCE( Assembly.GetExecutingAssembly().GetModules()[0]), 0); if (hKeyboardHook == 0) { le = Marshal.GetLastWin32Error(); Common.Log("Error installing keyboard hook: " + le.ToString(CultureInfo.CurrentCulture)); er = true; Stop(); } if (er) { if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { _ = MessageBox.Show( "Error installing keyboard/Mouse hook!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { Common.InitLastInputEventCount(); } } internal void Stop() { if (hMouseHook != 0) { int retMouse = NativeMethods.UnhookWindowsHookEx(hMouseHook); hMouseHook = 0; if (retMouse == 0) { int errorCode = Marshal.GetLastWin32Error(); // throw new Win32Exception(errorCode); Common.Log("Exception uninstalling Mouse hook, error code: " + errorCode.ToString(CultureInfo.CurrentCulture)); } } if (hKeyboardHook != 0) { int retKeyboard = NativeMethods.UnhookWindowsHookEx(hKeyboardHook); hKeyboardHook = 0; if (retKeyboard == 0) { int errorCode = Marshal.GetLastWin32Error(); // throw new Win32Exception(errorCode); Common.Log("Exception uninstalling keyboard hook, error code: " + errorCode.ToString(CultureInfo.CurrentCulture)); } } } // Better performance, compared to Marshal.PtrToStructure. private static MouseLLHookStruct LParamToMouseLLHookStruct(IntPtr lParam) { unsafe { return *(MouseLLHookStruct*)lParam; } } private static KeyboardHookStruct LParamToKeyboardHookStruct(IntPtr lParam) { unsafe { return *(KeyboardHookStruct*)lParam; } } private int MouseHookProc(int nCode, int wParam, IntPtr lParam) { int rv = 1, dx = 0, dy = 0; bool local = false; Common.InputEventCount++; try { if (!RealData) { RealData = true; // Common.Log("MouseHookProc: Not real data!"); // return rv; rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); } else { Common.RealInputEventCount++; if (Common.NewDesMachineID == Common.MachineID || Common.NewDesMachineID == ID.ALL) { local = true; if (Common.MainFormVisible && !Common.IsDropping) { Common.MainFormDot(); } } if (nCode >= 0 && MouseEvent != null) { if (wParam == Common.WM_LBUTTONUP && SkipMouseUpCount > 0) { Common.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}."); SkipMouseUpCount--; rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); return rv; } if ((wParam == Common.WM_LBUTTONUP || wParam == Common.WM_LBUTTONDOWN) && SkipMouseUpDown) { rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); return rv; } mouseHookStruct = LParamToMouseLLHookStruct(lParam); hookCallbackMouseData.dwFlags = wParam; // Use WheelDelta to store XBUTTON1/XBUTTON2 data. hookCallbackMouseData.WheelDelta = (short)((mouseHookStruct.MouseData >> 16) & 0xffff); if (local) { hookCallbackMouseData.X = mouseHookStruct.Pt.x; hookCallbackMouseData.Y = mouseHookStruct.Pt.y; if (Setting.Values.DrawMouse && Common.MouseCursorForm != null) { CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue); } } else { if (Common.SwitchLocation.Count > 0 && Common.NewDesMachineID != Common.MachineID && Common.NewDesMachineID != ID.ALL) { Common.SwitchLocation.Count--; if (Common.SwitchLocation.X > Common.XY_BY_PIXEL - 100000 || Common.SwitchLocation.Y > Common.XY_BY_PIXEL - 100000) { hookCallbackMouseData.X = Common.SwitchLocation.X - Common.XY_BY_PIXEL; hookCallbackMouseData.Y = Common.SwitchLocation.Y - Common.XY_BY_PIXEL; } else { hookCallbackMouseData.X = (Common.SwitchLocation.X * Common.ScreenWidth / 65535) + Common.PrimaryScreenBounds.Left; hookCallbackMouseData.Y = (Common.SwitchLocation.Y * Common.ScreenHeight / 65535) + Common.PrimaryScreenBounds.Top; } Common.HideMouseCursor(false); } else { dx = mouseHookStruct.Pt.x - Common.LastPos.X; dy = mouseHookStruct.Pt.y - Common.LastPos.Y; hookCallbackMouseData.X += dx; hookCallbackMouseData.Y += dy; if (hookCallbackMouseData.X < Common.PrimaryScreenBounds.Left) { hookCallbackMouseData.X = Common.PrimaryScreenBounds.Left - 1; } else if (hookCallbackMouseData.X > Common.PrimaryScreenBounds.Right) { hookCallbackMouseData.X = Common.PrimaryScreenBounds.Right + 1; } if (hookCallbackMouseData.Y < Common.PrimaryScreenBounds.Top) { hookCallbackMouseData.Y = Common.PrimaryScreenBounds.Top - 1; } else if (hookCallbackMouseData.Y > Common.PrimaryScreenBounds.Bottom) { hookCallbackMouseData.Y = Common.PrimaryScreenBounds.Bottom + 1; } dx += dx < 0 ? -Common.MOVE_MOUSE_RELATIVE : Common.MOVE_MOUSE_RELATIVE; dy += dy < 0 ? -Common.MOVE_MOUSE_RELATIVE : Common.MOVE_MOUSE_RELATIVE; } } MouseEvent(hookCallbackMouseData, dx, dy); Common.DragDropStep01(wParam); Common.DragDropStep09(wParam); } if (local) { rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); } } } catch (Exception e) { Common.Log(e); rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam); } return rv; } private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam) { Common.InputEventCount++; if (!RealData) { return NativeMethods.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); } Common.RealInputEventCount++; keyboardHookStruct = LParamToKeyboardHookStruct(lParam); hookCallbackKeybdData.dwFlags = keyboardHookStruct.Flags; hookCallbackKeybdData.wVk = (short)keyboardHookStruct.VkCode; if (nCode >= 0 && KeyboardEvent != null) { if (!ProcessKeyEx(keyboardHookStruct.VkCode, keyboardHookStruct.Flags, hookCallbackKeybdData)) { return 1; } KeyboardEvent(hookCallbackKeybdData); } if (Common.DesMachineID == ID.NONE || Common.DesMachineID == ID.ALL || Common.DesMachineID == Common.MachineID) { if (nCode >= 0 && Setting.Values.UseVKMap && Setting.Values.VKMap != null && Setting.Values.VKMap.ContainsKey(hookCallbackKeybdData.wVk) && !CtrlDown) { InputSimulation.SendKey(hookCallbackKeybdData); return 1; } else { return NativeMethods.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); } } else { return 1; } } // Returns false if we do not want to redirect the keystrokes to other machine(s). private int ctrlTouchesDnIndex; private int ctrlTouchesUpIndex = 1; private const int CONTROL_TOUCH = 4; private readonly long[] ctrlTouches = new long[CONTROL_TOUCH]; private bool ProcessKeyEx(int vkCode, int flags, KEYBDDATA hookCallbackKeybdData) { bool allTouched; if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP) { EasyMouseKeyDown = false; switch ((VK)vkCode) { case VK.LWIN: case VK.RWIN: winDown = false; break; case VK.LCONTROL: case VK.RCONTROL: CtrlDown = false; if (Setting.Values.HotKeySwitch2AllPC == 1) { ctrlTouches[ctrlTouchesUpIndex] = Common.GetTick(); ctrlTouchesUpIndex = ((ctrlTouchesUpIndex % CONTROL_TOUCH) + 2) % CONTROL_TOUCH; } break; case VK.LMENU: case VK.RMENU: altDown = false; break; case VK.LSHIFT: shiftDown = false; break; default: break; } } else { UpdateEasyMouseKeyDown((VK)vkCode); switch ((VK)vkCode) { case VK.LWIN: case VK.RWIN: winDown = true; break; case VK.LCONTROL: case VK.RCONTROL: Common.LogDebug("VK.RCONTROL"); CtrlDown = true; if (Setting.Values.HotKeySwitch2AllPC == 1) { ctrlTouches[ctrlTouchesDnIndex] = Common.GetTick(); ctrlTouchesDnIndex = (ctrlTouchesDnIndex + 2) % CONTROL_TOUCH; } allTouched = true; for (int i = 0; i < CONTROL_TOUCH; i++) { if (ctrlTouches[i] == 0 || Common.GetTick() - ctrlTouches[i] > 400) { allTouched = false; break; } } if (allTouched && Common.GetTick() - Common.JustGotAKey > 1000) { ResetLastSwitchKeys(); Common.SwitchToMultipleMode(Common.DesMachineID != ID.ALL, true); } break; case VK.LMENU: case VK.RMENU: altDown = true; break; case VK.LSHIFT: shiftDown = true; break; case VK.DELETE: if (CtrlDown && altDown) { CtrlDown = altDown = false; KeyboardEvent(hookCallbackKeybdData); if (Common.DesMachineID != ID.ALL) { Common.SwitchToMachine(Common.MachineName.Trim()); } /* #if CUSTOMIZE_LOGON_SCREEN Common.DoSomethingInUIThread(delegate() { Common.MainForm.LoadNewLogonBackground(); }); #endif * */ } break; case (VK)'L': if (winDown) { winDown = false; if (Common.DesMachineID != ID.ALL) { KeyboardEvent(hookCallbackKeybdData); Common.SwitchToMachine(Common.MachineName.Trim()); } } else { return ProcessHotKeys(vkCode, hookCallbackKeybdData); } break; case VK.ESCAPE: if (Common.IsTopMostMessageNotNull()) { Common.HideTopMostMessage(); } break; default: Common.LogDebug("X"); return ProcessHotKeys(vkCode, hookCallbackKeybdData); } } return true; } private void UpdateEasyMouseKeyDown(VK vkCode) { EasyMouseOption easyMouseOption = (EasyMouseOption)Setting.Values.EasyMouse; EasyMouseKeyDown = (easyMouseOption == EasyMouseOption.Ctrl && (vkCode == VK.LCONTROL || vkCode == VK.RCONTROL)) || (easyMouseOption == EasyMouseOption.Shift && (vkCode == VK.LSHIFT || vkCode == VK.RSHIFT)); } private static long lastHotKeyLockMachine; private bool ProcessHotKeys(int vkCode, KEYBDDATA hookCallbackKeybdData) { if (vkCode == Setting.Values.HotKeyCaptureScreen && CtrlDown && shiftDown && !altDown && (Common.DesMachineID == Common.MachineID || Common.DesMachineID == ID.ALL)) { CtrlDown = shiftDown = false; if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { Common.PrepareScreenCapture(); return false; } } if (CtrlDown && altDown) { if (shiftDown && vkCode == Setting.Values.HotKeyExitMM && (Common.DesMachineID == Common.MachineID || Common.DesMachineID == ID.ALL)) { Common.DoSomethingInUIThread(() => { Common.MainForm.NotifyIcon.Visible = false; for (int i = 1; i < 10; i++) { Application.DoEvents(); Thread.Sleep(20); } Common.MainForm.Quit(false, false); }); } else if (vkCode == Setting.Values.HotKeySwitchMachine || vkCode == Setting.Values.HotKeySwitchMachine + 1 || vkCode == Setting.Values.HotKeySwitchMachine + 2 || vkCode == Setting.Values.HotKeySwitchMachine + 3) { if (Switch2(vkCode - Setting.Values.HotKeySwitchMachine)) { return false; } } else if (vkCode == Setting.Values.HotKeyLockMachine) { if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { if (Common.GetTick() - lastHotKeyLockMachine < 500) { Common.SwitchToMultipleMode(true, true); hookCallbackKeybdData.wVk = (short)VK.LCONTROL; KeyboardEvent(hookCallbackKeybdData); hookCallbackKeybdData.wVk = (short)VK.LMENU; KeyboardEvent(hookCallbackKeybdData); hookCallbackKeybdData.wVk = vkCode; KeyboardEvent(hookCallbackKeybdData); hookCallbackKeybdData.dwFlags |= (int)Common.LLKHF.UP; hookCallbackKeybdData.wVk = (short)VK.LCONTROL; KeyboardEvent(hookCallbackKeybdData); hookCallbackKeybdData.wVk = (short)VK.LMENU; KeyboardEvent(hookCallbackKeybdData); hookCallbackKeybdData.wVk = vkCode; KeyboardEvent(hookCallbackKeybdData); Common.SwitchToMultipleMode(false, true); _ = NativeMethods.LockWorkStation(); } else { KeyboardEvent(hookCallbackKeybdData); } lastHotKeyLockMachine = Common.GetTick(); return false; } } else if (vkCode == Setting.Values.HotKeyReconnect) { Common.ShowToolTip("Reconnecting...", 2000); Common.LastReconnectByHotKeyTime = Common.GetTick(); Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY; return false; } else if (vkCode == Setting.Values.HotKeySwitch2AllPC) { Common.SwitchToMultipleMode(Common.DesMachineID != ID.ALL, true); return false; } else if (vkCode == Setting.Values.HotKeyToggleEasyMouse) { if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { EasyMouseOption easyMouseOption = (EasyMouseOption)Setting.Values.EasyMouse; if (easyMouseOption is EasyMouseOption.Disable or EasyMouseOption.Enable) { Setting.Values.EasyMouse = (int)(easyMouseOption == EasyMouseOption.Disable ? EasyMouseOption.Enable : EasyMouseOption.Disable); 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); return false; } } } else if (shiftDown && Setting.Values.VKMap != null && vkCode == (Setting.Values.VKMap.ContainsKey(0) ? (int)Setting.Values.VKMap[0] : 'K')) { if (Common.DesMachineID == Common.MachineID || Common.DesMachineID == ID.ALL) { Setting.Values.UseVKMap = !Setting.Values.UseVKMap; return Common.DesMachineID == ID.ALL; } } } return true; } private static bool Switch2(int index) { if (Common.MachineMatrix != null && Common.MachineMatrix.Length > index) { string mcName = Common.MachineMatrix[index].Trim(); if (!string.IsNullOrEmpty(mcName)) { // Common.DoSomethingInUIThread(delegate() { Common.ReleaseAllKeys(); } // ); Common.SwitchToMachine(mcName); if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { Common.ShowToolTip( string.Format( CultureInfo.CurrentCulture, "Control has been switched to {0} by the hotkey Ctrl+Alt+{1}{2}", mcName, Setting.Values.HotKeySwitchMachine == (int)VK.F1 ? "F" : string.Empty, index + 1), 3000); } return true; } } return false; } internal void ResetLastSwitchKeys() { for (int i = 0; i < CONTROL_TOUCH; i++) { ctrlTouches[i] = 0; } CtrlDown = winDown = altDown = false; } } }