Skip to content

Commit

Permalink
feat(server): basic gamepad support
Browse files Browse the repository at this point in the history
This uses a motley collection of hacks, mostly oriented around FUSE, to
simulate working gamepads inside our mini container runtime.

This is basic because:
 - Only ps5 controllers are emulated presently
 - Hotplug is unsupported.
 - Force feedback is unsupported.
  • Loading branch information
colinmarc committed Oct 14, 2024
1 parent a60eb39 commit f0eceab
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 36 deletions.
5 changes: 3 additions & 2 deletions mm-server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions mm-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ boring = "3"
rand = "0.8.5"
ring = "0.16.20"
rustix = { version = "0.38", default-features = false, features = ["mm", "mount", "pipe", "time", "thread", "stdio"] }
southpaw = { path = "../../southpaw" }
southpaw = { git = "https://github.com/colinmarc/southpaw", rev = "ff78fa6ce7e82c8afa5f7e365194ac083ce743b3" }
thiserror = "1.0.48"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
Expand Down Expand Up @@ -84,7 +84,7 @@ rev = "92084df65f52aa15b704279fb6d8d26a3ee71809"

[dependencies.fuser]
git = "https://github.com/colinmarc/fuser"
rev = "1e22afdb868a86afd20ce598aec6e634a9fdc603"
rev = "643facdc1bcc9a3b11d7a88ebfaaaa045c3596c1"
default-features = false

[dependencies.pulseaudio]
Expand Down
56 changes: 27 additions & 29 deletions mm-server/src/compositor/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use fuser as fuse;
use murmur3::murmur3_32 as murmur3;
use southpaw::{
sys::{EV_ABS, EV_KEY},
AbsAxis, AbsInfo, InputEvent, KeyCode, Scancode,
AbsAxis, AbsInfo, InputEvent, KeyCode,
};
use tracing::{debug, error};

Expand All @@ -36,7 +36,6 @@ pub struct InputDeviceManager {
}

struct DeviceState {
id: u64,
short_id: u32, // Used by udevfs.
counter: u16,
plugged: time::SystemTime,
Expand Down Expand Up @@ -72,17 +71,35 @@ impl GamepadHandle {
}

pub(crate) fn trigger(&mut self, trigger_code: u32, value: f64) {
// todo!()
let value = value.clamp(0.0, 1.0) * 256.0;
self.ev_buffer.push(InputEvent::new(
EV_ABS,
trigger_code as u16,
value.floor() as i32,
))
}

pub(crate) fn input(&mut self, button_code: u32, state: super::ButtonState) {
// TODO: the DualSense sends D-pad buttons as ABS_HAT0{X,Y}.

let value = match state {
super::ButtonState::Pressed => 1,
super::ButtonState::Released => 0,
};

// The DualSense sends D-pad buttons as ABS_HAT0{X,Y}.
let key_code = southpaw::KeyCode::try_from(button_code as u16);
if let Some((axis, direction)) = match key_code {
Ok(KeyCode::BtnDpadUp) => Some((AbsAxis::HAT0Y, -1)),
Ok(KeyCode::BtnDpadDown) => Some((AbsAxis::HAT0Y, 1)),
Ok(KeyCode::BtnDpadLeft) => Some((AbsAxis::HAT0X, -1)),
Ok(KeyCode::BtnDpadRight) => Some((AbsAxis::HAT0X, 1)),
_ => None,
} {
// Simulate a press and release, each in a frame.
self.ev_buffer
.push(InputEvent::new(EV_ABS, axis, value * direction));
return;
}

self.ev_buffer
.push(InputEvent::new(EV_KEY, button_code as u16, value));
}
Expand Down Expand Up @@ -114,7 +131,7 @@ impl InputDeviceManager {
let mode = 0o755 | rustix::fs::FileType::Directory.as_raw_mode();

let device_fd = c.fuse_mount(udevfs_path_clone, "udevfs", mode)?;
let mut session = fuse::Session::from_fd(device_fd, udevfs, fuse::SessionACL::Owner);
let mut session = fuse::Session::from_fd(udevfs, device_fd, fuse::SessionACL::Owner);
std::thread::spawn(move || session.run());

let device_fd = c.fuse_mount(southpaw_path_clone, "southpaw", mode)?;
Expand Down Expand Up @@ -189,24 +206,19 @@ impl InputDeviceManager {
KeyCode::BtnMode,
KeyCode::BtnThumbl,
KeyCode::BtnThumbr,
// Scancode::AbsoluteAxis(AbsAxis::X),
// Scancode::AbsoluteAxis(AbsAxis::Y),
// Scancode::AbsoluteAxis(AbsAxis::RX),
// Scancode::AbsoluteAxis(AbsAxis::RY),
// Scancode::AbsoluteAxis(AbsAxis::Z),
// Scancode::AbsoluteAxis(AbsAxis::RZ),
// Scancode::AbsoluteAxis(AbsAxis::HAT0X),
// Scancode::AbsoluteAxis(AbsAxis::HAT0Y),
])
.supported_absolute_axis(AbsAxis::X, xy_absinfo)
.supported_absolute_axis(AbsAxis::Y, xy_absinfo)
.supported_absolute_axis(AbsAxis::RX, xy_absinfo)
.supported_absolute_axis(AbsAxis::RY, xy_absinfo)
.supported_absolute_axis(AbsAxis::Z, trigger_absinfo)
.supported_absolute_axis(AbsAxis::RZ, trigger_absinfo)
.supported_absolute_axis(AbsAxis::HAT0X, dpad_absinfo)
.supported_absolute_axis(AbsAxis::HAT0Y, dpad_absinfo)
.add_to_tree(&mut self.southpaw, &devname)?;

let short_id = murmur3(&mut Cursor::new(id.to_ne_bytes()), 0).unwrap();
guard.devices.push(DeviceState {
id,
short_id,
counter,
devname,
Expand Down Expand Up @@ -290,18 +302,4 @@ mod test {
);
Ok(())
}

#[test_log::test]
fn gilrs_gamepad_info() -> anyhow::Result<()> {
let output = run_in_container_with_gamepads([
"/home/colinmarc/src/gilrs/target/debug/examples/gamepad_info",
])?;

pretty_assertions::assert_eq!(
output,
"/sys/devices/virtual/input/event1\n/sys/devices/virtual/input/event2\n"
);

Ok(())
}
}
3 changes: 0 additions & 3 deletions mm-server/src/server/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -909,9 +909,6 @@ fn axis_to_evdev(axis: protocol::gamepad_motion::GamepadAxis) -> Option<(u32, bo
fn gamepad_button_to_evdev(button: protocol::gamepad_input::GamepadButton) -> Option<u32> {
use protocol::gamepad_input::GamepadButton;

// TODO: My Dualsense actually reports Dpad events as an axis (ABS_HAT0X).
// Otherwise, this simulates a Sony controller.

match button {
GamepadButton::DpadLeft => Some(0x222), // BTN_DPAD_LEFT
GamepadButton::DpadRight => Some(0x223), // BTN_DPAD_RIGHT
Expand Down

0 comments on commit f0eceab

Please sign in to comment.