mirror of https://github.com/wayvr-org/wayvr.git
Implement zwlr_virtual_pointer_v1 and zwp_virtual_keyboard_v1 for better pointer and keyboard support on monitor overlays (#539)
* Refactor HID subsystem to support zwlr_virtual_pointer_v1/zwp_virtual_keyboard_v1, and only use uinput as a fallback * Add keymap support to HID providers and refactor related functionality * Replace `expect` with `context` for improved error handling in wl_virtual.rs * Run `cargo fmt` * Replace hard panic with Err on failed seat binding
This commit is contained in:
parent
da394ec2c7
commit
28da0347ae
|
|
@ -6569,6 +6569,7 @@ dependencies = [
|
|||
"vulkano",
|
||||
"vulkano-shaders",
|
||||
"wayland-client",
|
||||
"wayland-protocols-misc",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"winit",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ vulkano = { version = "0.35.2", default-features = false, features = [
|
|||
] }
|
||||
vulkano-shaders = "0.35.0"
|
||||
wayland-client = { version = "0.31.11" }
|
||||
wayland-protocols-misc = { version = "0.3.12" }
|
||||
xdg = "3.0.0"
|
||||
|
||||
[patch.crates-io]
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ tracing = "0.1.43"
|
|||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
uuid = { workspace = true }
|
||||
wayland-client = { workspace = true }
|
||||
wayland-protocols-misc = { workspace = true }
|
||||
winit = { version = "0.30.12", optional = true }
|
||||
xcb = { version = "1.6.0", features = [
|
||||
"as-raw-xcb-connection",
|
||||
|
|
|
|||
|
|
@ -1,90 +1,25 @@
|
|||
use glam::Vec2;
|
||||
use idmap::{IdMap, idmap};
|
||||
use idmap_derive::IntegerId;
|
||||
use input_linux::{
|
||||
AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, InputId, Key, RelativeAxis,
|
||||
UInputHandle,
|
||||
};
|
||||
use libc::{input_event, timeval};
|
||||
use serde::Deserialize;
|
||||
use std::mem::transmute;
|
||||
use std::sync::LazyLock;
|
||||
use std::{fs::File, sync::atomic::AtomicBool};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
||||
use wlx_common::overlays::ToastTopic;
|
||||
use strum::{EnumIter, EnumString};
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use crate::overlays::toast::Toast;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod wayland;
|
||||
|
||||
pub mod provider;
|
||||
#[cfg(feature = "x11")]
|
||||
mod x11;
|
||||
|
||||
pub static USE_UINPUT: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub(super) fn initialize() -> Result<UInputProvider, 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(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))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WheelDelta {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
pub trait HidProvider: Sync + Send {
|
||||
fn mouse_move(&mut self, pos: Vec2);
|
||||
fn send_button(&mut self, button: u16, down: bool);
|
||||
fn wheel(&mut self, delta: WheelDelta);
|
||||
fn set_modifiers(&mut self, mods: u8);
|
||||
fn send_key(&self, key: VirtualKey, down: bool);
|
||||
fn set_desktop_extent(&mut self, extent: Vec2);
|
||||
fn set_desktop_origin(&mut self, origin: Vec2);
|
||||
fn commit(&mut self);
|
||||
}
|
||||
|
||||
struct MouseButtonAction {
|
||||
button: u16,
|
||||
down: bool,
|
||||
|
|
@ -98,17 +33,6 @@ struct MouseAction {
|
|||
scroll: Option<WheelDelta>,
|
||||
}
|
||||
|
||||
pub struct UInputProvider {
|
||||
keyboard_handle: UInputHandle<File>,
|
||||
mouse_handle: UInputHandle<File>,
|
||||
desktop_extent: Vec2,
|
||||
desktop_origin: Vec2,
|
||||
cur_modifiers: u8,
|
||||
current_action: MouseAction,
|
||||
}
|
||||
|
||||
pub struct DummyProvider;
|
||||
|
||||
pub const MOUSE_LEFT: u16 = 0x110;
|
||||
pub const MOUSE_RIGHT: u16 = 0x111;
|
||||
pub const MOUSE_MIDDLE: u16 = 0x112;
|
||||
|
|
@ -120,216 +44,6 @@ const EV_KEY: u16 = 0x1;
|
|||
const EV_REL: u16 = 0x2;
|
||||
const EV_ABS: u16 = 0x3;
|
||||
|
||||
impl UInputProvider {
|
||||
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 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_desktop_extent(&mut self, extent: Vec2) {
|
||||
self.desktop_extent = extent;
|
||||
}
|
||||
fn set_desktop_origin(&mut self, origin: Vec2) {
|
||||
self.desktop_origin = origin;
|
||||
}
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HidProvider for DummyProvider {
|
||||
fn mouse_move(&mut self, _pos: Vec2) {}
|
||||
fn send_button(&mut self, _button: u16, _down: bool) {}
|
||||
fn wheel(&mut self, _delta: WheelDelta) {}
|
||||
fn set_modifiers(&mut self, _modifiers: u8) {}
|
||||
fn send_key(&self, _key: VirtualKey, _down: bool) {}
|
||||
fn set_desktop_extent(&mut self, _extent: Vec2) {}
|
||||
fn set_desktop_origin(&mut self, _origin: Vec2) {}
|
||||
fn commit(&mut self) {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_time() -> timeval {
|
||||
let mut time = timeval {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
use crate::subsystem::hid::provider::HidProvider;
|
||||
use crate::subsystem::hid::{VirtualKey, WheelDelta, XkbKeymap};
|
||||
use glam::Vec2;
|
||||
|
||||
pub struct DummyProvider;
|
||||
|
||||
impl HidProvider for DummyProvider {
|
||||
fn mouse_move(&mut self, _pos: Vec2) {}
|
||||
fn send_button(&mut self, _button: u16, _down: bool) {}
|
||||
fn wheel(&mut self, _delta: WheelDelta) {}
|
||||
fn set_desktop_extent(&mut self, _extent: Vec2) {}
|
||||
fn set_desktop_origin(&mut self, _origin: Vec2) {}
|
||||
fn set_modifiers(&mut self, _modifiers: u8) {}
|
||||
fn send_key(&self, _key: VirtualKey, _down: bool) {}
|
||||
|
||||
fn set_keymap(&mut self, _keymap: &XkbKeymap) {}
|
||||
|
||||
fn commit(&mut self) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
use crate::subsystem::hid::{VirtualKey, WheelDelta, XkbKeymap};
|
||||
use glam::Vec2;
|
||||
|
||||
pub mod dummy;
|
||||
pub mod uinput;
|
||||
pub mod wl_virtual;
|
||||
|
||||
pub trait HidProvider: Sync + Send {
|
||||
// Pointer Functions
|
||||
fn mouse_move(&mut self, pos: Vec2);
|
||||
fn send_button(&mut self, button: u16, down: bool);
|
||||
fn wheel(&mut self, delta: WheelDelta);
|
||||
fn set_desktop_extent(&mut self, extent: Vec2);
|
||||
fn set_desktop_origin(&mut self, origin: Vec2);
|
||||
|
||||
// Keyboard Functions
|
||||
fn set_modifiers(&mut self, mods: u8);
|
||||
fn send_key(&self, key: VirtualKey, down: bool);
|
||||
fn set_keymap(&mut self, keymap: &XkbKeymap);
|
||||
|
||||
// Common Functions
|
||||
fn commit(&mut self);
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
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::intrinsics::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))
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
use std::io::Write;
|
||||
use std::os::fd::AsFd;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use anyhow::Context as _;
|
||||
use glam::Vec2;
|
||||
use input_linux::sys::{*};
|
||||
use smithay::reexports::rustix::fs::{memfd_create, MemfdFlags};
|
||||
use smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::client::zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1;
|
||||
use smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::client::zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1;
|
||||
use smithay::reexports::wayland_server::protocol::wl_keyboard::KeymapFormat;
|
||||
use wayland_client::{delegate_noop, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_client::globals::{registry_queue_init, GlobalListContents};
|
||||
use wayland_client::protocol::wl_pointer::{Axis, AxisSource, ButtonState};
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::protocol::wl_seat::{WlSeat};
|
||||
use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1;
|
||||
use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1;
|
||||
use xkbcommon::xkb::{Context, Keymap, CONTEXT_NO_FLAGS, KEYMAP_COMPILE_NO_FLAGS, KEYMAP_FORMAT_TEXT_V1};
|
||||
use wlx_common::overlays::ToastTopic;
|
||||
use crate::overlays::toast::Toast;
|
||||
use crate::subsystem::hid::provider::HidProvider;
|
||||
use crate::subsystem::hid::{VirtualKey, WheelDelta, *};
|
||||
|
||||
pub struct WlVirtualProvider {
|
||||
_connection: wayland_client::Connection,
|
||||
queue: wayland_client::EventQueue<KbState>,
|
||||
state: KbState,
|
||||
|
||||
virtual_pointer: ZwlrVirtualPointerV1,
|
||||
desktop_extent: Vec2,
|
||||
desktop_origin: Vec2,
|
||||
keymap_file: Option<std::fs::File>,
|
||||
|
||||
virtual_keyboard: ZwpVirtualKeyboardV1,
|
||||
keyboard_mods_state: u8,
|
||||
}
|
||||
|
||||
struct KbState;
|
||||
|
||||
impl HidProvider for WlVirtualProvider {
|
||||
fn mouse_move(&mut self, pos: Vec2) {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Pointer move: {pos:?}");
|
||||
|
||||
self.virtual_pointer.motion_absolute(
|
||||
Self::now_ms(),
|
||||
(pos.x - self.desktop_origin.x) as u32,
|
||||
(pos.y - self.desktop_origin.y) as u32,
|
||||
self.desktop_extent.x as u32,
|
||||
self.desktop_extent.y as u32,
|
||||
);
|
||||
self.virtual_pointer.motion(Self::now_ms(), 0.0, 0.0);
|
||||
self.virtual_pointer.frame();
|
||||
}
|
||||
|
||||
fn send_button(&mut self, button: u16, down: bool) {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Pointer button: {button}, down: {down}");
|
||||
|
||||
self.virtual_pointer.button(
|
||||
Self::now_ms(),
|
||||
match button {
|
||||
i if i == MOUSE_LEFT => BTN_LEFT as u32,
|
||||
i if i == MOUSE_RIGHT => BTN_RIGHT as u32,
|
||||
i if i == MOUSE_MIDDLE => BTN_MIDDLE as u32,
|
||||
_ => panic!("Invalid mouse button: {button}"),
|
||||
},
|
||||
match down {
|
||||
true => ButtonState::Pressed,
|
||||
false => ButtonState::Released,
|
||||
},
|
||||
);
|
||||
self.virtual_pointer.frame();
|
||||
}
|
||||
|
||||
fn wheel(&mut self, delta: WheelDelta) {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Scroll Axis: {delta:?}");
|
||||
|
||||
self.virtual_pointer.axis_source(AxisSource::Wheel);
|
||||
|
||||
if delta.y != 0.0 {
|
||||
let steps = -delta.y.round() as i32;
|
||||
self.virtual_pointer.axis_discrete(
|
||||
Self::now_ms(),
|
||||
Axis::VerticalScroll,
|
||||
steps as f64 * 15.0,
|
||||
steps,
|
||||
);
|
||||
}
|
||||
if delta.x != 0.0 {
|
||||
let steps = delta.x.round() as i32;
|
||||
self.virtual_pointer.axis_discrete(
|
||||
Self::now_ms(),
|
||||
Axis::HorizontalScroll,
|
||||
steps as f64 * 15.0,
|
||||
steps,
|
||||
);
|
||||
}
|
||||
|
||||
self.virtual_pointer.motion(Self::now_ms(), 0.0, 0.0);
|
||||
self.virtual_pointer.frame();
|
||||
}
|
||||
|
||||
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, mods: u8) {
|
||||
const LOCKED: u8 = CAPS_LOCK | NUM_LOCK;
|
||||
|
||||
let changed = (self.keyboard_mods_state ^ mods) & !LOCKED;
|
||||
for bit in [SHIFT, CTRL, ALT, SUPER] {
|
||||
if changed & bit != 0 {
|
||||
let down = mods & bit != 0;
|
||||
if let Some(kc) = Self::modifier_keycode(bit) {
|
||||
self.virtual_keyboard
|
||||
.key(Self::now_ms(), kc - 8, down as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let depressed = (mods & !LOCKED) as u32;
|
||||
let locked = (mods & LOCKED) as u32;
|
||||
self.virtual_keyboard.modifiers(depressed, 0, locked, 0);
|
||||
|
||||
self.keyboard_mods_state = mods;
|
||||
self._connection.flush().unwrap();
|
||||
}
|
||||
|
||||
fn send_key(&self, key: VirtualKey, down: bool) {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Keyboard key: {key:?} ({}), down: {down}", key as u16);
|
||||
|
||||
self.virtual_keyboard.key(
|
||||
Self::now_ms(),
|
||||
key as u32 - 8,
|
||||
match down {
|
||||
true => 1,
|
||||
false => 0,
|
||||
},
|
||||
);
|
||||
|
||||
self._connection.flush().unwrap();
|
||||
}
|
||||
|
||||
fn set_keymap(&mut self, keymap: &XkbKeymap) {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!(
|
||||
"Keyboard keymap: {:?}",
|
||||
keymap.inner.layouts().next().unwrap_or("Unknown")
|
||||
);
|
||||
|
||||
let mut bytes = keymap
|
||||
.inner
|
||||
.get_as_string(KEYMAP_FORMAT_TEXT_V1)
|
||||
.into_bytes();
|
||||
bytes.push(0);
|
||||
let fd = memfd_create("virtual-keyboard-keymap", MemfdFlags::CLOEXEC)
|
||||
.expect("Failed to create memfd");
|
||||
|
||||
let mut file = std::fs::File::from(fd);
|
||||
file.write_all(&bytes).expect("failed to write the keymap");
|
||||
|
||||
self.virtual_keyboard
|
||||
.keymap(KeymapFormat::XkbV1 as u32, file.as_fd(), bytes.len() as u32);
|
||||
self.queue.roundtrip(&mut self.state).unwrap();
|
||||
self.keymap_file.replace(file);
|
||||
}
|
||||
|
||||
fn commit(&mut self) {
|
||||
self.queue.roundtrip(&mut self.state).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl WlVirtualProvider {
|
||||
pub fn try_new() -> anyhow::Result<Self> {
|
||||
let state = KbState;
|
||||
|
||||
let connection = wayland_client::Connection::connect_to_env()?;
|
||||
let (globals, queue) = registry_queue_init::<KbState>(&connection)?;
|
||||
let qh = queue.handle();
|
||||
let seat: WlSeat = globals
|
||||
.bind(&qh, 4..=9, ())
|
||||
.context("compositor doesn't expose a compatible wl_seat (version 4..=9)")?;
|
||||
|
||||
let pointer_manager: ZwlrVirtualPointerManagerV1 = globals
|
||||
.bind(&qh, 1..=1, ())
|
||||
.context("compositor doesn't support zwlr_virtual_pointer_v1")?;
|
||||
|
||||
let virtual_pointer = pointer_manager.create_virtual_pointer(Some(&seat), &qh, ());
|
||||
|
||||
let keyboard_manager: ZwpVirtualKeyboardManagerV1 = globals
|
||||
.bind(&qh, 1..=1, ())
|
||||
.context("compositor doesn't support zwp_virtual_keyboard_v1")?;
|
||||
|
||||
let virtual_keyboard = keyboard_manager.create_virtual_keyboard(&seat, &qh, ());
|
||||
|
||||
let mut result = Self {
|
||||
keymap_file: None,
|
||||
_connection: connection,
|
||||
queue,
|
||||
state,
|
||||
virtual_pointer,
|
||||
virtual_keyboard,
|
||||
desktop_extent: Vec2::ZERO,
|
||||
desktop_origin: Vec2::ZERO,
|
||||
keyboard_mods_state: 0,
|
||||
};
|
||||
|
||||
result.set_keymap(&XkbKeymap {
|
||||
inner: Self::default_keymap(),
|
||||
});
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn default_keymap() -> Keymap {
|
||||
let xkb_context = Context::new(CONTEXT_NO_FLAGS);
|
||||
Keymap::new_from_names(
|
||||
&xkb_context,
|
||||
"",
|
||||
"",
|
||||
"us",
|
||||
"",
|
||||
None,
|
||||
KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
.expect("Failed to compile XKB keymap")
|
||||
}
|
||||
|
||||
fn modifier_keycode(bit: u8) -> Option<u32> {
|
||||
let evdev = match bit {
|
||||
b if b == SHIFT => KEY_LEFTSHIFT,
|
||||
b if b == CTRL => KEY_LEFTCTRL,
|
||||
b if b == ALT => KEY_LEFTALT,
|
||||
b if b == SUPER => KEY_LEFTMETA,
|
||||
_ => return None,
|
||||
};
|
||||
Some(evdev as u32 + 8)
|
||||
}
|
||||
|
||||
fn now_ms() -> u32 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlRegistry, GlobalListContents> for KbState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &WlRegistry,
|
||||
_event: <WlRegistry as Proxy>::Event,
|
||||
_data: &GlobalListContents,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_noop!(KbState: ignore WlSeat);
|
||||
delegate_noop!(KbState: ignore ZwlrVirtualPointerV1);
|
||||
delegate_noop!(KbState: ignore ZwlrVirtualPointerManagerV1);
|
||||
delegate_noop!(KbState: ignore ZwpVirtualKeyboardV1);
|
||||
delegate_noop!(KbState: ignore ZwpVirtualKeyboardManagerV1);
|
||||
|
||||
pub fn initialize_wl_virtual() -> anyhow::Result<Box<dyn HidProvider>, Toast> {
|
||||
let provider = WlVirtualProvider::try_new().map_err(|e| {
|
||||
Toast::new(
|
||||
ToastTopic::System,
|
||||
String::with_capacity(0),
|
||||
format!("Could not initialize wl_virtual: {e}"),
|
||||
)
|
||||
})?;
|
||||
Ok(Box::new(provider))
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
use super::hid::{self, HidProvider, VirtualKey};
|
||||
use super::hid::{self, VirtualKey};
|
||||
|
||||
use crate::{
|
||||
backend::wayvr::WvrServerState,
|
||||
overlays::toast::Toast,
|
||||
subsystem::hid::{DummyProvider, XkbKeymap},
|
||||
};
|
||||
use crate::subsystem::hid::provider::HidProvider;
|
||||
use crate::subsystem::hid::provider::dummy::DummyProvider;
|
||||
use crate::{backend::wayvr::WvrServerState, overlays::toast::Toast, subsystem::hid::XkbKeymap};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum KeyboardFocus {
|
||||
|
|
@ -20,26 +18,18 @@ pub struct HidWrapper {
|
|||
|
||||
impl HidWrapper {
|
||||
pub fn new() -> (Self, Option<Toast>) {
|
||||
let hid_result = hid::initialize();
|
||||
let hid_provider: Box<dyn HidProvider>;
|
||||
let error: Option<Toast>;
|
||||
match hid_result {
|
||||
Ok(uinput) => {
|
||||
hid_provider = Box::new(uinput);
|
||||
error = None;
|
||||
}
|
||||
Err(toast) => {
|
||||
hid_provider = Box::new(DummyProvider {});
|
||||
error = Some(toast);
|
||||
}
|
||||
}
|
||||
let (provider, toast) = hid::provider::wl_virtual::initialize_wl_virtual()
|
||||
.or_else(|_| hid::provider::uinput::initialize_uinput())
|
||||
.map(|provider| (provider, None))
|
||||
.unwrap_or_else(|toast| (Box::new(DummyProvider {}), Some(toast)));
|
||||
|
||||
(
|
||||
Self {
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
inner: hid_provider,
|
||||
inner: provider,
|
||||
keymap: None,
|
||||
},
|
||||
error,
|
||||
toast,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +56,7 @@ impl HidWrapper {
|
|||
.inspect_err(|e| log::error!("Could not set WayVR keymap: {e:?}"));
|
||||
} else {
|
||||
self.keymap = Some(keymap.clone());
|
||||
self.inner.set_keymap(&keymap);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
|
|
|
|||
Loading…
Reference in New Issue