From 590980e560f4580c0039e4461a353c61650b017f Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:43:47 +0900 Subject: [PATCH] input capture rework --- dash-frontend/assets/lang/de.json | 17 +- dash-frontend/assets/lang/en.json | 10 +- dash-frontend/assets/lang/es.json | 17 +- dash-frontend/assets/lang/it.json | 17 +- dash-frontend/assets/lang/ja.json | 29 ++- dash-frontend/assets/lang/pl.json | 17 +- dash-frontend/assets/lang/zh_CN.json | 17 +- dash-frontend/src/tab/settings/mod.rs | 7 + dash-frontend/src/tab/settings/tab_misc.rs | 1 + wayvr/src/assets/gui/alttab-help.xml | 120 +++++++++ wayvr/src/assets/lang/de.json | 22 +- wayvr/src/assets/lang/en.json | 16 ++ wayvr/src/assets/lang/es.json | 22 +- wayvr/src/assets/lang/it.json | 22 +- wayvr/src/assets/lang/ja.json | 22 +- wayvr/src/assets/lang/pl.json | 22 +- wayvr/src/assets/lang/zh_CN.json | 22 +- .../sound/wvr_input_capture_grabbed.mp3 | Bin 0 -> 6151 bytes .../sound/wvr_input_capture_ungrabbed.mp3 | Bin 0 -> 6830 bytes wayvr/src/backend/wayvr/input_capture.rs | 70 +++-- wayvr/src/backend/wayvr/mod.rs | 240 +++++++++++------- wayvr/src/config.rs | 5 +- wayvr/src/overlays/anchor.rs | 30 ++- wayvr/src/overlays/screen/backend.rs | 10 +- wayvr/src/overlays/screen/wl.rs | 6 +- wayvr/src/overlays/screen/x11.rs | 19 +- wayvr/src/overlays/toast.rs | 3 +- wayvr/src/state.rs | 16 ++ wayvr/src/subsystem/hid/mod.rs | 14 +- wayvr/src/subsystem/hid/provider/dummy.rs | 9 +- wayvr/src/subsystem/hid/provider/mod.rs | 9 +- wayvr/src/subsystem/hid/provider/uinput.rs | 28 +- .../src/subsystem/hid/provider/wl_virtual.rs | 28 +- wayvr/src/windowing/manager.rs | 5 +- wayvr/src/windowing/mod.rs | 2 + wlx-common/src/config.rs | 16 ++ 36 files changed, 689 insertions(+), 221 deletions(-) create mode 100644 wayvr/src/assets/gui/alttab-help.xml create mode 100644 wayvr/src/assets/sound/wvr_input_capture_grabbed.mp3 create mode 100644 wayvr/src/assets/sound/wvr_input_capture_ungrabbed.mp3 diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index b35a7df8..37ba7ddb 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -76,9 +76,13 @@ "EYE_PINCH": "Auge + Kneifen", "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.", "WL_VIRTUAL": "Wayland-virtueller Input", - "WL_VIRTUAL_HELP": "Sendet Ereignisse an den Wayland-Compositor unter Verwendung von\nzwlr_virtual_pointer und zwp_virtual_keyboard.\nFunktioniert nur zuverlässig auf einigen Desktops.\nFunktioniert NICHT auf KDE, GNOME oder COSMIC." + "WL_VIRTUAL_HELP": "Sendet Ereignisse an den Wayland-Compositor unter Verwendung von\nzwlr_virtual_pointer und zwp_virtual_keyboard.\nFunktioniert nur zuverlässig auf einigen Desktops.\nFunktioniert NICHT auf KDE, GNOME oder COSMIC.", + "EYE_ONLY": "Augenlaser", + "HMD_ONLY": "HMD-Laser", + "NONE_INPUT_CAPTURE_HELP": "Maus und Tastatur nicht erfassen.\nDie physische Maus oder Tastatur kann in den \ninnerhalb von WayVR gestarteten Apps nicht verwendet werden.", + "UINPUT_HELP": "Verwendet das uinput-Modul, um ein Gerät zu emulieren.\nFunktioniert auf den meisten Desktops zuverlässig.", + "EVDEV_HELP": "Eingabegeräte für die exklusive Nutzung erfassen.\nIhr Benutzer muss Mitglied der Input-Gruppe sein." }, "AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen", "HANDSFREE_POINTER": "Freihändige Modus", @@ -196,7 +200,14 @@ "SAVE_PLAYSPACE_CENTER": "Playspace-Zentrum speichern", "SAVE_PLAYSPACE_CENTER_HELP": "Nutzen Sie den aktuellen Playspace für die Zuweisung zum Zurücksetzen des Playspaces.", "RESET_PLAYSPACE_CENTER": "Playspace-Zentrum zurücksetzen", - "RESET_PLAYSPACE_CENTER_HELP": "Zuvor festgelegtes Playspace-Zentrum zurücksetzen." + "RESET_PLAYSPACE_CENTER_HELP": "Zuvor festgelegtes Playspace-Zentrum zurücksetzen.", + "HANDSFREE_ALT_TAB": "App-Switcher-Ziel", + "HANDSFREE_ALT_TAB_HELP": "Super+X gedrückt halten, auf das gewünschte Fenster zielen und dann loslassen, um den Fokus zu wechseln.", + "INPUT_CAPTURE_METHOD": "Eingabe-Erfassungsmethode", + "POINTER_ACCELERATION": "Mausbeschleunigung", + "POINTER_ACCELERATION_HELP": "An: Adaptive Mausbeschleunigung\nAus: Konstante Geschwindigkeit", + "POINTER_SPEED": "Mausgeschwindigkeit", + "INPUT_CAPTURE_METHOD_HELP": "Ermöglicht die Nutzung physischer Peripheriegeräte innerhalb von WayVR.\n Super+Z: Eingabe zwischen WayVR und echtem Desktop umschalten\n Super+X: Zwischen WayVR-Apps umschalten" }, "HELLO": "Hallo!", "AUDIO": { diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 6852beb7..b3372560 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -123,12 +123,14 @@ "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.", + "HANDSFREE_ALT_TAB": "App switcher aim", + "HANDSFREE_ALT_TAB_HELP": "Hold Super+X, 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", "INPUT_EMULATION_METHOD_HELP": "Try change this in case mouse or\nkeyboard input does not register.", + "INPUT_CAPTURE_METHOD": "Input capture method", + "INPUT_CAPTURE_METHOD_HELP": "Allows using physical peripherial inside WayVR.\n Super+Z: Switch input between WayVR and real desktop\n Super+X: Switch between WayVR apps", "INPUT_PROFILES": "Change Input Bindings", "INPUT_PROFILES_HELP": "OpenXR controller input bindings", "INVERT_SCROLL_DIRECTION_X": "Invert horizontal scroll direction", @@ -156,12 +158,14 @@ "HMD_PINCH": "HMD + pinch", "NONE": "None", "NONE_INPUT_HELP": "Do not emulate mouse and keyboard.\nWill not be able to interact with screens.", + "NONE_INPUT_CAPTURE_HELP": "Do not capture mouse and keyboard.\nWill not be able to use physical mouse or\nkeyboard in apps launched inside WayVR.", "PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.", "PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work", "SCREENCOPY_GPU_HELP": "Fast, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway", "SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway", "UINPUT": "Device emulation", - "UINPUT_HELP": "Uses the uinput module to emulate a device.\nYour user must be in the input group.\nKnown to work well on most desktops.", + "UINPUT_HELP": "Uses the uinput module to emulate a device.\nKnown to work well on most desktops.", + "EVDEV_HELP": "Grab input devices for exclusive use.\nYour user must be in the input group.", "WL_VIRTUAL": "Wayland virtual input", "WL_VIRTUAL_HELP": "Send events to the Wayland compositor using\nzwlr_virtual_pointer and zwp_virtual_keyboard.\nOnly works reliably on some desktops.\nDoes NOT work on KDE, GNOME or COSMIC." }, diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index b00a5a95..109fb33e 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -76,9 +76,13 @@ "EYE_PINCH": "Ojo + pellizco", "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.", "WL_VIRTUAL": "Entrada virtual de Wayland", - "WL_VIRTUAL_HELP": "Enviar eventos al compositor de Wayland usando\nzwlr_virtual_pointer y zwp_virtual_keyboard.\nSolo funciona de forma fiable en algunos escritorios.\nNO funciona en KDE, GNOME o COSMIC." + "WL_VIRTUAL_HELP": "Enviar eventos al compositor de Wayland usando\nzwlr_virtual_pointer y zwp_virtual_keyboard.\nSolo funciona de forma fiable en algunos escritorios.\nNO funciona en KDE, GNOME o COSMIC.", + "EYE_ONLY": "Láser ocular", + "HMD_ONLY": "Láser del HMD", + "NONE_INPUT_CAPTURE_HELP": "No capturar el ratón ni el teclado.\nNo se podrá usar el ratón físico ni el\nteclado en las aplicaciones lanzadas dentro de WayVR.", + "UINPUT_HELP": "Utiliza el módulo uinput para emular un dispositivo.\nSe sabe que funciona bien en la mayoría de los escritorios.", + "EVDEV_HELP": "Captura los dispositivos de entrada para uso exclusivo.\nTu usuario debe pertenecer al grupo 'input'." }, "AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio", "HANDSFREE_POINTER": "Modo manos libres", @@ -196,7 +200,14 @@ "SAVE_PLAYSPACE_CENTER": "Guardar centro del playspace", "SAVE_PLAYSPACE_CENTER_HELP": "Usa el playspace actual para el comando de restablecer playspace.", "RESET_PLAYSPACE_CENTER": "Reiniciar centro del playspace", - "RESET_PLAYSPACE_CENTER_HELP": "Borra el centro del playspace establecido previamente." + "RESET_PLAYSPACE_CENTER_HELP": "Borra el centro del playspace establecido previamente.", + "HANDSFREE_ALT_TAB": "Puntero del selector de aplicaciones", + "HANDSFREE_ALT_TAB_HELP": "Mantén presionadas Super+X, apunta a la ventana deseada y luego suelta para cambiar el foco.", + "INPUT_CAPTURE_METHOD": "Método de captura de entrada", + "POINTER_ACCELERATION": "Aceleración del ratón", + "POINTER_ACCELERATION_HELP": "Activado: Aceleración adaptativa del ratón\nDesactivado: Velocidad constante", + "POINTER_SPEED": "Velocidad del ratón", + "INPUT_CAPTURE_METHOD_HELP": "Permite usar periféricos físicos dentro de WayVR.\n Super+Z: Cambiar la entrada entre WayVR y el escritorio real\n Super+X: Cambiar entre aplicaciones de WayVR" }, "HELLO": "¡Hola!", "AUDIO": { diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index c62a5689..4326ebe1 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -78,9 +78,13 @@ "EYE_PINCH": "Occhio + pizzico", "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.", "WL_VIRTUAL": "Input virtuale Wayland", - "WL_VIRTUAL_HELP": "Invia eventi al compositor Wayland utilizzando\nzwlr_virtual_pointer e zwp_virtual_keyboard.\nFunziona in modo affidabile solo su alcuni desktop.\nNON funziona su KDE, GNOME o COSMIC." + "WL_VIRTUAL_HELP": "Invia eventi al compositor Wayland utilizzando\nzwlr_virtual_pointer e zwp_virtual_keyboard.\nFunziona in modo affidabile solo su alcuni desktop.\nNON funziona su KDE, GNOME o COSMIC.", + "EYE_ONLY": "Laser oculare", + "HMD_ONLY": "Laser HMD", + "NONE_INPUT_CAPTURE_HELP": "Non acquisire mouse e tastiera.\nNon sarà possibile utilizzare il mouse fisico o\nla tastiera nelle app lanciate all'interno di WayVR.", + "UINPUT_HELP": "Utilizza il modulo uinput per emulare un dispositivo.\nNoto per funzionare bene sulla maggior parte dei desktop.", + "EVDEV_HELP": "Acquisisci i dispositivi di input per l'uso esclusivo.\nL'utente deve appartenere al gruppo 'input'." }, "POINTER_LERP_FACTOR": "Smussamento puntatore", "RESTART_SOFTWARE": "Riavvia il software", @@ -217,7 +221,14 @@ "SAVE_PLAYSPACE_CENTER": "Salva centro playspace", "SAVE_PLAYSPACE_CENTER_HELP": "Usa il playspace corrente per il comando di reset del playspace.", "RESET_PLAYSPACE_CENTER": "Ripristina centro playspace", - "RESET_PLAYSPACE_CENTER_HELP": "Ripristina il centro del playspace impostato precedentemente." + "RESET_PLAYSPACE_CENTER_HELP": "Ripristina il centro del playspace impostato precedentemente.", + "HANDSFREE_ALT_TAB": "Mira commutatore app", + "HANDSFREE_ALT_TAB_HELP": "Tieni premuto Super+X, punta alla finestra desiderata, poi rilascia per cambiare focus.", + "INPUT_CAPTURE_METHOD": "Metodo di acquisizione input", + "POINTER_ACCELERATION": "Accelerazione del mouse", + "POINTER_ACCELERATION_HELP": "Attivo: Accelerazione adattiva del mouse\nDisattivo: Velocità costante", + "POINTER_SPEED": "Velocità mouse", + "INPUT_CAPTURE_METHOD_HELP": "Consente l'uso di periferiche fisiche all'interno di WayVR.\n Super+Z: Passa l'input tra WayVR e il desktop reale\n Super+X: Passa tra le app di WayVR" }, "APPLICATION_LAUNCHER": "Lanciatore applicazioni", "APPLICATION_STARTED": "Applicazione avviata", diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 72dd7440..0e7bd8af 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -76,9 +76,13 @@ "EYE_PINCH": "つまんで目を合わせる", "NONE_INPUT_HELP": "マウスとキーボードをエミュレートしません。\nディスプレイを操作できなくなります。", "UINPUT": "デバイス・エミュレーション", - "UINPUT_HELP": "uinput モジュールを使用してデバイスをエミュレートします。\nユーザーが input グループに属している必要があります。\nほとんどのデスクトップ環境で正常に動作することが確認されています。", "WL_VIRTUAL": "Wayland 仮想入力", - "WL_VIRTUAL_HELP": "zwlr_virtual_pointer および zwp_virtual_keyboard を使用して、Wayland コンポジタにイベントを送信します。\n一部のデスクトップ環境でのみ、安定して動作します。\nKDE、GNOME、または COSMIC では動作しません。" + "WL_VIRTUAL_HELP": "zwlr_virtual_pointer および zwp_virtual_keyboard を使用して、Wayland コンポジタにイベントを送信します。\n一部のデスクトップ環境でのみ、安定して動作します。\nKDE、GNOME、または COSMIC では動作しません。", + "EYE_ONLY": "視線レーザー", + "HMD_ONLY": "HMDレーザー", + "NONE_INPUT_CAPTURE_HELP": "マウスとキーボードをキャプチャしない。\nWayVR内で起動したアプリで物理マウスや\nキーボードを使用できなくなります。", + "UINPUT_HELP": "uinputモジュールを使用してデバイスをエミュレートします。\nほとんどのデスクトップでうまく動作することが確認されています。", + "EVDEV_HELP": "入力デバイスを排他的に使用するために確保します。\nユーザーがinputグループに属している必要があります。" }, "AUTOSTART_APPS": "起動時に実行するアプリ", "HANDSFREE_POINTER": "ハンズフリーモード", @@ -141,9 +145,9 @@ "ALT_CLICK": "カスタムシェル実行 (configでalt_clickを設定)", "SHOW_HIDE": "表示・非表示、再中心化*", "TOGGLE_DASHBOARD": "ダッシュボードの切り替え", - "SPACE_DRAG": "Playspaceドラッグ", - "SPACE_ROTATE": "Playspace 回転", - "SPACE_RESET": "Playspace リセット", + "SPACE_DRAG": "スペースドラッグ", + "SPACE_ROTATE": "スペース回転", + "SPACE_RESET": "スペース リセット", "CLICK_MODIFIER_RIGHT": "右クリック修飾キー", "CLICK_MODIFIER_MIDDLE": "中クリック修飾キー", "MOVE_MOUSE": "マウスを移動 (デフォルトでオフの場合)", @@ -161,8 +165,8 @@ "INPUT_PROFILES": "入力バインドの変更", "INPUT_PROFILES_HELP": "OpenXRコントローラーの入力バインディング", "NOT_SUPPORTED": "未対応", - "ENABLE_WATCH": "Watchを有効にする", - "SPACE_DRAG": "Space drag", + "ENABLE_WATCH": "ヴォッチを有効にする", + "SPACE_DRAG": "スペースムーバー", "SPACE_GRAVITY_DAMPING": "減衰", "SPACE_GRAVITY_DAMPING_HELP": "動きを減速させるための人工的な抵抗です。0.1 - 強い抵抗、1.0 - 抵抗なし", "SPACE_GRAVITY_FLING_STRENGTH": "スリングの強さ", @@ -195,8 +199,15 @@ "SNAP_ANGLE_DEG_HELP": "掴んでいるウィンドウが、ピッチ軸とロール軸でこの角度にスナップします。", "SAVE_PLAYSPACE_CENTER": "プレイスペースの中心を保存", "SAVE_PLAYSPACE_CENTER_HELP": "現在のプレイスペースを、プレイスペースのリセットバインディングに使用します。", - "RESET_PLAYSPACE_CENTER": "playspaceの中心をリセット", - "RESET_PLAYSPACE_CENTER_HELP": "previously set playspace center をリセットします。" + "RESET_PLAYSPACE_CENTER": "スペースの中心をリセット", + "RESET_PLAYSPACE_CENTER_HELP": "スペースセンターをリセットします。", + "HANDSFREE_ALT_TAB": "アプリ切り替えの照準", + "HANDSFREE_ALT_TAB_HELP": "Super+Xを押し続け、切り替えたいウィンドウに照準を合わせ、離してフォーカスを切り替えます。", + "INPUT_CAPTURE_METHOD": "入力キャプチャ方式", + "POINTER_ACCELERATION": "マウス加速", + "POINTER_ACCELERATION_HELP": "オン: 適応型マウス加速\nオフ: 一定速度", + "POINTER_SPEED": "マウス速度", + "INPUT_CAPTURE_METHOD_HELP": "WayVR内で物理周辺機器を使用できるようにします。\n Super+Z: WayVRと実際のデスクトップ間で入力を切り替え\n Super+X: WayVRアプリ間で切り替え" }, "HELLO": "こんにちは!", "AUDIO": { diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index a6a6ae6d..8aad4e36 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -71,9 +71,13 @@ "EYE_PINCH": "Ściśnięcie palcami + oko", "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.", "WL_VIRTUAL": "Wirtualne wejście Wayland", - "WL_VIRTUAL_HELP": "Wysyłaj wejście myszki/klawiatury do kompozytora Wayland za pomocą\nzwlr_virtual_pointer i zwp_virtual_keyboard.\nDziała niezawodnie tylko na niektórych pulpitach.\nNIE działa na KDE, GNOME ani COSMIC." + "WL_VIRTUAL_HELP": "Wysyłaj wejście myszki/klawiatury do kompozytora Wayland za pomocą\nzwlr_virtual_pointer i zwp_virtual_keyboard.\nDziała niezawodnie tylko na niektórych pulpitach.\nNIE działa na KDE, GNOME ani COSMIC.", + "EYE_ONLY": "Laser oczami", + "HMD_ONLY": "Laser HMD", + "NONE_INPUT_CAPTURE_HELP": "Nie przechwytuj myszy i klawiatury.\nFizyczna mysz lub klawiatura nie będą\ndostępne w aplikacjach uruchomionych w WayVR.", + "UINPUT_HELP": "Używa modułu uinput do emulacji urządzenia.\nSprawdzono, że działa dobrze na większości komputerów stacjonarnych.", + "EVDEV_HELP": "Przejmij urządzenia wejściowe do wyłącznego użytku.\nUżytkownik musi należeć do grupy 'input'." }, "AUTOSTART_APPS": "Aplikacje auto-start", "HANDSFREE_POINTER": "Tryb bez użycia rąk", @@ -191,7 +195,14 @@ "SAVE_PLAYSPACE_CENTER": "Zapisz centrum obszaru gry", "SAVE_PLAYSPACE_CENTER_HELP": "Użyj aktualnego obszaru gry dla przypisania resetowania obszaru gry.", "RESET_PLAYSPACE_CENTER": "Zresetuj środek obszaru gry", - "RESET_PLAYSPACE_CENTER_HELP": "Wyczyść poprzednio ustawiony środek obszaru playspace." + "RESET_PLAYSPACE_CENTER_HELP": "Wyczyść poprzednio ustawiony środek obszaru playspace.", + "HANDSFREE_ALT_TAB": "Cel przełącznika aplikacji", + "HANDSFREE_ALT_TAB_HELP": "Przytrzymaj Super+X, wyceluj w żądane okno, a następnie puść, aby zmienić fokus.", + "INPUT_CAPTURE_METHOD": "Metoda przechwytywania wejścia", + "POINTER_ACCELERATION": "Akceleracja myszy", + "POINTER_ACCELERATION_HELP": "Wł.: Adaptacyjna akceleracja myszy\nWył.: Stała prędkość", + "POINTER_SPEED": "Prędkość myszy", + "INPUT_CAPTURE_METHOD_HELP": "Umożliwia korzystanie z fizycznych urządzeń peryferyjnych wewnątrz WayVR.\n Super+Z: Przełącz wejście między WayVR a rzeczywistym pulpitem\n Super+X: Przełącz między aplikacjami WayVR" }, "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATIONS": "Aplikacje", diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index 64f3e73e..9d8e5a52 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -78,9 +78,13 @@ "EYE_PINCH": "眼睛 + 捏合", "NONE_INPUT_HELP": "不模拟鼠标和键盘。\n将无法与屏幕进行交互。", "UINPUT": "设备模拟", - "UINPUT_HELP": "使用 uinput 模块来模拟设备。\n您的用户必须处于 input 用户组中。\n已知在大多数桌面上运行良好。", "WL_VIRTUAL": "Wayland 虚拟输入", - "WL_VIRTUAL_HELP": "使用 zwlr_virtual_pointer 和 zwp_virtual_keyboard 向 Wayland 合成器发送事件。\n仅在部分桌面环境下能可靠工作。\n在 KDE、GNOME 或 COSMIC 上无法工作。" + "WL_VIRTUAL_HELP": "使用 zwlr_virtual_pointer 和 zwp_virtual_keyboard 向 Wayland 合成器发送事件。\n仅在部分桌面环境下能可靠工作。\n在 KDE、GNOME 或 COSMIC 上无法工作。", + "EYE_ONLY": "眼部激光", + "HMD_ONLY": "HMD 激光指针", + "NONE_INPUT_CAPTURE_HELP": "不捕捉鼠标和键盘。\n在 WayVR 中启动的应用里将无法\n使用物理鼠标或键盘。", + "UINPUT_HELP": "使用 uinput 模块来模拟设备。\n已知在大多数桌面环境下运行良好。", + "EVDEV_HELP": "独占使用输入设备。\n您的用户必须在 input 组中。" }, "POINTER_LERP_FACTOR": "指针平滑", "RESTART_SOFTWARE": "重启软件", @@ -217,7 +221,14 @@ "SAVE_PLAYSPACE_CENTER": "保存 Playspace 中心", "SAVE_PLAYSPACE_CENTER_HELP": "将当前活动空间用于重置活动空间的绑定。", "RESET_PLAYSPACE_CENTER": "重置游玩区域中心", - "RESET_PLAYSPACE_CENTER_HELP": "清除之前设置的 Playspace 中心。" + "RESET_PLAYSPACE_CENTER_HELP": "清除之前设置的 Playspace 中心。", + "HANDSFREE_ALT_TAB": "应用切换瞄准点", + "HANDSFREE_ALT_TAB_HELP": "按住 Super+X,对准目标窗口,然后松开以切换焦点。", + "INPUT_CAPTURE_METHOD": "输入捕获方式", + "POINTER_ACCELERATION": "鼠标加速", + "POINTER_ACCELERATION_HELP": "开启:自适应鼠标加速\n关闭:恒定速度", + "POINTER_SPEED": "鼠标速度", + "INPUT_CAPTURE_METHOD_HELP": "允许在 WayVR 中使用物理外设。\n Super+Z: 在 WayVR 和真实桌面之间切换输入\n Super+X: 在 WayVR 应用之间切换" }, "APPLICATION_LAUNCHER": "应用启动器", "APPLICATION_STARTED": "应用已启动", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 344ab323..0bac4297 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -319,6 +319,7 @@ pub(crate) enum SettingType { HandsfreePointer, HideGrabHelp, HideUsername, + InputCaptureMethod, InputEmulationMethod, InvertScrollDirectionX, InvertScrollDirectionY, @@ -436,6 +437,9 @@ impl SettingType { Self::CaptureMethod => { config.capture_method = wlx_common::config::CaptureMethod::from_str(value).expect("Invalid enum value!") } + Self::InputCaptureMethod => { + config.wvr_input_capture = wlx_common::config::InputCaptureMethod::from_str(value).expect("Invalid enum value!") + } Self::InputEmulationMethod => { config.input_emulation_method = wlx_common::config::InputEmulationMethod::from_str(value).expect("Invalid enum value!") @@ -460,6 +464,7 @@ impl SettingType { fn get_enum_title(self, config: &mut GeneralConfig) -> Translation { match self { Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method), + Self::InputCaptureMethod => Self::get_enum_title_inner(config.wvr_input_capture), 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), @@ -509,6 +514,7 @@ impl SettingType { Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"), Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"), Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"), + Self::InputCaptureMethod => Ok("APP_SETTINGS.INPUT_CAPTURE_METHOD"), Self::InputEmulationMethod => Ok("APP_SETTINGS.INPUT_EMULATION_METHOD"), Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"), Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), @@ -561,6 +567,7 @@ impl SettingType { 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::InputCaptureMethod => Some("APP_SETTINGS.INPUT_CAPTURE_METHOD_HELP"), Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), diff --git a/dash-frontend/src/tab/settings/tab_misc.rs b/dash-frontend/src/tab/settings/tab_misc.rs index 3d883887..d1fa6ca9 100644 --- a/dash-frontend/src/tab/settings/tab_misc.rs +++ b/dash-frontend/src/tab/settings/tab_misc.rs @@ -11,6 +11,7 @@ impl State { pub fn mount(par: SettingsMountParams) -> anyhow::Result { let c = options_category(par.mp, par.id_parent, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?; options_dropdown::(par.mp, c, &SettingType::InputEmulationMethod)?; + options_dropdown::(par.mp, c, &SettingType::InputCaptureMethod)?; options_dropdown::(par.mp, c, &SettingType::CaptureMethod)?; options_checkbox(par.mp, c, SettingType::XwaylandByDefault)?; options_checkbox(par.mp, c, SettingType::UprightScreenFix)?; diff --git a/wayvr/src/assets/gui/alttab-help.xml b/wayvr/src/assets/gui/alttab-help.xml new file mode 100644 index 00000000..416afc37 --- /dev/null +++ b/wayvr/src/assets/gui/alttab-help.xml @@ -0,0 +1,120 @@ + + +
+ + +
+
+
+
+
+
diff --git a/wayvr/src/assets/lang/de.json b/wayvr/src/assets/lang/de.json index 5afbdaa8..48b16772 100644 --- a/wayvr/src/assets/lang/de.json +++ b/wayvr/src/assets/lang/de.json @@ -17,7 +17,9 @@ "TITLE": "Freihändig-Modus", "NONE": "Aus", "HMD": "HMD + Kneifen", - "EYE_TRACKING": "Auge + Kneifen" + "EYE_TRACKING": "Auge + Kneifen", + "EYE_ONLY": "Augenlaser", + "HMD_ONLY": "HMD-Laser" } }, "WATCH": { @@ -133,5 +135,21 @@ "WAITING_FOR_USER_DESC": "Überprüfen Sie Ihren Desktop auf ein Pop-up zur Bildschirmübertragung.\nWählen Sie Folgendes aus:", "WAITING_FOR_USER": "Überprüfen Sie Ihren Desktop auf ein Pop-up zur Bildschirmübertragung.", "FAIL": "Bildschirmübertragungsquelle konnte nicht initialisiert werden." + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "Richten Sie den Cursor auf eine App und lassen Sie die Super-Taste los, um diese App zu fokussieren.", + "WHILE_AIMING": "Beim Zielen", + "CLICK": "VR-Pointer-Klick", + "GRABALL": "Gesamtes Set bewegen", + "GRAB": "Einzelnes Overlay bewegen", + "DASH": "Dashboard umschalten", + "EDIT": "Bearbeitungsmodus umschalten", + "SET": "Set wechseln", + "RECENTER": "Anzeigen/Ausblenden/Zentrieren", + "GENERAL_BINDS": "Allgemeine Belegungen", + "TOGGLE_CAPTURE": "Kontrolle an Desktop zurückgeben", + "FOCUS_MODE": "Fokus-Modus (Super gedrückt halten)", + "CLOSE": "Fokussierte App schließen", + "MOUSE": "Echte Mausklicks & Scrollen werden auch auf den VR-Zeiger übertragen" } -} +} \ No newline at end of file diff --git a/wayvr/src/assets/lang/en.json b/wayvr/src/assets/lang/en.json index 4912bf38..bb9cdfeb 100644 --- a/wayvr/src/assets/lang/en.json +++ b/wayvr/src/assets/lang/en.json @@ -78,6 +78,22 @@ "TITLE": "3D Stereo Mode" } }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "Aim at an app and release Super to focus that app.", + "WHILE_AIMING": "While aiming", + "CLICK": "VR pointer click", + "GRABALL": "Move entire set", + "GRAB": "Move individual overlay", + "DASH": "Toggle dashboard", + "EDIT": "Toggle edit mode", + "SET": "Switch set", + "RECENTER": "Show/hide/recenter", + "GENERAL_BINDS": "General bindings", + "TOGGLE_CAPTURE": "Give control back to desktop", + "FOCUS_MODE": "Focus mode (hold Super)", + "CLOSE": "Close focused app", + "MOUSE": "Real mouse clicks & scroll also translate to VR pointer" + }, "GRAB": { "ADJUST_DISTANCE": "Adjust distance", "ADJUST_SIZE": "Adjust size", diff --git a/wayvr/src/assets/lang/es.json b/wayvr/src/assets/lang/es.json index afda655b..98bb314d 100644 --- a/wayvr/src/assets/lang/es.json +++ b/wayvr/src/assets/lang/es.json @@ -17,7 +17,9 @@ "TITLE": "Modo manos libres", "NONE": "Desactivado", "HMD": "HMD + pellizco", - "EYE_TRACKING": "Ojo + pellizco" + "EYE_TRACKING": "Ojo + pellizco", + "EYE_ONLY": "Láser ocular", + "HMD_ONLY": "Láser del HMD" } }, "WATCH": { @@ -133,5 +135,21 @@ "WAITING_FOR_USER_DESC": "Busca la ventana emergente de transmisión de pantalla en tu escritorio.\nSelecciona lo siguiente:", "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." + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "Apunta a una aplicación y suelta Super para enfocar esa aplicación.", + "WHILE_AIMING": "Mientras se apunta", + "CLICK": "Clic del puntero de VR", + "GRABALL": "Mover set completo", + "GRAB": "Mover superposición individual", + "DASH": "Alternar tablero", + "EDIT": "Alternar modo de edición", + "SET": "Cambiar set", + "RECENTER": "Mostrar/ocultar/recentrar", + "GENERAL_BINDS": "Asignaciones generales", + "TOGGLE_CAPTURE": "Devolver el control al escritorio", + "FOCUS_MODE": "Modo de enfoque (mantener Super)", + "CLOSE": "Cerrar aplicación enfocada", + "MOUSE": "Los clics y el desplazamiento del ratón real también se traducen al puntero de VR" } -} +} \ No newline at end of file diff --git a/wayvr/src/assets/lang/it.json b/wayvr/src/assets/lang/it.json index bb4709d7..328ab119 100644 --- a/wayvr/src/assets/lang/it.json +++ b/wayvr/src/assets/lang/it.json @@ -17,7 +17,9 @@ "TITLE": "Modalità a mani libere", "NONE": "Disattivato", "HMD": "HMD + pizzico", - "EYE_TRACKING": "Occhio + pizzico" + "EYE_TRACKING": "Occhio + pizzico", + "EYE_ONLY": "Laser oculare", + "HMD_ONLY": "Laser HMD" } }, "DEFAULT": "Predefinito", @@ -131,5 +133,21 @@ "WAITING_FOR_USER_DESC": "Controlla il desktop per un pop-up di condivisione schermo.\nSeleziona quanto segue:", "WAITING_FOR_USER": "Controlla il desktop per un pop-up di condivisione schermo.", "FAIL": "Impossibile inizializzare la sorgente di trasmissione dello schermo." + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "Mira a un'app e rilascia il tasto Super per focalizzare l'app.", + "WHILE_AIMING": "Mentre si mira", + "CLICK": "Click del puntatore VR", + "GRABALL": "Sposta l'intero set", + "GRAB": "Sposta overlay singolo", + "DASH": "Attiva/disattiva dashboard", + "EDIT": "Attiva/disattiva modalità modifica", + "SET": "Cambia set", + "RECENTER": "Mostra/nascondi/ricentra", + "GENERAL_BINDS": "Binding generali", + "TOGGLE_CAPTURE": "Restituisci il controllo al desktop", + "FOCUS_MODE": "Modalità focus (tieni premuto Super)", + "CLOSE": "Chiudi l'app focalizzata", + "MOUSE": "I clic e lo scorrimento del mouse reale vengono tradotti anche nel puntatore VR" } -} +} \ No newline at end of file diff --git a/wayvr/src/assets/lang/ja.json b/wayvr/src/assets/lang/ja.json index 62403976..1f3886b8 100644 --- a/wayvr/src/assets/lang/ja.json +++ b/wayvr/src/assets/lang/ja.json @@ -17,7 +17,9 @@ "TITLE": "ハンズフリーモード", "NONE": "オフ", "HMD": "HMD + ピンチ", - "EYE_TRACKING": "視線 + ピンチ" + "EYE_TRACKING": "視線 + ピンチ", + "EYE_ONLY": "アイルーザー", + "HMD_ONLY": "HMDレーザー" } }, "WATCH": { @@ -131,5 +133,21 @@ "WAITING_FOR_USER_DESC": "デスクトップのスクリーンキャストのポップアップを確認してください。\n以下を選択してください:", "WAITING_FOR_USER": "デスクトップにスクリーンキャストのポップアップが表示されていないか確認してください。", "FAIL": "スクリーンキャストのソースを初期化できませんでした。" + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "アプリを指してSuperキーを離すと、そのアプリにフォーカスします。", + "WHILE_AIMING": "照準合わせ中", + "CLICK": "VRポインタークリック", + "GRABALL": "セット全体を移動", + "GRAB": "個別のオーバーレイを移動", + "DASH": "ダッシュボードの切り替え", + "EDIT": "編集モードを切り替える", + "SET": "セットを切り替える", + "RECENTER": "表示/非表示/センター合わせ", + "GENERAL_BINDS": "一般バインド", + "TOGGLE_CAPTURE": "コントロールをデスクトップに戻す", + "FOCUS_MODE": "フォーカスモード (Superキーを長押し)", + "CLOSE": "フォーカス中のアプリを閉じる", + "MOUSE": "実際のマウスのクリックとスクロールもVRポインターに反映されます" } -} +} \ No newline at end of file diff --git a/wayvr/src/assets/lang/pl.json b/wayvr/src/assets/lang/pl.json index 1bbdaa96..f9afd408 100644 --- a/wayvr/src/assets/lang/pl.json +++ b/wayvr/src/assets/lang/pl.json @@ -17,7 +17,9 @@ "TITLE": "Tryb wolnych rąk", "NONE": "Wyłączone", "HMD": "HMD + uszczypnięcie", - "EYE_TRACKING": "Oko + uszczypnięcie" + "EYE_TRACKING": "Oko + uszczypnięcie", + "EYE_ONLY": "Laser wzrokowy", + "HMD_ONLY": "Laser w HMD" } }, "WATCH": { @@ -131,5 +133,21 @@ "WAITING_FOR_USER_DESC": "Sprawdź na pulpicie okno wyboru ekranu do transmitowania.\nWybierz następujące:", "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." + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "Wyceluj w aplikację i puść klawisz Super, aby ją przenieść na pierwszy plan.", + "WHILE_AIMING": "Podczas celowania", + "CLICK": "Kliknięcie wskaźnikiem VR", + "GRABALL": "Przesuń cały zestaw", + "GRAB": "Przesuń pojedynczy overlay", + "DASH": "Przełącz pulpit sterowania", + "EDIT": "Przełącz tryb edycji", + "SET": "Zmień zestaw", + "RECENTER": "Pokaż/ukryj/wycentruj", + "GENERAL_BINDS": "Ogólne przypisania", + "TOGGLE_CAPTURE": "Oddaj kontrolę do pulpitu", + "FOCUS_MODE": "Tryb skupienia (przytrzymaj klawisz Super)", + "CLOSE": "Zamknij aktualną aplikację", + "MOUSE": "Kliknięcia i przewijanie fizycznej myszy są również przeniesione na wskaźnik VR" } -} +} \ 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 ee9efab5..67a8b952 100644 --- a/wayvr/src/assets/lang/zh_CN.json +++ b/wayvr/src/assets/lang/zh_CN.json @@ -17,7 +17,9 @@ "TITLE": "免提模式", "NONE": "关闭", "HMD": "HMD + 捏合", - "EYE_TRACKING": "眼动 + 捏合" + "EYE_TRACKING": "眼动 + 捏合", + "EYE_ONLY": "眼睛激光", + "HMD_ONLY": "头显射线" } }, "DEFAULT": "默认", @@ -131,5 +133,21 @@ "WAITING_FOR_USER_DESC": "请检查桌面上的屏幕投射弹出窗口。\n选择以下选项:", "WAITING_FOR_USER": "请检查桌面上的屏幕投射弹出窗口。", "FAIL": "无法初始化屏幕镜像源。" + }, + "ALTTAB": { + "RELEASE_TO_FOCUS": "瞄准一个应用程序并松开 Super 键以将焦点切换到该程序。", + "WHILE_AIMING": "瞄准时", + "CLICK": "VR 指针点击", + "GRABALL": "移动整个集", + "GRAB": "移动单个覆盖层", + "DASH": "切换控制面板", + "EDIT": "切换编辑模式", + "SET": "切换 Set", + "RECENTER": "显示/隐藏/重新中心化", + "GENERAL_BINDS": "通用绑定", + "TOGGLE_CAPTURE": "将控制权还给桌面", + "FOCUS_MODE": "焦点模式 (按住 Super 键)", + "CLOSE": "关闭当前焦点应用", + "MOUSE": "真实鼠标的点击和滚动也将转换为 VR 指针" } -} +} \ No newline at end of file diff --git a/wayvr/src/assets/sound/wvr_input_capture_grabbed.mp3 b/wayvr/src/assets/sound/wvr_input_capture_grabbed.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a247e12edfc65dcae8ef220bcce7aa8741fb6381 GIT binary patch literal 6151 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 O=huAvRq5}Rw|@XKI{K0T literal 0 HcmV?d00001 diff --git a/wayvr/src/assets/sound/wvr_input_capture_ungrabbed.mp3 b/wayvr/src/assets/sound/wvr_input_capture_ungrabbed.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3f8145069d9b83fd92b26c5046aaca4da02966bf GIT binary patch literal 6830 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, horizontal_v120_remainder: f64, vertical_v120_remainder: f64, + aiming: bool, } enum ProcessResult { Continue, - EmergencyUngrab, + ForceUngrab, ReceiverGone, } @@ -471,7 +468,7 @@ fn worker_main( pointer_accel, ) { ProcessResult::Continue => {} - ProcessResult::EmergencyUngrab => { + ProcessResult::ForceUngrab => { emergency_ungrab = true; break; } @@ -551,33 +548,32 @@ fn process_libinput_event( } if !grabbed { - // while ungrabbed, SuperTab will arm an automatic grab + // while ungrabbed, arm an automatic grab that triggers after release if allow_deferred_grab - && key_combo_is_pressed(KeyCombo::SwitchApp, &state.pressed_keys) + && key_combo_is_pressed(KeyCombo::GrabRelease, &state.pressed_keys) { *pending_grab = true; } return ProcessResult::Continue; - } - - if pressed && key_combo_is_pressed(KeyCombo::EmergencyRelease, &state.pressed_keys) { - log::info!("Ctrl+Alt+Del pressed, ungrabbing all input devices"); - return ProcessResult::EmergencyUngrab; + } else if pressed && key_combo_is_pressed(KeyCombo::GrabRelease, &state.pressed_keys) { + log::info!("Ungrabbing all input devices"); + return ProcessResult::ForceUngrab; } // while grabbed, these combos get emitted as normal - for combo in [ - KeyCombo::SwitchApp, - KeyCombo::CloseApp, - KeyCombo::Set1, - KeyCombo::Set2, - KeyCombo::Set3, - KeyCombo::Set4, - ] { - let combo_pressed = key_combo_is_pressed(combo, &state.pressed_keys); + for combo in [KeyCombo::SwitchApp, KeyCombo::CloseApp] { + let mut combo_pressed = key_combo_is_pressed(combo, &state.pressed_keys); let was_pressed = state.active_combos.contains(&combo); + if matches!(combo, KeyCombo::SwitchApp) + && was_pressed + && mod_super(&state.pressed_keys) + { + // keep SwitchApp active until Super is released + combo_pressed = true; + } + if combo_pressed == was_pressed { continue; } @@ -792,6 +788,7 @@ fn clear_transient_state(runtime_devices: &mut HashMap [KeyCode; 8] { ] } +fn mod_alt(pressed: &HashSet) -> bool { + pressed.contains(&KeyCode::KEY_LEFTALT.0) || pressed.contains(&KeyCode::KEY_RIGHTALT.0) +} + +fn mod_super(pressed: &HashSet) -> bool { + pressed.contains(&KeyCode::KEY_LEFTMETA.0) || pressed.contains(&KeyCode::KEY_RIGHTMETA.0) +} + fn key_combo_is_pressed(combo: KeyCombo, pressed: &HashSet) -> bool { - let alt = - pressed.contains(&KeyCode::KEY_LEFTALT.0) || pressed.contains(&KeyCode::KEY_RIGHTALT.0); - - // 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); - match combo { - KeyCombo::CloseApp => alt && pressed.contains(&KeyCode::KEY_F4.0), - KeyCombo::EmergencyRelease => meta && pressed.contains(&KeyCode::KEY_BACKSPACE.0), - KeyCombo::SwitchApp => meta && pressed.contains(&KeyCode::KEY_X.0), - KeyCombo::Set1 => meta && pressed.contains(&KeyCode::KEY_1.0), - KeyCombo::Set2 => meta && pressed.contains(&KeyCode::KEY_2.0), - KeyCombo::Set3 => meta && pressed.contains(&KeyCode::KEY_3.0), - KeyCombo::Set4 => meta && pressed.contains(&KeyCode::KEY_4.0), + KeyCombo::CloseApp => mod_alt(pressed) && pressed.contains(&KeyCode::KEY_F4.0), + KeyCombo::GrabRelease => mod_super(pressed) && pressed.contains(&KeyCode::KEY_Z.0), + KeyCombo::SwitchApp => mod_super(pressed) && pressed.contains(&KeyCode::KEY_X.0), } } diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index ea86326f..e4d0f38b 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -58,7 +58,11 @@ use wayland_protocols::xdg::shell::server::xdg_toplevel; use wayvr_ipc::{packet_client::PositionMode, packet_server}; use wgui::{gfx::WGfx, log::LogErr}; use wlx_capture::frame::Transform; -use wlx_common::{config::GeneralConfig, desktop_finder::DesktopFinder}; +use wlx_common::{ + audio::{AudioSystem, SamplePlayer}, + config::{GeneralConfig, InputCaptureMethod}, + desktop_finder::DesktopFinder, +}; use xkbcommon::xkb; use crate::{ @@ -73,7 +77,10 @@ use crate::{ }, graphics::{ExtentExt, WGfxExtras}, ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal}, - overlays::wayvr::{WvrCommand, create_wl_window_overlay}, + overlays::{ + anchor::ALTTAB_HELP_NAME, + wayvr::{WvrCommand, create_wl_window_overlay}, + }, state::AppState, subsystem::{ dbus::DbusConnector, @@ -139,6 +146,7 @@ pub struct WvrServerState { overlay_to_window: SecondaryMap, process_overlays: HashMap>, input_capture: Option, + input_capture_init_attempted: bool, has_input_focus: bool, grab_toast_sent: bool, } @@ -263,10 +271,6 @@ 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, @@ -304,7 +308,8 @@ impl WvrServerState { window_to_overlay: HashMap::new(), overlay_to_window: SecondaryMap::new(), process_overlays: HashMap::new(), - input_capture, + input_capture: None, + input_capture_init_attempted: false, has_input_focus: false, grab_toast_sent: false, }) @@ -643,6 +648,9 @@ impl WvrServerState { &mut app.input_state, &mut app.tasks, &mut app.hid_provider, + &mut app.audio_sample_player, + &mut app.audio_system, + &app.session.config, ); wvr_server.ticks += 1; @@ -759,9 +767,42 @@ impl WvrServerState { input_state: &mut InputState, tasks: &mut TaskContainer, hid_wrapper: &mut HidWrapper, + audio_sample_player: &mut SamplePlayer, + audio_system: &mut AudioSystem, + config: &GeneralConfig, ) { - let Some(input_capture) = self.input_capture.as_mut() else { - return; + let input_capture = match (self.input_capture.as_mut(), config.wvr_input_capture) { + (Some(cap), InputCaptureMethod::Evdev) => cap, + (Some(_), InputCaptureMethod::None) => { + self.input_capture.take(); + return; + } + (None, InputCaptureMethod::Evdev) if !self.input_capture_init_attempted => { + self.input_capture = InputCapture::new() + .log_err("Could not initialize evdev input capture") + .ok(); + self.input_capture_init_attempted = true; + if let Some(cap) = self.input_capture.as_mut() { + cap + } else { + let _ = DbusConnector::notify_send( + "Could not initialize keyboard/mouse capture!", + "Check that your user is in the input group.", + 1, + 5000, + 0, + true, + ); + return; + } + } + (None, InputCaptureMethod::Evdev) => { + return; + } + (None, InputCaptureMethod::None) => { + self.input_capture_init_attempted = false; + return; + } }; let mut mouse_delta = DVec2::ZERO; @@ -775,21 +816,33 @@ impl WvrServerState { 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 { + } else if (hid::VirtualKey::A as u32) == vk { input_state.handsfree_state.grab = pressed; - } - if (hid::VirtualKey::W as u32) == vk { + } else if (hid::VirtualKey::W as u32) == vk { input_state.handsfree_state.click = pressed; - } - if (hid::VirtualKey::E as u32) == vk && pressed { + } else if (hid::VirtualKey::E as u32) == vk && pressed { tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode)); - } - if (hid::VirtualKey::D as u32) == vk && pressed { + } else if (hid::VirtualKey::D as u32) == vk && pressed { tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); + } else if (hid::VirtualKey::R as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ShowHide)); + } else if (hid::VirtualKey::N1 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(0))) + } else if (hid::VirtualKey::N2 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(1))) + } else if (hid::VirtualKey::N3 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(2))) + } else if (hid::VirtualKey::N4 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(3))) + } else if (hid::VirtualKey::N5 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(4))) + } else if (hid::VirtualKey::N6 as u32) == vk && pressed { + tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(5))) } - } else { + } else if self.has_input_focus { self.manager.send_key(vk, pressed); + } else if let Some(vk) = hid::VirtualKey::from_repr(vk as u16) { + hid_wrapper.inner.send_key(vk, pressed); } } input_capture::CapturedEvent::PointerButton { button, pressed } => { @@ -808,8 +861,10 @@ impl WvrServerState { } _ => {} } - } else { + } else if self.has_input_focus { self.manager.send_pointer_button(mouse_index, pressed); + } else { + hid_wrapper.inner.send_button(button as u16, pressed); } } input_capture::CapturedEvent::PointerMotion { @@ -828,23 +883,42 @@ impl WvrServerState { horizontal_v120, vertical_v120, } => { - let delta = WheelDelta { + let mut delta = WheelDelta { x: horizontal_v120 as f32, y: vertical_v120 as f32, }; if input_state.picking_focus.is_aiming() { input_state.handsfree_state.scroll_y = (vertical_v120 as f32) * -0.12; - } else { + } else if self.has_input_focus { self.manager.send_pointer_axis_wheel_raw(delta); + } else { + delta.y *= -1.0; + hid_wrapper.inner.wheel(delta); } } input_capture::CapturedEvent::Grabbed => { hid_wrapper.set_input_focus(Some(self), InputFocus::WayVR); + audio_sample_player.play_sample(audio_system, "input_grab"); + if !self.grab_toast_sent { + self.grab_toast_sent = true; + //TODO: toast + let _ = DbusConnector::notify_send( + "WayVR has your keyboard and mouse!", + "Super+Z to release", + 1, + 5000, + 0, + true, + ); + } } input_capture::CapturedEvent::Ungrabbed => { + hid_wrapper.inner.send_key(hid::VirtualKey::LSuper, false); + hid_wrapper.inner.send_key(hid::VirtualKey::RSuper, false); self.manager.release_all_keys(); self.has_input_focus = false; self.wm.keyboard_focus = None; + audio_sample_player.play_sample(audio_system, "input_ungrab"); } input_capture::CapturedEvent::KeyCombo { combo, pressed } => match combo { input_capture::KeyCombo::CloseApp if pressed => { @@ -856,9 +930,21 @@ impl WvrServerState { input_capture::KeyCombo::SwitchApp => { if pressed { input_state.picking_focus = super::input::FocusPickState::Aiming; + tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(ALTTAB_HELP_NAME.clone()), + Box::new(move |app, o| { + o.activate(app); + }), + ))); } else { input_state.picking_focus = super::input::FocusPickState::Picking; + tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(ALTTAB_HELP_NAME.clone()), + Box::new(move |_app, o| { + o.deactivate(); + }), + ))); tasks.enqueue_at( TaskType::Global(Box::new(|app| { app.input_state.picking_focus = @@ -866,66 +952,61 @@ impl WvrServerState { let _ = std::mem::take(&mut app.input_state.handsfree_state); })), - Instant::now() + Duration::from_millis(33), + Instant::now() + Duration::from_millis(50), ); } } - input_capture::KeyCombo::Set1 if pressed => { - tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(0))) - } - input_capture::KeyCombo::Set2 if pressed => { - tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(1))) - } - input_capture::KeyCombo::Set3 if pressed => { - tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(2))) - } - input_capture::KeyCombo::Set4 if pressed => { - tasks.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(3))) - } _ => {} }, } } if mouse_delta.length_squared() > 1e-6 { - 'mouse_update: { - let Some(ref hover) = self.wm.mouse else { - break 'mouse_update; - }; + if self.has_input_focus { + 'mouse_update: { + let Some(ref hover) = self.wm.mouse else { + break 'mouse_update; + }; - let Some(window) = self.wm.windows.get(&hover.hover_window) else { - break 'mouse_update; - }; - let toplevel = window.toplevel.wl_surface().clone(); - let inner_extent = with_states(&toplevel, |states| { - SurfaceBufWithImage::get_from_surface(states) - .map(|s| s.image.extent_u32arr()) - .unwrap_or([1, 1]) - }); - let Some(hit_ctx) = - build_hit_context(&toplevel, &self.manager.state.popup_manager, inner_extent) - else { - break 'mouse_update; - }; + let Some(window) = self.wm.windows.get(&hover.hover_window) else { + break 'mouse_update; + }; + let toplevel = window.toplevel.wl_surface().clone(); + let inner_extent = with_states(&toplevel, |states| { + SurfaceBufWithImage::get_from_surface(states) + .map(|s| s.image.extent_u32arr()) + .unwrap_or([1, 1]) + }); + let Some(hit_ctx) = build_hit_context( + &toplevel, + &self.manager.state.popup_manager, + inner_extent, + ) else { + break 'mouse_update; + }; - let new_x = (hover.pos.x + mouse_delta.x).clamp(0., inner_extent[0] as f64); - let new_y = (hover.pos.y + mouse_delta.y).clamp(0., inner_extent[1] as f64); - let new_pos = DVec2::new(new_x, new_y); - let new_pos_f32 = Vec2::new(new_x as f32, new_y as f32); + let new_x = (hover.pos.x + mouse_delta.x).clamp(0., inner_extent[0] as f64); + let new_y = (hover.pos.y + mouse_delta.y).clamp(0., inner_extent[1] as f64); + let new_pos = DVec2::new(new_x, new_y); + let new_pos_f32 = Vec2::new(new_x as f32, new_y as f32); - let target = hit_ctx.hit_target_at(new_pos_f32); - let focus_target = self.hit_target_to_focus( - target.unwrap_or(WvrHitTarget::Toplevel { pos: new_pos_f32 }), - hover.hover_window, - new_pos_f32, - ); - self.send_mouse_move_relative( - focus_target, - new_pos, - mouse_delta, - mouse_delta_raw, - hover.hover_window, - ); + let target = hit_ctx.hit_target_at(new_pos_f32); + let focus_target = self.hit_target_to_focus( + target.unwrap_or(WvrHitTarget::Toplevel { pos: new_pos_f32 }), + hover.hover_window, + new_pos_f32, + ); + self.send_mouse_move_relative( + focus_target, + new_pos, + mouse_delta, + mouse_delta_raw, + hover.hover_window, + ); + } + } else { + // no has_input_focus → send back to desktop + hid_wrapper.inner.mouse_move_rel(mouse_delta); } } } @@ -941,27 +1022,6 @@ 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; - }; - - let res = input_capture - .set_grabbed(has_focus) - .log_err("Could not set input grab"); - - 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; if !has_focus { self.wm.mouse = None; diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 97603645..eb6fa871 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -10,7 +10,8 @@ use wlx_common::{ astr_containers::AStrMap, config::{ AltModifier, CaptureMethod, ChromaKeyParams, GeneralConfig, HandsfreeAltTab, - HandsfreePointer, InputEmulationMethod, SerializedWindowSet, SerializedWindowStates, + HandsfreePointer, InputCaptureMethod, InputEmulationMethod, SerializedWindowSet, + SerializedWindowStates, }, config_io, locale::Language, @@ -191,6 +192,7 @@ pub struct AutoSettings { pub snap_angle_deg: f32, pub wvr_mouse_acceleration: bool, pub wvr_mouse_speed: f32, + pub wvr_input_capture: InputCaptureMethod, } fn get_settings_path() -> PathBuf { @@ -260,6 +262,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { snap_angle_deg: config.snap_angle_deg, wvr_mouse_acceleration: config.wvr_mouse_acceleration, wvr_mouse_speed: config.wvr_mouse_speed, + wvr_input_capture: config.wvr_input_capture, }; let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic diff --git a/wayvr/src/overlays/anchor.rs b/wayvr/src/overlays/anchor.rs index 8ca6c497..b96fdbcd 100644 --- a/wayvr/src/overlays/anchor.rs +++ b/wayvr/src/overlays/anchor.rs @@ -1,4 +1,4 @@ -use glam::{Affine3A, Quat, Vec3}; +use glam::{Affine3A, Quat, Vec3, vec3}; use std::sync::{Arc, LazyLock}; use wgui::event::EventAlterables; use wgui::parser::Fetchable; @@ -9,7 +9,7 @@ use crate::overlays::watch::WATCH_NAME; use crate::state::AppState; use crate::windowing::backend::OverlayEventData; use crate::windowing::window::OverlayWindowConfig; -use crate::windowing::{Z_ORDER_ANCHOR, Z_ORDER_HELP}; +use crate::windowing::{PIXELS_TO_METERS, Z_ORDER_ANCHOR, Z_ORDER_HELP}; pub static ANCHOR_NAME: LazyLock> = LazyLock::new(|| Arc::from("anchor")); @@ -108,3 +108,29 @@ pub fn create_grab_help(app: &mut AppState) -> anyhow::Result> = LazyLock::new(|| Arc::from("alttab-help")); + +pub fn create_alltab_help(app: &mut AppState) -> anyhow::Result { + let mut panel = + GuiPanel::new_from_template(app, "gui/alttab-help.xml", (), Default::default())?; + panel.update_layout(app)?; + + Ok(OverlayWindowConfig { + name: ALTTAB_HELP_NAME.clone(), + z_order: Z_ORDER_HELP, + default_state: OverlayWindowState { + interactable: false, + grabbable: false, + transform: Affine3A::from_scale_rotation_translation( + Vec3::ONE * panel.layout.content_size.x * PIXELS_TO_METERS, + Quat::IDENTITY, + vec3(0., -0.2, -0.51), // 1 cm behind toast + ), + positioning: Positioning::FollowHead { lerp: 0.1 }, + ..OverlayWindowState::default() + }, + global: true, + ..OverlayWindowConfig::from_backend(Box::new(panel)) + }) +} diff --git a/wayvr/src/overlays/screen/backend.rs b/wayvr/src/overlays/screen/backend.rs index 0fa1484f..fb03e281 100644 --- a/wayvr/src/overlays/screen/backend.rs +++ b/wayvr/src/overlays/screen/backend.rs @@ -3,7 +3,7 @@ use std::{ time::Instant, }; -use glam::{Affine2, Vec2, vec2}; +use glam::{Affine2, DVec2, Vec2, vec2}; use wlx_capture::{WlxCapture, frame::Transform}; use crate::{ @@ -314,7 +314,9 @@ impl OverlayBackend for ScreenBackend { || app.input_state.pointers[hit.pointer].now.move_mouse) { let pos = self.mouse_transform.transform_point2(hit.uv); - app.hid_provider.inner.mouse_move(pos); + app.hid_provider + .inner + .mouse_move(DVec2::new(pos.x as _, pos.y as _)); set_next_move(app.session.config.mouse_move_interval_ms as _); } HoverResult::consume() @@ -345,7 +347,9 @@ impl OverlayBackend for ScreenBackend { return; } let pos = self.mouse_transform.transform_point2(hit.uv); - app.hid_provider.inner.mouse_move(pos); + app.hid_provider + .inner + .mouse_move(DVec2::new(pos.x as _, pos.y as _)); } fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta: WheelDelta) { diff --git a/wayvr/src/overlays/screen/wl.rs b/wayvr/src/overlays/screen/wl.rs index 5c264aec..ba1692a6 100644 --- a/wayvr/src/overlays/screen/wl.rs +++ b/wayvr/src/overlays/screen/wl.rs @@ -1,4 +1,4 @@ -use glam::vec2; +use glam::{DVec2, vec2}; use wgui::log::LogErr; use wlx_capture::{ WlxCapture, @@ -116,10 +116,10 @@ pub fn create_screens_wayland( app.hid_provider .inner - .set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32)); + .set_desktop_extent(DVec2::new(extent.0 as _, extent.1 as _)); app.hid_provider .inner - .set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32)); + .set_desktop_origin(DVec2::new(origin.0 as _, origin.1 as _)); Ok(ScreenCreateData { screens }) } diff --git a/wayvr/src/overlays/screen/x11.rs b/wayvr/src/overlays/screen/x11.rs index bbca9577..783f518e 100644 --- a/wayvr/src/overlays/screen/x11.rs +++ b/wayvr/src/overlays/screen/x11.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use glam::vec2; +use glam::{DVec2, vec2}; use wlx_capture::{ WlxCapture, frame::Transform, @@ -42,7 +42,6 @@ impl ScreenBackend { #[cfg(feature = "pipewire")] pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result { - use glam::vec2; use wlx_capture::xshm::xshm_get_monitors; use wlx_common::astr_containers::AStrMapExt; @@ -91,7 +90,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result anyhow::Result anyhow::Result( pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result { use wlx_capture::xshm::xshm_get_monitors; - let mut extent = vec2(0., 0.); + let mut extent = DVec2::ZERO; let monitors = match xshm_get_monitors() { Ok(m) => m, @@ -181,8 +180,8 @@ pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result anyhow::Result> = LazyLock::new(|| "toast".into()); pub struct Toast { diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 60f63b69..c95f40f1 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -148,6 +148,22 @@ impl AppState { ))?, )?; + audio_sample_player.register_sample( + "input_grab", + audio::AudioSample::from_mp3(&audio::AudioSample::bytes_from_config_or_default( + "sound/wvr_input_capture_grabbed.mp3", + include_bytes!("assets/sound/wvr_input_capture_grabbed.mp3"), + ))?, + )?; + + audio_sample_player.register_sample( + "input_ungrab", + audio::AudioSample::from_mp3(&audio::AudioSample::bytes_from_config_or_default( + "sound/wvr_input_capture_ungrabbed.mp3", + include_bytes!("assets/sound/wvr_input_capture_ungrabbed.mp3"), + ))?, + )?; + let mut assets = Box::new(gui::asset::GuiAsset {}); audio_sample_player.register_wgui_samples(assets.as_mut())?; diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index 30f9312d..0b737ac0 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -1,10 +1,10 @@ -use glam::Vec2; +use glam::DVec2; use idmap::{IdMap, idmap}; use idmap_derive::IntegerId; use libc::{input_event, timeval}; use serde::Deserialize; use std::sync::LazyLock; -use strum::{EnumIter, EnumString}; +use strum::{EnumIter, EnumString, FromRepr}; use xkbcommon::xkb; #[cfg(feature = "wayland")] @@ -27,8 +27,8 @@ struct MouseButtonAction { #[derive(Default)] struct MouseAction { - last_requested_pos: Option, - pos: Option, + last_requested_pos: Option, + pos: Option, button: Option, scroll: Option, } @@ -37,7 +37,7 @@ pub const MOUSE_LEFT: u16 = 0x110; pub const MOUSE_RIGHT: u16 = 0x111; pub const MOUSE_MIDDLE: u16 = 0x112; -const MOUSE_EXTENT: f32 = 32768.; +const MOUSE_EXTENT: f64 = 32768.; const EV_SYN: u16 = 0x0; const EV_KEY: u16 = 0x1; @@ -75,7 +75,9 @@ pub const ALTGR: KeyModifier = 0x80; #[allow(non_camel_case_types)] #[repr(u16)] -#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)] +#[derive( + Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter, FromRepr, +)] pub enum VirtualKey { Escape = 9, N1, // number row diff --git a/wayvr/src/subsystem/hid/provider/dummy.rs b/wayvr/src/subsystem/hid/provider/dummy.rs index b05afdc1..fd0bd682 100644 --- a/wayvr/src/subsystem/hid/provider/dummy.rs +++ b/wayvr/src/subsystem/hid/provider/dummy.rs @@ -1,16 +1,17 @@ use crate::overlays::toast::Toast; use crate::subsystem::hid::provider::HidProvider; use crate::subsystem::hid::{VirtualKey, WheelDelta, XkbKeymap}; -use glam::Vec2; +use glam::DVec2; pub struct DummyProvider; impl HidProvider for DummyProvider { - fn mouse_move(&mut self, _pos: Vec2) {} + fn mouse_move(&mut self, _pos: DVec2) {} + fn mouse_move_rel(&mut self, _pos: DVec2) {} fn send_button(&mut self, _button: u16, _down: bool) {} fn wheel(&mut self, _delta: WheelDelta) {} - fn set_desktop_extent(&mut self, _extent: Vec2) {} - fn set_desktop_origin(&mut self, _origin: Vec2) {} + fn set_desktop_extent(&mut self, _extent: DVec2) {} + fn set_desktop_origin(&mut self, _origin: DVec2) {} fn set_modifiers(&mut self, _modifiers: u8) {} fn send_key(&self, _key: VirtualKey, _down: bool) {} diff --git a/wayvr/src/subsystem/hid/provider/mod.rs b/wayvr/src/subsystem/hid/provider/mod.rs index f3dbb7f6..770956cd 100644 --- a/wayvr/src/subsystem/hid/provider/mod.rs +++ b/wayvr/src/subsystem/hid/provider/mod.rs @@ -1,5 +1,5 @@ use crate::subsystem::hid::{VirtualKey, WheelDelta, XkbKeymap}; -use glam::Vec2; +use glam::DVec2; pub mod dummy; pub mod uinput; @@ -7,11 +7,12 @@ pub mod wl_virtual; pub trait HidProvider: Sync + Send { // Pointer Functions - fn mouse_move(&mut self, pos: Vec2); + fn mouse_move(&mut self, pos: DVec2); + fn mouse_move_rel(&mut self, rel_pos: DVec2); fn send_button(&mut self, button: u16, down: bool); fn wheel(&mut self, delta: WheelDelta); - fn set_desktop_extent(&mut self, extent: Vec2); - fn set_desktop_origin(&mut self, origin: Vec2); + fn set_desktop_extent(&mut self, extent: DVec2); + fn set_desktop_origin(&mut self, origin: DVec2); // Keyboard Functions fn set_modifiers(&mut self, mods: u8); diff --git a/wayvr/src/subsystem/hid/provider/uinput.rs b/wayvr/src/subsystem/hid/provider/uinput.rs index 349f3939..8ad6c89d 100644 --- a/wayvr/src/subsystem/hid/provider/uinput.rs +++ b/wayvr/src/subsystem/hid/provider/uinput.rs @@ -4,7 +4,7 @@ use crate::subsystem::hid::{ EV_ABS, EV_KEY, EV_REL, EV_SYN, MODS_TO_KEYS, MOUSE_EXTENT, MOUSE_LEFT, MOUSE_MIDDLE, MouseAction, MouseButtonAction, VirtualKey, WheelDelta, XkbKeymap, get_time, new_event, }; -use glam::Vec2; +use glam::DVec2; use input_linux::{ AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, InputId, Key, RelativeAxis, UInputHandle, @@ -18,8 +18,8 @@ use wlx_common::overlays::ToastTopic; pub struct UInputProvider { keyboard_handle: UInputHandle, mouse_handle: UInputHandle, - desktop_extent: Vec2, - desktop_origin: Vec2, + desktop_extent: DVec2, + desktop_origin: DVec2, cur_modifiers: u8, current_action: MouseAction, } @@ -101,8 +101,8 @@ impl UInputProvider { Some(Self { keyboard_handle, mouse_handle, - desktop_extent: Vec2::ZERO, - desktop_origin: Vec2::ZERO, + desktop_extent: DVec2::ZERO, + desktop_origin: DVec2::ZERO, current_action: MouseAction::default(), cur_modifiers: 0, }) @@ -117,7 +117,7 @@ impl UInputProvider { log::error!("send_button: {res}"); } } - fn mouse_move_internal(&mut self, pos: Vec2) { + fn mouse_move_internal(&mut self, pos: DVec2) { #[cfg(debug_assertions)] log::trace!("Mouse move: {pos:?}"); @@ -157,12 +157,22 @@ impl UInputProvider { } impl HidProvider for UInputProvider { - fn mouse_move(&mut self, pos: Vec2) { + fn mouse_move(&mut self, pos: DVec2) { if self.current_action.pos.is_none() && self.current_action.scroll.is_none() { self.current_action.pos = Some(pos); } self.current_action.last_requested_pos = Some(pos); } + fn mouse_move_rel(&mut self, rel_pos: DVec2) { + let last_pos = self.current_action.last_requested_pos.unwrap_or_default(); + let new_pos = (last_pos + rel_pos).clamp( + self.desktop_origin, + self.desktop_origin + self.desktop_extent, + ); + + self.current_action.pos = Some(new_pos); + self.current_action.last_requested_pos = Some(new_pos); + } fn send_button(&mut self, button: u16, down: bool) { if self.current_action.button.is_none() { self.current_action.button = Some(MouseButtonAction { button, down }); @@ -177,10 +187,10 @@ impl HidProvider for UInputProvider { self.current_action.pos = None; } } - fn set_desktop_extent(&mut self, extent: Vec2) { + fn set_desktop_extent(&mut self, extent: DVec2) { self.desktop_extent = extent; } - fn set_desktop_origin(&mut self, origin: Vec2) { + fn set_desktop_origin(&mut self, origin: DVec2) { self.desktop_origin = origin; } fn set_modifiers(&mut self, modifiers: u8) { diff --git a/wayvr/src/subsystem/hid/provider/wl_virtual.rs b/wayvr/src/subsystem/hid/provider/wl_virtual.rs index 678684f9..c105cd3e 100644 --- a/wayvr/src/subsystem/hid/provider/wl_virtual.rs +++ b/wayvr/src/subsystem/hid/provider/wl_virtual.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::os::fd::AsFd; use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::Context as _; -use glam::Vec2; +use glam::{DVec2}; use input_linux::sys::{*}; use smithay::reexports::rustix::fs::{memfd_create, MemfdFlags}; use smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::client::zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1; @@ -27,18 +27,20 @@ pub struct WlVirtualProvider { state: KbState, virtual_pointer: ZwlrVirtualPointerV1, - desktop_extent: Vec2, - desktop_origin: Vec2, + desktop_extent: DVec2, + desktop_origin: DVec2, keymap_file: Option, virtual_keyboard: ZwpVirtualKeyboardV1, keyboard_mods_state: u8, + + last_pointer_position: DVec2, } struct KbState; impl HidProvider for WlVirtualProvider { - fn mouse_move(&mut self, pos: Vec2) { + fn mouse_move(&mut self, pos: DVec2) { #[cfg(debug_assertions)] log::trace!("Pointer move: {pos:?}"); @@ -51,6 +53,15 @@ impl HidProvider for WlVirtualProvider { ); self.virtual_pointer.motion(Self::now_ms(), 0.0, 0.0); self.virtual_pointer.frame(); + self.last_pointer_position = pos; + } + + fn mouse_move_rel(&mut self, rel_pos: DVec2) { + let pos = (self.last_pointer_position + rel_pos).clamp( + self.desktop_origin, + self.desktop_origin + self.desktop_extent, + ); + self.mouse_move(pos); } fn send_button(&mut self, button: u16, down: bool) { @@ -102,11 +113,11 @@ impl HidProvider for WlVirtualProvider { self.virtual_pointer.frame(); } - fn set_desktop_extent(&mut self, extent: Vec2) { + fn set_desktop_extent(&mut self, extent: DVec2) { self.desktop_extent = extent; } - fn set_desktop_origin(&mut self, origin: Vec2) { + fn set_desktop_origin(&mut self, origin: DVec2) { self.desktop_origin = origin; } @@ -207,9 +218,10 @@ impl WlVirtualProvider { state, virtual_pointer, virtual_keyboard, - desktop_extent: Vec2::ZERO, - desktop_origin: Vec2::ZERO, + desktop_extent: DVec2::ZERO, + desktop_origin: DVec2::ZERO, keyboard_mods_state: 0, + last_pointer_position: DVec2::ZERO, }; result.set_keymap(&XkbKeymap { diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index 41a6878d..02278935 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -19,7 +19,7 @@ use crate::{ backend::task::{CreateOverlayTask, GlobalChange, OverlayTask, SpawnPos, ToggleMode}, config::save_state, overlays::{ - anchor::{create_anchor, create_grab_help}, + anchor::{create_alltab_help, create_anchor, create_grab_help}, custom::create_custom, dashboard::{DASH_NAME, create_dash_frontend}, edit::EditWrapperManager, @@ -139,6 +139,9 @@ where let grab_help = OverlayWindowData::from_config(create_grab_help(app)?); me.add(grab_help, app); + let alttab_help = OverlayWindowData::from_config(create_alltab_help(app)?); + me.add(alttab_help, app); + #[cfg(feature = "whisper")] { use crate::overlays::whisper::create_whisper; diff --git a/wayvr/src/windowing/mod.rs b/wayvr/src/windowing/mod.rs index 5c11272e..358d9dac 100644 --- a/wayvr/src/windowing/mod.rs +++ b/wayvr/src/windowing/mod.rs @@ -20,6 +20,8 @@ pub enum OverlaySelector { Nothing, } +pub const PIXELS_TO_METERS: f32 = 1. / 2000.; + pub const Z_ORDER_TOAST: u32 = 71; pub const Z_ORDER_HELP: u32 = 70; pub const Z_ORDER_LINES: u32 = 69; diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 89c4390c..ab8b320f 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -62,6 +62,19 @@ pub enum InputEmulationMethod { None, } +#[derive(Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)] +pub enum InputCaptureMethod { + #[strum(props(Tooltip = "APP_SETTINGS.OPTION.EVDEV_HELP"))] + Evdev, + + #[default] + #[strum(props( + Translation = "APP_SETTINGS.OPTION.NONE", + Tooltip = "APP_SETTINGS.OPTION.NONE_INPUT_CAPTURE_HELP" + ))] + None, +} + #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)] pub enum AltModifier { #[default] @@ -494,4 +507,7 @@ pub struct GeneralConfig { #[serde(default)] pub wvr_mouse_speed: f32, + + #[serde(default)] + pub wvr_input_capture: InputCaptureMethod, }