namespace WirelessTextSyncer.Windows.Services; using System.Runtime.InteropServices; public sealed class KeyboardInjectionService : IKeyboardInjectionService { private const uint InputKeyboard = 1; private const uint KeyEventFKeyUp = 0x0002; private const uint KeyEventFUnicode = 0x0004; private const ushort VirtualKeyA = 0x41; private const ushort VirtualKeyBack = 0x08; private const ushort VirtualKeyControl = 0x11; private const ushort VirtualKeyV = 0x56; private const ushort VirtualKeyReturn = 0x0D; public void InsertText(string text) { if (string.IsNullOrEmpty(text)) { return; } AppLogger.Info($"Injecting text length: {text.Length}"); PasteText(text); } public void TypeText(string text) { if (string.IsNullOrEmpty(text)) { return; } foreach (var character in text) { SendUnicode(character); } } private static void PasteText(string text) { var hadText = Clipboard.ContainsText(); var previousText = hadText ? Clipboard.GetText() : null; try { Clipboard.SetText(text); SendModifiedKey(VirtualKeyControl, VirtualKeyV); } finally { if (hadText && previousText is not null) { Clipboard.SetText(previousText); } else { Clipboard.Clear(); } } } public void ReplaceFocusedText(string text) { AppLogger.Info($"Replacing focused text length: {text.Length}"); SendModifiedKey(VirtualKeyControl, VirtualKeyA); Backspace(); InsertText(text); AppLogger.Info("Finished replacing focused text."); } public void Backspace() { AppLogger.Info("Injecting backspace."); SendVirtualKey(VirtualKeyBack); } public void Enter() { AppLogger.Info("Injecting enter."); SendVirtualKey(VirtualKeyReturn); } private static void SendUnicode(char character) { Send([ CreateUnicodeInput(character, 0), CreateUnicodeInput(character, KeyEventFKeyUp) ]); } private static void SendVirtualKey(ushort virtualKey) { Send([ CreateVirtualKeyInput(virtualKey, 0), CreateVirtualKeyInput(virtualKey, KeyEventFKeyUp) ]); } private static void SendModifiedKey(ushort modifierKey, ushort key) { Send([ CreateVirtualKeyInput(modifierKey, 0), CreateVirtualKeyInput(key, 0), CreateVirtualKeyInput(key, KeyEventFKeyUp), CreateVirtualKeyInput(modifierKey, KeyEventFKeyUp) ]); } private static void Send(INPUT[] inputs) { var sent = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf()); if (sent != inputs.Length) { var error = Marshal.GetLastWin32Error(); AppLogger.Error( $"Windows accepted {sent}/{inputs.Length} keyboard input events. Win32 error: {error}"); } } private static INPUT CreateUnicodeInput(char character, uint flags) { return new INPUT { type = InputKeyboard, U = new InputUnion { ki = new KEYBDINPUT { wScan = character, dwFlags = KeyEventFUnicode | flags } } }; } private static INPUT CreateVirtualKeyInput(ushort virtualKey, uint flags) { return new INPUT { type = InputKeyboard, U = new InputUnion { ki = new KEYBDINPUT { wVk = virtualKey, dwFlags = flags } } }; } [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure); [StructLayout(LayoutKind.Sequential)] private struct INPUT { public uint type; public InputUnion U; } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public HARDWAREINPUT hi; } [StructLayout(LayoutKind.Sequential)] private struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct HARDWAREINPUT { public uint uMsg; public ushort wParamL; public ushort wParamH; } }