From 6a6264347b3cb4da6299377a2e6d222a1a3addbe Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:41:22 +0900 Subject: [PATCH] mouse wheel handling & wl_virtual modifiers --- wayvr/src/backend/wayvr/input_capture.rs | 18 ++--- wayvr/src/backend/wayvr/mod.rs | 3 +- wayvr/src/overlays/screen/backend.rs | 5 +- wayvr/src/subsystem/hid/provider/dummy.rs | 2 +- wayvr/src/subsystem/hid/provider/mod.rs | 2 +- wayvr/src/subsystem/hid/provider/uinput.rs | 10 +-- .../src/subsystem/hid/provider/wl_virtual.rs | 69 ++++++++++++------- wayvr/src/subsystem/input.rs | 2 +- 8 files changed, 62 insertions(+), 49 deletions(-) diff --git a/wayvr/src/backend/wayvr/input_capture.rs b/wayvr/src/backend/wayvr/input_capture.rs index 2246cf19..fcf77f34 100644 --- a/wayvr/src/backend/wayvr/input_capture.rs +++ b/wayvr/src/backend/wayvr/input_capture.rs @@ -121,16 +121,6 @@ impl InputCapture { self.event_rx.try_iter().collect() } - /// Exclusively grabs every currently detected keyboard and mouse. - /// Newly connected matching devices are grabbed automatically. - pub fn set_grabbed(&self, grabbed: bool) -> anyhow::Result<()> { - self.command_tx - .try_send(Command::SetGrabbed(grabbed)) - .map_err(|error| anyhow::anyhow!("worker thread unreachable: {error}"))?; - - Ok(()) - } - /// Set acceleration profile for current and future mice pub fn set_pointer_accel(&self, accel: bool, speed: f32) -> anyhow::Result<()> { if !speed.is_finite() || !(-1.0..=1.0).contains(&speed) { @@ -156,8 +146,12 @@ impl Drop for InputCapture { enum Command { ResetWatchdog, - SetGrabbed(bool), - SetPointerAccel { accel: bool, speed: f32 }, + #[allow(dead_code)] + SetGrabbed(bool), // might need later? + SetPointerAccel { + accel: bool, + speed: f32, + }, Shutdown, } diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index e4d0f38b..a46b69c7 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -85,7 +85,7 @@ use crate::{ subsystem::{ dbus::DbusConnector, hid::{self, MODS_TO_KEYS, WheelDelta}, - input::{HidWrapper, InputFocus}, + input::HidWrapper, }, windowing::{OverlayID, OverlaySelector, backend::OverlayEventData}, }; @@ -897,7 +897,6 @@ impl WvrServerState { } } input_capture::CapturedEvent::Grabbed => { - hid_wrapper.set_input_focus(Some(self), InputFocus::WayVR); audio_sample_player.play_sample(audio_system, "input_grab"); if !self.grab_toast_sent { self.grab_toast_sent = true; diff --git a/wayvr/src/overlays/screen/backend.rs b/wayvr/src/overlays/screen/backend.rs index fb03e281..d00186de 100644 --- a/wayvr/src/overlays/screen/backend.rs +++ b/wayvr/src/overlays/screen/backend.rs @@ -352,7 +352,10 @@ impl OverlayBackend for ScreenBackend { .mouse_move(DVec2::new(pos.x as _, pos.y as _)); } - fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta: WheelDelta) { + fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, mut delta: WheelDelta) { + // convert to v120 value; maximum deflect scrolls at half speed + delta.x *= 60.0; + delta.y *= 60.0; app.hid_provider.inner.wheel(delta); } diff --git a/wayvr/src/subsystem/hid/provider/dummy.rs b/wayvr/src/subsystem/hid/provider/dummy.rs index fd0bd682..9c0fbc3c 100644 --- a/wayvr/src/subsystem/hid/provider/dummy.rs +++ b/wayvr/src/subsystem/hid/provider/dummy.rs @@ -13,7 +13,7 @@ impl HidProvider for DummyProvider { fn set_desktop_extent(&mut self, _extent: DVec2) {} fn set_desktop_origin(&mut self, _origin: DVec2) {} fn set_modifiers(&mut self, _modifiers: u8) {} - fn send_key(&self, _key: VirtualKey, _down: bool) {} + fn send_key(&mut self, _key: VirtualKey, _down: bool) {} fn set_keymap(&mut self, _keymap: &XkbKeymap) {} diff --git a/wayvr/src/subsystem/hid/provider/mod.rs b/wayvr/src/subsystem/hid/provider/mod.rs index 770956cd..1905fe0d 100644 --- a/wayvr/src/subsystem/hid/provider/mod.rs +++ b/wayvr/src/subsystem/hid/provider/mod.rs @@ -16,7 +16,7 @@ pub trait HidProvider: Sync + Send { // Keyboard Functions fn set_modifiers(&mut self, mods: u8); - fn send_key(&self, key: VirtualKey, down: bool); + fn send_key(&mut self, key: VirtualKey, down: bool); fn set_keymap(&mut self, keymap: &XkbKeymap); // Common Functions diff --git a/wayvr/src/subsystem/hid/provider/uinput.rs b/wayvr/src/subsystem/hid/provider/uinput.rs index 8ad6c89d..0b4de4bb 100644 --- a/wayvr/src/subsystem/hid/provider/uinput.rs +++ b/wayvr/src/subsystem/hid/provider/uinput.rs @@ -135,18 +135,14 @@ impl UInputProvider { } 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::WheelHiRes as _, delta.y as _), new_event( time, EV_REL, RelativeAxis::HorizontalWheelHiRes as _, - delta_x, + delta.x as _, ), new_event(time, EV_SYN, 0, 0), ]; @@ -206,7 +202,7 @@ impl HidProvider for UInputProvider { self.cur_modifiers = modifiers; } - fn send_key(&self, key: VirtualKey, down: bool) { + fn send_key(&mut self, key: VirtualKey, down: bool) { #[cfg(debug_assertions)] log::trace!("send_key: {key:?} {down}"); diff --git a/wayvr/src/subsystem/hid/provider/wl_virtual.rs b/wayvr/src/subsystem/hid/provider/wl_virtual.rs index c105cd3e..31ed051c 100644 --- a/wayvr/src/subsystem/hid/provider/wl_virtual.rs +++ b/wayvr/src/subsystem/hid/provider/wl_virtual.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::os::fd::AsFd; use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::Context as _; -use glam::{DVec2}; +use glam::{Vec2, DVec2}; 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; @@ -21,6 +21,8 @@ use crate::overlays::toast::Toast; use crate::subsystem::hid::provider::HidProvider; use crate::subsystem::hid::{VirtualKey, WheelDelta, *}; +const LOCKED: u8 = CAPS_LOCK | NUM_LOCK; + pub struct WlVirtualProvider { _connection: wayland_client::Connection, queue: wayland_client::EventQueue, @@ -35,6 +37,7 @@ pub struct WlVirtualProvider { keyboard_mods_state: u8, last_pointer_position: DVec2, + wheel_accum: Vec2, } struct KbState; @@ -84,28 +87,30 @@ impl HidProvider for WlVirtualProvider { self.virtual_pointer.frame(); } - fn wheel(&mut self, delta: WheelDelta) { + fn wheel(&mut self, mut delta: WheelDelta) { #[cfg(debug_assertions)] log::trace!("Scroll Axis: {delta:?}"); + // no clue; a value of 10 seems to equal a v120 value of 120 + delta.x /= 12.0; + delta.y /= 12.0; + 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, + -delta.y as _, + accumulate_discrete_scroll(&mut self.wheel_accum.y, -delta.y), ); } 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, + delta.x as _, + accumulate_discrete_scroll(&mut self.wheel_accum.x, delta.x), ); } @@ -122,15 +127,13 @@ impl HidProvider for WlVirtualProvider { } 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) { + if let Some(kc) = MODS_TO_KEYS.get(bit) { self.virtual_keyboard - .key(Self::now_ms(), kc - 8, down as u32); + .key(Self::now_ms(), kc[0] as u32 - 8, down as u32); } } } @@ -143,7 +146,7 @@ impl HidProvider for WlVirtualProvider { self._connection.flush().unwrap(); } - fn send_key(&self, key: VirtualKey, down: bool) { + fn send_key(&mut self, key: VirtualKey, down: bool) { #[cfg(debug_assertions)] log::trace!("Keyboard key: {key:?} ({}), down: {down}", key as u16); @@ -156,6 +159,20 @@ impl HidProvider for WlVirtualProvider { }, ); + // sending a mod key → also have to update mod state + if let Some(m) = KEYS_TO_MODS.get(key) { + match (down, m & LOCKED != 0) { + (true, true) => self.keyboard_mods_state ^= m, + (true, false) => self.keyboard_mods_state |= m, + (false, false) => self.keyboard_mods_state &= !m, + (false, true) => {} + } + + let depressed = (self.keyboard_mods_state & !LOCKED) as u32; + let locked = (self.keyboard_mods_state & LOCKED) as u32; + self.virtual_keyboard.modifiers(depressed, 0, locked, 0); + } + self._connection.flush().unwrap(); } @@ -222,6 +239,7 @@ impl WlVirtualProvider { desktop_origin: DVec2::ZERO, keyboard_mods_state: 0, last_pointer_position: DVec2::ZERO, + wheel_accum: Vec2::ZERO, }; result.set_keymap(&XkbKeymap { @@ -245,17 +263,6 @@ impl WlVirtualProvider { .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) @@ -292,3 +299,17 @@ pub fn initialize_wl_virtual() -> anyhow::Result, Toast> { })?; Ok(Box::new(provider)) } + +fn accumulate_discrete_scroll(acc: &mut f32, delta: f32) -> i32 { + const WHEEL_DETENT: f32 = 10.0; + *acc += delta; + let steps = (*acc / WHEEL_DETENT).trunc() as i32; + if steps != 0 { + *acc -= steps as f32 * WHEEL_DETENT; + if acc.abs() < 0.001 { + *acc = 0.0; + } + } + + steps +} diff --git a/wayvr/src/subsystem/input.rs b/wayvr/src/subsystem/input.rs index b852e145..dbacc8e6 100644 --- a/wayvr/src/subsystem/input.rs +++ b/wayvr/src/subsystem/input.rs @@ -63,7 +63,7 @@ impl HidWrapper { } pub fn send_key_routed( - &self, + &mut self, wvr_server: Option<&mut WvrServerState>, key: VirtualKey, down: bool,