package remdeshid import ( "encoding/json" "fmt" "log" "net/http" "time" "github.com/gorilla/websocket" ) type EventType int const ( EventTypeKeyPress EventType = iota EventTypeKeyRelease EventTypeMouseMove EventTypeMousePress EventTypeMouseRelease EventTypeMouseScroll EventTypeHIDCommand ) const MinCusrorEventInterval = 1 //ms type HIDCommand struct { Event EventType `json:"event"` Keycode int `json:"keycode,omitempty"` IsRightModKey bool `json:"is_right_modifier_key,omitempty"` // true if the key is a right modifier key (Ctrl, Shift, Alt, GUI) MouseAbsX int `json:"mouse_x,omitempty"` // Absolute mouse position in X direction MouseAbsY int `json:"mouse_y,omitempty"` // Absolute mouse position in Y direction MouseRelX int `json:"mouse_rel_x,omitempty"` // Relative mouse movement in X direction MouseRelY int `json:"mouse_rel_y,omitempty"` // Relative mouse movement in Y direction MouseButton int `json:"mouse_button,omitempty"` //0x01 for left click, 0x02 for right click, 0x03 for middle clicks MouseScroll int `json:"mouse_scroll,omitempty"` // Positive for scroll up, negative for scroll down, max 127 } // HIDWebSocketHandler is a handler for the HID WebSocket connection var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, } func (c *Controller) IsModifierKeys(keycode int) bool { // Modifier keycodes for JavaScript modifierKeys := []int{16, 17, 18, 91} // Shift, Ctrl, Alt, Meta (Windows/Command key) for _, key := range modifierKeys { if keycode == key { return true } } return false } // Convert a keycode to the corresponding HID opcode for modifier keys func (c *Controller) GetModkeyOpcode(keycode int, isRight bool) int { switch keycode { case 17: // Ctrl if isRight { return 0x05 // Right Ctrl } return 0x01 // Left Ctrl case 16: // Shift if isRight { return 0x06 // Right Shift } return 0x02 // Left Shift case 18: // Alt if isRight { return 0x07 // Right Alt } return 0x03 // Left Alt case 91: // Meta (Windows/Command key) if isRight { return 0x08 // Right GUI (Windows) } return 0x04 // Left GUI (Windows) default: return 0x00 // Unknown or unmapped key } } // Generate the required LTV (length, type, value) bytes for the HID command func (c *Controller) GenerateUARTCmd(HIDCommand *HIDCommand) ([]byte, error) { switch HIDCommand.Event { case EventTypeKeyPress: if c.IsModifierKeys(HIDCommand.Keycode) { // If it's a modifier key, we need to return the appropriate opcode opcode := c.GetModkeyOpcode(HIDCommand.Keycode, HIDCommand.IsRightModKey) if opcode == 0x00 { return nil, fmt.Errorf("unsupported modifier key: %d", HIDCommand.Keycode) } return []byte{0x02, 0x03, byte(opcode)}, nil } else if HIDCommand.Keycode == 13 && HIDCommand.IsRightModKey { //Special case for right Enter key return []byte{0x02, 0x01, 0x92}, nil } return []byte{0x02, 0x01, byte(HIDCommand.Keycode)}, nil case EventTypeKeyRelease: if c.IsModifierKeys(HIDCommand.Keycode) { // If it's a modifier key, we need to return the appropriate opcode opcode := c.GetModkeyOpcode(HIDCommand.Keycode, HIDCommand.IsRightModKey) if opcode == 0x00 { return nil, fmt.Errorf("unsupported modifier key: %d", HIDCommand.Keycode) } return []byte{0x02, 0x04, byte(opcode)}, nil } else if HIDCommand.Keycode == 13 && HIDCommand.IsRightModKey { //Special case for right Enter key return []byte{0x02, 0x02, 0x92}, nil } return []byte{0x02, 0x02, byte(HIDCommand.Keycode)}, nil case EventTypeMouseMove: if (time.Now().UnixMilli() - c.lastCursorEventTime) < MinCusrorEventInterval { // If the last cursor event was too recent, we skip sending this one return []byte{}, nil } c.lastCursorEventTime = time.Now().UnixMilli() if HIDCommand.MouseAbsX != 0 || HIDCommand.MouseAbsY != 0 { // If absolute mouse movement is specified, we send it as a single command xLSB := byte(HIDCommand.MouseAbsX & 0xFF) xMSB := byte((HIDCommand.MouseAbsX >> 8) & 0xFF) yLSB := byte(HIDCommand.MouseAbsY & 0xFF) yMSB := byte((HIDCommand.MouseAbsY >> 8) & 0xFF) return []byte{0x05, 0x09, xLSB, xMSB, yLSB, yMSB}, nil } else if HIDCommand.MouseRelX != 0 || HIDCommand.MouseRelY != 0 { // If relative mouse movement is specified, we send it as a single command //TODO return []byte{0x03, 0x0A, byte(HIDCommand.MouseRelX), byte(HIDCommand.MouseRelY)}, nil } return nil, fmt.Errorf("no mouse movement specified") case EventTypeMousePress: if HIDCommand.MouseButton < 1 || HIDCommand.MouseButton > 3 { return nil, fmt.Errorf("invalid mouse button: %d", HIDCommand.MouseButton) } // Mouse button click event return []byte{0x02, 0x05, byte(HIDCommand.MouseButton)}, nil case EventTypeMouseRelease: if HIDCommand.MouseButton < 1 || HIDCommand.MouseButton > 3 { return nil, fmt.Errorf("invalid mouse button: %d", HIDCommand.MouseButton) } // Mouse button release event return []byte{0x02, 0x06, byte(HIDCommand.MouseButton)}, nil case EventTypeMouseScroll: if (time.Now().UnixMilli() - c.lastCursorEventTime) < MinCusrorEventInterval { // If the last cursor event was too recent, we skip sending this one return []byte{}, nil } c.lastCursorEventTime = time.Now().UnixMilli() if HIDCommand.MouseScroll < -127 || HIDCommand.MouseScroll > 127 { return nil, fmt.Errorf("invalid mouse scroll value: %d", HIDCommand.MouseScroll) } // Mouse scroll event if HIDCommand.MouseScroll < 0 { return []byte{0x02, 0x07, byte(HIDCommand.MouseScroll * -1)}, nil } else { return []byte{0x02, 0x08, byte(HIDCommand.MouseScroll)}, nil } default: return nil, fmt.Errorf("unsupported HID command event type: %d", HIDCommand.Event) } } func (c *Controller) HIDWebSocketHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("Failed to upgrade to websocket:", err) return } defer conn.Close() for { _, message, err := conn.ReadMessage() if err != nil { log.Println("Error reading message:", err) break } log.Printf("Received: %s", message) //Try parsing the message as a HIDCommand var hidCmd HIDCommand if err := json.Unmarshal(message, &hidCmd); err != nil { log.Println("Error parsing message:", err) continue } bytes, err := c.GenerateUARTCmd(&hidCmd) if err != nil { errmsg := map[string]string{"error": err.Error()} if err := conn.WriteJSON(errmsg); err != nil { log.Println("Error writing message:", err) continue } log.Println("Error generating UART command:", err) continue } if len(bytes) == 0 { if err := conn.WriteMessage(websocket.TextMessage, []byte("ok")); err != nil { log.Println("Error writing message:", err) } continue } fmt.Println("Sending bytes:", bytes) //Write the bytes to the serial port if err := c.Send(bytes); err != nil { errmsg := map[string]string{"error": err.Error()} if err := conn.WriteJSON(errmsg); err != nil { log.Println("Error writing message:", err) } continue } if err := conn.WriteMessage(websocket.TextMessage, []byte("ok")); err != nil { log.Println("Error writing message:", err) continue } } }