handsfree alt-tab

This commit is contained in:
galister 2026-07-15 21:46:50 +09:00
parent 0b8040b9ae
commit 4cd238e487
24 changed files with 176 additions and 101 deletions

View File

@ -74,8 +74,6 @@
"NONE": "Keine",
"HMD_PINCH": "HMD + Kneifen",
"EYE_PINCH": "Auge + Kneifen",
"EYE_ONLY": "Nur Auge",
"HMD_ONLY": "Nur HMD",
"NONE_INPUT_HELP": "Maus und Tastatur nicht emulieren.\nEs wird nicht möglich sein, mit Displays zu interagieren.",
"UINPUT": "Gerätesimulation",
"UINPUT_HELP": "Verwendet das uinput-Modul, um ein Gerät zu emulieren.\nIhr Benutzer muss in der Gruppe „input“ sein.\nBekannt dafür, auf den meisten Desktops gut zu funktionieren.",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "Aus Favoriten entfernen",
"EMPTY": "Leer",
"PINNED_APPS": "Angepinnte Anwendungen"
}
}

View File

@ -123,6 +123,8 @@
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
"HANDSFREE_POINTER": "Handsfree mode",
"HANDSFREE_POINTER_HELP": "Input to use when motion\ncontrollers are unavailable.\nLeft pinch is grab, right is click.",
"HANDSFREE_ALT_TAB": "Handsfree alt-tab aim",
"HANDSFREE_ALT_TAB_HELP": "Hold alt-tab, aim at the desired window, then release to switch focus.",
"HIDE_GRAB_HELP": "Hide grab help",
"HIDE_USERNAME": "Hide username",
"INPUT_EMULATION_METHOD": "Input emulation method",
@ -148,9 +150,9 @@
"OPTION": {
"AUTO": "Automatic",
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
"EYE_ONLY": "Eye only",
"EYE_ONLY": "Eye laser",
"EYE_PINCH": "Eye + pinch",
"HMD_ONLY": "HMD only",
"HMD_ONLY": "HMD laser",
"HMD_PINCH": "HMD + pinch",
"NONE": "None",
"NONE_INPUT_HELP": "Do not emulate mouse and keyboard.\nWill not be able to interact with screens.",

View File

@ -74,8 +74,6 @@
"NONE": "Ninguno",
"HMD_PINCH": "HMD + pellizco",
"EYE_PINCH": "Ojo + pellizco",
"EYE_ONLY": "Solo ojo",
"HMD_ONLY": "Solo HMD",
"NONE_INPUT_HELP": "No emular ratón y teclado.\nNo podrá interactuar con las pantallas.",
"UINPUT": "Emulación de dispositivo",
"UINPUT_HELP": "Utiliza el módulo uinput para emular un dispositivo.\nSu usuario debe estar en el grupo input.\nSe sabe que funciona bien en la mayoría de los escritorios.",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "Eliminar de favoritos",
"EMPTY": "Vacío",
"PINNED_APPS": "Aplicaciones ancladas"
}
}

View File

@ -76,8 +76,6 @@
"NONE": "Nessuno",
"HMD_PINCH": "HMD + pizzico",
"EYE_PINCH": "Occhio + pizzico",
"EYE_ONLY": "Solo occhio",
"HMD_ONLY": "Solo HMD",
"NONE_INPUT_HELP": "Non emulare mouse e tastiera.\nNon sarà possibile interagire con gli schermi.",
"UINPUT": "Emulazione dispositivo",
"UINPUT_HELP": "Utilizza il modulo uinput per emulare un dispositivo.\nL'utente deve far parte del gruppo input.\nRisultato ottimale su quasi tutti i desktop.",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "Rimuovi dai preferiti",
"EMPTY": "Vuoto",
"PINNED_APPS": "Applicazioni fissate"
}
}

View File

@ -74,8 +74,6 @@
"NONE": "なし",
"HMD_PINCH": "HMD + ピンチ",
"EYE_PINCH": "つまんで目を合わせる",
"EYE_ONLY": "視野のみ",
"HMD_ONLY": "HMDのみ",
"NONE_INPUT_HELP": "マウスとキーボードをエミュレートしません。\nディスプレイを操作できなくなります。",
"UINPUT": "デバイス・エミュレーション",
"UINPUT_HELP": "uinput モジュールを使用してデバイスをエミュレートします。\nユーザーが input グループに属している必要があります。\nほとんどのデスクトップ環境で正常に動作することが確認されています。",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "お気に入りから削除",
"EMPTY": "空",
"PINNED_APPS": "ピン留めされたアプリケーション"
}
}

View File

@ -69,8 +69,6 @@
"NONE": "Brak",
"HMD_PINCH": "HMD + ściśnięcie placami",
"EYE_PINCH": "Ściśnięcie palcami + oko",
"EYE_ONLY": "Tylko oko",
"HMD_ONLY": "Tylko HMD",
"NONE_INPUT_HELP": "Nie emuluj myszy ani klawiatury.\nBrak możliwości interakcji z ekranami.",
"UINPUT": "Emulacja urządzenia",
"UINPUT_HELP": "Używa modułu uinput do emulowania urządzenia.\nTwoje konto użytkownika musi należeć do grupy input.\nWiadomo, że działa dobrze na większości komputerów stacjonarnych.",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "Usuń z ulubionych",
"EMPTY": "Puste",
"PINNED_APPS": "Przypięte aplikacje"
}
}

View File

@ -76,8 +76,6 @@
"NONE": "无",
"HMD_PINCH": "HMD + 捏合",
"EYE_PINCH": "眼睛 + 捏合",
"EYE_ONLY": "仅眼球",
"HMD_ONLY": "仅限头显",
"NONE_INPUT_HELP": "不模拟鼠标和键盘。\n将无法与屏幕进行交互。",
"UINPUT": "设备模拟",
"UINPUT_HELP": "使用 uinput 模块来模拟设备。\n您的用户必须处于 input 用户组中。\n已知在大多数桌面上运行良好。",
@ -315,4 +313,4 @@
"REMOVE_FROM_FAVOURITES": "从收藏中移除",
"EMPTY": "空",
"PINNED_APPS": "已固定应用程序"
}
}

View File

@ -303,6 +303,7 @@ pub(crate) enum SettingType {
EnableWatch,
FocusFollowsMouseMode,
GridOpacity,
HandsfreeAltTab,
HandsfreePointer,
HideGrabHelp,
HideUsername,
@ -431,6 +432,9 @@ impl SettingType {
config.keyboard_middle_click_mode =
wlx_common::config::AltModifier::from_str(value).expect("Invalid enum value!")
}
Self::HandsfreeAltTab => {
config.handsfree_alt_tab = wlx_common::config::HandsfreeAltTab::from_str(value).expect("Invalid enum value!")
}
Self::HandsfreePointer => {
config.handsfree_pointer = wlx_common::config::HandsfreePointer::from_str(value).expect("Invalid enum value!")
}
@ -447,6 +451,7 @@ impl SettingType {
Self::InputEmulationMethod => Self::get_enum_title_inner(config.input_emulation_method),
Self::KeyboardMiddleClick => Self::get_enum_title_inner(config.keyboard_middle_click_mode),
Self::HandsfreePointer => Self::get_enum_title_inner(config.handsfree_pointer),
Self::HandsfreeAltTab => Self::get_enum_title_inner(config.handsfree_alt_tab),
Self::Language => match &config.language {
Some(lang) => Self::get_enum_title_inner(*lang),
None => Translation::from_translation_key("APP_SETTINGS.OPTION.AUTO"),
@ -488,6 +493,7 @@ impl SettingType {
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
Self::GridOpacity => Ok("APP_SETTINGS.GRID_OPACITY"),
Self::HandsfreeAltTab => Ok("APP_SETTINGS.HANDSFREE_ALT_TAB"),
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
@ -541,6 +547,7 @@ impl SettingType {
Self::DefaultOverlayScale => Some("APP_SETTINGS.DEFAULT_OVERLAY_SCALE_HELP"),
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"),
Self::HandsfreeAltTab => Some("APP_SETTINGS.HANDSFREE_ALT_TAB_HELP"),
Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"),
Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"),
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),

View File

@ -97,6 +97,7 @@ impl State {
}
options_dropdown::<wlx_common::config::AltModifier>(par.mp, c, &SettingType::KeyboardMiddleClick)?;
options_dropdown::<wlx_common::config::HandsfreePointer>(par.mp, c, &SettingType::HandsfreePointer)?;
options_dropdown::<wlx_common::config::HandsfreeAltTab>(par.mp, c, &SettingType::HandsfreeAltTab)?;
options_checkbox(par.mp, c, SettingType::FocusFollowsMouseMode)?;
options_checkbox(par.mp, c, SettingType::LeftHandedMouse)?;
options_checkbox(par.mp, c, SettingType::AllowSliding)?;

View File

@ -17,9 +17,7 @@
"TITLE": "Freihändig-Modus",
"NONE": "Aus",
"HMD": "HMD + Kneifen",
"EYE_TRACKING": "Auge + Kneifen",
"HMD_ONLY": "Nur HMD",
"EYE_ONLY": "Nur Augen"
"EYE_TRACKING": "Auge + Kneifen"
}
},
"WATCH": {
@ -136,4 +134,4 @@
"WAITING_FOR_USER": "Überprüfen Sie Ihren Desktop auf ein Pop-up zur Bildschirmübertragung.",
"FAIL": "Bildschirmübertragungsquelle konnte nicht initialisiert werden."
}
}
}

View File

@ -11,10 +11,10 @@
"EDIT_MODE_TOGGLE": "Toggle edit mode",
"FORCE_CLOSE_APP": "Force close app",
"HANDSFREE": {
"EYE_ONLY": "Eye only",
"EYE_ONLY": "Eye laser",
"EYE_TRACKING": "Eye + pinch",
"HMD": "HMD + pinch",
"HMD_ONLY": "HMD only",
"HMD_ONLY": "HMD laser",
"NONE": "Off",
"TITLE": "Handsfree mode"
},

View File

@ -17,9 +17,7 @@
"TITLE": "Modo manos libres",
"NONE": "Desactivado",
"HMD": "HMD + pellizco",
"EYE_TRACKING": "Ojo + pellizco",
"HMD_ONLY": "Solo HMD",
"EYE_ONLY": "Solo ojos"
"EYE_TRACKING": "Ojo + pellizco"
}
},
"WATCH": {
@ -136,4 +134,4 @@
"WAITING_FOR_USER": "Revisa tu escritorio para ver la ventana emergente de transmisión de pantalla.",
"FAIL": "No se pudo inicializar la fuente de transmisión de pantalla."
}
}
}

View File

@ -17,9 +17,7 @@
"TITLE": "Modalità a mani libere",
"NONE": "Disattivato",
"HMD": "HMD + pizzico",
"EYE_TRACKING": "Occhio + pizzico",
"HMD_ONLY": "Solo HMD",
"EYE_ONLY": "Solo occhi"
"EYE_TRACKING": "Occhio + pizzico"
}
},
"DEFAULT": "Predefinito",
@ -134,4 +132,4 @@
"WAITING_FOR_USER": "Controlla il desktop per un pop-up di condivisione schermo.",
"FAIL": "Impossibile inizializzare la sorgente di trasmissione dello schermo."
}
}
}

View File

@ -17,9 +17,7 @@
"TITLE": "ハンズフリーモード",
"NONE": "オフ",
"HMD": "HMD + ピンチ",
"EYE_TRACKING": "視線 + ピンチ",
"HMD_ONLY": "HMDのみ",
"EYE_ONLY": "目のみ"
"EYE_TRACKING": "視線 + ピンチ"
}
},
"WATCH": {
@ -134,4 +132,4 @@
"WAITING_FOR_USER": "デスクトップにスクリーンキャストのポップアップが表示されていないか確認してください。",
"FAIL": "スクリーンキャストのソースを初期化できませんでした。"
}
}
}

View File

@ -17,9 +17,7 @@
"TITLE": "Tryb wolnych rąk",
"NONE": "Wyłączone",
"HMD": "HMD + uszczypnięcie",
"EYE_TRACKING": "Oko + uszczypnięcie",
"HMD_ONLY": "Tylko HMD",
"EYE_ONLY": "Tylko oczy"
"EYE_TRACKING": "Oko + uszczypnięcie"
}
},
"WATCH": {
@ -134,4 +132,4 @@
"WAITING_FOR_USER": "Sprawdź na pulpicie powiadomienie o przesyłaniu ekranu.",
"FAIL": "Nie udało się zainicjować źródła przesyłania obrazu z ekranu."
}
}
}

View File

@ -17,9 +17,7 @@
"TITLE": "免提模式",
"NONE": "关闭",
"HMD": "HMD + 捏合",
"EYE_TRACKING": "眼动 + 捏合",
"HMD_ONLY": "仅 HMD",
"EYE_ONLY": "仅限眼睛"
"EYE_TRACKING": "眼动 + 捏合"
}
},
"DEFAULT": "默认",
@ -134,4 +132,4 @@
"WAITING_FOR_USER": "请检查桌面上的屏幕投射弹出窗口。",
"FAIL": "无法初始化屏幕镜像源。"
}
}
}

View File

@ -64,6 +64,7 @@ pub struct InputState {
pub pointers: [Pointer; 2],
pub devices: Vec<TrackedDevice>,
pub handsfree_state: PointerState,
pub picking_focus: bool,
processes: Vec<Child>,
}
@ -76,6 +77,7 @@ impl InputState {
devices: Vec::new(),
processes: Vec::new(),
handsfree_state: PointerState::default(),
picking_focus: false,
}
}

View File

@ -211,7 +211,7 @@ impl OpenXrInputSource {
);
}
pub fn update(&mut self, xr: &XrState, state: &mut AppState) -> anyhow::Result<()> {
pub fn update(&mut self, xr: &XrState, app: &mut AppState) -> anyhow::Result<()> {
xr.session.sync_actions(&[(&self.action_set).into()])?;
let loc = xr.view.locate(&xr.stage, xr.predicted_display_time)?;
@ -221,7 +221,7 @@ impl OpenXrInputSource {
.location_flags
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
{
state.input_state.hmd.matrix3 = hmd.matrix3;
app.input_state.hmd.matrix3 = hmd.matrix3;
} else {
hmd_tracked = false;
}
@ -230,26 +230,39 @@ impl OpenXrInputSource {
.location_flags
.contains(xr::SpaceLocationFlags::POSITION_VALID)
{
state.input_state.hmd.translation = hmd.translation;
app.input_state.hmd.translation = hmd.translation;
} else {
hmd_tracked = false;
}
let mut any_tracked = false;
for i in 0..2 {
let pointer = &mut state.input_state.pointers[i];
self.pointers[i].update(pointer, xr, &state.session)?;
any_tracked |= pointer.tracked;
let old_handsfree = app.session.config.handsfree_pointer;
if app.input_state.picking_focus {
app.session.config.handsfree_pointer = app.session.config.handsfree_alt_tab.into();
let ptr1 = &mut app.input_state.pointers[1];
ptr1.before = ptr1.now;
ptr1.now = PointerState::default();
ptr1.tracked = false
} else {
for i in 0..2 {
let pointer = &mut app.input_state.pointers[i];
self.pointers[i].update(pointer, xr, &app.session)?;
any_tracked |= pointer.tracked;
}
}
if !any_tracked {
self.handsfree_pointer.update_handsfree(
&mut state.input_state.pointers[0],
&mut app.input_state.pointers[0],
xr,
&state.session,
&app.session,
hmd,
hmd_tracked,
&state.input_state.handsfree_state,
&app.input_state.handsfree_state,
)?;
app.session.config.handsfree_pointer = old_handsfree;
}
Ok(())

View File

@ -30,8 +30,13 @@ const POLL_TIMEOUT_MS: i32 = 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyCombo {
Super1,
Super2,
Super3,
Super4,
AltF4,
AltTab,
AltShiftTab,
/// Always consumed by InputCapture internally
CtrlAltDel,
}
@ -123,19 +128,10 @@ impl InputCapture {
/// Exclusively grabs every currently detected keyboard and mouse.
/// Newly connected matching devices are grabbed automatically.
pub fn set_grabbed(&self, grabbed: bool) -> anyhow::Result<()> {
let (response_tx, response_rx) = mpsc::sync_channel(1);
self.command_tx
.send(Command::SetGrabbed {
grabbed,
response_tx,
})
.try_send(Command::SetGrabbed(grabbed))
.map_err(|error| anyhow::anyhow!("worker thread unreachable: {error}"))?;
response_rx
.recv()
.map_err(|error| anyhow::anyhow!("worker thread unreachable: {error}"))??;
Ok(())
}
@ -146,7 +142,7 @@ impl InputCapture {
}
self.command_tx
.send(Command::SetPointerAccel { accel, speed })
.try_send(Command::SetPointerAccel { accel, speed })
.map_err(|error| anyhow::anyhow!("worker thread unreachable: {error}"))?;
Ok(())
@ -164,14 +160,8 @@ impl Drop for InputCapture {
enum Command {
ResetWatchdog,
SetGrabbed {
grabbed: bool,
response_tx: SyncSender<io::Result<()>>,
},
SetPointerAccel {
accel: bool,
speed: f32,
},
SetGrabbed(bool),
SetPointerAccel { accel: bool, speed: f32 },
Shutdown,
}
@ -345,11 +335,8 @@ fn worker_main(
'worker: loop {
loop {
match command_rx.try_recv() {
Ok(Command::SetGrabbed {
grabbed: requested,
response_tx,
}) => {
let result = if requested {
Ok(Command::SetGrabbed(requested)) => {
let _ = if requested {
match restricted_state.borrow_mut().set_grabbed(true) {
Ok(()) => {
grabbed = true;
@ -391,8 +378,6 @@ fn worker_main(
pointer_accel,
&event_tx,
);
let _ = response_tx.send(result);
}
Ok(Command::SetPointerAccel { accel, speed }) => {
pointer_accel = PointerAccelConfig { accel, speed };
@ -542,7 +527,15 @@ fn process_libinput_event(
}
// emit press/release whenever a combo's complete state changes.
for combo in [KeyCombo::AltF4, KeyCombo::AltTab] {
for combo in [
KeyCombo::AltF4,
KeyCombo::AltShiftTab,
KeyCombo::AltTab,
KeyCombo::Super1,
KeyCombo::Super2,
KeyCombo::Super3,
KeyCombo::Super4,
] {
let combo_pressed = key_combo_is_pressed(combo, &state.pressed_keys);
let was_pressed = state.active_combos.contains(&combo);
@ -840,10 +833,21 @@ fn key_combo_is_pressed(combo: KeyCombo, pressed: &HashSet<u16>) -> bool {
let ctrl =
pressed.contains(&KeyCode::KEY_LEFTCTRL.0) || pressed.contains(&KeyCode::KEY_RIGHTCTRL.0);
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::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),
KeyCombo::Super4 => meta && pressed.contains(&KeyCode::KEY_4.0),
}
}

View File

@ -63,6 +63,7 @@ use xkbcommon::xkb;
use crate::{
backend::{
input::InputState,
task::{OverlayTask, SpawnPos, TaskContainer, TaskType, ToggleMode},
wayvr::{
image_importer::ImageImporter,
@ -636,7 +637,7 @@ impl WvrServerState {
wvr_server.manager.cleanup_handles();
}
wvr_server.process_input_capture();
wvr_server.process_input_capture(&mut app.input_state, &mut app.tasks);
wvr_server.ticks += 1;
@ -747,7 +748,11 @@ impl WvrServerState {
self.manager.seat_pointer.is_grabbed()
}
pub fn process_input_capture(&mut self) {
pub fn process_input_capture(
&mut self,
input_state: &mut InputState,
tasks: &mut TaskContainer,
) {
if !self.has_input_focus {
return;
}
@ -803,7 +808,22 @@ impl WvrServerState {
self.close_window(window_handle);
}
}
input_capture::KeyCombo::AltTab => self.alt_tab(pressed),
input_capture::KeyCombo::AltTab => input_state.picking_focus = pressed,
input_capture::KeyCombo::AltShiftTab => {
input_state.handsfree_state.grab = pressed
}
input_capture::KeyCombo::Super1 => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(0)))
}
input_capture::KeyCombo::Super2 => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(1)))
}
input_capture::KeyCombo::Super3 => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(2)))
}
input_capture::KeyCombo::Super4 => {
tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(3)))
}
_ => {}
},
}
@ -852,15 +872,6 @@ impl WvrServerState {
}
}
fn alt_tab(&mut self, pressed: bool) {
let mut windows: Vec<_> = self.wm.windows.iter().collect();
if windows.is_empty() {
return;
}
//TODO
}
fn button_to_mouse_index(button: u32) -> Option<MouseIndex> {
match button {
272 => Some(MouseIndex::Left),
@ -877,7 +888,7 @@ impl WvrServerState {
let res = input_capture
.set_grabbed(has_focus)
.log_err("Could not grab input.");
.log_err("Could not set input grab");
if res.is_ok() && !self.grab_toast_sent {
self.grab_toast_sent = true;
@ -971,6 +982,7 @@ impl WvrServerState {
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
force_focus: bool,
) {
if self.mouse_freeze > Instant::now() {
return;
@ -978,7 +990,17 @@ impl WvrServerState {
let global_pos = DVec2::from(global_pos);
let (focus, _) = self.get_mouse_focus(target, hover_window, false);
let (focus, focus_keyboard) = self.get_mouse_focus(target, hover_window, force_focus);
if focus_keyboard.is_some() {
self.manager.seat_keyboard.set_focus(
&mut self.manager.state,
focus_keyboard,
self.manager.serial_counter.next_serial(),
);
self.wm.keyboard_focus = Some(hover_window);
}
let linear_delta = self.get_mouse_relative(global_pos, hover_window);
self.manager
.send_mouse_move(focus, global_pos, linear_delta, linear_delta);

View File

@ -9,8 +9,8 @@ use wayvr_ipc::packet_client::WvrProcessLaunchParams;
use wlx_common::{
astr_containers::AStrMap,
config::{
AltModifier, CaptureMethod, ChromaKeyParams, GeneralConfig, HandsfreePointer,
InputEmulationMethod, SerializedWindowSet, SerializedWindowStates,
AltModifier, CaptureMethod, ChromaKeyParams, GeneralConfig, HandsfreeAltTab,
HandsfreePointer, InputEmulationMethod, SerializedWindowSet, SerializedWindowStates,
},
config_io,
locale::Language,
@ -180,6 +180,7 @@ pub struct AutoSettings {
pub keyboard_middle_click_mode: AltModifier,
pub autostart_apps: Vec<WvrProcessLaunchParams>,
pub handsfree_pointer: HandsfreePointer,
pub handsfree_alt_tab: HandsfreeAltTab,
pub language: Option<Language>,
pub chroma_key_params: ChromaKeyParams,
pub input_emulation_method: InputEmulationMethod,
@ -248,6 +249,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
keyboard_middle_click_mode: config.keyboard_middle_click_mode,
autostart_apps: config.autostart_apps.clone(),
handsfree_pointer: config.handsfree_pointer,
handsfree_alt_tab: config.handsfree_alt_tab,
language: config.language,
chroma_key_params: config.chroma_key_params.clone(),
input_emulation_method: config.input_emulation_method,

View File

@ -13,7 +13,10 @@ use crate::{
},
overlays::screen::capture::MyFirstDmaExporter,
state::AppState,
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta},
subsystem::{
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta},
input::InputFocus,
},
windowing::backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform,
},
@ -298,6 +301,12 @@ impl OverlayBackend for ScreenBackend {
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult {
#[cfg(debug_assertions)]
log::trace!("Hover: {:?}", hit.uv);
if app.input_state.picking_focus {
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::PhysicalScreen);
}
if can_move()
&& (!app.session.config.focus_follows_mouse_mode
|| app.input_state.pointers[hit.pointer].now.move_mouse)

View File

@ -845,11 +845,17 @@ impl OverlayBackend for WvrWindowBackend {
self.panel_hovered = false;
}
if app.input_state.picking_focus {
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::WayVR);
}
let wvr_server = app.wvr_server.as_mut().unwrap();
wvr_server.send_mouse_move(
PointerFocusTarget::Surface { surface, origin },
global_pos,
self.window,
app.input_state.picking_focus,
);
HoverResult::consume()
@ -860,8 +866,18 @@ impl OverlayBackend for WvrWindowBackend {
self.panel_hovered = false;
}
if app.input_state.picking_focus {
app.hid_provider
.set_input_focus(app.wvr_server.as_mut(), InputFocus::WayVR);
}
let wvr_server = app.wvr_server.as_mut().unwrap();
wvr_server.send_mouse_move(PointerFocusTarget::Toplevel, pos, self.window);
wvr_server.send_mouse_move(
PointerFocusTarget::Toplevel,
pos,
self.window,
app.input_state.picking_focus,
);
HoverResult::consume()
}

View File

@ -89,6 +89,24 @@ pub enum HandsfreePointer {
EyeTrackingOnly,
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)]
pub enum HandsfreeAltTab {
#[strum(props(Translation = "APP_SETTINGS.OPTION.HMD_ONLY"))]
#[default]
Hmd,
#[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_ONLY"))]
EyeTracking,
}
impl From<HandsfreeAltTab> for HandsfreePointer {
fn from(value: HandsfreeAltTab) -> Self {
match value {
HandsfreeAltTab::Hmd => HandsfreePointer::HmdOnly,
HandsfreeAltTab::EyeTracking => HandsfreePointer::EyeTrackingOnly,
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ChromaKeyParams {
pub hsv_min: [f32; 3],
@ -449,6 +467,9 @@ pub struct GeneralConfig {
#[serde(default)]
pub handsfree_pointer: HandsfreePointer,
#[serde(default)]
pub handsfree_alt_tab: HandsfreeAltTab,
#[serde(default = "def_one")]
pub grid_opacity: f32,