diff --git a/Cargo.lock b/Cargo.lock index c7bc2cb3..620e572d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6569,6 +6569,7 @@ dependencies = [ "vulkano", "vulkano-shaders", "wayland-client", + "wayland-protocols-misc", "wayvr-ipc", "wgui", "winit", diff --git a/Cargo.toml b/Cargo.toml index 1550b9d5..d32be292 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index aedac498..3fa7d064 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -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", diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index 5a8ee423..7ebcbaeb 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -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 { - 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, } -pub struct UInputProvider { - keyboard_handle: UInputHandle, - mouse_handle: UInputHandle, - 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 { - 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 { diff --git a/wayvr/src/subsystem/hid/provider/dummy.rs b/wayvr/src/subsystem/hid/provider/dummy.rs new file mode 100644 index 00000000..ecc9b4e2 --- /dev/null +++ b/wayvr/src/subsystem/hid/provider/dummy.rs @@ -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) {} +} diff --git a/wayvr/src/subsystem/hid/provider/mod.rs b/wayvr/src/subsystem/hid/provider/mod.rs new file mode 100644 index 00000000..f3dbb7f6 --- /dev/null +++ b/wayvr/src/subsystem/hid/provider/mod.rs @@ -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); +} diff --git a/wayvr/src/subsystem/hid/provider/uinput.rs b/wayvr/src/subsystem/hid/provider/uinput.rs new file mode 100644 index 00000000..367575f8 --- /dev/null +++ b/wayvr/src/subsystem/hid/provider/uinput.rs @@ -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, + mouse_handle: UInputHandle, + desktop_extent: Vec2, + desktop_origin: Vec2, + cur_modifiers: u8, + current_action: MouseAction, +} + +impl UInputProvider { + pub fn try_new() -> Option { + 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, 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)) +} diff --git a/wayvr/src/subsystem/hid/provider/wl_virtual.rs b/wayvr/src/subsystem/hid/provider/wl_virtual.rs new file mode 100644 index 00000000..678684f9 --- /dev/null +++ b/wayvr/src/subsystem/hid/provider/wl_virtual.rs @@ -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, + state: KbState, + + virtual_pointer: ZwlrVirtualPointerV1, + desktop_extent: Vec2, + desktop_origin: Vec2, + keymap_file: Option, + + 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 { + let state = KbState; + + let connection = wayland_client::Connection::connect_to_env()?; + let (globals, queue) = registry_queue_init::(&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 { + 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 for KbState { + fn event( + _state: &mut Self, + _proxy: &WlRegistry, + _event: ::Event, + _data: &GlobalListContents, + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle, + ) { + } +} + +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, 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)) +} diff --git a/wayvr/src/subsystem/input.rs b/wayvr/src/subsystem/input.rs index 51716405..9c60c179 100644 --- a/wayvr/src/subsystem/input.rs +++ b/wayvr/src/subsystem/input.rs @@ -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) { - let hid_result = hid::initialize(); - let hid_provider: Box; - let error: Option; - 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!(