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",
|
||||||
"vulkano-shaders",
|
"vulkano-shaders",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
|
"wayland-protocols-misc",
|
||||||
"wayvr-ipc",
|
"wayvr-ipc",
|
||||||
"wgui",
|
"wgui",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ vulkano = { version = "0.35.2", default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
vulkano-shaders = "0.35.0"
|
vulkano-shaders = "0.35.0"
|
||||||
wayland-client = { version = "0.31.11" }
|
wayland-client = { version = "0.31.11" }
|
||||||
|
wayland-protocols-misc = { version = "0.3.12" }
|
||||||
xdg = "3.0.0"
|
xdg = "3.0.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ tracing = "0.1.43"
|
||||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
wayland-client = { workspace = true }
|
wayland-client = { workspace = true }
|
||||||
|
wayland-protocols-misc = { workspace = true }
|
||||||
winit = { version = "0.30.12", optional = true }
|
winit = { version = "0.30.12", optional = true }
|
||||||
xcb = { version = "1.6.0", features = [
|
xcb = { version = "1.6.0", features = [
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,25 @@
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use idmap::{IdMap, idmap};
|
use idmap::{IdMap, idmap};
|
||||||
use idmap_derive::IntegerId;
|
use idmap_derive::IntegerId;
|
||||||
use input_linux::{
|
|
||||||
AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, InputId, Key, RelativeAxis,
|
|
||||||
UInputHandle,
|
|
||||||
};
|
|
||||||
use libc::{input_event, timeval};
|
use libc::{input_event, timeval};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::mem::transmute;
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::{fs::File, sync::atomic::AtomicBool};
|
use strum::{EnumIter, EnumString};
|
||||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
|
||||||
use wlx_common::overlays::ToastTopic;
|
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
use crate::overlays::toast::Toast;
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
|
pub mod provider;
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
mod x11;
|
mod x11;
|
||||||
|
|
||||||
pub static USE_UINPUT: AtomicBool = AtomicBool::new(true);
|
#[derive(Debug)]
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WheelDelta {
|
pub struct WheelDelta {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: 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 {
|
struct MouseButtonAction {
|
||||||
button: u16,
|
button: u16,
|
||||||
down: bool,
|
down: bool,
|
||||||
|
|
@ -98,17 +33,6 @@ struct MouseAction {
|
||||||
scroll: Option<WheelDelta>,
|
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_LEFT: u16 = 0x110;
|
||||||
pub const MOUSE_RIGHT: u16 = 0x111;
|
pub const MOUSE_RIGHT: u16 = 0x111;
|
||||||
pub const MOUSE_MIDDLE: u16 = 0x112;
|
pub const MOUSE_MIDDLE: u16 = 0x112;
|
||||||
|
|
@ -120,216 +44,6 @@ const EV_KEY: u16 = 0x1;
|
||||||
const EV_REL: u16 = 0x2;
|
const EV_REL: u16 = 0x2;
|
||||||
const EV_ABS: u16 = 0x3;
|
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]
|
#[inline]
|
||||||
fn get_time() -> timeval {
|
fn get_time() -> timeval {
|
||||||
let mut 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::{
|
use crate::subsystem::hid::provider::HidProvider;
|
||||||
backend::wayvr::WvrServerState,
|
use crate::subsystem::hid::provider::dummy::DummyProvider;
|
||||||
overlays::toast::Toast,
|
use crate::{backend::wayvr::WvrServerState, overlays::toast::Toast, subsystem::hid::XkbKeymap};
|
||||||
subsystem::hid::{DummyProvider, XkbKeymap},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum KeyboardFocus {
|
pub enum KeyboardFocus {
|
||||||
|
|
@ -20,26 +18,18 @@ pub struct HidWrapper {
|
||||||
|
|
||||||
impl HidWrapper {
|
impl HidWrapper {
|
||||||
pub fn new() -> (Self, Option<Toast>) {
|
pub fn new() -> (Self, Option<Toast>) {
|
||||||
let hid_result = hid::initialize();
|
let (provider, toast) = hid::provider::wl_virtual::initialize_wl_virtual()
|
||||||
let hid_provider: Box<dyn HidProvider>;
|
.or_else(|_| hid::provider::uinput::initialize_uinput())
|
||||||
let error: Option<Toast>;
|
.map(|provider| (provider, None))
|
||||||
match hid_result {
|
.unwrap_or_else(|toast| (Box::new(DummyProvider {}), Some(toast)));
|
||||||
Ok(uinput) => {
|
|
||||||
hid_provider = Box::new(uinput);
|
|
||||||
error = None;
|
|
||||||
}
|
|
||||||
Err(toast) => {
|
|
||||||
hid_provider = Box::new(DummyProvider {});
|
|
||||||
error = Some(toast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||||
inner: hid_provider,
|
inner: provider,
|
||||||
keymap: None,
|
keymap: None,
|
||||||
},
|
},
|
||||||
error,
|
toast,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,6 +56,7 @@ impl HidWrapper {
|
||||||
.inspect_err(|e| log::error!("Could not set WayVR keymap: {e:?}"));
|
.inspect_err(|e| log::error!("Could not set WayVR keymap: {e:?}"));
|
||||||
} else {
|
} else {
|
||||||
self.keymap = Some(keymap.clone());
|
self.keymap = Some(keymap.clone());
|
||||||
|
self.inner.set_keymap(&keymap);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue