mirror of https://github.com/wayvr-org/wayvr.git
input capture #1 - keyboard
This commit is contained in:
parent
a29451ceaa
commit
968cb230e3
|
|
@ -689,6 +689,18 @@ dependencies = [
|
|||
"no_std_io2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -1779,6 +1791,18 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evdev"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b686663ba7f08d92880ff6ba22170f1df4e83629341cba34cf82cd65ebea99"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"nix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
|
|
@ -1971,6 +1995,12 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.32"
|
||||
|
|
@ -4349,6 +4379,12 @@ version = "6.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
|
|
@ -5628,6 +5664,12 @@ dependencies = [
|
|||
"slotmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
|
|
@ -6492,6 +6534,7 @@ dependencies = [
|
|||
"config",
|
||||
"dash-frontend",
|
||||
"dbus",
|
||||
"evdev",
|
||||
"futures",
|
||||
"glam",
|
||||
"idmap",
|
||||
|
|
@ -7084,6 +7127,15 @@ version = "0.6.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ xkbcommon = {
|
|||
|
||||
calloop = { version = "0.14.4", optional = true }
|
||||
calloop-wayland-source = { version = "0.4.1", optional = true }
|
||||
evdev = "0.13.2"
|
||||
|
||||
[build-dependencies]
|
||||
regex.workspace = true
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::overlays::keyboard::KEYBOARD_NAME;
|
|||
use crate::overlays::watch::WATCH_NAME;
|
||||
use crate::state::{AppSession, AppState};
|
||||
use crate::subsystem::hid::WheelDelta;
|
||||
use crate::subsystem::input::KeyboardFocus;
|
||||
use crate::subsystem::input::InputFocus;
|
||||
use crate::windowing::backend::OverlayEventData;
|
||||
use crate::windowing::manager::OverlayWindowManager;
|
||||
use crate::windowing::window::{self, OverlayWindowData, realign, scalar_scale};
|
||||
|
|
@ -407,12 +407,13 @@ fn populate_lines(
|
|||
}
|
||||
}
|
||||
|
||||
fn update_focus(focus: &mut KeyboardFocus, overlay_keyboard_focus: Option<KeyboardFocus>) {
|
||||
if let Some(f) = &overlay_keyboard_focus
|
||||
&& *focus != *f
|
||||
fn update_focus(app: &mut AppState, overlay_input_focus: Option<InputFocus>) {
|
||||
if let Some(f) = &overlay_input_focus
|
||||
&& app
|
||||
.hid_provider
|
||||
.set_input_focus(app.wvr_server.as_mut(), *f)
|
||||
{
|
||||
log::debug!("Setting keyboard focus to {:?}", *f);
|
||||
*focus = *f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -537,10 +538,7 @@ where
|
|||
|
||||
// grab
|
||||
if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable {
|
||||
update_focus(
|
||||
&mut app.hid_provider.keyboard_focus,
|
||||
hovered.config.keyboard_focus,
|
||||
);
|
||||
update_focus(app, hovered.config.input_focus);
|
||||
start_grab(
|
||||
idx,
|
||||
hit.overlay,
|
||||
|
|
@ -567,10 +565,7 @@ where
|
|||
let pointer = &mut app.input_state.pointers[hit.pointer];
|
||||
if pointer.now.click && !pointer.before.click {
|
||||
pointer.interaction.clicked_id = Some(hit.overlay);
|
||||
update_focus(
|
||||
&mut app.hid_provider.keyboard_focus,
|
||||
hovered.config.keyboard_focus,
|
||||
);
|
||||
update_focus(app, hovered.config.input_focus);
|
||||
hovered.config.backend.on_pointer(app, &hit, true);
|
||||
} else if !pointer.now.click && pointer.before.click {
|
||||
// send release event to overlay that was originally clicked
|
||||
|
|
|
|||
|
|
@ -232,11 +232,26 @@ impl WayVRCompositor {
|
|||
Keycode::new(virtual_key),
|
||||
state,
|
||||
self.serial_counter.next_serial(),
|
||||
0,
|
||||
super::time::get_millis() as u32,
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn release_all_keys(&mut self) {
|
||||
let pressed = self.seat_keyboard.pressed_keys();
|
||||
|
||||
for keycode in pressed {
|
||||
self.seat_keyboard.input::<(), _>(
|
||||
&mut self.state,
|
||||
keycode,
|
||||
smithay::backend::input::KeyState::Released,
|
||||
self.serial_counter.next_serial(),
|
||||
super::time::get_millis() as u32,
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> {
|
||||
// Smithay only accepts keymaps in a string form due to thread safety concerns
|
||||
self.seat_keyboard
|
||||
|
|
|
|||
|
|
@ -0,0 +1,604 @@
|
|||
use evdev::{AttributeSetRef, Device, EventType, KeyCode, RelativeAxisCode};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io,
|
||||
os::fd::AsRawFd,
|
||||
path::{Path, PathBuf},
|
||||
sync::mpsc::{self, Receiver, SyncSender, TryRecvError},
|
||||
thread::{self, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub const IGNORE_PREFIX: &str = "WayVR";
|
||||
|
||||
const WATCHDOG_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
const POLL_TIMEOUT_MS: i32 = 20;
|
||||
const SYN_REPORT_CODE: u16 = 0;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CapturedEvent {
|
||||
Key {
|
||||
code: u16,
|
||||
pressed: bool,
|
||||
},
|
||||
PointerButton {
|
||||
button: u32,
|
||||
pressed: bool,
|
||||
},
|
||||
PointerMotion {
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
},
|
||||
PointerAxis {
|
||||
horizontal_v120: i32,
|
||||
vertical_v120: i32,
|
||||
},
|
||||
UngrabbedAll,
|
||||
}
|
||||
|
||||
pub struct InputCapture {
|
||||
command_tx: SyncSender<Command>,
|
||||
event_rx: Receiver<CapturedEvent>,
|
||||
worker: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl InputCapture {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let (command_tx, command_rx) = mpsc::sync_channel(8);
|
||||
let (event_tx, event_rx) = mpsc::sync_channel(64);
|
||||
|
||||
let worker = thread::Builder::new()
|
||||
.name("wayvr-input-capture".into())
|
||||
.spawn(move || worker_main(command_rx, event_tx))?;
|
||||
|
||||
Ok(Self {
|
||||
command_tx,
|
||||
event_rx,
|
||||
worker: Some(worker),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns every currently queued event without blocking.
|
||||
pub fn drain_events(&self) -> Vec<CapturedEvent> {
|
||||
let _ = self.command_tx.try_send(Command::ResetWatchdog);
|
||||
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<()> {
|
||||
if let Err(e) = self.command_tx.send(Command::SetGrabbed { grabbed }) {
|
||||
anyhow::bail!("Worker thread unreachable: {e:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InputCapture {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.command_tx.send(Command::Shutdown);
|
||||
if let Some(worker) = self.worker.take() {
|
||||
let _ = worker.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Command {
|
||||
ResetWatchdog,
|
||||
SetGrabbed { grabbed: bool },
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
struct OpenDevice {
|
||||
path: PathBuf,
|
||||
device: Device,
|
||||
is_keyboard: bool,
|
||||
is_mouse: bool,
|
||||
pending: Vec<PendingEvent>,
|
||||
pressed_keys: HashSet<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PendingEvent {
|
||||
Key {
|
||||
code: u16,
|
||||
pressed: bool,
|
||||
},
|
||||
Button {
|
||||
button: u32,
|
||||
pressed: bool,
|
||||
},
|
||||
Motion {
|
||||
dx: i32,
|
||||
dy: i32,
|
||||
},
|
||||
Axis {
|
||||
horizontal: i32,
|
||||
vertical: i32,
|
||||
horizontal_hi_res: i32,
|
||||
vertical_hi_res: i32,
|
||||
},
|
||||
}
|
||||
|
||||
fn worker_main(command_rx: Receiver<Command>, event_tx: SyncSender<CapturedEvent>) {
|
||||
let mut devices = Vec::<OpenDevice>::new();
|
||||
let mut grabbed = false;
|
||||
let mut emergency_ungrab = false;
|
||||
|
||||
let mut want_rescan = true;
|
||||
let mut watchdog = Instant::now(); // in case wayvr main thread gets blocked
|
||||
|
||||
'worker: loop {
|
||||
if Instant::now() >= watchdog && grabbed {
|
||||
log::warn!("Watchdog timed out, ungrabbing all input devices.");
|
||||
emergency_ungrab = true;
|
||||
} else if want_rescan {
|
||||
scan_for_devices(&mut devices, grabbed);
|
||||
want_rescan = false;
|
||||
}
|
||||
|
||||
loop {
|
||||
match command_rx.try_recv() {
|
||||
Ok(Command::SetGrabbed { grabbed: requested }) => {
|
||||
let result = if requested {
|
||||
grab_existing_devices(&mut devices)
|
||||
} else {
|
||||
ungrab_existing_devices(&mut devices)
|
||||
};
|
||||
|
||||
grabbed = requested && result.is_ok();
|
||||
watchdog = Instant::now() + WATCHDOG_TIMEOUT;
|
||||
|
||||
for entry in &mut devices {
|
||||
entry.pending.clear();
|
||||
entry.pressed_keys.clear();
|
||||
discard_available_events(&mut entry.device);
|
||||
}
|
||||
}
|
||||
Ok(Command::ResetWatchdog) => watchdog = Instant::now() + WATCHDOG_TIMEOUT,
|
||||
Ok(Command::Shutdown) => break 'worker,
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(TryRecvError::Disconnected) => break 'worker,
|
||||
}
|
||||
}
|
||||
|
||||
let mut poll_fds: Vec<libc::pollfd> = devices
|
||||
.iter()
|
||||
.map(|entry| libc::pollfd {
|
||||
fd: entry.device.as_raw_fd(),
|
||||
events: libc::POLLIN,
|
||||
revents: 0,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// SAFETY: poll_fds is a valid contiguous array for the duration of the call.
|
||||
let poll_result = unsafe {
|
||||
libc::poll(
|
||||
poll_fds.as_mut_ptr(),
|
||||
poll_fds.len() as libc::nfds_t,
|
||||
POLL_TIMEOUT_MS,
|
||||
)
|
||||
};
|
||||
|
||||
if poll_result < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() != io::ErrorKind::Interrupted {
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut dead = vec![false; devices.len()];
|
||||
|
||||
for (index, poll_fd) in poll_fds.iter().enumerate() {
|
||||
if poll_fd.revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL) != 0 {
|
||||
dead[index] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if poll_fd.revents & libc::POLLIN == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
match read_device_events(&mut devices[index], grabbed, &event_tx) {
|
||||
ReadResult::Continue => {}
|
||||
ReadResult::DeviceGone => dead[index] = true,
|
||||
ReadResult::EmergencyUngrab => {
|
||||
emergency_ungrab = true;
|
||||
break;
|
||||
}
|
||||
ReadResult::ReceiverGone => break 'worker,
|
||||
}
|
||||
}
|
||||
|
||||
if emergency_ungrab {
|
||||
// dropping the descriptors forcibly releases EVIOCGRAB
|
||||
devices.clear();
|
||||
|
||||
grabbed = false;
|
||||
emergency_ungrab = false;
|
||||
|
||||
// rediscover the devices immediately, but leave them ungrabbed
|
||||
want_rescan = true;
|
||||
|
||||
let e = event_tx.send(CapturedEvent::UngrabbedAll); // send this synchronously
|
||||
if e.is_err() {
|
||||
break 'worker;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for index in (0..dead.len()).rev() {
|
||||
if dead[index] {
|
||||
devices.swap_remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_for_devices(devices: &mut Vec<OpenDevice>, grabbed: bool) {
|
||||
let known_paths: HashSet<PathBuf> = devices.iter().map(|entry| entry.path.clone()).collect();
|
||||
|
||||
for (path, mut device) in evdev::enumerate() {
|
||||
if known_paths.contains(&path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if device
|
||||
.name()
|
||||
.is_some_and(|name| name.starts_with(IGNORE_PREFIX))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(keys) = device.supported_keys() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let is_keyboard = looks_like_keyboard(keys);
|
||||
let is_mouse = looks_like_mouse(&device, keys);
|
||||
if !is_keyboard && !is_mouse {
|
||||
continue;
|
||||
}
|
||||
|
||||
if device.set_nonblocking(true).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if grabbed && device.grab().is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
discard_available_events(&mut device);
|
||||
devices.push(OpenDevice {
|
||||
path,
|
||||
device,
|
||||
is_keyboard,
|
||||
is_mouse,
|
||||
pending: Vec::new(),
|
||||
pressed_keys: HashSet::with_capacity(8),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_keyboard(keys: &AttributeSetRef<KeyCode>) -> bool {
|
||||
let full_keyboard = (keys.contains(KeyCode::KEY_A) || keys.contains(KeyCode::KEY_Z))
|
||||
&& [KeyCode::KEY_ESC, KeyCode::KEY_ENTER, KeyCode::KEY_SPACE]
|
||||
.into_iter()
|
||||
.filter(|key| keys.contains(*key))
|
||||
.count()
|
||||
>= 2;
|
||||
|
||||
let numeric_keypad = keys.contains(KeyCode::KEY_KP0)
|
||||
&& keys.contains(KeyCode::KEY_KP1)
|
||||
&& keys.contains(KeyCode::KEY_KPENTER);
|
||||
|
||||
full_keyboard || numeric_keypad
|
||||
}
|
||||
|
||||
fn looks_like_mouse(device: &Device, keys: &AttributeSetRef<KeyCode>) -> bool {
|
||||
let Some(relative_axes) = device.supported_relative_axes() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
relative_axes.contains(RelativeAxisCode::REL_X)
|
||||
&& relative_axes.contains(RelativeAxisCode::REL_Y)
|
||||
&& mouse_buttons()
|
||||
.into_iter()
|
||||
.any(|button| keys.contains(button))
|
||||
}
|
||||
|
||||
fn mouse_buttons() -> [KeyCode; 8] {
|
||||
[
|
||||
KeyCode::BTN_LEFT,
|
||||
KeyCode::BTN_RIGHT,
|
||||
KeyCode::BTN_MIDDLE,
|
||||
KeyCode::BTN_SIDE,
|
||||
KeyCode::BTN_EXTRA,
|
||||
KeyCode::BTN_FORWARD,
|
||||
KeyCode::BTN_BACK,
|
||||
KeyCode::BTN_TASK,
|
||||
]
|
||||
}
|
||||
|
||||
fn grab_existing_devices(devices: &mut [OpenDevice]) -> io::Result<()> {
|
||||
let mut grabbed_now = Vec::new();
|
||||
|
||||
for index in 0..devices.len() {
|
||||
if devices[index].device.is_grabbed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(error) = devices[index].device.grab() {
|
||||
let error = with_device_context("grab", &devices[index].path, error);
|
||||
for previous in grabbed_now {
|
||||
let dev: &mut OpenDevice = &mut devices[previous];
|
||||
let _ = dev.device.ungrab();
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
grabbed_now.push(index);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ungrab_existing_devices(devices: &mut [OpenDevice]) -> io::Result<()> {
|
||||
let mut first_error = None;
|
||||
|
||||
for entry in devices {
|
||||
if !entry.device.is_grabbed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(error) = entry.device.ungrab() {
|
||||
if first_error.is_none() {
|
||||
first_error = Some(with_device_context("ungrab", &entry.path, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match first_error {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn discard_available_events(device: &mut Device) {
|
||||
loop {
|
||||
match device.fetch_events() {
|
||||
Ok(events) => {
|
||||
if events.count() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ReadResult {
|
||||
Continue,
|
||||
DeviceGone,
|
||||
EmergencyUngrab,
|
||||
ReceiverGone,
|
||||
}
|
||||
|
||||
fn read_device_events(
|
||||
entry: &mut OpenDevice,
|
||||
emit: bool,
|
||||
event_tx: &SyncSender<CapturedEvent>,
|
||||
) -> ReadResult {
|
||||
loop {
|
||||
let events = match entry.device.fetch_events() {
|
||||
Ok(events) => events.collect::<Vec<_>>(),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
return ReadResult::Continue;
|
||||
}
|
||||
Err(_) => return ReadResult::DeviceGone,
|
||||
};
|
||||
|
||||
if events.is_empty() {
|
||||
return ReadResult::Continue;
|
||||
}
|
||||
|
||||
if !emit {
|
||||
entry.pending.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
for event in events {
|
||||
if event.event_type() == EventType::KEY {
|
||||
match event.value() {
|
||||
0 | 1 => {
|
||||
let code = event.code();
|
||||
let pressed = event.value() == 1;
|
||||
|
||||
if entry.is_mouse && is_mouse_button(code) {
|
||||
entry.pending.push(PendingEvent::Button {
|
||||
button: u32::from(code),
|
||||
pressed,
|
||||
});
|
||||
} else if entry.is_keyboard {
|
||||
entry.pending.push(PendingEvent::Key { code, pressed });
|
||||
|
||||
if !is_mouse_button(code) {
|
||||
if pressed {
|
||||
entry.pressed_keys.insert(code);
|
||||
if emergency_chord_active(&entry.pressed_keys) {
|
||||
// do not forward delete or any pending events to wayvr
|
||||
entry.pending.clear();
|
||||
log::info!(
|
||||
"Ctrl+Alt+Del pressed, ungrabbing all input devices"
|
||||
);
|
||||
return ReadResult::EmergencyUngrab;
|
||||
}
|
||||
} else {
|
||||
entry.pressed_keys.remove(&code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// kernel autorepeat. smithay/xkb performs repeat itself
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if event.event_type() == EventType::RELATIVE && entry.is_mouse {
|
||||
push_relative_event(entry, event.code(), event.value());
|
||||
} else if event.event_type() == EventType::SYNCHRONIZATION
|
||||
&& event.code() == SYN_REPORT_CODE
|
||||
&& !flush_pending(entry, event_tx)
|
||||
{
|
||||
return ReadResult::ReceiverGone;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_relative_event(entry: &mut OpenDevice, code: u16, value: i32) {
|
||||
if code == RelativeAxisCode::REL_X.0 || code == RelativeAxisCode::REL_Y.0 {
|
||||
let (dx, dy) = if code == RelativeAxisCode::REL_X.0 {
|
||||
(value, 0)
|
||||
} else {
|
||||
(0, value)
|
||||
};
|
||||
|
||||
if let Some(PendingEvent::Motion {
|
||||
dx: pending_dx,
|
||||
dy: pending_dy,
|
||||
..
|
||||
}) = entry.pending.last_mut()
|
||||
{
|
||||
*pending_dx = pending_dx.saturating_add(dx);
|
||||
*pending_dy = pending_dy.saturating_add(dy);
|
||||
} else {
|
||||
entry.pending.push(PendingEvent::Motion { dx, dy });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let axis_field = if code == RelativeAxisCode::REL_WHEEL.0 {
|
||||
AxisField::Vertical
|
||||
} else if code == RelativeAxisCode::REL_HWHEEL.0 {
|
||||
AxisField::Horizontal
|
||||
} else if code == RelativeAxisCode::REL_WHEEL_HI_RES.0 {
|
||||
AxisField::VerticalHiRes
|
||||
} else if code == RelativeAxisCode::REL_HWHEEL_HI_RES.0 {
|
||||
AxisField::HorizontalHiRes
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(entry.pending.last(), Some(PendingEvent::Axis { .. })) {
|
||||
entry.pending.push(PendingEvent::Axis {
|
||||
horizontal: 0,
|
||||
vertical: 0,
|
||||
horizontal_hi_res: 0,
|
||||
vertical_hi_res: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let Some(PendingEvent::Axis {
|
||||
horizontal,
|
||||
vertical,
|
||||
horizontal_hi_res,
|
||||
vertical_hi_res,
|
||||
..
|
||||
}) = entry.pending.last_mut()
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let target = match axis_field {
|
||||
AxisField::Horizontal => horizontal,
|
||||
AxisField::Vertical => vertical,
|
||||
AxisField::HorizontalHiRes => horizontal_hi_res,
|
||||
AxisField::VerticalHiRes => vertical_hi_res,
|
||||
};
|
||||
*target = target.saturating_add(value);
|
||||
}
|
||||
|
||||
enum AxisField {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
HorizontalHiRes,
|
||||
VerticalHiRes,
|
||||
}
|
||||
|
||||
fn flush_pending(entry: &mut OpenDevice, event_tx: &SyncSender<CapturedEvent>) -> bool {
|
||||
for pending in entry.pending.drain(..) {
|
||||
let event = match pending {
|
||||
PendingEvent::Key { code, pressed } => CapturedEvent::Key { code, pressed },
|
||||
PendingEvent::Button { button, pressed } => {
|
||||
CapturedEvent::PointerButton { button, pressed }
|
||||
}
|
||||
PendingEvent::Motion { dx, dy } => {
|
||||
if dx == 0 && dy == 0 {
|
||||
continue;
|
||||
}
|
||||
CapturedEvent::PointerMotion {
|
||||
dx: f64::from(dx),
|
||||
dy: f64::from(dy),
|
||||
}
|
||||
}
|
||||
PendingEvent::Axis {
|
||||
horizontal,
|
||||
vertical,
|
||||
horizontal_hi_res,
|
||||
vertical_hi_res,
|
||||
} => {
|
||||
let horizontal_v120 = if horizontal_hi_res != 0 {
|
||||
horizontal_hi_res
|
||||
} else {
|
||||
horizontal.saturating_mul(120)
|
||||
};
|
||||
let vertical_v120_raw = if vertical_hi_res != 0 {
|
||||
vertical_hi_res
|
||||
} else {
|
||||
vertical.saturating_mul(120)
|
||||
};
|
||||
|
||||
if horizontal_v120 == 0 && vertical_v120_raw == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
CapturedEvent::PointerAxis {
|
||||
horizontal_v120,
|
||||
// REL_WHEEL is positive for wheel-up
|
||||
vertical_v120: vertical_v120_raw.saturating_neg(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if event_tx.send(event).is_err() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_mouse_button(code: u16) -> bool {
|
||||
mouse_buttons().into_iter().any(|button| button.0 == code)
|
||||
}
|
||||
|
||||
fn with_device_context(action: &str, path: &Path, error: io::Error) -> io::Error {
|
||||
io::Error::new(
|
||||
error.kind(),
|
||||
format!("failed to {action} {}: {error}", path.display()),
|
||||
)
|
||||
}
|
||||
|
||||
fn emergency_chord_active(pressed: &HashSet<u16>) -> bool {
|
||||
let ctrl =
|
||||
pressed.contains(&KeyCode::KEY_LEFTCTRL.0) || pressed.contains(&KeyCode::KEY_RIGHTCTRL.0);
|
||||
|
||||
let alt =
|
||||
pressed.contains(&KeyCode::KEY_LEFTALT.0) || pressed.contains(&KeyCode::KEY_RIGHTALT.0);
|
||||
|
||||
ctrl && alt && pressed.contains(&KeyCode::KEY_DELETE.0)
|
||||
}
|
||||
|
|
@ -2,9 +2,11 @@ pub mod client;
|
|||
mod comp;
|
||||
mod handle;
|
||||
mod image_importer;
|
||||
mod input_capture;
|
||||
pub mod process;
|
||||
mod time;
|
||||
pub mod window;
|
||||
|
||||
use anyhow::Context;
|
||||
use comp::Application;
|
||||
use glam::Vec2;
|
||||
|
|
@ -52,7 +54,7 @@ use std::{
|
|||
use vulkano::image::view::ImageView;
|
||||
use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use wayvr_ipc::{packet_client::PositionMode, packet_server};
|
||||
use wgui::gfx::WGfx;
|
||||
use wgui::{gfx::WGfx, log::LogErr};
|
||||
use wlx_capture::frame::Transform;
|
||||
use wlx_common::desktop_finder::DesktopFinder;
|
||||
use xkbcommon::xkb;
|
||||
|
|
@ -62,6 +64,7 @@ use crate::{
|
|||
task::{OverlayTask, SpawnPos, TaskContainer, TaskType, ToggleMode},
|
||||
wayvr::{
|
||||
image_importer::ImageImporter,
|
||||
input_capture::InputCapture,
|
||||
process::{KillSignal, Process},
|
||||
},
|
||||
},
|
||||
|
|
@ -69,7 +72,10 @@ use crate::{
|
|||
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
|
||||
overlays::wayvr::create_wl_window_overlay,
|
||||
state::AppState,
|
||||
subsystem::hid::{MODS_TO_KEYS, WheelDelta},
|
||||
subsystem::{
|
||||
dbus::DbusConnector,
|
||||
hid::{MODS_TO_KEYS, WheelDelta},
|
||||
},
|
||||
windowing::{OverlayID, OverlaySelector},
|
||||
};
|
||||
|
||||
|
|
@ -125,6 +131,9 @@ pub struct WvrServerState {
|
|||
window_to_overlay: HashMap<window::WindowHandle, OverlayID>,
|
||||
overlay_to_window: SecondaryMap<OverlayID, window::WindowHandle>,
|
||||
process_overlays: HashMap<process::ProcessHandle, Vec<OverlayID>>,
|
||||
input_capture: Option<InputCapture>,
|
||||
has_input_focus: bool,
|
||||
grab_toast_sent: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
|
|
@ -246,6 +255,10 @@ impl WvrServerState {
|
|||
|
||||
let dma_importer = ImageImporter::new(gfx);
|
||||
|
||||
let input_capture = InputCapture::new()
|
||||
.log_err("Could not initialize evdev input capture")
|
||||
.ok();
|
||||
|
||||
let state = Application {
|
||||
output,
|
||||
image_importer: dma_importer,
|
||||
|
|
@ -281,6 +294,9 @@ impl WvrServerState {
|
|||
window_to_overlay: HashMap::new(),
|
||||
overlay_to_window: SecondaryMap::new(),
|
||||
process_overlays: HashMap::new(),
|
||||
input_capture,
|
||||
has_input_focus: false,
|
||||
grab_toast_sent: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -589,6 +605,8 @@ impl WvrServerState {
|
|||
wvr_server.manager.cleanup_handles();
|
||||
}
|
||||
|
||||
wvr_server.process_input_capture();
|
||||
|
||||
wvr_server.ticks += 1;
|
||||
|
||||
Ok(tasks)
|
||||
|
|
@ -661,6 +679,59 @@ impl WvrServerState {
|
|||
self.manager.seat_pointer.is_grabbed()
|
||||
}
|
||||
|
||||
pub fn process_input_capture(&mut self) {
|
||||
if !self.has_input_focus {
|
||||
return;
|
||||
}
|
||||
let Some(input_capture) = self.input_capture.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
#[allow(unused_variables)] //TODO: remove this
|
||||
for ev in input_capture.drain_events() {
|
||||
match ev {
|
||||
input_capture::CapturedEvent::Key { code, pressed } => {
|
||||
self.manager.send_key((code as u32) + 8, pressed);
|
||||
}
|
||||
input_capture::CapturedEvent::PointerButton { button, pressed } => {}
|
||||
input_capture::CapturedEvent::PointerMotion { dx, dy } => {}
|
||||
input_capture::CapturedEvent::PointerAxis {
|
||||
horizontal_v120,
|
||||
vertical_v120,
|
||||
} => {}
|
||||
input_capture::CapturedEvent::UngrabbedAll => {
|
||||
self.manager.release_all_keys();
|
||||
self.has_input_focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_input_focus(&mut self, has_focus: bool) {
|
||||
let Some(input_capture) = self.input_capture.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let res = input_capture
|
||||
.set_grabbed(has_focus)
|
||||
.log_err("Could not grab input.");
|
||||
|
||||
if res.is_ok() && !self.grab_toast_sent {
|
||||
self.grab_toast_sent = true;
|
||||
//TODO: localize
|
||||
let _ = DbusConnector::notify_send(
|
||||
"WayVR has your keyboard and mouse!",
|
||||
"Ctrl+Alt+Del to release",
|
||||
1,
|
||||
5000,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
self.has_input_focus = has_focus;
|
||||
}
|
||||
|
||||
pub fn send_mouse_move(
|
||||
&mut self,
|
||||
target: PointerFocusTarget,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use wlx_common::windowing::{OverlayWindowState, Positioning};
|
|||
|
||||
use crate::{
|
||||
state::{AppSession, AppState, ScreenMeta},
|
||||
subsystem::input::KeyboardFocus,
|
||||
subsystem::input::InputFocus,
|
||||
windowing::{
|
||||
backend::OverlayBackend,
|
||||
window::{OverlayCategory, OverlayWindowConfig},
|
||||
|
|
@ -56,7 +56,7 @@ fn create_screen_from_backend(
|
|||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
|
||||
input_focus: Some(InputFocus::PhysicalScreen),
|
||||
..OverlayWindowConfig::from_backend(backend)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ use crate::{
|
|||
},
|
||||
overlays::screen::capture::ScreenPipeline,
|
||||
state::{self, AppState},
|
||||
subsystem::{hid::WheelDelta, input::KeyboardFocus},
|
||||
subsystem::{hid::WheelDelta, input::InputFocus},
|
||||
windowing::{
|
||||
OverlayID, OverlaySelector,
|
||||
backend::{
|
||||
|
|
@ -112,7 +112,7 @@ pub fn create_wl_window_overlay(
|
|||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
keyboard_focus: Some(KeyboardFocus::WayVR),
|
||||
input_focus: Some(InputFocus::WayVR),
|
||||
category: OverlayCategory::WayVR,
|
||||
show_on_spawn: true,
|
||||
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ use crate::{
|
|||
subsystem::{
|
||||
clipboard::{self, ClipboardProvider},
|
||||
hid::VirtualKey,
|
||||
input::KeyboardFocus,
|
||||
input::InputFocus,
|
||||
whisper_stt::WhisperStt,
|
||||
},
|
||||
windowing::{
|
||||
|
|
@ -51,14 +51,14 @@ impl WhisperState {
|
|||
return false;
|
||||
};
|
||||
|
||||
match app.hid_provider.keyboard_focus {
|
||||
KeyboardFocus::WayVR => {
|
||||
match app.hid_provider.get_input_focus() {
|
||||
InputFocus::WayVR => {
|
||||
if let Some(wvr) = app.wvr_server.as_mut() {
|
||||
wvr.set_clipboard(text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
KeyboardFocus::PhysicalScreen => {
|
||||
InputFocus::PhysicalScreen => {
|
||||
if let Some(clip) = self.clipboard_provider.as_mut() {
|
||||
clip.set_clipboard_utf8(text);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ 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 {
|
||||
pub enum InputFocus {
|
||||
PhysicalScreen,
|
||||
WayVR, // (wayland window id data is handled internally),
|
||||
}
|
||||
|
||||
pub struct HidWrapper {
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
input_focus: InputFocus,
|
||||
pub inner: Box<dyn HidProvider>,
|
||||
pub keymap: Option<XkbKeymap>,
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ impl HidWrapper {
|
|||
|
||||
(
|
||||
Self {
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
input_focus: InputFocus::PhysicalScreen,
|
||||
inner: provider,
|
||||
keymap: None,
|
||||
},
|
||||
|
|
@ -40,15 +40,37 @@ impl HidWrapper {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn set_input_focus(
|
||||
&mut self,
|
||||
wvr_server: Option<&mut WvrServerState>,
|
||||
value: InputFocus,
|
||||
) -> bool {
|
||||
if self.input_focus != value {
|
||||
self.input_focus = value;
|
||||
|
||||
if let Some(wvr_server) = wvr_server {
|
||||
wvr_server.set_input_focus(matches!(value, InputFocus::WayVR));
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_focus(&self) -> InputFocus {
|
||||
self.input_focus
|
||||
}
|
||||
|
||||
pub fn send_key_routed(
|
||||
&self,
|
||||
wvr_server: Option<&mut WvrServerState>,
|
||||
key: VirtualKey,
|
||||
down: bool,
|
||||
) {
|
||||
match self.keyboard_focus {
|
||||
KeyboardFocus::PhysicalScreen => self.inner.send_key(key, down),
|
||||
KeyboardFocus::WayVR => {
|
||||
match self.input_focus {
|
||||
InputFocus::PhysicalScreen => self.inner.send_key(key, down),
|
||||
InputFocus::WayVR => {
|
||||
if let Some(wvr_server) = wvr_server {
|
||||
wvr_server.send_key(key as u32, down);
|
||||
}
|
||||
|
|
@ -73,9 +95,9 @@ impl HidWrapper {
|
|||
}
|
||||
|
||||
pub fn set_modifiers_routed(&mut self, wvr_server: Option<&mut WvrServerState>, mods: u8) {
|
||||
match self.keyboard_focus {
|
||||
KeyboardFocus::PhysicalScreen => self.inner.set_modifiers(mods),
|
||||
KeyboardFocus::WayVR => {
|
||||
match self.input_focus {
|
||||
InputFocus::PhysicalScreen => self.inner.set_modifiers(mods),
|
||||
InputFocus::WayVR => {
|
||||
if let Some(wvr_server) = wvr_server {
|
||||
wvr_server.set_modifiers(mods);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use wlx_common::windowing::{OverlayWindowState, Positioning};
|
|||
|
||||
use crate::{
|
||||
state::AppState,
|
||||
subsystem::input::KeyboardFocus,
|
||||
subsystem::input::InputFocus,
|
||||
windowing::{
|
||||
backend::{FrameMeta, OverlayBackend, RenderResources, ShouldRender},
|
||||
snap_upright,
|
||||
|
|
@ -76,7 +76,7 @@ pub struct OverlayWindowConfig {
|
|||
/// Order to draw overlays in. Overlays with higher numbers will be drawn over ones with lower numbers.
|
||||
pub z_order: u32,
|
||||
/// If set, hovering this overlay will cause the HID provider to switch focus.
|
||||
pub keyboard_focus: Option<KeyboardFocus>,
|
||||
pub input_focus: Option<InputFocus>,
|
||||
/// Category of the overlay, used by toolbox on the watch.
|
||||
pub category: OverlayCategory,
|
||||
/// Should the overlay be displayed on the next frame?
|
||||
|
|
@ -104,7 +104,7 @@ impl OverlayWindowConfig {
|
|||
},
|
||||
active_state: None,
|
||||
z_order: 0,
|
||||
keyboard_focus: None,
|
||||
input_focus: None,
|
||||
category: OverlayCategory::Internal,
|
||||
show_on_spawn: false,
|
||||
global: false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue