diff --git a/pkg/mcp/keycodes.go b/pkg/mcp/keycodes.go new file mode 100644 index 00000000..954d7c41 --- /dev/null +++ b/pkg/mcp/keycodes.go @@ -0,0 +1,21 @@ +package mcp + +var keys = map[string]uint32{ + "ArrowUp": 273, + "ArrowDown": 274, + "ArrowRight": 275, + "ArrowLeft": 276, + "Enter": 13, + "Space": 32, + "KeyA": 97, + "KeyB": 98, + "KeyX": 99, + "KeyY": 100, +} + +func keyCode(k string) uint32 { + if v, ok := keys[k]; ok { + return v + } + return 0 +} diff --git a/pkg/mcp/modelcontext.go b/pkg/mcp/modelcontext.go new file mode 100644 index 00000000..09e9437f --- /dev/null +++ b/pkg/mcp/modelcontext.go @@ -0,0 +1,39 @@ +package mcp + +import ( + "encoding/binary" + "encoding/json" +) + +type Action struct { + Key string `json:"key"` + Press bool `json:"press"` +} + +type Message struct { + Actions []Action `json:"actions"` +} + +func Parse(data []byte) (Message, error) { + var m Message + err := json.Unmarshal(data, &m) + return m, err +} + +func actionBytes(a Action) []byte { + buf := make([]byte, 7) + binary.BigEndian.PutUint32(buf, keyCode(a.Key)) + if a.Press { + buf[4] = 1 + } + // last two bytes are modifier flags, not used + return buf +} + +func ToBytes(m Message) [][]byte { + b := make([][]byte, len(m.Actions)) + for i, a := range m.Actions { + b[i] = actionBytes(a) + } + return b +} diff --git a/pkg/worker/caged/caged.go b/pkg/worker/caged/caged.go index 85ede127..26a5a33d 100644 --- a/pkg/worker/caged/caged.go +++ b/pkg/worker/caged/caged.go @@ -19,6 +19,7 @@ const ( RetroPad = libretro.RetroPad Keyboard = libretro.Keyboard Mouse = libretro.Mouse + MCP = libretro.MCP ) type ModName string diff --git a/pkg/worker/caged/libretro/frontend.go b/pkg/worker/caged/libretro/frontend.go index c3666e98..4ec25086 100644 --- a/pkg/worker/caged/libretro/frontend.go +++ b/pkg/worker/caged/libretro/frontend.go @@ -78,6 +78,7 @@ const ( RetroPad = Device(nanoarch.RetroPad) Keyboard = Device(nanoarch.Keyboard) Mouse = Device(nanoarch.Mouse) + MCP = Device(nanoarch.MCP) ) var ( @@ -340,6 +341,8 @@ func (f *Frontend) Input(port int, device byte, data []byte) { f.nano.InputKeyboard(port, data) case Mouse: f.nano.InputMouse(port, data) + case MCP: + f.nano.InputMCP(port, data) } } diff --git a/pkg/worker/caged/libretro/nanoarch/input.go b/pkg/worker/caged/libretro/nanoarch/input.go index 246988d4..760fe6a5 100644 --- a/pkg/worker/caged/libretro/nanoarch/input.go +++ b/pkg/worker/caged/libretro/nanoarch/input.go @@ -47,6 +47,7 @@ const ( RetroPad Device = iota Keyboard Mouse + MCP ) const ( diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index fd12ef14..66c853a0 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -13,6 +13,7 @@ import ( "unsafe" "github.com/giongto35/cloud-game/v3/pkg/logger" + "github.com/giongto35/cloud-game/v3/pkg/mcp" "github.com/giongto35/cloud-game/v3/pkg/os" "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/graphics" "github.com/giongto35/cloud-game/v3/pkg/worker/thread" @@ -443,6 +444,17 @@ func (n *Nanoarch) InputMouse(_ int, data []byte) { } } +func (n *Nanoarch) InputMCP(port int, data []byte) { + msg, err := mcp.Parse(data) + if err != nil { + Nan0.log.Error().Err(err).Msg("mcp parse") + return + } + for _, b := range mcp.ToBytes(msg) { + n.InputKeyboard(port, b) + } +} + func videoSetPixelFormat(format uint32) (C.bool, error) { switch format { case C.RETRO_PIXEL_FORMAT_0RGB1555: diff --git a/pkg/worker/coordinatorhandlers.go b/pkg/worker/coordinatorhandlers.go index 536c6ed6..ad11afea 100644 --- a/pkg/worker/coordinatorhandlers.go +++ b/pkg/worker/coordinatorhandlers.go @@ -200,6 +200,7 @@ func (c *coordinator) HandleGameStart(rq api.StartGameRequest[com.Uid], w *Worke if needsKbMouse { _ = s.AddChannel("keyboard", func(data []byte) { r.App().Input(user.Index, byte(caged.Keyboard), data) }) _ = s.AddChannel("mouse", func(data []byte) { r.App().Input(user.Index, byte(caged.Mouse), data) }) + _ = s.AddChannel("mcp", func(data []byte) { r.App().Input(user.Index, byte(caged.MCP), data) }) } c.RegisterRoom(r.Id())