Skip to content

Commit

Permalink
Add the initial mouse support
Browse files Browse the repository at this point in the history
  • Loading branch information
sergystepanov committed Mar 2, 2024
1 parent 1539151 commit 0704a76
Show file tree
Hide file tree
Showing 15 changed files with 581 additions and 338 deletions.
12 changes: 12 additions & 0 deletions pkg/network/webrtc/webrtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Peer struct {
log *logger.Logger
OnMessage func(data []byte)
OnKeyboard func(data []byte)
OnMouse func(data []byte)

a *webrtc.TrackLocalStaticSample
v *webrtc.TrackLocalStaticSample
Expand Down Expand Up @@ -109,6 +110,17 @@ func (p *Peer) NewCall(vCodec, aCodec string, onICECandidate func(ice any)) (sdp
})
p.log.Debug().Msg("Added [keyboard] chan")

mChan, err := p.addDataChannel("mouse")
if err != nil {
return "", err
}
mChan.OnMessage(func(m webrtc.DataChannelMessage) {
if p.OnMouse != nil {
p.OnMouse(m.Data)
}
})
p.log.Debug().Msg("Added [mouse] chan")

p.conn.OnICEConnectionStateChange(p.handleICEState(func() { p.log.Info().Msg("Connected") }))
// Stream provider supposes to send offer
offer, err := p.conn.CreateOffer(nil)
Expand Down
1 change: 1 addition & 0 deletions pkg/worker/caged/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type App interface {
SetDataCb(func([]byte))
InputGamepad(port int, data []byte)
InputKeyboard(port int, data []byte)
InputMouse(port int, data []byte)
}

type Audio struct {
Expand Down
1 change: 1 addition & 0 deletions pkg/worker/caged/libretro/caged.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (c *Caged) ViewportSize() (int, int) { return c.base.ViewportSiz
func (c *Caged) Scale() float64 { return c.Emulator.Scale() }
func (c *Caged) InputGamepad(port int, data []byte) { c.base.Input(port, RetroPad, data) }
func (c *Caged) InputKeyboard(port int, data []byte) { c.base.Input(port, Keyboard, data) }
func (c *Caged) InputMouse(port int, data []byte) { c.base.Input(port, Mouse, data) }
func (c *Caged) Start() { go c.Emulator.Start() }
func (c *Caged) SetSaveOnClose(v bool) { c.base.SaveOnClose = v }
func (c *Caged) SetSessionId(name string) { c.base.SetSessionId(name) }
Expand Down
3 changes: 3 additions & 0 deletions pkg/worker/caged/libretro/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Device byte
const (
RetroPad = Device(nanoarch.RetroPad)
Keyboard = Device(nanoarch.Keyboard)
Mouse = Device(nanoarch.Mouse)
)

var (
Expand Down Expand Up @@ -300,6 +301,8 @@ func (f *Frontend) Input(port int, d Device, data []byte) {
f.nano.InputRetropad(port, data)
case Keyboard:
f.nano.InputKeyboard(port, data)
case Mouse:
f.nano.InputMouse(port, data)
}
}

Expand Down
112 changes: 101 additions & 11 deletions pkg/worker/caged/libretro/nanoarch/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import "C"
const KeyPressed = C.int16_t(1)
const KeyReleased = C.int16_t(0)

const RetroDeviceTypeShift = 8

// InputState stores full controller state.
// It consists of:
// - uint16 button values
Expand All @@ -29,13 +27,27 @@ type (
mod uint16
mu sync.Mutex
}
MouseState struct {
x, y int16
mp sync.Mutex
l bool
r bool
m bool
mb sync.Mutex
}
)

type Device byte

const (
RetroPad Device = iota
Keyboard
Mouse
)

const (
MouseMove = iota
MouseButton
)

const (
Expand Down Expand Up @@ -64,24 +76,42 @@ func (s *InputState) IsDpadTouched(port uint, axis uint) (shift C.int16_t) {
return C.int16_t(atomic.LoadInt32(&s[port].axes[axis]))
}

func RetroDeviceSubclass(base, id int) int { return ((id + 1) << RetroDeviceTypeShift) | base }

func NewKeyboardState() KeyboardState {
return KeyboardState{
keys: make(map[uint]struct{}),
mod: 0,
}
}

func (ks *KeyboardState) Set(press bool, key uint, mod uint16) {
// SetKey sets keyboard state.
//
// data format
//
// 0 1 2 3 4 5 6
// [ KEY ] P MOD
//
// KEY contains Libretro code of the keyboard key (4 bytes).
// P contains 0 or 1 if the key is pressed (1 byte).
// MOD contains bitmask for Alt | Ctrl | Meta | Shift keys press state (2 bytes).
//
// Returns decoded state from the input bytes.
func (ks *KeyboardState) SetKey(data []byte) (pressed bool, key uint, mod uint16) {
if len(data) != 7 {
return
}

pressed = data[4] == 1
key = uint(binary.BigEndian.Uint32(data))
mod = binary.BigEndian.Uint16(data[5:])
ks.mu.Lock()
if press {
if pressed {
ks.keys[key] = struct{}{}
} else {
delete(ks.keys, key)
}
ks.mod = mod
ks.mu.Unlock()
return
}

func (ks *KeyboardState) Pressed(key uint) C.int16_t {
Expand All @@ -94,9 +124,69 @@ func (ks *KeyboardState) Pressed(key uint) C.int16_t {
return KeyReleased
}

func decodeKeyboardState(data []byte) (bool, uint, uint16) {
press := data[4] == 1
key := uint(binary.LittleEndian.Uint32(data))
mod := binary.LittleEndian.Uint16(data[5:])
return press, key, mod
// ShiftPos sets mouse relative position state.
//
// data format
//
// 0 1 2 3
// [x] [y]
//
// x and y contain relative (to the previous values)
// X, Y positions as signed int.
func (ms *MouseState) ShiftPos(data []byte) {
if len(data) != 4 {
return
}
dx := int16(data[0])<<8 + int16(data[1])
dy := int16(data[2])<<8 + int16(data[3])
ms.mp.Lock()
ms.x += dx
ms.y += dy
ms.mp.Unlock()
}

func (ms *MouseState) PopX() C.int16_t {
var x int16
ms.mp.Lock()
x = ms.x
ms.x = 0
ms.mp.Unlock()
return C.int16_t(x)
}

func (ms *MouseState) PopY() C.int16_t {
var y int16
ms.mp.Lock()
y = ms.y
ms.y = 0
ms.mp.Unlock()
return C.int16_t(y)
}

// SetButtons sets the state of mouse buttons.
//
// data format
//
// 0 1 2
// L R M
//
// L is the left button, R is right, and M is the middle button.
func (ms *MouseState) SetButtons(data []byte) {
if len(data) != 3 {
return
}
ms.mb.Lock()
ms.l = data[0] == 1
ms.r = data[1] == 1
ms.m = data[2] == 1
ms.mb.Unlock()
}

func (ms *MouseState) Buttons() (l, r, m bool) {
ms.mb.Lock()
l = ms.l
r = ms.r
m = ms.m
ms.mb.Unlock()
return
}
61 changes: 50 additions & 11 deletions pkg/worker/caged/libretro/nanoarch/nanoarch.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ var (
type Nanoarch struct {
Handlers

// keyboard keeps pressed buttons for input poll
keyboard KeyboardState
mouse MouseState
retropad InputState

retropad InputState
keyboardCb *C.struct_retro_keyboard_callback
LastFrameTime int64
LibCo bool
Expand Down Expand Up @@ -169,6 +169,8 @@ func (n *Nanoarch) CoreLoad(meta Metadata) {
n.keyboardCb = nil
n.keyboard = NewKeyboardState()

n.mouse = MouseState{}

n.options = &meta.Options

filePath := meta.LibPath
Expand Down Expand Up @@ -381,12 +383,26 @@ func (n *Nanoarch) InputKeyboard(_ int, data []byte) {
return
}

press, key, mod := decodeKeyboardState(data)

n.keyboard.Set(press, key, mod)
C.bridge_retro_keyboard_callback(unsafe.Pointer(n.keyboardCb), C.bool(press),
// we should preserve the state of pressed buttons for the input poll function (each retro_run)
// and explicitly call the retro_keyboard_callback function when a keyboard event happens
pressed, key, mod := n.keyboard.SetKey(data)
C.bridge_retro_keyboard_callback(unsafe.Pointer(n.keyboardCb), C.bool(pressed),
C.unsigned(key), C.uint32_t(0), C.uint16_t(mod))
}
func (n *Nanoarch) InputMouse(_ int, data []byte) {
if len(data) == 0 {
return
}

t := data[0]
state := data[1:]
switch t {
case MouseMove:
n.mouse.ShiftPos(state)
case MouseButton:
n.mouse.SetButtons(state)
}
}

func videoSetPixelFormat(format uint32) (C.bool, error) {
switch format {
Expand Down Expand Up @@ -615,10 +631,6 @@ func coreInputPoll() {}
func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.unsigned) C.int16_t {
//Nan0.log.Debug().Msgf("%v %v %v %v", port, device, index, id)

//if uint(port) >= uint(MaxPort) {
// return KeyReleased
//}

switch device {
case C.RETRO_DEVICE_JOYPAD:
return Nan0.retropad.IsKeyPressed(uint(port), int(id))
Expand All @@ -631,6 +643,33 @@ func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.u
}
case C.RETRO_DEVICE_KEYBOARD:
return Nan0.keyboard.Pressed(uint(id))
case C.RETRO_DEVICE_MOUSE:
switch id {
case C.RETRO_DEVICE_ID_MOUSE_X:
x := Nan0.mouse.PopX()
return x
case C.RETRO_DEVICE_ID_MOUSE_Y:
y := Nan0.mouse.PopY()
return y
case C.RETRO_DEVICE_ID_MOUSE_LEFT:
if l, _, _ := Nan0.mouse.Buttons(); l {
return KeyPressed
}
case C.RETRO_DEVICE_ID_MOUSE_RIGHT:
if _, r, _ := Nan0.mouse.Buttons(); r {
return KeyPressed
}
case C.RETRO_DEVICE_ID_MOUSE_WHEELUP:
case C.RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
case C.RETRO_DEVICE_ID_MOUSE_MIDDLE:
if _, _, m := Nan0.mouse.Buttons(); m {
return KeyPressed
}
case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP:
case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN:
case C.RETRO_DEVICE_ID_MOUSE_BUTTON_4:
case C.RETRO_DEVICE_ID_MOUSE_BUTTON_5:
}
}

return KeyReleased
Expand Down Expand Up @@ -791,7 +830,7 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
}
cInfo.WriteString(fmt.Sprintf("%v: %v%s", cd[i].id, C.GoString(cd[i].desc), delim))
}
Nan0.log.Debug().Msgf("%v", cInfo.String())
//Nan0.log.Debug().Msgf("%v", cInfo.String())
}
return true
case C.RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS:
Expand Down
1 change: 1 addition & 0 deletions pkg/worker/coordinatorhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func (c *coordinator) HandleGameStart(rq api.StartGameRequest[com.Uid], w *Worke
s := room.WithWebRTC(user.Session)
s.OnMessage = func(data []byte) { r.App().InputGamepad(user.Index, data) }
s.OnKeyboard = func(data []byte) { r.App().InputKeyboard(user.Index, data) }
s.OnMouse = func(data []byte) { r.App().InputMouse(user.Index, data) }

c.RegisterRoom(r.Id())

Expand Down
13 changes: 6 additions & 7 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,24 @@ <h1>Options</h1>
<script src="js/utils.js?v1"></script>
<script src="js/gui/message.js?v=2"></script>
<script src="js/log.js?v=5"></script>
<script src="js/event/event.js?v=5"></script>
<script src="js/input/libretro.js?v=1"></script>
<script src="js/event/event.js?v=6"></script>
<script src="js/input/keys.js?v=3"></script>
<script src="js/settings/opts.js?v=2"></script>
<script src="js/settings/settings.js?v=4"></script>
<script src="js/env.js?v=5"></script>
<script src="js/input/input.js?v=3"></script>
<script src="js/gameList.js?v=3"></script>
<script src="js/stream/stream.js?v=6"></script>
<script src="js/room.js?v=3"></script>
<script src="js/network/ajax.js?v=3"></script>
<script src="js/network/socket.js?v=4"></script>
<script src="js/network/webrtc.js?v=3"></script>
<script src="js/network/webrtc.js?v=4"></script>
<script src="js/recording.js?v=1"></script>
<script src="js/api/api.js?v=3"></script>
<script src="js/api/api.js?v=4"></script>
<script src="js/workerManager.js?v=1"></script>
<script src="js/stats/stats.js?v=1"></script>
<script src="js/controller.js?v=9"></script>
<script src="js/input/keyboard.js?v=7"></script>
<script src="js/controller.js?v=10"></script>
<script src="js/stream/stream.js?v=7"></script>
<script src="js/input/keyboard.js?v=8"></script>
<script src="js/input/touch.js?v=3"></script>
<script src="js/input/joystick.js?v=3"></script>

Expand Down
Loading

0 comments on commit 0704a76

Please sign in to comment.