Initial POC
This commit is contained in:
105
src/main.rs
Normal file
105
src/main.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
mod virtual_gamepad;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use evdev::{Device, EventType, KeyCode};
|
||||
use tokio::sync::mpsc::{Receiver, Sender, channel};
|
||||
use virtual_gamepad::VirtualGamepad;
|
||||
|
||||
async fn watch_keyboard(path: PathBuf, target: KeyCode, tx: Sender<PathBuf>) {
|
||||
let device = Device::open(path.clone()).unwrap();
|
||||
let Ok(mut stream) = device.into_event_stream() else {
|
||||
return;
|
||||
};
|
||||
|
||||
while let Ok(event) = stream.next_event().await {
|
||||
if event.event_type() == EventType::KEY
|
||||
&& event.code() == target.code()
|
||||
&& event.value() == 0
|
||||
{
|
||||
let _ = tx.send(path).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_keyboards() -> Vec<PathBuf> {
|
||||
evdev::enumerate()
|
||||
.filter_map(|(path, device)| {
|
||||
if device.supported_keys()?.contains(KeyCode::KEY_SPACE) {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn find_target_keyboard(device_paths: &Vec<PathBuf>) -> Option<Device>{
|
||||
|
||||
let (tx, mut rx): (Sender<PathBuf>, Receiver<PathBuf>) = channel(1);
|
||||
|
||||
let mut handles = vec![];
|
||||
|
||||
for path in device_paths {
|
||||
handles.push(tokio::spawn(watch_keyboard(
|
||||
path.clone(),
|
||||
KeyCode::KEY_SPACE,
|
||||
tx.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let path = rx.recv().await?;
|
||||
|
||||
for handle in handles {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
Device::open(path).ok()
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "local")]
|
||||
async fn main() {
|
||||
|
||||
|
||||
let devices_paths = enumerate_keyboards();
|
||||
|
||||
println!("Found the following devices");
|
||||
for path in &devices_paths {
|
||||
if let Ok(d) = Device::open(path){
|
||||
println!("\t{} ({})", d.name().unwrap_or("null"), path.display())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let Some(kb) = find_target_keyboard(&devices_paths).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
let Ok(mut kb) = kb.into_event_stream() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut gamepad = VirtualGamepad::new("Virtual Gamepad 1");
|
||||
|
||||
if !kb.device_mut().grab().is_ok() {
|
||||
println!("Failed to grab device");
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Device is grabbed");
|
||||
|
||||
gamepad.handle_events(&mut kb).await;
|
||||
|
||||
match kb.device_mut().ungrab() {
|
||||
Err(err) => println!("Failed to release device: {err}"),
|
||||
_ => ()
|
||||
};
|
||||
|
||||
println!("Device released");
|
||||
|
||||
}
|
||||
147
src/virtual_gamepad.rs
Normal file
147
src/virtual_gamepad.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use evdev::uinput::VirtualDevice;
|
||||
use evdev::{
|
||||
AbsInfo, AbsoluteAxisCode, AttributeSet, EventStream, EventType, InputEvent, KeyCode, UinputAbsSetup
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum Direction {
|
||||
Up = 0,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtualGamepad {
|
||||
_name: String,
|
||||
ui: VirtualDevice,
|
||||
|
||||
btn_bindings: HashMap<u16, KeyCode>,
|
||||
|
||||
dpad_bindings: HashMap<u16, Direction>,
|
||||
|
||||
dpad_state: [i8; 4],
|
||||
}
|
||||
|
||||
impl VirtualGamepad {
|
||||
pub fn new(name: &str) -> VirtualGamepad {
|
||||
let mut keys = AttributeSet::<KeyCode>::new();
|
||||
keys.insert(KeyCode::BTN_SOUTH);
|
||||
keys.insert(KeyCode::BTN_EAST);
|
||||
keys.insert(KeyCode::BTN_NORTH);
|
||||
keys.insert(KeyCode::BTN_WEST);
|
||||
|
||||
keys.insert(KeyCode::BTN_TL);
|
||||
keys.insert(KeyCode::BTN_TR);
|
||||
keys.insert(KeyCode::BTN_TL2);
|
||||
keys.insert(KeyCode::BTN_TR2);
|
||||
|
||||
keys.insert(KeyCode::BTN_START);
|
||||
keys.insert(KeyCode::BTN_SELECT);
|
||||
keys.insert(KeyCode::BTN_MODE);
|
||||
|
||||
keys.insert(KeyCode::BTN_THUMBL);
|
||||
keys.insert(KeyCode::BTN_THUMBR);
|
||||
|
||||
let mut btn_bindings = HashMap::new();
|
||||
|
||||
btn_bindings.insert(KeyCode::KEY_J.code(), KeyCode::BTN_SOUTH);
|
||||
btn_bindings.insert(KeyCode::KEY_I.code(), KeyCode::BTN_EAST);
|
||||
btn_bindings.insert(KeyCode::KEY_O.code(), KeyCode::BTN_NORTH);
|
||||
btn_bindings.insert(KeyCode::KEY_L.code(), KeyCode::BTN_WEST);
|
||||
btn_bindings.insert(KeyCode::KEY_M.code(), KeyCode::BTN_TR);
|
||||
btn_bindings.insert(KeyCode::KEY_K.code(), KeyCode::BTN_THUMBL);
|
||||
btn_bindings.insert(KeyCode::KEY_SEMICOLON.code(), KeyCode::BTN_TL);
|
||||
btn_bindings.insert(KeyCode::KEY_ENTER.code(), KeyCode::BTN_START);
|
||||
btn_bindings.insert(KeyCode::KEY_ESC.code(), KeyCode::BTN_SELECT);
|
||||
|
||||
let mut dpad_bindings = HashMap::new();
|
||||
|
||||
dpad_bindings.insert(KeyCode::KEY_SPACE.code(), Direction::Up);
|
||||
dpad_bindings.insert(KeyCode::KEY_E.code(), Direction::Down);
|
||||
dpad_bindings.insert(KeyCode::KEY_F.code(), Direction::Right);
|
||||
dpad_bindings.insert(KeyCode::KEY_W.code(), Direction::Left);
|
||||
|
||||
|
||||
|
||||
let ui = VirtualDevice::builder()
|
||||
.unwrap()
|
||||
.name(&name)
|
||||
.with_keys(&keys)
|
||||
.unwrap()
|
||||
.with_absolute_axis(&UinputAbsSetup::new(
|
||||
AbsoluteAxisCode::ABS_HAT0X,
|
||||
AbsInfo::new(0, -1, 1, 0, 0, 0),
|
||||
))
|
||||
.unwrap()
|
||||
.with_absolute_axis(&UinputAbsSetup::new(
|
||||
AbsoluteAxisCode::ABS_HAT0Y,
|
||||
AbsInfo::new(0, -1, 1, 0, 0, 0),
|
||||
))
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
VirtualGamepad {
|
||||
ui,
|
||||
btn_bindings,
|
||||
dpad_bindings,
|
||||
_name: name.to_owned(),
|
||||
dpad_state: [0, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn press_btn(&mut self, btn: KeyCode, press: bool) {
|
||||
let _ = self
|
||||
.ui
|
||||
.emit(&[InputEvent::new(EventType::KEY.0, btn.code(), press as i32)]);
|
||||
}
|
||||
|
||||
pub fn press_dpad(&mut self, btn: Direction, press: bool) {
|
||||
match btn {
|
||||
Direction::Up => self.dpad_state[0] = press as i8,
|
||||
Direction::Down => self.dpad_state[1] = press as i8,
|
||||
Direction::Left => self.dpad_state[2] = press as i8,
|
||||
Direction::Right => self.dpad_state[3] = press as i8,
|
||||
};
|
||||
|
||||
if [Direction::Up, Direction::Down].contains(&btn) {
|
||||
let _ = self.ui.emit(&[InputEvent::new(
|
||||
EventType::ABSOLUTE.0,
|
||||
AbsoluteAxisCode::ABS_HAT0Y.0,
|
||||
(-self.dpad_state[0] + self.dpad_state[1]) as i32,
|
||||
)]);
|
||||
} else {
|
||||
let _ = self.ui.emit(&[InputEvent::new(
|
||||
EventType::ABSOLUTE.0,
|
||||
AbsoluteAxisCode::ABS_HAT0X.0,
|
||||
(-self.dpad_state[2] + self.dpad_state[3]) as i32,
|
||||
)]);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_events(&mut self, es: &mut EventStream) {
|
||||
while let Ok(event) = es.next_event().await {
|
||||
if event.event_type() != EventType::KEY {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(btn) = self.btn_bindings.get(&event.code()) {
|
||||
self.press_btn(btn.clone(), event.value() > 0);
|
||||
}
|
||||
|
||||
if let Some(dir) = self.dpad_bindings.get(&event.code()) {
|
||||
self.press_dpad(dir.clone(), event.value() > 0);
|
||||
}
|
||||
|
||||
if event.code() == KeyCode::KEY_END.code() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user