alt-tabbing and stuff

This commit is contained in:
galister 2026-07-16 00:52:57 +09:00
parent 4cd238e487
commit 7efcb3cc84
10 changed files with 211 additions and 53 deletions

View File

@ -7,7 +7,7 @@ use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
use idmap_derive::IntegerId;
use smallvec::{SmallVec, smallvec};
use strum::AsRefStr;
use strum::{AsRefStr, EnumIs};
use wayvr_ipc::packet_client::{HandsfreeAction, HandsfreeParams};
use wlx_common::common::LeftRight;
use wlx_common::windowing::{OverlayWindowState, Positioning};
@ -58,13 +58,22 @@ pub enum TrackedDeviceRole {
Tracker,
}
#[derive(Debug, Clone, Copy, EnumIs)]
pub enum FocusPickState {
None,
/// aiming to pick the focus
Aiming,
/// picking the focus. this will be consumed on valid on_hover
Picking,
}
pub struct InputState {
pub hmd: Affine3A,
pub ipd: f32,
pub pointers: [Pointer; 2],
pub devices: Vec<TrackedDevice>,
pub handsfree_state: PointerState,
pub picking_focus: bool,
pub picking_focus: FocusPickState,
processes: Vec<Child>,
}
@ -77,7 +86,7 @@ impl InputState {
devices: Vec::new(),
processes: Vec::new(),
handsfree_state: PointerState::default(),
picking_focus: false,
picking_focus: FocusPickState::None,
}
}
@ -210,6 +219,9 @@ fn debug_print_hand(hand: &Pointer) {
if hand.now.grab != hand.before.grab {
log::debug!("Hand {}: grab {}", hand.idx, hand.now.grab);
}
if hand.now.grab_float != hand.before.grab_float {
log::debug!("Hand {}: grab {}", hand.idx, hand.now.grab);
}
if hand.now.alt_click != hand.before.alt_click {
log::debug!("Hand {}: alt_click {}", hand.idx, hand.now.alt_click);
}
@ -316,6 +328,7 @@ pub struct PointerState {
pub scroll_y: f32,
pub click: bool,
pub grab: bool,
pub grab_float: bool,
pub alt_click: bool,
pub show_hide: bool,
pub toggle_dashboard: bool,
@ -411,6 +424,7 @@ fn populate_lines(
fn update_focus(app: &mut AppState, overlay_input_focus: Option<InputFocus>) {
if let Some(f) = &overlay_input_focus
&& app.input_state.picking_focus.is_none()
&& app
.hid_provider
.set_input_focus(app.wvr_server.as_mut(), *f)
@ -538,8 +552,11 @@ where
let hovered_state = hovered.config.active_state.as_mut().unwrap();
let grab_float = pointer.now.grab_float;
let grab_start = pointer.now.grab && !pointer.before.grab;
// grab
if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable {
if grab_start && hovered_state.grabbable {
update_focus(app, hovered.config.input_focus);
start_grab(
idx,
@ -549,6 +566,7 @@ where
hovered_state,
app,
edit_mode,
grab_float,
);
log::debug!("Hand {}: grabbed {}", hit.pointer, hovered.config.name);
return (
@ -762,15 +780,19 @@ fn start_grab(
state: &mut OverlayWindowState,
app: &mut AppState,
edit_mode: bool,
grab_float: bool,
) {
let pointer = &mut app.input_state.pointers[idx];
// Grab anchor if:
// - grabbed overlay is Anchored
// - not in editmode
// - not using grab_float
// - grabbing with one hand. (grabbing with the 2nd hand will grab the individual overlay instead)
let grab_anchor =
!edit_mode && !app.anchor_grabbed && matches!(state.positioning, Positioning::Anchored);
let grab_anchor = !edit_mode
&& !grab_float
&& !app.anchor_grabbed
&& matches!(state.positioning, Positioning::Anchored);
let relative_grab_transform = if grab_anchor {
app.anchor

View File

@ -219,6 +219,9 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> {
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Global(task) => {
task(&mut app);
}
TaskType::Input(task) => {
app.input_state.handle_task(task);
}

View File

@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use glam::{Affine3A, Quat, Vec3, bool};
use glam::{Affine3A, FloatExt, Quat, Vec3, bool};
use libmonado::{self as mnd, DeviceLogic};
use openxr::{self as xr, Quaternionf, Vector2f, Vector3f};
use wlx_common::{
@ -238,13 +238,18 @@ impl OpenXrInputSource {
let mut any_tracked = false;
let old_handsfree = app.session.config.handsfree_pointer;
if app.input_state.picking_focus {
if !app.input_state.picking_focus.is_none() {
app.session.config.handsfree_pointer = app.session.config.handsfree_alt_tab.into();
app.input_state.handsfree_state.scroll_x =
app.input_state.handsfree_state.scroll_x.lerp(0.0, 0.7);
app.input_state.handsfree_state.scroll_y =
app.input_state.handsfree_state.scroll_y.lerp(0.0, 0.7);
let ptr1 = &mut app.input_state.pointers[1];
ptr1.before = ptr1.now;
ptr1.now = PointerState::default();
ptr1.tracked = false
ptr1.tracked = false;
} else {
for i in 0..2 {
let pointer = &mut app.input_state.pointers[i];
@ -399,6 +404,7 @@ impl OpenXrPointer {
// input from wayvrctl
pointer.now.click = handsfree_state.click;
pointer.now.grab = handsfree_state.grab;
pointer.now.grab_float = handsfree_state.grab_float;
pointer.now.click_modifier_right = handsfree_state.click_modifier_right;
pointer.now.click_modifier_middle = handsfree_state.click_modifier_middle;
pointer.now.scroll_y = handsfree_state.scroll_y;

View File

@ -473,6 +473,9 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> {
app.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Global(task) => {
task(&mut app);
}
TaskType::Input(task) => {
app.input_state.handle_task(task);
}

View File

@ -128,8 +128,11 @@ pub enum OverlayTask {
Drop(OverlaySelector),
}
pub type GlobalTask = dyn FnOnce(&mut AppState) + Send;
#[allow(dead_code)]
pub enum TaskType {
Global(Box<GlobalTask>),
Input(InputTask),
Overlay(OverlayTask),
Playspace(PlayspaceTask),

View File

@ -34,9 +34,8 @@ pub enum KeyCombo {
Super2,
Super3,
Super4,
SuperTab,
AltF4,
AltTab,
AltShiftTab,
/// Always consumed by InputCapture internally
CtrlAltDel,
}
@ -61,7 +60,8 @@ pub enum CapturedEvent {
horizontal_v120: i32,
vertical_v120: i32,
},
UngrabbedAll,
Grabbed,
Ungrabbed,
KeyCombo {
combo: KeyCombo,
pressed: bool,
@ -326,6 +326,7 @@ fn worker_main(
}
let mut grabbed = false;
let mut pending_grab = false;
let mut watchdog = Instant::now() + WATCHDOG_TIMEOUT;
let mut emergency_ungrab = false;
let mut pointer_accel = PointerAccelConfig::default();
@ -336,6 +337,8 @@ fn worker_main(
loop {
match command_rx.try_recv() {
Ok(Command::SetGrabbed(requested)) => {
pending_grab = false;
let _ = if requested {
match restricted_state.borrow_mut().set_grabbed(true) {
Ok(()) => {
@ -409,9 +412,10 @@ fn worker_main(
&event_tx,
);
grabbed = false;
pending_grab = false;
emergency_ungrab = false;
if event_tx.send(CapturedEvent::UngrabbedAll).is_err() {
if event_tx.send(CapturedEvent::Ungrabbed).is_err() {
break 'worker;
}
continue;
@ -459,6 +463,8 @@ fn worker_main(
match process_libinput_event(
event,
grabbed,
true,
&mut pending_grab,
&event_tx,
&mut runtime_devices,
&mut pointer_devices,
@ -473,6 +479,27 @@ fn worker_main(
}
}
if !grabbed && pending_grab && all_keyboard_keys_released(&runtime_devices) {
pending_grab = false;
match restricted_state.borrow_mut().set_grabbed(true) {
Ok(()) => {
grabbed = true;
watchdog = Instant::now() + WATCHDOG_TIMEOUT;
// should not be needed but sanity
clear_transient_state(&mut runtime_devices);
if event_tx.send(CapturedEvent::Grabbed).is_err() {
break 'worker;
}
}
Err(error) => {
log::warn!("Could not grab input devices after Super+Tab: {error}");
}
}
}
if emergency_ungrab {
force_emergency_ungrab(
&mut libinput,
@ -485,7 +512,7 @@ fn worker_main(
grabbed = false;
emergency_ungrab = false;
if event_tx.send(CapturedEvent::UngrabbedAll).is_err() {
if event_tx.send(CapturedEvent::Ungrabbed).is_err() {
break 'worker;
}
}
@ -494,7 +521,9 @@ fn worker_main(
fn process_libinput_event(
event: Event,
emit: bool,
grabbed: bool,
allow_deferred_grab: bool,
pending_grab: &mut bool,
event_tx: &Sender<CapturedEvent>,
runtime_devices: &mut HashMap<LibinputDevice, RuntimeDeviceState>,
pointer_devices: &mut HashSet<LibinputDevice>,
@ -504,7 +533,7 @@ fn process_libinput_event(
Event::Device(event) => {
handle_device_event(event, runtime_devices, pointer_devices, pointer_accel);
}
Event::Keyboard(KeyboardEvent::Key(event)) if emit => {
Event::Keyboard(KeyboardEvent::Key(event)) => {
let code_u32 = event.key();
let Ok(code) = u16::try_from(code_u32) else {
log::warn!("Ignoring out-of-range Linux key code {code_u32}");
@ -521,16 +550,26 @@ fn process_libinput_event(
state.pressed_keys.remove(&code);
}
if !grabbed {
// while ungrabbed, SuperTab will arm an automatic grab
if allow_deferred_grab
&& key_combo_is_pressed(KeyCombo::SuperTab, &state.pressed_keys)
{
*pending_grab = true;
}
return ProcessResult::Continue;
}
if pressed && key_combo_is_pressed(KeyCombo::CtrlAltDel, &state.pressed_keys) {
log::info!("Ctrl+Alt+Del pressed, ungrabbing all input devices");
return ProcessResult::EmergencyUngrab;
}
// emit press/release whenever a combo's complete state changes.
// while grabbed, these combos get emitted as normal
for combo in [
KeyCombo::SuperTab,
KeyCombo::AltF4,
KeyCombo::AltShiftTab,
KeyCombo::AltTab,
KeyCombo::Super1,
KeyCombo::Super2,
KeyCombo::Super3,
@ -564,7 +603,7 @@ fn process_libinput_event(
return ProcessResult::ReceiverGone;
}
}
Event::Pointer(PointerEvent::Motion(event)) if emit => {
Event::Pointer(PointerEvent::Motion(event)) if grabbed => {
let dx = event.dx();
let dy = event.dy();
let dx_raw = event.dx_unaccelerated();
@ -582,7 +621,7 @@ fn process_libinput_event(
return ProcessResult::ReceiverGone;
}
}
Event::Pointer(PointerEvent::Button(event)) if emit => {
Event::Pointer(PointerEvent::Button(event)) if grabbed => {
let pressed = event.button_state() == ButtonState::Pressed;
if event_tx
.send(CapturedEvent::PointerButton {
@ -594,7 +633,7 @@ fn process_libinput_event(
return ProcessResult::ReceiverGone;
}
}
Event::Pointer(PointerEvent::ScrollWheel(event)) if emit => {
Event::Pointer(PointerEvent::ScrollWheel(event)) if grabbed => {
let horizontal = if event.has_axis(Axis::Horizontal) {
event.scroll_value_v120(Axis::Horizontal)
} else {
@ -680,10 +719,14 @@ fn drain_pending_libinput_events(
event_tx: &Sender<CapturedEvent>,
) {
let _ = libinput.dispatch();
let mut ignored = false;
for event in libinput {
let _ = process_libinput_event(
event,
false,
false,
&mut ignored,
event_tx,
runtime_devices,
pointer_devices,
@ -836,14 +879,10 @@ fn key_combo_is_pressed(combo: KeyCombo, pressed: &HashSet<u16>) -> bool {
let meta =
pressed.contains(&KeyCode::KEY_LEFTMETA.0) || pressed.contains(&KeyCode::KEY_RIGHTMETA.0);
let shift =
pressed.contains(&KeyCode::KEY_LEFTSHIFT.0) || pressed.contains(&KeyCode::KEY_RIGHTSHIFT.0);
match combo {
KeyCombo::AltF4 => alt && pressed.contains(&KeyCode::KEY_F4.0),
KeyCombo::AltShiftTab => alt && shift && pressed.contains(&KeyCode::KEY_TAB.0),
KeyCombo::AltTab => alt && pressed.contains(&KeyCode::KEY_TAB.0),
KeyCombo::CtrlAltDel => ctrl && alt && pressed.contains(&KeyCode::KEY_DELETE.0),
KeyCombo::SuperTab => meta && pressed.contains(&KeyCode::KEY_TAB.0),
KeyCombo::Super1 => meta && pressed.contains(&KeyCode::KEY_1.0),
KeyCombo::Super2 => meta && pressed.contains(&KeyCode::KEY_2.0),
KeyCombo::Super3 => meta && pressed.contains(&KeyCode::KEY_3.0),
@ -861,3 +900,11 @@ fn with_device_context(action: &str, path: &Path, error: io::Error) -> io::Error
format!("failed to {action} {}: {error}", path.display()),
)
}
fn all_keyboard_keys_released(
runtime_devices: &HashMap<LibinputDevice, RuntimeDeviceState>,
) -> bool {
runtime_devices
.values()
.all(|state| state.pressed_keys.is_empty())
}

View File

@ -77,12 +77,13 @@ use crate::{
state::AppState,
subsystem::{
dbus::DbusConnector,
hid::{MODS_TO_KEYS, WheelDelta},
hid::{self, MODS_TO_KEYS, WheelDelta},
input::{HidWrapper, InputFocus},
},
windowing::{OverlayID, OverlaySelector, backend::OverlayEventData},
};
pub use hit_test::{WvrHitContext, WvrHitTarget, build_hit_context};
pub use hit_test::{WvrHitTarget, build_hit_context};
#[derive(Debug, Clone)]
pub struct WaylandEnv {
@ -322,6 +323,7 @@ impl WvrServerState {
input_state: &app.input_state,
tasks: &mut tasks,
signals: &app.wayvr_signals,
hid_wrapper: &mut app.hid_provider,
});
// Tick all child processes
@ -637,7 +639,11 @@ impl WvrServerState {
wvr_server.manager.cleanup_handles();
}
wvr_server.process_input_capture(&mut app.input_state, &mut app.tasks);
wvr_server.process_input_capture(
&mut app.input_state,
&mut app.tasks,
&mut app.hid_provider,
);
wvr_server.ticks += 1;
@ -752,10 +758,8 @@ impl WvrServerState {
&mut self,
input_state: &mut InputState,
tasks: &mut TaskContainer,
hid_wrapper: &mut HidWrapper,
) {
if !self.has_input_focus {
return;
}
let Some(input_capture) = self.input_capture.as_mut() else {
return;
};
@ -766,13 +770,47 @@ impl WvrServerState {
for ev in input_capture.drain_events() {
match ev {
input_capture::CapturedEvent::Key { code, pressed } => {
self.manager.send_key((code as u32) + 8, pressed);
let vk = (code as u32) + 8;
if input_state.picking_focus.is_aiming() {
if (hid::VirtualKey::Q as u32) == vk {
input_state.handsfree_state.grab = pressed;
input_state.handsfree_state.grab_float = pressed;
}
if (hid::VirtualKey::A as u32) == vk {
input_state.handsfree_state.grab = pressed;
}
if (hid::VirtualKey::W as u32) == vk {
input_state.handsfree_state.click = pressed;
}
if (hid::VirtualKey::E as u32) == vk && pressed {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
}
if (hid::VirtualKey::D as u32) == vk && pressed {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
}
} else {
self.manager.send_key(vk, pressed);
}
}
input_capture::CapturedEvent::PointerButton { button, pressed } => {
let Some(mouse_index) = Self::button_to_mouse_index(button) else {
continue;
};
self.manager.send_pointer_button(mouse_index, pressed);
if input_state.picking_focus.is_aiming() {
match mouse_index {
MouseIndex::Left => input_state.handsfree_state.click = pressed,
MouseIndex::Right => {
input_state.handsfree_state.click = pressed;
input_state.handsfree_state.click_modifier_right = pressed;
}
MouseIndex::Center if pressed => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
}
_ => {}
}
} else {
self.manager.send_pointer_button(mouse_index, pressed);
}
}
input_capture::CapturedEvent::PointerMotion {
dx,
@ -794,9 +832,16 @@ impl WvrServerState {
x: horizontal_v120 as f32,
y: vertical_v120 as f32,
};
self.manager.send_pointer_axis_wheel_raw(delta);
if input_state.picking_focus.is_aiming() {
input_state.handsfree_state.scroll_y = (vertical_v120 as f32) * -0.12;
} else {
self.manager.send_pointer_axis_wheel_raw(delta);
}
}
input_capture::CapturedEvent::UngrabbedAll => {
input_capture::CapturedEvent::Grabbed => {
hid_wrapper.set_input_focus(Some(self), InputFocus::WayVR);
}
input_capture::CapturedEvent::Ungrabbed => {
self.manager.release_all_keys();
self.has_input_focus = false;
self.wm.keyboard_focus = None;
@ -808,20 +853,33 @@ impl WvrServerState {
self.close_window(window_handle);
}
}
input_capture::KeyCombo::AltTab => input_state.picking_focus = pressed,
input_capture::KeyCombo::AltShiftTab => {
input_state.handsfree_state.grab = pressed
input_capture::KeyCombo::SuperTab => {
if pressed {
input_state.picking_focus = super::input::FocusPickState::Aiming;
} else {
input_state.picking_focus = super::input::FocusPickState::Picking;
tasks.enqueue_at(
TaskType::Global(Box::new(|app| {
app.input_state.picking_focus =
super::input::FocusPickState::None;
let _ = std::mem::take(&mut app.input_state.handsfree_state);
})),
Instant::now() + Duration::from_millis(33),
);
}
}
input_capture::KeyCombo::Super1 => {
input_capture::KeyCombo::Super1 if pressed => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(0)))
}
input_capture::KeyCombo::Super2 => {
input_capture::KeyCombo::Super2 if pressed => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(1)))
}
input_capture::KeyCombo::Super3 => {
input_capture::KeyCombo::Super3 if pressed => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(2)))
}
input_capture::KeyCombo::Super4 => {
input_capture::KeyCombo::Super4 if pressed => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(3)))
}
_ => {}
@ -881,6 +939,7 @@ impl WvrServerState {
}
}
/// Use HidWrapper::set_input_focus instead!!!
pub fn set_input_focus(&mut self, has_focus: bool) {
let Some(input_capture) = self.input_capture.as_mut() else {
return;
@ -982,7 +1041,7 @@ impl WvrServerState {
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
force_focus: bool,
picking_focus: bool,
) {
if self.mouse_freeze > Instant::now() {
return;
@ -990,7 +1049,7 @@ impl WvrServerState {
let global_pos = DVec2::from(global_pos);
let (focus, focus_keyboard) = self.get_mouse_focus(target, hover_window, force_focus);
let (focus, focus_keyboard) = self.get_mouse_focus(target, hover_window, picking_focus);
if focus_keyboard.is_some() {
self.manager.seat_keyboard.set_focus(
&mut self.manager.state,

View File

@ -1,5 +1,6 @@
use crate::backend::wayvr::{self, WvrServerState};
use crate::subsystem::input::{HidWrapper, InputFocus};
use crate::{
backend::input::InputState,
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
@ -86,6 +87,7 @@ pub struct TickParams<'a> {
pub tasks: &'a mut Vec<wayvr::TickTask>,
pub signals: &'a SyncEventQueue<WayVRSignal>,
pub input_state: &'a InputState,
pub hid_wrapper: &'a mut HidWrapper,
}
pub fn gen_args_vec(input: &str) -> Vec<&str> {
@ -335,7 +337,14 @@ impl Connection {
}
fn handle_wvr_input_capture(params: &mut TickParams, grab: bool) {
params.wvr_server.set_input_focus(grab);
let val = if grab {
InputFocus::WayVR
} else {
InputFocus::PhysicalScreen
};
params
.hid_wrapper
.set_input_focus(Some(params.wvr_server), val);
}
fn handle_wlx_device_haptics(

View File

@ -9,7 +9,7 @@ use wlx_capture::{WlxCapture, frame::Transform};
use crate::{
backend::{
XrBackend,
input::{HoverResult, PointerHit, PointerMode},
input::{self, HoverResult, PointerHit, PointerMode},
},
overlays::screen::capture::MyFirstDmaExporter,
state::AppState,
@ -302,7 +302,9 @@ impl OverlayBackend for ScreenBackend {
#[cfg(debug_assertions)]
log::trace!("Hover: {:?}", hit.uv);
if app.input_state.picking_focus {
let pick_now = app.input_state.picking_focus.is_picking();
if pick_now {
app.input_state.picking_focus = input::FocusPickState::None;
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::PhysicalScreen);
}

View File

@ -845,7 +845,9 @@ impl OverlayBackend for WvrWindowBackend {
self.panel_hovered = false;
}
if app.input_state.picking_focus {
let pick_now = app.input_state.picking_focus.is_picking();
if pick_now {
app.input_state.picking_focus = input::FocusPickState::None;
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::WayVR);
}
@ -855,7 +857,7 @@ impl OverlayBackend for WvrWindowBackend {
PointerFocusTarget::Surface { surface, origin },
global_pos,
self.window,
app.input_state.picking_focus,
pick_now,
);
HoverResult::consume()
@ -866,7 +868,9 @@ impl OverlayBackend for WvrWindowBackend {
self.panel_hovered = false;
}
if app.input_state.picking_focus {
let pick_now = app.input_state.picking_focus.is_picking();
if pick_now {
app.input_state.picking_focus = input::FocusPickState::None;
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::WayVR);
}
@ -876,7 +880,7 @@ impl OverlayBackend for WvrWindowBackend {
PointerFocusTarget::Toplevel,
pos,
self.window,
app.input_state.picking_focus,
pick_now,
);
HoverResult::consume()