游戏手柄输入转换桥接器 (Yóuxì shǒubǐng shūrù zhuǎnhuàn qiáojiē)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.Web.WebView2.WinForms;

//thx
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
//https://qiita.com/kob58im/items/5a9d909377272d74eefd
//https://www.it-swarm-ja.com/ja/c%23/user32dll%e3%81%aesendinput%e3%82%92%e4%bb%8b%e3%81%97%e3%81%a6%e3%82%ad%e3%83%bc%e3%82%92%e9%80%81%e4%bf%a1%e3%81%99%e3%82%8b/1068919389/

namespace Gamepad
{
    public class InputStructures {

        [DllImport("user32.dll", SetLastError = true)]
        public extern static int
            SendInput(int length, ref InputEvent events, int size);

        [DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
        public extern static int MapVirtualKey(int wCode, int wMapType);

        public const int MOUSEEVENTF_MOVE       = 0x01;
        public const int MOUSEEVENTF_LEFTDOWN   = 0x02;
        public const int MOUSEEVENTF_LEFTUP     = 0x04;
        public const int MOUSEEVENTF_RIGHTDOWN  = 0x08;
        public const int MOUSEEVENTF_RIGHTUP    = 0x10;
        public const int MOUSEEVENTF_MIDDLEDOWN = 0x20;
        public const int MOUSEEVENTF_MIDDLEUP   = 0x40;

        public const int KEYEVENTF_KEYUP        = 0x02;
        public const int MAPVK_VK_TO_VSC        = 0;

        [StructLayout(LayoutKind.Sequential)]
        public struct InputEvent{
            public int Type;
            public InputUnion Data;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct InputUnion{
            [FieldOffset(0)] public MouseInput Mouse;
            [FieldOffset(0)] public KeyboardInput Keyboard;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MouseInput{
            public int X;
            public int Y;
            public int MouseData;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KeyboardInput{
            public short VirtualKey;
            public short ScanCode;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }
    }

    [ComVisible(true)]
    public class Bridge : InputStructures {

        public class ControllerEvent{
            public string type  { get; set; }
            public string key   { get; set; }
            public int button   { get; set; }
            public float x      { get; set; }
            public float y      { get; set; }
        }

        public void dispatch(string s){
            var e = JsonSerializer.Deserialize<ControllerEvent>(s);
            switch(e.type){
                case "mousemove":   SendMouseMove(e);   break;
                case "mousedown":   SendMouse(e);       break;
                case "mouseup":     SendMouse(e);       break;
                case "keydown":     SendKeyboard(e);    break;
                case "keyup":       SendKeyboard(e);    break;
            }
        }

        static readonly Dictionary<string,object>
            flag = new Dictionary<string,object>(){
                {"keydown",     0},
                {"keyup",       KEYEVENTF_KEYUP},
                {"mousemove",   MOUSEEVENTF_MOVE},
                {"mousedown",   new []{
                    MOUSEEVENTF_LEFTDOWN,
                    MOUSEEVENTF_MIDDLEDOWN,
                    MOUSEEVENTF_RIGHTDOWN,
                }},
                {"mouseup",     new []{
                    MOUSEEVENTF_LEFTUP,
                    MOUSEEVENTF_MIDDLEUP,
                    MOUSEEVENTF_RIGHTUP,
                }}
            };

        static void SendMouseMove(ControllerEvent e){
            var v = new InputEvent(){Type = 0};
            v.Data.Mouse    = new MouseInput(){
                Flags       = (int)flag[e.type],
                X           = (int)Math.Round(e.x),
                Y           = (int)Math.Round(e.y),
            };
            SendInput(1, ref v, Marshal.SizeOf(v));
        }

        static void SendMouse(ControllerEvent e){
            var v = new InputEvent(){Type = 0};
            v.Data.Mouse    = new MouseInput(){
                Flags       = (int)(flag[e.type] as int[])[e.button]
            };
            SendInput(1, ref v, Marshal.SizeOf(v));
        }

        static void SendKeyboard(ControllerEvent e){
            if (Keys.TryParse(e.key, out Keys k)){
                var v = new InputEvent(){Type = 1};
                v.Data.Keyboard = new KeyboardInput(){
                    VirtualKey  = (short)k,
                    ScanCode    = (short)MapVirtualKey((int)k,MAPVK_VK_TO_VSC),
                    Flags       = (int)flag[e.type],
                };
                SendInput(1, ref v, Marshal.SizeOf(v));
            }
        }
    }

    public class Browser : Form {

        public Browser(string html){
            ClientSize = new System.Drawing.Size(600,400);
            Text = "Gamepad";
            var v = new WebView2(){
                Source  = new Uri(GetPath()+"\\"+html),
                Size    = ClientSize,
            };
            v.NavigationCompleted += (s,e) => 
                v.CoreWebView2.AddHostObjectToScript("bridge",new Bridge());
            SizeChanged += (s,e) => v.Size = ClientSize;
            Controls.Add(v);
        }

        private static string GetPath(){
            var p = System.Diagnostics.Process.GetCurrentProcess();
            return System.IO.Path.GetDirectoryName(p.MainModule.FileName);
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Browser("Gamepad.html"));
        }
    }
}