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 VirtualKeyBack = 0x08; private const ushort VirtualKeyReturn = 0x0D; public void InsertText(string text) { if (string.IsNullOrEmpty(text)) { return; } AppLogger.Info($"Injecting text length: {text.Length}"); foreach (var character in text) { SendUnicode(character); } } 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 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 KEYBDINPUT ki; } [StructLayout(LayoutKind.Sequential)] private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo; } }