From 44b7a2a6ab1a62e6daee8e91aee66e35323da3b8 Mon Sep 17 00:00:00 2001 From: cubee Date: Tue, 28 Jul 2026 17:04:02 +1000 Subject: [PATCH 01/54] make space_reset obey space_drag_affects_world --- wayvr/src/backend/openvr/playspace.rs | 10 ++++++---- wayvr/src/backend/openxr/playspace.rs | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/wayvr/src/backend/openvr/playspace.rs b/wayvr/src/backend/openvr/playspace.rs index 74b8903c..67568160 100644 --- a/wayvr/src/backend/openvr/playspace.rs +++ b/wayvr/src/backend/openvr/playspace.rs @@ -49,7 +49,7 @@ impl PlayspaceMover { self.fix_floor(chaperone_mgr, app, overlays); } PlayspaceTask::Reset => { - self.reset_offset(chaperone_mgr, overlays, &mut app.anchor); + self.reset_offset(chaperone_mgr, app, overlays); } PlayspaceTask::Recenter => { self.recenter(chaperone_mgr, app, overlays); @@ -181,7 +181,7 @@ impl PlayspaceMover { for pointer in &app.input_state.pointers { if pointer.now.space_reset && !pointer.before.space_reset { - self.reset_offset(chaperone_mgr, overlays, &mut app.anchor); + self.reset_offset(chaperone_mgr, app, overlays); log::info!("Space reset"); return; } @@ -191,8 +191,8 @@ impl PlayspaceMover { pub fn reset_offset( &mut self, chaperone_mgr: &mut ChaperoneSetupManager, + app: &mut AppState, overlays: &mut OverlayWindowManager, - anchor: &mut Affine3A, ) { let Some(before) = get_working_copy(&self.universe, chaperone_mgr) else { log::warn!("Can't reset offset - failed to get zero pose"); @@ -207,7 +207,9 @@ impl PlayspaceMover { set_working_copy(&self.universe, chaperone_mgr, &xform); chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live); - playspace_common::shift_world(overlays, anchor, &before, &xform); + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, &mut app.anchor, &before, &xform); + } if self.drag.is_some() { log::info!("Space drag interrupted by manual reset"); diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index e77db07d..106165a1 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -338,8 +338,10 @@ impl PlayspaceMover { let offset = self.playspace_state.openxr_space_center; apply_offset(offset, &mut monado.ipc); - let after = offset; - playspace_common::shift_world(overlays, &mut app.anchor, &before, &after); + if !app.session.config.space_drag_affects_world { + let after = offset; + playspace_common::shift_world(overlays, &mut app.anchor, &before, &after); + } } pub fn fix_floor( From 67877d6a5f34a386fa0766b19103612c9e034c73 Mon Sep 17 00:00:00 2001 From: cubee Date: Tue, 28 Jul 2026 17:48:47 +1000 Subject: [PATCH 02/54] make other `shift_world` obey `space_drag_affects_world`, and overlays follow `space-rotate` --- wayvr/src/backend/openvr/playspace.rs | 12 ++++++++++-- wayvr/src/backend/openxr/playspace.rs | 21 ++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/wayvr/src/backend/openvr/playspace.rs b/wayvr/src/backend/openvr/playspace.rs index 67568160..9290eaf1 100644 --- a/wayvr/src/backend/openvr/playspace.rs +++ b/wayvr/src/backend/openvr/playspace.rs @@ -95,7 +95,11 @@ impl PlayspaceMover { * -1.0; space_transform.translation = offset; + let before_pose = data.pose; data.pose *= space_transform; + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &data.pose); + } data.hand_pose = new_hand; if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding { @@ -243,7 +247,9 @@ impl PlayspaceMover { set_working_copy(&self.universe, chaperone_mgr, &mat); chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live); - playspace_common::shift_world(overlays, anchor, &before, &mat); + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, anchor, &before, &mat); + } if self.drag.is_some() { log::info!("Space drag interrupted by fix floor"); @@ -314,7 +320,9 @@ impl PlayspaceMover { set_working_copy(&self.universe, chaperone_mgr, &new_mat); chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live); - playspace_common::shift_world(overlays, anchor, &before, &new_mat); + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, anchor, &before, &new_mat); + } } pub fn playspace_changed( diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 106165a1..d34c96db 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -114,7 +114,11 @@ impl PlayspaceMover { space_transform.translation = offset; + let before_pose = data.pose; data.pose *= space_transform; + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &data.pose); + } data.hand_pose = new_hand; apply_offset(data.pose, &mut monado.ipc); @@ -228,7 +232,9 @@ impl PlayspaceMover { let after_pose = Affine3A::from_translation(res.playspace_pos.into()); apply_offset(after_pose, &mut monado.ipc); - playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &after_pose); + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &after_pose); + } } } @@ -300,9 +306,11 @@ impl PlayspaceMover { self.gravity.reset(); - let after = - Affine3A::from_rotation_translation(pose.orientation.into(), pose.position.into()); - playspace_common::shift_world(overlays, anchor, &before, &after); + if !app.session.config.space_drag_affects_world { + let after = + Affine3A::from_rotation_translation(pose.orientation.into(), pose.position.into()); + playspace_common::shift_world(overlays, anchor, &before, &after); + } } pub fn reset_offset( @@ -391,7 +399,10 @@ impl PlayspaceMover { let after = Affine3A::from_rotation_translation(pose.orientation.into(), pose.position.into()); - playspace_common::shift_world(overlays, anchor, &before, &after); + + if !app.session.config.space_drag_affects_world { + playspace_common::shift_world(overlays, anchor, &before, &after); + } } pub fn save_center(&mut self, monado: &mut Monado) { From 239337ae760f28d261ecbdd80c630122cb147a52 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Fri, 24 Jul 2026 03:25:51 +0900 Subject: [PATCH 03/54] toast when input capture fails --- wayvr/src/backend/wayvr/input_capture.rs | 13 ++++++++++++- wayvr/src/backend/wayvr/mod.rs | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/wayvr/src/backend/wayvr/input_capture.rs b/wayvr/src/backend/wayvr/input_capture.rs index fcf77f34..c983b8ae 100644 --- a/wayvr/src/backend/wayvr/input_capture.rs +++ b/wayvr/src/backend/wayvr/input_capture.rs @@ -24,6 +24,8 @@ use std::{ time::{Duration, Instant}, }; +use crate::subsystem::dbus::DbusConnector; + const IGNORE_PREFIX: &str = "WayVR"; const WATCHDOG_TIMEOUT: Duration = Duration::from_millis(5000); const POLL_TIMEOUT_MS: i32 = 20; @@ -486,7 +488,16 @@ fn worker_main( } } Err(error) => { - log::warn!("Could not grab input devices after Super+Tab: {error}"); + //TODO: translate + let _ = DbusConnector::notify_send( + "Could not capture input", + &format!("Linux user must be in input group!\n Error: \n{error}"), + 1, + 5000, + 0, + true, + ); + log::warn!("Could not grab input devices: {error}"); } } } diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index ba38aa27..b7f0350b 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -896,7 +896,7 @@ impl WvrServerState { audio_sample_player.play_sample(audio_system, "input_grab"); if !self.grab_toast_sent { self.grab_toast_sent = true; - //TODO: toast + //TODO: translate let _ = DbusConnector::notify_send( "WayVR has your keyboard and mouse!", "Super+Z to release", From 2574784ad37ab1fcc7115ebf2b67e269429affec Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:37:10 +0900 Subject: [PATCH 04/54] remove smol-hyper --- Cargo.lock | 35 ----------------------------------- dash-frontend/Cargo.toml | 1 - 2 files changed, 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee28ae54..772d3f32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1434,7 +1434,6 @@ dependencies = [ "serde", "serde_json", "smol", - "smol-hyper", "strum", "ureq", "uuid", @@ -2371,18 +2370,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" -[[package]] -name = "hyper" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" -dependencies = [ - "bytes", - "http", - "http-body", - "tokio", -] - [[package]] name = "iana-time-zone" version = "0.1.65" @@ -5475,19 +5462,6 @@ dependencies = [ "futures-lite", ] -[[package]] -name = "smol-hyper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7428a49d323867702cd12b97b08a6b0104f39ec13b49117911f101271321bc1a" -dependencies = [ - "async-executor", - "async-io", - "futures-io", - "hyper", - "pin-project-lite", -] - [[package]] name = "smol_str" version = "0.2.2" @@ -5907,15 +5881,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "pin-project-lite", -] - [[package]] name = "toml" version = "1.1.2+spec-1.1.0" diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index bfe5064b..075ba9ab 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -31,7 +31,6 @@ keyvalues-parser = { rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" } lz4_flex = { version = "0.13.1", features = ["frame"] } -smol-hyper = "0.1.1" ureq = "3.3.0" [features] From d1f29958461b6b9eda78aea4ee86523a982357a5 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 03:26:12 +0900 Subject: [PATCH 05/54] dependency cleaning --- Cargo.lock | 435 +------------------------- dash-frontend/Cargo.toml | 7 +- dash-frontend/src/util/steam_utils.rs | 4 +- 3 files changed, 11 insertions(+), 435 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 772d3f32..83fc4995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,18 +360,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-native-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37dd6b179962fe4048a6f81d4c0d7ed419a21fdf49204b4c6b04971693358e79" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 2.0.18", - "url", -] - [[package]] name = "async-net" version = "2.0.0" @@ -1109,16 +1097,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1132,9 +1110,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", + "core-foundation", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -1145,7 +1123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", + "core-foundation", "libc", ] @@ -1423,10 +1401,8 @@ name = "dash-frontend" version = "0.1.0" dependencies = [ "anyhow", - "async-native-tls", "chrono", "glam", - "http-body-util", "keyvalues-parser", "log", "lz4_flex", @@ -1566,17 +1542,6 @@ dependencies = [ "objc2 0.6.4", ] -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "dlib" version = "0.5.3" @@ -1937,15 +1902,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -1953,7 +1909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -1967,27 +1923,12 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -2335,29 +2276,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - [[package]] name = "httparse" version = "1.10.1" @@ -2394,88 +2312,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2515,27 +2351,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "image" version = "0.25.10" @@ -2889,8 +2704,9 @@ dependencies = [ [[package]] name = "keyvalues-parser" -version = "0.2.2" -source = "git+https://codeberg.org/CosmicHarper/vdf-rs.git?rev=fc6dcbea9eb13cacb98dea40063f6f56cde6e145#fc6dcbea9eb13cacb98dea40063f6f56cde6e145" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded51a3b2cdcdc173e23683fd49f72e8151160b279f80c0378f7661a2ae10b7" dependencies = [ "pest", ] @@ -3061,12 +2877,6 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - [[package]] name = "lock_api" version = "0.4.14" @@ -3232,23 +3042,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndk" version = "0.9.0" @@ -3788,49 +3581,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "openxr" version = "0.21.1" @@ -4188,15 +3938,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -4993,15 +4734,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -5033,29 +4765,6 @@ dependencies = [ "tiny-skia 0.11.4", ] -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "self_cell" version = "1.2.2" @@ -5623,17 +5332,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "sys-locale" version = "0.3.2" @@ -5856,16 +5554,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.11.0" @@ -6140,18 +5828,6 @@ dependencies = [ "log", ] -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - [[package]] name = "usvg" version = "0.47.0" @@ -6180,12 +5856,6 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -6222,12 +5892,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.1" @@ -7117,7 +6781,7 @@ dependencies = [ "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation 0.9.4", + "core-foundation", "core-graphics", "cursor-icon", "dpi", @@ -7212,12 +6876,6 @@ dependencies = [ "xdg 3.0.0", ] -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - [[package]] name = "wyz" version = "0.5.1" @@ -7370,29 +7028,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", - "synstructure", -] - [[package]] name = "zeno" version = "0.3.3" @@ -7419,66 +7054,12 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", - "synstructure", -] - [[package]] name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "zmij" version = "1.0.21" diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 075ba9ab..f5c05861 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -24,12 +24,7 @@ strum.workspace = true uuid.workspace = true xdg.workspace = true -async-native-tls = "0.6.0" -http-body-util = "0.1.3" -keyvalues-parser = { - git = "https://codeberg.org/CosmicHarper/vdf-rs.git", - rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" -} +keyvalues-parser = "0.2.4" lz4_flex = { version = "0.13.1", features = ["frame"] } ureq = "3.3.0" diff --git a/dash-frontend/src/util/steam_utils.rs b/dash-frontend/src/util/steam_utils.rs index 56ce0bb6..860c0344 100644 --- a/dash-frontend/src/util/steam_utils.rs +++ b/dash-frontend/src/util/steam_utils.rs @@ -249,7 +249,7 @@ impl SteamUtils { PathBuf::from(&app_entry.root_path).join(format!("steamapps/appmanifest_{}.acf", app_entry.app_id)); let vdf_data = std::fs::read_to_string(manifest_path)?; - let vdf_root = keyvalues_parser::Vdf::parse(&vdf_data)?; + let vdf_root = keyvalues_parser::parse(&vdf_data)?.into_vdf(); let Some(manifest) = vdf_parse_appstate(app_entry.app_id.clone(), &vdf_root) else { anyhow::bail!("Failed to parse AppState"); @@ -262,7 +262,7 @@ impl SteamUtils { let path = self.get_dir_steamapps().join("libraryfolders.vdf"); let vdf_data = std::fs::read_to_string(path)?; - let vdf_root = keyvalues_parser::Vdf::parse(&vdf_data)?; + let vdf_root = keyvalues_parser::parse(&vdf_data)?.into_vdf(); let Some(apps) = vdf_parse_libraryfolders(&vdf_root) else { anyhow::bail!("Failed to fetch installed Steam apps"); From 0b4c59f1c7ecf62bee0150e4331ee65b8f9e4bf1 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Wed, 29 Jul 2026 21:18:36 +0200 Subject: [PATCH 06/54] dash-frontend: download in a separate thread --- dash-frontend/src/frontend.rs | 2 +- .../src/util/networking/http_client.rs | 228 +++++++++--------- dash-frontend/src/views/download_file.rs | 14 +- wgui/src/task.rs | 4 + 4 files changed, 128 insertions(+), 120 deletions(-) diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 7be4bd61..1a3e0a19 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -56,7 +56,7 @@ pub struct Frontend { pub interface: BoxDashInterface, - // async runtime executor + // single-threaded async runtime executor pub executor: AsyncExecutor, #[allow(dead_code)] diff --git a/dash-frontend/src/util/networking/http_client.rs b/dash-frontend/src/util/networking/http_client.rs index ea5b2b34..9ab865af 100644 --- a/dash-frontend/src/util/networking/http_client.rs +++ b/dash-frontend/src/util/networking/http_client.rs @@ -1,13 +1,9 @@ use anyhow::Context as _; -use smol::{ - Task, - channel::{Receiver, bounded}, - io::AsyncWriteExt as _, -}; +use smol::{channel::bounded, io::AsyncWriteExt as _}; use std::{io::Read as _, path::Path, str, sync::OnceLock}; const IO_BUFFER_SIZE: usize = 256 * 1024; -const CHANNEL_CAPACITY: usize = 4; +const CHANNEL_CAPACITY: usize = 8; const MAX_INITIAL_ALLOCATION: usize = 8 * 1024 * 1024; pub struct HttpClientResponse { @@ -37,10 +33,15 @@ pub struct GetParams<'a> { pub on_progress: Option>, } +enum HttpClientData { + FileSize(u64), + Chunk(Vec), + Ended(anyhow::Result<()>), +} + struct DownloadStream { file_size: u64, - chunks: Receiver>, - worker: Task>, + receiver: smol::channel::Receiver, } static HTTP_AGENT: OnceLock = OnceLock::new(); @@ -55,119 +56,116 @@ fn http_agent() -> &'static ureq::Agent { }) } -/// Starts a blocking HTTP request and streams its body through a bounded -/// asynchronous channel. -/// +fn thread_http_client( + url: &str, + allow_missing_content_length: bool, + sender: smol::channel::Sender, +) -> anyhow::Result<()> { + log::info!("fetching URL \"{}\"", url); + let agent = http_agent().clone(); + + let response = agent + .get(url) + .header("Accept-Encoding", "identity") + .call() + .with_context(|| format!("failed to fetch URL \"{url}\""))?; + + if !response.status().is_success() { + anyhow::bail!("non-200 HTTP response: {}", response.status().as_u16(),); + } + + let file_size = match response.body().content_length() { + Some(file_size) => file_size, + + None if allow_missing_content_length => 0, + + None => { + anyhow::bail!("HTTP response has no Content-Length header"); + } + }; + + sender.send_blocking(HttpClientData::FileSize(file_size))?; + + let mut reader = response.into_body().into_reader(); + let mut buffer = [0_u8; IO_BUFFER_SIZE]; + + loop { + let count = reader.read(&mut buffer).with_context(|| { + format!( + "failed while reading HTTP response body \ + from \"{url}\"" + ) + })?; + + if count == 0 { + break; + } + + sender.send_blocking(HttpClientData::Chunk(buffer[..count].to_vec()))?; + } + + Ok(()) +} + +/// Starts a HTTP request in a separate thread and streams its body through an async channel /// The request, TLS operations, and all BodyReader reads remain inside one /// blocking worker. Only owned byte chunks cross back to the async task. async fn start_download(url: &str, allow_missing_content_length: bool) -> anyhow::Result { let url = url.to_owned(); - let agent = http_agent().clone(); - let (metadata_tx, metadata_rx) = bounded::(1); - - let (chunk_tx, chunk_rx) = bounded::>(CHANNEL_CAPACITY); - - let worker = smol::unblock(move || -> anyhow::Result<()> { - log::info!("fetching URL \"{}\"", url); - - let response = agent - .get(&url) - .header("Accept-Encoding", "identity") - .call() - .with_context(|| format!("failed to fetch URL \"{url}\""))?; - - if !response.status().is_success() { - anyhow::bail!("non-200 HTTP response: {}", response.status().as_u16(),); - } - - let file_size = match response.body().content_length() { - Some(file_size) => file_size, - - None if allow_missing_content_length => 0, - - None => { - anyhow::bail!("HTTP response has no Content-Length header"); - } - }; - - if metadata_tx.send_blocking(file_size).is_err() { - return Ok(()); - } - - drop(metadata_tx); - - let mut reader = response.into_body().into_reader(); - let mut buffer = [0_u8; IO_BUFFER_SIZE]; - - loop { - let count = reader.read(&mut buffer).with_context(|| { - format!( - "failed while reading HTTP response body \ - from \"{url}\"" - ) - })?; - - if count == 0 { - break; - } - - let chunk = buffer[..count].to_vec(); - - if chunk_tx.send_blocking(chunk).is_err() { - return Ok(()); - } - } - - Ok(()) + let (sender, receiver) = bounded::(CHANNEL_CAPACITY); + std::thread::spawn(move || { + let res = thread_http_client(&url, allow_missing_content_length, sender.clone()); + let _ = sender.send_blocking(HttpClientData::Ended(res)); }); - let file_size = match metadata_rx.recv().await { - Ok(file_size) => file_size, - - Err(_) => { - worker.await?; - - anyhow::bail!("HTTP worker stopped before providing response metadata"); + let file_size = match receiver.recv().await? { + HttpClientData::Ended(res) => match res { + Ok(_) => anyhow::bail!("HTTP worker failure"), + Err(e) => return Err(anyhow::anyhow!(e)), + }, + HttpClientData::FileSize(file_size) => file_size, + _ => { + anyhow::bail!("HTTP worker failure") } }; - Ok(DownloadStream { - file_size, - chunks: chunk_rx, - worker, - }) + Ok(DownloadStream { file_size, receiver }) } /// Downloads a response into memory. /// /// This fails if the server does not provide a Content-Length header. pub async fn get(mut params: GetParams<'_>) -> anyhow::Result { - let DownloadStream { - file_size, - chunks, - worker, - } = start_download(params.url, false).await?; + let DownloadStream { file_size, receiver } = start_download(params.url, false).await?; let initial_capacity = usize::try_from(file_size).unwrap_or(0).min(MAX_INITIAL_ALLOCATION); let mut data = Vec::with_capacity(initial_capacity); let mut bytes_downloaded = 0_u64; - while let Ok(chunk) = chunks.recv().await { - bytes_downloaded += chunk.len() as u64; - data.extend_from_slice(&chunk); + while let Ok(msg) = receiver.recv().await { + match msg { + HttpClientData::FileSize(_) => unreachable!(), + HttpClientData::Ended(e) => { + if let Err(e) = e { + anyhow::bail!("HTTP request failed: {}", e); + } + } + HttpClientData::Chunk(chunk) => { + bytes_downloaded += chunk.len() as u64; + data.extend_from_slice(&chunk); - if let Some(on_progress) = params.on_progress.as_mut() { - on_progress(ProgressFuncData { - bytes_downloaded, - file_size, - }); + if let Some(on_progress) = params.on_progress.as_mut() { + on_progress(ProgressFuncData { + bytes_downloaded, + file_size, + }); + } + } } } - worker.await?; - if bytes_downloaded != file_size { anyhow::bail!( "HTTP response size mismatch: expected {} bytes, received {}", @@ -189,11 +187,7 @@ pub async fn get(mut params: GetParams<'_>) -> anyhow::Result, path: impl AsRef) -> anyhow::Result<()> { let path = path.as_ref().to_owned(); - let DownloadStream { - file_size, - chunks, - worker, - } = start_download(params.url, true).await?; + let DownloadStream { file_size, receiver } = start_download(params.url, true).await?; let mut file = smol::fs::File::create(&path) .await @@ -201,24 +195,32 @@ pub async fn download_to_file(mut params: GetParams<'_>, path: impl AsRef) let mut bytes_downloaded = 0_u64; - while let Ok(chunk) = chunks.recv().await { - file - .write_all(&chunk) - .await - .with_context(|| format!("failed to write download file {:?}", path,))?; + while let Ok(msg) = receiver.recv().await { + match msg { + HttpClientData::FileSize(_) => unreachable!(), + HttpClientData::Ended(e) => { + if let Err(e) = e { + anyhow::bail!("HTTP request failed: {}", e); + } + } + HttpClientData::Chunk(chunk) => { + file + .write_all(&chunk) + .await + .with_context(|| format!("failed to write download file {:?}", path,))?; - bytes_downloaded += chunk.len() as u64; + bytes_downloaded += chunk.len() as u64; - if let Some(on_progress) = params.on_progress.as_mut() { - on_progress(ProgressFuncData { - bytes_downloaded, - file_size, - }); + if let Some(on_progress) = params.on_progress.as_mut() { + on_progress(ProgressFuncData { + bytes_downloaded, + file_size, + }); + } + } } } - worker.await?; - file .flush() .await diff --git a/dash-frontend/src/views/download_file.rs b/dash-frontend/src/views/download_file.rs index be80b3c6..35602d52 100644 --- a/dash-frontend/src/views/download_file.rs +++ b/dash-frontend/src/views/download_file.rs @@ -208,12 +208,14 @@ impl View { on_progress: Some(Box::new({ let tasks = tasks.clone(); move |data: ProgressFuncData| { - tasks.push(Task::SetStatusText(format!( - "{}/{} MiB ({}%)", - data.bytes_downloaded / 1024 / 1024, - data.file_size / 1024 / 1024, - (data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round() - ))) + if tasks.len() < 50 { + tasks.push(Task::SetStatusText(format!( + "{}/{} MiB ({}%)", + data.bytes_downloaded / 1024 / 1024, + data.file_size / 1024 / 1024, + (data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round() + ))) + } } })), }, diff --git a/wgui/src/task.rs b/wgui/src/task.rs index e34be1ad..8cd9f914 100644 --- a/wgui/src/task.rs +++ b/wgui/src/task.rs @@ -18,6 +18,10 @@ impl Tasks { self.0.borrow_mut().push_back(task); } + pub fn len(&self) -> usize { + self.0.borrow().len() + } + pub fn drain(&mut self) -> VecDeque { let mut tasks = self.0.borrow_mut(); std::mem::take(&mut *tasks) From 707e3a4927c83f15badf391db35ea090f2766427 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Wed, 29 Jul 2026 21:53:00 +0200 Subject: [PATCH 07/54] dash-frontend: remove partially downloaded files, cancel download on Downloader view exit --- dash-frontend/src/frontend.rs | 6 ++--- .../src/util/networking/http_client.rs | 24 ++++++++++++++++++- dash-frontend/src/views/download_file.rs | 16 +++++++++---- uidev/src/testbed/testbed_dashboard.rs | 12 ++++++++-- wayvr/run | 6 ----- wayvr/src/backend/openvr/mod.rs | 1 + wayvr/src/backend/openxr/mod.rs | 1 + wayvr/src/overlays/dashboard.rs | 1 + wayvr/src/state.rs | 5 ++++ wlx-common/src/async_executor.rs | 4 ++++ 10 files changed, 58 insertions(+), 18 deletions(-) delete mode 100755 wayvr/run diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 1a3e0a19..67f929f3 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -98,6 +98,7 @@ pub struct InitParams<'a, T> { pub has_monado: bool, pub theme: Rc, pub color_palette: &'a str, + pub executor: AsyncExecutor, } #[derive(Clone)] @@ -202,7 +203,7 @@ impl Frontend { toast_manager, window_audio_settings: WguiWindow::default(), view_audio_settings: None, - executor: Rc::new(smol::LocalExecutor::new()), + executor: params.executor, sounds_to_play: Vec::new(), }; @@ -254,9 +255,6 @@ impl Frontend { self.current_tab = Some(tab); } - // process async runtime tasks - while self.executor.try_tick() {} - let res = self.tick(params)?; self.ticks += 1; diff --git a/dash-frontend/src/util/networking/http_client.rs b/dash-frontend/src/util/networking/http_client.rs index 9ab865af..6cff1ea7 100644 --- a/dash-frontend/src/util/networking/http_client.rs +++ b/dash-frontend/src/util/networking/http_client.rs @@ -117,6 +117,7 @@ async fn start_download(url: &str, allow_missing_content_length: bool) -> anyhow std::thread::spawn(move || { let res = thread_http_client(&url, allow_missing_content_length, sender.clone()); let _ = sender.send_blocking(HttpClientData::Ended(res)); + log::debug!("thread_http_client exiting"); }); let file_size = match receiver.recv().await? { @@ -177,6 +178,20 @@ pub async fn get(mut params: GetParams<'_>) -> anyhow::Result, path: impl AsRef) let DownloadStream { file_size, receiver } = start_download(params.url, true).await?; + let mut file_cancel_guard = FileCancelGuard { + path: path.clone(), + should_delete: true, + }; + let mut file = smol::fs::File::create(&path) .await - .with_context(|| format!("failed to create download file {:?}", path,))?; + .with_context(|| format!("failed to create download file {:?}", path))?; let mut bytes_downloaded = 0_u64; @@ -234,6 +254,8 @@ pub async fn download_to_file(mut params: GetParams<'_>, path: impl AsRef) ); } + file_cancel_guard.should_delete = false; // we're good! + Ok(()) } diff --git a/dash-frontend/src/views/download_file.rs b/dash-frontend/src/views/download_file.rs index 35602d52..498a126e 100644 --- a/dash-frontend/src/views/download_file.rs +++ b/dash-frontend/src/views/download_file.rs @@ -51,6 +51,9 @@ pub struct View { id_content: WidgetID, on_close_request: Option>, on_downloaded: Option>, + + // will cancel on Drop + task_downloader: Option>>, } fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> { @@ -67,10 +70,12 @@ impl ViewTrait for View { match task { Task::StartDownload(url, path) => { if let Some(on_downloaded) = self.on_downloaded.take() { - self - .executor - .spawn(View::download(self.tasks.clone(), url, path, on_downloaded)) - .detach(); + self.task_downloader = Some(self.executor.spawn(View::download( + self.tasks.clone(), + url, + path, + on_downloaded, + ))); } } Task::SetStatusText(text) => { @@ -178,6 +183,7 @@ impl View { id_content, on_close_request: Some(on_close_request), on_downloaded: Some(par.on_downloaded), + task_downloader: None, }) } @@ -208,7 +214,7 @@ impl View { on_progress: Some(Box::new({ let tasks = tasks.clone(); move |data: ProgressFuncData| { - if tasks.len() < 50 { + if tasks.len() < 100 { tasks.push(Task::SetStatusText(format!( "{}/{} MiB ({}%)", data.bytes_downloaded / 1024 / 1024, diff --git a/uidev/src/testbed/testbed_dashboard.rs b/uidev/src/testbed/testbed_dashboard.rs index eb0f197d..55ea8e14 100644 --- a/uidev/src/testbed/testbed_dashboard.rs +++ b/uidev/src/testbed/testbed_dashboard.rs @@ -3,9 +3,13 @@ use std::rc::Rc; use crate::testbed::{Testbed, TestbedUpdateParams}; use dash_frontend::frontend::{self, FrontendUpdateParams}; use wgui::{layout::Layout, theme::WguiTheme}; -use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider}; +use wlx_common::{ + async_executor::AsyncExecutor, dash_interface_emulated::DashInterfaceEmulated, + locale::WayVRLangProvider, +}; pub struct TestbedDashboard { + executor: AsyncExecutor, frontend: frontend::Frontend<()>, } @@ -14,6 +18,7 @@ impl TestbedDashboard { let interface = DashInterfaceEmulated::new(); let lang_provider = WayVRLangProvider::default(); let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string()); + let executor = wlx_common::async_executor::create_local(); let frontend = frontend::Frontend::new(frontend::InitParams { interface: Box::new(interface), @@ -22,8 +27,9 @@ impl TestbedDashboard { lang_provider: &lang_provider, theme: Rc::new(WguiTheme::default()), color_palette: &palette_name, + executor: executor.clone(), })?; - Ok(Self { frontend }) + Ok(Self { frontend, executor }) } } @@ -38,6 +44,8 @@ impl Testbed for TestbedDashboard { self .frontend .process_update(res, params.audio_system, params.audio_sample_player)?; + + while self.executor.try_tick() {} Ok(()) } diff --git a/wayvr/run b/wayvr/run deleted file mode 100755 index ea9d3697..00000000 --- a/wayvr/run +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - - - - -cargo run --profile=plain -- --show --replace diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index bf08d1d9..97782b90 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -247,6 +247,7 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> { let universe = playspace.get_universe(); + while app.executor.try_tick() {} app.input_state.pre_update(); input_source.update( universe.clone(), diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index fd37819a..ebc3b6f4 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -267,6 +267,7 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> { continue 'main_loop; } + while app.executor.try_tick() {} app.input_state.pre_update(); input_source.update(&xr_state, &mut app)?; app.input_state.post_update(&app.session); diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index fe972925..bbff2c88 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -95,6 +95,7 @@ impl DashFrontend { has_monado: matches!(app.xr_backend, XrBackend::OpenXR), theme: app.wgui_theme.clone(), color_palette: &*app.session.config.color_palette, + executor: app.executor.clone(), })?; frontend diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index e42ad4a9..22753a99 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -58,6 +58,8 @@ pub struct AppState { pub wgui_shared: WSharedContext, + pub executor: wlx_common::async_executor::AsyncExecutor, + pub input_state: InputState, pub screens: SmallVec<[ScreenMeta; 8]>, pub anchor: Affine3A, @@ -189,6 +191,8 @@ impl AppState { ) .ok(); + let executor = wlx_common::async_executor::create_local(); + let mut app_state = Self { tasks, gfx, @@ -209,6 +213,7 @@ impl AppState { load_palette(&*session.config.color_palette), )?, wgui_theme: Rc::new(theme), + executor, dbus, xr_backend, ipc_server, diff --git a/wlx-common/src/async_executor.rs b/wlx-common/src/async_executor.rs index 7ec2f737..158b3bbc 100644 --- a/wlx-common/src/async_executor.rs +++ b/wlx-common/src/async_executor.rs @@ -1,3 +1,7 @@ use std::rc::Rc; pub type AsyncExecutor = Rc>; + +pub fn create_local() -> AsyncExecutor { + Rc::new(smol::LocalExecutor::new()) +} From 6b22e4aef143c80984b771b7ba39697bdccdc507 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Wed, 29 Jul 2026 22:00:10 +0200 Subject: [PATCH 08/54] dash-frontend: minor refactoring for Tasks --- dash-frontend/src/views/app_launcher.rs | 8 +- dash-frontend/src/views/download_file.rs | 105 +++++++++--------- dash-frontend/src/views/game_launcher.rs | 8 +- dash-frontend/src/views/game_list.rs | 8 +- dash-frontend/src/views/running_games_list.rs | 9 +- dash-frontend/src/views/skymap_list.rs | 8 +- wgui/src/task.rs | 4 + 7 files changed, 68 insertions(+), 82 deletions(-) diff --git a/dash-frontend/src/views/app_launcher.rs b/dash-frontend/src/views/app_launcher.rs index 63d66181..bb429663 100644 --- a/dash-frontend/src/views/app_launcher.rs +++ b/dash-frontend/src/views/app_launcher.rs @@ -298,12 +298,8 @@ impl View { } pub fn update(&mut self, interface: &mut BoxDashInterface, data: &mut T) -> anyhow::Result<()> { - loop { - let tasks = self.tasks.drain(); - if tasks.is_empty() { - break; - } - for task in tasks { + while !self.tasks.is_empty() { + for task in self.tasks.drain() { match task { Task::SetCompositor(mode) => self.compositor_mode = mode, Task::SetRes(mode) => self.res_mode = mode, diff --git a/dash-frontend/src/views/download_file.rs b/dash-frontend/src/views/download_file.rs index 498a126e..66c7d37e 100644 --- a/dash-frontend/src/views/download_file.rs +++ b/dash-frontend/src/views/download_file.rs @@ -66,63 +66,66 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> { impl ViewTrait for View { fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> { - for task in self.tasks.drain() { - match task { - Task::StartDownload(url, path) => { - if let Some(on_downloaded) = self.on_downloaded.take() { - self.task_downloader = Some(self.executor.spawn(View::download( - self.tasks.clone(), - url, - path, - on_downloaded, - ))); + while !self.tasks.is_empty() { + for task in self.tasks.drain() { + match task { + Task::StartDownload(url, path) => { + if let Some(on_downloaded) = self.on_downloaded.take() { + self.task_downloader = Some(self.executor.spawn(View::download( + self.tasks.clone(), + url, + path, + on_downloaded, + ))); + } } - } - Task::SetStatusText(text) => { - let widgets = &mut par.layout.state.widgets; - widgets - .fetch(self.id_label_status)? - .cast::()? - .set_text(&mut par.layout.common(), Translation::from_raw_text_string(text)); - } - Task::ShowIconSuccess => { - par.layout.remove_children(self.id_loading_parent); - wgui_simple::create_icon( - par.layout, - self.id_loading_parent, - Vec2::splat(32.0), - AssetPath::BuiltIn("dashboard/check.svg"), - )?; - - // "Close window" button - self - .parser_state - .realize_template( - &doc_params(&self.globals), - "btn_close", + Task::SetStatusText(text) => { + let widgets = &mut par.layout.state.widgets; + widgets + .fetch(self.id_label_status)? + .cast::()? + .set_text(&mut par.layout.common(), Translation::from_raw_text_string(text)); + } + Task::ShowIconSuccess => { + par.layout.remove_children(self.id_loading_parent); + wgui_simple::create_icon( par.layout, - self.id_content, - Default::default(), - )? - .fetch_component_as::("btn")? - .on_click(self.tasks.get_button_click_callback(Task::Close)); - } - Task::ShowIconError => { - par.layout.remove_children(self.id_loading_parent); - wgui_simple::create_icon( - par.layout, - self.id_loading_parent, - Vec2::splat(32.0), - AssetPath::BuiltIn("dashboard/error.svg"), - )?; - } - Task::Close => { - if let Some(on_close) = self.on_close_request.take() { - on_close(); + self.id_loading_parent, + Vec2::splat(32.0), + AssetPath::BuiltIn("dashboard/check.svg"), + )?; + + // "Close window" button + self + .parser_state + .realize_template( + &doc_params(&self.globals), + "btn_close", + par.layout, + self.id_content, + Default::default(), + )? + .fetch_component_as::("btn")? + .on_click(self.tasks.get_button_click_callback(Task::Close)); + } + Task::ShowIconError => { + par.layout.remove_children(self.id_loading_parent); + wgui_simple::create_icon( + par.layout, + self.id_loading_parent, + Vec2::splat(32.0), + AssetPath::BuiltIn("dashboard/error.svg"), + )?; + } + Task::Close => { + if let Some(on_close) = self.on_close_request.take() { + on_close(); + } } } } } + Ok(()) } } diff --git a/dash-frontend/src/views/game_launcher.rs b/dash-frontend/src/views/game_launcher.rs index f10163e3..e763d0fb 100644 --- a/dash-frontend/src/views/game_launcher.rs +++ b/dash-frontend/src/views/game_launcher.rs @@ -52,12 +52,8 @@ pub struct View { impl ViewTrait for View { fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> { - loop { - let tasks = self.tasks.drain(); - if tasks.is_empty() { - break; - } - for task in tasks { + while !self.tasks.is_empty() { + for task in self.tasks.drain() { match task { Task::FillAppDetails(details) => self.action_fill_app_details(par.layout, details)?, Task::Launch => self.action_launch(), diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index 1097f5f2..4736ba7a 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -70,12 +70,8 @@ pub struct View { impl ViewTrait for View { fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> { - loop { - let tasks = self.tasks.drain(); - if tasks.is_empty() { - break; - } - for task in tasks { + while !self.tasks.is_empty() { + for task in self.tasks.drain() { match task { Task::LoadManifests => self.load_manifests(), Task::FillPage(page_idx) => self.fill_page(par.layout, par.executor, page_idx)?, diff --git a/dash-frontend/src/views/running_games_list.rs b/dash-frontend/src/views/running_games_list.rs index bc607b20..39941c1f 100644 --- a/dash-frontend/src/views/running_games_list.rs +++ b/dash-frontend/src/views/running_games_list.rs @@ -77,13 +77,8 @@ impl View { } pub fn update(&mut self, layout: &mut Layout, time_ms: u32) -> anyhow::Result<()> { - loop { - let tasks = self.tasks.drain(); - if tasks.is_empty() { - break; - } - - for task in tasks { + while !self.tasks.is_empty() { + for task in self.tasks.drain() { match task { Task::Refresh => self.refresh(layout)?, Task::StopGame(app_id, kill) => self.stop_game(app_id, kill), diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs index e95f4ddd..ddd8a169 100644 --- a/dash-frontend/src/views/skymap_list.rs +++ b/dash-frontend/src/views/skymap_list.rs @@ -58,12 +58,8 @@ impl ViewTrait for View { self.popup_remote_skymap_list.update(par)?; self.popup_dialog_box.update(par)?; - loop { - let tasks = self.tasks.drain(); - if tasks.is_empty() { - break; - } - for task in tasks { + while !self.tasks.is_empty() { + for task in self.tasks.drain() { match task { Task::DownloadSkymaps => { self.download_skymaps(par.executor)?; diff --git a/wgui/src/task.rs b/wgui/src/task.rs index 8cd9f914..5ac26388 100644 --- a/wgui/src/task.rs +++ b/wgui/src/task.rs @@ -22,6 +22,10 @@ impl Tasks { self.0.borrow().len() } + pub fn is_empty(&self) -> bool { + self.0.borrow().is_empty() + } + pub fn drain(&mut self) -> VecDeque { let mut tasks = self.0.borrow_mut(); std::mem::take(&mut *tasks) From 6f6f9af983a86dc2acb2df6db05e1e8eda702e52 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Thu, 30 Jul 2026 08:20:57 +0200 Subject: [PATCH 09/54] move executor.try_tick() to AppState::tick() [skip ci] --- wayvr/src/backend/openvr/mod.rs | 1 - wayvr/src/backend/openxr/mod.rs | 1 - wayvr/src/state.rs | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index 97782b90..bf08d1d9 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -247,7 +247,6 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> { let universe = playspace.get_universe(); - while app.executor.try_tick() {} app.input_state.pre_update(); input_source.update( universe.clone(), diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index ebc3b6f4..fd37819a 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -267,7 +267,6 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> { continue 'main_loop; } - while app.executor.try_tick() {} app.input_state.pre_update(); input_source.update(&xr_state, &mut app)?; app.input_state.post_update(&app.session); diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 22753a99..e5ac8d5d 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -267,6 +267,7 @@ impl AppState { } pub fn tick(&mut self) { + while self.executor.try_tick() {} self.dbus.tick(); for toast in self.notifications.drain_pending(&self.session) { From 7ff096c9ab158e3a94f148159ee0f0f0393ca062 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:33:12 +0900 Subject: [PATCH 10/54] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 49bccfba..408a23bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.~lock.* /target .gdb_history .vscode From deccbda473a69f188303e56d8742413a74cccd49 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:36:54 +0900 Subject: [PATCH 11/54] add skip attribute --- wgui/src/parser/mod.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index 42067933..d8cd9824 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -981,23 +981,14 @@ fn parse_widget_universal(ctx: &mut ParserContext, widget: &WidgetPair, attribs: fn parse_child<'a>( file: &ParserFile, ctx: &mut ParserContext, - parent_node: roxmltree::Node<'a, 'a>, child_node: roxmltree::Node<'a, 'a>, parent_id: WidgetID, ) -> anyhow::Result<()> { let tag_name = child_node.tag_name().name(); - match parent_node.attribute("ignore_in_mode") { - Some("dev") => { - if !ctx.doc_params.extra.dev_mode { - return Ok(()); // do not parse - } + match child_node.attribute("skip") { + Some("1") => { + return Ok(()); // do not parse this element } - Some("live") => { - if ctx.doc_params.extra.dev_mode { - return Ok(()); // do not parse - } - } - Some(s) => ctx.print_invalid_attrib(tag_name, "ignore_in_mode", s), _ => {} } @@ -1117,7 +1108,7 @@ fn parse_children<'a>( parent_id: WidgetID, ) -> anyhow::Result<()> { for child_node in parent_node.children() { - parse_child(file, ctx, parent_node, child_node, parent_id)?; + parse_child(file, ctx, child_node, parent_id)?; } Ok(()) From 395e9d191915c1393b29f26ad27374a604ba5e09 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:08:24 +0900 Subject: [PATCH 12/54] passthru windows --- wayvr/src/assets/gui/keyboard.xml | 3 +- wayvr/src/assets/lang/de.json | 5 +- wayvr/src/assets/lang/en.json | 3 +- wayvr/src/assets/lang/es.json | 5 +- wayvr/src/assets/lang/it.json | 7 +- wayvr/src/assets/lang/ja.json | 5 +- wayvr/src/assets/lang/pl.json | 5 +- wayvr/src/assets/lang/zh_CN.json | 5 +- wayvr/src/backend/openxr/helpers.rs | 24 +++ wayvr/src/backend/openxr/overlay.rs | 49 +++++- wayvr/src/backend/task.rs | 7 +- wayvr/src/gui/panel/button.rs | 30 +++- wayvr/src/gui/panel/overlay_list.rs | 7 + wayvr/src/overlays/mod.rs | 1 + wayvr/src/overlays/passthrough.rs | 243 ++++++++++++++++++++++++++++ wayvr/src/overlays/wayvr.rs | 10 +- wayvr/src/windowing/manager.rs | 4 +- wayvr/src/windowing/mod.rs | 9 ++ wayvr/src/windowing/window.rs | 1 + 19 files changed, 387 insertions(+), 36 deletions(-) create mode 100644 wayvr/src/overlays/passthrough.rs diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 35c8f698..205fc272 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -154,7 +154,7 @@ - + @@ -182,6 +182,7 @@ + diff --git a/wayvr/src/assets/lang/de.json b/wayvr/src/assets/lang/de.json index 48b16772..8a4ac873 100644 --- a/wayvr/src/assets/lang/de.json +++ b/wayvr/src/assets/lang/de.json @@ -10,7 +10,6 @@ "TOGGLE_VISIBILITY": "Sichtbarkeit umschalten", "RESET_POSITION": "Position zurücksetzen", "RELOAD_FROM_DISK": "XML-Datei von der Festplatte neu laden", - "CLOSE_MIRROR": "Spiegel schließen", "CLOSE_APP": "App schließen", "FORCE_CLOSE_APP": "App zwangsweise schließen", "HANDSFREE": { @@ -20,7 +19,9 @@ "EYE_TRACKING": "Auge + Kneifen", "EYE_ONLY": "Augenlaser", "HMD_ONLY": "HMD-Laser" - } + }, + "ADD_PASSTHRU": "Passthrough-Fenster hinzufügen", + "CLOSE_OVERLAY": "Overlay schließen" }, "WATCH": { "RECENTER": "Spielbereich neu zentrieren", diff --git a/wayvr/src/assets/lang/en.json b/wayvr/src/assets/lang/en.json index bb9cdfeb..2a9ded47 100644 --- a/wayvr/src/assets/lang/en.json +++ b/wayvr/src/assets/lang/en.json @@ -5,8 +5,9 @@ "BAR": { "ADD_MIRROR": "Add a new mirror overlay", "ADD_NEW_SET": "Add new set", + "ADD_PASSTHRU": "Add a passthrough window", "CLOSE_APP": "Close app", - "CLOSE_MIRROR": "Close mirror", + "CLOSE_OVERLAY": "Close overlay", "DELETE_CURRENT_SET": "Delete current set", "EDIT_MODE_TOGGLE": "Toggle edit mode", "FORCE_CLOSE_APP": "Force close app", diff --git a/wayvr/src/assets/lang/es.json b/wayvr/src/assets/lang/es.json index 98bb314d..8a714076 100644 --- a/wayvr/src/assets/lang/es.json +++ b/wayvr/src/assets/lang/es.json @@ -10,7 +10,6 @@ "TOGGLE_VISIBILITY": "Alternar visibilidad", "RESET_POSITION": "Restablecer posición", "RELOAD_FROM_DISK": "Volver a cargar XML desde el disco", - "CLOSE_MIRROR": "Cerrar espejo", "CLOSE_APP": "Cerrar aplicación", "FORCE_CLOSE_APP": "Forzar cierre de la aplicación", "HANDSFREE": { @@ -20,7 +19,9 @@ "EYE_TRACKING": "Ojo + pellizco", "EYE_ONLY": "Láser ocular", "HMD_ONLY": "Láser del HMD" - } + }, + "ADD_PASSTHRU": "Añadir una ventana de passthrough", + "CLOSE_OVERLAY": "Cerrar superposición" }, "WATCH": { "RECENTER": "Recentrar el área de juego", diff --git a/wayvr/src/assets/lang/it.json b/wayvr/src/assets/lang/it.json index 328ab119..837fffdf 100644 --- a/wayvr/src/assets/lang/it.json +++ b/wayvr/src/assets/lang/it.json @@ -10,8 +10,6 @@ "TOGGLE_VISIBILITY": "Alterna visibilità", "RESET_POSITION": "Ripristina posizione", "RELOAD_FROM_DISK": "Ricarica XML dal disco", - "CLOSE_MIRROR": "Chiudi mirror", - "CLOSE_APP": "Chiudi app", "FORCE_CLOSE_APP": "Chiudi forzatamente l'app", "HANDSFREE": { "TITLE": "Modalità a mani libere", @@ -20,7 +18,10 @@ "EYE_TRACKING": "Occhio + pizzico", "EYE_ONLY": "Laser oculare", "HMD_ONLY": "Laser HMD" - } + }, + "ADD_PASSTHRU": "Aggiungi una finestra passthrough", + "CLOSE_APP": "Chiudi app", + "CLOSE_OVERLAY": "Chiudi overlay" }, "DEFAULT": "Predefinito", "DISABLED": "Disabilitato", diff --git a/wayvr/src/assets/lang/ja.json b/wayvr/src/assets/lang/ja.json index 1f3886b8..6056f0a7 100644 --- a/wayvr/src/assets/lang/ja.json +++ b/wayvr/src/assets/lang/ja.json @@ -10,7 +10,6 @@ "TOGGLE_VISIBILITY": "表示/非表示の切り替え", "RESET_POSITION": "位置をリセット", "RELOAD_FROM_DISK": "ディスクからXMLを再読み込み", - "CLOSE_MIRROR": "ミラーを閉じる", "CLOSE_APP": "アプリを閉じる", "FORCE_CLOSE_APP": "アプリを強制終了", "HANDSFREE": { @@ -20,7 +19,9 @@ "EYE_TRACKING": "視線 + ピンチ", "EYE_ONLY": "アイルーザー", "HMD_ONLY": "HMDレーザー" - } + }, + "ADD_PASSTHRU": "パススルーウィンドウを追加", + "CLOSE_OVERLAY": "オーバーレイを閉じる" }, "WATCH": { "RECENTER": "プレイスペースをリセンター", diff --git a/wayvr/src/assets/lang/pl.json b/wayvr/src/assets/lang/pl.json index f9afd408..dea58992 100644 --- a/wayvr/src/assets/lang/pl.json +++ b/wayvr/src/assets/lang/pl.json @@ -10,7 +10,6 @@ "TOGGLE_VISIBILITY": "Przełącz widoczność", "RESET_POSITION": "Zresetuj pozycję", "RELOAD_FROM_DISK": "Przeładuj XML z dysku", - "CLOSE_MIRROR": "Zamknij lustro", "CLOSE_APP": "Zamknij aplikację", "FORCE_CLOSE_APP": "Wymuś zamknięcie aplikacji", "HANDSFREE": { @@ -20,7 +19,9 @@ "EYE_TRACKING": "Oko + uszczypnięcie", "EYE_ONLY": "Laser wzrokowy", "HMD_ONLY": "Laser w HMD" - } + }, + "ADD_PASSTHRU": "Dodaj okno passthrough", + "CLOSE_OVERLAY": "Zamknij nakładkę" }, "WATCH": { "RECENTER": "Wyśrodkuj przestrzeń gry", diff --git a/wayvr/src/assets/lang/zh_CN.json b/wayvr/src/assets/lang/zh_CN.json index 67a8b952..7c872229 100644 --- a/wayvr/src/assets/lang/zh_CN.json +++ b/wayvr/src/assets/lang/zh_CN.json @@ -10,7 +10,6 @@ "TOGGLE_VISIBILITY": "切换可见性", "RESET_POSITION": "重置位置", "RELOAD_FROM_DISK": "从磁盘重新加载 XML", - "CLOSE_MIRROR": "关闭镜像", "CLOSE_APP": "关闭应用", "FORCE_CLOSE_APP": "强制关闭应用", "HANDSFREE": { @@ -20,7 +19,9 @@ "EYE_TRACKING": "眼动 + 捏合", "EYE_ONLY": "眼睛激光", "HMD_ONLY": "头显射线" - } + }, + "ADD_PASSTHRU": "添加透视窗口", + "CLOSE_OVERLAY": "关闭叠加层" }, "DEFAULT": "默认", "DISABLED": "已禁用", diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index d2c475eb..e7a9fabf 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -1,3 +1,5 @@ +use std::ffi::CStr; + use anyhow::{bail, ensure}; use glam::{Affine3A, Quat, Vec3, Vec3A}; use openxr::{self as xr, SessionCreateFlags, Version, sys::Handle}; @@ -41,6 +43,12 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { } else { log::warn!("Missing EXT_dpad_binding extension."); } + if available_extensions.fb_composition_layer_alpha_blend { + enabled_extensions.fb_composition_layer_alpha_blend = true; + } else { + log::warn!("Missing XR_FB_composition_layer_alpha_blend extension."); + } + if available_extensions.ext_samsung_odyssey_controller { enabled_extensions.ext_samsung_odyssey_controller = true; } else { @@ -118,6 +126,22 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { instance_props.runtime_version ); + let enabled_extensions = enabled_extensions + .names() + .into_iter() + .filter_map(|name| { + Some( + CStr::from_bytes_with_nul(name) + .ok()? + .to_string_lossy() + .into_owned(), + ) + }) + .collect::>() + .join("\n"); + + log::info!("OpenXR extensions:\n{enabled_extensions}"); + let Ok(system) = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY) else { bail!("Failed to access OpenXR HMD system."); }; diff --git a/wayvr/src/backend/openxr/overlay.rs b/wayvr/src/backend/openxr/overlay.rs index 8479569c..48a53527 100644 --- a/wayvr/src/backend/openxr/overlay.rs +++ b/wayvr/src/backend/openxr/overlay.rs @@ -11,7 +11,7 @@ use crate::{ swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain}, }, state::AppState, - windowing::window::OverlayWindowData, + windowing::window::{OverlayCategory, OverlayWindowData}, }; #[derive(Default)] @@ -21,6 +21,7 @@ pub struct OpenXrOverlayData { pub(super) init: bool, pub(super) cur_visible: bool, color_bias_khr: Option>, + layer_alpha_blend_fb: Option>, } impl OverlayWindowData { @@ -104,12 +105,34 @@ impl OverlayWindowData { (major, major / aspect_ratio) }; - let flags = if state.additive { - CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA - } else { - CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA - | CompositionLayerFlags::UNPREMULTIPLIED_ALPHA - }; + let mut flags = CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA; + + if state.additive { + flags |= CompositionLayerFlags::UNPREMULTIPLIED_ALPHA; + } + + if xr + .instance + .exts() + .fb_composition_layer_alpha_blend + .is_some() + && self.config.category == OverlayCategory::Passthru + && self.data.layer_alpha_blend_fb.is_none() + && !self.config.editing + { + self.data.layer_alpha_blend_fb = + Some(Box::new(openxr::sys::CompositionLayerAlphaBlendFB { + ty: xr::sys::CompositionLayerAlphaBlendFB::TYPE, + next: std::ptr::null_mut(), + src_factor_color: xr::BlendFactorFB::ZERO, + dst_factor_color: xr::BlendFactorFB::SRC_ALPHA, + src_factor_alpha: xr::BlendFactorFB::ONE, + dst_factor_alpha: xr::BlendFactorFB::ZERO, + })); + flags &= !CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA; + } else if self.config.editing { + self.data.layer_alpha_blend_fb = None; + } if let Some(curvature) = state.curvature { let radius = scale_x / (2.0 * PI * curvature); @@ -132,6 +155,12 @@ impl OverlayWindowData { .central_angle(angle) .aspect_ratio(aspect_ratio); + if let Some(layer_alpha_blend_fb) = self.data.layer_alpha_blend_fb.as_mut() { + unsafe { + let raw = next_chain_insert!(cylinder, layer_alpha_blend_fb); + cylinder = xr::CompositionLayerCylinderKHR::from_raw(raw); + } + } if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() { unsafe { let raw = next_chain_insert!(cylinder, color_bias_khr); @@ -157,6 +186,12 @@ impl OverlayWindowData { height: scale_y, }); + if let Some(layer_alpha_blend_fb) = self.data.layer_alpha_blend_fb.as_mut() { + unsafe { + let raw = next_chain_insert!(quad, layer_alpha_blend_fb); + quad = xr::CompositionLayerQuad::from_raw(raw); + } + } if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() { unsafe { let raw = next_chain_insert!(quad, color_bias_khr); diff --git a/wayvr/src/backend/task.rs b/wayvr/src/backend/task.rs index c2885ed7..94a8ee06 100644 --- a/wayvr/src/backend/task.rs +++ b/wayvr/src/backend/task.rs @@ -10,7 +10,10 @@ use serde::Deserialize; use crate::{ backend::input, state::AppState, - windowing::{OverlaySelector, window::OverlayWindowConfig}, + windowing::{ + OverlaySelector, + window::{OverlayCategory, OverlayWindowConfig}, + }, }; static TASK_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); @@ -123,7 +126,7 @@ pub enum OverlayTask { ToggleEditMode, ToggleDashboard, ShowHide, - CleanupMirrors, + CleanupOverlays(OverlayCategory), GlobalChange(GlobalChange), Modify(OverlaySelector, Box), Spawn(OverlaySelector, SpawnPos, Box), diff --git a/wayvr/src/gui/panel/button.rs b/wayvr/src/gui/panel/button.rs index 840a0c82..a050607b 100644 --- a/wayvr/src/gui/panel/button.rs +++ b/wayvr/src/gui/panel/button.rs @@ -552,7 +552,35 @@ pub(super) fn setup_custom_button( } app.tasks - .enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors)); + .enqueue(TaskType::Overlay(OverlayTask::CleanupOverlays( + OverlayCategory::Mirror, + ))); + Ok(EventResult::Consumed) + }), + "::NewPassthru" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let name = crate::overlays::passthrough::new_passtrhu_name(); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Spawn( + OverlaySelector::Name(name.clone()), + SpawnPos::Spread, + Box::new(move |app| { + Some(crate::overlays::passthrough::new_passthru(name, app)) + }), + ))); + Ok(EventResult::Consumed) + }), + "::CleanupPassthrus" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::CleanupOverlays( + OverlayCategory::Passthru, + ))); Ok(EventResult::Consumed) }), "::PlayspaceReset" => Box::new(move |_common, data, app, _| { diff --git a/wayvr/src/gui/panel/overlay_list.rs b/wayvr/src/gui/panel/overlay_list.rs index 3601fe54..c43e41e2 100644 --- a/wayvr/src/gui/panel/overlay_list.rs +++ b/wayvr/src/gui/panel/overlay_list.rs @@ -59,6 +59,13 @@ impl OverlayList { ); ("Mirror", panels_root) } + OverlayCategory::Passthru => { + params.insert_rc( + "display", + (*meta.name).chars().last().unwrap().to_string().into(), + ); + ("Mirror", panels_root) + } OverlayCategory::Panel | OverlayCategory::BuiltInPanel => { let icon: Rc = if let Some(icon) = meta.icon.as_ref() { icon.to_string().into() diff --git a/wayvr/src/overlays/mod.rs b/wayvr/src/overlays/mod.rs index b8d8a9e4..2fbd7152 100644 --- a/wayvr/src/overlays/mod.rs +++ b/wayvr/src/overlays/mod.rs @@ -3,6 +3,7 @@ pub mod custom; pub mod dashboard; pub mod edit; pub mod keyboard; +pub mod passthrough; pub mod screen; pub mod toast; pub mod watch; diff --git a/wayvr/src/overlays/passthrough.rs b/wayvr/src/overlays/passthrough.rs new file mode 100644 index 00000000..a166b5dd --- /dev/null +++ b/wayvr/src/overlays/passthrough.rs @@ -0,0 +1,243 @@ +use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, +}; + +use glam::{Affine2, Affine3A, Quat, Vec3}; +use slotmap::Key; +use vulkano::buffer::BufferUsage; +use wgui::{ + color::WguiColorName, + gfx::pipeline::{WGfxPipeline, WPipelineCreateInfo}, +}; +use wlx_common::{ + overlays::{BackendAttrib, BackendAttribValue}, + windowing::{OverlayWindowState, Positioning}, +}; + +use crate::{ + backend::task::{OverlayTask, TaskType}, + graphics::Vert2Uv, + state::AppState, + windowing::{ + OverlayID, OverlaySelector, + backend::{FrameMeta, OverlayBackend, OverlayEventData, ShouldRender, ui_transform}, + overlay_scale_from_extent, + window::{OverlayCategory, OverlayWindowConfig}, + }, +}; + +static PASSTHRU_COUNTER: AtomicUsize = AtomicUsize::new(1); + +pub fn new_passtrhu_name() -> Arc { + format!("P-{}", PASSTHRU_COUNTER.fetch_add(1, Ordering::Relaxed)).into() +} + +pub fn new_passthru(name: Arc, app: &AppState) -> OverlayWindowConfig { + OverlayWindowConfig { + name, + default_state: OverlayWindowState { + transform: Affine3A::from_scale_rotation_translation( + Vec3::ONE * 0.5, + Quat::IDENTITY, + Vec3::new(0., 0., -0.5), + ), + grabbable: true, + interactable: true, + positioning: Positioning::Static, + ..OverlayWindowState::default() + }, + global: true, + show_on_spawn: true, + category: OverlayCategory::Passthru, + ..OverlayWindowConfig::from_backend(Box::new(PassthruBackend::new(app))) + } +} + +struct PassthruBackend { + frame_meta: FrameMeta, + rendered: bool, + interaction_transform: Affine2, + scale: f32, + overlay_id: OverlayID, +} + +const DEFAULT_EXTENT: [u32; 2] = [512, 512]; + +impl PassthruBackend { + fn new(app: &AppState) -> Self { + let (scale, _) = overlay_scale_from_extent(DEFAULT_EXTENT); + Self { + frame_meta: FrameMeta { + extent: DEFAULT_EXTENT, + format: app.gfx.surface_format, + ..Default::default() + }, + interaction_transform: ui_transform(DEFAULT_EXTENT), + rendered: false, + scale, + overlay_id: OverlayID::null(), + } + } +} + +impl OverlayBackend for PassthruBackend { + fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result { + Ok(if self.rendered { + ShouldRender::Can + } else { + ShouldRender::Should + }) + } + fn render( + &mut self, + app: &mut AppState, + rdr: &mut crate::windowing::backend::RenderResources, + ) -> anyhow::Result<()> { + // this is heavy, but only done once + let pipeline: Arc> = app.gfx.create_pipeline( + app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic + app.gfx_extras.shaders.get("frag_color").unwrap(), // want panic + WPipelineCreateInfo::new(app.gfx.surface_format), + )?; + + let color = WguiColorName::Primary + .to_wgui_color() + .resolve(&app.wgui_globals.get().palette); + + let buf_color = app.gfx.new_buffer( + BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, + color.with_alpha(1.0).as_arr().iter(), + )?; + + let set0 = pipeline.buffer(0, buf_color.clone())?; + let extentf32 = [ + self.frame_meta.extent[0] as f32, + self.frame_meta.extent[1] as f32, + ]; + + let pass = pipeline.create_pass( + extentf32, + [0.0, 0.0], + app.gfx_extras.quad_verts.clone(), + 0..4, + 0..1, + vec![set0], + &Default::default(), + )?; + + rdr.cmd_buf_single().run_ref(&pass)?; + + let buf_color = app.gfx.new_buffer( + BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, + [0.0, 0.0, 0.0, 1.0].iter(), + )?; + + let set0 = pipeline.buffer(0, buf_color.clone())?; + + let pass = pipeline.create_pass( + [extentf32[0] - 8.0, extentf32[1] - 8.0], + [4.0, 4.0], + app.gfx_extras.quad_verts.clone(), + 0..4, + 0..1, + vec![set0], + &Default::default(), + )?; + + rdr.cmd_buf_single().run_ref(&pass)?; + + //self.rendered = true; + Ok(()) + } + fn frame_meta(&mut self) -> Option { + Some(self.frame_meta) + } + fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> { + match event_data { + OverlayEventData::IdAssigned(id) => self.overlay_id = id, + OverlayEventData::ResizeRequest(new_size) => { + self.frame_meta.extent = + [new_size[0].clamp(256, 1024), new_size[1].clamp(256, 1024)]; + self.interaction_transform = ui_transform(self.frame_meta.extent); + + let old_scale = self.scale; + let (new_scale, _) = overlay_scale_from_extent(self.frame_meta.extent); + self.scale = new_scale; + + if new_scale.abs() > f32::EPSILON { + let scale_delta = new_scale / old_scale; + + if (scale_delta - 1.0).abs() > f32::EPSILON { + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(self.overlay_id), + Box::new(move |_app, owc| { + if let Some(state) = owc.active_state.as_mut() { + if let Some(saved) = state.saved_transform.as_mut() { + saved.matrix3 = saved.matrix3.mul_scalar(scale_delta); + } + + state.transform.matrix3 = + state.transform.matrix3.mul_scalar(scale_delta); + } + }), + ))); + } + } + self.rendered = false; + } + _ => {} + } + Ok(()) + } + fn on_hover( + &mut self, + _app: &mut AppState, + _hit: &crate::backend::input::PointerHit, + ) -> crate::backend::input::HoverResult { + crate::backend::input::HoverResult::consume() + } + fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {} + fn on_pointer( + &mut self, + _app: &mut AppState, + _hit: &crate::backend::input::PointerHit, + _pressed: bool, + ) { + } + fn on_scroll( + &mut self, + _app: &mut AppState, + _hit: &crate::backend::input::PointerHit, + _delta: crate::subsystem::hid::WheelDelta, + ) { + } + fn get_interaction_transform(&mut self) -> Option { + Some(self.interaction_transform) + } + fn get_attrib( + &self, + attrib: wlx_common::overlays::BackendAttrib, + ) -> Option { + match attrib { + BackendAttrib::Resizable => Some(BackendAttribValue::Resizable(true)), + _ => None, + } + } + fn set_attrib( + &mut self, + _app: &mut AppState, + _value: wlx_common::overlays::BackendAttribValue, + ) -> bool { + false + } +} diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 69bd52e1..4bd3b95b 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -64,6 +64,7 @@ use crate::{ FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, }, + overlay_scale_from_extent, window::{OverlayCategory, OverlayWindowConfig}, }, }; @@ -1078,12 +1079,3 @@ impl OverlayBackend for WvrWindowBackend { } } } - -fn overlay_scale_from_extent(size: [u32; 2]) -> (f32, f32) { - const RELATIVE_SIZE: f32 = 1440.0; // sqrt(1920 * 1080) - - let w = size[0].max(1) as f32; - let h = size[1].max(1) as f32; - - ((w * h).sqrt() / RELATIVE_SIZE, w / 1920.0) -} diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index 596409bb..a4e9142a 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -347,10 +347,10 @@ where .notify(app, OverlayEventData::ColorPaletteRefresh); } } - OverlayTask::CleanupMirrors => { + OverlayTask::CleanupOverlays(category) => { let mut ids_to_remove = vec![]; for (oid, o) in &self.overlays { - if !matches!(o.config.category, OverlayCategory::Mirror) { + if o.config.category == category { continue; } if o.config.active_state.is_some() { diff --git a/wayvr/src/windowing/mod.rs b/wayvr/src/windowing/mod.rs index 358d9dac..f73f19b2 100644 --- a/wayvr/src/windowing/mod.rs +++ b/wayvr/src/windowing/mod.rs @@ -49,3 +49,12 @@ pub fn snap_upright(transform: Affine3A, up_dir: Vec3A) -> Affine3A { transform } } + +pub fn overlay_scale_from_extent(size: [u32; 2]) -> (f32, f32) { + const RELATIVE_SIZE: f32 = 1440.0; // sqrt(1920 * 1080) + + let w = size[0].max(1) as f32; + let h = size[1].max(1) as f32; + + ((w * h).sqrt() / RELATIVE_SIZE, w / 1920.0) +} diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index 9b674010..c3110758 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -64,6 +64,7 @@ pub enum OverlayCategory { Screen, Mirror, WayVR, + Passthru, } pub struct OverlayWindowConfig { From 7efc4bb0fc64019e31b5a6eb8ff43dd4eca560b4 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:24:30 +0900 Subject: [PATCH 13/54] don't show options that are incompatible --- dash-frontend/src/tab/settings/macros.rs | 18 ++++++++-- dash-frontend/src/tab/settings/mod.rs | 3 +- .../src/tab/settings/tab_controls.rs | 4 +-- .../src/tab/settings/tab_features.rs | 2 +- .../src/tab/settings/tab_space_drag.rs | 2 +- wayvr/src/assets/gui/keyboard.xml | 8 ++--- wayvr/src/backend/mod.rs | 6 ---- wayvr/src/backend/openvr/mod.rs | 4 +-- wayvr/src/backend/openxr/mod.rs | 12 +++---- wayvr/src/main.rs | 3 +- wayvr/src/overlays/dashboard.rs | 6 ++-- wayvr/src/overlays/keyboard/builder.rs | 13 ++++++++ wayvr/src/overlays/keyboard/mod.rs | 4 +-- wayvr/src/overlays/screen/backend.rs | 12 ++----- wayvr/src/overlays/screen/mod.rs | 12 ++++--- wayvr/src/overlays/wayvr.rs | 7 +--- wayvr/src/overlays/whisper.rs | 14 ++++---- wayvr/src/state.rs | 8 +++-- wayvr/src/windowing/manager.rs | 11 +++---- wgui/src/parser/mod.rs | 5 +-- wlx-common/src/config.rs | 33 ++++++++++++++----- wlx-common/src/dash_interface.rs | 5 +-- wlx-common/src/dash_interface_emulated.rs | 5 +-- wlx-common/src/lib.rs | 15 +++++++++ 24 files changed, 130 insertions(+), 82 deletions(-) diff --git a/dash-frontend/src/tab/settings/macros.rs b/dash-frontend/src/tab/settings/macros.rs index 9593f806..53a484ec 100644 --- a/dash-frontend/src/tab/settings/macros.rs +++ b/dash-frontend/src/tab/settings/macros.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart}; +use crate::tab::settings::{self, horiz_cell, mount_requires_restart, SettingType, Task}; use wgui::{ components::{ button::{ButtonClickEvent, ComponentButton}, @@ -13,7 +13,7 @@ use wgui::{ widget::label::WidgetLabel, windowing::context_menu, }; -use wlx_common::config::GeneralConfig; +use wlx_common::{config::GeneralConfig, dash_interface::InterfaceFeats, DesktopBackend, XrBackend}; pub fn options_category( mp: &mut MacroParams, @@ -272,6 +272,7 @@ where let btn = mp.parser_state.fetch_component_as::(&id)?; btn.on_click(Rc::new({ + let feats = mp.feats; let tasks = mp.tasks.clone(); move |_common, e: ButtonClickEvent| { tasks.push(Task::OpenContextMenu( @@ -282,6 +283,18 @@ where if item.get_bool("Hidden").unwrap_or(false) { return None; } + if item + .get_str("Backend") + .is_some_and(|x| XrBackend::try_from(x).unwrap() != feats.xr_backend) + { + return None; + } + if item + .get_str("Desktop") + .is_some_and(|x| DesktopBackend::try_from(x).unwrap() != feats.desktop_backend) + { + return None; + } let value = item.as_ref(); let title = SettingType::get_enum_title_inner(*item); @@ -404,4 +417,5 @@ pub struct MacroParams<'a> { pub config: &'a mut GeneralConfig, pub tasks: Tasks, pub idx: usize, + pub feats: InterfaceFeats, } diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 3f116cf3..64d1a866 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -680,6 +680,7 @@ impl TabSettings { config: frontend.interface.general_config(data), tasks: self.tasks.clone(), idx: 9001, + feats, }; let settings_mount_params = SettingsMountParams { @@ -733,7 +734,7 @@ impl TabSettings { let tasks = Tasks::default(); let tabs = parser_state.fetch_component_as::("tabs")?; - if !frontend.interface.get_feats(data).openxr { + if !frontend.interface.get_feats(data).xr_backend.is_open_xr() { let skybox_btn = tabs.get_tab_button("skybox").unwrap(); frontend .layout diff --git a/dash-frontend/src/tab/settings/tab_controls.rs b/dash-frontend/src/tab/settings/tab_controls.rs index 41ec4dab..01454a23 100644 --- a/dash-frontend/src/tab/settings/tab_controls.rs +++ b/dash-frontend/src/tab/settings/tab_controls.rs @@ -92,7 +92,7 @@ impl State { "APP_SETTINGS.CONTROLS", "dashboard/controller.svg", )?; - if par.feats.openxr { + if par.feats.xr_backend.is_open_xr() { create_input_profiles_button(par.mp, c, tasks.clone(), &popup)?; } options_dropdown::(par.mp, c, &SettingType::KeyboardMiddleClick)?; @@ -107,7 +107,7 @@ impl State { options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?; options_slider_f32(par.mp, c, SettingType::SnapAngleDeg, 0., 45., 5.)?; - if par.feats.openxr { + if par.feats.xr_backend.is_open_xr() { options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?; } diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 75c8db7f..7ec59415 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -152,7 +152,7 @@ impl State { options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; - if !par.feats.openxr || par.feats.monado { + if par.feats.xr_backend.is_open_vr() || par.feats.monado { // monado or openvr options_checkbox(par.mp, c, SettingType::BlockGameInput)?; options_checkbox(par.mp, c, SettingType::BlockGameInputIgnoreWatch)?; diff --git a/dash-frontend/src/tab/settings/tab_space_drag.rs b/dash-frontend/src/tab/settings/tab_space_drag.rs index fab2c5c6..c0e5750a 100644 --- a/dash-frontend/src/tab/settings/tab_space_drag.rs +++ b/dash-frontend/src/tab/settings/tab_space_drag.rs @@ -54,7 +54,7 @@ impl State { let id_gravity_enabled_parent = tab_state.get_widget_id("gravity_enabled_parent")?; let id_space_gravity_parent = tab_state.get_widget_id("space_gravity_parent")?; - if !par.feats.openxr || par.feats.monado { + if par.feats.xr_backend.is_open_vr() || par.feats.monado { // monado or openvr options_button( diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 205fc272..08e9eedd 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -172,8 +172,8 @@ - - + + @@ -181,8 +181,8 @@ - - + + diff --git a/wayvr/src/backend/mod.rs b/wayvr/src/backend/mod.rs index b1ded4f3..4e34f525 100644 --- a/wayvr/src/backend/mod.rs +++ b/wayvr/src/backend/mod.rs @@ -15,12 +15,6 @@ pub mod task; use thiserror::Error; -#[derive(Clone, Copy)] -pub enum XrBackend { - OpenXR, - OpenVR, -} - #[derive(Error, Debug)] pub enum BackendError { #[error("backend not supported")] diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index bf08d1d9..add2c4dc 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -15,9 +15,9 @@ use vulkano::{Handle, VulkanObject, device::physical::PhysicalDevice}; use wlx_common::overlays::ToastTopic; use crate::{ - Args, FRAME_COUNTER, RUNNING, + Args, FRAME_COUNTER, RUNNING, XrBackend, backend::{ - BackendError, XrBackend, + BackendError, input::interact, openvr::{ helpers::adjust_gain, diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index fd37819a..4e33eef3 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -13,9 +13,9 @@ use vulkano::{Handle, VulkanObject}; use wlx_common::overlays::{StereoMode, ToastTopic}; use crate::{ - Args, FRAME_COUNTER, RUNNING, + Args, FRAME_COUNTER, RUNNING, XrBackend, backend::{ - BackendError, XrBackend, + BackendError, input::interact, openxr::{helpers::try_apply_chroma_key, lines::LinePool, overlay::OpenXrOverlayData}, task::{OpenXrTask, OverlayTask, TaskType}, @@ -88,11 +88,9 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> { app.late_init(); - let mut playspace_mover = app.monado_state.as_mut().and_then(|m| { - playspace::PlayspaceMover::new() - .map_err(|e| log::warn!("Will not use Monado playspace mover: {e}")) - .ok() - }); + let mut playspace_mover = playspace::PlayspaceMover::new() + .map_err(|e| log::warn!("Will not use Monado playspace mover: {e}")) + .ok(); let mut blocker = app .monado_state diff --git a/wayvr/src/main.rs b/wayvr/src/main.rs index 67187fc1..27ad2b21 100644 --- a/wayvr/src/main.rs +++ b/wayvr/src/main.rs @@ -44,7 +44,8 @@ use sysinfo::Pid; use tracing::level_filters::LevelFilter; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; -use crate::{backend::XrBackend, subsystem::dbus::DbusConnector}; +use crate::subsystem::dbus::DbusConnector; +use wlx_common::XrBackend; pub static FRAME_COUNTER: AtomicUsize = AtomicUsize::new(0); pub static RUNNING: AtomicBool = AtomicBool::new(true); diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index bbff2c88..09cbefd5 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -35,7 +35,6 @@ use libmonado::{ClientLogic, DeviceLogic}; use crate::{ RESTART, RUNNING, backend::{ - XrBackend, input::{Haptics, HoverResult, PointerHit, PointerMode}, task::{GlobalChange, OverlayTask, PlayspaceTask, TaskType, ToggleMode}, wayvr::{ @@ -92,7 +91,7 @@ impl DashFrontend { interface: Box::new(interface), lang_provider: &WayVRLangProvider::from_config(&app.session.config), show_welcome: tutorial, - has_monado: matches!(app.xr_backend, XrBackend::OpenXR), + has_monado: app.xr_backend.is_open_xr(), theme: app.wgui_theme.clone(), color_palette: &*app.session.config.color_palette, executor: app.executor.clone(), @@ -580,7 +579,8 @@ impl DashInterface for DashInterfaceLive { fn get_feats(&mut self, data: &mut AppState) -> dash_interface::InterfaceFeats { dash_interface::InterfaceFeats { - openxr: matches!(data.xr_backend, XrBackend::OpenXR), + xr_backend: data.xr_backend, + desktop_backend: data.desktop_backend, #[cfg(feature = "openxr")] monado: data.monado_state.is_some(), #[cfg(not(feature = "openxr"))] diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 269353da..b93ad5dd 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -46,6 +46,10 @@ fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'s } } +fn bool_to_rc_str(val: bool) -> Rc { + if val { "1" } else { "0" }.into() +} + #[allow(clippy::too_many_lines, clippy::significant_drop_tightening)] pub(super) fn create_keyboard_panel( app: &mut AppState, @@ -53,6 +57,15 @@ pub(super) fn create_keyboard_panel( state: KeyboardState, layout: &layout::Layout, ) -> anyhow::Result> { + let mut params = NewGuiPanelParams::::default(); + params + .extra_vars + .insert("openvr".into(), bool_to_rc_str(app.xr_backend.is_open_vr())); + params.extra_vars.insert( + "not_wayland".into(), + bool_to_rc_str(!app.desktop_backend.is_wayland()), + ); + let mut panel = GuiPanel::new_from_template(app, "gui/keyboard.xml", state, NewGuiPanelParams::default())?; diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 0080333e..87983bf0 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -49,7 +49,7 @@ pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR]; const SYSTEM_LAYOUT_ALIASES: [&str; 5] = ["mozc", "pinyin", "hangul", "sayura", "unikey"]; -pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result { +pub fn create_keyboard(app: &mut AppState) -> anyhow::Result { let layout = layout::Layout::load_from_disk(); let default_state = KeyboardState { modifiers: 0, @@ -70,7 +70,7 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result, } -pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, bool)> { +pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, DesktopBackend)> { app.screens.clear(); #[cfg(feature = "wayland")] { if let Some(mut wl) = wlx_capture::wayland::WlxClient::new() { log::info!("Wayland detected."); - return Ok((wl::create_screens_wayland(&mut wl, app)?, true)); + return Ok(( + wl::create_screens_wayland(&mut wl, app)?, + DesktopBackend::Wayland, + )); } log::info!("Wayland not detected, assuming X11."); } @@ -81,11 +85,11 @@ pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, b { #[cfg(feature = "pipewire")] match x11::create_screens_x11pw(app) { - Ok(data) => return Ok((data, false)), + Ok(data) => return Ok((data, DesktopBackend::X11)), Err(e) => log::info!("Will not use X11 PipeWire capture: {e:?}"), } - Ok((x11::create_screens_xshm(app)?, false)) + return Ok((x11::create_screens_xshm(app)?, DesktopBackend::X11)); } #[cfg(not(feature = "x11"))] anyhow::bail!("No backends left to try.") diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 4bd3b95b..beb6632a 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -36,7 +36,6 @@ use wlx_common::{ use crate::{ backend::{ - XrBackend, input::{self, HoverResult}, task::{OverlayTask, TaskType}, wayvr::{ @@ -249,11 +248,7 @@ impl WvrWindowBackend { just_resumed: false, meta: None, mouse: None, - stereo: if matches!(app.xr_backend, XrBackend::OpenXR) { - Some(StereoMode::None) - } else { - None - }, + stereo: app.xr_backend.is_open_xr().then_some(StereoMode::None), stereo_full_frame: false, stereo_adjust_mouse: false, cur_image: None, diff --git a/wayvr/src/overlays/whisper.rs b/wayvr/src/overlays/whisper.rs index 70f8a575..98cf11b8 100644 --- a/wayvr/src/overlays/whisper.rs +++ b/wayvr/src/overlays/whisper.rs @@ -37,6 +37,8 @@ use crate::{ window::{OverlayCategory, OverlayWindowConfig}, }, }; +#[cfg(feature = "wayland")] +use wlx_common::DesktopBackend; const WHISPER_NAME: &str = "whisper"; @@ -69,19 +71,15 @@ impl WhisperState { } } -pub fn create_whisper( - app: &mut AppState, - headless: bool, - wayland: bool, -) -> anyhow::Result { - let clipboard_provider: Option> = match (headless, wayland) { +pub fn create_whisper(app: &mut AppState) -> anyhow::Result { + let clipboard_provider: Option> = match app.desktop_backend { #[cfg(feature = "wayland")] - (false, true) => clipboard::wl::Provider::new() + DesktopBackend::Wayland => clipboard::wl::Provider::new() .log_err("Could not create Wayland clipboard provider") .ok() .map(|p| Box::new(p) as Box), #[cfg(feature = "x11")] - (false, false) => clipboard::x11::Provider::new() + DesktopBackend::X11 => clipboard::x11::Provider::new() .log_err("Could not create X11 clipboard provider") .ok() .map(|p| Box::new(p) as Box), diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index e5ac8d5d..b564d2e6 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -27,6 +27,7 @@ use wlx_common::{ #[cfg(feature = "openxr")] use crate::backend; use crate::backend::wayvr::WvrServerState; +use wlx_common::DesktopBackend; use crate::subsystem::notifications::NotificationManager; #[cfg(feature = "osc")] @@ -35,7 +36,8 @@ use crate::subsystem::osc::OscSender; #[cfg(feature = "whisper")] use crate::subsystem::whisper_stt::WhisperStt; use crate::{ - backend::{XrBackend, input::InputState, task::TaskContainer}, + XrBackend, + backend::{input::InputState, task::TaskContainer}, config::load_general_config, graphics::WGfxExtras, gui, @@ -71,6 +73,7 @@ pub struct AppState { pub dbus: DbusConnector, pub xr_backend: XrBackend, + pub desktop_backend: DesktopBackend, pub ipc_server: ipc_server::WayVRServer, pub wayvr_signals: SyncEventQueue, @@ -218,6 +221,7 @@ impl AppState { xr_backend, ipc_server, wayvr_signals: wvr_signals, + desktop_backend: DesktopBackend::Headless, // set by OverlayWindowManager desktop_finder, notifications: NotificationManager::new(), @@ -249,7 +253,7 @@ impl AppState { self.notifications.run_udp(); #[cfg(feature = "openxr")] - if matches!(self.xr_backend, XrBackend::OpenXR) { + if self.xr_backend.is_open_xr() { use crate::backend::openxr::monado_state::MonadoState; log::debug!("Connecting to Monado IPC"); diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index a4e9142a..ed5c5a57 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -81,8 +81,6 @@ where initialized: false, }; - let mut wayland = false; - if headless { log::info!("Running in headless mode; keyboard will be en-US"); } else { @@ -90,7 +88,8 @@ where // this is the default and would be overwritten by // OverlayWindowManager::restore_layout down below match create_screens(app) { - Ok((data, is_wayland)) => { + Ok((data, backend)) => { + app.desktop_backend = backend; let last_idx = data.screens.len() - 1; for (idx, (meta, mut config)) in data.screens.into_iter().enumerate() { config.show_on_spawn = true; @@ -102,14 +101,12 @@ where } app.screens.push(meta); } - - wayland = is_wayland; } Err(e) => log::error!("Unable to initialize screens: {e:?}"), } } - let mut keyboard = OverlayWindowData::from_config(create_keyboard(app, wayland)?); + let mut keyboard = OverlayWindowData::from_config(create_keyboard(app)?); keyboard.config.show_on_spawn = true; me.keyboard_id = me.add(keyboard, app); @@ -145,7 +142,7 @@ where #[cfg(feature = "whisper")] { use crate::overlays::whisper::create_whisper; - let whisper = OverlayWindowData::from_config(create_whisper(app, headless, wayland)?); + let whisper = OverlayWindowData::from_config(create_whisper(app)?); me.add(whisper, app); } diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index d8cd9824..4f19435d 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -353,7 +353,7 @@ impl ParserState { "tooltip_str" => tooltip = Some(Translation::from_raw_text(value)), "action" => action_name = Some(value.into()), other => { - if !other.starts_with('_') { + if !other.starts_with('_') && other != "skip" { anyhow::bail!("unexpected \"{other}\" attribute"); } attribs.push(AttribPair::new(key, replace_vars(value, template_params))); @@ -986,7 +986,8 @@ fn parse_child<'a>( ) -> anyhow::Result<()> { let tag_name = child_node.tag_name().name(); match child_node.attribute("skip") { - Some("1") => { + Some(val) => { + // TODO: need to resolve variables in val return Ok(()); // do not parse this element } _ => {} diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index b2e4585d..eac2c0b7 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -25,18 +25,34 @@ pub enum CaptureMethod { Auto, #[serde(alias = "pipewire")] - #[strum(props(Text = "PipeWire GPU", Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP"))] + #[strum(props( + Text = "PipeWire GPU", + Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP", + Desktop = "Wayland" + ))] PipeWire, - #[strum(props(Text = "ScreenCopy GPU", Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_GPU_HELP"))] + #[strum(props( + Text = "ScreenCopy GPU", + Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_GPU_HELP", + Desktop = "Wayland" + ))] ScreenCopyGpu, #[serde(alias = "pw-fallback")] - #[strum(props(Text = "PipeWire CPU", Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP"))] + #[strum(props( + Text = "PipeWire CPU", + Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP", + Desktop = "Wayland" + ))] PipeWireCpu, #[serde(alias = "screencopy")] - #[strum(props(Text = "ScreenCopy CPU", Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_HELP"))] + #[strum(props( + Text = "ScreenCopy CPU", + Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_HELP", + Desktop = "Wayland" + ))] ScreenCopyCpu, } @@ -51,7 +67,8 @@ pub enum InputEmulationMethod { #[strum(props( Translation = "APP_SETTINGS.OPTION.WL_VIRTUAL", - Tooltip = "APP_SETTINGS.OPTION.WL_VIRTUAL_HELP" + Tooltip = "APP_SETTINGS.OPTION.WL_VIRTUAL_HELP", + Desktop = "Wayland" ))] WlVirtual, @@ -97,9 +114,9 @@ pub enum HandsfreePointer { Hmd, #[strum(props(Translation = "APP_SETTINGS.OPTION.HMD_ONLY"))] HmdOnly, - #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_PINCH"))] + #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_PINCH", Backend = "OpenXR"))] EyeTracking, - #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_ONLY"))] + #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_ONLY", Backend = "OpenXR"))] EyeTrackingOnly, } @@ -108,7 +125,7 @@ pub enum HandsfreeAltTab { #[strum(props(Translation = "APP_SETTINGS.OPTION.HMD_ONLY"))] #[default] Hmd, - #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_ONLY"))] + #[strum(props(Translation = "APP_SETTINGS.OPTION.EYE_ONLY", Backend = "OpenXR"))] EyeTracking, } diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index 420931a4..3318f620 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -3,7 +3,7 @@ use wayvr_ipc::{ packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle}, }; -use crate::{config::GeneralConfig, desktop_finder::DesktopFinder}; +use crate::{DesktopBackend, XrBackend, config::GeneralConfig, desktop_finder::DesktopFinder}; #[derive(Clone)] pub struct MonadoClient { @@ -47,7 +47,8 @@ pub enum DashPlayspaceTask { #[derive(Clone, Copy)] pub struct InterfaceFeats { - pub openxr: bool, + pub xr_backend: XrBackend, + pub desktop_backend: DesktopBackend, pub monado: bool, pub whisper: bool, } diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index 9a4ff1a0..52603ab4 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -1,4 +1,4 @@ -use slotmap::{DenseSlotMap, new_key_type}; +use slotmap::{new_key_type, DenseSlotMap}; use wayvr_ipc::{ packet_client::WvrProcessLaunchParams, packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle}, @@ -235,7 +235,8 @@ impl DashInterface<()> for DashInterfaceEmulated { fn get_feats(&mut self, _data: &mut ()) -> dash_interface::InterfaceFeats { dash_interface::InterfaceFeats { - openxr: true, + xr_backend: crate::XrBackend::OpenXR, + desktop_backend: crate::DesktopBackend::Wayland, monado: true, whisper: true, } diff --git a/wlx-common/src/lib.rs b/wlx-common/src/lib.rs index 59be4476..2aa6f7c6 100644 --- a/wlx-common/src/lib.rs +++ b/wlx-common/src/lib.rs @@ -1,3 +1,5 @@ +use strum::{EnumIs, EnumString}; + pub mod astr_containers; pub mod async_executor; pub mod audio; @@ -17,3 +19,16 @@ pub mod overlays; pub mod palette; pub mod timestep; pub mod windowing; + +#[derive(Clone, Copy, EnumIs, EnumString, PartialEq, Eq)] +pub enum XrBackend { + OpenXR, + OpenVR, +} + +#[derive(Clone, Copy, EnumIs, EnumString, PartialEq, Eq)] +pub enum DesktopBackend { + Headless, + Wayland, + X11, +} From 7261f7fa1f10390e28a48216a4aa4b71df689403 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:30:42 +0900 Subject: [PATCH 14/54] skip attrib woes --- dash-frontend/src/tab/settings/macros.rs | 4 ++-- wayvr/src/assets/gui/keyboard.xml | 10 ++++---- wayvr/src/overlays/toast.rs | 28 ----------------------- wayvr/src/overlays/wayvr.rs | 2 +- wayvr/src/windowing/manager.rs | 4 ++-- wgui/src/parser/mod.rs | 19 ++++++++++----- wlx-common/src/dash_interface_emulated.rs | 2 +- 7 files changed, 24 insertions(+), 45 deletions(-) diff --git a/dash-frontend/src/tab/settings/macros.rs b/dash-frontend/src/tab/settings/macros.rs index 53a484ec..efb0cd98 100644 --- a/dash-frontend/src/tab/settings/macros.rs +++ b/dash-frontend/src/tab/settings/macros.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::tab::settings::{self, horiz_cell, mount_requires_restart, SettingType, Task}; +use crate::tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart}; use wgui::{ components::{ button::{ButtonClickEvent, ComponentButton}, @@ -13,7 +13,7 @@ use wgui::{ widget::label::WidgetLabel, windowing::context_menu, }; -use wlx_common::{config::GeneralConfig, dash_interface::InterfaceFeats, DesktopBackend, XrBackend}; +use wlx_common::{DesktopBackend, XrBackend, config::GeneralConfig, dash_interface::InterfaceFeats}; pub fn options_category( mp: &mut MacroParams, diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 08e9eedd..0aceeef2 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -172,8 +172,8 @@ - - + + @@ -181,8 +181,8 @@ - - + + @@ -216,7 +216,7 @@
-
+
diff --git a/wayvr/src/overlays/toast.rs b/wayvr/src/overlays/toast.rs index 887e7af7..8ed82e84 100644 --- a/wayvr/src/overlays/toast.rs +++ b/wayvr/src/overlays/toast.rs @@ -207,31 +207,3 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { ..OverlayWindowConfig::from_backend(Box::new(panel)) }) } - -// FIXME: Will these functions be used in the future? Can they be removed? - -#[allow(dead_code)] -fn msg_err(app: &mut AppState, message: &str) { - Toast::new(ToastTopic::Error, "TOAST.ERROR".into(), message.into()) - .with_timeout(3.) - .submit(app); -} - -// Display the same error in the terminal and as a toast in VR. -// Formatted as "Failed to XYZ: Object is not defined" -#[allow(dead_code)] -pub fn error_toast(app: &mut AppState, title: &str, err: ErrorType) -where - ErrorType: std::fmt::Display + std::fmt::Debug, -{ - log::error!("{title}: {err:?}"); // More detailed version (use Debug) - - // Brief version (use Display) - msg_err(app, &format!("{title}: {err}")); -} - -#[allow(dead_code)] -pub fn error_toast_str(app: &mut AppState, message: &str) { - log::error!("{message}"); - msg_err(app, message); -} diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index beb6632a..760c62b0 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -731,7 +731,7 @@ impl OverlayBackend for WvrWindowBackend { match state.cursor_image { CursorImageStatus::Hidden => {} CursorImageStatus::Named(_) | CursorImageStatus::Surface(_) => { - // FIXME: properly render surface? + // TODO: properly render surface? if let Some(mouse) = self.mouse.as_ref() { self.pipeline.as_mut().unwrap().render_mouse(mouse, rdr)?; } diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index ed5c5a57..f6ce3830 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -691,12 +691,12 @@ impl OverlayWindowManager { self.wrappers .wrap_edit_mode(id, &mut overlay.config, app) .inspect_err(|e| log::error!("{e:?}")) - .unwrap(); // FIXME: unwrap + .unwrap(); } else { self.wrappers .unwrap_edit_mode(&mut overlay.config, app) .inspect_err(|e| log::error!("{e:?}")) - .unwrap(); // FIXME: unwrap + .unwrap(); } } diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index 4f19435d..0576d3cd 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -334,7 +334,7 @@ impl ParserState { let mut cells = Vec::::new(); - for child in el_context_menu.children() { + 'children: for child in el_context_menu.children() { match child.tag_name().name() { "" => {} "cell" => { @@ -352,8 +352,15 @@ impl ParserState { "tooltip" => tooltip = Some(Translation::from_translation_key(value)), "tooltip_str" => tooltip = Some(Translation::from_raw_text(value)), "action" => action_name = Some(value.into()), + "skip" => { + let resolved = replace_vars(value, template_params); + //FIXME: this is always empty + if &*resolved == "1" { + continue 'children; + } + } other => { - if !other.starts_with('_') && other != "skip" { + if !other.starts_with('_') { anyhow::bail!("unexpected \"{other}\" attribute"); } attribs.push(AttribPair::new(key, replace_vars(value, template_params))); @@ -985,12 +992,12 @@ fn parse_child<'a>( parent_id: WidgetID, ) -> anyhow::Result<()> { let tag_name = child_node.tag_name().name(); - match child_node.attribute("skip") { - Some(val) => { - // TODO: need to resolve variables in val + if let Some(skip) = child_node.attribute("skip") { + let resolved = process_attrib(&file.template_parameters, ctx, "skip", skip).value; + //FIXME: this is always empty + if &*resolved == "1" { return Ok(()); // do not parse this element } - _ => {} } let attribs = process_attribs(file, ctx, &child_node, false); diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index 52603ab4..153ce13d 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -1,4 +1,4 @@ -use slotmap::{new_key_type, DenseSlotMap}; +use slotmap::{DenseSlotMap, new_key_type}; use wayvr_ipc::{ packet_client::WvrProcessLaunchParams, packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle}, From 98d8a3138d98eafa745b38a31d3c7398206a3870 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:19:28 +0900 Subject: [PATCH 15/54] actually pass the params --- wayvr/src/overlays/keyboard/builder.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index b93ad5dd..06d7d1b5 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -57,7 +57,7 @@ pub(super) fn create_keyboard_panel( state: KeyboardState, layout: &layout::Layout, ) -> anyhow::Result> { - let mut params = NewGuiPanelParams::::default(); + let mut params = NewGuiPanelParams::default(); params .extra_vars .insert("openvr".into(), bool_to_rc_str(app.xr_backend.is_open_vr())); @@ -66,8 +66,7 @@ pub(super) fn create_keyboard_panel( bool_to_rc_str(!app.desktop_backend.is_wayland()), ); - let mut panel = - GuiPanel::new_from_template(app, "gui/keyboard.xml", state, NewGuiPanelParams::default())?; + let mut panel = GuiPanel::new_from_template(app, "gui/keyboard.xml", state, params)?; let doc_params = new_doc_params(&mut panel); From 4ad6287efcbc2d20ac299d4da0d8005870dbe29d Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:09:57 +0900 Subject: [PATCH 16/54] refactor: InterfaceFeats in AppState --- wayvr/src/assets/gui/keyboard.xml | 2 +- wayvr/src/backend/openvr/mod.rs | 6 ++++-- wayvr/src/backend/openxr/mod.rs | 15 +++++++++++++-- wayvr/src/overlays/dashboard.rs | 15 ++------------- wayvr/src/overlays/keyboard/builder.rs | 8 ++++++-- wayvr/src/overlays/keyboard/mod.rs | 2 +- wayvr/src/overlays/screen/mirror.rs | 8 ++++++-- wayvr/src/overlays/screen/pw.rs | 2 +- wayvr/src/overlays/screen/wl.rs | 2 +- wayvr/src/overlays/screen/x11.rs | 4 ++-- wayvr/src/overlays/wayvr.rs | 6 +++++- wayvr/src/overlays/whisper.rs | 2 +- wayvr/src/state.rs | 15 +++++++-------- wayvr/src/windowing/manager.rs | 2 +- wlx-common/src/dash_interface.rs | 13 +++++++++++++ wlx-common/src/dash_interface_emulated.rs | 1 + 16 files changed, 65 insertions(+), 38 deletions(-) diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 0aceeef2..ebb3d59c 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -182,7 +182,7 @@ - + diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index add2c4dc..107e17a5 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -12,7 +12,7 @@ use ovr_overlay::{ }; use smallvec::smallvec; use vulkano::{Handle, VulkanObject, device::physical::PhysicalDevice}; -use wlx_common::overlays::ToastTopic; +use wlx_common::{dash_interface::InterfaceFeats, overlays::ToastTopic}; use crate::{ Args, FRAME_COUNTER, RUNNING, XrBackend, @@ -87,9 +87,11 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> { names.iter().map(std::string::String::as_str).collect() }; + let feats = InterfaceFeats::default_for_backend(XrBackend::OpenVR); + let mut app = { let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?; - AppState::from_graphics(gfx, gfx_extras, XrBackend::OpenVR)? + AppState::from_graphics(gfx, gfx_extras, feats)? }; app.session.no_autostart = args.no_autostart; diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 4e33eef3..b63b5026 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -10,7 +10,10 @@ use input::OpenXrInputSource; use openxr as xr; use skybox::{Skybox, create_skybox}; use vulkano::{Handle, VulkanObject}; -use wlx_common::overlays::{StereoMode, ToastTopic}; +use wlx_common::{ + dash_interface::InterfaceFeats, + overlays::{StereoMode, ToastTopic}, +}; use crate::{ Args, FRAME_COUNTER, RUNNING, XrBackend, @@ -63,9 +66,17 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> { } }; + let feats = InterfaceFeats { + passthru: xr_instance + .exts() + .fb_composition_layer_alpha_blend + .is_some(), + ..InterfaceFeats::default_for_backend(XrBackend::OpenXR) + }; + let mut app = { let (gfx, gfx_extras) = init_openxr_graphics(xr_instance.clone(), system)?; - AppState::from_graphics(gfx, gfx_extras, XrBackend::OpenXR)? + AppState::from_graphics(gfx, gfx_extras, feats)? }; app.session.no_autostart = args.no_autostart; diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index 09cbefd5..a228daf9 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -91,7 +91,7 @@ impl DashFrontend { interface: Box::new(interface), lang_provider: &WayVRLangProvider::from_config(&app.session.config), show_welcome: tutorial, - has_monado: app.xr_backend.is_open_xr(), + has_monado: app.feats.xr_backend.is_open_xr(), theme: app.wgui_theme.clone(), color_palette: &*app.session.config.color_palette, executor: app.executor.clone(), @@ -578,18 +578,7 @@ impl DashInterface for DashInterfaceLive { } fn get_feats(&mut self, data: &mut AppState) -> dash_interface::InterfaceFeats { - dash_interface::InterfaceFeats { - xr_backend: data.xr_backend, - desktop_backend: data.desktop_backend, - #[cfg(feature = "openxr")] - monado: data.monado_state.is_some(), - #[cfg(not(feature = "openxr"))] - monado: false, - #[cfg(feature = "whisper")] - whisper: true, - #[cfg(not(feature = "whisper"))] - whisper: false, - } + data.feats } #[cfg(feature = "openxr")] diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 06d7d1b5..dfb73514 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -58,12 +58,16 @@ pub(super) fn create_keyboard_panel( layout: &layout::Layout, ) -> anyhow::Result> { let mut params = NewGuiPanelParams::default(); + params.extra_vars.insert( + "openvr".into(), + bool_to_rc_str(app.feats.xr_backend.is_open_vr()), + ); params .extra_vars - .insert("openvr".into(), bool_to_rc_str(app.xr_backend.is_open_vr())); + .insert("not_passthru".into(), bool_to_rc_str(!app.feats.passthru)); params.extra_vars.insert( "not_wayland".into(), - bool_to_rc_str(!app.desktop_backend.is_wayland()), + bool_to_rc_str(!app.feats.desktop_backend.is_wayland()), ); let mut panel = GuiPanel::new_from_template(app, "gui/keyboard.xml", state, params)?; diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 87983bf0..c8b71e10 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -70,7 +70,7 @@ pub fn create_keyboard(app: &mut AppState) -> anyhow::Result, app: &mut AppState) -> anyhow::Result>, app: &mut AppState, ) -> Box { - let renderer = - ScreenBackend::new_raw(name.clone(), app.xr_backend, CaptureType::PipeWire, capture); + let renderer = ScreenBackend::new_raw( + name.clone(), + app.feats.xr_backend, + CaptureType::PipeWire, + capture, + ); let backend = MirrorBackend(renderer); diff --git a/wayvr/src/overlays/screen/pw.rs b/wayvr/src/overlays/screen/pw.rs index 782953d8..66cb22e0 100644 --- a/wayvr/src/overlays/screen/pw.rs +++ b/wayvr/src/overlays/screen/pw.rs @@ -81,7 +81,7 @@ impl ScreenCastBackend { use wlx_capture::frame::Transform; let mut backend = - ScreenBackend::new_raw(name, app.xr_backend, CaptureType::PipeWire, capture); + ScreenBackend::new_raw(name, app.feats.xr_backend, CaptureType::PipeWire, capture); backend.logical_pos = logical_pos; backend.logical_size = logical_size; diff --git a/wayvr/src/overlays/screen/wl.rs b/wayvr/src/overlays/screen/wl.rs index ba1692a6..bd7baea4 100644 --- a/wayvr/src/overlays/screen/wl.rs +++ b/wayvr/src/overlays/screen/wl.rs @@ -29,7 +29,7 @@ impl ScreenBackend { ); Some(Self::new_raw( output.name.clone(), - app.xr_backend, + app.feats.xr_backend, CaptureType::ScreenCopy, capture, )) diff --git a/wayvr/src/overlays/screen/x11.rs b/wayvr/src/overlays/screen/x11.rs index 783f518e..6d648608 100644 --- a/wayvr/src/overlays/screen/x11.rs +++ b/wayvr/src/overlays/screen/x11.rs @@ -33,7 +33,7 @@ impl ScreenBackend { ); Self::new_raw( screen.name.clone(), - app.xr_backend, + app.feats.xr_backend, CaptureType::Xshm, capture, ) @@ -103,7 +103,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result anyhow::Result { - let clipboard_provider: Option> = match app.desktop_backend { + let clipboard_provider: Option> = match app.feats.desktop_backend { #[cfg(feature = "wayland")] DesktopBackend::Wayland => clipboard::wl::Provider::new() .log_err("Could not create Wayland clipboard provider") diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index b564d2e6..c4335a3a 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -27,7 +27,7 @@ use wlx_common::{ #[cfg(feature = "openxr")] use crate::backend; use crate::backend::wayvr::WvrServerState; -use wlx_common::DesktopBackend; +use wlx_common::dash_interface::InterfaceFeats; use crate::subsystem::notifications::NotificationManager; #[cfg(feature = "osc")] @@ -36,7 +36,6 @@ use crate::subsystem::osc::OscSender; #[cfg(feature = "whisper")] use crate::subsystem::whisper_stt::WhisperStt; use crate::{ - XrBackend, backend::{input::InputState, task::TaskContainer}, config::load_general_config, graphics::WGfxExtras, @@ -72,8 +71,7 @@ pub struct AppState { pub dbus: DbusConnector, - pub xr_backend: XrBackend, - pub desktop_backend: DesktopBackend, + pub feats: InterfaceFeats, pub ipc_server: ipc_server::WayVRServer, pub wayvr_signals: SyncEventQueue, @@ -102,7 +100,7 @@ impl AppState { pub fn from_graphics( gfx: Arc, gfx_extras: WGfxExtras, - xr_backend: XrBackend, + feats: InterfaceFeats, ) -> anyhow::Result { // insert shared resources let mut tasks = TaskContainer::new(); @@ -218,10 +216,9 @@ impl AppState { wgui_theme: Rc::new(theme), executor, dbus, - xr_backend, + feats, ipc_server, wayvr_signals: wvr_signals, - desktop_backend: DesktopBackend::Headless, // set by OverlayWindowManager desktop_finder, notifications: NotificationManager::new(), @@ -253,7 +250,7 @@ impl AppState { self.notifications.run_udp(); #[cfg(feature = "openxr")] - if self.xr_backend.is_open_xr() { + if self.feats.xr_backend.is_open_xr() { use crate::backend::openxr::monado_state::MonadoState; log::debug!("Connecting to Monado IPC"); @@ -262,9 +259,11 @@ impl AppState { match MonadoState::new() { Ok(m) => { self.monado_state = Some(m); + self.feats.monado = true; } Err(e) => { log::error!("Will not use libmonado: {e:?}"); + self.feats.monado = false; } } } diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index f6ce3830..89e9f6b3 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -89,7 +89,7 @@ where // OverlayWindowManager::restore_layout down below match create_screens(app) { Ok((data, backend)) => { - app.desktop_backend = backend; + app.feats.desktop_backend = backend; let last_idx = data.screens.len() - 1; for (idx, (meta, mut config)) in data.screens.into_iter().enumerate() { config.show_on_spawn = true; diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index 3318f620..d61d4311 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -50,9 +50,22 @@ pub struct InterfaceFeats { pub xr_backend: XrBackend, pub desktop_backend: DesktopBackend, pub monado: bool, + pub passthru: bool, pub whisper: bool, } +impl InterfaceFeats { + pub fn default_for_backend(xr_backend: XrBackend) -> Self { + Self { + xr_backend, + desktop_backend: DesktopBackend::Headless, + monado: false, + passthru: false, + whisper: false, + } + } +} + pub trait DashInterface { fn window_list(&mut self, data: &mut T) -> anyhow::Result>; fn window_set_visible(&mut self, data: &mut T, handle: WvrWindowHandle, visible: bool) -> anyhow::Result<()>; diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index 153ce13d..699282af 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -238,6 +238,7 @@ impl DashInterface<()> for DashInterfaceEmulated { xr_backend: crate::XrBackend::OpenXR, desktop_backend: crate::DesktopBackend::Wayland, monado: true, + passthru: false, whisper: true, } } From 05708813bee6ff167ecbba6f999f0f55d8da4609 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:43:59 +0900 Subject: [PATCH 17/54] start blocking poses after keyboard was clicked once --- wayvr/src/backend/input.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index 48626692..824d3c47 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -14,14 +14,13 @@ use wlx_common::windowing::{OverlayWindowState, Positioning}; use crate::backend::task::{InputTask, OverlayTask}; use crate::overlays::anchor::{ANCHOR_NAME, GRAB_HELP_NAME}; -use crate::overlays::keyboard::KEYBOARD_NAME; use crate::overlays::watch::WATCH_NAME; use crate::state::{AppSession, AppState}; use crate::subsystem::hid::WheelDelta; use crate::subsystem::input::InputFocus; use crate::windowing::backend::OverlayEventData; use crate::windowing::manager::OverlayWindowManager; -use crate::windowing::window::{self, OverlayWindowData, realign, scalar_scale}; +use crate::windowing::window::{self, OverlayCategory, OverlayWindowData, realign, scalar_scale}; use crate::windowing::{OverlayID, OverlaySelector}; use super::task::TaskType; @@ -281,6 +280,7 @@ pub struct InteractionState { pub hovered_id: Option, pub should_block_input: bool, pub should_block_poses: bool, + pub kbd_block_activated: bool, } impl Default for InteractionState { @@ -292,6 +292,7 @@ impl Default for InteractionState { hovered_id: None, should_block_input: false, should_block_poses: false, + kbd_block_activated: false, } } } @@ -490,6 +491,7 @@ where if !pointer.tracked { pointer.interaction.should_block_input = false; pointer.interaction.should_block_poses = false; + pointer.interaction.kbd_block_activated = false; return (None, pending_haptics); // no hit } @@ -554,7 +556,8 @@ where pointer.interaction.should_block_poses = state.block_input && app.session.config.block_poses_on_kbd_interaction - && hovered.config.name.as_ref() == KEYBOARD_NAME; + && hovered.config.category == OverlayCategory::Keyboard + && pointer.interaction.kbd_block_activated; } else { pointer.interaction.should_block_input = false; pointer.interaction.should_block_poses = false; @@ -598,6 +601,9 @@ where let pointer = &mut app.input_state.pointers[hit.pointer]; if pointer.now.click && !pointer.before.click { pointer.interaction.clicked_id = Some(hit.overlay); + if hovered.config.category == OverlayCategory::Keyboard { + pointer.interaction.kbd_block_activated = true; + } update_focus(app, hovered.config.input_focus); hovered.config.backend.on_pointer(app, &hit, true); } else if !pointer.now.click && pointer.before.click { @@ -634,6 +640,7 @@ fn handle_no_hit( let pointer = &mut app.input_state.pointers[pointer_idx]; pointer.interaction.should_block_input = false; pointer.interaction.should_block_poses = false; + pointer.interaction.kbd_block_activated = false; // in case click released while not aiming at anything // send release event to overlay that was originally clicked From 3cfbafa577cec669208e9bcd9091d62ab5e18826 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:01:17 +0900 Subject: [PATCH 18/54] focus mode: add keyboard toggle, num keys --- wayvr/src/assets/gui/alttab-help.xml | 8 +++++++ wayvr/src/assets/lang/en.json | 1 + wayvr/src/backend/wayvr/mod.rs | 36 +++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/wayvr/src/assets/gui/alttab-help.xml b/wayvr/src/assets/gui/alttab-help.xml index 416afc37..313dcd0c 100644 --- a/wayvr/src/assets/gui/alttab-help.xml +++ b/wayvr/src/assets/gui/alttab-help.xml @@ -60,6 +60,14 @@
+
+
+
+
+
+
+