input capture #1 - keyboard

This commit is contained in:
galister 2026-07-15 10:47:03 +09:00
parent a29451ceaa
commit 968cb230e3
11 changed files with 796 additions and 36 deletions

52
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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