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:
SparkyTD 2026-06-30 13:13:59 +00:00 committed by GitHub
parent da394ec2c7
commit 28da0347ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 615 additions and 310 deletions

1
Cargo.lock generated
View File

@ -6569,6 +6569,7 @@ dependencies = [
"vulkano",
"vulkano-shaders",
"wayland-client",
"wayland-protocols-misc",
"wayvr-ipc",
"wgui",
"winit",

View File

@ -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]

View File

@ -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",

View File

@ -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 {

View File

@ -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) {}
}

View File

@ -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);
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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!(