using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; public static class PiDesktop { [StructLayout(LayoutKind.Sequential)] struct MouseInput { public int dx, dy; public uint mouseData, flags, time; public UIntPtr extraInfo; } [StructLayout(LayoutKind.Sequential)] struct KeyboardInput { public ushort vk, scan; public uint flags, time; public UIntPtr extraInfo; } [StructLayout(LayoutKind.Explicit)] struct InputUnion { [FieldOffset(0)] public MouseInput mouse; [FieldOffset(0)] public KeyboardInput keyboard; } [StructLayout(LayoutKind.Sequential)] struct Input { public uint type; public InputUnion data; } [StructLayout(LayoutKind.Sequential)] struct Point { public int x, y; } [StructLayout(LayoutKind.Sequential)] struct CursorInfo { public uint size, flags; public IntPtr cursor; public Point position; } [StructLayout(LayoutKind.Sequential)] struct IconInfo { public int isIcon; public uint xHotspot, yHotspot; public IntPtr mask, color; } [DllImport("user32.dll", SetLastError = true)] static extern uint SendInput(uint count, Input[] inputs, int size); [DllImport("user32.dll")] static extern int GetSystemMetrics(int index); [DllImport("user32.dll")] static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context); [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern short VkKeyScanEx(char ch, IntPtr layout); [DllImport("user32.dll")] static extern IntPtr GetKeyboardLayout(uint threadId); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr window, IntPtr processId); [DllImport("user32.dll")] static extern bool GetCursorInfo(ref CursorInfo info); [DllImport("user32.dll")] static extern bool GetIconInfo(IntPtr icon, out IconInfo info); [DllImport("user32.dll")] static extern bool DrawIconEx(IntPtr dc, int x, int y, IntPtr icon, int width, int height, uint step, IntPtr brush, uint flags); [DllImport("gdi32.dll")] static extern bool DeleteObject(IntPtr obj); static int heldButtons; static readonly List heldKeys = new List(); static readonly Dictionary modifiers = new Dictionary { { "shift", 0x10 }, { "ctrl", 0x11 }, { "alt", 0x12 }, { "logo", 0x5b } }; static readonly Dictionary namedKeys = new Dictionary { { "Return", 0x0d }, { "Enter", 0x0d }, { "Escape", 0x1b }, { "Esc", 0x1b }, { "Tab", 0x09 }, { "BackSpace", 0x08 }, { "Backspace", 0x08 }, { "Delete", 0x2e }, { "Del", 0x2e }, { "Insert", 0x2d }, { "Ins", 0x2d }, { "space", 0x20 }, { "Left", 0x25 }, { "Right", 0x27 }, { "Up", 0x26 }, { "Down", 0x28 }, { "Page_Up", 0x21 }, { "Page_Down", 0x22 }, { "Home", 0x24 }, { "End", 0x23 } }; public static void Initialize() { // DPI virtualization would otherwise put capture and input in different coordinate spaces. SetProcessDPIAware(); try { SetThreadDpiAwarenessContext(new IntPtr(-4)); } catch (EntryPointNotFoundException) { } for (int i = 1; i <= 12; i++) namedKeys["F" + i] = (ushort)(0x6f + i); } static void Send(params Input[] inputs) { if (SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(Input))) != inputs.Length) throw new Win32Exception(Marshal.GetLastWin32Error(), "Windows rejected desktop input. Elevated apps require Pi to run at the same integrity level; secure desktops are not supported."); } static Input Mouse(uint flags, uint data, int x, int y) { return new Input { type = 0, data = new InputUnion { mouse = new MouseInput { flags = flags, mouseData = data, dx = x, dy = y } } }; } static Input Key(ushort vk, ushort scan, uint flags) { if ((vk >= 0x21 && vk <= 0x28) || vk == 0x2d || vk == 0x2e || vk == 0x5b) flags |= 0x0001; return new Input { type = 1, data = new InputUnion { keyboard = new KeyboardInput { vk = vk, scan = scan, flags = flags } } }; } static int ButtonMask(string[] buttons) { int mask = 0; foreach (string button in buttons ?? new string[0]) { switch (button) { case "left": mask |= 1; break; case "middle": mask |= 2; break; case "right": mask |= 4; break; default: throw new ArgumentException("Unsupported button: " + button); } } return mask; } public static void Pointer(int x, int y, string[] buttons) { if (x < 0 || x > 1919 || y < 0 || y > 1079) throw new ArgumentOutOfRangeException("Desktop coordinates"); int next = ButtonMask(buttons); uint flags = 0x0001 | 0x8000 | 0x4000; int[] bits = { 1, 2, 4 }; uint[] down = { 0x0002, 0x0020, 0x0008 }; uint[] up = { 0x0004, 0x0040, 0x0010 }; for (int i = 0; i < bits.Length; i++) if (((next ^ heldButtons) & bits[i]) != 0) flags |= (next & bits[i]) != 0 ? down[i] : up[i]; heldButtons |= next; Send(Mouse(flags, 0, (int)Math.Round(x * 65535.0 / 1919), (int)Math.Round(y * 65535.0 / 1079))); heldButtons = next; } public static void Scroll(int x, int y, string direction, int ticks, string[] buttons) { uint flag; int delta; switch (direction) { case "up": flag = 0x0800; delta = 120; break; case "down": flag = 0x0800; delta = -120; break; case "left": flag = 0x1000; delta = -120; break; case "right": flag = 0x1000; delta = 120; break; default: throw new ArgumentException("Unsupported direction: " + direction); } Pointer(x, y, buttons); for (int i = 0; i < ticks; i++) Send(Mouse(flag, unchecked((uint)delta), 0, 0)); } public static void PressKey(string name, string[] requestedModifiers) { var keys = new List(); foreach (string modifier in requestedModifiers ?? new string[0]) { ushort key; if (!modifiers.TryGetValue(modifier, out key)) throw new ArgumentException("Unsupported modifier: " + modifier); if (!keys.Contains(key)) keys.Add(key); } ushort vk; if (!namedKeys.TryGetValue(name, out vk)) { if (name.Length != 1) throw new ArgumentException("Unsupported key name: " + name); uint thread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero); short mapped = VkKeyScanEx(name[0], GetKeyboardLayout(thread)); if (mapped == -1) throw new ArgumentException("Key is absent from the active keyboard layout: " + name); vk = (ushort)(mapped & 0xff); ushort[] implied = { 0x10, 0x11, 0x12 }; for (int i = 0; i < implied.Length; i++) if ((mapped & (0x100 << i)) != 0 && !keys.Contains(implied[i])) keys.Add(implied[i]); } keys.Add(vk); try { foreach (ushort key in keys) { heldKeys.Add(key); Send(Key(key, 0, 0)); } } finally { ReleaseKeys(); } } public static void TypeText(string text) { foreach (char ch in text.Replace("\r\n", "\n")) { if (ch == '\n' || ch == '\r') PressKey("Return", null); else if (ch == '\t') PressKey("Tab", null); else Send(Key(0, ch, 0x0004), Key(0, ch, 0x0004 | 0x0002)); } } static void ReleaseKeys() { var keys = new List(heldKeys); keys.Reverse(); foreach (ushort key in keys) { Send(Key(key, 0, 0x0002)); heldKeys.Remove(key); } } public static void Release() { try { ReleaseKeys(); } finally { uint flags = 0; if ((heldButtons & 1) != 0) flags |= 0x0004; if ((heldButtons & 2) != 0) flags |= 0x0040; if ((heldButtons & 4) != 0) flags |= 0x0010; if (flags != 0) Send(Mouse(flags, 0, 0, 0)); heldButtons = 0; } } public static string Screenshot() { int left = GetSystemMetrics(76), top = GetSystemMetrics(77); int width = GetSystemMetrics(78), height = GetSystemMetrics(79); using (var desktop = new Bitmap(width, height, PixelFormat.Format32bppArgb)) using (var graphics = Graphics.FromImage(desktop)) using (var output = new Bitmap(1920, 1080, PixelFormat.Format32bppArgb)) using (var scaled = Graphics.FromImage(output)) using (var stream = new MemoryStream()) { graphics.CopyFromScreen(left, top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); var cursor = new CursorInfo { size = (uint)Marshal.SizeOf(typeof(CursorInfo)) }; IconInfo icon; if (GetCursorInfo(ref cursor) && (cursor.flags & 1) != 0 && GetIconInfo(cursor.cursor, out icon)) { IntPtr dc = graphics.GetHdc(); try { DrawIconEx(dc, cursor.position.x - left - (int)icon.xHotspot, cursor.position.y - top - (int)icon.yHotspot, cursor.cursor, 0, 0, 0, IntPtr.Zero, 3); } finally { graphics.ReleaseHdc(dc); if (icon.mask != IntPtr.Zero) DeleteObject(icon.mask); if (icon.color != IntPtr.Zero) DeleteObject(icon.color); } } scaled.InterpolationMode = InterpolationMode.HighQualityBicubic; scaled.DrawImage(desktop, new Rectangle(0, 0, 1920, 1080)); output.Save(stream, ImageFormat.Png); return Convert.ToBase64String(stream.ToArray()); } } public static void OpenBrowser(string url) { var uri = new Uri(url); if (uri.Scheme != "http" && uri.Scheme != "https") throw new ArgumentException("Only HTTP and HTTPS URLs are supported"); Process.Start(new ProcessStartInfo(uri.AbsoluteUri) { UseShellExecute = true }); } }