// 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 simulation.
//
//
// 2008 created by Truong Do (ductdo).
// 2009-... modified by Truong Do (TruongDo).
// 2023- Included in PowerToys.
//
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library;
using MouseWithoutBorders.Core;
using Windows.UI.Input.Preview.Injection;
using static MouseWithoutBorders.Class.NativeMethods;
[module: SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", Scope = "member", Target = "MouseWithoutBorders.InputSimulation.#keybd_event(System.Byte,System.Byte,System.UInt32,System.Int32)", MessageId = "3", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.InputSimulation.#InputProcessKeyEx(System.Int32,System.Int32,System.Boolean&)", MessageId = "MouseWithoutBorders.NativeMethods.LockWorkStation", Justification = "Dotnet port with style preservation")]
namespace MouseWithoutBorders.Class
{
internal class InputSimulation
{
public static InputInjector Injector;
private InputSimulation()
{
}
internal static InjectedInputMouseInfo MouseInputToInjectedInputMouseInfo(MOUSEINPUT mouseInput)
{
var injectedInput = new InjectedInputMouseInfo();
injectedInput.DeltaX = mouseInput.dx;
injectedInput.DeltaY = mouseInput.dy;
injectedInput.MouseData = (uint)mouseInput.mouseData;
injectedInput.MouseOptions = (InjectedInputMouseOptions)mouseInput.dwFlags;
injectedInput.TimeOffsetInMilliseconds = (uint)mouseInput.time;
return injectedInput;
}
private static uint SendInputEx(NativeMethods.INPUT input)
{
Common.PaintCount = 0;
uint rv = 0;
if (Common.Is64bitOS)
{
NativeMethods.INPUT64 input64 = default;
input64.type = input.type;
// Keyboard
if (input.type == 1)
{
input64.ki.wVk = input.ki.wVk;
input64.ki.wScan = input.ki.wScan;
input64.ki.dwFlags = input.ki.dwFlags;
input64.ki.time = input.ki.time;
input64.ki.dwExtraInfo = input.ki.dwExtraInfo;
}
// Mouse
else
{
input64.mi.dx = input.mi.dx;
input64.mi.dy = input.mi.dy;
input64.mi.dwFlags = input.mi.dwFlags;
input64.mi.mouseData = input.mi.mouseData;
input64.mi.time = input.mi.time;
input64.mi.dwExtraInfo = input.mi.dwExtraInfo;
}
if (input.type == 0 && (input.mi.dwFlags & (int)NativeMethods.MOUSEEVENTF.MOVE) != 0 && NativeMethods.InjectMouseInputAvailable)
{
Injector.InjectMouseInput(new[] { MouseInputToInjectedInputMouseInfo(input64.mi) });
}
else
{
NativeMethods.INPUT64[] inputs = { input64 };
rv = NativeMethods.SendInput64(1, inputs, Marshal.SizeOf(input64));
}
}
else
{
if (input.type == 0 && (input.mi.dwFlags & (int)NativeMethods.MOUSEEVENTF.MOVE) != 0 && NativeMethods.InjectMouseInputAvailable)
{
Injector.InjectMouseInput(new[] { MouseInputToInjectedInputMouseInfo(input.mi) });
}
else
{
NativeMethods.INPUT[] inputs = { input };
rv = NativeMethods.SendInput(1, inputs, Marshal.SizeOf(input));
}
}
return rv;
}
internal static void SendKey(KEYBDDATA kd)
{
string log = string.Empty;
NativeMethods.KEYEVENTF dwFlags = NativeMethods.KEYEVENTF.KEYDOWN;
uint scanCode = 0;
// http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx
if ((kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
{
dwFlags = NativeMethods.KEYEVENTF.KEYUP;
}
if ((kd.dwFlags & (int)Common.LLKHF.EXTENDED) == (int)Common.LLKHF.EXTENDED)
{
dwFlags |= NativeMethods.KEYEVENTF.EXTENDEDKEY;
}
scanCode = NativeMethods.MapVirtualKey((uint)kd.wVk, 0);
InputProcessKeyEx(kd.wVk, kd.dwFlags, out bool eatKey);
if (!eatKey)
{
InputHook.RealData = false;
#if !USING_keybd_event
// #if USING_SendInput
NativeMethods.INPUT structInput;
structInput = default;
structInput.type = 1;
structInput.ki.wScan = (short)scanCode;
structInput.ki.time = 0;
structInput.ki.wVk = (short)kd.wVk;
structInput.ki.dwFlags = (int)dwFlags;
structInput.ki.dwExtraInfo = NativeMethods.GetMessageExtraInfo();
Common.DoSomethingInTheInputSimulationThread(() =>
{
// key press simulation
SendInputEx(structInput);
});
#else
keybd_event((byte)kd.wVk, (byte)scanCode, (UInt32)dwFlags, 0);
#endif
InputHook.RealData = true;
}
log += "*"; // ((Keys)kd.wVk).ToString(CultureInfo.InvariantCulture);
Logger.LogDebug(log);
}
// Md.X, Md.Y is from 0 to 65535
internal static uint SendMouse(MOUSEDATA md)
{
uint rv = 0;
NativeMethods.INPUT mouse_input = default;
long w65535 = (MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left) * 65535 / Common.ScreenWidth;
long h65535 = (MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top) * 65535 / Common.ScreenHeight;
long l65535 = MachineStuff.DesktopBounds.Left * 65535 / Common.ScreenWidth;
long t65535 = MachineStuff.DesktopBounds.Top * 65535 / Common.ScreenHeight;
mouse_input.type = 0;
long dx = (md.X * w65535 / 65535) + l65535;
long dy = (md.Y * h65535 / 65535) + t65535;
mouse_input.mi.dx = (int)dx;
mouse_input.mi.dy = (int)dy;
mouse_input.mi.mouseData = md.WheelDelta;
if (md.dwFlags != Common.WM_MOUSEMOVE)
{
Logger.LogDebug($"InputSimulation.SendMouse: x = {md.X}, y = {md.Y}, WheelDelta = {md.WheelDelta}, dwFlags = {md.dwFlags}.");
}
switch (md.dwFlags)
{
case Common.WM_MOUSEMOVE:
mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
break;
case Common.WM_LBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTDOWN;
break;
case Common.WM_LBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTUP;
break;
case Common.WM_RBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN;
break;
case Common.WM_RBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTUP;
break;
case Common.WM_MBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEDOWN;
break;
case Common.WM_MBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEUP;
break;
case Common.WM_MOUSEWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL;
break;
case Common.WM_MOUSEHWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL;
break;
case Common.WM_XBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP;
break;
case Common.WM_XBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XDOWN;
break;
default:
break;
}
Common.DoSomethingInTheInputSimulationThread(() =>
{
InputHook.RealData = false;
rv = SendInputEx(mouse_input);
});
if (Common.MainFormVisible && !DragDrop.IsDropping)
{
Helper.MainFormDot();
}
return rv;
}
internal static void MoveMouseEx(int x, int y)
{
NativeMethods.INPUT mouse_input = default;
long w65535 = (MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left) * 65535 / Common.ScreenWidth;
long h65535 = (MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top) * 65535 / Common.ScreenHeight;
long l65535 = MachineStuff.DesktopBounds.Left * 65535 / Common.ScreenWidth;
long t65535 = MachineStuff.DesktopBounds.Top * 65535 / Common.ScreenHeight;
mouse_input.type = 0;
long dx = (x * w65535 / 65535) + l65535;
long dy = (y * h65535 / 65535) + t65535;
mouse_input.mi.dx = (int)dx;
mouse_input.mi.dy = (int)dy;
Logger.LogDebug($"InputSimulation.MoveMouseEx: x = {x}, y = {y}.");
mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
Common.DoSomethingInTheInputSimulationThread(() =>
{
InputHook.RealData = false;
SendInputEx(mouse_input);
});
}
// x, y is in pixel
internal static void MoveMouse(int x, int y)
{
// Common.Log("Mouse move: " + x.ToString(CultureInfo.CurrentCulture) + "," + y.ToString(CultureInfo.CurrentCulture));
NativeMethods.INPUT mouse_input = default;
mouse_input.type = 0;
mouse_input.mi.dx = x * 65535 / Common.ScreenWidth;
mouse_input.mi.dy = y * 65535 / Common.ScreenHeight;
mouse_input.mi.mouseData = 0;
mouse_input.mi.dwFlags = (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
Logger.LogDebug($"InputSimulation.MoveMouse: x = {x}, y = {y}.");
Common.DoSomethingInTheInputSimulationThread(() =>
{
InputHook.RealData = false;
SendInputEx(mouse_input);
// NativeMethods.SetCursorPos(x, y);
});
}
// dx, dy is in pixel, relative
internal static void MoveMouseRelative(int dx, int dy)
{
NativeMethods.INPUT mouse_input = default;
mouse_input.type = 0;
mouse_input.mi.dx = dx; // *65535 / Common.ScreenWidth;
mouse_input.mi.dy = dy; // *65535 / Common.ScreenHeight;
mouse_input.mi.mouseData = 0;
mouse_input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.MOVE;
Logger.LogDebug($"InputSimulation.MoveMouseRelative: x = {dx}, y = {dy}.");
Common.DoSomethingInTheInputSimulationThread(() =>
{
InputHook.RealData = false;
SendInputEx(mouse_input);
// NativeMethods.SetCursorPos(x, y);
});
}
internal static void MouseUp()
{
Common.DoSomethingInTheInputSimulationThread(() =>
{
NativeMethods.INPUT input = default;
input.type = 0;
input.mi.dx = 0;
input.mi.dy = 0;
input.mi.mouseData = 0;
input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.LEFTUP;
InputHook.SkipMouseUpCount++;
_ = SendInputEx(input);
Logger.LogDebug("MouseUp() called");
});
}
internal static void MouseClickDotForm(int x, int y)
{
_ = Task.Factory.StartNew(
() =>
{
NativeMethods.INPUT input = default;
input.type = 0;
input.mi.dx = 0;
input.mi.dy = 0;
input.mi.mouseData = 0;
InputHook.SkipMouseUpDown = true;
try
{
MoveMouse(x, y);
InputHook.RealData = false;
input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.LEFTDOWN;
_ = SendInputEx(input);
InputHook.RealData = false;
input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.LEFTUP;
_ = SendInputEx(input);
Logger.LogDebug("MouseClick() called");
Thread.Sleep(200);
}
finally
{
InputHook.SkipMouseUpDown = false;
}
},
System.Threading.CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
}
private static bool winDown;
private static bool ctrlDown;
private static bool altDown;
private static bool shiftDown;
internal static readonly string[] Args = new string[] { "CAD" };
private static void ResetModifiersState(HotkeySettings matchingHotkey)
{
ctrlDown = ctrlDown && matchingHotkey.Ctrl;
altDown = altDown && matchingHotkey.Alt;
shiftDown = shiftDown && matchingHotkey.Shift;
winDown = winDown && matchingHotkey.Win;
}
private static void InputProcessKeyEx(int vkCode, int flags, out bool eatKey)
{
eatKey = false;
if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
{
switch ((VK)vkCode)
{
case VK.LWIN:
case VK.RWIN:
winDown = false;
break;
case VK.LCONTROL:
case VK.RCONTROL:
ctrlDown = false;
break;
case VK.LMENU:
case VK.RMENU:
altDown = false;
break;
case VK.LSHIFT:
case VK.RSHIFT:
shiftDown = false;
break;
default:
break;
}
}
else
{
if (Common.HotkeyMatched(vkCode, winDown, ctrlDown, altDown, shiftDown, Setting.Values.HotKeyLockMachine))
{
if (!Common.RunOnLogonDesktop
&& !Common.RunOnScrSaverDesktop)
{
ResetModifiersState(Setting.Values.HotKeyLockMachine);
eatKey = true;
InitAndCleanup.ReleaseAllKeys();
_ = NativeMethods.LockWorkStation();
}
}
switch ((VK)vkCode)
{
case VK.LWIN:
case VK.RWIN:
winDown = true;
break;
case VK.LCONTROL:
case VK.RCONTROL:
ctrlDown = true;
break;
case VK.LMENU:
case VK.RMENU:
altDown = true;
break;
case VK.LSHIFT:
case VK.RSHIFT:
shiftDown = true;
break;
case VK.DELETE:
if (ctrlDown && altDown)
{
ctrlDown = altDown = false;
eatKey = true;
InitAndCleanup.ReleaseAllKeys();
}
break;
case (VK)'L':
if (winDown)
{
winDown = false;
eatKey = true;
InitAndCleanup.ReleaseAllKeys();
uint rv = NativeMethods.LockWorkStation();
Logger.LogDebug("LockWorkStation returned " + rv.ToString(CultureInfo.CurrentCulture));
}
break;
case VK.END:
if (ctrlDown && altDown)
{
ctrlDown = altDown = false;
new ServiceController("MouseWithoutBordersSvc").Start(Args);
}
break;
default:
break;
}
}
}
internal static void ResetSystemKeyFlags()
{
ctrlDown = winDown = altDown = false;
}
}
}