mirror of https://github.com/wayvr-org/wayvr.git
274 lines
9.1 KiB
Rust
274 lines
9.1 KiB
Rust
use crate::overlays::toast::Toast;
|
|
use crate::subsystem::hid::provider::HidProvider;
|
|
use crate::subsystem::hid::{
|
|
EV_ABS, EV_KEY, EV_REL, EV_SYN, MODS_TO_KEYS, MOUSE_EXTENT, MOUSE_LEFT, MOUSE_MIDDLE,
|
|
MouseAction, MouseButtonAction, VirtualKey, WheelDelta, XkbKeymap, get_time, new_event,
|
|
};
|
|
use glam::Vec2;
|
|
use input_linux::{
|
|
AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, InputId, Key, RelativeAxis,
|
|
UInputHandle,
|
|
};
|
|
use std::fs::File;
|
|
use std::mem::transmute;
|
|
use std::sync::atomic::AtomicBool;
|
|
use strum::IntoEnumIterator;
|
|
use wlx_common::overlays::ToastTopic;
|
|
|
|
pub struct UInputProvider {
|
|
keyboard_handle: UInputHandle<File>,
|
|
mouse_handle: UInputHandle<File>,
|
|
desktop_extent: Vec2,
|
|
desktop_origin: Vec2,
|
|
cur_modifiers: u8,
|
|
current_action: MouseAction,
|
|
}
|
|
|
|
impl UInputProvider {
|
|
pub fn try_new() -> Option<Self> {
|
|
let keyboard_file = File::create("/dev/uinput").ok()?;
|
|
let keyboard_handle = UInputHandle::new(keyboard_file);
|
|
|
|
let mouse_file = File::create("/dev/uinput").ok()?;
|
|
let mouse_handle = UInputHandle::new(mouse_file);
|
|
|
|
let kbd_id = InputId {
|
|
bustype: 0x03,
|
|
vendor: 0x4711,
|
|
product: 0x0829,
|
|
version: 5,
|
|
};
|
|
let mouse_id = InputId {
|
|
bustype: 0x03,
|
|
vendor: 0x4711,
|
|
product: 0x0830,
|
|
version: 5,
|
|
};
|
|
let kbd_name = b"WayVR Keyboard\0";
|
|
let mouse_name = b"WayVR Mouse\0";
|
|
|
|
let abs_info = vec![
|
|
AbsoluteInfoSetup {
|
|
axis: input_linux::AbsoluteAxis::X,
|
|
info: AbsoluteInfo {
|
|
value: 0,
|
|
minimum: 0,
|
|
maximum: MOUSE_EXTENT as _,
|
|
fuzz: 0,
|
|
flat: 0,
|
|
resolution: 10,
|
|
},
|
|
},
|
|
AbsoluteInfoSetup {
|
|
axis: input_linux::AbsoluteAxis::Y,
|
|
info: AbsoluteInfo {
|
|
value: 0,
|
|
minimum: 0,
|
|
maximum: MOUSE_EXTENT as _,
|
|
fuzz: 0,
|
|
flat: 0,
|
|
resolution: 10,
|
|
},
|
|
},
|
|
];
|
|
|
|
keyboard_handle.set_evbit(EventKind::Key).ok()?;
|
|
for key in VirtualKey::iter() {
|
|
let mapped_key: Key = unsafe { std::mem::transmute((key as u16) - 8) };
|
|
keyboard_handle.set_keybit(mapped_key).ok()?;
|
|
}
|
|
|
|
keyboard_handle.create(&kbd_id, kbd_name, 0, &[]).ok()?;
|
|
|
|
mouse_handle.set_evbit(EventKind::Absolute).ok()?;
|
|
mouse_handle.set_evbit(EventKind::Relative).ok()?;
|
|
mouse_handle.set_absbit(AbsoluteAxis::X).ok()?;
|
|
mouse_handle.set_absbit(AbsoluteAxis::Y).ok()?;
|
|
mouse_handle.set_relbit(RelativeAxis::WheelHiRes).ok()?;
|
|
mouse_handle
|
|
.set_relbit(RelativeAxis::HorizontalWheelHiRes)
|
|
.ok()?;
|
|
mouse_handle.set_evbit(EventKind::Key).ok()?;
|
|
|
|
for btn in MOUSE_LEFT..=MOUSE_MIDDLE {
|
|
let mouse_btn: Key = unsafe { transmute(btn) };
|
|
mouse_handle.set_keybit(mouse_btn).ok()?;
|
|
}
|
|
mouse_handle
|
|
.create(&mouse_id, mouse_name, 0, &abs_info)
|
|
.ok()?;
|
|
|
|
Some(Self {
|
|
keyboard_handle,
|
|
mouse_handle,
|
|
desktop_extent: Vec2::ZERO,
|
|
desktop_origin: Vec2::ZERO,
|
|
current_action: MouseAction::default(),
|
|
cur_modifiers: 0,
|
|
})
|
|
}
|
|
fn send_button_internal(&self, button: u16, down: bool) {
|
|
let time = get_time();
|
|
let events = [
|
|
new_event(time, EV_KEY, button, down.into()),
|
|
new_event(time, EV_SYN, 0, 0),
|
|
];
|
|
if let Err(res) = self.mouse_handle.write(&events) {
|
|
log::error!("send_button: {res}");
|
|
}
|
|
}
|
|
fn mouse_move_internal(&mut self, pos: Vec2) {
|
|
#[cfg(debug_assertions)]
|
|
log::trace!("Mouse move: {pos:?}");
|
|
|
|
let pos = (pos - self.desktop_origin) * (MOUSE_EXTENT / self.desktop_extent);
|
|
|
|
let time = get_time();
|
|
let events = [
|
|
new_event(time, EV_ABS, AbsoluteAxis::X as _, pos.x as i32),
|
|
new_event(time, EV_ABS, AbsoluteAxis::Y as _, pos.y as i32),
|
|
new_event(time, EV_SYN, 0, 0),
|
|
];
|
|
if let Err(res) = self.mouse_handle.write(&events) {
|
|
log::error!("{res}");
|
|
}
|
|
}
|
|
|
|
fn wheel_internal(&self, delta: WheelDelta) {
|
|
let multiplier = 64.0; /* cherry-picked value, overall scrolling speed can be altered via `scroll_speed` in the config */
|
|
let delta_x = (delta.x * multiplier) as i32;
|
|
let delta_y = (delta.y * multiplier) as i32;
|
|
|
|
let time = get_time();
|
|
let events = [
|
|
new_event(time, EV_REL, RelativeAxis::WheelHiRes as _, delta_y),
|
|
new_event(
|
|
time,
|
|
EV_REL,
|
|
RelativeAxis::HorizontalWheelHiRes as _,
|
|
delta_x,
|
|
),
|
|
new_event(time, EV_SYN, 0, 0),
|
|
];
|
|
if let Err(res) = self.mouse_handle.write(&events) {
|
|
log::error!("wheel: {res}");
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HidProvider for UInputProvider {
|
|
fn mouse_move(&mut self, pos: Vec2) {
|
|
if self.current_action.pos.is_none() && self.current_action.scroll.is_none() {
|
|
self.current_action.pos = Some(pos);
|
|
}
|
|
self.current_action.last_requested_pos = Some(pos);
|
|
}
|
|
fn send_button(&mut self, button: u16, down: bool) {
|
|
if self.current_action.button.is_none() {
|
|
self.current_action.button = Some(MouseButtonAction { button, down });
|
|
self.current_action.pos = self.current_action.last_requested_pos;
|
|
}
|
|
}
|
|
fn wheel(&mut self, delta: WheelDelta) {
|
|
if self.current_action.scroll.is_none() {
|
|
self.current_action.scroll = Some(delta);
|
|
// Pass mouse motion events only if not scrolling
|
|
// (allows scrolling on all Chromium-based applications)
|
|
self.current_action.pos = None;
|
|
}
|
|
}
|
|
fn set_desktop_extent(&mut self, extent: Vec2) {
|
|
self.desktop_extent = extent;
|
|
}
|
|
fn set_desktop_origin(&mut self, origin: Vec2) {
|
|
self.desktop_origin = origin;
|
|
}
|
|
fn set_modifiers(&mut self, modifiers: u8) {
|
|
let changed = self.cur_modifiers ^ modifiers;
|
|
for i in 0..8 {
|
|
let m = 1 << i;
|
|
if changed & m != 0
|
|
&& let Some(vk) = MODS_TO_KEYS.get(m).into_iter().flatten().next()
|
|
{
|
|
self.send_key(*vk, modifiers & m != 0);
|
|
}
|
|
}
|
|
self.cur_modifiers = modifiers;
|
|
}
|
|
|
|
fn send_key(&self, key: VirtualKey, down: bool) {
|
|
#[cfg(debug_assertions)]
|
|
log::trace!("send_key: {key:?} {down}");
|
|
|
|
let time = get_time();
|
|
let events = [
|
|
new_event(time, EV_KEY, (key as u16) - 8, down.into()),
|
|
new_event(time, EV_SYN, 0, 0),
|
|
];
|
|
if let Err(res) = self.keyboard_handle.write(&events) {
|
|
log::error!("send_key: {res}");
|
|
}
|
|
}
|
|
|
|
fn set_keymap(&mut self, _keymap: &XkbKeymap) {}
|
|
|
|
fn commit(&mut self) {
|
|
if let Some(pos) = self.current_action.pos.take() {
|
|
self.mouse_move_internal(pos);
|
|
}
|
|
if let Some(button) = self.current_action.button.take() {
|
|
self.send_button_internal(button.button, button.down);
|
|
}
|
|
if let Some(scroll) = self.current_action.scroll.take() {
|
|
self.wheel_internal(scroll);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub static USE_UINPUT: AtomicBool = AtomicBool::new(true);
|
|
|
|
pub fn initialize_uinput() -> Result<Box<dyn HidProvider>, Toast> {
|
|
const CHECK_UINPUT_MESSAGE: &str =
|
|
"Could not create uinput provider. Keyboard/Mouse input will not work!
|
|
|
|
Check if the uinput kernel module is loaded: lsmod | grep uinput
|
|
- If not loaded, follow your distro's instructions to load the uinput kernel module.
|
|
|
|
Check if you're in input group, run: id -nG";
|
|
|
|
if !USE_UINPUT.load(std::sync::atomic::Ordering::Relaxed) {
|
|
const UINPUT_DISABLED: &str = "Uinput disabled by user.";
|
|
log::info!("{UINPUT_DISABLED}");
|
|
return Err(Toast::new(
|
|
ToastTopic::System,
|
|
String::with_capacity(0),
|
|
String::from(UINPUT_DISABLED),
|
|
)
|
|
.with_timeout(5.0));
|
|
}
|
|
|
|
if let Some(uinput) = UInputProvider::try_new() {
|
|
log::info!("Initialized uinput.");
|
|
return Ok(Box::new(uinput));
|
|
}
|
|
let mut full_uinput_error = String::from(CHECK_UINPUT_MESSAGE);
|
|
if let Ok(user) = std::env::var("USER") {
|
|
let check_group_message = format!(
|
|
" - To add yourself to the input group, run: sudo usermod -aG input {user}
|
|
- After adding yourself to the input group, you will need to reboot."
|
|
);
|
|
full_uinput_error.push_str(&check_group_message);
|
|
}
|
|
for error_line in full_uinput_error.lines() {
|
|
if !error_line.is_empty() {
|
|
log::error!("{error_line}");
|
|
}
|
|
}
|
|
Err(Toast::new(
|
|
ToastTopic::Error,
|
|
String::with_capacity(0),
|
|
full_uinput_error,
|
|
)
|
|
.with_timeout(30.0))
|
|
}
|