From 4cd238e487dcb1164b2a345b54b17f22eecb4c07 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:46:50 +0900 Subject: [PATCH] handsfree alt-tab --- dash-frontend/assets/lang/de.json | 4 +- dash-frontend/assets/lang/en.json | 6 +- dash-frontend/assets/lang/es.json | 4 +- dash-frontend/assets/lang/it.json | 4 +- dash-frontend/assets/lang/ja.json | 4 +- dash-frontend/assets/lang/pl.json | 4 +- dash-frontend/assets/lang/zh_CN.json | 4 +- dash-frontend/src/tab/settings/mod.rs | 7 +++ .../src/tab/settings/tab_controls.rs | 1 + wayvr/src/assets/lang/de.json | 6 +- wayvr/src/assets/lang/en.json | 4 +- wayvr/src/assets/lang/es.json | 6 +- wayvr/src/assets/lang/it.json | 6 +- wayvr/src/assets/lang/ja.json | 6 +- wayvr/src/assets/lang/pl.json | 6 +- wayvr/src/assets/lang/zh_CN.json | 6 +- wayvr/src/backend/input.rs | 2 + wayvr/src/backend/openxr/input.rs | 33 +++++++---- wayvr/src/backend/wayvr/input_capture.rs | 58 ++++++++++--------- wayvr/src/backend/wayvr/mod.rs | 50 +++++++++++----- wayvr/src/config.rs | 6 +- wayvr/src/overlays/screen/backend.rs | 11 +++- wayvr/src/overlays/wayvr.rs | 18 +++++- wlx-common/src/config.rs | 21 +++++++ 24 files changed, 176 insertions(+), 101 deletions(-) diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index 4a18a994..b60599f2 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -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" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 5bf2abc6..eee5530c 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -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.", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index c49727e8..ec40f9a2 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -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" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index 75c9103e..1ebd6174 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -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" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index c9cc7c06..190e5740 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -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": "ピン留めされたアプリケーション" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index 1a543be0..ff600206 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -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" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index f39ae596..64e5e368 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -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": "已固定应用程序" -} \ No newline at end of file +} diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 0ba65fde..87051555 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -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"), diff --git a/dash-frontend/src/tab/settings/tab_controls.rs b/dash-frontend/src/tab/settings/tab_controls.rs index 6a9ad1bc..41ec4dab 100644 --- a/dash-frontend/src/tab/settings/tab_controls.rs +++ b/dash-frontend/src/tab/settings/tab_controls.rs @@ -97,6 +97,7 @@ impl State { } options_dropdown::(par.mp, c, &SettingType::KeyboardMiddleClick)?; options_dropdown::(par.mp, c, &SettingType::HandsfreePointer)?; + options_dropdown::(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)?; diff --git a/wayvr/src/assets/lang/de.json b/wayvr/src/assets/lang/de.json index 72d3b803..5afbdaa8 100644 --- a/wayvr/src/assets/lang/de.json +++ b/wayvr/src/assets/lang/de.json @@ -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." } -} \ No newline at end of file +} diff --git a/wayvr/src/assets/lang/en.json b/wayvr/src/assets/lang/en.json index 95ff8ece..4912bf38 100644 --- a/wayvr/src/assets/lang/en.json +++ b/wayvr/src/assets/lang/en.json @@ -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" }, diff --git a/wayvr/src/assets/lang/es.json b/wayvr/src/assets/lang/es.json index 7a8c14c5..afda655b 100644 --- a/wayvr/src/assets/lang/es.json +++ b/wayvr/src/assets/lang/es.json @@ -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." } -} \ No newline at end of file +} diff --git a/wayvr/src/assets/lang/it.json b/wayvr/src/assets/lang/it.json index ebe6ede0..bb4709d7 100644 --- a/wayvr/src/assets/lang/it.json +++ b/wayvr/src/assets/lang/it.json @@ -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." } -} \ No newline at end of file +} diff --git a/wayvr/src/assets/lang/ja.json b/wayvr/src/assets/lang/ja.json index 70d21d64..62403976 100644 --- a/wayvr/src/assets/lang/ja.json +++ b/wayvr/src/assets/lang/ja.json @@ -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": "スクリーンキャストのソースを初期化できませんでした。" } -} \ No newline at end of file +} diff --git a/wayvr/src/assets/lang/pl.json b/wayvr/src/assets/lang/pl.json index 43db4fd2..1bbdaa96 100644 --- a/wayvr/src/assets/lang/pl.json +++ b/wayvr/src/assets/lang/pl.json @@ -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." } -} \ No newline at end of file +} diff --git a/wayvr/src/assets/lang/zh_CN.json b/wayvr/src/assets/lang/zh_CN.json index a17bfd80..ee9efab5 100644 --- a/wayvr/src/assets/lang/zh_CN.json +++ b/wayvr/src/assets/lang/zh_CN.json @@ -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": "无法初始化屏幕镜像源。" } -} \ No newline at end of file +} diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index d46cb66c..6cb97a44 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -64,6 +64,7 @@ pub struct InputState { pub pointers: [Pointer; 2], pub devices: Vec, pub handsfree_state: PointerState, + pub picking_focus: bool, processes: Vec, } @@ -76,6 +77,7 @@ impl InputState { devices: Vec::new(), processes: Vec::new(), handsfree_state: PointerState::default(), + picking_focus: false, } } diff --git a/wayvr/src/backend/openxr/input.rs b/wayvr/src/backend/openxr/input.rs index 6e231bea..b0a4392d 100644 --- a/wayvr/src/backend/openxr/input.rs +++ b/wayvr/src/backend/openxr/input.rs @@ -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(()) diff --git a/wayvr/src/backend/wayvr/input_capture.rs b/wayvr/src/backend/wayvr/input_capture.rs index 9fd4fb54..c0d7b1db 100644 --- a/wayvr/src/backend/wayvr/input_capture.rs +++ b/wayvr/src/backend/wayvr/input_capture.rs @@ -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>, - }, - 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) -> 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), } } diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 01e55fab..cbc67833 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -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 { 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); diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 10b4d22d..97603645 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -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, pub handsfree_pointer: HandsfreePointer, + pub handsfree_alt_tab: HandsfreeAltTab, pub language: Option, 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, diff --git a/wayvr/src/overlays/screen/backend.rs b/wayvr/src/overlays/screen/backend.rs index e40d974d..e73c11d0 100644 --- a/wayvr/src/overlays/screen/backend.rs +++ b/wayvr/src/overlays/screen/backend.rs @@ -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) diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index d77702df..8a2cc1af 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -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() } diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 29b8295c..be55aa76 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -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 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,