From db1675d3992cc39ba995d650eb11cddfb4e2dac8 Mon Sep 17 00:00:00 2001 From: Diamond White <53794934+oneshinyboi@users.noreply.github.com> Date: Fri, 10 Apr 2026 00:46:22 -0500 Subject: [PATCH 001/307] feat: swipe to type (#451) * start to integrate swipetype with keyboardstate * successful debug implementation * change to git dependency * add manual trigger to app image build to test * use latest swipeType lib to test including binaries * use clipboard to paste swipe output * use new prediction engine, move swipe type related logic to its own module * update appimage bulid to support building ort runtime * implement swipe selection ui in keyboard overlay * only add motion data for the device that started the swipe * process swipes on separate thread to not block ui * add toggle to settings.rs * default swipe to type config to false * remove println, minor fix * use correct key_dimensions * use latest swipe_type library version * optionally use wayvr compositor clipboard when pasting * remove unused imports * fix typo in log::error * fix within key positioning * run swipes on AltGr keycaps --- .github/workflows/build-appimage.yml | 3 +- Cargo.lock | 1099 ++++++++++++++++- dash-frontend/assets/lang/en.json | 2 + dash-frontend/src/tab/settings/mod.rs | 4 + .../src/tab/settings/tab_features.rs | 1 + wayvr/Cargo.toml | 2 + wayvr/src/assets/gui/keyboard.xml | 17 +- wayvr/src/backend/wayvr/client.rs | 15 +- wayvr/src/backend/wayvr/mod.rs | 5 +- wayvr/src/config.rs | 2 + wayvr/src/main.rs | 1 + wayvr/src/overlays/keyboard/builder.rs | 221 +++- wayvr/src/overlays/keyboard/layout.rs | 3 +- wayvr/src/overlays/keyboard/mod.rs | 201 ++- wayvr/src/overlays/keyboard/swipe_type.rs | 238 ++++ wayvr/src/subsystem/hid/mod.rs | 2 + wgui/src/event.rs | 4 + wlx-common/src/config.rs | 3 + 18 files changed, 1758 insertions(+), 65 deletions(-) create mode 100644 wayvr/src/overlays/keyboard/swipe_type.rs diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 9eb37b2c..826fc778 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -1,6 +1,7 @@ name: Build AppImage on: + workflow_dispatch: push: branches: - 'main' @@ -15,7 +16,7 @@ env: jobs: build_appimage: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/Cargo.lock b/Cargo.lock index 18302e7a..847a98bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "ahash" version = "0.8.12" @@ -195,9 +206,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "appendlist" @@ -233,6 +244,27 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -665,6 +697,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bcdec_rs" version = "0.2.0" @@ -800,6 +838,37 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cached-path" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e79c94134331f0107c9f5b7caaf15f118ec91de67dcd3fa1145921e9fc27fa" +dependencies = [ + "flate2", + "fs2", + "glob", + "infer", + "log", + "rand 0.10.0", + "reqwest", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "thiserror 2.0.17", + "zip", +] + [[package]] name = "calloop" version = "0.13.0" @@ -878,6 +947,17 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-expr" version = "0.15.8" @@ -910,6 +990,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.42" @@ -934,6 +1025,16 @@ dependencies = [ "phf", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -985,6 +1086,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.57" @@ -1076,6 +1186,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.4.0" @@ -1231,6 +1347,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1464,6 +1589,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "deflate64" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -1494,6 +1635,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1745,6 +1887,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "etagere" version = "0.2.15" @@ -1806,6 +1954,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.113", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1815,6 +1983,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.6" @@ -1840,6 +2019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -1950,6 +2130,22 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "futures" version = "0.3.31" @@ -2102,10 +2298,26 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + [[package]] name = "gif" version = "0.14.1" @@ -2262,6 +2474,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + [[package]] name = "hound" version = "3.5.1" @@ -2335,6 +2562,45 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.64" @@ -2440,6 +2706,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2517,8 +2789,9 @@ dependencies = [ "png 0.18.0", "ravif", "rayon", - "zune-core", - "zune-jpeg", + "tiff", + "zune-core 0.5.0", + "zune-jpeg 0.5.8", ] [[package]] @@ -2575,6 +2848,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -2583,6 +2858,24 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "input-linux" version = "0.7.1" @@ -2629,6 +2922,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2795,12 +3104,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + [[package]] name = "libc" version = "0.2.179" @@ -2904,6 +3225,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -2980,6 +3310,21 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" + +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2", +] + [[package]] name = "mach2" version = "0.4.3" @@ -2995,7 +3340,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata", + "regex-automata 0.4.13", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", ] [[package]] @@ -3014,6 +3369,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "memmap2" version = "0.9.9" @@ -3130,6 +3495,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3264,6 +3644,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -3373,6 +3762,18 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-audio-toolbox" version = "0.3.2" @@ -3457,6 +3858,19 @@ dependencies = [ "objc2 0.6.3", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -3521,6 +3935,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -3529,7 +3954,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3754,6 +4179,40 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ort" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133" +dependencies = [ + "ndarray", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" +dependencies = [ + "hmac-sha256", + "lzma-rust2 0.15.7", + "ureq", +] + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "ouroboros" version = "0.18.5" @@ -3861,6 +4320,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4090,6 +4568,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4295,6 +4779,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.7.3" @@ -4330,6 +4820,17 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -4387,6 +4888,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_hc" version = "0.2.0" @@ -4488,6 +4995,12 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.11.0" @@ -4554,14 +5067,24 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.13", + "regex-syntax 0.8.8", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "fst", + "regex-syntax 0.6.29", ] [[package]] @@ -4572,15 +5095,59 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.8", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "resvg" version = "0.45.1" @@ -4763,6 +5330,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -4963,6 +5539,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -4976,6 +5564,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4983,7 +5582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5271,6 +5870,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5319,6 +5929,28 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "super-swipe-type" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22126517bdf94f41b3d0af625fdf69b5ff131b7dd19a59866d3c00a56072af88" +dependencies = [ + "anyhow", + "cached-path", + "fst", + "memmap", + "ort", + "regex", + "regex-automata 0.1.10", + "vector2", +] + [[package]] name = "svg_fmt" version = "0.4.5" @@ -5417,6 +6049,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -5476,6 +6117,17 @@ dependencies = [ "slotmap", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -5553,6 +6205,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "time" version = "0.3.47" @@ -5561,6 +6227,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -5670,6 +6337,16 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -5760,6 +6437,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -5813,7 +6535,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex-automata", + "regex-automata 0.4.13", "sharded-slab", "smallvec", "thread_local", @@ -5822,6 +6544,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom 8.0.0", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -5837,6 +6570,12 @@ dependencies = [ "core_maths", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -5925,12 +6664,48 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "ureq" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf-8", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.7" @@ -5965,6 +6740,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6013,6 +6794,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vector2" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f562f8d4f41867568374e1a63bd12914795f558319f9bf9b0582c4d96a8de04" + [[package]] name = "version-compare" version = "0.2.1" @@ -6126,7 +6913,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -6187,6 +6983,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.12.1", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.12.1", + "semver", +] + [[package]] name = "wayland-backend" version = "0.3.12" @@ -6343,6 +7173,7 @@ name = "wayvr" version = "26.2.1" dependencies = [ "anyhow", + "arboard", "ash", "bytes", "chrono", @@ -6382,6 +7213,7 @@ dependencies = [ "smithay", "smol", "strum", + "super-swipe-type", "sysinfo", "thiserror 2.0.17", "tracing", @@ -6450,6 +7282,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -6952,7 +7793,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -6995,6 +7836,112 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.12.1", + "prettyplease", + "syn 2.0.113", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.113", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.12.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.12.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 1.1.3", + "thiserror 2.0.17", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "wlx-capture" version = "0.6.0" @@ -7073,6 +8020,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.3", +] + [[package]] name = "xcb" version = "1.7.0" @@ -7315,6 +8272,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.113", +] + [[package]] name = "zerotrie" version = "0.2.3" @@ -7348,12 +8325,91 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "zip" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.2", + "hmac", + "indexmap 2.12.1", + "lzma-rust2 0.16.2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + [[package]] name = "zmij" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.0" @@ -7369,13 +8425,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" dependencies = [ - "zune-core", + "zune-core 0.5.0", ] [[package]] diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index beb5eff9..96b56062 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -63,6 +63,8 @@ "KEYBOARD_MIDDLE_CLICK": "Keyboard middle click", "KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser", "KEYBOARD_SOUND_ENABLED": "Keyboard sounds", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Keyboard swipe to type", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Only works for English! Results will be drastically worse on non qwerty keyboards.", "LANGUAGE": "Language", "LEFT_HANDED_MOUSE": "Left-handed mouse", "LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index bcd79abf..55eb02a9 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -231,6 +231,7 @@ enum SettingType { InvertScrollDirectionY, KeyboardMiddleClick, KeyboardSoundEnabled, + KeyboardSwipeToTypeEnabled, Language, LeftHandedMouse, LongPressDuration, @@ -283,6 +284,7 @@ impl SettingType { Self::HideUsername => &mut config.hide_username, Self::OpaqueBackground => &mut config.opaque_background, Self::XwaylandByDefault => &mut config.xwayland_by_default, + Self::KeyboardSwipeToTypeEnabled => &mut config.keyboard_swipe_to_type_enabled, _ => panic!("Requested bool for non-bool SettingType"), } } @@ -381,6 +383,7 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), + Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), @@ -417,6 +420,7 @@ impl SettingType { Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"), Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), + Self::KeyboardSwipeToTypeEnabled => Some("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index eb0e9865..264a40fd 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -9,6 +9,7 @@ pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { options_checkbox(mp, c, SettingType::NotificationsEnabled)?; options_checkbox(mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(mp, c, SettingType::KeyboardSoundEnabled)?; + options_checkbox(mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; options_checkbox(mp, c, SettingType::SpaceDragUnlocked)?; options_checkbox(mp, c, SettingType::SpaceRotateUnlocked)?; options_slider_f32(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5)?; diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 843ee83f..eb7e052a 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -90,6 +90,8 @@ xcb = { version = "1.6.0", features = [ "as-raw-xcb-connection", ], optional = true } xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros +arboard = { version="3.6.1", features = ["wayland-data-control", "wl-clipboard-rs"] } +super-swipe-type = "0.2.2" [build-dependencies] regex.workspace = true diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 4d6fc321..405823d8 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -13,6 +13,11 @@ width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}" /> + + @@ -90,6 +95,13 @@ + + + @@ -231,7 +243,10 @@ -
+
+ + +
diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 862fcc5d..662eee26 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -7,10 +7,12 @@ use smithay::{ reexports::wayland_server, utils::SerialCounter, }; +use smithay::input::Seat; +use smithay::wayland::selection::data_device::set_data_device_selection; use xkbcommon::xkb; use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; - +use crate::backend::wayvr::comp::Application; use super::{ ProcessWayVREnv, comp::{self, ClientState}, @@ -26,6 +28,7 @@ pub struct WayVRCompositor { pub state: comp::Application, pub seat_keyboard: KeyboardHandle, pub seat_pointer: PointerHandle, + pub seat: Seat, pub serial_counter: SerialCounter, pub wayland_env: super::WaylandEnv, @@ -68,6 +71,7 @@ impl WayVRCompositor { display: wayland_server::Display, seat_keyboard: KeyboardHandle, seat_pointer: PointerHandle, + seat: Seat, ) -> anyhow::Result { let (wayland_env, listener) = create_wayland_listener()?; @@ -81,6 +85,7 @@ impl WayVRCompositor { serial_counter: SerialCounter::new(), clients: Vec::new(), toplevel_surf_count: 0, + seat, }) } @@ -200,6 +205,14 @@ impl WayVRCompositor { ); } + pub fn set_clipboard_text(&mut self, text: String) { + set_data_device_selection::( + &self.state.display_handle, + &self.seat, + vec!["text/plain;charset=utf-8".to_string(), "text/plain".to_string()], + text.as_bytes().into(), + ); + } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { // Smithay only accepts keymaps in a string form due to thread safety concerns self.seat_keyboard diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 5d83f02b..119248bf 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -241,7 +241,7 @@ impl WvrServerState { }; Ok(Self { - manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer)?, + manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer, seat)?, processes: ProcessVec::new(), wm: window::WindowManager::new(), ticks: 0, @@ -608,6 +608,9 @@ impl WvrServerState { pub fn send_key(&mut self, virtual_key: u32, down: bool) { self.manager.send_key(virtual_key, down); } + pub fn set_clipboard_text(&mut self, text: String) { + self.manager.set_clipboard_text(text); + } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { self.manager.set_keymap(keymap) diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 9eb28d2b..ab19a337 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -140,6 +140,7 @@ pub struct AutoSettings { pub context_menu_hold_and_release: bool, pub capture_method: CaptureMethod, pub keyboard_middle_click_mode: AltModifier, + pub keyboard_swipe_to_type_enabled: bool, pub autostart_apps: Vec, pub handsfree_pointer: HandsfreePointer, pub language: Option, @@ -191,6 +192,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { context_menu_hold_and_release: config.context_menu_hold_and_release, capture_method: config.capture_method, keyboard_middle_click_mode: config.keyboard_middle_click_mode, + keyboard_swipe_to_type_enabled: config.keyboard_swipe_to_type_enabled, autostart_apps: config.autostart_apps.clone(), handsfree_pointer: config.handsfree_pointer, language: config.language, diff --git a/wayvr/src/main.rs b/wayvr/src/main.rs index f39bf450..8c692ca9 100644 --- a/wayvr/src/main.rs +++ b/wayvr/src/main.rs @@ -281,6 +281,7 @@ fn logging_init(args: &mut Args) { .from_env_lossy() .add_directive("symphonia_core::probe=warn".parse().unwrap()) .add_directive("symphonia_bundle_mp3=warn".parse().unwrap()) + .add_directive("ort=warn".parse().unwrap()) .add_directive("zbus=warn".parse().unwrap()) .add_directive("usvg=error".parse().unwrap()) .add_directive("resvg=error".parse().unwrap()) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index b96b0368..c1b957f5 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, rc::Rc, time::Duration}; - use crate::{ app_misc, gui::{ @@ -11,8 +10,9 @@ use crate::{ subsystem::hid::XkbKeymap, windowing::backend::OverlayEventData, }; -use anyhow::Context; +use anyhow::{bail, Context}; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; +use slotmap::Key; use wgui::{ animation::{Animation, AnimationEasing}, assets::AssetPath, @@ -25,11 +25,10 @@ use wgui::{ taffy::{self, prelude::length}, widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; - -use super::{ - KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, - layout::{self, KeyCapType}, -}; +use wgui::event::StyleSetRequest; +use wgui::layout::LayoutTask; +use wgui::taffy::Display; +use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; const PIXELS_PER_UNIT: f32 = 60.; @@ -41,6 +40,147 @@ fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'s } } +pub(super) fn update_swipe_prediction_bar( + panel: &mut GuiPanel, + app: &mut AppState +) -> anyhow::Result { + let mut elements_changed = false; + + let (accent_color, anim_mult) = { + let theme = &app.wgui_theme; + (theme.accent_color, theme.animation_mult) + }; + + if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() + && let Ok(candidates) = recv.try_recv() { + + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if predictions_root.is_null() { + return Ok(elements_changed) + } + let doc_params = new_doc_params(panel); + + panel.layout.remove_children(predictions_root); + + let Some(new_suggestions) = candidates else { + return Ok(elements_changed) + }; + + let mut iter = new_suggestions.iter(); + let Some(best_prediction) = iter.next() else { + bail!("not enough swipe predictions"); + }; + if let Some(manager) = panel.state.swipe_typing_manager.as_mut() { + manager.select_word(best_prediction, app, panel.state.modifiers); + } + for (i, prediction) in iter.enumerate() { + let mut params = HashMap::new(); + let id: Rc = Rc::from(format!("Prediction-{i}")); + params.insert("id".into(), id.clone()); + params.insert("text".into(), prediction.clone().into()); + + panel.parser_state.instantiate_template( + &doc_params, + "KeyPrediction", + &mut panel.layout, + predictions_root, + params + )?; + + if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { + let key_state = { + let rect = panel + .layout + .state + .widgets + .get_as::(widget_id) + .unwrap(); // want panic + + Rc::new(KeyState { + // fake button state just so we get key state for anims + button_state: KeyButtonData::Modifier { + modifier: 0, + sticky: core::cell::Cell::new(false), + }, + color: rect.params.color, + color2: rect.params.color2, + base_border_color: rect.params.border_color, + cur_border_color: rect.params.border_color.into(), + border: rect.params.border, + drawn_state: false.into(), + }) + }; + panel.add_event_listener( + widget_id, + EventListenerKind::MousePress, + Box::new({ + let k = key_state.clone(); + let pred = prediction.clone(); + move |common, data, app, state| { + if let Some(manager) = state.swipe_typing_manager.as_mut() { + manager.select_alternate_prediction(&pred, app, state.modifiers); + on_press_anim(k.clone(), common, data) + } + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseEnter, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_enter_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseLeave, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state | { + on_leave_anim( + k.clone(), + common, + data, + accent_color, + anim_mult, + 0.0, + ); + Ok(EventResult::Pass) + } + }) + ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseRelease, + Box::new({ + let k = key_state.clone(); + move |common, data, _app, _state| { + on_release_anim(k.clone(), common, data); + Ok(EventResult::Pass) + } + }) + ); + } + } + elements_changed = true; + } + Ok(elements_changed) +} #[allow(clippy::too_many_lines, clippy::significant_drop_tightening)] pub(super) fn create_keyboard_panel( app: &mut AppState, @@ -113,7 +253,7 @@ pub(super) fn create_keyboard_panel( params.insert(Rc::from("width"), Rc::from(key_width.to_string())); params.insert(Rc::from("height"), Rc::from(key_height.to_string())); - let mut label = key.label.into_iter(); + let mut label = key.label.clone().into_iter(); label .next() .and_then(|s| params.insert("text".into(), s.into())); @@ -169,6 +309,9 @@ pub(super) fn create_keyboard_panel( }) }; + let key_cap_type: Rc = Rc::from(key.cap_type); + let key_label: Rc> = Rc::from(key.label); + let width_mul = 1. / my_size_f32; panel.add_event_listener( @@ -186,6 +329,8 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); + + Ok(EventResult::Pass) } }), @@ -205,6 +350,7 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); + Ok(EventResult::Pass) } }), @@ -214,26 +360,49 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MousePress, Box::new({ let k = key_state.clone(); + let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; + let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press(app, &k, state, button); + handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button, button.device); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } }), ); + panel.add_event_listener( + widget_id, + EventListenerKind::MouseMotion, + Box::new({ + let k = key_state.clone(); + let k_label = key_label.clone(); + let k_cap_type = key_cap_type.clone(); + move |common, data, _app, state| { + let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let CallbackMetadata::MousePosition(position) = data.metadata else { + panic!("CallbackMetadata should contain MousePosition!"); + }; + + handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos, position.device); + Ok(EventResult::Pass) + } + }), + ); panel.add_event_listener( widget_id, EventListenerKind::MouseRelease, Box::new({ let k = key_state.clone(); + let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { - if handle_release(app, &k, state) { + if handle_release(app, &k, &k_cap_type, state) { on_release_anim(k.clone(), common, data); } + Ok(EventResult::Pass) } }), @@ -307,6 +476,38 @@ pub(super) fn create_keyboard_panel( elems_changed = true; } } + if !app.session.config.keyboard_swipe_to_type_enabled { + panel.state.swipe_typing_manager = None; + panel.state.swipe_candidate_receiver = None; + + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if !predictions_root.is_null() { + panel.layout.remove_children(predictions_root); + + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::None), + )); + + } + } + if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { + init_swipe_type_manager(&mut panel.state); + + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + + if !predictions_root.is_null() { + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::Flex), + )); + } + } } OverlayEventData::CustomCommand { element, command } => { diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 2af0590f..84c489a8 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -2,7 +2,6 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use regex::Regex; use serde::{Deserialize, Serialize}; - use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ @@ -230,7 +229,7 @@ pub(super) struct KeyData { pub(super) cap_type: KeyCapType, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum KeyCapType { /// Label an SVG Special, diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index 966f5baa..ded5cd33 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -4,7 +4,7 @@ use std::{ process::{Child, Command}, sync::atomic::Ordering, }; - +use std::sync::mpsc::Receiver; use crate::{ KEYMAP_CHANGE, backend::{ @@ -27,21 +27,29 @@ use crate::{ }, }; use anyhow::Context; -use glam::{Affine3A, Quat, Vec3, vec3}; +use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; use regex::Regex; -use slotmap::{SlotMap, new_key_type}; +use slotmap::{SlotMap, new_key_type, Key}; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, }; +use wgui::event::StyleSetRequest; +use wgui::layout::{LayoutTask}; +use wgui::parser::Fetchable; +use wgui::taffy::Display; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue}, }; +use crate::overlays::keyboard::builder::update_swipe_prediction_bar; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; +mod swipe_type; pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META]; @@ -56,6 +64,8 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { + state.swipe_typing_manager = Some(engine); + state.swipe_candidate_receiver = Some(receiver); + }, + Err(e) => { + log::error!("Error occurred while trying to load swipe engine: {}", e); + } + }; +} +pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { + let predictions_root = panel.parser_state + .get_widget_id("swipe_predictions_root") + .unwrap_or_default(); + if !predictions_root.is_null() { + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(Display::None), + )); + } +} const fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -150,8 +181,19 @@ impl KeyboardBackend { keymap: Option<&XkbKeymap>, app: &mut AppState, ) -> anyhow::Result { - let panel = - create_keyboard_panel(app, keymap, self.default_state.take(), &self.wlx_layout)?; + let mut state = self.default_state.take(); + + if app.session.config.keyboard_swipe_to_type_enabled { + init_swipe_type_manager(&mut state); + } + + log::info!("swipe engine created"); + let mut panel = + create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; + + if !app.session.config.keyboard_swipe_to_type_enabled { + hide_swipe_type_manager(&mut panel); + } let id = self.layout_panels.insert(panel); if let Some(layout_name) = keymap.and_then(|k| k.get_name()) { @@ -162,6 +204,7 @@ impl KeyboardBackend { Ok(id) } + fn switch_keymap(&mut self, keymap: &XkbKeymap, app: &mut AppState) -> anyhow::Result { if !self.wlx_layout.auto_labels.unwrap_or(true) { return Ok(false); @@ -176,30 +219,41 @@ impl KeyboardBackend { if self.active_layout.eq(new_key) { return Ok(false); } - self.internal_switch_keymap(*new_key); + self.internal_switch_keymap(*new_key, app); } else { let new_key = self.add_new_keymap(Some(keymap), app)?; - self.internal_switch_keymap(new_key); + self.internal_switch_keymap(new_key, app); } app.tasks .enqueue(TaskType::Overlay(OverlayTask::KeyboardChanged)); Ok(true) } - fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey) { - let state_from = self + fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey, app: &AppState) { + let mut state_from = self .layout_panels .get_mut(self.active_layout) .unwrap() .state .take(); + if app.session.config.keyboard_swipe_to_type_enabled { + init_swipe_type_manager(&mut state_from); + } + self.active_layout = new_key; self.layout_panels .get_mut(self.active_layout) .unwrap() .state = state_from; + + if !app.session.config.keyboard_swipe_to_type_enabled { + hide_swipe_type_manager(self.layout_panels + .get_mut(self.active_layout) + .unwrap() + ) + } } fn get_effective_keymap(&mut self) -> anyhow::Result { @@ -233,6 +287,13 @@ impl KeyboardBackend { } } + fn update_swipe_prediction_bar(&mut self, app: &mut AppState) -> anyhow::Result<()> { + if update_swipe_prediction_bar(self.panel(), app)? { + self.panel().process_custom_elems(app); + } + Ok(()) + } + fn auto_switch_keymap(&mut self, app: &mut AppState) -> anyhow::Result { let keymap = self.get_effective_keymap()?; app.hid_provider @@ -266,6 +327,7 @@ impl OverlayBackend for KeyboardBackend { }); } } + self.update_swipe_prediction_bar(app)?; self.panel().should_render(app) } fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> { @@ -327,6 +389,8 @@ struct KeyboardState { overlay_list: OverlayList, set_list: SetList, clock_12h: bool, + swipe_typing_manager: Option, + swipe_candidate_receiver: Option>>> } macro_rules! take_and_leave_default { @@ -346,6 +410,8 @@ impl KeyboardState { overlay_list: OverlayList::default(), set_list: SetList::default(), clock_12h: self.clock_12h, + swipe_typing_manager: None, + swipe_candidate_receiver: None, } } } @@ -386,26 +452,70 @@ enum KeyButtonData { }, } +fn handle_mouse_motion( + key: &KeyState, + key_label: &Vec, + key_cap_type: &KeyCapType, + keyboard: &mut KeyboardState, + within_key_pos: &Option, + device: usize +) { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { + if !swipe_manager.is_current_swipe_empty() { + match &key.button_state { + KeyButtonData::Key { vk, pressed } => { + if let Some(pos) = within_key_pos { + // check because mouse motion can trigger despite hover being false + if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { + + if let Some(label) = key_label.first() { + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + } + } + } + } + _ => {} + } + } + } + +} fn handle_press( app: &mut AppState, key: &KeyState, + key_label: &Vec, + key_cap_type: &KeyCapType, + within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, + device: usize ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - keyboard.modifiers |= match button.index { - MouseButtonIndex::Right => SHIFT, - MouseButtonIndex::Middle => keyboard.alt_modifier, - _ => 0, - }; - - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, true); - pressed.set(true); - play_key_click(app); + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { + if let Some(pos) = within_key_pos { + if let Some(label) = key_label.first() { + swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + } + } + } + else { + keyboard.modifiers |= match button.index { + MouseButtonIndex::Right => SHIFT, + MouseButtonIndex::Middle => keyboard.alt_modifier, + _ => 0, + }; + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, true); + pressed.set(true); + play_key_click(app); + } } KeyButtonData::Modifier { modifier, sticky } => { sticky.set(keyboard.modifiers & *modifier == 0); @@ -435,20 +545,47 @@ fn handle_press( } } -fn handle_release(app: &mut AppState, key: &KeyState, keyboard: &mut KeyboardState) -> bool { +fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - pressed.set(false); + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + && matches!(*k_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + { + if swipe_manager.did_swipe_leave_first_key() { + match swipe_manager.predict() { + Ok(()) => {}, + Err(e) => { + log::error!("{}", e) + } + } + } + else { // pointer must have been released on the same key it was pressed on + swipe_manager.reset(); // drop swipe tracking that was started on press - for m in &AUTO_RELEASE_MODS { - if keyboard.modifiers & *m != 0 { - keyboard.modifiers &= !*m; + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, true); + pressed.set(true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + play_key_click(app); } } - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + else { + if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { + swipe_manager.reset(); + } + pressed.set(false); + + for m in &AUTO_RELEASE_MODS { + if keyboard.modifiers & *m != 0 { + keyboard.modifiers &= !*m; + } + } + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + } true } KeyButtonData::Modifier { modifier, sticky } => { diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs new file mode 100644 index 00000000..428303dc --- /dev/null +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -0,0 +1,238 @@ +use crate::state::AppState; +use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; +use anyhow::{bail}; +use arboard::Clipboard; +use glam::Vec2; +use std::mem; +use std::sync::mpsc::{sync_channel, Receiver, SyncSender, channel, Sender}; +use std::thread::{self, JoinHandle}; +use std::time::Instant; +use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; +use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; +use super_swipe_type::{SwipePoint}; +use crate::subsystem::input::KeyboardFocus; + +const PREDICTION_SUGGESTION_COUNT: usize = 5; + +enum PredictionTask { + Predict { + swipe: Vec, + last_word: Option, + }, + Shutdown, +} + +pub struct SwipeTypingManager { + keyboard_gird: QwertyKeyboardGrid, + current_swipe: Vec, + swipe_candidate_sender: SyncSender>>, + prediction_task_sender: Sender, + worker_thread: Option>, + swipe_start_time: Option, + clipboard: Clipboard, + swipe_left_first_key: bool, + first_swipe_char: char, + current_swipe_device: Option, + last_swiped_word: Option, +} + +impl SwipeTypingManager { + pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + Self::undo(app, original_keyboard_mods); + self.select_word(word, app, original_keyboard_mods); + } + + pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + self.last_swiped_word = Some(word.clone()); + let text_to_paste = format!("{word} "); + + match app.hid_provider.keyboard_focus { + KeyboardFocus::PhysicalScreen => { + if let Ok(_) = self.clipboard.set_text(text_to_paste) { + Self::paste(app, original_keyboard_mods); + } + }, + KeyboardFocus::WayVR => { + if let Some(wvr_server) = app.wvr_server.as_mut() { + wvr_server.set_clipboard_text(text_to_paste); + Self::paste(app, original_keyboard_mods); + } + }, + } + + } + + fn undo(app: &mut AppState, original_keyboard_mods: KeyModifier) { + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); + } + + fn paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, true); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); + } + pub fn new() -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { + let (candidate_sender, candidate_receiver) = sync_channel(1); + let (task_sender, task_receiver) = channel::(); + + // Spawn persistent worker thread + let worker_candidate_sender = candidate_sender.clone(); + let worker_thread = thread::spawn(move || { + let mut swipe_engine = match SwipeOrchestrator::new() { + Ok(engine) => engine, + Err(e) => { + log::error!("Failed to initialize SwipeOrchestrator: {}", e); + return; + } + }; + + while let Ok(task) = task_receiver.recv() { + match task { + PredictionTask::Predict { swipe, last_word } => { + match swipe_engine.predict(swipe, &last_word) { + Ok(candidates) => { + let words: Vec = candidates + .into_iter() + .take(PREDICTION_SUGGESTION_COUNT) + .map(|c| c.word) + .collect(); + + let _ = worker_candidate_sender.send(Some(words)); + } + Err(e) => { + log::error!("Prediction failed: {}", e); + } + } + } + PredictionTask::Shutdown => break, + } + } + }); + + Ok(( + Self { + keyboard_gird: QwertyKeyboardGrid::new(), + current_swipe: Vec::new(), + swipe_candidate_sender: candidate_sender, + prediction_task_sender: task_sender, + worker_thread: Some(worker_thread), + swipe_start_time: None, + clipboard: Clipboard::new()?, + swipe_left_first_key: false, + first_swipe_char: char::default(), + current_swipe_device: None, + last_swiped_word: None, + }, + candidate_receiver, + )) + } + + pub fn predict(&mut self) -> anyhow::Result<()> { + if self.is_current_swipe_empty() { + bail!("nothing to predict"); + } + + let current_swipe = mem::take(&mut self.current_swipe); + let last_word = self.last_swiped_word.clone(); + self.reset_swipe(); + + self.prediction_task_sender + .send(PredictionTask::Predict { + swipe: current_swipe, + last_word, + })?; + + Ok(()) + } + + pub fn reset(&mut self) { + self.reset_swipe(); + let _ = self.swipe_candidate_sender.send(None); + self.last_swiped_word = None; + } + + fn reset_swipe(&mut self) { + self.swipe_start_time = None; + self.current_swipe = Vec::new(); + self.first_swipe_char = char::default(); + self.swipe_left_first_key = false; + self.current_swipe_device = None; + } + + fn start_swipe(&mut self, key_label: char, device: usize) -> Instant { + let now = Instant::now(); + self.swipe_start_time = Some(now); + self.first_swipe_char = key_label.to_ascii_lowercase(); + self.current_swipe_device = Some(device); + now + } + + pub fn did_swipe_leave_first_key(&self) -> bool { + self.swipe_left_first_key + } + + pub fn is_current_swipe_empty(&self) -> bool { + self.current_swipe.is_empty() + } + + pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { + if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { + if let Some(current_device) = self.current_swipe_device { + if current_device != device { + return; + } + } + + if self.first_swipe_char != char::default() + && self.first_swipe_char != key_label.to_ascii_lowercase() + { + self.swipe_left_first_key = true; + } + + let key_pos = Vec2 { + x: pos.x as f32, + y: pos.y as f32, + }; + + let start_time = match self.swipe_start_time { + Some(time) => time, + None => self.start_swipe(key_label, device), + }; + + let within_key_pos_from_center = Vec2 { + x: within_key_pos_normalized.x - 0.5, + y: within_key_pos_normalized.y - 0.5, + }; + let key_dimensions = Vec2 { + x: QwertyKeyboardGrid::get_key_width() as f32, + y: QwertyKeyboardGrid::get_key_height() as f32, + }; + + let point = within_key_pos_from_center * key_dimensions + key_pos; + let duration = Instant::now().duration_since(start_time).mul_f32(0.8); // multiply by .8 because library is trained on mobile swipes which happen on a smaller keyboard and are faster + self.current_swipe + .push(SwipePoint::new(point.x.into(), point.y.into(), duration)) + } + } +} + +impl Drop for SwipeTypingManager { + fn drop(&mut self) { + let _ = self.prediction_task_sender.send(PredictionTask::Shutdown); + if let Some(handle) = self.worker_thread.take() { + let _ = handle.join(); + } + } +} diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index ed446d12..cba95ac6 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -253,6 +253,7 @@ impl HidProvider for UInputProvider { log::error!("send_key: {res}"); } } + fn set_desktop_extent(&mut self, extent: Vec2) { self.desktop_extent = extent; } @@ -337,6 +338,7 @@ pub const META: KeyModifier = 0x80; #[allow(non_camel_case_types)] #[repr(u16)] #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)] +#[derive(Hash)] pub enum VirtualKey { Escape = 9, N1, // number row diff --git a/wgui/src/event.rs b/wgui/src/event.rs index e3d93dac..83e2c921 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -215,6 +215,10 @@ impl CallbackMetadata { let mouse_pos_abs = self.get_mouse_pos_absolute()?; Some(mouse_pos_abs - transform_stack.get().abs_pos) } + pub fn get_mouse_pos_normalized(&self, transform_stack: &TransformStack) -> Option { + let pos_relative = self.get_mouse_pos_relative(transform_stack)?; + Some(pos_relative/transform_stack.parent().raw_dim) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 8ab23297..1878df81 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -327,4 +327,7 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, + + #[serde(default = "def_false")] + pub keyboard_swipe_to_type_enabled: bool, } From bec00c6f48b4fa01d1f5c08ed0976acb39739f63 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:47:36 +0900 Subject: [PATCH 002/307] fmt --- wayvr/src/backend/wayvr/client.rs | 13 +-- wayvr/src/backend/wayvr/mod.rs | 8 +- wayvr/src/overlays/keyboard/builder.rs | 99 +++++++++++++---------- wayvr/src/overlays/keyboard/layout.rs | 4 +- wayvr/src/overlays/keyboard/mod.rs | 85 ++++++++++--------- wayvr/src/overlays/keyboard/swipe_type.rs | 44 ++++++---- wayvr/src/subsystem/hid/mod.rs | 3 +- wgui/src/event.rs | 2 +- wlx-common/src/config.rs | 7 +- 9 files changed, 152 insertions(+), 113 deletions(-) diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 662eee26..73a2f944 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -1,23 +1,23 @@ use std::{io::Read, os::unix::net::UnixStream, path::PathBuf, sync::Arc}; use anyhow::Context; +use smithay::input::Seat; +use smithay::wayland::selection::data_device::set_data_device_selection; use smithay::{ backend::input::Keycode, input::{keyboard::KeyboardHandle, pointer::PointerHandle}, reexports::wayland_server, utils::SerialCounter, }; -use smithay::input::Seat; -use smithay::wayland::selection::data_device::set_data_device_selection; use xkbcommon::xkb; -use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; -use crate::backend::wayvr::comp::Application; use super::{ ProcessWayVREnv, comp::{self, ClientState}, process, }; +use crate::backend::wayvr::comp::Application; +use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; pub struct WayVRClient { pub client: wayland_server::Client, @@ -209,7 +209,10 @@ impl WayVRCompositor { set_data_device_selection::( &self.state.display_handle, &self.seat, - vec!["text/plain;charset=utf-8".to_string(), "text/plain".to_string()], + vec![ + "text/plain;charset=utf-8".to_string(), + "text/plain".to_string(), + ], text.as_bytes().into(), ); } diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 119248bf..74674949 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -241,7 +241,13 @@ impl WvrServerState { }; Ok(Self { - manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer, seat)?, + manager: client::WayVRCompositor::new( + state, + display, + seat_keyboard, + seat_pointer, + seat, + )?, processes: ProcessVec::new(), wm: window::WindowManager::new(), ticks: 0, diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index c1b957f5..ec72e09c 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,4 +1,8 @@ -use std::{collections::HashMap, rc::Rc, time::Duration}; +use super::{ + KeyButtonData, KeyState, KeyboardState, handle_mouse_motion, handle_press, handle_release, + init_swipe_type_manager, + layout::{self, KeyCapType}, +}; use crate::{ app_misc, gui::{ @@ -10,9 +14,13 @@ use crate::{ subsystem::hid::XkbKeymap, windowing::backend::OverlayEventData, }; -use anyhow::{bail, Context}; +use anyhow::{Context, bail}; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; use slotmap::Key; +use std::{collections::HashMap, rc::Rc, time::Duration}; +use wgui::event::StyleSetRequest; +use wgui::layout::LayoutTask; +use wgui::taffy::Display; use wgui::{ animation::{Animation, AnimationEasing}, assets::AssetPath, @@ -25,10 +33,6 @@ use wgui::{ taffy::{self, prelude::length}, widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; -use wgui::event::StyleSetRequest; -use wgui::layout::LayoutTask; -use wgui::taffy::Display; -use super::{KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, layout::{self, KeyCapType}, handle_mouse_motion, init_swipe_type_manager}; const PIXELS_PER_UNIT: f32 = 60.; @@ -42,7 +46,7 @@ fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'s pub(super) fn update_swipe_prediction_bar( panel: &mut GuiPanel, - app: &mut AppState + app: &mut AppState, ) -> anyhow::Result { let mut elements_changed = false; @@ -52,21 +56,22 @@ pub(super) fn update_swipe_prediction_bar( }; if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() - && let Ok(candidates) = recv.try_recv() { - - let predictions_root = panel.parser_state + && let Ok(candidates) = recv.try_recv() + { + let predictions_root = panel + .parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); if predictions_root.is_null() { - return Ok(elements_changed) + return Ok(elements_changed); } let doc_params = new_doc_params(panel); panel.layout.remove_children(predictions_root); let Some(new_suggestions) = candidates else { - return Ok(elements_changed) + return Ok(elements_changed); }; let mut iter = new_suggestions.iter(); @@ -87,7 +92,7 @@ pub(super) fn update_swipe_prediction_bar( "KeyPrediction", &mut panel.layout, predictions_root, - params + params, )?; if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { @@ -126,7 +131,7 @@ pub(super) fn update_swipe_prediction_bar( } Ok(EventResult::Pass) } - }) + }), ); panel.add_event_listener( widget_id, @@ -134,35 +139,21 @@ pub(super) fn update_swipe_prediction_bar( Box::new({ let k = key_state.clone(); move |common, data, _app, _state| { - on_enter_anim( - k.clone(), - common, - data, - accent_color, - anim_mult, - 0.0, - ); + on_enter_anim(k.clone(), common, data, accent_color, anim_mult, 0.0); Ok(EventResult::Pass) } - }) + }), ); panel.add_event_listener( widget_id, EventListenerKind::MouseLeave, Box::new({ let k = key_state.clone(); - move |common, data, _app, _state | { - on_leave_anim( - k.clone(), - common, - data, - accent_color, - anim_mult, - 0.0, - ); + move |common, data, _app, _state| { + on_leave_anim(k.clone(), common, data, accent_color, anim_mult, 0.0); Ok(EventResult::Pass) } - }) + }), ); panel.add_event_listener( widget_id, @@ -173,7 +164,7 @@ pub(super) fn update_swipe_prediction_bar( on_release_anim(k.clone(), common, data); Ok(EventResult::Pass) } - }) + }), ); } } @@ -330,7 +321,6 @@ pub(super) fn create_keyboard_panel( width_mul, ); - Ok(EventResult::Pass) } }), @@ -366,9 +356,20 @@ pub(super) fn create_keyboard_panel( let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; - let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let within_key_pos = data + .metadata + .get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press(app, &k, &k_label, &k_cap_type, &within_key_pos, state, button, button.device); + handle_press( + app, + &k, + &k_label, + &k_cap_type, + &within_key_pos, + state, + button, + button.device, + ); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } @@ -382,12 +383,21 @@ pub(super) fn create_keyboard_panel( let k_label = key_label.clone(); let k_cap_type = key_cap_type.clone(); move |common, data, _app, state| { - let within_key_pos = data.metadata.get_mouse_pos_normalized(&common.alterables.transform_stack); + let within_key_pos = data + .metadata + .get_mouse_pos_normalized(&common.alterables.transform_stack); let CallbackMetadata::MousePosition(position) = data.metadata else { panic!("CallbackMetadata should contain MousePosition!"); }; - handle_mouse_motion(&k, &k_label, &k_cap_type, state, &within_key_pos, position.device); + handle_mouse_motion( + &k, + &k_label, + &k_cap_type, + state, + &within_key_pos, + position.device, + ); Ok(EventResult::Pass) } }), @@ -480,7 +490,8 @@ pub(super) fn create_keyboard_panel( panel.state.swipe_typing_manager = None; panel.state.swipe_candidate_receiver = None; - let predictions_root = panel.parser_state + let predictions_root = panel + .parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); @@ -491,13 +502,15 @@ pub(super) fn create_keyboard_panel( predictions_root, StyleSetRequest::Display(Display::None), )); - } } - if app.session.config.keyboard_swipe_to_type_enabled && panel.state.swipe_typing_manager.is_none() { + if app.session.config.keyboard_swipe_to_type_enabled + && panel.state.swipe_typing_manager.is_none() + { init_swipe_type_manager(&mut panel.state); - let predictions_root = panel.parser_state + let predictions_root = panel + .parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 84c489a8..87cfa8c1 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -1,13 +1,13 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; -use regex::Regex; -use serde::{Deserialize, Serialize}; use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ KEYS_TO_MODS, KeyType, META, NUM_LOCK, SHIFT, VirtualKey, XkbKeymap, get_key_type, }, }; +use regex::Regex; +use serde::{Deserialize, Serialize}; use super::KeyButtonData; diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index ded5cd33..dc432395 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -1,10 +1,6 @@ -use std::{ - cell::Cell, - collections::HashMap, - process::{Child, Command}, - sync::atomic::Ordering, -}; -use std::sync::mpsc::Receiver; +use crate::overlays::keyboard::builder::update_swipe_prediction_bar; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::overlays::keyboard::swipe_type::SwipeTypingManager; use crate::{ KEYMAP_CHANGE, backend::{ @@ -27,25 +23,29 @@ use crate::{ }, }; use anyhow::Context; -use glam::{Affine3A, Quat, Vec3, vec3, Vec2}; +use glam::{Affine3A, Quat, Vec2, Vec3, vec3}; use regex::Regex; -use slotmap::{SlotMap, new_key_type, Key}; +use slotmap::{Key, SlotMap, new_key_type}; +use std::sync::mpsc::Receiver; +use std::{ + cell::Cell, + collections::HashMap, + process::{Child, Command}, + sync::atomic::Ordering, +}; +use wgui::event::StyleSetRequest; +use wgui::layout::LayoutTask; +use wgui::parser::Fetchable; +use wgui::taffy::Display; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, }; -use wgui::event::StyleSetRequest; -use wgui::layout::{LayoutTask}; -use wgui::parser::Fetchable; -use wgui::taffy::Display; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, overlays::{BackendAttrib, BackendAttribValue}, }; -use crate::overlays::keyboard::builder::update_swipe_prediction_bar; -use crate::overlays::keyboard::layout::KeyCapType; -use crate::overlays::keyboard::swipe_type::SwipeTypingManager; pub mod builder; mod layout; @@ -133,14 +133,15 @@ pub(self) fn init_swipe_type_manager(state: &mut KeyboardState) { Ok((engine, receiver)) => { state.swipe_typing_manager = Some(engine); state.swipe_candidate_receiver = Some(receiver); - }, + } Err(e) => { log::error!("Error occurred while trying to load swipe engine: {}", e); } }; } pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { - let predictions_root = panel.parser_state + let predictions_root = panel + .parser_state .get_widget_id("swipe_predictions_root") .unwrap_or_default(); if !predictions_root.is_null() { @@ -188,8 +189,7 @@ impl KeyboardBackend { } log::info!("swipe engine created"); - let mut panel = - create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; + let mut panel = create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; if !app.session.config.keyboard_swipe_to_type_enabled { hide_swipe_type_manager(&mut panel); @@ -204,7 +204,6 @@ impl KeyboardBackend { Ok(id) } - fn switch_keymap(&mut self, keymap: &XkbKeymap, app: &mut AppState) -> anyhow::Result { if !self.wlx_layout.auto_labels.unwrap_or(true) { return Ok(false); @@ -249,10 +248,7 @@ impl KeyboardBackend { .state = state_from; if !app.session.config.keyboard_swipe_to_type_enabled { - hide_swipe_type_manager(self.layout_panels - .get_mut(self.active_layout) - .unwrap() - ) + hide_swipe_type_manager(self.layout_panels.get_mut(self.active_layout).unwrap()) } } @@ -390,7 +386,7 @@ struct KeyboardState { set_list: SetList, clock_12h: bool, swipe_typing_manager: Option, - swipe_candidate_receiver: Option>>> + swipe_candidate_receiver: Option>>>, } macro_rules! take_and_leave_default { @@ -458,7 +454,7 @@ fn handle_mouse_motion( key_cap_type: &KeyCapType, keyboard: &mut KeyboardState, within_key_pos: &Option, - device: usize + device: usize, ) { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) @@ -469,9 +465,12 @@ fn handle_mouse_motion( if let Some(pos) = within_key_pos { // check because mouse motion can trigger despite hover being false if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { - if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + swipe_manager.add_swipe( + pos, + label.chars().next().unwrap_or_default(), + device, + ); } } } @@ -480,7 +479,6 @@ fn handle_mouse_motion( } } } - } fn handle_press( app: &mut AppState, @@ -490,7 +488,7 @@ fn handle_press( within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, - device: usize + device: usize, ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { @@ -499,11 +497,14 @@ fn handle_press( { if let Some(pos) = within_key_pos { if let Some(label) = key_label.first() { - swipe_manager.add_swipe(pos, label.chars().next().unwrap_or_default(), device); + swipe_manager.add_swipe( + pos, + label.chars().next().unwrap_or_default(), + device, + ); } } - } - else { + } else { keyboard.modifiers |= match button.index { MouseButtonIndex::Right => SHIFT, MouseButtonIndex::Middle => keyboard.alt_modifier, @@ -545,7 +546,12 @@ fn handle_press( } } -fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, keyboard: &mut KeyboardState) -> bool { +fn handle_release( + app: &mut AppState, + key: &KeyState, + k_cap_type: &KeyCapType, + keyboard: &mut KeyboardState, +) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() @@ -553,13 +559,13 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k { if swipe_manager.did_swipe_leave_first_key() { match swipe_manager.predict() { - Ok(()) => {}, + Ok(()) => {} Err(e) => { log::error!("{}", e) } } - } - else { // pointer must have been released on the same key it was pressed on + } else { + // pointer must have been released on the same key it was pressed on swipe_manager.reset(); // drop swipe tracking that was started on press app.hid_provider @@ -569,8 +575,7 @@ fn handle_release(app: &mut AppState, key: &KeyState, k_cap_type: &KeyCapType, k .send_key_routed(app.wvr_server.as_mut(), *vk, false); play_key_click(app); } - } - else { + } else { if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { swipe_manager.reset(); } diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs index 428303dc..d4e98e75 100644 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -1,16 +1,16 @@ use crate::state::AppState; -use crate::subsystem::hid::{KeyModifier, VirtualKey, CTRL}; -use anyhow::{bail}; +use crate::subsystem::hid::{CTRL, KeyModifier, VirtualKey}; +use crate::subsystem::input::KeyboardFocus; +use anyhow::bail; use arboard::Clipboard; use glam::Vec2; use std::mem; -use std::sync::mpsc::{sync_channel, Receiver, SyncSender, channel, Sender}; +use std::sync::mpsc::{Receiver, Sender, SyncSender, channel, sync_channel}; use std::thread::{self, JoinHandle}; use std::time::Instant; +use super_swipe_type::SwipePoint; use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; -use super_swipe_type::{SwipePoint}; -use crate::subsystem::input::KeyboardFocus; const PREDICTION_SUGGESTION_COUNT: usize = 5; @@ -37,12 +37,22 @@ pub struct SwipeTypingManager { } impl SwipeTypingManager { - pub fn select_alternate_prediction(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + pub fn select_alternate_prediction( + &mut self, + word: &String, + app: &mut AppState, + original_keyboard_mods: KeyModifier, + ) { Self::undo(app, original_keyboard_mods); self.select_word(word, app, original_keyboard_mods); } - pub fn select_word(&mut self, word: &String, app: &mut AppState, original_keyboard_mods: KeyModifier) { + pub fn select_word( + &mut self, + word: &String, + app: &mut AppState, + original_keyboard_mods: KeyModifier, + ) { self.last_swiped_word = Some(word.clone()); let text_to_paste = format!("{word} "); @@ -51,15 +61,14 @@ impl SwipeTypingManager { if let Ok(_) = self.clipboard.set_text(text_to_paste) { Self::paste(app, original_keyboard_mods); } - }, + } KeyboardFocus::WayVR => { if let Some(wvr_server) = app.wvr_server.as_mut() { wvr_server.set_clipboard_text(text_to_paste); Self::paste(app, original_keyboard_mods); } - }, + } } - } fn undo(app: &mut AppState, original_keyboard_mods: KeyModifier) { @@ -148,11 +157,10 @@ impl SwipeTypingManager { let last_word = self.last_swiped_word.clone(); self.reset_swipe(); - self.prediction_task_sender - .send(PredictionTask::Predict { - swipe: current_swipe, - last_word, - })?; + self.prediction_task_sender.send(PredictionTask::Predict { + swipe: current_swipe, + last_word, + })?; Ok(()) } @@ -188,7 +196,11 @@ impl SwipeTypingManager { } pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { - if let Some(pos) = self.keyboard_gird.key_positions.get(&key_label.to_ascii_lowercase()) { + if let Some(pos) = self + .keyboard_gird + .key_positions + .get(&key_label.to_ascii_lowercase()) + { if let Some(current_device) = self.current_swipe_device { if current_device != device { return; diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index cba95ac6..b48f8ee7 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -337,8 +337,7 @@ pub const META: KeyModifier = 0x80; #[allow(non_camel_case_types)] #[repr(u16)] -#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)] -#[derive(Hash)] +#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter, Hash)] pub enum VirtualKey { Escape = 9, N1, // number row diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 83e2c921..1d4c3d5e 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -217,7 +217,7 @@ impl CallbackMetadata { } pub fn get_mouse_pos_normalized(&self, transform_stack: &TransformStack) -> Option { let pos_relative = self.get_mouse_pos_relative(transform_stack)?; - Some(pos_relative/transform_stack.parent().raw_dim) + Some(pos_relative / transform_stack.parent().raw_dim) } } diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 1878df81..7fc43dcb 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -7,7 +7,10 @@ use strum::{AsRefStr, EnumProperty, EnumString, VariantArray}; use wayvr_ipc::packet_client::WvrProcessLaunchParams; use crate::{ - astr_containers::{AStrMap, AStrSet}, locale::{self}, overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic}, windowing::OverlayWindowState + astr_containers::{AStrMap, AStrSet}, + locale::{self}, + overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic}, + windowing::OverlayWindowState, }; pub type PwTokenMap = AStrMap; @@ -138,8 +141,6 @@ const fn def_max_height() -> u16 { 1440 } - - #[derive(Deserialize, Serialize)] pub struct GeneralConfig { #[serde(default = "def_theme_path")] From 5eb258e3103ad9f499f1aa2cb10c32c98ef42e51 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:01:39 +0900 Subject: [PATCH 003/307] update libmonado-rs & oxr chroma key --- Cargo.lock | 2 +- wayvr/Cargo.toml | 2 +- wayvr/src/backend/openxr/helpers.rs | 20 ++++++++++++++++ wayvr/src/backend/openxr/mod.rs | 5 +++- wgui/src/drawing.rs | 36 +++++++++++++++++++++++++++++ wlx-common/src/config.rs | 11 +++++++++ 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 847a98bf..89da8af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3176,7 +3176,7 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmonado" version = "1.6.0" -source = "git+https://github.com/oo8dev/libmonado-rs.git?rev=fc39940a64dea2df080a0d2c974c7d651006241f#fc39940a64dea2df080a0d2c974c7d651006241f" +source = "git+https://github.com/wayvr-org/libmonado-rs.git?rev=6f66b26930c24a8a2fc57ddcd85704784894c750#6f66b26930c24a8a2fc57ddcd85704784894c750" dependencies = [ "dlopen2", "flagset", diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index eb7e052a..c7ff289e 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -57,7 +57,7 @@ interprocess = { version = "2.2.3" } json = { version = "0.12.4", optional = true } json5 = "1.3.0" libc = "0.2.178" -libmonado = { git = "https://github.com/oo8dev/libmonado-rs.git", rev = "fc39940a64dea2df080a0d2c974c7d651006241f", optional = true } +libmonado = { git = "https://github.com/wayvr-org/libmonado-rs.git", rev = "6f66b26930c24a8a2fc57ddcd85704784894c750", optional = true } log-panics = { version = "2.1.0", features = ["with-backtrace"] } mint = "0.5.9" openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true } diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index 936ab5a3..56ed80ca 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -15,6 +15,8 @@ macro_rules! next_chain_insert { } pub(crate) use next_chain_insert; +use crate::state::AppState; + pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { let entry = xr::Entry::linked(); @@ -209,3 +211,21 @@ pub(super) fn posef_to_transform(pose: &xr::Posef) -> Affine3A { let translation = Vec3M::from(pose.position).into(); Affine3A::from_rotation_translation(rotation, translation) } + +pub(super) fn reconfigure_chroma_key(app: &AppState) { + let Some(monado) = app.monado_state.as_ref() else { + return; + }; + + let params = &app.session.config.chroma_key_params; + + if let Err(e) = monado.ipc.set_chroma_key_params( + params.hsv_min[0]..params.hsv_max[0], + params.hsv_min[1]..params.hsv_max[1], + params.hsv_min[2]..params.hsv_max[2], + params.curve, + params.despill, + ) { + log::warn!("Could not set Chroma Key: {e:?}") + } +} diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 8e7938f7..57a6506b 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -17,7 +17,7 @@ use crate::{ backend::{ BackendError, XrBackend, input::interact, - openxr::{lines::LinePool, overlay::OpenXrOverlayData}, + openxr::{helpers::reconfigure_chroma_key, lines::LinePool, overlay::OpenXrOverlayData}, task::{OpenXrTask, OverlayTask, TaskType}, }, config::{save_settings, save_state}, @@ -94,6 +94,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr .ok() }); + reconfigure_chroma_key(&app); + let mut blocker = app .monado_state .as_ref() @@ -489,6 +491,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr &mut environment_blend_mode, main_session_visible, ); + reconfigure_chroma_key(&app); } } #[cfg(feature = "openvr")] diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 71a66e43..2f1aca81 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -192,6 +192,42 @@ impl Default for Color { } } +pub struct HsvColor { + pub h: f32, + pub s: f32, + pub v: f32, + pub a: f32, +} + +impl HsvColor { + pub const fn new(h: f32, s: f32, v: f32, a: f32) -> Self { + Self { h, s, v, a } + } +} + +impl From for HsvColor { + fn from(value: Color) -> Self { + let max = value.r.max(value.g).max(value.b); + let min = value.r.min(value.g).min(value.b); + let delta = max - min; + + let h = if delta == 0.0 { + 0.0 + } else if max == value.r { + (((value.g - value.b) / delta) / 6.0 + 1.0) % 1.0 + } else if max == value.g { + ((value.b - value.r) / delta + 2.0) / 6.0 + } else { + ((value.r - value.g) / delta + 4.0) / 6.0 + }; + + let s = if max == 0.0 { 0.0 } else { delta / max }; + let v = max; + + Self::new(h, s, v, value.a) + } +} + #[repr(u8)] #[derive(Debug, Default, Clone, Copy)] pub enum GradientMode { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 7fc43dcb..8d5a0e20 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -66,6 +66,14 @@ pub enum HandsfreePointer { EyeTrackingOnly, } +#[derive(Clone, Serialize, Deserialize, Default)] +pub struct ChromaKeyParams { + pub hsv_min: [f32; 3], + pub hsv_max: [f32; 3], + pub curve: f32, + pub despill: f32, +} + #[derive(Clone, Serialize, Deserialize)] pub struct SerializedWindowSet { pub name: Arc, @@ -331,4 +339,7 @@ pub struct GeneralConfig { #[serde(default = "def_false")] pub keyboard_swipe_to_type_enabled: bool, + + #[serde(default)] + pub chroma_key_params: ChromaKeyParams, } From 0c1b0906c1f94e2b3ceb23bdca50337298f76f54 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 11 Apr 2026 00:22:24 +0200 Subject: [PATCH 004/307] wgui: Add `ColorSelector` component --- dash-frontend/src/frontend.rs | 1 - dash-frontend/src/tab/monado.rs | 1 - dash-frontend/src/tab/settings/macros.rs | 2 - dash-frontend/src/views/audio_settings.rs | 6 +- uidev/assets/gui/testbed.xml | 2 +- uidev/assets/gui/various_widgets.xml | 8 +- uidev/src/testbed/testbed_generic.rs | 1 - wayvr/src/overlays/edit/mod.rs | 1 - wgui/assets/wgui/color_selector.xml | 25 ++ wgui/src/components/bar_graph.rs | 6 +- wgui/src/components/button.rs | 20 +- wgui/src/components/color_selector.rs | 299 ++++++++++++++++++++ wgui/src/components/editbox.rs | 8 +- wgui/src/components/mod.rs | 4 +- wgui/src/components/slider.rs | 19 +- wgui/src/drawing.rs | 10 +- wgui/src/layout.rs | 5 +- wgui/src/parser/component_color_selector.rs | 25 ++ wgui/src/parser/mod.rs | 3 + wgui/src/windowing/context_menu.rs | 1 - wgui/src/windowing/window.rs | 9 +- 21 files changed, 418 insertions(+), 38 deletions(-) create mode 100644 wgui/assets/wgui/color_selector.xml create mode 100644 wgui/src/components/color_selector.rs create mode 100644 wgui/src/parser/component_color_selector.rs diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 18f1d579..26a1970c 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -485,7 +485,6 @@ impl Frontend { fn action_show_audio_settings(&mut self) -> anyhow::Result<()> { self.window_audio_settings.open(&mut WguiWindowParams { - globals: &self.globals, position: Vec2::new(64.0, 64.0), layout: &mut self.layout, extra: WguiWindowParamsExtra { diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index 0f714306..abc65c7f 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -267,7 +267,6 @@ impl SubtabGeneralSettings { let tasks = tasks.clone(); Box::new(move |_common, e| { tasks.push(Task::SetBrightness(e.value / 100.0)); - Ok(()) }) }); } diff --git a/dash-frontend/src/tab/settings/macros.rs b/dash-frontend/src/tab/settings/macros.rs index 8c761412..235c053e 100644 --- a/dash-frontend/src/tab/settings/macros.rs +++ b/dash-frontend/src/tab/settings/macros.rs @@ -118,7 +118,6 @@ pub fn options_slider_f32( let tasks = mp.tasks.clone(); move |_common, e| { tasks.push(Task::UpdateFloat(setting, e.value)); - Ok(()) } })); @@ -168,7 +167,6 @@ pub fn options_slider_i32( let tasks = mp.tasks.clone(); move |_common, e| { tasks.push(Task::UpdateInt(setting, e.value as i32)); - Ok(()) } })); Ok(()) diff --git a/dash-frontend/src/views/audio_settings.rs b/dash-frontend/src/views/audio_settings.rs index 61061d33..8284ea50 100644 --- a/dash-frontend/src/views/audio_settings.rs +++ b/dash-frontend/src/views/audio_settings.rs @@ -823,8 +823,10 @@ impl View { slider.on_value_changed({ let control = params.control.clone(); Box::new(move |_common, event| { - control.on_volume_change(event.value * VOLUME_MULT)?; - Ok(()) + if let Err(e) = control.on_volume_change(event.value * VOLUME_MULT) { + log::error!("{:?}", e); + debug_assert!(false); + }; }) }); diff --git a/uidev/assets/gui/testbed.xml b/uidev/assets/gui/testbed.xml index b008f7fe..823d94c1 100644 --- a/uidev/assets/gui/testbed.xml +++ b/uidev/assets/gui/testbed.xml @@ -92,4 +92,4 @@
- \ No newline at end of file + diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml index 14ae8016..163e7391 100644 --- a/uidev/assets/gui/various_widgets.xml +++ b/uidev/assets/gui/various_widgets.xml @@ -59,7 +59,11 @@
-
+
+ +
- \ No newline at end of file + diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 6f8425c6..802863f4 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -229,7 +229,6 @@ impl TestbedGeneric { data: &mut Data, ) -> anyhow::Result<()> { data.popup_window.open(&mut WguiWindowParams { - globals: &self.globals, position: Vec2::new(128.0, 128.0), layout: &mut self.layout, extra: WguiWindowParamsExtra { diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index 04f4fd94..66b19a40 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -674,7 +674,6 @@ fn set_up_slider( OverlaySelector::Id(*overlay_id.borrow()), Box::new(move |app, owc| callback(app, owc, e_value)), ))); - Ok(()) })); Ok(()) diff --git a/wgui/assets/wgui/color_selector.xml b/wgui/assets/wgui/color_selector.xml new file mode 100644 index 00000000..d2b1d9d4 --- /dev/null +++ b/wgui/assets/wgui/color_selector.xml @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
diff --git a/wgui/src/components/bar_graph.rs b/wgui/src/components/bar_graph.rs index 083369a4..37026997 100644 --- a/wgui/src/components/bar_graph.rs +++ b/wgui/src/components/bar_graph.rs @@ -70,7 +70,11 @@ impl ComponentTrait for ComponentBarGraph { fn refresh(&self, data: &mut RefreshData) { let state = self.state.borrow(); - self.update_limits_text(&state, data.common); + + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + self.update_limits_text(&state, &mut lc.common()); + let _ = lc.finish(); } } diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index cb955c7f..e498d736 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -123,15 +123,21 @@ impl ComponentTrait for ComponentButton { fn refresh(&self, data: &mut RefreshData) { let mut state = self.state.borrow_mut(); + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + if state.active_tooltip.is_some() { - if let Some(node_id) = data.common.state.nodes.get(self.base.get_id()) { - if !widget::is_node_visible(&data.common.state.tree, *node_id) { + let common = lc.common(); + if let Some(node_id) = common.state.nodes.get(self.base.get_id()) { + if !widget::is_node_visible(&common.state.tree, *node_id) { state.active_tooltip = None; // destroy the tooltip, this button is now hidden } } else { debug_assert!(false); } } + + let _ = lc.finish(); } } @@ -139,6 +145,10 @@ fn get_color2(color: &drawing::Color, gradient_intensity: f32) -> drawing::Color color.lerp(&Color::new(0.0, 0.0, 0.0, color.a), gradient_intensity) } +fn get_hover_color(color: &drawing::Color) -> drawing::Color { + Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15) +} + impl ComponentButton { pub fn get_label(&self) -> WidgetID { self.data.id_label @@ -165,7 +175,7 @@ impl ComponentButton { let mut state = self.state.borrow_mut(); state.colors.color = color; - + state.colors.hover_color = get_hover_color(&color); rect.params.color = color; rect.params.color2 = get_color2(&color, gradient_intensity); } @@ -435,9 +445,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul .border_color .unwrap_or_else(|| Color::new(color.r, color.g, color.b, color.a + 0.25)); - let hover_color = params - .hover_color - .unwrap_or_else(|| Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15)); + let hover_color = params.hover_color.unwrap_or_else(|| get_hover_color(&color)); let hover_border_color = params .hover_border_color diff --git a/wgui/src/components/color_selector.rs b/wgui/src/components/color_selector.rs new file mode 100644 index 00000000..bbda2afe --- /dev/null +++ b/wgui/src/components/color_selector.rs @@ -0,0 +1,299 @@ +use crate::{ + assets::AssetPath, + components::{ + Component, ComponentBase, ComponentTrait, RefreshData, + button::{self, ComponentButton}, + slider::{ComponentSlider, SliderValueChangedCallback}, + }, + drawing::{self}, + event::CallbackDataCommon, + globals::WguiGlobals, + i18n::Translation, + layout::{Layout, WidgetID, WidgetPair}, + parser::{self, Fetchable, ParseDocumentParams, ParserState}, + widget::{ConstructEssentials, rectangle::WidgetRectangle, util::WLength}, + windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra}, +}; +use glam::Vec2; +use std::{ + cell::RefCell, + rc::{Rc, Weak}, +}; +use taffy::prelude::length; + +pub struct Params { + pub color: drawing::Color, + pub style: taffy::Style, +} + +impl Default for Params { + fn default() -> Self { + Self { + color: drawing::Color::new(1.0, 1.0, 1.0, 1.0), + style: Default::default(), + } + } +} + +struct WantsOpen { + position: Vec2, +} + +#[allow(dead_code)] +struct PopupState { + slider_r: Rc, + slider_g: Rc, + slider_b: Rc, + id_rect_color: WidgetID, +} + +struct State { + color: drawing::Color, + self_ref: Weak, + wants_open: Option, + on_changed: Option, + popup_state: Option, +} + +struct Data { + button: Rc, +} + +pub struct ColorSelectorChangedEvent { + pub color: drawing::Color, +} + +pub type ColorSelectorChangedCallback = Box; + +pub struct ComponentColorSelector { + base: ComponentBase, + data: Rc, + state: Rc>, + window: WguiWindow, +} + +impl ComponentTrait for ComponentColorSelector { + fn base(&self) -> &ComponentBase { + &self.base + } + + fn base_mut(&mut self) -> &mut ComponentBase { + &mut self.base + } + + fn refresh(&self, data: &mut RefreshData) { + let mut state = self.state.borrow_mut(); + + if let Some(wants_open) = state.wants_open.take() { + if let Err(e) = self.open(data.layout, &mut state, wants_open.position) { + log::error!("{:?}", e); + debug_assert!(false); + } + } + + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); + + self.data.button.set_text( + &mut common, + Translation::from_raw_text_string(format!("{}", state.color.to_hex_rgb())), + ); + + self.data.button.set_color(&mut common, state.color); + + let _ = lc.finish(); + } +} + +enum ColorIndex { + Red, + Green, + Blue, +} + +fn set_color_internal(state: &mut State, common: &mut CallbackDataCommon, color: drawing::Color) { + if state.color == color { + return; + } + + if let Some(on_changed) = &state.on_changed { + on_changed(common, ColorSelectorChangedEvent { color }) + } + + state.color = color; + common.alterables.refresh_component_once(&state.self_ref); + common.alterables.mark_redraw(); +} + +impl ComponentColorSelector { + pub fn on_changed(&self, func: ColorSelectorChangedCallback) { + self.state.borrow_mut().on_changed = Some(func); + } + + pub fn set_color(&self, common: &mut CallbackDataCommon, color: drawing::Color) { + let mut state = self.state.borrow_mut(); + set_color_internal(&mut state, common, color); + } + + pub fn get_color(&self) -> drawing::Color { + self.state.borrow().color + } + + fn open(&self, layout: &mut Layout, state: &mut State, position: Vec2) -> anyhow::Result<()> { + self.window.open(&mut WguiWindowParams { + position, + layout, + extra: WguiWindowParamsExtra { + close_if_clicked_outside: true, + // TODO: translation text in wgui too? + title: Some(Translation::from_raw_text("Select color")), + ..Default::default() + }, + })?; + + let id_content = self.window.get_content().id; + + let parser_state = parser::parse_from_assets( + &mut ParseDocumentParams { + globals: layout.state.globals.clone(), + path: AssetPath::WguiInternal("wgui/color_selector.xml"), + extra: Default::default(), + }, + layout, + id_content, + )?; + + let slider_r = parser_state.fetch_component_as::("slider_r")?; + let slider_g = parser_state.fetch_component_as::("slider_g")?; + let slider_b = parser_state.fetch_component_as::("slider_b")?; + + { + let mut lc = layout.start_common(); + let common = &mut lc.common(); + + slider_r.set_value(common, state.color.r * 255.0); + slider_g.set_value(common, state.color.g * 255.0); + slider_b.set_value(common, state.color.b * 255.0); + } + + slider_r.on_value_changed(self.gen_slider_callback(ColorIndex::Red)); + slider_g.on_value_changed(self.gen_slider_callback(ColorIndex::Green)); + slider_b.on_value_changed(self.gen_slider_callback(ColorIndex::Blue)); + + let id_rect_color = parser_state.get_widget_id("rect_color")?; + + state.popup_state = Some(PopupState { + slider_r, + slider_g, + slider_b, + id_rect_color, + }); + Ok(()) + } + + fn gen_slider_callback(&self, idx: ColorIndex) -> SliderValueChangedCallback { + let state = Rc::downgrade(&self.state); + Box::new(move |common, evt| { + let Some(state) = state.upgrade() else { + return; + }; + + let mut state = state.borrow_mut(); + let Some(popup_state) = &state.popup_state else { + return; + }; + + let norm = evt.value / 255.0; + + let mut new_color = state.color; + match idx { + ColorIndex::Red => new_color.r = norm, + ColorIndex::Green => new_color.g = norm, + ColorIndex::Blue => new_color.b = norm, + } + + if let Some(mut rect) = common + .state + .widgets + .get_as::(popup_state.id_rect_color) + { + rect.set_color(common, new_color); + } + set_color_internal(&mut state, common, new_color); + }) + } +} + +const DEFAULT_WIDTH: f32 = 96.0; +const DEFAULT_HEIGHT: f32 = 32.0; + +pub fn construct( + ess: &mut ConstructEssentials, + params: Params, +) -> anyhow::Result<(WidgetPair, Rc)> { + let mut style = params.style; + + if style.size.width.is_auto() { + style.size.width = length(DEFAULT_WIDTH); + } + + if style.size.height.is_auto() { + style.size.height = length(DEFAULT_HEIGHT); + } + + style.min_size = style.size; + style.max_size = style.size; + + let (widget_button, button) = button::construct( + ess, + button::Params { + color: Some(params.color), + round: WLength::Percent(1.0), + border: 2.0, + border_color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), + style, + ..Default::default() + }, + )?; + + let data = Rc::new(Data { button: button.clone() }); + + let state = Rc::new(RefCell::new(State { + color: params.color, + self_ref: Default::default(), + wants_open: None, + popup_state: None, + on_changed: None, + })); + + let base = ComponentBase { + id: widget_button.id, + lhandles: Default::default(), + }; + + let color_selector = Rc::new(ComponentColorSelector { + base, + data, + state: state.clone(), + window: WguiWindow::default(), + }); + + let self_ref = Rc::downgrade(&color_selector); + state.borrow_mut().self_ref = self_ref.clone(); + + button.on_click(Rc::new({ + let color_selector = color_selector.clone(); + move |common, evt| { + let mut state = color_selector.state.borrow_mut(); + state.wants_open = Some(WantsOpen { + position: evt.mouse_pos_absolute.unwrap_or_default(), + }); + common.alterables.refresh_component_once(&self_ref); + Ok(()) + } + })); + + ess.layout.defer_component_refresh(Component(color_selector.clone())); + Ok((widget_button, color_selector)) +} diff --git a/wgui/src/components/editbox.rs b/wgui/src/components/editbox.rs index 7dff8cb5..0b9032bf 100644 --- a/wgui/src/components/editbox.rs +++ b/wgui/src/components/editbox.rs @@ -147,9 +147,15 @@ impl ComponentTrait for ComponentEditBox { } fn refresh(&self, data: &mut RefreshData) { + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); + let mut state = self.state.borrow_mut(); - let res = refresh_all(data.common, &self.data, &mut state); + let res = refresh_all(&mut common, &self.data, &mut state); debug_assert!(res.is_some()); + + let _ = lc.finish(); } fn on_focus_change(&self, data: &mut FocusChangeData) { diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs index b5916f18..d91e7d71 100644 --- a/wgui/src/components/mod.rs +++ b/wgui/src/components/mod.rs @@ -1,6 +1,7 @@ use std::hash::Hash; use std::{hash::Hasher, rc::Rc}; +use crate::layout::Layout; use crate::{ any::AnyTrait, event::{CallbackDataCommon, EventListenerID}, @@ -10,6 +11,7 @@ use crate::{ pub mod bar_graph; pub mod button; pub mod checkbox; +pub mod color_selector; pub mod editbox; pub mod radio_group; pub mod slider; @@ -17,7 +19,7 @@ pub mod tabs; pub mod tooltip; pub struct RefreshData<'a> { - pub common: &'a mut CallbackDataCommon<'a>, + pub layout: &'a mut Layout, } pub struct FocusChangeData<'a> { diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index af94d3ec..36ae9421 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -101,8 +101,7 @@ pub struct SliderValueChangedEvent { pub value: f32, } -pub type SliderValueChangedCallback = - Box anyhow::Result<()>>; +pub type SliderValueChangedCallback = Box; pub struct ComponentSlider { base: ComponentBase, @@ -111,10 +110,14 @@ pub struct ComponentSlider { } impl ComponentTrait for ComponentSlider { - fn refresh(&self, init_data: &mut RefreshData) { + fn refresh(&self, data: &mut RefreshData) { + // FIXME: refactor this after merging feat-skybox-catalog branch + let mut lc = data.layout.start_common(); + let mut common = lc.common(); let mut state = self.state.borrow_mut(); let value = state.values.value; - state.set_value(init_data.common, &self.data, value); + state.set_value(&mut common, &self.data, value); + let _ = lc.finish(); } fn base(&self) -> &ComponentBase { @@ -250,15 +253,13 @@ impl State { Self::update_text(common, &mut label, self.values.value); } - if changed - && let Some(on_value_changed) = &self.on_value_changed - && let Err(e) = on_value_changed( + if changed && let Some(on_value_changed) = &self.on_value_changed { + on_value_changed( common, SliderValueChangedEvent { value: self.values.value, }, - ) { - log::error!("{e:?}"); // FIXME: proper error handling + ) } } } diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 2f1aca81..210c0a62 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -102,7 +102,7 @@ impl Boundary { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Color { pub r: f32, pub g: f32, @@ -179,6 +179,14 @@ impl Color { format!("#{r:02X}{g:02X}{b:02X}{a:02X}") } + #[must_use] + pub fn to_hex_rgb(&self) -> String { + let r = (self.r.clamp(0.0, 1.0) * 255.0).round() as u8; + let g = (self.g.clamp(0.0, 1.0) * 255.0).round() as u8; + let b = (self.b.clamp(0.0, 1.0) * 255.0).round() as u8; + format!("#{r:02X}{g:02X}{b:02X}") + } + #[must_use] pub const fn as_arr(&self) -> [f32; 4] { [self.r, self.b, self.g, self.a] diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 2569f8b7..b3a15aed 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -339,15 +339,14 @@ impl Layout { } fn process_pending_components(&mut self, alterables: &mut EventAlterables) { - for comp in &self.components_to_refresh_once { + for comp in std::mem::take(&mut self.components_to_refresh_once) { let mut common = CallbackDataCommon { state: &self.state, alterables, }; - comp.0.refresh(&mut RefreshData { common: &mut common }); + comp.0.refresh(&mut RefreshData { layout: self }); } - self.components_to_refresh_once.clear(); } fn process_pending_widget_ticks(&mut self, alterables: &mut EventAlterables) { diff --git a/wgui/src/parser/component_color_selector.rs b/wgui/src/parser/component_color_selector.rs new file mode 100644 index 00000000..63fa2110 --- /dev/null +++ b/wgui/src/parser/component_color_selector.rs @@ -0,0 +1,25 @@ +use crate::{ + components::{Component, color_selector}, + drawing, + layout::WidgetID, + parser::{AttribPair, ParserContext, process_component, style::parse_style}, +}; + +pub fn parse_component_color_selector( + ctx: &mut ParserContext, + parent_id: WidgetID, + attribs: &[AttribPair], + tag_name: &str, +) -> anyhow::Result { + let style = parse_style(ctx, attribs, tag_name); + let color = drawing::Color::new(1.0, 1.0, 1.0, 1.0); + + let (widget, component) = color_selector::construct( + &mut ctx.get_construct_essentials(parent_id), + color_selector::Params { color, style }, + )?; + + process_component(ctx, Component(component), widget.id, attribs); + + Ok(widget.id) +} diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index a715b4df..e8bac585 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -1,6 +1,7 @@ mod component_bar_graph; mod component_button; mod component_checkbox; +mod component_color_selector; mod component_editbox; mod component_radio_group; mod component_slider; @@ -25,6 +26,7 @@ use crate::{ component_bar_graph::parse_component_bar_graph, component_button::parse_component_button, component_checkbox::{CheckboxKind, parse_component_checkbox}, + component_color_selector::parse_component_color_selector, component_editbox::parse_component_editbox, component_radio_group::parse_component_radio_group, component_slider::parse_component_slider, @@ -1063,6 +1065,7 @@ fn parse_child<'a>( "Slider" => { new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?); } + "ColorSelector" => new_widget_id = Some(parse_component_color_selector(ctx, parent_id, &attribs, tag_name)?), "CheckBox" => { new_widget_id = Some(parse_component_checkbox( ctx, diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs index 6852604e..e9139015 100644 --- a/wgui/src/windowing/context_menu.rs +++ b/wgui/src/windowing/context_menu.rs @@ -94,7 +94,6 @@ impl ContextMenu { let globals = layout.state.globals.clone(); self.window.open(&mut WguiWindowParams { - globals: &globals, layout, position: params.position, extra: WguiWindowParamsExtra { diff --git a/wgui/src/windowing/window.rs b/wgui/src/windowing/window.rs index 2ab6fc95..82779273 100644 --- a/wgui/src/windowing/window.rs +++ b/wgui/src/windowing/window.rs @@ -81,7 +81,6 @@ impl Default for WguiWindowParamsExtra { pub struct WguiWindowParams<'a> { pub position: Vec2, - pub globals: &'a WguiGlobals, pub layout: &'a mut Layout, pub extra: WguiWindowParamsExtra, } @@ -100,7 +99,7 @@ impl WguiWindow { self.0.borrow_mut().opened_window = None; } - pub fn open(&mut self, params: &mut WguiWindowParams) -> anyhow::Result<()> { + pub fn open(&self, params: &mut WguiWindowParams) -> anyhow::Result<()> { // close previous one if it's already open self.close(); @@ -216,12 +215,14 @@ impl WguiWindow { }, )?; + let globals = params.layout.state.globals.clone(); + let content_id = if params.extra.with_decorations { let xml_path: AssetPath = AssetPath::WguiInternal("wgui/window_frame.xml"); let state = parser::parse_from_assets( &parser::ParseDocumentParams { - globals: params.globals.clone(), + globals: globals.clone(), path: xml_path, extra: Default::default(), }, @@ -240,7 +241,7 @@ impl WguiWindow { if let Some(title) = ¶ms.extra.title { let mut text_title = state.fetch_widget_as::(¶ms.layout.state, "text_window_title")?; - text_title.set_text_simple(&mut params.globals.get(), title.clone()); + text_title.set_text_simple(&mut globals.get(), title.clone()); } let content = state.fetch_widget(¶ms.layout.state, "content")?; From 26f5be36b61b9a32eeed527d525c72b22732dd38 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 11 Apr 2026 13:48:49 +0200 Subject: [PATCH 005/307] dash-frontend: Chroma key settings --- .../gui/tab/monado_tab_general_settings.xml | 58 ++++++++-- dash-frontend/assets/lang/en.json | 1 + dash-frontend/src/tab/monado.rs | 105 +++++++++++++++++- wgui/src/drawing.rs | 26 +++++ wlx-common/src/config.rs | 46 +++++++- 5 files changed, 225 insertions(+), 11 deletions(-) diff --git a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml index d8329b63..3d918b82 100644 --- a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml +++ b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml @@ -1,12 +1,56 @@ - - - + - + + + + + +
+ + + + + + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
-
\ No newline at end of file + diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 96b56062..7d5e2e05 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -40,6 +40,7 @@ "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blocks the game from receiving poses when the keyboard is hovered and 'Block game input' is enabled", "CAPTURE_METHOD": "Wayland screen capture", "CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens", + "COLOR_KEYING": "Color keying", "CLEAR_PIPEWIRE_TOKENS": "Clear PipeWire tokens", "CLEAR_PIPEWIRE_TOKENS_HELP": "Prompt for screen selection on next start", "CLEAR_SAVED_STATE": "Clear saved state", diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index abc65c7f..8efe58de 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -6,7 +6,8 @@ use wgui::{ bar_graph::{ComponentBarGraph, ValueCell}, button::ComponentButton, checkbox::ComponentCheckbox, - slider::ComponentSlider, + color_selector::{ColorSelectorChangedCallback, ComponentColorSelector}, + slider::{ComponentSlider, SliderValueChangedCallback}, tabs::ComponentTabs, }, drawing::Color, @@ -15,7 +16,10 @@ use wgui::{ parser::{self, Fetchable, ParseDocumentParams, ParserState}, task::Tasks, }; -use wlx_common::dash_interface::{self, MonadoDumpSessionFrame}; +use wlx_common::{ + config::GeneralConfig, + dash_interface::{self, MonadoDumpSessionFrame}, +}; use crate::{ frontend::Frontend, @@ -40,6 +44,7 @@ impl TabNameEnum { } } +#[derive(Clone)] enum Task { SetBrightness(f32), SetTab(TabNameEnum), @@ -51,6 +56,8 @@ enum Task { // `DebugTimings` tab DebugTimingsRefreshSessionList, DebugTimingsSetSessionId(i64), + + GeneralSettingsChromaUpdate, } struct SubtabProcessList { @@ -62,6 +69,13 @@ struct SubtabProcessList { struct SubtabGeneralSettings { #[allow(dead_code)] state: ParserState, + + slider_keying_curve: Rc, + slider_keying_despill: Rc, + slider_keying_hue_range: Rc, + slider_keying_saturation_range: Rc, + slider_keying_value_range: Rc, + cs_keying: Rc, } struct DebugGraph { @@ -158,6 +172,12 @@ impl Tab for TabMonado { tab.set_session_id(&mut frontend.layout, session_id)?; } } + Task::GeneralSettingsChromaUpdate => { + if let Subtab::GeneralSettings(tab) = &mut self.subtab { + tab.chroma_update(frontend.interface.general_config(data)); + frontend.interface.config_changed(data); + } + } Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness), Task::SetTab(tab) => { frontend.layout.remove_children(self.id_content); @@ -243,6 +263,8 @@ fn yesno(n: bool) -> &'static str { } } +const SLIDER_MULTIPLIER: f32 = 100.0; + impl SubtabGeneralSettings { fn new( parent_id: WidgetID, @@ -271,7 +293,84 @@ impl SubtabGeneralSettings { }); } - Ok(Self { state }) + let config = frontend.interface.general_config(data); + + let cs_keying = state.fetch_component_as::("cs_keying")?; + let slider_keying_despill = state.fetch_component_as::("slider_keying_despill")?; + let slider_keying_curve = state.fetch_component_as::("slider_keying_curve")?; + let slider_keying_hue_range = state.fetch_component_as::("slider_keying_hue_range")?; + let slider_keying_saturation_range = + state.fetch_component_as::("slider_keying_saturation_range")?; + let slider_keying_value_range = state.fetch_component_as::("slider_keying_value_range")?; + + { + let mut lc = frontend.layout.start_common(); + let mut common = lc.common(); + + // set initial values + let (rgb, range_h, range_s, range_v) = config.chroma_key_params.get_rgb_and_hsv_ranges(); + slider_keying_curve.set_value(&mut common, config.chroma_key_params.curve * SLIDER_MULTIPLIER); + slider_keying_despill.set_value(&mut common, config.chroma_key_params.despill * SLIDER_MULTIPLIER); + slider_keying_hue_range.set_value(&mut common, range_h * SLIDER_MULTIPLIER); + slider_keying_saturation_range.set_value(&mut common, range_s * SLIDER_MULTIPLIER); + slider_keying_value_range.set_value(&mut common, range_v * SLIDER_MULTIPLIER); + cs_keying.set_color(&mut common, rgb); + + // prepare callbacks + fn get_slider_callback(tasks: &Tasks) -> SliderValueChangedCallback { + Box::new({ + let tasks = tasks.clone(); + move |_, _| { + tasks.push(Task::GeneralSettingsChromaUpdate); + } + }) + } + + fn get_color_selector_callback(tasks: &Tasks) -> ColorSelectorChangedCallback { + Box::new({ + let tasks = tasks.clone(); + move |_, _| { + tasks.push(Task::GeneralSettingsChromaUpdate); + } + }) + } + slider_keying_curve.on_value_changed(get_slider_callback(&tasks)); + slider_keying_despill.on_value_changed(get_slider_callback(&tasks)); + slider_keying_hue_range.on_value_changed(get_slider_callback(&tasks)); + slider_keying_saturation_range.on_value_changed(get_slider_callback(&tasks)); + slider_keying_value_range.on_value_changed(get_slider_callback(&tasks)); + cs_keying.on_changed(get_color_selector_callback(&tasks)); + + lc.finish()?; + } + + Ok(Self { + state, + slider_keying_curve, + slider_keying_despill, + slider_keying_hue_range, + slider_keying_saturation_range, + slider_keying_value_range, + cs_keying, + }) + } + + fn chroma_update(&mut self, config: &mut GeneralConfig) { + let val_curve = self.slider_keying_curve.get_value(); + let val_despill = self.slider_keying_despill.get_value(); + let val_range_h = self.slider_keying_hue_range.get_value(); + let val_range_s = self.slider_keying_saturation_range.get_value(); + let val_range_v = self.slider_keying_value_range.get_value(); + let val_rgb = self.cs_keying.get_color(); + + config.chroma_key_params.despill = val_despill / SLIDER_MULTIPLIER; + config.chroma_key_params.curve = val_curve / SLIDER_MULTIPLIER; + config.chroma_key_params.update_hsv_range_from_rgb( + val_rgb, + val_range_h / SLIDER_MULTIPLIER, + val_range_s / SLIDER_MULTIPLIER, + val_range_v / SLIDER_MULTIPLIER, + ); } } diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 210c0a62..ef8fa550 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -211,6 +211,32 @@ impl HsvColor { pub const fn new(h: f32, s: f32, v: f32, a: f32) -> Self { Self { h, s, v, a } } + + pub fn to_rgb(&self) -> Color { + if self.s == 0.0 { + // gray + return Color::new(self.v, self.v, self.v, 1.0); + } + + let h6 = self.h * 6.0; + let i = h6.floor(); + let f = h6 - i; + + let p = self.v * (1.0 - self.s); + let q = self.v * (1.0 - self.s * f); + let t = self.v * (1.0 - self.s * (1.0 - f)); + + let (r, g, b) = match i as i32 % 6 { + 0 => (self.v, t, p), + 1 => (q, self.v, p), + 2 => (p, self.v, t), + 3 => (p, q, self.v), + 4 => (t, p, self.v), + _ => (self.v, p, q), + }; + + Color::new(r, g, b, 1.0) + } } impl From for HsvColor { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 8d5a0e20..9ce45c5f 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -5,6 +5,7 @@ use idmap::IdMap; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, EnumProperty, EnumString, VariantArray}; use wayvr_ipc::packet_client::WvrProcessLaunchParams; +use wgui::drawing::{self, HsvColor}; use crate::{ astr_containers::{AStrMap, AStrSet}, @@ -66,7 +67,7 @@ pub enum HandsfreePointer { EyeTrackingOnly, } -#[derive(Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Serialize, Deserialize)] pub struct ChromaKeyParams { pub hsv_min: [f32; 3], pub hsv_max: [f32; 3], @@ -74,6 +75,49 @@ pub struct ChromaKeyParams { pub despill: f32, } +fn hsv_green() -> [f32; 3] { + [2.0 / 6.0, 1.0, 1.0] +} + +impl Default for ChromaKeyParams { + fn default() -> Self { + Self { + hsv_min: hsv_green(), + hsv_max: hsv_green(), + curve: 0.0, // Monado will ignore chroma keying completely if this value is zero + despill: 0.0, + } + } +} + +impl ChromaKeyParams { + pub fn update_hsv_range_from_rgb(&mut self, rgb_color: drawing::Color, range_h: f32, range_s: f32, range_v: f32) { + let hsv = HsvColor::from(rgb_color); + self.hsv_min[0] = hsv.h - range_h / 2.0; + self.hsv_min[1] = hsv.s - range_s / 2.0; + self.hsv_min[2] = hsv.v - range_v / 2.0; + self.hsv_max[0] = hsv.h + range_h / 2.0; + self.hsv_max[1] = hsv.s + range_s / 2.0; + self.hsv_max[2] = hsv.v + range_v / 2.0; + } + + // Inverse of `update_hsv_range_from_rgb` function + pub fn get_rgb_and_hsv_ranges(&self) -> (drawing::Color, f32, f32, f32) { + ( + HsvColor::new( + (self.hsv_min[0] + self.hsv_max[0]) / 2.0, + (self.hsv_min[1] + self.hsv_max[1]) / 2.0, + (self.hsv_min[2] + self.hsv_max[2]) / 2.0, + 1.0, + ) + .to_rgb(), + self.hsv_max[0] - self.hsv_min[0], + self.hsv_max[1] - self.hsv_min[1], + self.hsv_max[2] - self.hsv_min[2], + ) + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct SerializedWindowSet { pub name: Arc, From 1238808d87e136c9b44649f1e9ff00cb58abfad2 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:32:54 +0900 Subject: [PATCH 006/307] fix chroma_key_params not saving --- wayvr/src/config.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index ab19a337..676dda24 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -6,8 +6,8 @@ use wayvr_ipc::packet_client::WvrProcessLaunchParams; use wlx_common::{ astr_containers::AStrMap, config::{ - AltModifier, CaptureMethod, GeneralConfig, HandsfreePointer, SerializedWindowSet, - SerializedWindowStates, + AltModifier, CaptureMethod, ChromaKeyParams, GeneralConfig, HandsfreePointer, + SerializedWindowSet, SerializedWindowStates, }, config_io, locale::Language, @@ -144,6 +144,7 @@ pub struct AutoSettings { pub autostart_apps: Vec, pub handsfree_pointer: HandsfreePointer, pub language: Option, + pub chroma_key_params: ChromaKeyParams, } fn get_settings_path() -> PathBuf { @@ -196,6 +197,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { autostart_apps: config.autostart_apps.clone(), handsfree_pointer: config.handsfree_pointer, language: config.language, + chroma_key_params: config.chroma_key_params.clone(), }; let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic From 1d8b22d9922c345d6d2e8348132f7150c7aa5aad Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 4 Apr 2026 14:47:01 +0200 Subject: [PATCH 007/307] dash-frontend: "Skybox" tab --- dash-frontend/assets/gui/tab/settings.xml | 3 ++- dash-frontend/assets/lang/en.json | 1 + dash-frontend/src/tab/settings/mod.rs | 6 ++++++ dash-frontend/src/tab/settings/tab_look_and_feel.rs | 2 -- dash-frontend/src/tab/settings/tab_skybox.rs | 12 ++++++++++++ wayvr/src/backend/openxr/monado_state.rs | 6 ++++++ wayvr/src/state.rs | 5 ----- wayvr/src/subsystem/monado_metrics/metrics_fd.rs | 1 + 8 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 dash-frontend/src/tab/settings/tab_skybox.rs diff --git a/dash-frontend/assets/gui/tab/settings.xml b/dash-frontend/assets/gui/tab/settings.xml index 9ca21283..61583d75 100644 --- a/dash-frontend/assets/gui/tab/settings.xml +++ b/dash-frontend/assets/gui/tab/settings.xml @@ -48,6 +48,7 @@
+ @@ -59,4 +60,4 @@
- \ No newline at end of file + diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 7d5e2e05..81fe3df7 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -99,6 +99,7 @@ "SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens", "SCROLL_SPEED": "Scroll speed", "SETS_ON_WATCH": "Sets on watch", + "SKYBOX": "Skybox", "SPACE_DRAG_MULTIPLIER": "Space drag multiplier", "SPACE_DRAG_UNLOCKED": "Allow space drag on all axes", "SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 55eb02a9..0374fd95 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -33,6 +33,7 @@ mod tab_controls; mod tab_features; mod tab_look_and_feel; mod tab_misc; +mod tab_skybox; mod tab_troubleshooting; #[derive(Clone)] @@ -43,6 +44,7 @@ enum TabNameEnum { Misc, AutostartApps, Troubleshooting, + Skybox, } impl TabNameEnum { @@ -54,6 +56,7 @@ impl TabNameEnum { "misc" => Some(TabNameEnum::Misc), "autostart_apps" => Some(TabNameEnum::AutostartApps), "troubleshooting" => Some(TabNameEnum::Troubleshooting), + "skybox" => Some(TabNameEnum::Skybox), _ => None, } } @@ -533,6 +536,9 @@ impl TabSettings { TabNameEnum::Troubleshooting => { tab_troubleshooting::mount(&mut mp, root)?; } + TabNameEnum::Skybox => { + tab_skybox::mount(&mut mp, root)?; + } } Ok(()) diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs index 6fe40420..ad2d1a5a 100644 --- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs +++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs @@ -7,14 +7,12 @@ use wgui::layout::WidgetID; pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { let c = options_category(mp, parent, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?; options_dropdown::(mp, c, &SettingType::Language)?; - options_checkbox(mp, c, SettingType::OpaqueBackground)?; options_checkbox(mp, c, SettingType::HideUsername)?; options_checkbox(mp, c, SettingType::HideGrabHelp)?; options_slider_f32(mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step options_slider_f32(mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step options_slider_f32(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1)?; options_checkbox(mp, c, SettingType::SetsOnWatch)?; - options_checkbox(mp, c, SettingType::UseSkybox)?; options_slider_f32(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; // min, max, step options_checkbox(mp, c, SettingType::UsePassthrough)?; options_checkbox(mp, c, SettingType::Clock12h)?; diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs new file mode 100644 index 00000000..41d05831 --- /dev/null +++ b/dash-frontend/src/tab/settings/tab_skybox.rs @@ -0,0 +1,12 @@ +use crate::tab::settings::{ + SettingType, + macros::{MacroParams, options_category, options_checkbox}, +}; +use wgui::layout::WidgetID; + +pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let c = options_category(mp, parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?; + options_checkbox(mp, c, SettingType::UseSkybox)?; + options_checkbox(mp, c, SettingType::OpaqueBackground)?; + Ok(()) +} diff --git a/wayvr/src/backend/openxr/monado_state.rs b/wayvr/src/backend/openxr/monado_state.rs index 418e5673..ff27fc00 100644 --- a/wayvr/src/backend/openxr/monado_state.rs +++ b/wayvr/src/backend/openxr/monado_state.rs @@ -19,6 +19,8 @@ impl MonadoState { Ok(res) } + #[allow(clippy::missing_const_for_fn)] + #[allow(clippy::unused_self)] pub fn update(&mut self) { #[cfg(feature = "feat-monado-metrics")] if let Some(metrics) = &mut self.metrics { @@ -30,6 +32,10 @@ impl MonadoState { } } + #[allow(clippy::missing_const_for_fn)] + #[allow(clippy::unused_self)] + #[allow(clippy::unnecessary_wraps)] + #[cfg(feature = "feat-monado-metrics")] pub fn set_metrics_enabled(&mut self, enabled: bool) -> anyhow::Result<()> { #[cfg(feature = "feat-monado-metrics")] { diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 9e8d7da2..051427c3 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -9,7 +9,6 @@ use wgui::{ drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex, renderer_vk::context::SharedContext as WSharedContext, }; -use wlx_common::async_executor::AsyncExecutor; use wlx_common::locale::WayVRLangProvider; use wlx_common::{ audio, @@ -38,7 +37,6 @@ use crate::{ pub struct AppState { pub session: AppSession, pub tasks: TaskContainer, - pub executor: AsyncExecutor, pub gfx: Arc, pub gfx_extras: WGfxExtras, @@ -157,12 +155,9 @@ impl AppState { let lang_provider = WayVRLangProvider::from_config(&session.config); - let executor = Rc::new(smol::LocalExecutor::new()); - Ok(Self { session, tasks, - executor, gfx, gfx_extras, hid_provider, diff --git a/wayvr/src/subsystem/monado_metrics/metrics_fd.rs b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs index 983a39c9..4e349ca1 100644 --- a/wayvr/src/subsystem/monado_metrics/metrics_fd.rs +++ b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs @@ -8,6 +8,7 @@ use crate::subsystem::monado_metrics::proto; pub struct MonadoMetricsFd { stream_reader: UnixStream, + #[allow(dead_code)] stream_writer: UnixStream, records: VecDeque, From 7f822b6787c1b2d313f1989def092aca12dafad4 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 4 Apr 2026 21:50:35 +0200 Subject: [PATCH 008/307] dash-frontend: Skymap catalog fetch --- dash-frontend/assets/dashboard/download.svg | 1 + dash-frontend/assets/gui/view/skymap_list.xml | 11 ++ dash-frontend/assets/lang/en.json | 2 + dash-frontend/src/tab/settings/mod.rs | 38 +++++- dash-frontend/src/tab/settings/tab_skybox.rs | 40 ++++-- dash-frontend/src/util/http_client.rs | 10 ++ dash-frontend/src/util/mod.rs | 1 + dash-frontend/src/util/networking/mod.rs | 4 + .../src/util/networking/skymap_catalog.rs | 42 +++++++ dash-frontend/src/views/mod.rs | 1 + dash-frontend/src/views/skymap_list.rs | 117 ++++++++++++++++++ wlx-common/src/config_io.rs | 17 +++ 12 files changed, 273 insertions(+), 11 deletions(-) create mode 100644 dash-frontend/assets/dashboard/download.svg create mode 100644 dash-frontend/assets/gui/view/skymap_list.xml create mode 100644 dash-frontend/src/util/networking/mod.rs create mode 100644 dash-frontend/src/util/networking/skymap_catalog.rs create mode 100644 dash-frontend/src/views/skymap_list.rs diff --git a/dash-frontend/assets/dashboard/download.svg b/dash-frontend/assets/dashboard/download.svg new file mode 100644 index 00000000..f446969b --- /dev/null +++ b/dash-frontend/assets/dashboard/download.svg @@ -0,0 +1 @@ + diff --git a/dash-frontend/assets/gui/view/skymap_list.xml b/dash-frontend/assets/gui/view/skymap_list.xml new file mode 100644 index 00000000..00465088 --- /dev/null +++ b/dash-frontend/assets/gui/view/skymap_list.xml @@ -0,0 +1,11 @@ + + +
+
+
+
+
+ + diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 81fe3df7..122d8206 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -53,6 +53,7 @@ "DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d", "DOUBLE_CURSOR_FIX": "Double cursor fix", "DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors", + "DOWNLOAD_SKYMAPS": "Download skymaps", "FEATURES": "Features", "FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch", "HANDSFREE_POINTER": "Handsfree mode", @@ -72,6 +73,7 @@ "LONG_PRESS_DURATION": "Long press duration", "LOOK_AND_FEEL": "Look & Feel", "MISC": "Miscellaneous", + "NO_SKYMAPS_FOUND": "No skymaps found", "NOTIFICATIONS_ENABLED": "Enable notifications", "NOTIFICATIONS_SOUND_ENABLED": "Notification sounds", "OPAQUE_BACKGROUND": "Opaque background", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 0374fd95..1367f193 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -20,7 +20,9 @@ use wgui::{ }, windowing::context_menu::{self, Blueprint, ContextMenu, TickResult}, }; -use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode}; +use wlx_common::{ + async_executor::AsyncExecutor, config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode, +}; use crate::{ frontend::{Frontend, FrontendTask}, @@ -78,12 +80,31 @@ enum Task { SetTab(TabNameEnum), } +struct SettingsMountParams<'a> { + mp: &'a mut MacroParams<'a>, + globals: &'a WguiGlobals, + parent_id: WidgetID, +} + +struct SettingsUpdateParams<'a> { + layout: &'a mut Layout, + executor: &'a AsyncExecutor, +} + +trait SettingsTab { + fn update(&mut self, _par: SettingsUpdateParams) -> anyhow::Result<()> { + Ok(()) + } +} + pub struct TabSettings { pub state: ParserState, app_button_ids: Vec>, context_menu: ContextMenu, + current_tab: Option>, + tasks: Tasks, marker: PhantomData, } @@ -94,6 +115,13 @@ impl Tab for TabSettings { } fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> { + if let Some(tab) = &mut self.current_tab { + tab.update(SettingsUpdateParams { + layout: &mut frontend.layout, + executor: &frontend.executor, + })?; + } + let mut changed = false; for task in self.tasks.drain() { match task { @@ -507,6 +535,7 @@ impl TabSettings { let root = self.state.get_widget_id("settings_root")?; frontend.layout.remove_children(root); let globals = frontend.layout.state.globals.clone(); + self.current_tab = None; let mut mp = MacroParams { layout: &mut frontend.layout, @@ -537,7 +566,11 @@ impl TabSettings { tab_troubleshooting::mount(&mut mp, root)?; } TabNameEnum::Skybox => { - tab_skybox::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_skybox::State::mount(SettingsMountParams { + mp: &mut mp, + globals: &globals, + parent_id: root, + })?)); } } @@ -572,6 +605,7 @@ impl TabSettings { state: parser_state, marker: PhantomData, context_menu: ContextMenu::default(), + current_tab: None, }) } } diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs index 41d05831..3738f1aa 100644 --- a/dash-frontend/src/tab/settings/tab_skybox.rs +++ b/dash-frontend/src/tab/settings/tab_skybox.rs @@ -1,12 +1,34 @@ -use crate::tab::settings::{ - SettingType, - macros::{MacroParams, options_category, options_checkbox}, +use crate::{ + tab::settings::{ + SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox}, + }, + views::skymap_list, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?; - options_checkbox(mp, c, SettingType::UseSkybox)?; - options_checkbox(mp, c, SettingType::OpaqueBackground)?; - Ok(()) +pub struct State { + skymap_list: skymap_list::View, +} + +impl SettingsTab for State { + fn update(&mut self, par: super::SettingsUpdateParams) -> anyhow::Result<()> { + self.skymap_list.update(par.layout, par.executor)?; + Ok(()) + } +} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category(par.mp, par.parent_id, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?; + options_checkbox(par.mp, c, SettingType::UseSkybox)?; + options_checkbox(par.mp, c, SettingType::OpaqueBackground)?; + + let skymap_list = skymap_list::View::new(skymap_list::Params { + globals: par.globals.clone(), + layout: par.mp.layout, + parent_id: c, + })?; + + Ok(Self { skymap_list }) + } } diff --git a/dash-frontend/src/util/http_client.rs b/dash-frontend/src/util/http_client.rs index 875d509e..69a018f2 100644 --- a/dash-frontend/src/util/http_client.rs +++ b/dash-frontend/src/util/http_client.rs @@ -18,6 +18,16 @@ pub struct HttpClientResponse { pub data: Vec, } +impl HttpClientResponse { + pub fn as_json(self) -> anyhow::Result + where + T: for<'a> serde::Deserialize<'a>, + { + let utf8 = str::from_utf8(&self.data)?; + Ok(serde_json::from_str::(utf8)?) + } +} + pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result { log::info!("fetching URL \"{}\"", url); diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index e5290607..1bd01c98 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -1,5 +1,6 @@ pub mod cached_fetcher; pub mod http_client; +pub mod networking; pub mod pactl_wrapper; pub mod popup_manager; pub mod steam_utils; diff --git a/dash-frontend/src/util/networking/mod.rs b/dash-frontend/src/util/networking/mod.rs new file mode 100644 index 00000000..5f6c078e --- /dev/null +++ b/dash-frontend/src/util/networking/mod.rs @@ -0,0 +1,4 @@ +pub mod skymap_catalog; + +// pub const WAYVR_ROOT_URL: &'static str = "https://wayvr.org"; +pub const WAYVR_SKYMAPS_ROOT: &'static str = "https://wayvr.org/skymaps"; diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs new file mode 100644 index 00000000..498c12f9 --- /dev/null +++ b/dash-frontend/src/util/networking/skymap_catalog.rs @@ -0,0 +1,42 @@ +#![allow(dead_code)] // TODO: Remove later +use serde::Deserialize; +use wlx_common::async_executor::AsyncExecutor; + +use crate::util::{http_client, networking}; + +#[derive(Clone, Debug, Deserialize)] +pub struct SkymapCatalogEntryFiles { + pub size_16k: Option, // "my_skymap_16k.png" + pub size_8k: Option, // "my_skymap_8k.png" + pub size_4k: Option, // "my_skymap_4k.png" + pub size_2k: String, // we should have *at least* this + pub preview: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct SkymapCatalogEntry { + pub uuid: String, + pub created_at: String, + pub modified_at: String, + pub entry_version: u32, + pub name: String, + pub description: String, + pub author: String, + pub files: SkymapCatalogEntryFiles, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct SkymapCatalog { + pub version: u32, + pub r#type: String, + pub entries: Vec, +} + +pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result { + log::info!("Fetching skymap list"); + + let res = http_client::get(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?; + let catalog = res.as_json::()?; + + Ok(catalog) +} diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs index d2cd9369..2f832208 100644 --- a/dash-frontend/src/views/mod.rs +++ b/dash-frontend/src/views/mod.rs @@ -4,3 +4,4 @@ pub mod game_cover; pub mod game_launcher; pub mod game_list; pub mod running_games_list; +pub mod skymap_list; diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs new file mode 100644 index 00000000..9a968f75 --- /dev/null +++ b/dash-frontend/src/views/skymap_list.rs @@ -0,0 +1,117 @@ +use wgui::{ + assets::AssetPath, + components::button::ComponentButton, + globals::WguiGlobals, + i18n::Translation, + layout::{Layout, WidgetID}, + parser::{Fetchable, ParseDocumentParams, ParserState}, + task::Tasks, +}; +use wlx_common::{async_executor::AsyncExecutor, config_io}; + +use crate::util::{networking, wgui_simple}; + +#[derive(Clone)] +enum Task { + DownloadSkymaps, + SetSkymapCatalog(Result), + Refresh, +} + +pub struct Params<'a> { + pub globals: WguiGlobals, + pub layout: &'a mut Layout, + pub parent_id: WidgetID, +} + +pub struct View { + #[allow(dead_code)] + parser_state: ParserState, + tasks: Tasks, + list_parent: WidgetID, +} + +impl View { + pub fn new(params: Params) -> anyhow::Result { + let doc_params = &ParseDocumentParams { + globals: params.globals.clone(), + path: AssetPath::BuiltIn("gui/view/skymap_list.xml"), + extra: Default::default(), + }; + + let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?; + let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?.id; + let tasks = Tasks::new(); + + tasks.push(Task::Refresh); + + tasks.handle_button( + &parser_state.fetch_component_as::("btn_download_skymaps")?, + Task::DownloadSkymaps, + ); + + tasks.handle_button( + &parser_state.fetch_component_as::("btn_refresh")?, + Task::Refresh, + ); + + Ok(Self { + parser_state, + tasks, + list_parent, + }) + } + + pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> { + loop { + let tasks = self.tasks.drain(); + if tasks.is_empty() { + break; + } + for task in tasks { + match task { + Task::DownloadSkymaps => { + self.download_skymaps(executor)?; + } + Task::Refresh => { + self.refresh(layout)?; + } + Task::SetSkymapCatalog(skymap_catalog) => { + log::info!("{:?}", skymap_catalog); + } + } + } + } + + Ok(()) + } + + async fn skymap_catalog_request_wrapper(tasks: Tasks, executor: AsyncExecutor) { + let res = networking::skymap_catalog::request_catalog(&executor).await; + tasks.push(Task::SetSkymapCatalog(res.map_err(|e| format!("{}", e)))); + } + + fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> { + let fut = View::skymap_catalog_request_wrapper(self.tasks.clone(), executor.clone()); + executor.spawn(fut).detach(); + Ok(()) + } + + fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> { + let skymaps_uuids = config_io::get_skymaps_uuids().unwrap_or_default(); + log::info!("skymap uuids {:?}", skymaps_uuids); + + layout.remove_children(self.list_parent); + + if skymaps_uuids.is_empty() { + wgui_simple::create_label( + layout, + self.list_parent, + Translation::from_translation_key("APP_SETTINGS.NO_SKYMAPS_FOUND"), + )?; + return Ok(()); + } + + Ok(()) + } +} diff --git a/wlx-common/src/config_io.rs b/wlx-common/src/config_io.rs index 961e4b1a..7976a5de 100644 --- a/wlx-common/src/config_io.rs +++ b/wlx-common/src/config_io.rs @@ -21,6 +21,23 @@ pub fn get_config_root() -> PathBuf { CONFIG_ROOT_PATH.clone() } +pub fn get_skymaps_root() -> PathBuf { + get_config_root().join("skymaps") +} + +pub fn get_skymaps_uuids() -> anyhow::Result> { + let data = std::fs::read_to_string(get_skymaps_root().join("skymaps.txt"))?; + Ok(data.lines().filter(|line| !line.is_empty()).map(String::from).collect()) +} + +pub fn set_skymaps_uuids(uuids: &[String]) -> anyhow::Result<()> { + let skymaps_root = get_skymaps_root(); + let _ = std::fs::create_dir_all(&skymaps_root); + let data = String::from_iter(uuids.iter().map(|uuid| format!("{}\n", uuid))); + std::fs::write(skymaps_root.join("skymaps.txt"), data)?; + Ok(()) +} + impl ConfigRoot { pub fn get_conf_d_path(&self) -> PathBuf { get_config_root().join(match self { From a3460fbe7055f1ef2b3f927fee44283018c62a6c Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 4 Apr 2026 22:08:44 +0200 Subject: [PATCH 009/307] dash-frontend: use SettingsTab trait for each settings tab --- dash-frontend/src/tab/settings/mod.rs | 27 +++--- .../src/tab/settings/tab_autostart_apps.rs | 35 ++++--- .../src/tab/settings/tab_controls.rs | 48 ++++++---- .../src/tab/settings/tab_features.rs | 37 ++++---- .../src/tab/settings/tab_look_and_feel.rs | 42 +++++---- dash-frontend/src/tab/settings/tab_misc.rs | 27 +++--- .../src/tab/settings/tab_troubleshooting.rs | 92 ++++++++++--------- 7 files changed, 183 insertions(+), 125 deletions(-) diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 1367f193..3194e11e 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -546,31 +546,36 @@ impl TabSettings { idx: 9001, }; + let settings_mount_params = SettingsMountParams { + mp: &mut mp, + globals: &globals, + parent_id: root, + }; + match name { TabNameEnum::LookAndFeel => { - tab_look_and_feel::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_look_and_feel::State::mount(settings_mount_params)?)); } TabNameEnum::Features => { - tab_features::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_features::State::mount(settings_mount_params)?)); } TabNameEnum::Controls => { - tab_controls::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_controls::State::mount(settings_mount_params)?)); } TabNameEnum::Misc => { - tab_misc::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_misc::State::mount(settings_mount_params)?)); } TabNameEnum::AutostartApps => { - tab_autostart_apps::mount(&mut mp, root, &mut self.app_button_ids)?; + self.current_tab = Some(Box::new(tab_autostart_apps::State::mount( + settings_mount_params, + &mut self.app_button_ids, + )?)); } TabNameEnum::Troubleshooting => { - tab_troubleshooting::mount(&mut mp, root)?; + self.current_tab = Some(Box::new(tab_troubleshooting::State::mount(settings_mount_params)?)); } TabNameEnum::Skybox => { - self.current_tab = Some(Box::new(tab_skybox::State::mount(SettingsMountParams { - mp: &mut mp, - globals: &globals, - parent_id: root, - })?)); + self.current_tab = Some(Box::new(tab_skybox::State::mount(settings_mount_params)?)); } } diff --git a/dash-frontend/src/tab/settings/tab_autostart_apps.rs b/dash-frontend/src/tab/settings/tab_autostart_apps.rs index 6483fc94..79496d4e 100644 --- a/dash-frontend/src/tab/settings/tab_autostart_apps.rs +++ b/dash-frontend/src/tab/settings/tab_autostart_apps.rs @@ -1,19 +1,32 @@ use std::rc::Rc; -use crate::tab::settings::macros::{MacroParams, options_autostart_app, options_category}; -use wgui::layout::WidgetID; +use crate::tab::settings::{ + SettingsMountParams, SettingsTab, + macros::{options_autostart_app, options_category}, +}; -pub fn mount(mp: &mut MacroParams, parent: WidgetID, app_button_ids: &mut Vec>) -> anyhow::Result<()> { - *app_button_ids = Vec::new(); +pub struct State {} - if !mp.config.autostart_apps.is_empty() { - let c = options_category(mp, parent, "APP_SETTINGS.AUTOSTART_APPS", "dashboard/apps.svg")?; +impl SettingsTab for State {} - // todo: prevent clone - let autostart_apps = mp.config.autostart_apps.clone(); - for app in autostart_apps { - options_autostart_app(mp, c, &app.name, app_button_ids)?; +impl State { + pub fn mount(par: SettingsMountParams, app_button_ids: &mut Vec>) -> anyhow::Result { + *app_button_ids = Vec::new(); + + if !par.mp.config.autostart_apps.is_empty() { + let c = options_category( + par.mp, + par.parent_id, + "APP_SETTINGS.AUTOSTART_APPS", + "dashboard/apps.svg", + )?; + + // todo: prevent clone + let autostart_apps = par.mp.config.autostart_apps.clone(); + for app in autostart_apps { + options_autostart_app(par.mp, c, &app.name, app_button_ids)?; + } } + Ok(State {}) } - Ok(()) } diff --git a/dash-frontend/src/tab/settings/tab_controls.rs b/dash-frontend/src/tab/settings/tab_controls.rs index 27b1f10b..a8ba00f3 100644 --- a/dash-frontend/src/tab/settings/tab_controls.rs +++ b/dash-frontend/src/tab/settings/tab_controls.rs @@ -1,23 +1,33 @@ use crate::tab::settings::{ - SettingType, - macros::{MacroParams, options_category, options_checkbox, options_dropdown, options_slider_f32, options_slider_i32}, + SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox, options_dropdown, options_slider_f32, options_slider_i32}, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.CONTROLS", "dashboard/controller.svg")?; - options_dropdown::(mp, c, &SettingType::KeyboardMiddleClick)?; - options_dropdown::(mp, c, &SettingType::HandsfreePointer)?; - options_checkbox(mp, c, SettingType::FocusFollowsMouseMode)?; - options_checkbox(mp, c, SettingType::LeftHandedMouse)?; - options_checkbox(mp, c, SettingType::AllowSliding)?; - options_checkbox(mp, c, SettingType::InvertScrollDirectionX)?; - options_checkbox(mp, c, SettingType::InvertScrollDirectionY)?; - options_slider_f32(mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1)?; - options_slider_f32(mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?; - options_slider_f32(mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?; - options_slider_f32(mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1)?; - options_slider_f32(mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1)?; - options_slider_i32(mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50)?; - Ok(()) +pub struct State {} + +impl SettingsTab for State {} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category( + par.mp, + par.parent_id, + "APP_SETTINGS.CONTROLS", + "dashboard/controller.svg", + )?; + options_dropdown::(par.mp, c, &SettingType::KeyboardMiddleClick)?; + options_dropdown::(par.mp, c, &SettingType::HandsfreePointer)?; + options_checkbox(par.mp, c, SettingType::FocusFollowsMouseMode)?; + options_checkbox(par.mp, c, SettingType::LeftHandedMouse)?; + options_checkbox(par.mp, c, SettingType::AllowSliding)?; + options_checkbox(par.mp, c, SettingType::InvertScrollDirectionX)?; + options_checkbox(par.mp, c, SettingType::InvertScrollDirectionY)?; + options_slider_f32(par.mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1)?; + options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?; + options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?; + options_slider_f32(par.mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1)?; + options_slider_f32(par.mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1)?; + options_slider_i32(par.mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50)?; + Ok(State {}) + } } diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 264a40fd..a01abdc4 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -1,20 +1,25 @@ use crate::tab::settings::{ - SettingType, - macros::{MacroParams, options_category, options_checkbox, options_slider_f32}, + SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox, options_slider_f32}, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?; - options_checkbox(mp, c, SettingType::NotificationsEnabled)?; - options_checkbox(mp, c, SettingType::NotificationsSoundEnabled)?; - options_checkbox(mp, c, SettingType::KeyboardSoundEnabled)?; - options_checkbox(mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; - options_checkbox(mp, c, SettingType::SpaceDragUnlocked)?; - options_checkbox(mp, c, SettingType::SpaceRotateUnlocked)?; - options_slider_f32(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5)?; - options_checkbox(mp, c, SettingType::BlockGameInput)?; - options_checkbox(mp, c, SettingType::BlockGameInputIgnoreWatch)?; - options_checkbox(mp, c, SettingType::BlockPosesOnKbdInteraction)?; - Ok(()) +pub struct State {} + +impl SettingsTab for State {} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category(par.mp, par.parent_id, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?; + options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; + options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; + options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; + options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; + options_checkbox(par.mp, c, SettingType::SpaceDragUnlocked)?; + options_checkbox(par.mp, c, SettingType::SpaceRotateUnlocked)?; + options_slider_f32(par.mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5)?; + options_checkbox(par.mp, c, SettingType::BlockGameInput)?; + options_checkbox(par.mp, c, SettingType::BlockGameInputIgnoreWatch)?; + options_checkbox(par.mp, c, SettingType::BlockPosesOnKbdInteraction)?; + Ok(State {}) + } } diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs index ad2d1a5a..55927880 100644 --- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs +++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs @@ -1,20 +1,30 @@ use crate::tab::settings::{ - SettingType, - macros::{MacroParams, options_category, options_checkbox, options_dropdown, options_slider_f32}, + SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox, options_dropdown, options_slider_f32}, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?; - options_dropdown::(mp, c, &SettingType::Language)?; - options_checkbox(mp, c, SettingType::HideUsername)?; - options_checkbox(mp, c, SettingType::HideGrabHelp)?; - options_slider_f32(mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step - options_slider_f32(mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step - options_slider_f32(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1)?; - options_checkbox(mp, c, SettingType::SetsOnWatch)?; - options_slider_f32(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; // min, max, step - options_checkbox(mp, c, SettingType::UsePassthrough)?; - options_checkbox(mp, c, SettingType::Clock12h)?; - Ok(()) +pub struct State {} + +impl SettingsTab for State {} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category( + par.mp, + par.parent_id, + "APP_SETTINGS.LOOK_AND_FEEL", + "dashboard/palette.svg", + )?; + options_dropdown::(par.mp, c, &SettingType::Language)?; + options_checkbox(par.mp, c, SettingType::HideUsername)?; + options_checkbox(par.mp, c, SettingType::HideGrabHelp)?; + options_slider_f32(par.mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step + options_slider_f32(par.mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step + options_slider_f32(par.mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1)?; + options_checkbox(par.mp, c, SettingType::SetsOnWatch)?; + options_slider_f32(par.mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; // min, max, step + options_checkbox(par.mp, c, SettingType::UsePassthrough)?; + options_checkbox(par.mp, c, SettingType::Clock12h)?; + Ok(State {}) + } } diff --git a/dash-frontend/src/tab/settings/tab_misc.rs b/dash-frontend/src/tab/settings/tab_misc.rs index 9fa6f429..36b3e1b9 100644 --- a/dash-frontend/src/tab/settings/tab_misc.rs +++ b/dash-frontend/src/tab/settings/tab_misc.rs @@ -1,15 +1,20 @@ use crate::tab::settings::{ - SettingType, - macros::{MacroParams, options_category, options_checkbox, options_dropdown}, + SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox, options_dropdown}, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?; - options_dropdown::(mp, c, &SettingType::CaptureMethod)?; - options_checkbox(mp, c, SettingType::XwaylandByDefault)?; - options_checkbox(mp, c, SettingType::UprightScreenFix)?; - options_checkbox(mp, c, SettingType::DoubleCursorFix)?; - options_checkbox(mp, c, SettingType::ScreenRenderDown)?; - Ok(()) +pub struct State {} + +impl SettingsTab for State {} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category(par.mp, par.parent_id, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?; + options_dropdown::(par.mp, c, &SettingType::CaptureMethod)?; + options_checkbox(par.mp, c, SettingType::XwaylandByDefault)?; + options_checkbox(par.mp, c, SettingType::UprightScreenFix)?; + options_checkbox(par.mp, c, SettingType::DoubleCursorFix)?; + options_checkbox(par.mp, c, SettingType::ScreenRenderDown)?; + Ok(State {}) + } } diff --git a/dash-frontend/src/tab/settings/tab_troubleshooting.rs b/dash-frontend/src/tab/settings/tab_troubleshooting.rs index 9e16fd4b..83a822c3 100644 --- a/dash-frontend/src/tab/settings/tab_troubleshooting.rs +++ b/dash-frontend/src/tab/settings/tab_troubleshooting.rs @@ -1,45 +1,55 @@ use crate::tab::settings::{ - Task, - macros::{MacroParams, options_category, options_danger_button}, + SettingsMountParams, SettingsTab, Task, + macros::{options_category, options_danger_button}, }; -use wgui::layout::WidgetID; -pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { - let c = options_category(mp, parent, "APP_SETTINGS.TROUBLESHOOTING", "dashboard/cpu.svg")?; - options_danger_button( - mp, - c, - "APP_SETTINGS.RESET_PLAYSPACE", - "dashboard/recenter.svg", - Task::ResetPlayspace, - )?; - options_danger_button( - mp, - c, - "APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS", - "dashboard/display.svg", - Task::ClearPipewireTokens, - )?; - options_danger_button( - mp, - c, - "APP_SETTINGS.CLEAR_SAVED_STATE", - "dashboard/binary.svg", - Task::ClearSavedState, - )?; - options_danger_button( - mp, - c, - "APP_SETTINGS.DELETE_ALL_CONFIGS", - "dashboard/circle.svg", - Task::DeleteAllConfigs, - )?; - options_danger_button( - mp, - c, - "APP_SETTINGS.RESTART_SOFTWARE", - "dashboard/refresh.svg", - Task::RestartSoftware, - )?; - Ok(()) +pub struct State {} + +impl SettingsTab for State {} + +impl State { + pub fn mount(par: SettingsMountParams) -> anyhow::Result { + let c = options_category( + par.mp, + par.parent_id, + "APP_SETTINGS.TROUBLESHOOTING", + "dashboard/cpu.svg", + )?; + options_danger_button( + par.mp, + c, + "APP_SETTINGS.RESET_PLAYSPACE", + "dashboard/recenter.svg", + Task::ResetPlayspace, + )?; + options_danger_button( + par.mp, + c, + "APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS", + "dashboard/display.svg", + Task::ClearPipewireTokens, + )?; + options_danger_button( + par.mp, + c, + "APP_SETTINGS.CLEAR_SAVED_STATE", + "dashboard/binary.svg", + Task::ClearSavedState, + )?; + options_danger_button( + par.mp, + c, + "APP_SETTINGS.DELETE_ALL_CONFIGS", + "dashboard/circle.svg", + Task::DeleteAllConfigs, + )?; + options_danger_button( + par.mp, + c, + "APP_SETTINGS.RESTART_SOFTWARE", + "dashboard/refresh.svg", + Task::RestartSoftware, + )?; + Ok(State {}) + } } From f2dcdcc6c398e1d64f0e611fbed2d368763edd5e Mon Sep 17 00:00:00 2001 From: Aleksander Date: Mon, 6 Apr 2026 13:22:59 +0200 Subject: [PATCH 010/307] dash-frontend: list skymaps from the server (wip), loading spinner, wgui: animation bugfix --- dash-frontend/assets/dashboard/loading.svg | 1 + dash-frontend/assets/gui/t_loading.xml | 16 ++ .../assets/gui/view/download_skymaps.xml | 7 + dash-frontend/assets/gui/view/skymap_list.xml | 2 +- .../assets/gui/view/skymap_list_cell.xml | 26 ++++ dash-frontend/assets/lang/en.json | 1 + dash-frontend/src/frontend.rs | 23 ++- dash-frontend/src/tab/apps.rs | 49 ++---- dash-frontend/src/tab/settings/mod.rs | 6 +- dash-frontend/src/tab/settings/tab_skybox.rs | 1 + .../src/util/networking/skymap_catalog.rs | 20 ++- dash-frontend/src/util/popup_manager.rs | 78 ++++++++-- dash-frontend/src/util/wgui_simple.rs | 83 ++++++++++- dash-frontend/src/views/app_launcher.rs | 33 ++++- dash-frontend/src/views/download_skymaps.rs | 140 ++++++++++++++++++ dash-frontend/src/views/game_cover.rs | 11 ++ dash-frontend/src/views/game_launcher.rs | 30 ++++ dash-frontend/src/views/game_list.rs | 47 ++---- dash-frontend/src/views/mod.rs | 2 + dash-frontend/src/views/skymap_list.rs | 55 +++++-- dash-frontend/src/views/skymap_list_cell.rs | 56 +++++++ wgui/src/animation.rs | 8 +- wgui/src/task.rs | 9 +- 23 files changed, 594 insertions(+), 110 deletions(-) create mode 100644 dash-frontend/assets/dashboard/loading.svg create mode 100644 dash-frontend/assets/gui/t_loading.xml create mode 100644 dash-frontend/assets/gui/view/download_skymaps.xml create mode 100644 dash-frontend/assets/gui/view/skymap_list_cell.xml create mode 100644 dash-frontend/src/views/download_skymaps.rs create mode 100644 dash-frontend/src/views/skymap_list_cell.rs diff --git a/dash-frontend/assets/dashboard/loading.svg b/dash-frontend/assets/dashboard/loading.svg new file mode 100644 index 00000000..1c800c12 --- /dev/null +++ b/dash-frontend/assets/dashboard/loading.svg @@ -0,0 +1 @@ + diff --git a/dash-frontend/assets/gui/t_loading.xml b/dash-frontend/assets/gui/t_loading.xml new file mode 100644 index 00000000..ca8c5786 --- /dev/null +++ b/dash-frontend/assets/gui/t_loading.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/dash-frontend/assets/gui/view/download_skymaps.xml b/dash-frontend/assets/gui/view/download_skymaps.xml new file mode 100644 index 00000000..39c2711e --- /dev/null +++ b/dash-frontend/assets/gui/view/download_skymaps.xml @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/dash-frontend/assets/gui/view/skymap_list.xml b/dash-frontend/assets/gui/view/skymap_list.xml index 00465088..c273f753 100644 --- a/dash-frontend/assets/gui/view/skymap_list.xml +++ b/dash-frontend/assets/gui/view/skymap_list.xml @@ -1,7 +1,7 @@
-
+
- - - @@ -243,10 +231,7 @@
-
- - -
+
diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 73a2f944..862fcc5d 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -1,8 +1,6 @@ use std::{io::Read, os::unix::net::UnixStream, path::PathBuf, sync::Arc}; use anyhow::Context; -use smithay::input::Seat; -use smithay::wayland::selection::data_device::set_data_device_selection; use smithay::{ backend::input::Keycode, input::{keyboard::KeyboardHandle, pointer::PointerHandle}, @@ -11,13 +9,13 @@ use smithay::{ }; use xkbcommon::xkb; +use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; + use super::{ ProcessWayVREnv, comp::{self, ClientState}, process, }; -use crate::backend::wayvr::comp::Application; -use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask}; pub struct WayVRClient { pub client: wayland_server::Client, @@ -28,7 +26,6 @@ pub struct WayVRCompositor { pub state: comp::Application, pub seat_keyboard: KeyboardHandle, pub seat_pointer: PointerHandle, - pub seat: Seat, pub serial_counter: SerialCounter, pub wayland_env: super::WaylandEnv, @@ -71,7 +68,6 @@ impl WayVRCompositor { display: wayland_server::Display, seat_keyboard: KeyboardHandle, seat_pointer: PointerHandle, - seat: Seat, ) -> anyhow::Result { let (wayland_env, listener) = create_wayland_listener()?; @@ -85,7 +81,6 @@ impl WayVRCompositor { serial_counter: SerialCounter::new(), clients: Vec::new(), toplevel_surf_count: 0, - seat, }) } @@ -205,17 +200,6 @@ impl WayVRCompositor { ); } - pub fn set_clipboard_text(&mut self, text: String) { - set_data_device_selection::( - &self.state.display_handle, - &self.seat, - vec![ - "text/plain;charset=utf-8".to_string(), - "text/plain".to_string(), - ], - text.as_bytes().into(), - ); - } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { // Smithay only accepts keymaps in a string form due to thread safety concerns self.seat_keyboard diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 74674949..5d83f02b 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -241,13 +241,7 @@ impl WvrServerState { }; Ok(Self { - manager: client::WayVRCompositor::new( - state, - display, - seat_keyboard, - seat_pointer, - seat, - )?, + manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer)?, processes: ProcessVec::new(), wm: window::WindowManager::new(), ticks: 0, @@ -614,9 +608,6 @@ impl WvrServerState { pub fn send_key(&mut self, virtual_key: u32, down: bool) { self.manager.send_key(virtual_key, down); } - pub fn set_clipboard_text(&mut self, text: String) { - self.manager.set_clipboard_text(text); - } pub fn set_keymap(&mut self, keymap: &xkb::Keymap) -> anyhow::Result<()> { self.manager.set_keymap(keymap) diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 676dda24..9c89a629 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -140,7 +140,6 @@ pub struct AutoSettings { pub context_menu_hold_and_release: bool, pub capture_method: CaptureMethod, pub keyboard_middle_click_mode: AltModifier, - pub keyboard_swipe_to_type_enabled: bool, pub autostart_apps: Vec, pub handsfree_pointer: HandsfreePointer, pub language: Option, @@ -193,7 +192,6 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { context_menu_hold_and_release: config.context_menu_hold_and_release, capture_method: config.capture_method, keyboard_middle_click_mode: config.keyboard_middle_click_mode, - keyboard_swipe_to_type_enabled: config.keyboard_swipe_to_type_enabled, autostart_apps: config.autostart_apps.clone(), handsfree_pointer: config.handsfree_pointer, language: config.language, diff --git a/wayvr/src/main.rs b/wayvr/src/main.rs index 7a49f02e..6649b633 100644 --- a/wayvr/src/main.rs +++ b/wayvr/src/main.rs @@ -285,7 +285,6 @@ fn logging_init(args: &mut Args) { .from_env_lossy() .add_directive("symphonia_core::probe=warn".parse().unwrap()) .add_directive("symphonia_bundle_mp3=warn".parse().unwrap()) - .add_directive("ort=warn".parse().unwrap()) .add_directive("zbus=warn".parse().unwrap()) .add_directive("usvg=error".parse().unwrap()) .add_directive("resvg=error".parse().unwrap()) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index ec72e09c..b96b0368 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,8 +1,5 @@ -use super::{ - KeyButtonData, KeyState, KeyboardState, handle_mouse_motion, handle_press, handle_release, - init_swipe_type_manager, - layout::{self, KeyCapType}, -}; +use std::{collections::HashMap, rc::Rc, time::Duration}; + use crate::{ app_misc, gui::{ @@ -14,13 +11,8 @@ use crate::{ subsystem::hid::XkbKeymap, windowing::backend::OverlayEventData, }; -use anyhow::{Context, bail}; +use anyhow::Context; use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; -use slotmap::Key; -use std::{collections::HashMap, rc::Rc, time::Duration}; -use wgui::event::StyleSetRequest; -use wgui::layout::LayoutTask; -use wgui::taffy::Display; use wgui::{ animation::{Animation, AnimationEasing}, assets::AssetPath, @@ -34,6 +26,11 @@ use wgui::{ widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle}, }; +use super::{ + KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, + layout::{self, KeyCapType}, +}; + const PIXELS_PER_UNIT: f32 = 60.; fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'static> { @@ -44,134 +41,6 @@ fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'s } } -pub(super) fn update_swipe_prediction_bar( - panel: &mut GuiPanel, - app: &mut AppState, -) -> anyhow::Result { - let mut elements_changed = false; - - let (accent_color, anim_mult) = { - let theme = &app.wgui_theme; - (theme.accent_color, theme.animation_mult) - }; - - if let Some(recv) = panel.state.swipe_candidate_receiver.as_mut() - && let Ok(candidates) = recv.try_recv() - { - let predictions_root = panel - .parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if predictions_root.is_null() { - return Ok(elements_changed); - } - let doc_params = new_doc_params(panel); - - panel.layout.remove_children(predictions_root); - - let Some(new_suggestions) = candidates else { - return Ok(elements_changed); - }; - - let mut iter = new_suggestions.iter(); - let Some(best_prediction) = iter.next() else { - bail!("not enough swipe predictions"); - }; - if let Some(manager) = panel.state.swipe_typing_manager.as_mut() { - manager.select_word(best_prediction, app, panel.state.modifiers); - } - for (i, prediction) in iter.enumerate() { - let mut params = HashMap::new(); - let id: Rc = Rc::from(format!("Prediction-{i}")); - params.insert("id".into(), id.clone()); - params.insert("text".into(), prediction.clone().into()); - - panel.parser_state.instantiate_template( - &doc_params, - "KeyPrediction", - &mut panel.layout, - predictions_root, - params, - )?; - - if let Ok(widget_id) = panel.parser_state.get_widget_id(&id) { - let key_state = { - let rect = panel - .layout - .state - .widgets - .get_as::(widget_id) - .unwrap(); // want panic - - Rc::new(KeyState { - // fake button state just so we get key state for anims - button_state: KeyButtonData::Modifier { - modifier: 0, - sticky: core::cell::Cell::new(false), - }, - color: rect.params.color, - color2: rect.params.color2, - base_border_color: rect.params.border_color, - cur_border_color: rect.params.border_color.into(), - border: rect.params.border, - drawn_state: false.into(), - }) - }; - panel.add_event_listener( - widget_id, - EventListenerKind::MousePress, - Box::new({ - let k = key_state.clone(); - let pred = prediction.clone(); - move |common, data, app, state| { - if let Some(manager) = state.swipe_typing_manager.as_mut() { - manager.select_alternate_prediction(&pred, app, state.modifiers); - on_press_anim(k.clone(), common, data) - } - Ok(EventResult::Pass) - } - }), - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseEnter, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_enter_anim(k.clone(), common, data, accent_color, anim_mult, 0.0); - Ok(EventResult::Pass) - } - }), - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseLeave, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_leave_anim(k.clone(), common, data, accent_color, anim_mult, 0.0); - Ok(EventResult::Pass) - } - }), - ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseRelease, - Box::new({ - let k = key_state.clone(); - move |common, data, _app, _state| { - on_release_anim(k.clone(), common, data); - Ok(EventResult::Pass) - } - }), - ); - } - } - elements_changed = true; - } - Ok(elements_changed) -} #[allow(clippy::too_many_lines, clippy::significant_drop_tightening)] pub(super) fn create_keyboard_panel( app: &mut AppState, @@ -244,7 +113,7 @@ pub(super) fn create_keyboard_panel( params.insert(Rc::from("width"), Rc::from(key_width.to_string())); params.insert(Rc::from("height"), Rc::from(key_height.to_string())); - let mut label = key.label.clone().into_iter(); + let mut label = key.label.into_iter(); label .next() .and_then(|s| params.insert("text".into(), s.into())); @@ -300,9 +169,6 @@ pub(super) fn create_keyboard_panel( }) }; - let key_cap_type: Rc = Rc::from(key.cap_type); - let key_label: Rc> = Rc::from(key.label); - let width_mul = 1. / my_size_f32; panel.add_event_listener( @@ -320,7 +186,6 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); - Ok(EventResult::Pass) } }), @@ -340,7 +205,6 @@ pub(super) fn create_keyboard_panel( anim_mult, width_mul, ); - Ok(EventResult::Pass) } }), @@ -350,69 +214,26 @@ pub(super) fn create_keyboard_panel( EventListenerKind::MousePress, Box::new({ let k = key_state.clone(); - let k_label = key_label.clone(); - let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { let CallbackMetadata::MouseButton(button) = data.metadata else { panic!("CallbackMetadata should contain MouseButton!"); }; - let within_key_pos = data - .metadata - .get_mouse_pos_normalized(&common.alterables.transform_stack); - handle_press( - app, - &k, - &k_label, - &k_cap_type, - &within_key_pos, - state, - button, - button.device, - ); + handle_press(app, &k, state, button); on_press_anim(k.clone(), common, data); Ok(EventResult::Pass) } }), ); - panel.add_event_listener( - widget_id, - EventListenerKind::MouseMotion, - Box::new({ - let k = key_state.clone(); - let k_label = key_label.clone(); - let k_cap_type = key_cap_type.clone(); - move |common, data, _app, state| { - let within_key_pos = data - .metadata - .get_mouse_pos_normalized(&common.alterables.transform_stack); - let CallbackMetadata::MousePosition(position) = data.metadata else { - panic!("CallbackMetadata should contain MousePosition!"); - }; - - handle_mouse_motion( - &k, - &k_label, - &k_cap_type, - state, - &within_key_pos, - position.device, - ); - Ok(EventResult::Pass) - } - }), - ); panel.add_event_listener( widget_id, EventListenerKind::MouseRelease, Box::new({ let k = key_state.clone(); - let k_cap_type = key_cap_type.clone(); move |common, data, app, state| { - if handle_release(app, &k, &k_cap_type, state) { + if handle_release(app, &k, state) { on_release_anim(k.clone(), common, data); } - Ok(EventResult::Pass) } }), @@ -486,41 +307,6 @@ pub(super) fn create_keyboard_panel( elems_changed = true; } } - if !app.session.config.keyboard_swipe_to_type_enabled { - panel.state.swipe_typing_manager = None; - panel.state.swipe_candidate_receiver = None; - - let predictions_root = panel - .parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if !predictions_root.is_null() { - panel.layout.remove_children(predictions_root); - - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::None), - )); - } - } - if app.session.config.keyboard_swipe_to_type_enabled - && panel.state.swipe_typing_manager.is_none() - { - init_swipe_type_manager(&mut panel.state); - - let predictions_root = panel - .parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - - if !predictions_root.is_null() { - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::Flex), - )); - } - } } OverlayEventData::CustomCommand { element, command } => { diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 87cfa8c1..2af0590f 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -1,13 +1,14 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; +use regex::Regex; +use serde::{Deserialize, Serialize}; + use crate::{ config::{ConfigType, load_known_yaml}, subsystem::hid::{ KEYS_TO_MODS, KeyType, META, NUM_LOCK, SHIFT, VirtualKey, XkbKeymap, get_key_type, }, }; -use regex::Regex; -use serde::{Deserialize, Serialize}; use super::KeyButtonData; @@ -229,7 +230,7 @@ pub(super) struct KeyData { pub(super) cap_type: KeyCapType, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] pub enum KeyCapType { /// Label an SVG Special, diff --git a/wayvr/src/overlays/keyboard/mod.rs b/wayvr/src/overlays/keyboard/mod.rs index dc432395..966f5baa 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -1,6 +1,10 @@ -use crate::overlays::keyboard::builder::update_swipe_prediction_bar; -use crate::overlays::keyboard::layout::KeyCapType; -use crate::overlays::keyboard::swipe_type::SwipeTypingManager; +use std::{ + cell::Cell, + collections::HashMap, + process::{Child, Command}, + sync::atomic::Ordering, +}; + use crate::{ KEYMAP_CHANGE, backend::{ @@ -23,20 +27,9 @@ use crate::{ }, }; use anyhow::Context; -use glam::{Affine3A, Quat, Vec2, Vec3, vec3}; +use glam::{Affine3A, Quat, Vec3, vec3}; use regex::Regex; -use slotmap::{Key, SlotMap, new_key_type}; -use std::sync::mpsc::Receiver; -use std::{ - cell::Cell, - collections::HashMap, - process::{Child, Command}, - sync::atomic::Ordering, -}; -use wgui::event::StyleSetRequest; -use wgui::layout::LayoutTask; -use wgui::parser::Fetchable; -use wgui::taffy::Display; +use slotmap::{SlotMap, new_key_type}; use wgui::{ drawing, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, @@ -49,7 +42,6 @@ use wlx_common::{ pub mod builder; mod layout; -mod swipe_type; pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META]; @@ -64,8 +56,6 @@ pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result anyhow::Result { - state.swipe_typing_manager = Some(engine); - state.swipe_candidate_receiver = Some(receiver); - } - Err(e) => { - log::error!("Error occurred while trying to load swipe engine: {}", e); - } - }; -} -pub(self) fn hide_swipe_type_manager(panel: &mut GuiPanel) { - let predictions_root = panel - .parser_state - .get_widget_id("swipe_predictions_root") - .unwrap_or_default(); - if !predictions_root.is_null() { - panel.layout.tasks.push(LayoutTask::SetWidgetStyle( - predictions_root, - StyleSetRequest::Display(Display::None), - )); - } -} + const fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -182,18 +150,8 @@ impl KeyboardBackend { keymap: Option<&XkbKeymap>, app: &mut AppState, ) -> anyhow::Result { - let mut state = self.default_state.take(); - - if app.session.config.keyboard_swipe_to_type_enabled { - init_swipe_type_manager(&mut state); - } - - log::info!("swipe engine created"); - let mut panel = create_keyboard_panel(app, keymap, state, &self.wlx_layout)?; - - if !app.session.config.keyboard_swipe_to_type_enabled { - hide_swipe_type_manager(&mut panel); - } + let panel = + create_keyboard_panel(app, keymap, self.default_state.take(), &self.wlx_layout)?; let id = self.layout_panels.insert(panel); if let Some(layout_name) = keymap.and_then(|k| k.get_name()) { @@ -218,38 +176,30 @@ impl KeyboardBackend { if self.active_layout.eq(new_key) { return Ok(false); } - self.internal_switch_keymap(*new_key, app); + self.internal_switch_keymap(*new_key); } else { let new_key = self.add_new_keymap(Some(keymap), app)?; - self.internal_switch_keymap(new_key, app); + self.internal_switch_keymap(new_key); } app.tasks .enqueue(TaskType::Overlay(OverlayTask::KeyboardChanged)); Ok(true) } - fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey, app: &AppState) { - let mut state_from = self + fn internal_switch_keymap(&mut self, new_key: KeyboardPanelKey) { + let state_from = self .layout_panels .get_mut(self.active_layout) .unwrap() .state .take(); - if app.session.config.keyboard_swipe_to_type_enabled { - init_swipe_type_manager(&mut state_from); - } - self.active_layout = new_key; self.layout_panels .get_mut(self.active_layout) .unwrap() .state = state_from; - - if !app.session.config.keyboard_swipe_to_type_enabled { - hide_swipe_type_manager(self.layout_panels.get_mut(self.active_layout).unwrap()) - } } fn get_effective_keymap(&mut self) -> anyhow::Result { @@ -283,13 +233,6 @@ impl KeyboardBackend { } } - fn update_swipe_prediction_bar(&mut self, app: &mut AppState) -> anyhow::Result<()> { - if update_swipe_prediction_bar(self.panel(), app)? { - self.panel().process_custom_elems(app); - } - Ok(()) - } - fn auto_switch_keymap(&mut self, app: &mut AppState) -> anyhow::Result { let keymap = self.get_effective_keymap()?; app.hid_provider @@ -323,7 +266,6 @@ impl OverlayBackend for KeyboardBackend { }); } } - self.update_swipe_prediction_bar(app)?; self.panel().should_render(app) } fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> { @@ -385,8 +327,6 @@ struct KeyboardState { overlay_list: OverlayList, set_list: SetList, clock_12h: bool, - swipe_typing_manager: Option, - swipe_candidate_receiver: Option>>>, } macro_rules! take_and_leave_default { @@ -406,8 +346,6 @@ impl KeyboardState { overlay_list: OverlayList::default(), set_list: SetList::default(), clock_12h: self.clock_12h, - swipe_typing_manager: None, - swipe_candidate_receiver: None, } } } @@ -448,75 +386,26 @@ enum KeyButtonData { }, } -fn handle_mouse_motion( - key: &KeyState, - key_label: &Vec, - key_cap_type: &KeyCapType, - keyboard: &mut KeyboardState, - within_key_pos: &Option, - device: usize, -) { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) - { - if !swipe_manager.is_current_swipe_empty() { - match &key.button_state { - KeyButtonData::Key { vk, pressed } => { - if let Some(pos) = within_key_pos { - // check because mouse motion can trigger despite hover being false - if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { - if let Some(label) = key_label.first() { - swipe_manager.add_swipe( - pos, - label.chars().next().unwrap_or_default(), - device, - ); - } - } - } - } - _ => {} - } - } - } -} fn handle_press( app: &mut AppState, key: &KeyState, - key_label: &Vec, - key_cap_type: &KeyCapType, - within_key_pos: &Option, keyboard: &mut KeyboardState, button: MouseButtonEvent, - device: usize, ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) - { - if let Some(pos) = within_key_pos { - if let Some(label) = key_label.first() { - swipe_manager.add_swipe( - pos, - label.chars().next().unwrap_or_default(), - device, - ); - } - } - } else { - keyboard.modifiers |= match button.index { - MouseButtonIndex::Right => SHIFT, - MouseButtonIndex::Middle => keyboard.alt_modifier, - _ => 0, - }; - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, true); - pressed.set(true); - play_key_click(app); - } + keyboard.modifiers |= match button.index { + MouseButtonIndex::Right => SHIFT, + MouseButtonIndex::Middle => keyboard.alt_modifier, + _ => 0, + }; + + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, true); + pressed.set(true); + play_key_click(app); } KeyButtonData::Modifier { modifier, sticky } => { sticky.set(keyboard.modifiers & *modifier == 0); @@ -546,51 +435,20 @@ fn handle_press( } } -fn handle_release( - app: &mut AppState, - key: &KeyState, - k_cap_type: &KeyCapType, - keyboard: &mut KeyboardState, -) -> bool { +fn handle_release(app: &mut AppState, key: &KeyState, keyboard: &mut KeyboardState) -> bool { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() - && matches!(*k_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) - { - if swipe_manager.did_swipe_leave_first_key() { - match swipe_manager.predict() { - Ok(()) => {} - Err(e) => { - log::error!("{}", e) - } - } - } else { - // pointer must have been released on the same key it was pressed on - swipe_manager.reset(); // drop swipe tracking that was started on press + pressed.set(false); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, true); - pressed.set(true); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, false); - play_key_click(app); + for m in &AUTO_RELEASE_MODS { + if keyboard.modifiers & *m != 0 { + keyboard.modifiers &= !*m; } - } else { - if let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() { - swipe_manager.reset(); - } - pressed.set(false); - - for m in &AUTO_RELEASE_MODS { - if keyboard.modifiers & *m != 0 { - keyboard.modifiers &= !*m; - } - } - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), *vk, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); } + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + app.hid_provider + .set_modifiers_routed(app.wvr_server.as_mut(), keyboard.modifiers); true } KeyButtonData::Modifier { modifier, sticky } => { diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs deleted file mode 100644 index d4e98e75..00000000 --- a/wayvr/src/overlays/keyboard/swipe_type.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::state::AppState; -use crate::subsystem::hid::{CTRL, KeyModifier, VirtualKey}; -use crate::subsystem::input::KeyboardFocus; -use anyhow::bail; -use arboard::Clipboard; -use glam::Vec2; -use std::mem; -use std::sync::mpsc::{Receiver, Sender, SyncSender, channel, sync_channel}; -use std::thread::{self, JoinHandle}; -use std::time::Instant; -use super_swipe_type::SwipePoint; -use super_swipe_type::keyboard_manager::QwertyKeyboardGrid; -use super_swipe_type::swipe_orchestrator::SwipeOrchestrator; - -const PREDICTION_SUGGESTION_COUNT: usize = 5; - -enum PredictionTask { - Predict { - swipe: Vec, - last_word: Option, - }, - Shutdown, -} - -pub struct SwipeTypingManager { - keyboard_gird: QwertyKeyboardGrid, - current_swipe: Vec, - swipe_candidate_sender: SyncSender>>, - prediction_task_sender: Sender, - worker_thread: Option>, - swipe_start_time: Option, - clipboard: Clipboard, - swipe_left_first_key: bool, - first_swipe_char: char, - current_swipe_device: Option, - last_swiped_word: Option, -} - -impl SwipeTypingManager { - pub fn select_alternate_prediction( - &mut self, - word: &String, - app: &mut AppState, - original_keyboard_mods: KeyModifier, - ) { - Self::undo(app, original_keyboard_mods); - self.select_word(word, app, original_keyboard_mods); - } - - pub fn select_word( - &mut self, - word: &String, - app: &mut AppState, - original_keyboard_mods: KeyModifier, - ) { - self.last_swiped_word = Some(word.clone()); - let text_to_paste = format!("{word} "); - - match app.hid_provider.keyboard_focus { - KeyboardFocus::PhysicalScreen => { - if let Ok(_) = self.clipboard.set_text(text_to_paste) { - Self::paste(app, original_keyboard_mods); - } - } - KeyboardFocus::WayVR => { - if let Some(wvr_server) = app.wvr_server.as_mut() { - wvr_server.set_clipboard_text(text_to_paste); - Self::paste(app, original_keyboard_mods); - } - } - } - } - - fn undo(app: &mut AppState, original_keyboard_mods: KeyModifier) { - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, true); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::Z, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); - } - - fn paste(app: &mut AppState, original_keyboard_mods: KeyModifier) { - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), CTRL); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, true); - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), VirtualKey::V, false); - app.hid_provider - .set_modifiers_routed(app.wvr_server.as_mut(), original_keyboard_mods); - } - pub fn new() -> anyhow::Result<(SwipeTypingManager, Receiver>>)> { - let (candidate_sender, candidate_receiver) = sync_channel(1); - let (task_sender, task_receiver) = channel::(); - - // Spawn persistent worker thread - let worker_candidate_sender = candidate_sender.clone(); - let worker_thread = thread::spawn(move || { - let mut swipe_engine = match SwipeOrchestrator::new() { - Ok(engine) => engine, - Err(e) => { - log::error!("Failed to initialize SwipeOrchestrator: {}", e); - return; - } - }; - - while let Ok(task) = task_receiver.recv() { - match task { - PredictionTask::Predict { swipe, last_word } => { - match swipe_engine.predict(swipe, &last_word) { - Ok(candidates) => { - let words: Vec = candidates - .into_iter() - .take(PREDICTION_SUGGESTION_COUNT) - .map(|c| c.word) - .collect(); - - let _ = worker_candidate_sender.send(Some(words)); - } - Err(e) => { - log::error!("Prediction failed: {}", e); - } - } - } - PredictionTask::Shutdown => break, - } - } - }); - - Ok(( - Self { - keyboard_gird: QwertyKeyboardGrid::new(), - current_swipe: Vec::new(), - swipe_candidate_sender: candidate_sender, - prediction_task_sender: task_sender, - worker_thread: Some(worker_thread), - swipe_start_time: None, - clipboard: Clipboard::new()?, - swipe_left_first_key: false, - first_swipe_char: char::default(), - current_swipe_device: None, - last_swiped_word: None, - }, - candidate_receiver, - )) - } - - pub fn predict(&mut self) -> anyhow::Result<()> { - if self.is_current_swipe_empty() { - bail!("nothing to predict"); - } - - let current_swipe = mem::take(&mut self.current_swipe); - let last_word = self.last_swiped_word.clone(); - self.reset_swipe(); - - self.prediction_task_sender.send(PredictionTask::Predict { - swipe: current_swipe, - last_word, - })?; - - Ok(()) - } - - pub fn reset(&mut self) { - self.reset_swipe(); - let _ = self.swipe_candidate_sender.send(None); - self.last_swiped_word = None; - } - - fn reset_swipe(&mut self) { - self.swipe_start_time = None; - self.current_swipe = Vec::new(); - self.first_swipe_char = char::default(); - self.swipe_left_first_key = false; - self.current_swipe_device = None; - } - - fn start_swipe(&mut self, key_label: char, device: usize) -> Instant { - let now = Instant::now(); - self.swipe_start_time = Some(now); - self.first_swipe_char = key_label.to_ascii_lowercase(); - self.current_swipe_device = Some(device); - now - } - - pub fn did_swipe_leave_first_key(&self) -> bool { - self.swipe_left_first_key - } - - pub fn is_current_swipe_empty(&self) -> bool { - self.current_swipe.is_empty() - } - - pub fn add_swipe(&mut self, within_key_pos_normalized: &Vec2, key_label: char, device: usize) { - if let Some(pos) = self - .keyboard_gird - .key_positions - .get(&key_label.to_ascii_lowercase()) - { - if let Some(current_device) = self.current_swipe_device { - if current_device != device { - return; - } - } - - if self.first_swipe_char != char::default() - && self.first_swipe_char != key_label.to_ascii_lowercase() - { - self.swipe_left_first_key = true; - } - - let key_pos = Vec2 { - x: pos.x as f32, - y: pos.y as f32, - }; - - let start_time = match self.swipe_start_time { - Some(time) => time, - None => self.start_swipe(key_label, device), - }; - - let within_key_pos_from_center = Vec2 { - x: within_key_pos_normalized.x - 0.5, - y: within_key_pos_normalized.y - 0.5, - }; - let key_dimensions = Vec2 { - x: QwertyKeyboardGrid::get_key_width() as f32, - y: QwertyKeyboardGrid::get_key_height() as f32, - }; - - let point = within_key_pos_from_center * key_dimensions + key_pos; - let duration = Instant::now().duration_since(start_time).mul_f32(0.8); // multiply by .8 because library is trained on mobile swipes which happen on a smaller keyboard and are faster - self.current_swipe - .push(SwipePoint::new(point.x.into(), point.y.into(), duration)) - } - } -} - -impl Drop for SwipeTypingManager { - fn drop(&mut self) { - let _ = self.prediction_task_sender.send(PredictionTask::Shutdown); - if let Some(handle) = self.worker_thread.take() { - let _ = handle.join(); - } - } -} diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index b48f8ee7..ed446d12 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -253,7 +253,6 @@ impl HidProvider for UInputProvider { log::error!("send_key: {res}"); } } - fn set_desktop_extent(&mut self, extent: Vec2) { self.desktop_extent = extent; } @@ -337,7 +336,7 @@ pub const META: KeyModifier = 0x80; #[allow(non_camel_case_types)] #[repr(u16)] -#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter, Hash)] +#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)] pub enum VirtualKey { Escape = 9, N1, // number row diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 1d4c3d5e..e3d93dac 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -215,10 +215,6 @@ impl CallbackMetadata { let mouse_pos_abs = self.get_mouse_pos_absolute()?; Some(mouse_pos_abs - transform_stack.get().abs_pos) } - pub fn get_mouse_pos_normalized(&self, transform_stack: &TransformStack) -> Option { - let pos_relative = self.get_mouse_pos_relative(transform_stack)?; - Some(pos_relative / transform_stack.parent().raw_dim) - } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 9ce45c5f..e673bda9 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -381,9 +381,6 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, - #[serde(default = "def_false")] - pub keyboard_swipe_to_type_enabled: bool, - #[serde(default)] pub chroma_key_params: ChromaKeyParams, } From 9986b71b26bd61535b7260978f68aa48f7e5ef06 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:09:26 +0900 Subject: [PATCH 028/307] skybox selector --- .../gui/tab/monado_tab_general_settings.xml | 2 +- dash-frontend/src/tab/games.rs | 12 +++++- dash-frontend/src/tab/monado.rs | 4 +- dash-frontend/src/tab/settings/mod.rs | 37 ++++++++++++++----- .../src/tab/settings/tab_features.rs | 2 +- dash-frontend/src/views/mod.rs | 4 +- dash-frontend/src/views/skymap_list.rs | 23 ++++++++---- wayvr/src/backend/openxr/helpers.rs | 4 +- wayvr/src/backend/openxr/mod.rs | 3 ++ 9 files changed, 66 insertions(+), 25 deletions(-) diff --git a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml index 3d918b82..c99cbf21 100644 --- a/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml +++ b/dash-frontend/assets/gui/tab/monado_tab_general_settings.xml @@ -29,7 +29,7 @@
-
diff --git a/dash-frontend/src/tab/games.rs b/dash-frontend/src/tab/games.rs index 02082ef7..514b7c3e 100644 --- a/dash-frontend/src/tab/games.rs +++ b/dash-frontend/src/tab/games.rs @@ -27,12 +27,20 @@ impl Tab for TabGames { TabType::Games } - fn update(&mut self, frontend: &mut Frontend, time_ms: u32, _data: &mut T) -> anyhow::Result<()> { + fn update(&mut self, frontend: &mut Frontend, time_ms: u32, data: &mut T) -> anyhow::Result<()> { + let mut config_change_kind = None; + self.view_game_list.update(&mut ViewUpdateParams { layout: &mut frontend.layout, - executor: &mut frontend.executor, + executor: &frontend.executor, + general_config: frontend.interface.general_config(data), + config_change_kind: &mut config_change_kind, })?; + if let Some(kind) = config_change_kind { + frontend.interface.config_changed(data, kind); + } + self.view_running_games_list.update(&mut frontend.layout, time_ms)?; Ok(()) } diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index 4a9d1b95..ade22f89 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -175,7 +175,9 @@ impl Tab for TabMonado { Task::GeneralSettingsChromaUpdate => { if let Subtab::GeneralSettings(tab) = &mut self.subtab { tab.chroma_update(frontend.interface.general_config(data)); - frontend.interface.config_changed(data, ConfigChangeKind::EnvironmentBlend); + frontend + .interface + .config_changed(data, ConfigChangeKind::EnvironmentBlend); } } Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness), diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index fe539b59..f6e09be8 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -19,7 +19,11 @@ use wgui::{ }, windowing::context_menu::{self, Blueprint, ContextMenu, TickResult}, }; -use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::{ConfigChangeKind, RecenterMode}}; +use wlx_common::{ + config::GeneralConfig, + config_io::ConfigRoot, + dash_interface::{ConfigChangeKind, RecenterMode}, +}; use crate::{ frontend::{Frontend, FrontendTask, FrontendTasks}, @@ -110,13 +114,21 @@ impl Tab for TabSettings { fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> { if let Some(tab) = &mut self.current_tab { + let mut config_change_kind = None; + tab.update(&mut ViewUpdateParams { layout: &mut frontend.layout, executor: &frontend.executor, + general_config: frontend.interface.general_config(data), + config_change_kind: &mut config_change_kind, })?; + + if let Some(kind) = config_change_kind { + frontend.interface.config_changed(data, kind); + } } - let mut changed = false; + let mut changed = None; for task in self.tasks.drain() { match task { Task::SetTab(tab) => { @@ -129,7 +141,7 @@ impl Tab for TabSettings { } let config = frontend.interface.general_config(data); *setting.mut_bool(config) = n; - changed = true; + changed = Some(setting.change_kind()); } Task::UpdateFloat(setting, n) => { self.tasks.push(Task::SettingUpdated(setting)); @@ -138,7 +150,7 @@ impl Tab for TabSettings { } let config = frontend.interface.general_config(data); *setting.mut_f32(config) = n; - changed = true; + changed = Some(setting.change_kind()); } Task::UpdateInt(setting, n) => { self.tasks.push(Task::SettingUpdated(setting)); @@ -147,7 +159,7 @@ impl Tab for TabSettings { } let config = frontend.interface.general_config(data); *setting.mut_i32(config) = n; - changed = true; + changed = Some(setting.change_kind()); } Task::ClearPipewireTokens => { let _ = std::fs::remove_file(ConfigRoot::Generic.get_conf_d_path().join("pw_tokens.yaml")) @@ -186,7 +198,7 @@ impl Tab for TabSettings { let config = frontend.interface.general_config(data); config.autostart_apps.remove(idx); frontend.layout.remove_widget(widget); - changed = true; + changed = Some(ConfigChangeKind::OverlayConfig); } } Task::SettingUpdated(setting) => match setting { @@ -219,12 +231,12 @@ impl Tab for TabSettings { let setting = SettingType::from_str(setting).expect("Invalid Enum string"); let config = frontend.interface.general_config(data); setting.set_enum(config, value); - changed = true; + changed = Some(ConfigChangeKind::OverlayConfig); } // Notify overlays of the change - if changed { - frontend.interface.config_changed(data, ConfigChangeKind::OverlayConfig); + if let Some(changed) = changed { + frontend.interface.config_changed(data, changed); } Ok(()) @@ -277,6 +289,13 @@ enum SettingType { } impl SettingType { + pub fn change_kind(self) -> ConfigChangeKind { + match self { + Self::UseSkybox | Self::UsePassthrough => ConfigChangeKind::EnvironmentBlend, + _ => ConfigChangeKind::OverlayConfig, + } + } + pub fn mut_bool(self, config: &mut GeneralConfig) -> &mut bool { match self { Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x, diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 2269069e..992bf06c 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -1,6 +1,6 @@ use crate::tab::settings::{ - macros::{options_category, options_checkbox, options_slider_f32}, SettingType, SettingsMountParams, SettingsTab, + macros::{options_category, options_checkbox, options_slider_f32}, }; pub struct State {} diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs index 91cdee11..918c67fc 100644 --- a/dash-frontend/src/views/mod.rs +++ b/dash-frontend/src/views/mod.rs @@ -1,4 +1,4 @@ -use wlx_common::async_executor::AsyncExecutor; +use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind}; pub mod app_launcher; pub mod audio_settings; @@ -16,6 +16,8 @@ pub mod skymap_list_cell; pub struct ViewUpdateParams<'a> { pub layout: &'a mut wgui::layout::Layout, pub executor: &'a AsyncExecutor, + pub general_config: &'a mut GeneralConfig, + pub config_change_kind: &'a mut Option, } pub trait ViewTrait { diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs index 66a58635..5bedf5ea 100644 --- a/dash-frontend/src/views/skymap_list.rs +++ b/dash-frontend/src/views/skymap_list.rs @@ -9,7 +9,7 @@ use wgui::{ renderer_vk::text::custom_glyph::CustomGlyphData, task::Tasks, }; -use wlx_common::{async_executor::AsyncExecutor, config_io}; +use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, config_io, dash_interface::ConfigChangeKind}; use crate::{ frontend::{FrontendTask, FrontendTasks}, @@ -75,7 +75,7 @@ impl ViewTrait for View { self.show_skymap_resolution_selector(entry); } Task::SetSkymap(entry, resolution) => { - self.set_skymap(entry, resolution)?; + self.set_skymap(entry, resolution, par.general_config, par.config_change_kind)?; } } } @@ -132,16 +132,23 @@ impl View { Ok(()) } - fn set_skymap(&mut self, entry: SkymapCatalogEntry, resolution: SkymapResolution) -> anyhow::Result<()> { + fn set_skymap( + &mut self, + entry: SkymapCatalogEntry, + resolution: SkymapResolution, + config: &mut GeneralConfig, + change_kind: &mut Option, + ) -> anyhow::Result<()> { let skymap_file_path = entry .get_destination_path(resolution) .context("Skymap not found" /* you shouldn't really see this, like ever. */)?; - log::error!( - "not implemented (skymap path to be loaded: {} with resolution {:?})", - skymap_file_path.to_string_lossy(), - resolution - ); + config.skybox_texture = config_io::get_skymaps_root() + .join(skymap_file_path) + .to_str() + .context("Skymap filename not valid UTF-8")? + .into(); + *change_kind = Some(ConfigChangeKind::EnvironmentBlend); Ok(()) } diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index 5f85a094..0f9b352f 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -214,6 +214,7 @@ pub(super) fn posef_to_transform(pose: &xr::Posef) -> Affine3A { pub(super) fn try_apply_chroma_key(app: &AppState) -> bool { let Some(monado) = app.monado_state.as_ref() else { + log::warn!("Could not set Chroma Key: no monado_state"); return false; }; @@ -230,6 +231,5 @@ pub(super) fn try_apply_chroma_key(app: &AppState) -> bool { return false; } - // if all values were non-0, assume we're in chroma key mode - params.hsv_max[0] * params.hsv_max[1] * params.hsv_max[2] * params.curve > 0.001 + params.curve > 0.001 } diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 8e3787ed..f6ec7a44 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -569,6 +569,9 @@ fn reconfigure_environment_blend( log::debug!("Allocating skybox."); *skybox = create_skybox(xr_state, app); } + } else { + log::debug!("Allocating skybox."); + *skybox = create_skybox(xr_state, app); } } else { log::debug!("Destroying skybox."); From 07bd841bc669a7010b2f8945dbfb3d1ef5466c02 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Wed, 22 Apr 2026 19:03:47 +0200 Subject: [PATCH 029/307] dash-frontend: skymap browser ui improvements, wayvr: fix context menus not working --- .../gui/view/remote_skymap_downloader.xml | 20 +++++++++---------- .../src/tab/settings/tab_look_and_feel.rs | 2 +- dash-frontend/src/tab/settings/tab_skybox.rs | 2 +- wgui/src/windowing/context_menu.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dash-frontend/assets/gui/view/remote_skymap_downloader.xml b/dash-frontend/assets/gui/view/remote_skymap_downloader.xml index 12dfbadc..c6e386ae 100644 --- a/dash-frontend/assets/gui/view/remote_skymap_downloader.xml +++ b/dash-frontend/assets/gui/view/remote_skymap_downloader.xml @@ -16,20 +16,20 @@
-
diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs index f2e3ac66..601eb840 100644 --- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs +++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs @@ -17,13 +17,13 @@ impl State { )?; options_dropdown::(par.mp, c, &SettingType::Language)?; options_checkbox(par.mp, c, SettingType::HideUsername)?; + options_checkbox(par.mp, c, SettingType::OpaqueBackground)?; options_checkbox(par.mp, c, SettingType::HideGrabHelp)?; options_slider_f32(par.mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step options_slider_f32(par.mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step options_slider_f32(par.mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1)?; options_checkbox(par.mp, c, SettingType::SetsOnWatch)?; options_slider_f32(par.mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; // min, max, step - options_checkbox(par.mp, c, SettingType::UsePassthrough)?; options_checkbox(par.mp, c, SettingType::Clock12h)?; Ok(State {}) } diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs index f516a2d0..dfe4b3e6 100644 --- a/dash-frontend/src/tab/settings/tab_skybox.rs +++ b/dash-frontend/src/tab/settings/tab_skybox.rs @@ -39,7 +39,7 @@ impl State { pub fn mount(par: SettingsMountParams) -> anyhow::Result { let id_category = options_category(par.mp, par.id_parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?; options_checkbox(par.mp, id_category, SettingType::UseSkybox)?; - options_checkbox(par.mp, id_category, SettingType::OpaqueBackground)?; + options_checkbox(par.mp, id_category, SettingType::UsePassthrough)?; let tasks = Tasks::::new(); diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs index e9139015..ef0c0bf9 100644 --- a/wgui/src/windowing/context_menu.rs +++ b/wgui/src/windowing/context_menu.rs @@ -8,7 +8,7 @@ use crate::{ globals::WguiGlobals, i18n::Translation, layout::Layout, - parser::{self, Fetchable, ParserState}, + parser::{self, Fetchable, ParserData, ParserState}, task::Tasks, windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra}, }; @@ -117,7 +117,7 @@ impl ContextMenu { par.insert(Rc::from("tooltip_str"), tooltip.generate(&mut globals.i18n())); } - let mut data_cell = inner_parser.realize_template(&doc_params, "Cell", layout, id_buttons, par)?; + let mut data_cell = inner_parser.parse_template_only(&doc_params, "Cell", layout, id_buttons, par)?; let button = data_cell.fetch_component_as::("button")?; let button_id = button.base().get_id(); From f3732c7087eafb0ce94f6167c3330217c3d5f189 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Wed, 22 Apr 2026 20:22:45 +0200 Subject: [PATCH 030/307] wayvr: improve grid shader, dash-frontend: make pop-up title non-scrollable --- dash-frontend/assets/gui/dashboard.xml | 3 +- .../assets/gui/view/popup_window.xml | 15 ++++--- dash-frontend/src/tab/settings/mod.rs | 1 - wayvr/src/shaders/grid.frag | 43 +++++++++++++------ wgui/src/windowing/context_menu.rs | 2 +- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/dash-frontend/assets/gui/dashboard.xml b/dash-frontend/assets/gui/dashboard.xml index 7d5a9a4c..8f2b05f4 100644 --- a/dash-frontend/assets/gui/dashboard.xml +++ b/dash-frontend/assets/gui/dashboard.xml @@ -86,7 +86,8 @@ round="8" flex_grow="1" width="100%" - overflow_y="scroll" + overflow="scroll" + position="relative" > - - + position="relative" + > +
+ +
-
\ No newline at end of file + diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index f6e09be8..5477078e 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -475,7 +475,6 @@ impl SettingType { | Self::UiGradientIntensity | Self::UprightScreenFix | Self::DoubleCursorFix - | Self::ScreenRenderDown | Self::Language | Self::CaptureMethod ) diff --git a/wayvr/src/shaders/grid.frag b/wayvr/src/shaders/grid.frag index 467aec41..d9016ae1 100644 --- a/wayvr/src/shaders/grid.frag +++ b/wayvr/src/shaders/grid.frag @@ -1,18 +1,37 @@ #version 310 es precision highp float; -layout (location = 0) in vec2 in_uv; -layout (location = 0) out vec4 out_color; +layout(location = 0) in vec2 in_uv; +layout(location = 0) out vec4 out_color; -void main() -{ - float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0); - float grid; +const float circle_smoothness = 0.0025; +const float circle_thickness = 0.01; +const float circle_opacity = 0.3; +const float circle_size = 0.1; - if (fract(in_uv.x / 0.0005) < 0.01 || fract(in_uv.y / 0.0005) < 0.01) { - grid = 1.0; - } else { - grid = 0.0; - } - out_color = vec4(1.0, 1.0, 1.0, grid * fade); +float calc_grid(vec2 coord, float m) { + vec2 grid = fract(coord * m); + return (step(m, grid.x) * step(m, grid.y)); +} + +float calc_circle(float dist, float size) { + float c1 = size; + float c2 = size - circle_thickness; + + return smoothstep(c1, c1 - circle_smoothness, dist) * + smoothstep(c2 - circle_smoothness, c2, dist); +} + +void main() { + float dist = length(in_uv.xy + vec2(-0.5, -0.5)); + float fade = max(1.0 - 2.0 * dist, 0.0); + + float mask = 1.0 - calc_grid(in_uv.xy * 1000.0, 0.02); + + mask = max(mask, (calc_circle(dist, circle_size) + + calc_circle(dist, circle_size * 2.0) + + calc_circle(dist, circle_size * 3.0)) * + circle_opacity); + + out_color = vec4(1.0, 1.0, 1.0, mask * fade); } diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs index ef0c0bf9..16e62d01 100644 --- a/wgui/src/windowing/context_menu.rs +++ b/wgui/src/windowing/context_menu.rs @@ -8,7 +8,7 @@ use crate::{ globals::WguiGlobals, i18n::Translation, layout::Layout, - parser::{self, Fetchable, ParserData, ParserState}, + parser::{self, Fetchable, ParserState}, task::Tasks, windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra}, }; From 6cddb43b0fcb9f5fce32f21c8b682f4af673f15c Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:21:25 +0900 Subject: [PATCH 031/307] default skybox is shader skybox --- wayvr/src/backend/openxr/skybox.rs | 74 +++++++++++++++++++----------- wayvr/src/graphics/mod.rs | 5 +- wayvr/src/shaders/mod.rs | 7 +++ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index 1ca0c072..2d0215e2 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -24,7 +24,7 @@ use super::{ }; pub(super) struct Skybox { - view: Arc, + view: Option>, sky: Option, grid: Option, grid_pose: xr::Posef, @@ -73,14 +73,12 @@ impl Skybox { "".into() }; - if maybe_image.is_none() { - let p = include_bytes!("../../res/table_mountain_2.dds"); - maybe_image = Some(command_buffer.upload_image_dds(p.as_slice())?); - } - - command_buffer.build_and_execute_now()?; - - let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap + let view = if let Some(image) = maybe_image { + command_buffer.build_and_execute_now()?; + Some(ImageView::new_default(image)?) + } else { + None + }; let grid_color_scale_bias_khr = xr .instance @@ -125,7 +123,12 @@ impl Skybox { } let opts = SwapchainOpts::new().immutable(); - let extent = self.view.extent_u32arr(); + let extent = self + .view + .as_ref() + .map(|v| v.extent_u32arr()) + .unwrap_or([4096, 4096]); + let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, 1, opts)?; let tgt = swapchain .acquire_wait_image()? @@ -133,23 +136,42 @@ impl Skybox { .into_iter() .next() .unwrap(); - let pipeline = app.gfx.create_pipeline( - app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic - app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic - WPipelineCreateInfo::new(app.gfx.surface_format), - )?; - let set0 = pipeline.uniform_sampler(0, self.view.clone(), app.gfx.texture_filter)?; - let set1 = pipeline.uniform_buffer_upload(1, vec![1f32])?; - let pass = pipeline.create_pass( - tgt.extent_f32(), - [0.0, 0.0], - app.gfx_extras.quad_verts.clone(), - 0..4, - 0..1, - vec![set0, set1], - &Default::default(), - )?; + let pass = if let Some(view) = self.view.as_ref() { + let pipeline = app.gfx.create_pipeline( + app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic + app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic + WPipelineCreateInfo::new(app.gfx.surface_format), + )?; + + let set0 = pipeline.uniform_sampler(0, view.clone(), app.gfx.texture_filter)?; + let set1 = pipeline.uniform_buffer_upload(1, vec![1f32])?; + pipeline.create_pass( + tgt.extent_f32(), + [0.0, 0.0], + app.gfx_extras.quad_verts.clone(), + 0..4, + 0..1, + vec![set0, set1], + &Default::default(), + )? + } else { + let pipeline = app.gfx.create_pipeline( + app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic + app.gfx_extras.shaders.get("frag_sky").unwrap(), // want panic + WPipelineCreateInfo::new(app.gfx.surface_format), + )?; + + pipeline.create_pass( + tgt.extent_f32(), + [0.0, 0.0], + app.gfx_extras.quad_verts.clone(), + 0..4, + 0..1, + vec![], + &Default::default(), + )? + }; let mut cmd_buffer = app .gfx diff --git a/wayvr/src/graphics/mod.rs b/wayvr/src/graphics/mod.rs index 52ffa913..5c1573ba 100644 --- a/wayvr/src/graphics/mod.rs +++ b/wayvr/src/graphics/mod.rs @@ -21,7 +21,7 @@ use wgui::gfx::WGfx; use vulkano::instance::InstanceCreateFlags; use wlx_capture::DrmFormat; -use crate::shaders::{frag_color, frag_grid, frag_screen, frag_srgb, vert_quad}; +use crate::shaders::{frag_color, frag_grid, frag_screen, frag_sky, frag_srgb, vert_quad}; #[cfg(feature = "openxr")] use {ash::vk, std::os::raw::c_void}; @@ -74,6 +74,9 @@ impl WGfxExtras { let shader = frag_srgb::load(gfx.device.clone())?; shaders.insert("frag_srgb", shader); + let shader = frag_sky::load(gfx.device.clone())?; + shaders.insert("frag_sky", shader); + let shader = frag_grid::load(gfx.device.clone())?; shaders.insert("frag_grid", shader); diff --git a/wayvr/src/shaders/mod.rs b/wayvr/src/shaders/mod.rs index 86e9f5c4..d7c986a3 100644 --- a/wayvr/src/shaders/mod.rs +++ b/wayvr/src/shaders/mod.rs @@ -32,3 +32,10 @@ pub mod frag_srgb { path: "src/shaders/srgb.frag", } } + +pub mod frag_sky { + vulkano_shaders::shader! { + ty: "fragment", + path: "src/shaders/sky.frag", + } +} From 90c45675050e6cf2cce18e9c7e1610a372b5da2c Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:25:35 +0900 Subject: [PATCH 032/307] shuffle some options around --- dash-frontend/src/tab/settings/tab_look_and_feel.rs | 3 +-- dash-frontend/src/tab/settings/tab_skybox.rs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs index 601eb840..c632e667 100644 --- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs +++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs @@ -16,14 +16,13 @@ impl State { "dashboard/palette.svg", )?; options_dropdown::(par.mp, c, &SettingType::Language)?; - options_checkbox(par.mp, c, SettingType::HideUsername)?; options_checkbox(par.mp, c, SettingType::OpaqueBackground)?; + options_checkbox(par.mp, c, SettingType::HideUsername)?; options_checkbox(par.mp, c, SettingType::HideGrabHelp)?; options_slider_f32(par.mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step options_slider_f32(par.mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step options_slider_f32(par.mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1)?; options_checkbox(par.mp, c, SettingType::SetsOnWatch)?; - options_slider_f32(par.mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; // min, max, step options_checkbox(par.mp, c, SettingType::Clock12h)?; Ok(State {}) } diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs index dfe4b3e6..66c2edf8 100644 --- a/dash-frontend/src/tab/settings/tab_skybox.rs +++ b/dash-frontend/src/tab/settings/tab_skybox.rs @@ -4,7 +4,7 @@ use crate::{ frontend::FrontendTasks, tab::settings::{ SettingType, SettingsMountParams, SettingsTab, - macros::{options_category, options_checkbox}, + macros::{options_category, options_checkbox, options_slider_f32}, }, util::{popup_manager::PopupHolder, wgui_simple}, views::{self, ViewUpdateParams, skymap_list}, @@ -40,6 +40,7 @@ impl State { let id_category = options_category(par.mp, par.id_parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?; options_checkbox(par.mp, id_category, SettingType::UseSkybox)?; options_checkbox(par.mp, id_category, SettingType::UsePassthrough)?; + options_slider_f32(par.mp, id_category, SettingType::GridOpacity, 0.0, 1.0, 0.05)?; let tasks = Tasks::::new(); From bf7276b2dd7da7332ebb4fbb7b2f9bcb17373b2b Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:36:25 +0900 Subject: [PATCH 033/307] skybox downloader: apply button for downloaded skymaps --- dash-frontend/assets/lang/en.json | 1 + .../src/views/remote_skymap_downloader.rs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 30436cc1..7b1b1626 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -172,6 +172,7 @@ "PROCESS_LIST": "Process list", "REFRESH": "Refresh", "REMOVE": "Remove", + "APPLY": "Apply", "RELOAD_FROM_DISK": "Reload from disk", "SETTINGS": "Settings", "SHOW": "Show", diff --git a/dash-frontend/src/views/remote_skymap_downloader.rs b/dash-frontend/src/views/remote_skymap_downloader.rs index 84e03350..3b78a384 100644 --- a/dash-frontend/src/views/remote_skymap_downloader.rs +++ b/dash-frontend/src/views/remote_skymap_downloader.rs @@ -8,6 +8,7 @@ use crate::{ }, views::{self, ViewTrait, ViewUpdateParams}, }; +use anyhow::Context; use wgui::{ assets::AssetPath, components::button::ComponentButton, @@ -20,7 +21,7 @@ use wgui::{ task::Tasks, widget::{image::WidgetImage, label::WidgetLabel}, }; -use wlx_common::{async_executor::AsyncExecutor, config_io}; +use wlx_common::{async_executor::AsyncExecutor, config_io, dash_interface::ConfigChangeKind}; pub struct Params<'a> { pub globals: &'a WguiGlobals, @@ -41,6 +42,7 @@ enum Task { DownloadFinished, RunDownload(SkymapResolution), RemoveFile(SkymapResolution), + SetSkymap(SkymapResolution), } pub struct View { @@ -109,6 +111,19 @@ impl ViewTrait for View { Task::RemoveFile(resolution) => { self.remove_file(resolution)?; } + Task::SetSkymap(resolution) => { + let skymap_file_path = self + .entry + .get_destination_path(resolution) + .context("Skymap not found" /* you shouldn't really see this, like ever. */)?; + + par.general_config.skybox_texture = config_io::get_skymaps_root() + .join(skymap_file_path) + .to_str() + .context("Skymap filename not valid UTF-8")? + .into(); + *par.config_change_kind = Some(ConfigChangeKind::EnvironmentBlend); + } } } @@ -237,6 +252,7 @@ impl View { fn show_dialog_box_action(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> { const ACTION_REMOVE: &'static str = "remove"; const ACTION_DOWNLOAD_AGAIN: &'static str = "download_again"; + const ACTION_APPLY: &'static str = "apply"; let tasks = self.tasks.clone(); @@ -257,6 +273,11 @@ impl View { icon: "dashboard/download.svg", action: ACTION_DOWNLOAD_AGAIN, }, + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("APPLY"), + icon: "dashboard/check.svg", + action: ACTION_APPLY, + }, ], on_action_click: Box::new(move |action| match action { ACTION_REMOVE => { @@ -267,6 +288,10 @@ impl View { tasks.push(Task::RunDownload(resolution)); tasks.push(Task::Refresh); } + ACTION_APPLY => { + tasks.push(Task::SetSkymap(resolution)); + tasks.push(Task::Refresh); + } _ => unreachable!(), }), }, From dd3108d42350d59cd61c6177f48d8bd473260a6b Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:10:43 +0900 Subject: [PATCH 034/307] new built-in sky shader --- .../dashboard/builtin-skybox-preview.jpg | Bin 0 -> 9994 bytes .../src/views/remote_skymap_downloader.rs | 3 +- dash-frontend/src/views/remote_skymap_list.rs | 2 +- dash-frontend/src/views/skymap_list.rs | 29 +- dash-frontend/src/views/skymap_list_cell.rs | 19 +- wayvr/src/shaders/sky.frag | 368 ++++++++++++++++++ 6 files changed, 404 insertions(+), 17 deletions(-) create mode 100644 dash-frontend/assets/dashboard/builtin-skybox-preview.jpg create mode 100644 wayvr/src/shaders/sky.frag diff --git a/dash-frontend/assets/dashboard/builtin-skybox-preview.jpg b/dash-frontend/assets/dashboard/builtin-skybox-preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ca3e583b599b1227a967ea8b8c42a02fb873c43 GIT binary patch literal 9994 zcmb7pcR*816K{Y3p@$Bk_ZE7Q4xt2T(tDB6MO2zJQ7NI9&_l0+G^KeF5S88nQbYko zq>G5s5f$W}aJ~1t@BR0d!`X9YXJ%(-XMa0;az1yy1cDkM^^qU~puh>>Akg`HkT!?} zOiV%yCLtjvAtfb&kVDDI$;ikVsA(vn%nU3n%nVFSFb)AO7#klu6B9R_n@>RAxfRvP!970Y{PEIcjV}c3)f0y%K5G{lNL+h>{4<1SNukKm>dIn9ANbGJP_AEtVP( z$Oznh!B0S`*{|(SMoUI3szsZ?lGlGh?U#uJxlqmJM8a}g^RFRYPjCipGH;FHWVEzs zePL~YN1Krf1j^${j|2&`LOei3AfS5>P``psR*Qr=_t%mtna&Wo=T~qxnzN7)a(&u`=>(oPIxebF7(dE;J^W{3d$WUY!fj7$|R{RrEtC^^jn-pjfC>2Z)7&=0z8 zI)yF7nW7TG7GhN%Bv`wL6*!ssI$TlftXNU&8~#_mDYqO_+)6shJHC{$k9JNzwp89z zY}(dZR8@gG>nQI!n{NrMzsL)iHDj-uNjjj;kdzK83w(Qmi@lMwB3~N-%gCE%Ek2M{ zGjHXadoA`stVqGPLuvZ+l~2W9$o`FD&5ilaYjNWZL*kh!fp7TWm-@t8>h5_`r}v7Y zh~0$K(nqKU0#~Q>$DHIHXQ#ZRK9h>yW(-Ix3n-sU9Bk|$Uzz<7>qkA=CI8N9(5a@8 z^R9l;$ZZeVvAfoR52wQ>l)q+5;~Y|2C)Z#7MPl>7p5vQvh)kkA<7{<@cWyq?!qh`< z*2C3Wf7&|HGvITWUDbwXG*-My__=xAktQ}fb^zY2Bfta|9JN~3>T1|;7Yn8(?2eM> zcSw@4$ZdK17t>!g?e@v}2<7bz0wK$KN11U+yQ)uA)=PXfKajWV0{XXJSXNdQJTfa# zi6LuyBrDQqsztE)M9s%>Ld)I*1r?;tq;vHbBGW-9f0}66VoxZx{{em7Ol|NuC)Bxm zHc7vE^?LJaMx7mdkoX4ctZ|@r6{VE@gNL7Kxs%d$GI^`(_@nsd8og^{7?+nXqq*~Ss=fG(Q_(^__DDmg`I8acGbOHlL@Y7?6};} zl!RlnHsUtUsedTw<~Sx~Ft%vf+2U-m)xNG?ndb00UivV9Oc^z%?7QCAXxUy>;^4SR zul-6QOX^w}|>WKm*0D2;4n(~UDNp>E=I9H>~Y5( z8v22EuE-)9-jzoos;f7` z^OGw*+$$n;44yjLez`3}fU-4;G}fgzvyunpgN?0|4a~xd1X^*RqM&?_He!z`4K-iChO_(c{*A^6<Vl zp{l4=1GjlxSg~Er7|dwRTeeciJGGN?_%-{koF3f9F+gChx#k00q76rx71V4jk7G~G z9C0tOk_LRUu2ugwsFTTMazE~2PC;&vx7}2QQiX5m{G&B0PtzH9Du!SU;&azk=9=A&5SAT5VggWS!TswWSUKM;H8TKnc+3T(42F5LcP=cUE! zQka5xi{`VR-x&kNsGAl_!;;%L_&=C)!6kqXSJ1 z;-VB*D0#3k!ccKQC!yB80-k^_B6MfH%a41j)Gy{|mDoD`RiPME=nhs~SaQDL#5_x%KsH>OwKltV| zTJR>)^6C&8@D3-^`H8w~>F8w8x-an$1Ks)X09qd02cTUD3g9<@t%M~1f4$+3yz2~|j z|HBF>mOKzGjUhB}uLDZk(CgHDc&eB6Z8M+-1n^Bz%q2jnbi>am5%1IveIJ2ldTxM^e$Z#1vx<3-+X|SPl8AM744N%X(w^Jvrdhzjkk7 zS!{;lkHlAk_Byw|)VFagu1b8H3keCnlhV5J%8A)4IM~3vH0^$(fqC}Ri7Cv`hn(E& z>#sPzhs?j1nDBpgOE93t;&@%7_2sGC-tXgc&|YJV{4$oZ-D60?E&x~Jxc@0frrL6} z!$x!+qPK&eTcG0_D=TN?d#S;1+uq!;exs}&Kbgun<-{;$?06LL24}vke8w7euFpzz1moJAT6VAz&(=Rm`eWO13m)GBzQe?=2o0Esho2mKv7wZlZqGWo|E|99 zkm+ecgveY@9KS)JDT3^hOhO=hA7aPxe~{6|kUr0o$r zyjr!><1Eap@3GzC*iW0bJ6>(L_v?LL97SywqU&7u@1G2wgTx*KcK&_gtu+S%#f!}Y zBj`6&Gmo(xs=M>Fvu|Pfqm2{ul(JZ5NgB%?&%0vk`K3k^4{_?0?dubw9;_!J=b)5( z8o;Fg&aU!k`HXdJ{xpxV$W^npJu(WfRJA_T1gLrGTuyEf?y>E`I$q9#vle3&2UHQt*Rfb-uK`Ryp6P-!=VCp$1Me26e@GW} z{0%3Gb$qt|w02x*{SUkKzl#U|kaD4=p#>*^UlSLz18*5I^gWZ^$B*12^FsNBRy@0> z!v7`x&oZ=gZ<~0nAqMEMw!?SxaC;OGPQ9?pBNI@Q`#-yGlx9Etfj7`W%v>-x4LJ%<+NSXf>S^_#kdMG!Klo^qLZF%u>fsfOQ~FeTH0@ zHv_XGB8H7_u{M}s5w+AF-nt);AVT>E5MroF9YLpM}FQFxvGN+N|Cd#z4 z!)ssmxZfn!(;LBzNn#Kc#&p+)iv(5b3?=|QI=ofxDTV;Az2Qihq`=y|;5^@K)=k-O z_mt$_)CXe_>X~O0o0~z(p{&=odnWx9cQwk-Xg2R_v#bRuv8U+?tYK{ULNZVEPey-w zc2P;MO?qcpBRj-+SGhPwt|_?R9R4V{dQ+e&vt7TQvlm!E6z#D%N<1_$;#X4*P>WjD znpXfXw1l6W0FqPHsm8xvB_se75dXhkC6dxKGv(!zM*QtnLcCW`3884obTm&8UNuCE znW7V@Df0Pv2a;*(E=a)bwj0Yx$r#|w3m>CW+*kp>N~gH7;FzMPM)6!uwu9G1BWwF%Vj24Ku%!op;kw)p7%(Wt=yY>PuR4FB zjKbQDEdTa}o0r(iK7hUGG_K`Jk_;;#0@iU@31J3u@*Z0;VwcAP(()@NRm6fCiJX2n z@-7u7zuZ0t>GBRYWQ!2HT-a|%!3#|Bh@FT4IcyZRG)v6gzz}^`SniDB70rTpeygl+ zVd~_K{(0gu|9#W42e75rBnfh`7O^54MTPZ)Da9dA{HO+{_kl3O*D3j2H1W7&;0Y5a z{hJSPuQ(LQ4ZuXCU?55OQwRuoq-8P?{^d|wSxqfd7j9muD86MnGrvDs0*MCU&X*;M zfBw>o&*sTKe)w`F{_+WI&-t6SXbxT%k+ z{6aXE4c%PxDx=Bcj{0NW`mHLHz=BebVC{ygS;g=og>1R<2Ze6c{e`Y^*=+;j&4!n6 z8l@*SN^GOoT4du8 zP5&)&Xjx@A{ESDbmSH=e>Z3c;hp2fnqUyEx0o4n&Perde7`(HNz2^Fy{7`hV*4Oy0 za-#COSU^q(vI=54ott?hQDKlt^lA)MUf|8)D-&vYEOaJ1lYQCwUs zyqxTZ+I=3mwxPbNI`~|#6bsSv1=H$+p$M?*9W`de>U?NUoHPB$b`X9ULi5H@5F-kg zDcdL3@&J?GC+R=G5^ivXge|Ycw|sB4!h>;m@Len> z9BGS+Z|_DhMz#^?dNUFG22fC;xSLedk9Bwy;&6Sa<(A{$st)j`zG-g^!OBnnn(|3+ zS2?gX;bPChForlfyXqbKV_pxDIY}N|>X3s)S;{-1fuD(PF%kd_=25j=xo5o1GtV}S z^pfwWI!K{~mMj+2&?rum+p@C*@tL>j6;WSf=xDVUyx zaVYG1_oOqaG{=J%7V5cTeeDHegI+XFZ4RTV!9&_{)GOm(MQDEN9Q1~ zv(fTbbxuM4uxqKZGmj=x8O#j&=lyhrONJmJuBvWzbnT7LVXvgXBxy8%!P+Gu`(73C zu)CvSUk8&^RYN4tKr=L*6J_J~TD{udHB^kk5w$;ME{}wXD{;qJ@hJ0b-o434Ov7hhAI6T+Q0jBUoZaUzO&(1^(uLdCujO)mqpG&(v3d} z72j*UT|bI6Rl%%)%B~r3k37a zykW?wva7sFf(!USxri40%@^2-fqdSB!SvPc@({IW;mCK{Ezg+!5c>RriJsc+>vv!@ zB!vvd{tfEYca3~UU_JS)W?59Z4hhETXhCv%vi@H2-4GPDUDli4)?<_BCLc<}PGL94 zWA8Pt{nE(V{B3-XVKNNH1;3QCpqJPpZ*1ZCMt(fda#nuak;CZ`kF>N&^7AXa#qlNa z9MWY&?%X`-KadF>+9olw&XZ;XPI;H!y00xqoO(^hPgiVQy><>FRJhB)^_{i3GN!EZ zBv`@U%fEGkb5t0gpNxtTbJ|KKJW9Vf^uSwy^9@>Gq?jBMC^9r#n|T!_n_OAU9JsB= zPbr+;A*Uqhp=%U*UASd%71gXOah%<5cG}0SChPr^;pJYb&$8L?Pap1+ZgTbfoZY>* zb}Suz7K=N-K;9bO))QmIVC~DngkrW%7Sf6%jm(Z=Xq6 zkKE&Iy0548kB*Saz5iuo_@_R{{?q=H{;vK5+A&WWMX}Q1kC#yXJ^is?e#adievBl+ zy^C_CwY^RSmRtr!I)6IzYyvvX=Ln>j-c*}7!=|C)IER^dh$EYrdfu`vo`l7BftVt^ zpYwdX3h&aUlsdlGyvZHiZ7UQRLqo8nmM$KN+5r)iLj2-5SJ$x^k0t!@44bTBeX3wi zp0AwI^D2w|L_O@nuGj#A|Cnk<+C~U)1{432{AS;WmR9`xQq}usgu?m%5&Fk6tQhAIipIg{7q7q-NT7&yK8NAb>QKY-aHbrL}Ni_f_DO#uY8 zB({m{fGzHIuF}q?`xD>WvvIYaf0ibb8FG5?Gd(VlYhjNLG{KPD{ENU3fo2bO z;Cqeui}BhK#Os-0wfQRsP!yuV#;r(HqEs~SEO~$8y}ka!qtPL``mB0o8uq(OynIES z%dhma1$G4ByB2=Gg->I8EZd}HFq5@AaP>9~edXkXT<}`<8 z@`xn6`wkO)B0(Z4iYv9M zWiQN^wA0F`J*W4e&+kv9BjmnOC_B#)U5=>>;z%Rkal7mFd?tM_M0Lu>vFhtbFUR!T zQ!xWXN)+GuUN{VLYX#sIs3FgUSX@sPBPF(r{mm!MZJy%@{G%RJm(oY58fh6)E>GP+ zGro|h`a;vqFhH}{e)q$~F}q3RX7S-#OsX&EPMQxrS7`$G^`0_sAzdj}dPJ@mm{I2^ z5xYwVeO(uUQ{t_3qlrK&qdK$&Hfb4&S8H81aK z{_m&&z4h?|7E3yX@(z{}pU6f_7`dFJ?K;~HJ2ha7 zZI+&ipHWKM9GNfWTeGpqBCeT_E0}#(MK7l(ysS>W`9h`Q=i|-CDwRv$;;ah3#m!oz zuPLQz7kyHC$U&9762GjbY*rvo!0X*c}$hUKaS9Sd?B|9lh| zeK@`>^ZYmIkLn6wD+A;0h&hbD*wtL3?XqP4x4->)>Lv5Uf2QNZM+9)fAp(B1`6msh zg#r;_1|)6%n&rF87pWTlhy%Th{_V*9Hu`n`5@`B%M0NyZTy{(P*a16nWB3wSH)m6^_7Poir}ZzC|};B8-LlLUsNCi z&&Gc$V`Z(q+AqOycc^(=e?dGL=*U)cLA|mM*5-aJ;>!v(oqZYrX)p z%P=ZO%u{e_)~?MT&tD19;5)73L0E(JLM83pBdSy7-=09uXKXA=93Cy7g6hsJ|rAYqsGaCV|8444rKRCvd0mk?4W%F{p#}`7`zTmw^9^Yo&xS1jL-jxil9gOTAz9w3itD@HFy8KMI1`2T zrh1HzYlGCui8STkGTxXTd|nUb=Wd!pkS)!L=i)pvd%V}(wMWlEe0r0195GET_V8Ro z;^rB)Ne_}kju;v)Wg5=GK2NHbafFcW43>lj6r)Q68zP$?spUgm?%iZ7&SWT1F+wAB zN+qio-QyBW-83>kNe$Z-v;5L;69jPs2?P;m-sxVUw{CQ$t$BBk*$y zqNWx*j*2JmRP@4ahkqs3T2~Yo0rNKgaE~BYqre78x*|j!{#Wn z^a3(rac_b0C+}`BM)z8~SVPK>#aZVU!4>BG5V8uhVKjdNts8CGbc{W1SuR>wz8pp+ zELTnr1t<^9s4%<%gXm1@Kxw6b? z>u58cCPy+B!OM9~`sqm3rXs52Ls#>zKB4NBV5c#KPYPEnG|E-GXD1D0*LntV($QK7 zX=pLo)=kENKEItyZ1MhGHmem`jC{QPmj$44S+M2HaBvfX(n#I$t+-Q*Y7XPeCi|-a ztIpRx&PfX2q2if8p?$Sc%r0SlYhle3J9n$i>(wu+7JY%z>rTSPx4k?1q15$h$ig+~(+SHDBZr(|zPvT8`JP{l z8K0RYi@1W|Uq;anQTL1~kGMz$>xI$ZN;gfsL{v0C4cCo4UcnTwQRd5`3xkyklO9cf z6WTs}dX47N_pY{htntS=@2BK)F^i$ml%0>*OBd`tcck9zeLLWtz3PPi8sWcm4)WMe z*%!OMFJv8*=r~h)TUKH-B$4gPzVsUoRBT?~*v%tWvkuY3UHZg+dqMgH?xJG-nPR<9 z=b$X+rd@s>B3tgL3JCXxpcb;q^r&{Xt?X!IcQ2H_tH_u8s<51HnKw1blTC; zZJx?UInTUSG_z(jb4zebu%@IUJhRsC=8=F|GhZlo3IzyFMn(qF5{lxwfeen1!XpzO z9faFE8q#Yg@u0nizvC93Ro>rcdzqLi>zR+cN!*m3(vf#im|w|L9hcAxPGGqrSoRLRX%G`mU#?77 zR$R6i)5$=@nHj;W!i9T5KQ!C%0?3ZZv4^IyfUx-gl zu>v=vuYNpZP+6!jkI%FEWoysW^_N>mLB1+;>}DM}RSQ|M+a)0#eN1=2h>$cRpOM!Y zdpDoHw=J}`FHdPa@>JvpU9b@66~`*k?m|{VdGD-=IV;acu@8zivlDB>t*}Lq{rdiN zuMf8lO~3u5T7-4=2pYeCr~mL{z=M$EL_6L2<>m;V*X#UO3Ol)4*ZF8fBDhQiT=fEw zE4vY&Rb-KOnma_7oHiD0C}ci_73II8#0< z7x;ocpkpW^voV&D5VoP|9_)rK_7z%NZ~3~(lWn!pm74$5>V!R6=IOD(hLig#{R26Q zdCi#@4dh0NoJM1L<9_Olt8)7uz@!e67K zzh5(Zwf8NYX_-lYmKwyM%$-q`ea$ADmBg4_)MkBmyccS&ul&Xr7n|Kf-ywh zi&rXrQtK3IMbpatZs&cWZrY-5-%4u^!%~cOU8xMmCR*Mw*NrqXN-uUFi%Q8K8`L7W zuI;H*>UUj!D@#^r{?L=`C!!$aYO|+JS>=^ljNqBYeC84Tq36&h(((C7(i;vc+6wv+ zRyz6RTAe_YxVCi)dNNmG;)(YqSv}0qX44)G!Sc7BXNx%(Jnh}C)J|_>Z`y(1H;&c51yzoz1(i6bjZ;Sd z_D)B}IS79t1w^W^-A86OB$>+j7OO{c+=1WYOXSO`Yq<|t75Lo;P0z+}nov=3kzRL^ z?y@Cc^>24w4hq2#gtj|ztV$e&1ucr<29_Ni83$HQ`noz*cAZwcLejR`(2Ofxiz-9G z#H)Uf83&ggZ(gEaUNES9N$Nbiw7uD`GQQFkl62tT9)dNDX%BH(lwd|WScbTCUa9D6 zca*>mghg~ZajZ{&9vfS5;*#)qE#bN6EQX6S8uHtbz^3-L+pxv@10BQ(m@PQTe{d$| z^i1e-x|FyD$S>Svn*oIT*i&|GLohM9@{U4`13he2eOY-w4kWO8!4xrTV}h@X(FAdWEU2Z+^Y|5+ dmC#HP$pw-Io_XCbf?qK!CIoUR;{N&k{{c1KlL!C+ literal 0 HcmV?d00001 diff --git a/dash-frontend/src/views/remote_skymap_downloader.rs b/dash-frontend/src/views/remote_skymap_downloader.rs index 3b78a384..4a2aa537 100644 --- a/dash-frontend/src/views/remote_skymap_downloader.rs +++ b/dash-frontend/src/views/remote_skymap_downloader.rs @@ -117,8 +117,7 @@ impl ViewTrait for View { .get_destination_path(resolution) .context("Skymap not found" /* you shouldn't really see this, like ever. */)?; - par.general_config.skybox_texture = config_io::get_skymaps_root() - .join(skymap_file_path) + par.general_config.skybox_texture = skymap_file_path .to_str() .context("Skymap filename not valid UTF-8")? .into(); diff --git a/dash-frontend/src/views/remote_skymap_list.rs b/dash-frontend/src/views/remote_skymap_list.rs index 813cc7f3..76c171dc 100644 --- a/dash-frontend/src/views/remote_skymap_list.rs +++ b/dash-frontend/src/views/remote_skymap_list.rs @@ -215,7 +215,7 @@ impl View { view: views::skymap_list_cell::View::new(views::skymap_list_cell::Params { id_parent: id_list, layout, - entry: entry.clone(), + entry: Some(entry.clone()), on_click: self .tasks .get_button_click_callback(Task::ShowRemoteSkymapDownloader(skymap_uuid)), diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs index 5bedf5ea..cf97aa2d 100644 --- a/dash-frontend/src/views/skymap_list.rs +++ b/dash-frontend/src/views/skymap_list.rs @@ -16,7 +16,6 @@ use crate::{ util::{ networking::skymap_catalog::{self, SkymapCatalogEntry, SkymapResolution}, popup_manager::{MountPopupOnceParams, PopupHolder}, - wgui_simple, }, views::{self, ViewTrait, ViewUpdateParams}, }; @@ -27,6 +26,7 @@ enum Task { Refresh, ShowSkymapResolutionSelector(SkymapCatalogEntry), SetSkymap(SkymapCatalogEntry, SkymapResolution), + SetSkymapBuiltIn, } pub struct Params<'a> { @@ -77,6 +77,10 @@ impl ViewTrait for View { Task::SetSkymap(entry, resolution) => { self.set_skymap(entry, resolution, par.general_config, par.config_change_kind)?; } + Task::SetSkymapBuiltIn => { + par.general_config.skybox_texture = "".into(); + *par.config_change_kind = Some(ConfigChangeKind::EnvironmentBlend); + } } } } @@ -205,22 +209,27 @@ impl View { layout.remove_children(self.list_parent); self.cells.clear(); - if entries.is_empty() { - wgui_simple::create_label( - layout, - self.list_parent, - Translation::from_translation_key("APP_SETTINGS.NO_SKYMAPS_FOUND"), - )?; - return Ok(()); + let skymaps_root = config_io::get_skymaps_root(); + + let mut view = views::skymap_list_cell::View::new(views::skymap_list_cell::Params { + id_parent: self.list_parent, + layout, + entry: None, + on_click: self.tasks.get_button_click_callback(Task::SetSkymapBuiltIn), + })?; + // load preview image + let data = include_bytes!("../../assets/dashboard/builtin-skybox-preview.jpg"); + if let Ok(glyph_data) = CustomGlyphData::from_bytes_raster(&self.globals, "builtin-skybox-preview.jpg", data) { + view.set_image(layout, Some(glyph_data))?; } - let skymaps_root = config_io::get_skymaps_root(); + self.cells.push(Cell { view }); for entry in &entries { let mut view = views::skymap_list_cell::View::new(views::skymap_list_cell::Params { id_parent: self.list_parent, layout, - entry: entry.clone(), + entry: Some(entry.clone()), on_click: self .tasks .get_button_click_callback(Task::ShowSkymapResolutionSelector(entry.clone())), diff --git a/dash-frontend/src/views/skymap_list_cell.rs b/dash-frontend/src/views/skymap_list_cell.rs index 26bfcce3..f0267ce5 100644 --- a/dash-frontend/src/views/skymap_list_cell.rs +++ b/dash-frontend/src/views/skymap_list_cell.rs @@ -23,7 +23,7 @@ use crate::util::{ pub struct Params<'a> { pub id_parent: WidgetID, pub layout: &'a mut Layout, - pub entry: networking::skymap_catalog::SkymapCatalogEntry, + pub entry: Option, pub on_click: ButtonClickCallback, } @@ -108,11 +108,20 @@ impl View { label_title.set_text_simple( &mut globals.get(), - Translation::from_raw_text_string(par.entry.name.clone()), + Translation::from_raw_text_string( + par + .entry + .as_ref() + .map(|e| e.name.clone()) + .unwrap_or_else(|| "Built-in Sky Shader".into()), + ), ); label_author.set_text_simple( &mut globals.get(), - Translation::from_raw_text_string(format!("by {}", par.entry.author)), + Translation::from_raw_text_string(format!( + "by {}", + par.entry.as_ref().map(|e| e.author.as_str()).unwrap_or("WayVR Team") + )), ); } @@ -123,7 +132,9 @@ impl View { })?; // Populate resolution pips - populate_res_pips(par.layout, id_resolution_pips, &mut parser_state, &par.entry)?; + if let Some(entry) = par.entry.as_ref() { + populate_res_pips(par.layout, id_resolution_pips, &mut parser_state, entry)?; + } Ok(Self { parser_state, diff --git a/wayvr/src/shaders/sky.frag b/wayvr/src/shaders/sky.frag new file mode 100644 index 00000000..d26f8110 --- /dev/null +++ b/wayvr/src/shaders/sky.frag @@ -0,0 +1,368 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +float gauss(float x, float sigma) +{ + float t = x / sigma; + return exp(-t * t); +} + +float smoothBand(float x, float lo, float hi, float featherLo, float featherHi) +{ + float a = smoothstep(lo - featherLo, lo + featherLo, x); + float b = 1.0 - smoothstep(hi - featherHi, hi + featherHi, x); + return a * b; +} + +float thinLine(float x, float halfWidth) +{ + float d = abs(x); + float aa = max(fwidth(x) * 1.5, 1e-5); + + float core = 1.0 - smoothstep(0.0, halfWidth + aa, d); + float edge = 1.0 - smoothstep(halfWidth, halfWidth + aa, d); + + return core * edge; +} + +float hash11(float p) +{ + return fract(sin(p * 127.1 + 311.7) * 43758.5453123); +} + +float hash12(vec2 p) +{ + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float noise1Periodic(float x, float period) +{ + float i0 = floor(x); + float i1 = i0 + 1.0; + float f = fract(x); + float u = f * f * (3.0 - 2.0 * f); + + float a = hash11(mod(i0, period)); + float b = hash11(mod(i1, period)); + return mix(a, b, u); +} + +float fbm1Periodic(float x, float period) +{ + float sum = 0.0; + float amp = 0.5; + float norm = 0.0; + + for (int i = 0; i < 5; ++i) + { + sum += amp * noise1Periodic(x, period); + norm += amp; + x *= 2.0; + period *= 2.0; + amp *= 0.5; + } + + return sum / norm; +} + +float noise2TileX(vec2 p, float periodX) +{ + vec2 i = floor(p); + vec2 f = fract(p); + vec2 u = f * f * (3.0 - 2.0 * f); + + float x0 = mod(i.x, periodX); + float x1 = mod(i.x + 1.0, periodX); + float y0 = i.y; + float y1 = i.y + 1.0; + + float a = hash12(vec2(x0, y0)); + float b = hash12(vec2(x1, y0)); + float c = hash12(vec2(x0, y1)); + float d = hash12(vec2(x1, y1)); + + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} + +float fbm2TileX(vec2 p, float periodX) +{ + float sum = 0.0; + float amp = 0.5; + float norm = 0.0; + + for (int i = 0; i < 4; ++i) + { + sum += amp * noise2TileX(p, periodX); + norm += amp; + p *= 2.0; + periodX *= 2.0; + amp *= 0.5; + } + + return sum / norm; +} + +vec3 tonemapACESApprox(vec3 x) +{ + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + +float independentPath( + float u, + float baseLat, + float warpAmp0, float warpFreq0, float warpPhase0, + float warpAmp1, float warpFreq1, float warpPhase1, + float amp0, float freq0, float phase0, + float amp1, float freq1, float phase1, + float amp2, float freq2, float phase2, + out float uw) +{ + uw = fract(u + + warpAmp0 * (fbm1Periodic(u * warpFreq0 + warpPhase0, warpFreq0) - 0.5) + + warpAmp1 * (fbm1Periodic(u * warpFreq1 + warpPhase1, warpFreq1) - 0.5)); + + return baseLat + + amp0 * (fbm1Periodic(uw * freq0 + phase0, freq0) - 0.5) + + amp1 * (fbm1Periodic(uw * freq1 + phase1, freq1) - 0.5) + + amp2 * (fbm1Periodic(uw * freq2 + phase2, freq2) - 0.5); +} + +float independentPositive( + float u, + float baseValue, + float amp0, float freq0, float phase0, + float amp1, float freq1, float phase1) +{ + return baseValue + + amp0 * fbm1Periodic(u * freq0 + phase0, freq0) + + amp1 * fbm1Periodic(u * freq1 + phase1, freq1); +} + +void main() +{ + float u = fract(vUV.x); + float v = 0.5 - vUV.y; + + // background gradient + float skyT = smoothstep(-0.50, 0.50, v); + vec3 col = mix(vec3(0.00008, 0.00012, 0.00045), + vec3(0.0220, 0.0470, 0.0980), + skyT); + + col += gauss(v - 0.24, 0.30) * vec3(0.0045, 0.0100, 0.0220) * 0.28; + + // seamless flow field around 360 degrees + float flowA = fbm1Periodic(u * 7.0 + 1.3, 7.0); + float flowB = fbm1Periodic(u * 13.0 + 5.1, 13.0); + + float uu = fract(u + + 0.045 * (flowA - 0.5) + + 0.020 * (flowB - 0.5)); + + float sA = fbm1Periodic(uu * 5.0 + 2.7, 5.0); + float sB = fbm1Periodic(uu * 11.0 + 11.4, 11.0); + float sC = fbm1Periodic(uu * 23.0 + 4.2, 23.0); + float sD = fbm1Periodic(uu * 9.0 + 17.2, 9.0); + float sE = fbm1Periodic(uu * 19.0 + 8.3, 19.0); + + float center = -0.012 + + 0.100 * (sA - 0.5) + + 0.045 * (sB - 0.5) + + 0.018 * (sC - 0.5); + + float thickness = 0.072 + 0.040 * sD; + float skew = 0.014 * (sE - 0.5); + + float mainLo = center - 0.035 - skew; + float mainHi = center + thickness + 0.15 * skew; + + float veilU; + float veilCenter = independentPath( + u, 0.050, + 0.028, 7.0, 1.1, + -0.015, 13.0, 5.4, + 0.050, 3.0, 2.3, + 0.030, 11.0, 8.1, + 0.010, 29.0, 5.7, + veilU); + + float veilThickness = independentPositive( + veilU, 0.011, + 0.010, 9.0, 14.2, + 0.006, 21.0, 3.6); + + float veilLo = veilCenter - 0.004; + float veilHi = veilCenter + veilThickness; + + float auroraU; + float auroraCenter = independentPath( + u, 0.182, + 0.033, 5.0, 3.9, + 0.020, 17.0, 7.7, + 0.060, 4.0, 1.6, + 0.040, 9.0, 12.4, + 0.014, 27.0, 6.9, + auroraU); + + float auroraThickness = independentPositive( + auroraU, 0.034, + 0.014, 7.0, 4.1, + 0.008, 19.0, 15.2); + + float auroraLo = auroraCenter - 0.004; + float auroraHi = auroraCenter + auroraThickness; + + float mistU; + float mistCenter = independentPath( + u, 0.255, + 0.020, 6.0, 2.8, + -0.012, 15.0, 9.1, + 0.030, 4.0, 1.9, + 0.020, 10.0, 13.2, + 0.008, 26.0, 7.4, + mistU); + + float electric0U; + float electric0Center = independentPath( + u, 0.004, + 0.052, 9.0, 2.4, + -0.018, 1.0, 11.6, + 0.098, 3.0, 4.1, + 0.068, 1.0, 9.7, + 0.054, 2.0, 15.3, + electric0U); + + float electric1U; + float electric1Center = independentPath( + u, 0.002, + 0.046, 7.0, 8.3, + 0.020, 7.0, 3.1, + 0.080, 4.0, 6.6, + 0.071, 2.0, 12.2, + 0.064, 1.0, 5.8, + electric1U); + + float electric2U; + float electric2Center = independentPath( + u, 0.006, + 0.044, 3.0, 13.7, + -0.017, 4.0, 6.4, + 0.061, 5.0, 10.9, + 0.082, 6.0, 4.2, + 0.075, 5.0, 14.8, + electric2U); + + float mainTex = fbm2TileX(vec2(uu * 20.0 + 7.0, (v - center) * 8.0 + 13.0), 20.0); + float fineTex = fbm2TileX(vec2(uu * 36.0 + 17.0, (v - center) * 14.0 + 29.0), 36.0); + + float veilTex = fbm2TileX(vec2(veilU * 14.0 + 5.0, (v - veilCenter) * 9.0 + 41.0), 14.0); + float auroraTex = fbm2TileX(vec2(auroraU * 16.0 + 21.0, (v - auroraCenter) * 11.0 + 57.0), 16.0); + float auroraFine = fbm2TileX(vec2(auroraU * 28.0 + 3.0, (v - auroraCenter) * 18.0 + 71.0), 28.0); + + float bodyMod = 0.82 + 0.28 * mainTex + 0.18 * fineTex; + float veilMod = 0.80 + 0.30 * veilTex; + float auroraMod = 0.78 + 0.24 * auroraTex + 0.16 * auroraFine; + + // nasks + float aura = gauss(v - center, 0.14); + + float mainBody = smoothBand(v, mainLo, mainHi, 0.018, 0.024); + float mainMid = gauss(v - mix(mainLo, mainHi, 0.42), 0.55 * thickness); + float mainHiEdge = gauss(v - mainHi, 0.017); + + float veilBody = smoothBand(v, veilLo, veilHi, 0.014, 0.020); + float veilMid = gauss(v - mix(veilLo, veilHi, 0.52), 0.60 * veilThickness); + float veilLoEdge = gauss(v - veilLo, 0.012); + float veilHiEdge = gauss(v - veilHi, 0.018); + + float auroraBody = smoothBand(v, auroraLo, auroraHi, 0.016, 0.022); + float auroraMid = gauss(v - mix(auroraLo, auroraHi, 0.52), 0.62 * auroraThickness); + float auroraLoEdge = gauss(v - auroraLo, 0.013); + float auroraHiEdge = gauss(v - auroraHi, 0.020); + + float mist = gauss(v - mistCenter, 0.085); + + float veilLat = v - veilCenter; + float auroraLat = v - auroraCenter; + + float veilColorNoise = + 0.60 * fbm2TileX(vec2(veilU * 6.0 + 2.0, veilLat * 72.0 + 11.0), 6.0) + + 0.40 * fbm2TileX(vec2(veilU * 14.0 + 7.0, veilLat * 128.0 + 31.0), 14.0); + + float auroraColorU = fract( + auroraU + + 0.035 * (fbm1Periodic(auroraU * 9.0 + 2.7, 9.0) - 0.5) + + 0.018 * (fbm1Periodic(auroraU * 21.0 + 6.1, 21.0) - 0.5) + ); + + vec3 auroraColorNoise = clamp(vec3( + fbm1Periodic(auroraColorU * 5.0 + 1.3, 5.0), // R + fbm1Periodic(auroraColorU * 9.0 + 7.8, 9.0), // G + fbm1Periodic(auroraColorU * 17.0 + 12.4, 17.0) // B + ), 0.0, 1.0); + + veilColorNoise = clamp(veilColorNoise, 0.0, 1.0); + auroraColorNoise = clamp(auroraColorNoise, 0.0, 1.0); + + vec3 veilBaseColor = 0.2 * mix(vec3(0.070, 0.160, 0.460), + vec3(0.140, 0.620, 0.760), + 0.55 * veilColorNoise); + + vec3 veilPeakColor = 0.5 * mix(vec3(0.110, 0.240, 0.620), + vec3(0.180, 0.900, 0.950), + 0.65 * veilColorNoise); + + vec3 auroraBaseColor = 0.2 * vec3(0.100, 0.640, 0.720) * auroraColorNoise * auroraColorNoise; + vec3 auroraPeakColor = 0.5 * vec3(0.180, 0.980, 1.050) * auroraColorNoise * auroraColorNoise; + + float electric0 = thinLine(v - electric0Center, 0.0001); + float electric1 = thinLine(v - electric1Center, 0.00015); + float electric2 = thinLine(v - electric2Center, 0.0002); + + float electric0Glow = gauss(v - electric0Center, 0.0040); + float electric1Glow = gauss(v - electric1Center, 0.0048); + float electric2Glow = gauss(v - electric2Center, 0.0052); + + float e0Energy = 0.82 + 0.28 * fbm1Periodic(electric0U * 41.0 + 6.2, 41.0); + float e1Energy = 0.80 + 0.25 * fbm1Periodic(electric1U * 47.0 + 10.4, 47.0); + float e2Energy = 0.80 + 0.25 * fbm1Periodic(electric2U * 39.0 + 14.9, 39.0); + + // compose + col += aura * (0.85 + 0.25 * mainTex) * vec3(0.014, 0.032, 0.080) * 0.95; + + col += mainBody * bodyMod * vec3(0.070, 0.160, 0.420) * 0.5; + col += mainMid * bodyMod * vec3(0.120, 0.280, 0.740) * 0.4; + col += mainHiEdge * bodyMod * vec3(0.105, 0.240, 0.620) * 0.1; + + col += veilBody * veilMod * veilBaseColor * 1.05; + col += veilMid * veilMod * veilPeakColor * 0.90; + col += veilLoEdge * veilMod * mix(veilBaseColor, veilPeakColor, 0.55) * 0.80; + col += veilHiEdge * veilMod * vec3(0.100, 0.220, 0.580) * 0.20; + + col += auroraBody * auroraMod * auroraBaseColor * 0.95; + col += auroraMid * auroraMod * auroraPeakColor * 0.78; + col += auroraLoEdge * auroraMod * mix(auroraBaseColor, auroraPeakColor, 0.45) * 0.55; + col += auroraHiEdge * auroraMod * vec3(0.080, 0.180, 0.480) * 0.18; + + col += mist * (0.52 + 0.28 * auroraTex) * vec3(0.035, 0.082, 0.220) * 0.20; + + col += electric0Glow * vec3(0.70, 1.10, 1.90) * 0.55; + col += electric1Glow * mix(veilBaseColor, veilPeakColor, 0.60) * 1.20; + col += electric2Glow * mix(auroraBaseColor, auroraPeakColor, 0.65) * 1.35; + + col += electric0 * e0Energy * vec3(10.0, 10.3, 10.8); + col += electric1 * e1Energy * vec3( 6.2, 6.8, 7.4); + col += electric2 * e2Energy * vec3( 5.8, 6.5, 7.2); + + //TODO: skipped if rendering to a HDR swapchain + col = tonemapACESApprox(col); + + outColor = vec4(col, 1.0); +} From 9734955ebb60c9b3c231d8d9e64f01c6e845d77d Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:58:02 +0900 Subject: [PATCH 035/307] dont show skymap tab if not openxr --- dash-frontend/src/tab/settings/mod.rs | 13 ++++++++++++- wayvr/src/overlays/dashboard.rs | 7 +++++++ wgui/src/components/tabs.rs | 12 +++++++++++- wlx-common/src/dash_interface.rs | 7 +++++++ wlx-common/src/dash_interface_emulated.rs | 7 +++++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 5477078e..32ec394a 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -5,6 +5,7 @@ use wgui::{ assets::AssetPath, components::tabs::ComponentTabs, drawing, + event::StyleSetRequest, globals::WguiGlobals, i18n::Translation, layout::{Layout, WidgetID}, @@ -585,7 +586,7 @@ impl TabSettings { Ok(()) } - pub fn new(frontend: &mut Frontend, parent_id: WidgetID, _data: &mut T) -> anyhow::Result { + pub fn new(frontend: &mut Frontend, parent_id: WidgetID, data: &mut T) -> anyhow::Result { let doc_params = ParseDocumentParams { globals: frontend.layout.state.globals.clone(), path: AssetPath::BuiltIn("gui/tab/settings.xml"), @@ -595,6 +596,16 @@ impl TabSettings { let parser_state = wgui::parser::parse_from_assets(&doc_params, &mut frontend.layout, parent_id)?; let tasks = Tasks::default(); let tabs = parser_state.fetch_component_as::("tabs")?; + + if !frontend.interface.get_feats(data).openxr { + let skybox_btn = tabs.get_tab_button("skybox").unwrap(); + frontend + .layout + .common() + .alterables + .set_style(skybox_btn.get_rect(), StyleSetRequest::Display(taffy::Display::None)); + } + tabs.on_select({ let tasks = tasks.clone(); Rc::new(move |_common, evt| { diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index 10b05964..a0b531d7 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -476,6 +476,13 @@ impl DashInterface for DashInterfaceLive { .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); } + fn get_feats(&mut self, data: &mut AppState) -> dash_interface::InterfaceFeats { + dash_interface::InterfaceFeats { + openxr: matches!(data.xr_backend, XrBackend::OpenXR), + monado: data.monado_state.is_some(), + } + } + #[cfg(feature = "openxr")] fn monado_client_list( &mut self, diff --git a/wgui/src/components/tabs.rs b/wgui/src/components/tabs.rs index 521fb842..293cc00a 100644 --- a/wgui/src/components/tabs.rs +++ b/wgui/src/components/tabs.rs @@ -9,7 +9,7 @@ use crate::{ layout::WidgetPair, widget::{ConstructEssentials, div::WidgetDiv}, }; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, fmt::Pointer, rc::Rc}; use taffy::{ AlignItems, prelude::{length, percent}, @@ -94,6 +94,16 @@ impl ComponentTabs { pub fn on_select(&self, callback: TabSelectCallback) { self.state.borrow_mut().on_select = Some(callback); } + + pub fn get_tab_button(&self, name: &str) -> Option> { + self + .state + .borrow_mut() + .mounted_entries + .iter() + .find(|e| name == &*e.name) + .map(|e| e.button.clone()) + } } pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index bf9544ae..34990443 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -43,6 +43,12 @@ pub enum RecenterMode { Reset, } +#[derive(Clone, Copy)] +pub struct InterfaceFeats { + pub openxr: bool, + pub monado: bool, +} + 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<()>; @@ -68,6 +74,7 @@ pub trait DashInterface { fn config_changed(&mut self, data: &mut T, kind: ConfigChangeKind); fn restart(&mut self, data: &mut T); fn toggle_dashboard(&mut self, data: &mut T); + fn get_feats(&mut self, data: &mut T) -> InterfaceFeats; } #[derive(Clone, Copy)] diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index d2479e86..919054dd 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -236,6 +236,13 @@ impl DashInterface<()> for DashInterfaceEmulated { fn toggle_dashboard(&mut self, _data: &mut ()) {} + fn get_feats(&mut self, _data: &mut ()) -> dash_interface::InterfaceFeats { + dash_interface::InterfaceFeats { + openxr: true, + monado: true, + } + } + fn monado_client_list( &mut self, _data: &mut (), From 6b81f07cc805c818791682f725f9f32f7bab7794 Mon Sep 17 00:00:00 2001 From: oo8dev Date: Sat, 2 May 2026 19:21:30 +0200 Subject: [PATCH 036/307] wgui: Swipe kinetic scrolling support (#504) * wgui: use DeviceBitmask instead of usize * wgui: Swipe logic works (wip) * wgui: Kinetic scrolling, rubberband effect --- .../src/util/networking/skymap_catalog.rs | 11 +- dash-frontend/src/util/wgui_simple.rs | 1 + uidev/src/main.rs | 10 +- wayvr/src/gui/panel/mod.rs | 18 +- wayvr/src/ipc/event_queue.rs | 2 - wayvr/src/overlays/dashboard.rs | 20 +- wgui/src/components/button.rs | 13 + wgui/src/components/checkbox.rs | 12 + wgui/src/components/slider.rs | 6 +- wgui/src/components/tabs.rs | 2 +- wgui/src/event.rs | 32 ++- wgui/src/layout.rs | 71 ++++-- wgui/src/widget/mod.rs | 230 ++++++++++++++---- 13 files changed, 326 insertions(+), 102 deletions(-) diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs index db1feee6..d7d21ca2 100644 --- a/dash-frontend/src/util/networking/skymap_catalog.rs +++ b/dash-frontend/src/util/networking/skymap_catalog.rs @@ -1,12 +1,7 @@ -#![allow(dead_code)] -use std::path::PathBuf; - -// TODO: Remove later -use serde::{Deserialize, Serialize}; -use wlx_common::{async_executor::AsyncExecutor, config_io}; - use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client}; - +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use wlx_common::{async_executor::AsyncExecutor, config_io}; pub type SkymapUuid = uuid::Uuid; #[derive(Copy, Clone, Serialize, Deserialize, Debug)] diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs index f09b69ab..af4d393c 100644 --- a/dash-frontend/src/util/wgui_simple.rs +++ b/dash-frontend/src/util/wgui_simple.rs @@ -45,6 +45,7 @@ pub fn create_button(par: CreateButtonParams) -> anyhow::Result<()> { Ok(()) } +#[allow(dead_code)] pub fn create_label(layout: &mut Layout, id_parent: WidgetID, content: Translation) -> anyhow::Result<()> { let label = WidgetLabel::create( &mut layout.state, diff --git a/uidev/src/main.rs b/uidev/src/main.rs index a812d1ce..69e95fa2 100644 --- a/uidev/src/main.rs +++ b/uidev/src/main.rs @@ -143,7 +143,7 @@ fn main() -> Result<(), Box> { &wgui::event::Event::MouseWheel(MouseWheelEvent { delta: Vec2::new(x, y), pos: mouse / scale, - device: 0, + device: wgui::event::DeviceBitmask(0), }), &mut (), &mut (), @@ -157,7 +157,7 @@ fn main() -> Result<(), Box> { &wgui::event::Event::MouseWheel(MouseWheelEvent { delta: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0), pos: mouse / scale, - device: 0, + device: wgui::event::DeviceBitmask(0), }), &mut (), &mut (), @@ -177,7 +177,7 @@ fn main() -> Result<(), Box> { &wgui::event::Event::MouseDown(MouseButtonEvent { pos: mouse / scale, index: MouseButtonIndex::Left, - device: 0, + device: wgui::event::DeviceBitmask(0), }), &mut (), &mut (), @@ -190,7 +190,7 @@ fn main() -> Result<(), Box> { &wgui::event::Event::MouseUp(MouseButtonEvent { pos: mouse / scale, index: MouseButtonIndex::Left, - device: 0, + device: wgui::event::DeviceBitmask(0), }), &mut (), &mut (), @@ -209,7 +209,7 @@ fn main() -> Result<(), Box> { .push_event( &wgui::event::Event::MouseMotion(MouseMotionEvent { pos: mouse / scale, - device: 0, + device: wgui::event::DeviceBitmask(0), }), &mut (), &mut (), diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs index c0f6d8ac..04d067a1 100644 --- a/wayvr/src/gui/panel/mod.rs +++ b/wayvr/src/gui/panel/mod.rs @@ -22,7 +22,7 @@ use wgui::{ slider::ComponentSlider, }, event::{ - Event as WguiEvent, EventCallback, EventListenerID, EventListenerKind, + DeviceBitmask, Event as WguiEvent, EventCallback, EventListenerID, EventListenerKind, InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex, MouseLeaveEvent, MouseMotionEvent, MouseWheelEvent, }, @@ -368,7 +368,7 @@ impl OverlayBackend for GuiPanel { let e = WguiEvent::MouseWheel(MouseWheelEvent { delta: vec2(delta.x, delta.y) / 8.0, pos: hit.uv * self.layout.content_size, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(app, &e); } @@ -376,7 +376,7 @@ impl OverlayBackend for GuiPanel { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { let e = &WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv * self.layout.content_size, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.has_focus[hit.pointer] = true; @@ -397,7 +397,9 @@ impl OverlayBackend for GuiPanel { } fn on_left(&mut self, app: &mut AppState, pointer: usize) { - let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }); + let e = WguiEvent::MouseLeave(MouseLeaveEvent { + device: DeviceBitmask::from_usize(pointer), + }); self.has_focus[pointer] = false; self.push_event(app, &e); } @@ -414,13 +416,13 @@ impl OverlayBackend for GuiPanel { WguiEvent::MouseDown(MouseButtonEvent { pos: hit.uv * self.layout.content_size, index, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }) } else { WguiEvent::MouseUp(MouseButtonEvent { pos: hit.uv * self.layout.content_size, index, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }) }; self.push_event(app, &e); @@ -429,11 +431,11 @@ impl OverlayBackend for GuiPanel { if !pressed && !self.has_focus[hit.pointer] { let e = WguiEvent::MouseMotion(MouseMotionEvent { pos: vec2(-1., -1.), - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(app, &e); let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(app, &e); } diff --git a/wayvr/src/ipc/event_queue.rs b/wayvr/src/ipc/event_queue.rs index c150c4c4..13c9adb4 100644 --- a/wayvr/src/ipc/event_queue.rs +++ b/wayvr/src/ipc/event_queue.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use std::{cell::RefCell, collections::VecDeque, rc::Rc}; #[derive(Debug)] diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index a0b531d7..316f66ff 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -8,8 +8,8 @@ use wayvr_ipc::{ }; use wgui::{ event::{ - Event as WguiEvent, MouseButtonEvent, MouseButtonIndex, MouseLeaveEvent, MouseMotionEvent, - MouseWheelEvent, + DeviceBitmask, Event as WguiEvent, MouseButtonEvent, MouseButtonIndex, MouseLeaveEvent, + MouseMotionEvent, MouseWheelEvent, }, gfx::cmd::WGfxClearMode, renderer_vk::context::Context as WguiContext, @@ -205,7 +205,7 @@ impl OverlayBackend for DashFrontend { let e = WguiEvent::MouseWheel(MouseWheelEvent { delta: vec2(delta.x, delta.y) / 8.0, pos: hit.uv * self.inner.layout.content_size, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(&e); } @@ -213,7 +213,7 @@ impl OverlayBackend for DashFrontend { fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> HoverResult { let e = &WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv * self.inner.layout.content_size, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.has_focus[hit.pointer] = true; @@ -235,7 +235,9 @@ impl OverlayBackend for DashFrontend { } fn on_left(&mut self, _app: &mut AppState, pointer: usize) { - let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }); + let e = WguiEvent::MouseLeave(MouseLeaveEvent { + device: DeviceBitmask::from_usize(pointer), + }); self.has_focus[pointer] = false; self.push_event(&e); } @@ -252,13 +254,13 @@ impl OverlayBackend for DashFrontend { WguiEvent::MouseDown(MouseButtonEvent { pos: hit.uv * self.inner.layout.content_size, index, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }) } else { WguiEvent::MouseUp(MouseButtonEvent { pos: hit.uv * self.inner.layout.content_size, index, - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }) }; self.push_event(&e); @@ -267,11 +269,11 @@ impl OverlayBackend for DashFrontend { if !pressed && !self.has_focus[hit.pointer] { let e = WguiEvent::MouseMotion(MouseMotionEvent { pos: vec2(-1., -1.), - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(&e); let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: hit.pointer, + device: DeviceBitmask::from_usize(hit.pointer), }); self.push_event(&e); } diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index ab2bfda8..e543f059 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -334,6 +334,18 @@ fn register_event_mouse_leave( ) } +fn register_event_mouse_cancel(state: Rc>, listeners: &mut EventListenerCollection) -> EventListenerID { + listeners.register( + EventListenerKind::MouseCancel, + Box::new(move |_common, _event_data, (), ()| { + let mut state = state.borrow_mut(); + state.down = false; + state.hovered = false; + Ok(EventResult::Pass) + }), + ) +} + fn register_event_mouse_press(state: Rc>, listeners: &mut EventListenerCollection) -> EventListenerID { listeners.register( EventListenerKind::MousePress, @@ -552,6 +564,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let listeners = &mut root.widget.state().event_listeners; let anim_mult = ess.layout.state.theme.animation_mult; vec![ + register_event_mouse_cancel(state.clone(), listeners), register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult), register_event_mouse_leave(state.clone(), listeners, anim_mult), register_event_mouse_press(state.clone(), listeners), diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 552e7ed6..4a49e775 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -240,6 +240,17 @@ fn register_event_mouse_leave( ) } +fn register_event_mouse_cancel(state: Rc>, listeners: &mut EventListenerCollection) -> EventListenerID { + listeners.register( + EventListenerKind::MouseCancel, + Box::new(move |_common, _event_data, (), ()| { + let mut state = state.borrow_mut(); + state.down = false; + Ok(EventResult::Pass) + }), + ) +} + fn register_event_mouse_press(state: Rc>, listeners: &mut EventListenerCollection) -> EventListenerID { listeners.register( EventListenerKind::MousePress, @@ -439,6 +450,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul vec![ register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult), register_event_mouse_leave(state.clone(), listeners, anim_mult), + register_event_mouse_cancel(state.clone(), listeners), register_event_mouse_press(state.clone(), listeners), register_event_mouse_release(data.clone(), state.clone(), listeners), ] diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index 0814f329..004d4b22 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -11,8 +11,8 @@ use crate::{ }, drawing::{self}, event::{ - self, CallbackDataCommon, CallbackMetadata, EventAlterables, EventListenerCollection, EventListenerKind, - StyleSetRequest, + self, CallbackDataCommon, CallbackMetadata, DeviceBitmask, EventAlterables, EventListenerCollection, + EventListenerKind, StyleSetRequest, }, i18n::Translation, layout::{WidgetID, WidgetPair}, @@ -77,7 +77,7 @@ pub struct Params { } struct State { - dragged_by: Option, + dragged_by: Option, hovered: bool, values: ValuesMinMax, on_value_changed: Option, diff --git a/wgui/src/components/tabs.rs b/wgui/src/components/tabs.rs index 293cc00a..de6acb66 100644 --- a/wgui/src/components/tabs.rs +++ b/wgui/src/components/tabs.rs @@ -9,7 +9,7 @@ use crate::{ layout::WidgetPair, widget::{ConstructEssentials, div::WidgetDiv}, }; -use std::{cell::RefCell, fmt::Pointer, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use taffy::{ AlignItems, prelude::{length, percent}, diff --git a/wgui/src/event.rs b/wgui/src/event.rs index cba47205..5e09650e 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -19,6 +19,17 @@ use crate::{ widget::{EventResult, WidgetData, WidgetObj}, }; +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct DeviceBitmask(pub u8); + +impl DeviceBitmask { + pub fn from_usize(mask: usize) -> Self { + // more than 8 input devices? + debug_assert!(mask & !0xff == 0); + Self(mask as u8) + } +} + #[derive(Debug, Clone, Copy)] pub enum MouseButtonIndex { Left, @@ -30,28 +41,28 @@ pub enum MouseButtonIndex { pub struct MouseButtonEvent { pub index: MouseButtonIndex, pub pos: Vec2, - pub device: usize, + pub device: DeviceBitmask, } #[derive(Debug, Clone, Copy)] pub struct MousePosition { pub pos: Vec2, - pub device: usize, + pub device: DeviceBitmask, } pub struct MouseLeaveEvent { - pub device: usize, + pub device: DeviceBitmask, } pub struct MouseMotionEvent { pub pos: Vec2, - pub device: usize, + pub device: DeviceBitmask, } pub struct MouseWheelEvent { pub pos: Vec2, /* mouse position */ pub delta: Vec2, /* wheel delta */ - pub device: usize, + pub device: DeviceBitmask, } #[derive(Clone)] @@ -70,6 +81,7 @@ pub enum Event { MouseMotion(MouseMotionEvent), MouseUp(MouseButtonEvent), MouseWheel(MouseWheelEvent), + MouseCancel, // Called if the user started scrolling by swiping above the button, to cancel all currently pressed buttons (prevent clicks) TextInput(TextInputEvent), } @@ -106,6 +118,7 @@ pub struct EventAlterables { pub dirty_widgets: Vec, pub components_to_refresh_once: Vec, pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>, + pub global_events_to_emit: Vec, pub animations: Vec, pub widgets_to_tick: HashSet, // widgets which needs to be ticked in the next `Layout::update()` fn pub transform_stack: TransformStack, @@ -134,6 +147,10 @@ impl EventAlterables { self.mark_redraw(); } + pub fn emit_global_event(&mut self, event: Event) { + self.global_events_to_emit.push(event); + } + pub fn mark_tick(&mut self, widget_id: WidgetID) { self.widgets_to_tick.insert(widget_id); } @@ -224,9 +241,10 @@ impl CallbackMetadata { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EventListenerKind { MousePress, - MouseRelease, - MouseEnter, MouseMotion, + MouseRelease, + MouseCancel, + MouseEnter, MouseLeave, TextInput, InternalStateChange, diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 0d195e32..6bcda51f 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -11,7 +11,7 @@ use crate::{ drawing::{ self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack, }, - event::{self, CallbackDataCommon, EventAlterables}, + event::{self, CallbackDataCommon, Event, EventAlterables}, globals::WguiGlobals, sound::WguiSoundType, task::Tasks, @@ -173,6 +173,8 @@ pub struct Layout { pub widgets_to_tick: Vec, + global_events_to_emit: Vec, + // *Main root* // contains content_root_widget and topmost widgets pub tree_root_widget: WidgetID, @@ -450,19 +452,25 @@ impl Layout { .as_ref() .is_none_or(PushScissorStackResult::should_display) { - // check children first - self.push_event_children(node_id, event, event_result, alterables, user_data)?; + let res_priority = widget.process_event_priority( + &mut self.get_event_params(l, node_id, style, alterables), + widget_id, + event, + )?; - if event_result.can_propagate() { - let mut params = EventParams { - state: &self.state, - layout: l, - alterables, - node_id, - style, - }; + if res_priority.can_propagate() { + // check children first + self.push_event_children(node_id, event, event_result, alterables, user_data)?; - widget.process_event(widget_id, node_id, event, event_result, user_data, &mut params)?; + if event_result.can_propagate() { + widget.process_event( + &mut self.get_event_params(l, node_id, style, alterables), + widget_id, + event, + event_result, + user_data, + )?; + } } } @@ -474,6 +482,22 @@ impl Layout { Ok(()) } + fn get_event_params<'a>( + &'a self, + l: &'a taffy::Layout, + node_id: taffy::NodeId, + style: &'a taffy::Style, + alterables: &'a mut EventAlterables, + ) -> EventParams<'a> { + EventParams { + node_id, + style, + state: &self.state, + alterables, + taffy_layout: l, + } + } + pub const fn check_toggle_needs_redraw(&mut self) -> bool { if self.needs_redraw { self.needs_redraw = false; @@ -508,6 +532,18 @@ impl Layout { &mut (user1, user2), )?; self.process_alterables(alterables)?; + + let mut alterables = EventAlterables::default(); + for event in std::mem::take(&mut self.global_events_to_emit) { + self.push_event_widget( + self.tree_root_node, + &event, + &mut event_result, + &mut alterables, + &mut (user1, user2), + )?; + } + Ok(event_result) } @@ -580,6 +616,7 @@ impl Layout { sounds_to_play_once: Vec::new(), focused_component: None, alterables: Default::default(), + global_events_to_emit: Vec::new(), }) } @@ -815,10 +852,12 @@ impl Layout { } } - if !alterables.widgets_to_tick.is_empty() { - for widget_id in &alterables.widgets_to_tick { - self.widgets_to_tick.push(*widget_id); - } + for widget_id in alterables.widgets_to_tick { + self.widgets_to_tick.push(widget_id); + } + + for event in alterables.global_events_to_emit { + self.global_events_to_emit.push(event); } for c in alterables.components_to_refresh_once { diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index 08f7aeda..961d916d 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use glam::Vec2; +use glam::{FloatExt, Vec2}; use taffy::{NodeId, TaffyTree}; use super::drawing::RenderPrimitive; @@ -8,7 +8,8 @@ use crate::{ any::AnyTrait, drawing::{self, PrimitiveExtent}, event::{ - self, CallbackData, CallbackDataCommon, CallbackMetadata, Event, EventAlterables, EventListenerCollection, + self, CallbackData, CallbackDataCommon, CallbackMetadata, DeviceBitmask, Event, EventAlterables, + EventListenerCollection, EventListenerKind::{self, InternalStateChange, MouseLeave}, MouseWheelEvent, }, @@ -25,57 +26,66 @@ pub mod rectangle; pub mod sprite; pub mod util; +const SWIPE_START_THRESHOLD_UNITS: f32 = 16.0; +const KINETIC_VELOCITY_DAMPING: f32 = 0.92; +const KINETIC_VELOCITY_BRAKE: f32 = 0.85; +const RUBBERBANDING_DAMP: f32 = 0.8; + pub struct WidgetData { - hovered: usize, - pressed: usize, - pub scrolling_target: Vec2, // normalized, 0.0-1.0. Not used in case if overflow != scroll - pub scrolling_cur: Vec2, // normalized, used for smooth scrolling animation - pub scrolling_cur_prev: Vec2, // for motion interpolation while rendering between ticks + hovered: DeviceBitmask, + pressed: DeviceBitmask, + scrolling_target: Vec2, // normalized, 0.0-1.0. Not used in case if overflow != scroll + scrolling_cur: Vec2, // normalized, used for smooth scrolling animation + scrolling_cur_prev: Vec2, // for motion interpolation while rendering between ticks + scrolling_velocity: Vec2, + press_down_start_mouse_pos: Option, + swipe_running: bool, + swipe_scroll_start: Vec2, // normalized, 0.0-1.0 pub transform: glam::Mat4, pub cached_absolute_boundary: drawing::Boundary, // updated in Layout::push_event_widget } impl WidgetData { - pub const fn set_device_pressed(&mut self, device: usize, pressed: bool) -> bool { - let bit = 1 << device; + pub const fn set_device_pressed(&mut self, device: DeviceBitmask, pressed: bool) -> bool { + let bit = 1 << device.0; let state_changed; if pressed { - state_changed = self.pressed == 0; - self.pressed |= bit; + state_changed = self.pressed.0 == 0; + self.pressed.0 |= bit; } else { - state_changed = self.pressed == bit; - self.pressed &= !bit; + state_changed = self.pressed.0 == bit; + self.pressed.0 &= !bit; } state_changed } - pub const fn set_device_hovered(&mut self, device: usize, hovered: bool) -> bool { - let bit = 1 << device; + pub const fn set_device_hovered(&mut self, device: DeviceBitmask, hovered: bool) -> bool { + let bit = 1 << device.0; let state_changed; if hovered { - state_changed = self.hovered == 0; - self.hovered |= bit; + state_changed = self.hovered.0 == 0; + self.hovered.0 |= bit; } else { - state_changed = self.hovered == bit; - self.hovered &= !bit; + state_changed = self.hovered.0 == bit; + self.hovered.0 &= !bit; } state_changed } - pub const fn get_pressed(&self, device: usize) -> bool { - self.pressed & (1 << device) != 0 + pub const fn get_pressed(&self, device: DeviceBitmask) -> bool { + self.pressed.0 & (1 << device.0) != 0 } - pub const fn get_hovered(&self, device: usize) -> bool { - self.hovered & (1 << device) != 0 + pub const fn get_hovered(&self, device: DeviceBitmask) -> bool { + self.hovered.0 & (1 << device.0) != 0 } pub const fn is_pressed(&self) -> bool { - self.pressed != 0 + self.pressed.0 != 0 } pub const fn is_hovered(&self) -> bool { - self.hovered != 0 + self.hovered.0 != 0 } } @@ -116,13 +126,17 @@ impl WidgetState { fn new(flags: WidgetStateFlags, obj: Box) -> Self { Self { data: WidgetData { - hovered: 0, - pressed: 0, + hovered: DeviceBitmask(0), + pressed: DeviceBitmask(0), scrolling_target: Vec2::default(), scrolling_cur: Vec2::default(), scrolling_cur_prev: Vec2::default(), transform: glam::Mat4::IDENTITY, cached_absolute_boundary: drawing::Boundary::default(), + press_down_start_mouse_pos: None, + swipe_running: false, + swipe_scroll_start: Vec2::default(), + scrolling_velocity: Vec2::default(), }, obj, event_listeners: EventListenerCollection::default(), @@ -192,7 +206,7 @@ pub struct EventParams<'a> { pub style: &'a taffy::Style, pub state: &'a LayoutState, pub alterables: &'a mut EventAlterables, - pub layout: &'a taffy::Layout, + pub taffy_layout: &'a taffy::Layout, } #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] @@ -221,6 +235,22 @@ fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) { ) } +const OVERFLOW_START_THRESHOLD_UNITS: f32 = 3.0; // don't show scrollbars for nearly non-scrollable lists + +fn get_scroll_active_axis(style: &taffy::Style, taffy_layout: &taffy::Layout) -> (bool, bool) { + let (enabled_horiz, enabled_vert) = get_scroll_enabled(style); + if !enabled_horiz && !enabled_vert { + return (false, false); + } + + let overflow = Vec2::new(taffy_layout.scroll_width(), taffy_layout.scroll_height()); + + ( + overflow.x >= OVERFLOW_START_THRESHOLD_UNITS, + overflow.y >= OVERFLOW_START_THRESHOLD_UNITS, + ) +} + pub struct ScrollbarInfo { // total contents size of the currently scrolling widget content_size: Vec2, @@ -229,15 +259,13 @@ pub struct ScrollbarInfo { handle_size: Vec2, } -pub fn get_scrollbar_info(l: &taffy::Layout) -> Option { - let overflow_start_threshold_units = 3.0; // don't show scrollbars for nearly non-scrollable lists - - let overflow = Vec2::new(l.scroll_width(), l.scroll_height()); - if overflow.x < overflow_start_threshold_units && overflow.y < overflow_start_threshold_units { +pub fn get_scrollbar_info(taffy_layout: &taffy::Layout) -> Option { + let overflow = Vec2::new(taffy_layout.scroll_width(), taffy_layout.scroll_height()); + if overflow.x < OVERFLOW_START_THRESHOLD_UNITS && overflow.y < OVERFLOW_START_THRESHOLD_UNITS { return None; // not overflowing } - let content_size = Vec2::new(l.content_size.width, l.content_size.height); + let content_size = Vec2::new(taffy_layout.content_size.width, taffy_layout.content_size.height); let handle_size = 1.0 - (overflow / content_size); Some(ScrollbarInfo { @@ -246,6 +274,15 @@ pub fn get_scrollbar_info(l: &taffy::Layout) -> Option { }) } +impl ScrollbarInfo { + fn get_potential_scroll_axis_multiplier(&self, taffy_layout: &taffy::Layout) -> Vec2 { + Vec2::new( + self.content_size.x - taffy_layout.content_box_width(), + self.content_size.y - taffy_layout.content_box_height(), + ) + } +} + impl dyn WidgetObj { pub fn get_as(&self) -> Option<&T> { let any = self.as_any(); @@ -348,6 +385,46 @@ impl WidgetState { let scrolling_cur_prev = &mut self.data.scrolling_cur_prev; let scrolling_target = &mut self.data.scrolling_target; + let mut perform_next_tick = false; + + // check for boundaries + if !self.data.swipe_running { + // top bound + if scrolling_target.y < 0.0 { + self.data.scrolling_velocity.y *= KINETIC_VELOCITY_BRAKE; + scrolling_target.y *= RUBBERBANDING_DAMP; + perform_next_tick = true; + } + + // left bound + if scrolling_target.x < 0.0 { + self.data.scrolling_velocity.x *= KINETIC_VELOCITY_BRAKE; + scrolling_target.x *= RUBBERBANDING_DAMP; + perform_next_tick = true; + } + + // right bound + if scrolling_target.x > 1.0 { + self.data.scrolling_velocity.x *= KINETIC_VELOCITY_BRAKE; + scrolling_target.x = 1.0.lerp(scrolling_target.x, RUBBERBANDING_DAMP); + perform_next_tick = true; + } + + // bottom bound + if scrolling_target.y > 1.0 { + self.data.scrolling_velocity.y *= KINETIC_VELOCITY_BRAKE; + scrolling_target.y = 1.0.lerp(scrolling_target.y, RUBBERBANDING_DAMP); + perform_next_tick = true; + } + } + + // Perform kinetic velocity animation + if self.data.scrolling_velocity.length_squared() > 1e-9 { + *scrolling_target += self.data.scrolling_velocity; + self.data.scrolling_velocity *= KINETIC_VELOCITY_DAMPING; + perform_next_tick = true; + } + *scrolling_cur_prev = *scrolling_cur; if scrolling_cur != scrolling_target { @@ -355,8 +432,7 @@ impl WidgetState { *scrolling_cur = scrolling_cur.lerp(*scrolling_target, 0.2); // trigger tick request again - alterables.mark_tick(this_widget_id); - alterables.mark_redraw(); + perform_next_tick = true; let epsilon = 0.00001; if (scrolling_cur.x - scrolling_target.x).abs() < epsilon @@ -365,6 +441,11 @@ impl WidgetState { *scrolling_cur = *scrolling_target; } } + + if perform_next_tick { + alterables.mark_tick(this_widget_id); + alterables.mark_redraw(); + } } pub fn draw_scrollbars(&mut self, state: &mut DrawState, params: &DrawParams, info: &ScrollbarInfo) { @@ -432,13 +513,13 @@ impl WidgetState { return false; } - let l = params.layout; + let l = params.taffy_layout; let overflow = Vec2::new(l.scroll_width(), l.scroll_height()); if overflow.x == 0.0 && overflow.y == 0.0 { return false; // not overflowing } - let Some(info) = get_scrollbar_info(params.layout) else { + let Some(info) = get_scrollbar_info(l) else { return false; }; @@ -451,7 +532,7 @@ impl WidgetState { } let mult = (1.0 / (content_box_length - content_length)) * step_pixels; - let new_scroll = (*scrolling_target + wheel_delta * mult).clamp(0.0, 1.0); + let new_scroll = *scrolling_target + wheel_delta * mult; if *scrolling_target == new_scroll { return; } @@ -479,20 +560,81 @@ impl WidgetState { true } + // this is called before calling children of this widget + pub fn process_event_priority( + &mut self, + params: &mut EventParams, + widget_id: WidgetID, + event: &Event, + ) -> anyhow::Result { + match &event { + Event::MouseDown(e) => { + // firstly, check if this widget is scrollable at all + let (active_x, active_y) = get_scroll_active_axis(¶ms.style, ¶ms.taffy_layout); + if active_x || active_y { + self.data.press_down_start_mouse_pos = Some(e.pos); + self.data.swipe_scroll_start = self.data.scrolling_target; + } + } + Event::MouseUp(_e) => { + if self.data.swipe_running { + self.data.swipe_running = false; + let kinetic_force = (self.data.scrolling_cur - self.data.scrolling_cur_prev) * 20.0; + self.data.scrolling_velocity += kinetic_force; + params.alterables.mark_tick(widget_id); + params.alterables.mark_redraw(); + } + self.data.press_down_start_mouse_pos = None; + } + Event::MouseMotion(e) => { + if let Some(start_mouse_pos) = &self.data.press_down_start_mouse_pos { + let (active_x, active_y) = get_scroll_active_axis(¶ms.style, ¶ms.taffy_layout); + + if !self.data.swipe_running { + if (active_x && (e.pos.x - start_mouse_pos.x).abs() >= SWIPE_START_THRESHOLD_UNITS) + || (active_y && (e.pos.y - start_mouse_pos.y).abs() >= SWIPE_START_THRESHOLD_UNITS) + { + self.data.swipe_running = true; + params.alterables.emit_global_event(Event::MouseCancel); + } + } + + if self.data.swipe_running + && let Some(scrollbar_info) = get_scrollbar_info(params.taffy_layout) + { + let mouse_diff = e.pos - start_mouse_pos; + + let mult = scrollbar_info.get_potential_scroll_axis_multiplier(params.taffy_layout); + + let scroll_diff_x = if mult.x != 0.0 { -mouse_diff.x / mult.x } else { 0.0 }; + let scroll_diff_y = if mult.y != 0.0 { -mouse_diff.y / mult.y } else { 0.0 }; + + self.data.scrolling_target = self.data.swipe_scroll_start + Vec2::new(scroll_diff_x, scroll_diff_y); + params.alterables.mark_tick(self.obj.get_id()); + + return Ok(EventResult::Consumed); + } + } + } + _ => {} + } + + Ok(EventResult::Pass) + } + pub fn process_event<'a, 'b, U1: 'static, U2: 'static>( &mut self, + params: &'a mut EventParams<'a>, widget_id: WidgetID, - node_id: taffy::NodeId, event: &Event, event_result: &'a mut EventResult, user_data: &'a mut (&'b mut U1, &'b mut U2), - params: &'a mut EventParams<'a>, ) -> anyhow::Result<()> { let hovered = event.test_mouse_within_transform(params.alterables.transform_stack.get()); let mut invoke_data = InvokeData { widget_id, - node_id, + node_id: params.node_id, event_result, user_data, params, @@ -508,6 +650,9 @@ impl WidgetState { CallbackMetadata::TextInput(e.clone()), )?); } + Event::MouseCancel => { + res = Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseCancel, CallbackMetadata::None)?); + } Event::MouseDown(e) => { if hovered && self.data.set_device_pressed(e.device, true) { res = Some(self.invoke_listeners( @@ -528,7 +673,6 @@ impl WidgetState { } Event::MouseMotion(e) => { let hover_state_changed = self.data.set_device_hovered(e.device, hovered); - if hover_state_changed { if self.data.is_hovered() { res = From 10b14794ed90fc9f7aed4bc80312610883d08b04 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 3 May 2026 14:36:49 +0900 Subject: [PATCH 037/307] update to upstream pipewire-rs (#508) * update to upstream pipewire-rs * CI to use 24.04 without no pipewire ppa * fix build without openxr feature --- .github/workflows/build-all-features.yml | 2 +- .github/workflows/build-appimage.yml | 2 +- .github/workflows/build-default.yml | 2 +- .github/workflows/build-wayland-openvr.yml | 2 +- .github/workflows/build-wayland-openxr.yml | 2 +- .github/workflows/build-x11-openvr.yml | 2 +- .github/workflows/build-x11-openxr.yml | 2 +- .github/workflows/make-release.yml | 2 +- .../workflows/scripts/appimage_prepare_env.sh | 2 - Cargo.lock | 126 ++++++------------ wayvr/src/overlays/dashboard.rs | 3 + wlx-capture/Cargo.toml | 4 +- wlx-capture/src/pipewire.rs | 60 +++++---- 13 files changed, 86 insertions(+), 125 deletions(-) diff --git a/.github/workflows/build-all-features.yml b/.github/workflows/build-all-features.yml index ea748df3..81267f63 100644 --- a/.github/workflows/build-all-features.yml +++ b/.github/workflows/build-all-features.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 9eb37b2c..4776d417 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -15,7 +15,7 @@ env: jobs: build_appimage: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-default.yml b/.github/workflows/build-default.yml index a20e5dfa..3ad4d140 100644 --- a/.github/workflows/build-default.yml +++ b/.github/workflows/build-default.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-wayland-openvr.yml b/.github/workflows/build-wayland-openvr.yml index 9b540dc5..dd3beb57 100644 --- a/.github/workflows/build-wayland-openvr.yml +++ b/.github/workflows/build-wayland-openvr.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-wayland-openxr.yml b/.github/workflows/build-wayland-openxr.yml index 42a5c33c..9ff5c9d4 100644 --- a/.github/workflows/build-wayland-openxr.yml +++ b/.github/workflows/build-wayland-openxr.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-x11-openvr.yml b/.github/workflows/build-x11-openvr.yml index cacd5e5c..2d54b81f 100644 --- a/.github/workflows/build-x11-openvr.yml +++ b/.github/workflows/build-x11-openvr.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/build-x11-openxr.yml b/.github/workflows/build-x11-openxr.yml index 7e045dee..697f0806 100644 --- a/.github/workflows/build-x11-openxr.yml +++ b/.github/workflows/build-x11-openxr.yml @@ -11,7 +11,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 3225e372..01286bc0 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -13,7 +13,7 @@ env: jobs: make_release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 defaults: run: working-directory: ./wayvr diff --git a/.github/workflows/scripts/appimage_prepare_env.sh b/.github/workflows/scripts/appimage_prepare_env.sh index 7b7f14f3..857d81c7 100755 --- a/.github/workflows/scripts/appimage_prepare_env.sh +++ b/.github/workflows/scripts/appimage_prepare_env.sh @@ -1,7 +1,5 @@ #!/bin/sh -sudo add-apt-repository -syn universe -sudo add-apt-repository -syn ppa:pipewire-debian/pipewire-upstream || sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 25088A0359807596 sudo apt-get update sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev libxcb-glx0 libxcb-glx0-dev rustup update diff --git a/Cargo.lock b/Cargo.lock index dcfac810..0be28f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,16 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.2", +] + [[package]] name = "anstream" version = "0.6.21" @@ -677,6 +687,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ + "annotate-snippets", "bitflags 2.10.0", "cexpr", "clang-sys", @@ -880,9 +891,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.8" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -1051,7 +1062,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.10+spec-1.1.0", + "toml", "winnow", "yaml-rust2", ] @@ -1096,9 +1107,6 @@ name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] [[package]] name = "core-foundation" @@ -2881,24 +2889,23 @@ dependencies = [ [[package]] name = "libspa" -version = "0.8.0" -source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0" +version = "0.9.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=670b0aabf04263ece5dc1e752e5d64aeae3056e8#670b0aabf04263ece5dc1e752e5d64aeae3056e8" dependencies = [ "bitflags 2.10.0", "cc", - "convert_case 0.6.0", "cookie-factory", "libc", "libspa-sys", - "nix 0.27.1", - "nom 7.1.3", + "nom 8.0.0", + "rustix 1.1.3", "system-deps", ] [[package]] name = "libspa-sys" -version = "0.8.0" -source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0" +version = "0.9.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=670b0aabf04263ece5dc1e752e5d64aeae3056e8#670b0aabf04263ece5dc1e752e5d64aeae3056e8" dependencies = [ "bindgen", "cc", @@ -3167,17 +3174,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -3991,24 +3987,21 @@ dependencies = [ [[package]] name = "pipewire" -version = "0.8.0" -source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0" +version = "0.9.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=670b0aabf04263ece5dc1e752e5d64aeae3056e8#670b0aabf04263ece5dc1e752e5d64aeae3056e8" dependencies = [ - "anyhow", "bitflags 2.10.0", "libc", "libspa", "libspa-sys", - "nix 0.27.1", - "once_cell", "pipewire-sys", - "thiserror 1.0.69", + "rustix 1.1.3", ] [[package]] name = "pipewire-sys" -version = "0.8.0" -source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0" +version = "0.9.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=670b0aabf04263ece5dc1e752e5d64aeae3056e8#670b0aabf04263ece5dc1e752e5d64aeae3056e8" dependencies = [ "bindgen", "libspa-sys", @@ -4116,7 +4109,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit", ] [[package]] @@ -4946,15 +4939,6 @@ dependencies = [ "syn 2.0.113", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.4" @@ -5454,14 +5438,14 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.2" +version = "7.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.23", + "toml", "version-compare", ] @@ -5479,9 +5463,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" @@ -5684,40 +5668,21 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - [[package]] name = "toml" version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ + "indexmap 2.12.1", "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", + "toml_writer", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -5727,19 +5692,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.12.1", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "winnow", -] - [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -5747,7 +5699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.12.1", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] @@ -5761,6 +5713,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tracing" version = "0.1.44" diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index 316f66ff..a3eaf6b0 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -481,7 +481,10 @@ 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), + #[cfg(feature = "openxr")] monado: data.monado_state.is_some(), + #[cfg(not(feature = "openxr"))] + monado: false, } } diff --git a/wlx-capture/Cargo.toml b/wlx-capture/Cargo.toml index 8ff3a9e0..5294556e 100644 --- a/wlx-capture/Cargo.toml +++ b/wlx-capture/Cargo.toml @@ -32,8 +32,8 @@ drm-fourcc = "2.2.0" idmap = { workspace = true } libc = "0.2.178" log = { workspace = true } -pipewire = { git = "https://gitlab.freedesktop.org/galister/pipewire-rs.git", rev = "6ef2cd1af41e5e8844c63c37ea9a01f0758079b0", features = [ - "v0_3_33", +pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", rev = "670b0aabf04263ece5dc1e752e5d64aeae3056e8", features = [ + "v0_3_33", "v0_3_65", ], optional = true } #pipewire = { version = "0.8.0", features = ["v0_3_33"], optional = true } rxscreen = { version = "0.1.7", features = [ diff --git a/wlx-capture/src/pipewire.rs b/wlx-capture/src/pipewire.rs index 3d5de1cf..f3e5cdd5 100644 --- a/wlx-capture/src/pipewire.rs +++ b/wlx-capture/src/pipewire.rs @@ -17,14 +17,17 @@ use drm_fourcc::DrmFormat; use drm_fourcc::DrmFourcc; use drm_fourcc::DrmModifier; use pipewire as pw; +use pipewire::context::ContextRc; +use pipewire::main_loop::MainLoopRc; +use pipewire::spa::buffer::meta::MetaVideoTransformValue; +use pipewire::spa::buffer::meta::{MetaCursor, MetaHeader, MetaHeaderFlags, MetaVideoTransform}; +use pipewire::stream::StreamRc; use pw::spa; +use pw::Error; use pw::properties::properties; use pw::stream::{Stream, StreamFlags}; -use pw::{Error, context::Context, main_loop::MainLoop}; use spa::buffer::DataType; -use spa::buffer::MetaData; -use spa::buffer::MetaType; use spa::param::ParamType; use spa::param::video::VideoFormat; use spa::param::video::VideoInfoRaw; @@ -278,12 +281,12 @@ where R: Any, { log::debug!("{}: pipewire main_loop start", &name); - let main_loop = MainLoop::new(None)?; - let context = Context::new(&main_loop)?; - let core = context.connect(None)?; + let main_loop = MainLoopRc::new(None)?; + let context = ContextRc::new(&main_loop, None)?; + let core = context.connect_rc(None)?; - let stream = Stream::new( - &core, + let stream = StreamRc::new( + core, &name, properties! { *pw::keys::MEDIA_TYPE => "Video", @@ -377,36 +380,35 @@ where } if let Some(mut buffer) = maybe_buffer { - if let MetaData::Header(header) = buffer.find_meta_data(MetaType::Header) - && header.flags & spa::sys::SPA_META_HEADER_FLAG_CORRUPTED != 0 + if let Some(header) = buffer.find_meta::() + && header.flags().contains(MetaHeaderFlags::CORRUPTED) { log::warn!("{}: PipeWire buffer is corrupt.", &name); return; } - if let MetaData::VideoTransform(transform) = - buffer.find_meta_data(MetaType::VideoTransform) - { - format.transform = match transform.transform { - spa::sys::SPA_META_TRANSFORMATION_None => Transform::Normal, - spa::sys::SPA_META_TRANSFORMATION_90 => Transform::Rotated90, - spa::sys::SPA_META_TRANSFORMATION_180 => Transform::Rotated180, - spa::sys::SPA_META_TRANSFORMATION_270 => Transform::Rotated270, - spa::sys::SPA_META_TRANSFORMATION_Flipped => Transform::Flipped, - spa::sys::SPA_META_TRANSFORMATION_Flipped90 => Transform::Flipped90, - spa::sys::SPA_META_TRANSFORMATION_Flipped180 => Transform::Flipped180, - spa::sys::SPA_META_TRANSFORMATION_Flipped270 => Transform::Flipped270, + + if let Some(transform) = buffer.find_meta::() { + format.transform = match transform.transform() { + MetaVideoTransformValue::NONE => Transform::Normal, + MetaVideoTransformValue::ROTATED90 => Transform::Rotated90, + MetaVideoTransformValue::ROTATED180 => Transform::Rotated180, + MetaVideoTransformValue::ROTATED270 => Transform::Rotated270, + MetaVideoTransformValue::FLIPPED => Transform::Flipped, + MetaVideoTransformValue::FLIPPED90 => Transform::Flipped90, + MetaVideoTransformValue::FLIPPED180 => Transform::Flipped180, + MetaVideoTransformValue::FLIPPED270 => Transform::Flipped270, _ => Transform::Undefined, }; log::debug!("{}: Transform: {:?}", &name, &format.transform); } - let mouse_meta = match buffer.find_meta_data(MetaType::Cursor) { - MetaData::Cursor(cursor) if cursor.id != 0 => Some(MouseMeta { - x: cursor.position.x as f32 / format.width as f32, - y: cursor.position.y as f32 / format.height as f32, - }), - _ => None, - }; + let mouse_meta = buffer + .find_meta::() + .filter(|c| c.is_valid()) + .map(|c| MouseMeta { + x: c.position().x as f32 / format.width as f32, + y: c.position().y as f32 / format.height as f32, + }); let datas = buffer.datas_mut(); if datas.is_empty() { From 45af52ddf94ad114bf4f5876c1bb7b20bd35b92b Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sun, 3 May 2026 18:54:31 +0200 Subject: [PATCH 038/307] wgui: Range sliders, fix swipe scrolling <-> slider conflict --- dash-frontend/src/tab/monado.rs | 22 +- dash-frontend/src/views/audio_settings.rs | 2 +- uidev/assets/gui/various_widgets.xml | 5 + wayvr/src/gui/panel/mod.rs | 2 +- wayvr/src/overlays/edit/mod.rs | 6 +- wgui/src/components/color_selector.rs | 6 +- wgui/src/components/slider.rs | 406 ++++++++++++++++------ wgui/src/parser/component_slider.rs | 16 +- wgui/src/widget/mod.rs | 15 +- 9 files changed, 336 insertions(+), 144 deletions(-) diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index ade22f89..a9c5332e 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -283,7 +283,7 @@ impl SubtabGeneralSettings { // get brightness let slider_brightness = state.fetch_component_as::("slider_brightness")?; if let Some(brightness) = frontend.interface.monado_brightness_get(data) { - slider_brightness.set_value(&mut frontend.layout.common(), brightness * 100.0); + slider_brightness.set_value_primary(&mut frontend.layout.common(), brightness * 100.0); slider_brightness.on_value_changed({ let tasks = tasks.clone(); @@ -308,11 +308,11 @@ impl SubtabGeneralSettings { // set initial values let (rgb, range_h, range_s, range_v) = config.chroma_key_params.get_rgb_and_hsv_ranges(); - slider_keying_curve.set_value(&mut common, config.chroma_key_params.curve * SLIDER_MULTIPLIER); - slider_keying_despill.set_value(&mut common, config.chroma_key_params.despill * SLIDER_MULTIPLIER); - slider_keying_hue_range.set_value(&mut common, range_h * SLIDER_MULTIPLIER); - slider_keying_saturation_range.set_value(&mut common, range_s * SLIDER_MULTIPLIER); - slider_keying_value_range.set_value(&mut common, range_v * SLIDER_MULTIPLIER); + slider_keying_curve.set_value_primary(&mut common, config.chroma_key_params.curve * SLIDER_MULTIPLIER); + slider_keying_despill.set_value_primary(&mut common, config.chroma_key_params.despill * SLIDER_MULTIPLIER); + slider_keying_hue_range.set_value_primary(&mut common, range_h * SLIDER_MULTIPLIER); + slider_keying_saturation_range.set_value_primary(&mut common, range_s * SLIDER_MULTIPLIER); + slider_keying_value_range.set_value_primary(&mut common, range_v * SLIDER_MULTIPLIER); cs_keying.set_color(&mut common, rgb); // prepare callbacks @@ -353,11 +353,11 @@ impl SubtabGeneralSettings { } fn chroma_update(&mut self, config: &mut GeneralConfig) { - let val_curve = self.slider_keying_curve.get_value(); - let val_despill = self.slider_keying_despill.get_value(); - let val_range_h = self.slider_keying_hue_range.get_value(); - let val_range_s = self.slider_keying_saturation_range.get_value(); - let val_range_v = self.slider_keying_value_range.get_value(); + let val_curve = self.slider_keying_curve.get_value_primary(); + let val_despill = self.slider_keying_despill.get_value_primary(); + let val_range_h = self.slider_keying_hue_range.get_value_primary(); + let val_range_s = self.slider_keying_saturation_range.get_value_primary(); + let val_range_v = self.slider_keying_value_range.get_value_primary(); let val_rgb = self.cs_keying.get_color(); config.chroma_key_params.despill = val_despill / SLIDER_MULTIPLIER; diff --git a/dash-frontend/src/views/audio_settings.rs b/dash-frontend/src/views/audio_settings.rs index d3900bf3..9546d961 100644 --- a/dash-frontend/src/views/audio_settings.rs +++ b/dash-frontend/src/views/audio_settings.rs @@ -804,7 +804,7 @@ impl View { let btn_mute = data.fetch_component_as::("btn_mute")?; let slider = data.fetch_component_as::("slider")?; - slider.set_value(&mut common, params.control.on_volume_request()? / VOLUME_MULT); + slider.set_value_primary(&mut common, params.control.on_volume_request()? / VOLUME_MULT); checkbox.set_checked(&mut common, params.checked); diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml index 163e7391..b2eea786 100644 --- a/uidev/assets/gui/various_widgets.xml +++ b/uidev/assets/gui/various_widgets.xml @@ -93,6 +93,11 @@ + + +
diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs index 04d067a1..c135e58a 100644 --- a/wayvr/src/gui/panel/mod.rs +++ b/wayvr/src/gui/panel/mod.rs @@ -581,7 +581,7 @@ pub fn apply_custom_command( .fetch_component_as::(element) { let value_f32 = value_str.parse::().context("Not a valid number")?; - slider.set_value(&mut com, value_f32); + slider.set_value_primary(&mut com, value_f32); } else if let Ok(radio) = panel .parser_state .fetch_component_as::(element) diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index aa0c5169..d9f913b2 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -476,17 +476,17 @@ fn reset_panel( let c = panel .parser_state .fetch_component_as::("lerp_slider")?; - c.set_value(&mut com, state.positioning.get_lerp().unwrap_or(1.0)); + c.set_value_primary(&mut com, state.positioning.get_lerp().unwrap_or(1.0)); let c = panel .parser_state .fetch_component_as::("alpha_slider")?; - c.set_value(&mut com, state.alpha); + c.set_value_primary(&mut com, state.alpha); let c = panel .parser_state .fetch_component_as::("curve_slider")?; - c.set_value(&mut com, state.curvature.unwrap_or(0.0)); + c.set_value_primary(&mut com, state.curvature.unwrap_or(0.0)); let c = panel .parser_state diff --git a/wgui/src/components/color_selector.rs b/wgui/src/components/color_selector.rs index 65d6a4e7..16631c58 100644 --- a/wgui/src/components/color_selector.rs +++ b/wgui/src/components/color_selector.rs @@ -163,9 +163,9 @@ impl ComponentColorSelector { { let mut common = layout.common(); - slider_r.set_value(&mut common, state.color.r * 255.0); - slider_g.set_value(&mut common, state.color.g * 255.0); - slider_b.set_value(&mut common, state.color.b * 255.0); + slider_r.set_value_primary(&mut common, state.color.r * 255.0); + slider_g.set_value_primary(&mut common, state.color.g * 255.0); + slider_b.set_value_primary(&mut common, state.color.b * 255.0); } slider_r.on_value_changed(self.gen_slider_callback(ColorIndex::Red)); diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index 004d4b22..86af620d 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -29,31 +29,29 @@ use crate::{ }, }; +#[derive(Default, Clone)] +pub struct Value(pub f32); + #[derive(Default)] -pub struct ValuesMinMax { - pub value: f32, +pub struct Limits { pub min_value: f32, pub max_value: f32, pub step: f32, } -impl ValuesMinMax { - fn to_normalized(&self) -> f32 { - (self.value - self.min_value) / (self.max_value - self.min_value) +impl Value { + pub fn get(&self) -> f32 { + self.0 } - fn get_from_normalized(&self, normalized: f32) -> f32 { - normalized * (self.max_value - self.min_value) + self.min_value - } - - fn set_value(&mut self, new_value: f32) -> &mut Self { - let span = self.max_value - self.min_value; - let clamped = new_value.max(self.min_value).min(self.max_value); + pub fn set(&mut self, limits: &Limits, new_value: f32) { + let span = limits.max_value - limits.min_value; + let clamped = new_value.max(limits.min_value).min(limits.max_value); // get the step index from min - let mut k = ((clamped - self.min_value) / self.step).round(); + let mut k = ((clamped - limits.min_value) / limits.step).round(); - let k_max = (span / self.step).floor(); + let k_max = (span / limits.step).floor(); if k < 0.0 { k = 0.0; } @@ -61,25 +59,50 @@ impl ValuesMinMax { k = k_max; } - let snapped = self.min_value + k * self.step; - self.value = snapped.max(self.min_value).min(self.max_value); + let snapped = limits.min_value + k * limits.step; + self.0 = snapped.max(limits.min_value).min(limits.max_value); + } +} - self +impl Limits { + fn to_normalized(&self, value: f32) -> f32 { + (value - self.min_value) / (self.max_value - self.min_value) + } + + fn get_from_normalized(&self, normalized: f32) -> f32 { + normalized * (self.max_value - self.min_value) + self.min_value } } #[derive(Default)] pub struct Params { pub style: taffy::Style, - pub values: ValuesMinMax, + pub limits: Limits, + pub value1: Value, + pub value2: Option, // range slider support pub show_value: bool, pub tooltip: Option, } +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ValueIndex { + Primary, + Secondary, /* for range sliders */ +} + +struct DraggedBy { + index: ValueIndex, + device: DeviceBitmask, +} + struct State { - dragged_by: Option, - hovered: bool, - values: ValuesMinMax, + dragged_by: Option, + hovered_body: bool, + hovered1: bool, + hovered2: bool, + value1: Value, + value2: Option, + limits: Limits, on_value_changed: Option, active_tooltip: Option>, } @@ -90,14 +113,20 @@ impl TooltipTrait for State { } } +struct SliderHandleData { + id_handle_rect: WidgetID, // Rectangle + id_text: Option, // Text + id_handle: WidgetID, +} + struct Data { body_node: taffy::NodeId, - slider_handle_rect_id: WidgetID, // Rectangle - slider_text_id: Option, // Text - slider_handle_id: WidgetID, + handle1: SliderHandleData, + handle2: Option, } pub struct SliderValueChangedEvent { + pub index: ValueIndex, pub value: f32, } @@ -113,8 +142,13 @@ impl ComponentTrait for ComponentSlider { fn refresh(&self, data: &mut RefreshData) { let mut common = data.layout.common(); let mut state = self.state.borrow_mut(); - let value = state.values.value; - state.set_value(&mut common, &self.data, value); + + let value1 = state.value1.get(); + state.set_value(&mut common, &self.data, ValueIndex::Primary, value1); + + if let Some(value2) = state.value2.as_ref().map(|v| v.get()) { + state.set_value(&mut common, &self.data, ValueIndex::Secondary, value2); + } } fn base(&self) -> &ComponentBase { @@ -127,13 +161,25 @@ impl ComponentTrait for ComponentSlider { } impl ComponentSlider { - pub fn get_value(&self) -> f32 { - self.state.borrow().values.value + pub fn get_value_primary(&self) -> f32 { + self.get_value(ValueIndex::Primary).unwrap() /* safe */ } - pub fn set_value(&self, common: &mut CallbackDataCommon, value: f32) { + pub fn get_value(&self, index: ValueIndex) -> Option { + let state = self.state.borrow(); + match index { + ValueIndex::Primary => Some(state.value1.get()), + ValueIndex::Secondary => state.value2.as_ref().map(|v| v.get()), + } + } + + pub fn set_value(&self, common: &mut CallbackDataCommon, index: ValueIndex, new_value: f32) { let mut state = self.state.borrow_mut(); - state.set_value(common, &self.data, value); + state.set_value(common, &self.data, index, new_value); + } + + pub fn set_value_primary(&self, common: &mut CallbackDataCommon, new_value: f32) { + self.set_value(common, ValueIndex::Primary, new_value); } pub fn on_value_changed(&self, func: SliderValueChangedCallback) { @@ -153,14 +199,15 @@ fn get_width(slider_body_node: taffy::NodeId, tree: &taffy::tree::TaffyTree, ) -> bool { - /* returns false if nothing changed */ - let norm = values.to_normalized(); + /* returns false if nothing has changed */ + let norm = limits.to_normalized(value); // convert normalized value to taffy percentage margin in percent let width = get_width(body_node, tree); @@ -184,11 +231,22 @@ const HANDLE_WIDTH: f32 = 32.0; const HANDLE_HEIGHT: f32 = 24.0; impl State { + fn get_hovered_index(&self) -> Option { + if self.hovered1 { + Some(ValueIndex::Primary) + } else if self.hovered2 { + Some(ValueIndex::Secondary) + } else { + None + } + } + fn update_value_to_mouse( &mut self, event_data: &event::CallbackData<'_>, data: &Data, common: &mut CallbackDataCommon, + index: ValueIndex, ) { let mouse_pos = event_data .metadata @@ -200,10 +258,10 @@ impl State { get_width(data.body_node, &common.state.tree) - HANDLE_WIDTH, ); - let target_value = self.values.get_from_normalized(norm); + let target_value = self.limits.get_from_normalized(norm); let val = target_value; - self.set_value(common, data, val); + self.set_value(common, data, index, val); } fn update_text(common: &mut CallbackDataCommon, text: &mut WidgetLabel, value: f32) { @@ -217,23 +275,47 @@ impl State { text.set_text(common, Translation::from_raw_text(&pretty)); } - fn set_value(&mut self, common: &mut CallbackDataCommon, data: &Data, value: f32) { - let before = self.values.value; - self.values.set_value(value); + fn set_value(&mut self, common: &mut CallbackDataCommon, data: &Data, index: ValueIndex, new_value: f32) { + let val1 = self.value1.get(); - let changed = self.values.value != before; + let Some(value) = (match index { + ValueIndex::Primary => Some(&mut self.value1), + ValueIndex::Secondary => self.value2.as_mut(), + }) else { + return; + }; - let Some(slider_handle_node_id) = common.state.nodes.get(data.slider_handle_id) else { + // Slider handle widget + let Some(handle_data) = (match index { + ValueIndex::Primary => Some(&data.handle1), + ValueIndex::Secondary => data.handle2.as_ref(), + }) else { + unreachable!(); + }; + + let before = value.get(); + + if index == ValueIndex::Secondary { + value.set(&self.limits, new_value.max(val1)); + } else { + value.set(&self.limits, new_value); + } + + let has_changed = value.get() != before; + + let Some(slider_handle_node_id) = common.state.nodes.get(handle_data.id_handle) else { return; }; let Ok(style) = common.state.tree.style(*slider_handle_node_id) else { return; }; + if !conf_handle_style( common.alterables, - &self.values, - data.slider_handle_id, + &self.limits, + value.get(), + handle_data.id_handle, data.body_node, style, &common.state.tree, @@ -241,20 +323,21 @@ impl State { return; // nothing changed visually } - common.alterables.mark_dirty(data.slider_handle_id); + common.alterables.mark_dirty(handle_data.id_handle); common.alterables.mark_redraw(); - if let Some(slider_text_id) = data.slider_text_id - && let Some(mut label) = common.state.widgets.get_as::(slider_text_id) + if let Some(id_text) = handle_data.id_text + && let Some(mut label) = common.state.widgets.get_as::(id_text) { - Self::update_text(common, &mut label, self.values.value); + Self::update_text(common, &mut label, value.get()); } - if changed && let Some(on_value_changed) = &self.on_value_changed { + if has_changed && let Some(on_value_changed) = &self.on_value_changed { on_value_changed( common, SliderValueChangedEvent { - value: self.values.value, + index: index, + value: value.get(), }, ) } @@ -310,18 +393,16 @@ fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID, an } fn register_event_mouse_enter( - data: Rc, state: Rc>, listeners: &mut EventListenerCollection, tooltip_info: Option, - anim_mult: f32, ) -> event::EventListenerID { listeners.register( EventListenerKind::MouseEnter, Box::new(move |common, event_data, (), ()| { common.alterables.trigger_haptics(); - state.borrow_mut().hovered = true; - on_enter_anim(common, data.slider_handle_rect_id, anim_mult); + state.borrow_mut().hovered_body = true; + ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone()); Ok(EventResult::Pass) @@ -330,10 +411,8 @@ fn register_event_mouse_enter( } fn register_event_mouse_leave( - data: Rc, state: Rc>, listeners: &mut EventListenerCollection, - anim_mult: f32, ) -> event::EventListenerID { listeners.register( EventListenerKind::MouseLeave, @@ -342,36 +421,110 @@ fn register_event_mouse_leave( { let mut state = state.borrow_mut(); - state.hovered = false; + state.hovered_body = false; state.active_tooltip = None; } - on_leave_anim(common, data.slider_handle_rect_id, anim_mult); Ok(EventResult::Pass) }), ) } +fn get_handle_dist(common: &mut CallbackDataCommon, handle: &SliderHandleData, mouse_pos: Vec2) -> f32 { + let center = common.state.get_widget_boundary(handle.id_handle).center(); + Vec2::distance(center, mouse_pos) +} + +const MAX_HOVER_DIST: f32 = 64.0; + +fn update_handle_hovers( + common: &mut CallbackDataCommon, + data: &Data, + state: &mut State, + anim_mult: f32, + mouse_pos: Vec2, +) { + let hovered1_prev = state.hovered1; + let hovered2_prev = state.hovered2; + + if !state.hovered_body { + state.hovered1 = false; + state.hovered2 = false; + } else { + let dist1 = get_handle_dist(common, &data.handle1, mouse_pos); + + let dist2 = data + .handle2 + .as_ref() + .map(|h| get_handle_dist(common, h, mouse_pos)) + .unwrap_or(std::f32::MAX); + + state.hovered1 = dist1 <= MAX_HOVER_DIST; + state.hovered2 = dist2 <= MAX_HOVER_DIST; + + // both of them are hovered, hover the closest one + if state.hovered1 && state.hovered2 { + if dist1 < dist2 { + state.hovered2 = false; + } else { + state.hovered1 = false; + } + } + } + + // hover state changed, run animations + if state.hovered1 != hovered1_prev { + if state.hovered1 && !hovered1_prev { + on_enter_anim(common, data.handle1.id_handle_rect, anim_mult); + } else { + on_leave_anim(common, data.handle1.id_handle_rect, anim_mult); + } + } + + if state.hovered2 != hovered2_prev + && let Some(handle2) = data.handle2.as_ref() + { + if state.hovered2 && !hovered2_prev { + on_enter_anim(common, handle2.id_handle_rect, anim_mult); + } else { + on_leave_anim(common, handle2.id_handle_rect, anim_mult); + } + } +} + fn register_event_mouse_motion( data: Rc, state: Rc>, listeners: &mut EventListenerCollection, + anim_mult: f32, ) -> event::EventListenerID { listeners.register( EventListenerKind::MouseMotion, Box::new(move |common, event_data, (), ()| { let mut state = state.borrow_mut(); - let CallbackMetadata::MousePosition(pos) = event_data.metadata else { + let Some(pos_relative) = event_data + .metadata + .get_mouse_pos_relative(&common.alterables.transform_stack) + else { unreachable!(); }; - if state.dragged_by.is_some_and(|device| device == pos.device) { - state.update_value_to_mouse(event_data, &data, common); - Ok(EventResult::Consumed) - } else { - Ok(EventResult::Pass) + let CallbackMetadata::MousePosition(pos) = &event_data.metadata else { + unreachable!(); + }; + + update_handle_hovers(common, &data, &mut state, anim_mult, pos_relative); + + if let Some(dragged_by) = &state.dragged_by { + if dragged_by.device == pos.device { + let index = dragged_by.index; + state.update_value_to_mouse(event_data, &data, common, index); + return Ok(EventResult::Consumed); + } } + + Ok(EventResult::Pass) }), ) } @@ -392,13 +545,18 @@ fn register_event_mouse_press( unreachable!(); }; - if state.hovered { - state.dragged_by = Some(btn.device); - state.update_value_to_mouse(event_data, &data, common); - Ok(EventResult::Consumed) - } else { - Ok(EventResult::Pass) + if !state.hovered_body { + // this slider isn't hovered at all? + return Ok(EventResult::Pass); } + + let hovered_index = state.get_hovered_index().unwrap_or(ValueIndex::Primary); + state.dragged_by = Some(DraggedBy { + device: btn.device, + index: hovered_index, + }); + state.update_value_to_mouse(event_data, &data, common, hovered_index); + Ok(EventResult::Consumed) }), ) } @@ -423,36 +581,11 @@ fn register_event_mouse_release( ) } -pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { - let mut style = params.style; - style.position = taffy::Position::Relative; - style.min_size = style.size; - style.max_size = style.size; - - let (root, slider_body_node) = ess.layout.add_child(ess.parent, WidgetDiv::create(), style)?; - let body_id = root.id; - - let (_background_id, _) = ess.layout.add_child( - body_id, - WidgetRectangle::create(WidgetRectangleParams { - color: BODY_COLOR, - round: WLength::Percent(1.0), - border_color: BODY_BORDER_COLOR, - border: 2.0, - ..Default::default() - }), - taffy::Style { - size: taffy::Size { - width: percent(1.0), - height: percent(PAD_PERCENT), - }, - position: taffy::Position::Absolute, - align_self: Some(taffy::AlignItems::Center), - justify_self: Some(taffy::JustifySelf::Center), - ..Default::default() - }, - )?; - +fn mount_slider_handle( + ess: &mut ConstructEssentials, + body_id: WidgetID, + show_value: bool, +) -> anyhow::Result { let slider_handle_style = taffy::Style { size: taffy::Size { width: length(0.0), @@ -488,15 +621,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul }, )?; - let state = State { - dragged_by: None, - hovered: false, - values: params.values, - on_value_changed: None, - active_tooltip: None, - }; - - let slider_text: Option<(WidgetPair, taffy::NodeId)> = if params.show_value { + let slider_text: Option<(WidgetPair, taffy::NodeId)> = if show_value { let label = WidgetLabel::create( &mut ess.layout.state, WidgetLabelParams { @@ -514,11 +639,66 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul None }; + Ok(SliderHandleData { + id_handle_rect: slider_handle_rect.id, + id_text: slider_text.map(|s| s.0.id), + id_handle: slider_handle.id, + }) +} + +pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { + let mut style = params.style; + style.position = taffy::Position::Relative; + style.min_size = style.size; + style.max_size = style.size; + + let (root, slider_body_node) = ess.layout.add_child(ess.parent, WidgetDiv::create(), style)?; + let body_id = root.id; + + let (_background_id, _) = ess.layout.add_child( + body_id, + WidgetRectangle::create(WidgetRectangleParams { + color: BODY_COLOR, + round: WLength::Percent(1.0), + border_color: BODY_BORDER_COLOR, + border: 2.0, + ..Default::default() + }), + taffy::Style { + size: taffy::Size { + width: percent(1.0), + height: percent(PAD_PERCENT), + }, + position: taffy::Position::Absolute, + align_self: Some(taffy::AlignItems::Center), + justify_self: Some(taffy::JustifySelf::Center), + ..Default::default() + }, + )?; + + let slider_handle1 = mount_slider_handle(ess, body_id, params.show_value)?; + let slider_handle2 = if params.value2.is_some() { + Some(mount_slider_handle(ess, body_id, params.show_value)?) + } else { + None + }; + + let state = State { + dragged_by: None, + hovered_body: false, + hovered1: false, + hovered2: false, + value1: params.value1, + value2: params.value2, + limits: params.limits, + on_value_changed: None, + active_tooltip: None, + }; + let data = Rc::new(Data { - slider_handle_rect_id: slider_handle_rect.id, body_node: slider_body_node, - slider_handle_id: slider_handle.id, - slider_text_id: slider_text.map(|s| s.0.id), + handle1: slider_handle1, + handle2: slider_handle2, }); let state = Rc::new(RefCell::new(state)); @@ -529,9 +709,9 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let listeners = &mut root.widget.state().event_listeners; let anim_mult = ess.layout.state.theme.animation_mult; vec![ - register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult), - register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult), - register_event_mouse_motion(data.clone(), state.clone(), listeners), + register_event_mouse_enter(state.clone(), listeners, params.tooltip), + register_event_mouse_leave(state.clone(), listeners), + register_event_mouse_motion(data.clone(), state.clone(), listeners, anim_mult), register_event_mouse_press(data.clone(), state.clone(), listeners), register_event_mouse_release(state.clone(), listeners), ] diff --git a/wgui/src/parser/component_slider.rs b/wgui/src/parser/component_slider.rs index ef09e8a8..d38a738a 100644 --- a/wgui/src/parser/component_slider.rs +++ b/wgui/src/parser/component_slider.rs @@ -18,7 +18,8 @@ pub fn parse_component_slider( ) -> anyhow::Result { let mut min_value = 0.0; let mut max_value = 1.0; - let mut initial_value = 0.5; + let mut initial_value1 = 0.5; + let mut initial_value2: Option = None; let mut step = 1.0; let mut show_value = 1; let mut tooltip = TooltipAttribs::default(); @@ -35,7 +36,13 @@ pub fn parse_component_slider( ctx.parse_check_f32(tag_name, key, value, &mut max_value); } "value" => { - ctx.parse_check_f32(tag_name, key, value, &mut initial_value); + ctx.parse_check_f32(tag_name, key, value, &mut initial_value1); + } + "value2" => { + let mut val = 0.0; + if ctx.parse_check_f32(tag_name, key, value, &mut val) { + initial_value2 = Some(val); + } } "step" => { ctx.parse_check_f32(tag_name, key, value, &mut step); @@ -56,12 +63,13 @@ pub fn parse_component_slider( }, slider::Params { style, - values: slider::ValuesMinMax { + limits: slider::Limits { min_value, max_value, - value: initial_value, step, }, + value1: slider::Value(initial_value1), + value2: initial_value2.map(|v| slider::Value(v)), show_value: show_value != 0, tooltip: tooltip.get_info(), }, diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index 961d916d..f3e877eb 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -568,14 +568,6 @@ impl WidgetState { event: &Event, ) -> anyhow::Result { match &event { - Event::MouseDown(e) => { - // firstly, check if this widget is scrollable at all - let (active_x, active_y) = get_scroll_active_axis(¶ms.style, ¶ms.taffy_layout); - if active_x || active_y { - self.data.press_down_start_mouse_pos = Some(e.pos); - self.data.swipe_scroll_start = self.data.scrolling_target; - } - } Event::MouseUp(_e) => { if self.data.swipe_running { self.data.swipe_running = false; @@ -654,6 +646,13 @@ impl WidgetState { res = Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseCancel, CallbackMetadata::None)?); } Event::MouseDown(e) => { + // firstly, check if this widget is scrollable at all + let (active_x, active_y) = get_scroll_active_axis(&invoke_data.params.style, &invoke_data.params.taffy_layout); + if active_x || active_y { + self.data.press_down_start_mouse_pos = Some(e.pos); + self.data.swipe_scroll_start = self.data.scrolling_target; + } + if hovered && self.data.set_device_pressed(e.device, true) { res = Some(self.invoke_listeners( &mut invoke_data, From f30c650bbac3e5d8b3d3de60dc3d633bcd64a368 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sun, 3 May 2026 19:13:45 +0200 Subject: [PATCH 039/307] wgui: add `EventResult::ConsumedExclusive` for Slider components This prevents us from accidentally swipe-scrolling the content if the user tries to change a slider value. --- wgui/src/components/slider.rs | 2 +- wgui/src/layout.rs | 16 ++++++------ wgui/src/widget/mod.rs | 47 ++++++++++++++++++++++++----------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index 86af620d..fa61b8bf 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -556,7 +556,7 @@ fn register_event_mouse_press( index: hovered_index, }); state.update_value_to_mouse(event_data, &data, common, hovered_index); - Ok(EventResult::Consumed) + Ok(EventResult::ConsumedExclusive) }), ) } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 6bcda51f..0ff8f588 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -462,15 +462,13 @@ impl Layout { // check children first self.push_event_children(node_id, event, event_result, alterables, user_data)?; - if event_result.can_propagate() { - widget.process_event( - &mut self.get_event_params(l, node_id, style, alterables), - widget_id, - event, - event_result, - user_data, - )?; - } + widget.process_event( + &mut self.get_event_params(l, node_id, style, alterables), + widget_id, + event, + event_result, + user_data, + )?; } } diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index f3e877eb..bcbc1dfb 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -211,15 +211,19 @@ pub struct EventParams<'a> { #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] pub enum EventResult { - NoHit, // event was pushed but has not found a listener (yet) - Pass, // widget acknowledged it and allows the event to propagate further - Consumed, // widget triggered an action, do not propagate further + NoHit, // event was pushed but has not found a listener (yet) + Pass, // widget acknowledged it and allows the event to propagate further + Consumed, // widget triggered an action, do not propagate further + ConsumedExclusive, // same as `Consumed`, but with force-disabled swipe-scroll event (used in sliders for example) } impl EventResult { #[must_use] - pub const fn can_propagate(self) -> bool { - !matches!(self, EventResult::Consumed) + pub const fn can_propagate(&self) -> bool { + match &self { + EventResult::Consumed | EventResult::ConsumedExclusive => false, + _ => true, + } } #[must_use] @@ -351,8 +355,6 @@ impl WidgetState { } pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) { - //return (self.get_scroll_shift_raw(info, l), false); - let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev; let scrolling = self @@ -614,6 +616,20 @@ impl WidgetState { Ok(EventResult::Pass) } + fn process_swipe_event(&mut self, event: &Event, params: &EventParams) { + let Event::MouseDown(e) = &event else { + return; + }; + + // firstly, check if this widget is scrollable at all + let (active_x, active_y) = get_scroll_active_axis(¶ms.style, ¶ms.taffy_layout); + if active_x || active_y { + log::info!("swipe"); + self.data.press_down_start_mouse_pos = Some(e.pos); + self.data.swipe_scroll_start = self.data.scrolling_target; + } + } + pub fn process_event<'a, 'b, U1: 'static, U2: 'static>( &mut self, params: &'a mut EventParams<'a>, @@ -622,6 +638,16 @@ impl WidgetState { event_result: &'a mut EventResult, user_data: &'a mut (&'b mut U1, &'b mut U2), ) -> anyhow::Result<()> { + // check if we should swipe-scroll this widget + if *event_result != EventResult::ConsumedExclusive { + self.process_swipe_event(event, params); + } + + if !event_result.can_propagate() { + // we don't want to pass any other events + return Ok(()); + } + let hovered = event.test_mouse_within_transform(params.alterables.transform_stack.get()); let mut invoke_data = InvokeData { @@ -646,13 +672,6 @@ impl WidgetState { res = Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseCancel, CallbackMetadata::None)?); } Event::MouseDown(e) => { - // firstly, check if this widget is scrollable at all - let (active_x, active_y) = get_scroll_active_axis(&invoke_data.params.style, &invoke_data.params.taffy_layout); - if active_x || active_y { - self.data.press_down_start_mouse_pos = Some(e.pos); - self.data.swipe_scroll_start = self.data.scrolling_target; - } - if hovered && self.data.set_device_pressed(e.device, true) { res = Some(self.invoke_listeners( &mut invoke_data, From ec2e0501c105103eb36ec8290825083f5d557e6b Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sun, 3 May 2026 19:35:49 +0200 Subject: [PATCH 040/307] wayvr: skip unsupported config files (Closes #510) --- wayvr/src/config.rs | 31 +++++++++++++++++++++++++++++-- wgui/src/widget/mod.rs | 1 - 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 9c89a629..237fca9d 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -1,7 +1,7 @@ use config::{Config, File}; use log::error; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use wayvr_ipc::packet_client::WvrProcessLaunchParams; use wlx_common::{ astr_containers::AStrMap, @@ -45,6 +45,32 @@ where panic!("No usable config found."); } +const SUPPORTED_EXTESIONS: [&'static str; 4] = ["yaml", "yml", "json", "json5"]; + +fn is_supported_config_file(path: &Path) -> bool { + if path.is_dir() { + return false; + } + match path.extension().and_then(|e| e.to_str()) { + Some(ext) => { + for sup in SUPPORTED_EXTESIONS { + if sup.eq_ignore_ascii_case(ext) { + return true; + } + } + + log::error!( + "Unsupported file extension: \".{}\" in {}. Ignoring file.", + ext, + path.to_string_lossy() + ); + debug_assert!(false); + false + } + _ => false, + } +} + pub fn load_config_with_conf_d( root_config_filename: &str, ctype: config_io::ConfigRoot, @@ -68,7 +94,8 @@ where if let Ok(paths_unsorted) = std::fs::read_dir(path_conf_d) { let mut paths: Vec<_> = paths_unsorted .filter_map(|r| match r { - Ok(entry) => Some(entry), + Ok(entry) if is_supported_config_file(&entry.path()) => Some(entry), + Ok(_other) => None, Err(e) => { error!("Failed to read conf.d directory: {e}"); None diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index bcbc1dfb..f82aea71 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -624,7 +624,6 @@ impl WidgetState { // firstly, check if this widget is scrollable at all let (active_x, active_y) = get_scroll_active_axis(¶ms.style, ¶ms.taffy_layout); if active_x || active_y { - log::info!("swipe"); self.data.press_down_start_mouse_pos = Some(e.pos); self.data.swipe_scroll_start = self.data.scrolling_target; } From cdd658d4ea8fc0309bbd658ab9fee714695c4282 Mon Sep 17 00:00:00 2001 From: oo8dev Date: Thu, 7 May 2026 22:06:54 +0200 Subject: [PATCH 041/307] wayvr: IPC: Fix dangling fds (#513) --- wayvr/src/ipc/ipc_server.rs | 91 +++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/wayvr/src/ipc/ipc_server.rs b/wayvr/src/ipc/ipc_server.rs index 4490ac21..8a33c87f 100644 --- a/wayvr/src/ipc/ipc_server.rs +++ b/wayvr/src/ipc/ipc_server.rs @@ -42,35 +42,40 @@ pub fn send_packet(conn: &mut local_socket::Stream, data: &[u8]) -> anyhow::Resu Ok(()) } -fn read_check(expected_size: u32, res: std::io::Result) -> bool { +fn read_check(expected_size: u32, res: std::io::Result) -> anyhow::Result { match res { Ok(count) => { if count == 0 { - return false; + anyhow::bail!("End of stream"); } if count as u32 == expected_size { - true // read succeeded + return Ok(true); // read succeeded } else { log::error!("count {count} is not {expected_size}"); - false + return Ok(false); } } - Err(_e) => { - //log::error!("failed to get packet size: {}", e); - false - } + Err(e) => match e.kind() { + std::io::ErrorKind::WouldBlock => { + // no incoming data (this socket is non-blocking), try again later + return Ok(false); + } + _ => { + anyhow::bail!("Connection error: {:?}", e); + } + }, } } type Payload = SmallVec<[u8; 64]>; -fn read_payload(conn: &mut local_socket::Stream, size: u32) -> Option { +fn read_payload(conn: &mut local_socket::Stream, size: u32) -> anyhow::Result> { let mut payload = Payload::new(); payload.resize(size as usize, 0); - if read_check(size, conn.read(&mut payload)) { - Some(payload) + if read_check(size, conn.read(&mut payload))? { + Ok(Some(payload)) } else { - None + Ok(None) } } @@ -433,36 +438,33 @@ impl Connection { Ok(()) } - fn process_check_payload(&mut self, params: &mut TickParams, payload: Payload) -> bool { - log::debug!("payload size {}", payload.len()); - + fn process_check_payload( + &mut self, + params: &mut TickParams, + payload: Payload, + ) -> anyhow::Result<()> { if let Err(e) = self.process_payload(params, payload) { - log::error!("Invalid payload from the client, closing connection: {e}"); // send also error message directly to the client before disconnecting self.kill(format!("{e}").as_str()); - false - } else { - true + anyhow::bail!("Invalid payload from the client, closing connection: {e}"); } + Ok(()) } - fn read_packet(&mut self, params: &mut TickParams) -> bool { + fn read_packet(&mut self, params: &mut TickParams) -> anyhow::Result { if let Some(payload_size) = self.next_packet { - let Some(payload) = read_payload(&mut self.conn, payload_size) else { - // still failed to read payload, try in next tick - return false; + let Some(payload) = read_payload(&mut self.conn, payload_size)? else { + // still failed to read payload, try in the next tick + return Ok(false); }; - if !self.process_check_payload(params, payload) { - return false; - } - + self.process_check_payload(params, payload)?; self.next_packet = None; } let mut buf_packet_header: [u8; 4] = [0; 4]; - if !read_check(4, self.conn.read(&mut buf_packet_header)) { - return false; + if !read_check(4, self.conn.read(&mut buf_packet_header))? { + return Ok(false); } let payload_size = u32::from_be_bytes(buf_packet_header[0..4].try_into().unwrap()); // 0-3 bytes (u32 size) @@ -475,30 +477,43 @@ impl Connection { "Client sent a packet header with the size over {size_limit} bytes, closing connection." ); self.kill("Too big packet received (over 128 KiB)"); - return false; + return Ok(false); } - let Some(payload) = read_payload(&mut self.conn, payload_size) else { + let Some(payload) = read_payload(&mut self.conn, payload_size)? else { // failed to read payload, try in next tick self.next_packet = Some(payload_size); - return false; + return Ok(false); }; - if !self.process_check_payload(params, payload) { - return false; - } + self.process_check_payload(params, payload)?; - true + Ok(true) } fn tick(&mut self, params: &mut TickParams) { - while self.read_packet(params) {} + loop { + match self.read_packet(params) { + Ok(loop_further) => { + if !loop_further { + break; + } + } + Err(e) => { + log::debug!("Disconnecting client: {:?}", e); + self.alive = false; + break; + } + } + } } } impl Drop for Connection { fn drop(&mut self) { - log::info!("Connection closed"); + if let Some(auth) = &self.auth { + log::info!("IPC: Client \"{}\" disconnected.", auth.client_name); + } } } From 2309f89701005b0b2faf03a27993e432af2bfe1a Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Tue, 12 May 2026 17:11:15 +0900 Subject: [PATCH 042/307] update issue template --- .github/ISSUE_TEMPLATE/bug-report.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 582645a7..8eba3098 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -22,25 +22,28 @@ If this is a regression, please mention which version was working previously. **Kernel version**: **VR Runtime**: -- [ ] Monado/WiVRn -- [ ] SteamVR/ALVR +- [ ] Monado +- [ ] WiVRn +- [ ] ALVR +- [ ] SteamLink +- [ ] SteamVR (not SteamLink) **GPU models and driver versions**: ## Overlay Logs - From 557a0775c3daf501ded176ad99e5536dd965b78e Mon Sep 17 00:00:00 2001 From: steve hocktail Date: Tue, 12 May 2026 09:53:14 -0400 Subject: [PATCH 043/307] add a toast for uinput errors (#494) * add a toast for uinput errors this makes it more obvious if missing uinput permissions are the reason your keyboard and mouse aren't working. * uinput toaster part 2: the cargo formattening * increase uinput error toast's timeout to 30 seconds --------- Co-authored-by: galister <22305755+galister@users.noreply.github.com> --- wayvr/src/state.rs | 12 ++++++-- wayvr/src/subsystem/hid/mod.rs | 50 +++++++++++++++++++++++++--------- wayvr/src/subsystem/input.rs | 32 ++++++++++++++++++---- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index a2546111..478fb261 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -90,7 +90,7 @@ impl AppState { .log_err("Could not initialize WayVR Server") .ok(); - let mut hid_provider = HidWrapper::new(); + let (hid_provider, mut hid_error) = HidWrapper::new(); #[cfg(feature = "osc")] let osc_sender = crate::subsystem::osc::OscSender::new(session.config.osc_out_port).ok(); @@ -154,8 +154,9 @@ impl AppState { desktop_finder.refresh(); let lang_provider = WayVRLangProvider::from_config(&session.config); + let executor = Rc::new(smol::LocalExecutor::new()); - Ok(Self { + let mut app_state = Self { session, tasks, gfx, @@ -188,7 +189,12 @@ impl AppState { #[cfg(feature = "openxr")] monado_state: None, - }) + }; + + if let Some(error_toast) = hid_error { + error_toast.submit(&mut app_state); + } + Ok(app_state) } #[cfg(feature = "openxr")] diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index ed446d12..b3d5e611 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -11,8 +11,11 @@ use std::mem::transmute; use std::sync::LazyLock; use std::{fs::File, sync::atomic::AtomicBool}; use strum::{EnumIter, EnumString, IntoEnumIterator}; +use wlx_common::overlays::ToastTopic; use xkbcommon::xkb; +use crate::overlays::toast::Toast; + #[cfg(feature = "wayland")] pub mod wayland; @@ -21,27 +24,48 @@ mod x11; pub static USE_UINPUT: AtomicBool = AtomicBool::new(true); -pub(super) fn initialize() -> Box { +pub(super) fn initialize() -> Result { if !USE_UINPUT.load(std::sync::atomic::Ordering::Relaxed) { - log::info!("Uinput disabled by user."); - return Box::new(DummyProvider {}); + const UINPUT_DISABLED: &str = "Uinput disabled by user."; + log::info!("{UINPUT_DISABLED}"); + return Err(Toast::new( + ToastTopic::System, + String::with_capacity(0), + String::from(UINPUT_DISABLED), + ) + .with_timeout(5.0)); } if let Some(uinput) = UInputProvider::try_new() { log::info!("Initialized uinput."); - return Box::new(uinput); + return Ok(uinput); } - log::error!("Could not create uinput provider. Keyboard/Mouse input will not work!"); - log::error!("Check if the uinput kernel module is loaded: lsmod | grep uinput"); - log::error!( - " - If not loaded, follow your distro's instructions to load the uinput kernel module." - ); - log::error!("Check if you're in input group, run: id -nG"); + const CHECK_UINPUT_MESSAGE: &str = + "Could not create uinput provider. Keyboard/Mouse input will not work! + +Check if the uinput kernel module is loaded: lsmod | grep uinput + - If not loaded, follow your distro's instructions to load the uinput kernel module. + +Check if you're in input group, run: id -nG"; + let mut full_uinput_error = String::from(CHECK_UINPUT_MESSAGE); if let Ok(user) = std::env::var("USER") { - log::error!(" - To add yourself to the input group, run: sudo usermod -aG input {user}"); - log::error!(" - After adding yourself to the input group, you will need to reboot."); + let check_group_message = format!( + " - To add yourself to the input group, run: sudo usermod -aG input {user} + - After adding yourself to the input group, you will need to reboot." + ); + full_uinput_error.push_str(&check_group_message); } - Box::new(DummyProvider {}) + for error_line in full_uinput_error.lines() { + if !error_line.is_empty() { + log::error!("{error_line}"); + } + } + Err(Toast::new( + ToastTopic::Error, + String::with_capacity(0), + full_uinput_error, + ) + .with_timeout(30.0)) } pub struct WheelDelta { diff --git a/wayvr/src/subsystem/input.rs b/wayvr/src/subsystem/input.rs index c51f4876..51716405 100644 --- a/wayvr/src/subsystem/input.rs +++ b/wayvr/src/subsystem/input.rs @@ -1,6 +1,10 @@ use super::hid::{self, HidProvider, VirtualKey}; -use crate::{backend::wayvr::WvrServerState, subsystem::hid::XkbKeymap}; +use crate::{ + backend::wayvr::WvrServerState, + overlays::toast::Toast, + subsystem::hid::{DummyProvider, XkbKeymap}, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum KeyboardFocus { @@ -15,12 +19,28 @@ pub struct HidWrapper { } impl HidWrapper { - pub fn new() -> Self { - Self { - keyboard_focus: KeyboardFocus::PhysicalScreen, - inner: hid::initialize(), - keymap: None, + pub fn new() -> (Self, Option) { + let hid_result = hid::initialize(); + let hid_provider: Box; + let error: Option; + match hid_result { + Ok(uinput) => { + hid_provider = Box::new(uinput); + error = None; + } + Err(toast) => { + hid_provider = Box::new(DummyProvider {}); + error = Some(toast); + } } + ( + Self { + keyboard_focus: KeyboardFocus::PhysicalScreen, + inner: hid_provider, + keymap: None, + }, + error, + ) } pub fn send_key_routed( From ed1d7088029aaff0ea187bca379bb6e569fcb124 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Tue, 12 May 2026 22:57:56 +0900 Subject: [PATCH 044/307] mnd playspace: fetch offset on drag/rotate start --- wayvr/src/backend/openxr/playspace.rs | 62 ++++++++++++++++++--------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 3f02d377..21457765 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -1,5 +1,6 @@ use glam::{Affine3A, Quat, Vec3A, vec3a}; -use libmonado::{Monado, Pose, ReferenceSpaceType}; +use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType}; +use wgui::log::LogErr; use crate::{ backend::{input::InputState, task::PlayspaceTask}, @@ -16,7 +17,6 @@ struct MoverData { } pub(super) struct PlayspaceMover { - last_transform: Affine3A, drag: Option>, rotate: Option>, } @@ -25,19 +25,14 @@ impl PlayspaceMover { pub fn new(monado: &mut Monado) -> anyhow::Result { log::info!("Monado: using space offset API"); - let Ok(stage) = monado.get_reference_space_offset(ReferenceSpaceType::Stage) else { + if matches!( + monado.get_reference_space_offset(ReferenceSpaceType::Stage), + Err(MndResult::ErrorInvalidVersion) + ) { anyhow::bail!("Space offsets not supported."); }; - log::debug!("STAGE is at {:?}, {:?}", stage.position, stage.orientation); - - // initial offset - let last_transform = - Affine3A::from_rotation_translation(stage.orientation.into(), stage.position.into()); - Ok(Self { - last_transform, - drag: None, rotate: None, }) @@ -83,7 +78,6 @@ impl PlayspaceMover { if let Some(mut data) = self.rotate.take() { let pointer = &app.input_state.pointers[data.hand]; if !pointer.now.space_rotate { - self.last_transform = data.pose; log::info!("End space rotate"); return; } @@ -114,11 +108,26 @@ impl PlayspaceMover { apply_offset(data.pose, &mut monado.ipc); self.rotate = Some(data); } else { + // start space rotate for (i, pointer) in app.input_state.pointers.iter().enumerate() { if pointer.now.space_rotate { - let hand_pose = Quat::from_affine3(&(self.last_transform * pointer.raw_pose)); + let Ok(pose) = monado + .ipc + .get_reference_space_offset(ReferenceSpaceType::Stage) + .log_err("Could not get reference space offset.") + .map(|p| { + Affine3A::from_rotation_translation( + p.orientation.into(), + p.position.into(), + ) + }) + else { + return; + }; + + let hand_pose = Quat::from_affine3(&(pose * pointer.raw_pose)); self.rotate = Some(MoverData { - pose: self.last_transform, + pose, hand: i, hand_pose, }); @@ -132,7 +141,6 @@ impl PlayspaceMover { if let Some(mut data) = self.drag.take() { let pointer = &app.input_state.pointers[data.hand]; if !pointer.now.space_drag { - self.last_transform = data.pose; log::info!("End space drag"); return; } @@ -170,13 +178,26 @@ impl PlayspaceMover { apply_offset(data.pose, &mut monado.ipc); self.drag = Some(data); } else { + // start space drag for (i, pointer) in app.input_state.pointers.iter().enumerate() { if pointer.now.space_drag { - let hand_pos = self - .last_transform - .transform_point3a(pointer.raw_pose.translation); + let Ok(pose) = monado + .ipc + .get_reference_space_offset(ReferenceSpaceType::Stage) + .log_err("Could not get reference space offset.") + .map(|p| { + Affine3A::from_rotation_translation( + p.orientation.into(), + p.position.into(), + ) + }) + else { + return; + }; + + let hand_pos = pose.transform_point3a(pointer.raw_pose.translation); self.drag = Some(MoverData { - pose: self.last_transform, + pose, hand: i, hand_pose: hand_pos, }); @@ -222,8 +243,7 @@ impl PlayspaceMover { self.rotate = None; } - self.last_transform = Affine3A::IDENTITY; - apply_offset(self.last_transform, monado); + apply_offset(Affine3A::IDENTITY, monado); } pub fn fix_floor(&mut self, input: &InputState, monado: &mut Monado) { From c7b16c571372636f4a8f219368fb4f483d64acfc Mon Sep 17 00:00:00 2001 From: Snemby Date: Mon, 11 May 2026 18:19:13 +0200 Subject: [PATCH 045/307] fix space rotate the change in rotation is computed in the untransformed stage space. To produce the correct rotation change the quaternion needs to be rotated using the space offset --- wayvr/src/backend/openxr/playspace.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 21457765..0bc370b8 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -82,10 +82,11 @@ impl PlayspaceMover { return; } + let pose_quat = Quat::from_affine3(&data.pose); let new_hand = - Quat::from_affine3(&(data.pose * app.input_state.pointers[data.hand].raw_pose)); + pose_quat * Quat::from_affine3(&app.input_state.pointers[data.hand].raw_pose); - let dq = new_hand * data.hand_pose.conjugate(); + let dq = pose_quat.conjugate() * (new_hand * data.hand_pose.conjugate()) * pose_quat; let mut space_transform = if app.session.config.space_rotate_unlocked { Affine3A::from_quat(dq) } else { From 3d71746ff2d7633c0c0bfd9ade1e513ddd0355f5 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Tue, 12 May 2026 21:21:04 +0200 Subject: [PATCH 046/307] dash-frontend: watch_view_angle setting (Closes #505), re-format language files --- dash-frontend/assets/lang/de.json | 334 +++++++++--------- dash-frontend/assets/lang/en.json | 4 + dash-frontend/assets/lang/es.json | 334 +++++++++--------- dash-frontend/assets/lang/it.json | 334 +++++++++--------- dash-frontend/assets/lang/ja.json | 334 +++++++++--------- dash-frontend/assets/lang/pl.json | 334 +++++++++--------- dash-frontend/assets/lang/zh_CN.json | 334 +++++++++--------- dash-frontend/src/tab/settings/mod.rs | 8 + .../src/tab/settings/tab_features.rs | 2 + wayvr/src/config.rs | 4 + 10 files changed, 1032 insertions(+), 990 deletions(-) diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index 5d3d933f..bb3753a2 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -1,166 +1,170 @@ { - "HOME_SCREEN": "Startbildschirm", - "MONADO_RUNTIME": "Monado-Laufzeitumgebung", - "APPLICATIONS": "Anwendungen", - "GAMES": "Spiele", - "SETTINGS": "Einstellungen", - "HELLO_USER": "Hallo, {USER}!", - "GENERAL_SETTINGS": "Allgemeine Einstellungen", - "APPLICATION_LAUNCHER": "Anwendung Launcher", - "APP_SETTINGS": { - "HIDE_USERNAME": "Benutzernamen ausblenden", - "OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund", - "LOOK_AND_FEEL": "Aussehen und Verhalten", - "HIDE_GRAB_HELP": "Greif-Hilfe ausblenden", - "ANIMATION_SPEED": "UI-Animationsgeschwindigkeit", - "ROUND_MULTIPLIER": "UI-Kantenrundung", - "USE_SKYBOX": "Skybox aktivieren", - "USE_PASSTHROUGH": "Passthrough aktivieren", - "CLOCK_12H": "12-Stunden-Uhr", - "FEATURES": "Funktionen", - "NOTIFICATIONS_ENABLED": "Benachrichtigungen aktivieren", - "NOTIFICATIONS_SOUND_ENABLED": "Benachrichtigungstöne", - "KEYBOARD_SOUND_ENABLED": "Tastengeräusche", - "SPACE_DRAG_MULTIPLIER": "Raum-Drag-Multiplikator", - "SPACE_DRAG_UNLOCKED": "Erlaube Space-Drag auf allen Achsen", - "SPACE_ROTATE_UNLOCKED": "Erlaube Drehungen in allen Achsen", - "BLOCK_GAME_INPUT": "Spieleingabe blockieren", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignoriere Watch beim Blockieren der Eingabe", - "CONTROLS": "Steuerung", - "FOCUS_FOLLOWS_MOUSE_MODE": "Mausbewegung bei Triggerberührung", - "LEFT_HANDED_MOUSE": "Linkshändige Maus", - "ALLOW_SLIDING": "Stabinteraktion während des Greifens", - "INVERT_SCROLL_DIRECTION_X": "Horizontale Scrollrichtung umkehren", - "INVERT_SCROLL_DIRECTION_Y": "Vertikale Bildlaufrichtung umkehren", - "SCROLL_SPEED": "Scrollgeschwindigkeit", - "LONG_PRESS_DURATION": "Dauer für lange Drückvorgänge", - "POINTER_LERP_FACTOR": "Zeigerglättung", - "XR_CLICK_SENSITIVITY": "XR-Klicksensitivität", - "XR_CLICK_SENSITIVITY_RELEASE": "XR-Loslassempfindlichkeit", - "CLICK_FREEZE_TIME_MS": "Klick-Freeze-Zeit (ms)", - "MISC": "Verschiedenes", - "XWAYLAND_BY_DEFAULT": "Standardmäßig Apps im Kompatibilitätsmodus ausführen", - "UPRIGHT_SCREEN_FIX": "Bildschirm-Drehkorrektur", - "DOUBLE_CURSOR_FIX": "Doppelter Cursor Fix", - "SCREEN_RENDER_DOWN": "Bildschirm bei niedrigerer Auflösung rendern", - "UPRIGHT_SCREEN_FIX_HELP": "Behebt hochstehende Bildschirme auf einigen Desktops", - "DOUBLE_CURSOR_FIX_HELP": "Aktivieren Sie dies, wenn Sie 2 Cursor sehen", - "XR_CLICK_SENSITIVITY_HELP": "Analoge Trigger-Empfindlichkeit", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Muss niedriger als Klick sein", - "CLICK_FREEZE_TIME_MS_HELP": "Hilft bei der Präzision von Doppelklicks", - "LEFT_HANDED_MOUSE_HELP": "Verwenden Sie diese Option, wenn die Maustasten vertauscht sind", - "BLOCK_GAME_INPUT_HELP": "Blockiert alle Eingaben, wenn ein Overlay angefahren wird", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Blockiere die Eingabe nicht, wenn die Überwachung aktiviert ist", - "USE_SKYBOX_HELP": "Zeige einen Skybox, wenn keine Szenen-App oder Passthrough vorhanden ist", - "USE_PASSTHROUGH_HELP": "Aktiviere Passthrough, falls die XR-Laufzeitumgebung dies unterstützt", - "SCREEN_RENDER_DOWN_HELP": "Hilft bei Aliasing auf hochauflösenden Bildschirmen", - "SETS_ON_WATCH": "Sets auf der Watch", - "TROUBLESHOOTING": "Fehlerbehebung", - "CLEAR_SAVED_STATE": "Gespeicherten Zustand löschen", - "CLEAR_PIPEWIRE_TOKENS": "PipeWire Tokens löschen", - "DELETE_ALL_CONFIGS": "Konfiguration löschen", - "RESTART_SOFTWARE": "Software neu starten", - "CLEAR_SAVED_STATE_HELP": "Sets und Overlay-Positionen zurücksetzen", - "CLEAR_PIPEWIRE_TOKENS_HELP": "Bildschirmauswahl beim nächsten Start abfragen", - "DELETE_ALL_CONFIGS_HELP": "Entfernen Sie alle Konfigurationsdateien aus conf.d", - "RESTART_SOFTWARE_HELP": "Einstellungen anwenden, die einen Neustart erfordern", - "CAPTURE_METHOD": "Wayland-Bildschirmaufnahme", - "CAPTURE_METHOD_HELP": "Versuchen Sie, dies zu ändern, wenn Sie\nschwarze oder fehlerhafte Bildschirme erleben", - "KEYBOARD_MIDDLE_CLICK": "Keyboard-Mittelklick", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modifikator bei Eingabe mit violettem Laser", - "OPTION": { - "AUTO": "Automatisch", - "AUTO_HELP": "Bildschirmkopie GPU falls unterstützt,\nansonsten PipeWire GPU.", - "PIPEWIRE_HELP": "Schnelle GPU-Erfassung,\nStandard auf allen Desktops.", - "PW_FALLBACK_HELP": "Langsame Methode mit hoher CPU-Auslastung.\nVersuchen Sie es, falls PipeWire GPU nicht funktioniert.", - "SCREENCOPY_GPU_HELP": "Schnell, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway", - "NONE": "Keine", - "HMD_PINCH": "HMD + Kneifen", - "EYE_PINCH": "Auge + Kneifen", - "EYE_ONLY": "Nur Auge", - "HMD_ONLY": "Nur HMD" - }, - "AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen", - "HANDSFREE_POINTER": "Freihändige Modus", - "HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.", - "UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität", - "RESET_PLAYSPACE": "Spielbereich zurücksetzen", - "RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen.", - "BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist", - "LANGUAGE": "Sprache", - "REQUIRES_RESTART": "Erfordert Neustart", - "GRID_OPACITY": "Bodenraster-Undurchsichtigkeit", - "GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist" - }, - "HELLO": "Hallo!", - "AUDIO": { - "VOLUME": "Lautstärke", - "SETTINGS": "Audioeinstellungen", - "AUTO_SWITCH_TO_VR_AUDIO": "Automatisch auf VR-Audio umschalten", - "SPEAKERS": "Lautsprecher", - "MICROPHONES": "Mikrofone", - "CARDS": "Sound-Karten", - "SELECT_AUDIO_CARD_PROFILE": "Wählen Sie das Sound-Kartenprofil", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Keine VR-Lautsprecher gefunden. Schalten Sie diese manuell um.", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Kein VR-Mikrofon gefunden. Schalten Sie es manuell um.", - "FAILED_TO_SWITCH_MICROPHONE": "Fehler beim Wechseln des Mikrofons", - "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon erfolgreich umgeschaltet", - "SPEAKERS_SET_SUCCESSFULLY": "Lautsprecher erfolgreich umgeschaltet" - }, - "ACTIONS": { - "RECENTER_PLAYSPACE": "Playspace neu zentrieren" - }, - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "Auflösung" - }, - "WIDTH": "Breite", - "HEIGHT": "Höhe", - "HIDE": "Verbergen", - "REMOVE": "Entfernen", - "SHOW": "Anzeigen", - "FAILED_TO_LAUNCH_APPLICATION": "Fehler beim Starten der Anwendung:", - "APPLICATION_STARTED": "Anwendung gestartet", - "CLOSE_WINDOW": "Fenster schließen", - "GAME_LIST": { - "NO_GAMES_FOUND": "Keine Spiele gefunden", - "RUNNING_GAMES_LIST": "Liste der laufenden Spiele" - }, - "TERMINATE_PROCESS": "Prozess beenden", - "GAME_LAUNCHED": "Spiel gestartet", - "APP_LAUNCHER": { - "MODE": { - "NATIVE": "Nativer Modus", - "CAGE": "Kompatibilitätsmodus (Cage)" - }, - "RES_TITLE": "Auflösung", - "ASPECT_TITLE": "Seitenverhältnis", - "ASPECT": { - "WIDE": "Breit", - "SEMI_WIDE": "Halb-breit", - "SQUARE": "Quadratisch", - "SEMI_TALL": "Halbhoch", - "TALL": "Hoch" - }, - "POS_TITLE": "Positionierung", - "POS": { - "FLOATING": "Schwebend", - "ANCHORED": "Verankert", - "STATIC": "Statisch", - "FLOATING_HELP": "Bewegt sich unabhängig, zentriert sich bei Anzeige neu.", - "ANCHORED_HELP": "Bleibt relativ zur Mittelmarkierung an Ort und Stelle.", - "STATIC_HELP": "Keine Set zugeordnet. Wird nicht zentriert." - }, - "AUTOSTART": "Automatisch beim Start ausführen", - "LAUNCH": "Starten" - }, - "DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit", - "PROCESS_LIST": "Prozessliste", - "REFRESH": "Aktualisieren", - "PROCESS": { - "STOP": "Stopp", - "FORCE_KILL": "Erzwinge Beenden" - }, - "DEBUG_INFO": "Debug-Informationen" -} \ No newline at end of file + "HOME_SCREEN": "Startbildschirm", + "MONADO_RUNTIME": "Monado-Laufzeitumgebung", + "APPLICATIONS": "Anwendungen", + "GAMES": "Spiele", + "SETTINGS": "Einstellungen", + "HELLO_USER": "Hallo, {USER}!", + "GENERAL_SETTINGS": "Allgemeine Einstellungen", + "APPLICATION_LAUNCHER": "Anwendung Launcher", + "APP_SETTINGS": { + "HIDE_USERNAME": "Benutzernamen ausblenden", + "OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund", + "LOOK_AND_FEEL": "Aussehen und Verhalten", + "HIDE_GRAB_HELP": "Greif-Hilfe ausblenden", + "ANIMATION_SPEED": "UI-Animationsgeschwindigkeit", + "ROUND_MULTIPLIER": "UI-Kantenrundung", + "USE_SKYBOX": "Skybox aktivieren", + "USE_PASSTHROUGH": "Passthrough aktivieren", + "CLOCK_12H": "12-Stunden-Uhr", + "FEATURES": "Funktionen", + "NOTIFICATIONS_ENABLED": "Benachrichtigungen aktivieren", + "NOTIFICATIONS_SOUND_ENABLED": "Benachrichtigungstöne", + "KEYBOARD_SOUND_ENABLED": "Tastengeräusche", + "SPACE_DRAG_MULTIPLIER": "Raum-Drag-Multiplikator", + "SPACE_DRAG_UNLOCKED": "Erlaube Space-Drag auf allen Achsen", + "SPACE_ROTATE_UNLOCKED": "Erlaube Drehungen in allen Achsen", + "BLOCK_GAME_INPUT": "Spieleingabe blockieren", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignoriere Watch beim Blockieren der Eingabe", + "CONTROLS": "Steuerung", + "FOCUS_FOLLOWS_MOUSE_MODE": "Mausbewegung bei Triggerberührung", + "LEFT_HANDED_MOUSE": "Linkshändige Maus", + "ALLOW_SLIDING": "Stabinteraktion während des Greifens", + "INVERT_SCROLL_DIRECTION_X": "Horizontale Scrollrichtung umkehren", + "INVERT_SCROLL_DIRECTION_Y": "Vertikale Bildlaufrichtung umkehren", + "SCROLL_SPEED": "Scrollgeschwindigkeit", + "LONG_PRESS_DURATION": "Dauer für lange Drückvorgänge", + "POINTER_LERP_FACTOR": "Zeigerglättung", + "XR_CLICK_SENSITIVITY": "XR-Klicksensitivität", + "XR_CLICK_SENSITIVITY_RELEASE": "XR-Loslassempfindlichkeit", + "CLICK_FREEZE_TIME_MS": "Klick-Freeze-Zeit (ms)", + "MISC": "Verschiedenes", + "XWAYLAND_BY_DEFAULT": "Standardmäßig Apps im Kompatibilitätsmodus ausführen", + "UPRIGHT_SCREEN_FIX": "Bildschirm-Drehkorrektur", + "DOUBLE_CURSOR_FIX": "Doppelter Cursor Fix", + "SCREEN_RENDER_DOWN": "Bildschirm bei niedrigerer Auflösung rendern", + "UPRIGHT_SCREEN_FIX_HELP": "Behebt hochstehende Bildschirme auf einigen Desktops", + "DOUBLE_CURSOR_FIX_HELP": "Aktivieren Sie dies, wenn Sie 2 Cursor sehen", + "XR_CLICK_SENSITIVITY_HELP": "Analoge Trigger-Empfindlichkeit", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Muss niedriger als Klick sein", + "CLICK_FREEZE_TIME_MS_HELP": "Hilft bei der Präzision von Doppelklicks", + "LEFT_HANDED_MOUSE_HELP": "Verwenden Sie diese Option, wenn die Maustasten vertauscht sind", + "BLOCK_GAME_INPUT_HELP": "Blockiert alle Eingaben, wenn ein Overlay angefahren wird", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Blockiere die Eingabe nicht, wenn die Überwachung aktiviert ist", + "USE_SKYBOX_HELP": "Zeige einen Skybox, wenn keine Szenen-App oder Passthrough vorhanden ist", + "WATCH_VIEW_ANGLE_MIN": "Blickwinkel Min (Watch)", + "WATCH_VIEW_ANGLE_MIN_HELP": "Winkel, ab dem die Watch zu verblassen beginnt", + "WATCH_VIEW_ANGLE_MAX": "Blickwinkel Max (Watch)", + "WATCH_VIEW_ANGLE_MAX_HELP": "Winkel, ab dem die Watch voll sichtbar wird", + "USE_PASSTHROUGH_HELP": "Aktiviere Passthrough, falls die XR-Laufzeitumgebung dies unterstützt", + "SCREEN_RENDER_DOWN_HELP": "Hilft bei Aliasing auf hochauflösenden Bildschirmen", + "SETS_ON_WATCH": "Sets auf der Watch", + "TROUBLESHOOTING": "Fehlerbehebung", + "CLEAR_SAVED_STATE": "Gespeicherten Zustand löschen", + "CLEAR_PIPEWIRE_TOKENS": "PipeWire Tokens löschen", + "DELETE_ALL_CONFIGS": "Konfiguration löschen", + "RESTART_SOFTWARE": "Software neu starten", + "CLEAR_SAVED_STATE_HELP": "Sets und Overlay-Positionen zurücksetzen", + "CLEAR_PIPEWIRE_TOKENS_HELP": "Bildschirmauswahl beim nächsten Start abfragen", + "DELETE_ALL_CONFIGS_HELP": "Entfernen Sie alle Konfigurationsdateien aus conf.d", + "RESTART_SOFTWARE_HELP": "Einstellungen anwenden, die einen Neustart erfordern", + "CAPTURE_METHOD": "Wayland-Bildschirmaufnahme", + "CAPTURE_METHOD_HELP": "Versuchen Sie, dies zu ändern, wenn Sie\nschwarze oder fehlerhafte Bildschirme erleben", + "KEYBOARD_MIDDLE_CLICK": "Keyboard-Mittelklick", + "KEYBOARD_MIDDLE_CLICK_HELP": "Modifikator bei Eingabe mit violettem Laser", + "OPTION": { + "AUTO": "Automatisch", + "AUTO_HELP": "Bildschirmkopie GPU falls unterstützt,\nansonsten PipeWire GPU.", + "PIPEWIRE_HELP": "Schnelle GPU-Erfassung,\nStandard auf allen Desktops.", + "PW_FALLBACK_HELP": "Langsame Methode mit hoher CPU-Auslastung.\nVersuchen Sie es, falls PipeWire GPU nicht funktioniert.", + "SCREENCOPY_GPU_HELP": "Schnell, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway", + "NONE": "Keine", + "HMD_PINCH": "HMD + Kneifen", + "EYE_PINCH": "Auge + Kneifen", + "EYE_ONLY": "Nur Auge", + "HMD_ONLY": "Nur HMD" + }, + "AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen", + "HANDSFREE_POINTER": "Freihändige Modus", + "HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.", + "UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität", + "RESET_PLAYSPACE": "Spielbereich zurücksetzen", + "RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen.", + "BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist", + "LANGUAGE": "Sprache", + "REQUIRES_RESTART": "Erfordert Neustart", + "GRID_OPACITY": "Bodenraster-Undurchsichtigkeit", + "GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist" + }, + "HELLO": "Hallo!", + "AUDIO": { + "VOLUME": "Lautstärke", + "SETTINGS": "Audioeinstellungen", + "AUTO_SWITCH_TO_VR_AUDIO": "Automatisch auf VR-Audio umschalten", + "SPEAKERS": "Lautsprecher", + "MICROPHONES": "Mikrofone", + "CARDS": "Sound-Karten", + "SELECT_AUDIO_CARD_PROFILE": "Wählen Sie das Sound-Kartenprofil", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Keine VR-Lautsprecher gefunden. Schalten Sie diese manuell um.", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Kein VR-Mikrofon gefunden. Schalten Sie es manuell um.", + "FAILED_TO_SWITCH_MICROPHONE": "Fehler beim Wechseln des Mikrofons", + "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon erfolgreich umgeschaltet", + "SPEAKERS_SET_SUCCESSFULLY": "Lautsprecher erfolgreich umgeschaltet" + }, + "ACTIONS": { + "RECENTER_PLAYSPACE": "Playspace neu zentrieren" + }, + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Auflösung" + }, + "WIDTH": "Breite", + "HEIGHT": "Höhe", + "HIDE": "Verbergen", + "REMOVE": "Entfernen", + "SHOW": "Anzeigen", + "FAILED_TO_LAUNCH_APPLICATION": "Fehler beim Starten der Anwendung:", + "APPLICATION_STARTED": "Anwendung gestartet", + "CLOSE_WINDOW": "Fenster schließen", + "GAME_LIST": { + "NO_GAMES_FOUND": "Keine Spiele gefunden", + "RUNNING_GAMES_LIST": "Liste der laufenden Spiele" + }, + "TERMINATE_PROCESS": "Prozess beenden", + "GAME_LAUNCHED": "Spiel gestartet", + "APP_LAUNCHER": { + "MODE": { + "NATIVE": "Nativer Modus", + "CAGE": "Kompatibilitätsmodus (Cage)" + }, + "RES_TITLE": "Auflösung", + "ASPECT_TITLE": "Seitenverhältnis", + "ASPECT": { + "WIDE": "Breit", + "SEMI_WIDE": "Halb-breit", + "SQUARE": "Quadratisch", + "SEMI_TALL": "Halbhoch", + "TALL": "Hoch" + }, + "POS_TITLE": "Positionierung", + "POS": { + "FLOATING": "Schwebend", + "ANCHORED": "Verankert", + "STATIC": "Statisch", + "FLOATING_HELP": "Bewegt sich unabhängig, zentriert sich bei Anzeige neu.", + "ANCHORED_HELP": "Bleibt relativ zur Mittelmarkierung an Ort und Stelle.", + "STATIC_HELP": "Keine Set zugeordnet. Wird nicht zentriert." + }, + "AUTOSTART": "Automatisch beim Start ausführen", + "LAUNCH": "Starten" + }, + "DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit", + "PROCESS_LIST": "Prozessliste", + "REFRESH": "Aktualisieren", + "PROCESS": { + "STOP": "Stopp", + "FORCE_KILL": "Erzwinge Beenden" + }, + "DEBUG_INFO": "Debug-Informationen" +} diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 7b1b1626..3e4d039c 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -114,6 +114,10 @@ "USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it", "USE_SKYBOX": "Enable skybox", "USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough", + "WATCH_VIEW_ANGLE_MIN": "Watch view angle min", + "WATCH_VIEW_ANGLE_MIN_HELP": "Angle where the watch starts to fade away", + "WATCH_VIEW_ANGLE_MAX": "Watch view angle max", + "WATCH_VIEW_ANGLE_MAX_HELP": "Angle where the watch is fully visible", "GRID_OPACITY": "Floor grid opacity", "GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled", "XR_CLICK_SENSITIVITY": "XR click sensitivity", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index b9dac274..e2a9e2ba 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -1,166 +1,170 @@ { - "HOME_SCREEN": "Inicio", - "MONADO_RUNTIME": "Monado tiempo de ejecución", - "APPLICATIONS": "Aplicaciones", - "GAMES": "Juegos", - "SETTINGS": "Ajustes", - "HELLO_USER": "¡Hola, {USER}!", - "GENERAL_SETTINGS": "Ajustes generales", - "APPLICATION_LAUNCHER": "Lanzador de aplicaciones", - "APP_SETTINGS": { - "HIDE_USERNAME": "Ocultar nombre de usuario", - "OPAQUE_BACKGROUND": "Fondo opaco", - "LOOK_AND_FEEL": "Apariencia y estilo", - "HIDE_GRAB_HELP": "Ocultar ayuda para agarrar", - "ANIMATION_SPEED": "Velocidad de animación de la IU", - "ROUND_MULTIPLIER": "Redondeo de bordes de la IU", - "USE_SKYBOX": "Activar skybox", - "USE_PASSTHROUGH": "Activar passthrough", - "CLOCK_12H": "Reloj de 12 horas", - "FEATURES": "Funciones", - "NOTIFICATIONS_ENABLED": "Habilitar notificaciones", - "NOTIFICATIONS_SOUND_ENABLED": "Sonidos de notificación", - "KEYBOARD_SOUND_ENABLED": "Sonidos del teclado", - "SPACE_DRAG_MULTIPLIER": "Multiplicador de arrastre espacial", - "SPACE_DRAG_UNLOCKED": "Permitir arrastre del espacio en todos los ejes", - "SPACE_ROTATE_UNLOCKED": "Permitir rotación espacial en todos los ejes", - "BLOCK_GAME_INPUT": "Bloquear entrada del juego", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignorar watch al bloquear la entrada", - "CONTROLS": "Controles", - "FOCUS_FOLLOWS_MOUSE_MODE": "El movimiento del ratón en el disparador", - "LEFT_HANDED_MOUSE": "Ratón para zurdos", - "ALLOW_SLIDING": "Interacción con el stick durante el agarre", - "INVERT_SCROLL_DIRECTION_X": "Invertir dirección de desplazamiento horizontal", - "INVERT_SCROLL_DIRECTION_Y": "Invertir la dirección del desplazamiento vertical", - "SCROLL_SPEED": "Velocidad de desplazamiento", - "LONG_PRESS_DURATION": "Duración de la pulsación larga", - "POINTER_LERP_FACTOR": "Suavizado del puntero", - "XR_CLICK_SENSITIVITY": "Sensibilidad del clic XR", - "XR_CLICK_SENSITIVITY_RELEASE": "Sensibilidad de liberación de OpenXR", - "CLICK_FREEZE_TIME_MS": "Tiempo de congelación al hacer clic (ms)", - "MISC": "Miscelánea", - "XWAYLAND_BY_DEFAULT": "Ejecutar aplicaciones en modo de compatibilidad por defecto", - "UPRIGHT_SCREEN_FIX": "Corrección de pantalla vertical", - "DOUBLE_CURSOR_FIX": "Solución de doble cursor", - "SCREEN_RENDER_DOWN": "Renderizar pantalla a menor resolución", - "UPRIGHT_SCREEN_FIX_HELP": "Corrige pantallas en posición vertical en algunos escritorios", - "DOUBLE_CURSOR_FIX_HELP": "Habilita esto si ves 2 cursores", - "XR_CLICK_SENSITIVITY_HELP": "Sensibilidad del gatillo analógico", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Debe ser inferior a 'click'", - "CLICK_FREEZE_TIME_MS_HELP": "Ayuda con la precisión de los dobles clics", - "LEFT_HANDED_MOUSE_HELP": "Utilice esto si los botones del ratón están intercambiados", - "BLOCK_GAME_INPUT_HELP": "Bloquea toda la entrada cuando se pasa el cursor sobre un overlay", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "No bloquear la entrada cuando el cursor está sobre la ventana", - "USE_SKYBOX_HELP": "Mostrar una skybox si no hay una aplicación de escena o passthrough", - "USE_PASSTHROUGH_HELP": "Permitir passthrough si el entorno de ejecución XR lo admite", - "SCREEN_RENDER_DOWN_HELP": "Ayuda a reducir el aliasing en pantallas de alta resolución", - "SETS_ON_WATCH": "Conjuntos en el reloj", - "TROUBLESHOOTING": "Solución de problemas", - "CLEAR_SAVED_STATE": "Borrar estado guardado", - "CLEAR_PIPEWIRE_TOKENS": "Limpiar tokens de PipeWire", - "DELETE_ALL_CONFIGS": "Borrar configuración", - "RESTART_SOFTWARE": "Reiniciar software", - "CLEAR_SAVED_STATE_HELP": "Restablecer sets y posiciones de superposición", - "CLEAR_PIPEWIRE_TOKENS_HELP": "Solicitar la selección de pantalla al iniciar la próxima vez", - "DELETE_ALL_CONFIGS_HELP": "Eliminar todos los archivos de configuración de conf.d", - "RESTART_SOFTWARE_HELP": "Aplicar la configuración que requiere un reinicio", - "CAPTURE_METHOD": "Captura de pantalla de Wayland", - "CAPTURE_METHOD_HELP": "Intente cambiar esta opción si\nexperimenta pantallas negras o con fallos", - "KEYBOARD_MIDDLE_CLICK": "Clic del botón central del teclado", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador para usar al escribir\ncon láser púrpura", - "OPTION": { - "AUTO": "Automático", - "AUTO_HELP": "Copiar pantalla usando la GPU si es compatible,\nde lo contrario, GPU de PipeWire.", - "PIPEWIRE_HELP": "Captura de GPU rápida,\nestándar en todos los escritorios.", - "PW_FALLBACK_HELP": "Método lento con alto uso de CPU.\nPruébalo si PipeWire GPU no funciona", - "SCREENCOPY_GPU_HELP": "Rápido, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway", - "NONE": "Ninguno", - "HMD_PINCH": "HMD + pellizco", - "EYE_PINCH": "Ojo + pellizco", - "EYE_ONLY": "Solo ojo", - "HMD_ONLY": "Solo HMD" - }, - "AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio", - "HANDSFREE_POINTER": "Modo manos libres", - "HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.", - "UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU", - "RESET_PLAYSPACE": "Restablecer espacio de juego", - "RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego.", - "BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado", - "LANGUAGE": "Idioma", - "REQUIRES_RESTART": "Requiere reinicio", - "GRID_OPACITY": "Opacidad de la cuadrícula del suelo", - "GRID_OPACITY_HELP": "Opacidad de la cuadrícula del suelo cuando el skybox está habilitado" - }, - "HELLO": "¡Hola!", - "AUDIO": { - "VOLUME": "Volumen", - "SETTINGS": "Configuración de audio", - "AUTO_SWITCH_TO_VR_AUDIO": "Conmutar automáticamente al audio VR", - "SPEAKERS": "Altavoces", - "MICROPHONES": "Micrófonos", - "CARDS": "Tarjetas", - "SELECT_AUDIO_CARD_PROFILE": "Seleccionar perfil de tarjeta de audio", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No se encontraron altavoces VR. Actívelos manualmente.", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "No se encontró micrófono VR. Actívelo manualmente.", - "FAILED_TO_SWITCH_MICROPHONE": "No se pudo cambiar el micrófono", - "MICROPHONE_SET_SUCCESSFULLY": "Micrófono configurado correctamente", - "SPEAKERS_SET_SUCCESSFULLY": "Altavoces configurados correctamente" - }, - "ACTIONS": { - "RECENTER_PLAYSPACE": "Re-centrar espacio de juego" - }, - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "Resolución" - }, - "WIDTH": "Ancho", - "HEIGHT": "Altura", - "HIDE": "Ocultar", - "REMOVE": "Eliminar", - "SHOW": "Mostrar", - "FAILED_TO_LAUNCH_APPLICATION": "No se pudo iniciar la aplicación:", - "APPLICATION_STARTED": "Aplicación iniciada", - "CLOSE_WINDOW": "Cerrar ventana", - "GAME_LIST": { - "NO_GAMES_FOUND": "No se encontraron juegos", - "RUNNING_GAMES_LIST": "Lista de juegos en ejecución" - }, - "TERMINATE_PROCESS": "Finalizar proceso", - "GAME_LAUNCHED": "Juego lanzado", - "APP_LAUNCHER": { - "MODE": { - "NATIVE": "Modo nativo", - "CAGE": "Modo de compatibilidad (Cage)" - }, - "RES_TITLE": "Resolución", - "ASPECT_TITLE": "Aspecto", - "ASPECT": { - "WIDE": "Ancho", - "SEMI_WIDE": "Semi-ancho", - "SQUARE": "Cuadrado", - "SEMI_TALL": "Semi alto", - "TALL": "Alto" - }, - "POS_TITLE": "Posicionamiento", - "POS": { - "FLOATING": "Flotante", - "ANCHORED": "Anclado", - "STATIC": "Estático", - "FLOATING_HELP": "Se mueve independientemente, se recentra al mostrarse.", - "ANCHORED_HELP": "Se mantiene en su lugar con respecto al marcador central.", - "STATIC_HELP": "No pertenece a ningún conjunto. No se recentra." - }, - "AUTOSTART": "Ejecutar automáticamente al inicio", - "LAUNCH": "Iniciar" - }, - "DISPLAY_BRIGHTNESS": "Brillo de la pantalla", - "PROCESS_LIST": "Lista de procesos", - "REFRESH": "Actualizar", - "PROCESS": { - "STOP": "Detener", - "FORCE_KILL": "Forzar cierre" - }, - "DEBUG_INFO": "Información de depuración" -} \ No newline at end of file + "HOME_SCREEN": "Inicio", + "MONADO_RUNTIME": "Monado tiempo de ejecución", + "APPLICATIONS": "Aplicaciones", + "GAMES": "Juegos", + "SETTINGS": "Ajustes", + "HELLO_USER": "¡Hola, {USER}!", + "GENERAL_SETTINGS": "Ajustes generales", + "APPLICATION_LAUNCHER": "Lanzador de aplicaciones", + "APP_SETTINGS": { + "HIDE_USERNAME": "Ocultar nombre de usuario", + "OPAQUE_BACKGROUND": "Fondo opaco", + "LOOK_AND_FEEL": "Apariencia y estilo", + "HIDE_GRAB_HELP": "Ocultar ayuda para agarrar", + "ANIMATION_SPEED": "Velocidad de animación de la IU", + "ROUND_MULTIPLIER": "Redondeo de bordes de la IU", + "USE_SKYBOX": "Activar skybox", + "USE_PASSTHROUGH": "Activar passthrough", + "CLOCK_12H": "Reloj de 12 horas", + "FEATURES": "Funciones", + "NOTIFICATIONS_ENABLED": "Habilitar notificaciones", + "NOTIFICATIONS_SOUND_ENABLED": "Sonidos de notificación", + "KEYBOARD_SOUND_ENABLED": "Sonidos del teclado", + "SPACE_DRAG_MULTIPLIER": "Multiplicador de arrastre espacial", + "SPACE_DRAG_UNLOCKED": "Permitir arrastre del espacio en todos los ejes", + "SPACE_ROTATE_UNLOCKED": "Permitir rotación espacial en todos los ejes", + "BLOCK_GAME_INPUT": "Bloquear entrada del juego", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignorar watch al bloquear la entrada", + "CONTROLS": "Controles", + "FOCUS_FOLLOWS_MOUSE_MODE": "El movimiento del ratón en el disparador", + "LEFT_HANDED_MOUSE": "Ratón para zurdos", + "ALLOW_SLIDING": "Interacción con el stick durante el agarre", + "INVERT_SCROLL_DIRECTION_X": "Invertir dirección de desplazamiento horizontal", + "INVERT_SCROLL_DIRECTION_Y": "Invertir la dirección del desplazamiento vertical", + "SCROLL_SPEED": "Velocidad de desplazamiento", + "LONG_PRESS_DURATION": "Duración de la pulsación larga", + "POINTER_LERP_FACTOR": "Suavizado del puntero", + "XR_CLICK_SENSITIVITY": "Sensibilidad del clic XR", + "XR_CLICK_SENSITIVITY_RELEASE": "Sensibilidad de liberación de OpenXR", + "CLICK_FREEZE_TIME_MS": "Tiempo de congelación al hacer clic (ms)", + "MISC": "Miscelánea", + "XWAYLAND_BY_DEFAULT": "Ejecutar aplicaciones en modo de compatibilidad por defecto", + "UPRIGHT_SCREEN_FIX": "Corrección de pantalla vertical", + "DOUBLE_CURSOR_FIX": "Solución de doble cursor", + "SCREEN_RENDER_DOWN": "Renderizar pantalla a menor resolución", + "UPRIGHT_SCREEN_FIX_HELP": "Corrige pantallas en posición vertical en algunos escritorios", + "DOUBLE_CURSOR_FIX_HELP": "Habilita esto si ves 2 cursores", + "XR_CLICK_SENSITIVITY_HELP": "Sensibilidad del gatillo analógico", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Debe ser inferior a 'click'", + "CLICK_FREEZE_TIME_MS_HELP": "Ayuda con la precisión de los dobles clics", + "LEFT_HANDED_MOUSE_HELP": "Utilice esto si los botones del ratón están intercambiados", + "BLOCK_GAME_INPUT_HELP": "Bloquea toda la entrada cuando se pasa el cursor sobre un overlay", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "No bloquear la entrada cuando el cursor está sobre la ventana", + "USE_SKYBOX_HELP": "Mostrar una skybox si no hay una aplicación de escena o passthrough", + "WATCH_VIEW_ANGLE_MIN": "Watch view angle min", + "WATCH_VIEW_ANGLE_MIN_HELP": "Angle where the watch starts to fade away", + "WATCH_VIEW_ANGLE_MAX": "Watch view angle max", + "WATCH_VIEW_ANGLE_MAX_HELP": "Angle where the watch is fully visible", + "USE_PASSTHROUGH_HELP": "Permitir passthrough si el entorno de ejecución XR lo admite", + "SCREEN_RENDER_DOWN_HELP": "Ayuda a reducir el aliasing en pantallas de alta resolución", + "SETS_ON_WATCH": "Conjuntos en el reloj", + "TROUBLESHOOTING": "Solución de problemas", + "CLEAR_SAVED_STATE": "Borrar estado guardado", + "CLEAR_PIPEWIRE_TOKENS": "Limpiar tokens de PipeWire", + "DELETE_ALL_CONFIGS": "Borrar configuración", + "RESTART_SOFTWARE": "Reiniciar software", + "CLEAR_SAVED_STATE_HELP": "Restablecer sets y posiciones de superposición", + "CLEAR_PIPEWIRE_TOKENS_HELP": "Solicitar la selección de pantalla al iniciar la próxima vez", + "DELETE_ALL_CONFIGS_HELP": "Eliminar todos los archivos de configuración de conf.d", + "RESTART_SOFTWARE_HELP": "Aplicar la configuración que requiere un reinicio", + "CAPTURE_METHOD": "Captura de pantalla de Wayland", + "CAPTURE_METHOD_HELP": "Intente cambiar esta opción si\nexperimenta pantallas negras o con fallos", + "KEYBOARD_MIDDLE_CLICK": "Clic del botón central del teclado", + "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador para usar al escribir\ncon láser púrpura", + "OPTION": { + "AUTO": "Automático", + "AUTO_HELP": "Copiar pantalla usando la GPU si es compatible,\nde lo contrario, GPU de PipeWire.", + "PIPEWIRE_HELP": "Captura de GPU rápida,\nestándar en todos los escritorios.", + "PW_FALLBACK_HELP": "Método lento con alto uso de CPU.\nPruébalo si PipeWire GPU no funciona", + "SCREENCOPY_GPU_HELP": "Rápido, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway", + "NONE": "Ninguno", + "HMD_PINCH": "HMD + pellizco", + "EYE_PINCH": "Ojo + pellizco", + "EYE_ONLY": "Solo ojo", + "HMD_ONLY": "Solo HMD" + }, + "AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio", + "HANDSFREE_POINTER": "Modo manos libres", + "HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.", + "UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU", + "RESET_PLAYSPACE": "Restablecer espacio de juego", + "RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego.", + "BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado", + "LANGUAGE": "Idioma", + "REQUIRES_RESTART": "Requiere reinicio", + "GRID_OPACITY": "Opacidad de la cuadrícula del suelo", + "GRID_OPACITY_HELP": "Opacidad de la cuadrícula del suelo cuando el skybox está habilitado" + }, + "HELLO": "¡Hola!", + "AUDIO": { + "VOLUME": "Volumen", + "SETTINGS": "Configuración de audio", + "AUTO_SWITCH_TO_VR_AUDIO": "Conmutar automáticamente al audio VR", + "SPEAKERS": "Altavoces", + "MICROPHONES": "Micrófonos", + "CARDS": "Tarjetas", + "SELECT_AUDIO_CARD_PROFILE": "Seleccionar perfil de tarjeta de audio", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No se encontraron altavoces VR. Actívelos manualmente.", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "No se encontró micrófono VR. Actívelo manualmente.", + "FAILED_TO_SWITCH_MICROPHONE": "No se pudo cambiar el micrófono", + "MICROPHONE_SET_SUCCESSFULLY": "Micrófono configurado correctamente", + "SPEAKERS_SET_SUCCESSFULLY": "Altavoces configurados correctamente" + }, + "ACTIONS": { + "RECENTER_PLAYSPACE": "Re-centrar espacio de juego" + }, + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Resolución" + }, + "WIDTH": "Ancho", + "HEIGHT": "Altura", + "HIDE": "Ocultar", + "REMOVE": "Eliminar", + "SHOW": "Mostrar", + "FAILED_TO_LAUNCH_APPLICATION": "No se pudo iniciar la aplicación:", + "APPLICATION_STARTED": "Aplicación iniciada", + "CLOSE_WINDOW": "Cerrar ventana", + "GAME_LIST": { + "NO_GAMES_FOUND": "No se encontraron juegos", + "RUNNING_GAMES_LIST": "Lista de juegos en ejecución" + }, + "TERMINATE_PROCESS": "Finalizar proceso", + "GAME_LAUNCHED": "Juego lanzado", + "APP_LAUNCHER": { + "MODE": { + "NATIVE": "Modo nativo", + "CAGE": "Modo de compatibilidad (Cage)" + }, + "RES_TITLE": "Resolución", + "ASPECT_TITLE": "Aspecto", + "ASPECT": { + "WIDE": "Ancho", + "SEMI_WIDE": "Semi-ancho", + "SQUARE": "Cuadrado", + "SEMI_TALL": "Semi alto", + "TALL": "Alto" + }, + "POS_TITLE": "Posicionamiento", + "POS": { + "FLOATING": "Flotante", + "ANCHORED": "Anclado", + "STATIC": "Estático", + "FLOATING_HELP": "Se mueve independientemente, se recentra al mostrarse.", + "ANCHORED_HELP": "Se mantiene en su lugar con respecto al marcador central.", + "STATIC_HELP": "No pertenece a ningún conjunto. No se recentra." + }, + "AUTOSTART": "Ejecutar automáticamente al inicio", + "LAUNCH": "Iniciar" + }, + "DISPLAY_BRIGHTNESS": "Brillo de la pantalla", + "PROCESS_LIST": "Lista de procesos", + "REFRESH": "Actualizar", + "PROCESS": { + "STOP": "Detener", + "FORCE_KILL": "Forzar cierre" + }, + "DEBUG_INFO": "Información de depuración" +} diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index fd02f8b0..3ce1bf86 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -1,166 +1,170 @@ { - "ACTIONS": { - "RECENTER_PLAYSPACE": "Recentra lo spazio di gioco" - }, - "APP_LAUNCHER": { - "ASPECT": { - "SEMI_TALL": "Semi-alto", - "SEMI_WIDE": "Semi-ampio", - "SQUARE": "Quadrato", - "TALL": "Alto", - "WIDE": "Ampio" - }, - "ASPECT_TITLE": "Proporzioni", - "AUTOSTART": "Avvia automaticamente all'avvio", - "LAUNCH": "Avvia", - "MODE": { - "CAGE": "Modalità compatibilità (Cage)", - "NATIVE": "Modalità nativa" - }, - "POS": { - "ANCHORED": "Ancorato", - "ANCHORED_HELP": "Rimane fermo rispetto al marcatore centrale.", - "FLOATING": "Fluttuante", - "FLOATING_HELP": "Si muove in modo indipendente, si recentra quando viene mostrato.", - "STATIC": "Statico", - "STATIC_HELP": "Non fa parte di alcun set. Non recentra." - }, - "POS_TITLE": "Posizionamento", - "RES_TITLE": "Risoluzione" - }, - "APP_SETTINGS": { - "ALLOW_SLIDING": "Interazione tramite stick durante l'afferrare", - "ANIMATION_SPEED": "Velocità dell'animazione dell'interfaccia utente", - "BLOCK_GAME_INPUT": "Blocca l'input di gioco", - "BLOCK_GAME_INPUT_HELP": "Blocca tutti gli input quando il cursore è sopra un overlay", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignora il Watch quando si blocca l'input", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Non bloccare l'input quando il watch è evidenziato", - "CAPTURE_METHOD": "Acquisizione dello schermo Wayland", - "CAPTURE_METHOD_HELP": "Prova a modificare questa impostazione se riscontri schermate nere o con artefatti.", - "CLEAR_PIPEWIRE_TOKENS": "Cancella i token PipeWire", - "CLEAR_PIPEWIRE_TOKENS_HELP": "Richiedi la selezione dello schermo all'avvio successivo", - "CLEAR_SAVED_STATE": "Cancella stato salvato", - "CLEAR_SAVED_STATE_HELP": "Ripristina set e posizioni degli overlay", - "CLICK_FREEZE_TIME_MS": "Tempo di congelamento del click (ms)", - "CLICK_FREEZE_TIME_MS_HELP": "Aiuta con la precisione dei doppi clic", - "CLOCK_12H": "Orologio a 12 ore", - "CONTROLS": "Controlli", - "DELETE_ALL_CONFIGS": "Elimina configurazione", - "DELETE_ALL_CONFIGS_HELP": "Rimuovi tutti i file di configurazione da conf.d", - "DOUBLE_CURSOR_FIX": "Correzione doppio cursore", - "DOUBLE_CURSOR_FIX_HELP": "Abilita questa opzione se vedi 2 cursori", - "FEATURES": "Funzionalità", - "FOCUS_FOLLOWS_MOUSE_MODE": "Movimento del mouse al tocco del grilletto", - "HIDE_GRAB_HELP": "Nascondi l'aiuto per l'afferrare", - "HIDE_USERNAME": "Nascondi nome utente", - "INVERT_SCROLL_DIRECTION_X": "Inverti direzione dello scorrimento orizzontale", - "INVERT_SCROLL_DIRECTION_Y": "Inverti direzione dello scorrimento verticale", - "KEYBOARD_MIDDLE_CLICK": "Click centrale del tasto", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modificatore da usare durante la digitazione\ncon laser viola", - "KEYBOARD_SOUND_ENABLED": "Suoni della tastiera", - "LEFT_HANDED_MOUSE": "Mouse per mancini", - "LEFT_HANDED_MOUSE_HELP": "Usalo se i pulsanti del mouse sono invertiti", - "LONG_PRESS_DURATION": "Durata pressione prolungata", - "LOOK_AND_FEEL": "Aspetto", - "MISC": "Varie", - "NOTIFICATIONS_ENABLED": "Abilita notifiche", - "NOTIFICATIONS_SOUND_ENABLED": "Suoni di notifica", - "OPAQUE_BACKGROUND": "Sfondo opaco", - "OPTION": { - "AUTO": "Automatico", - "AUTO_HELP": "Copia dello schermo tramite GPU se supportata,\naltrimenti tramite GPU PipeWire.", - "PIPEWIRE_HELP": "Acquisizione GPU veloce,\nstandard su tutti i desktop.", - "PW_FALLBACK_HELP": "Metodo lento con elevato utilizzo della CPU.\nProva in caso PipeWire GPU non funzioni", - "SCREENCOPY_GPU_HELP": "Veloce, senza popup di condivisione schermo.\nFunziona su: Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "Lento, nessuna finestra pop-up per la condivisione dello schermo.\nFunziona su: Hyprland, Niri, River, Sway", - "NONE": "Nessuno", - "HMD_PINCH": "HMD + pizzico", - "EYE_PINCH": "Occhio + pizzico", - "EYE_ONLY": "Solo occhio", - "HMD_ONLY": "Solo HMD" - }, - "POINTER_LERP_FACTOR": "Smussamento puntatore", - "RESTART_SOFTWARE": "Riavvia il software", - "RESTART_SOFTWARE_HELP": "Applica impostazioni che richiedono un riavvio", - "ROUND_MULTIPLIER": "Arrotondamento bordi UI", - "SCREEN_RENDER_DOWN": "Rendering dello schermo a risoluzione inferiore", - "SCREEN_RENDER_DOWN_HELP": "Aiuta a ridurre l'aliasing su schermi ad alta risoluzione", - "SCROLL_SPEED": "Velocità di scorrimento", - "SETS_ON_WATCH": "Set sul Watch", - "SPACE_DRAG_MULTIPLIER": "Moltiplicatore space-drag", - "SPACE_DRAG_UNLOCKED": "Consenti lo space-drag su tutti gli assi", - "SPACE_ROTATE_UNLOCKED": "Consenti la rotazione dello spazio su tutti gli assi", - "TROUBLESHOOTING": "Risoluzione dei problemi", - "UPRIGHT_SCREEN_FIX": "Correzione schermo verticale", - "UPRIGHT_SCREEN_FIX_HELP": "Corregge schermi verticali su alcuni desktop", - "USE_PASSTHROUGH": "Abilita passthrough", - "USE_PASSTHROUGH_HELP": "Consenti il passthrough se supportato dal runtime XR", - "USE_SKYBOX": "Abilita skybox", - "USE_SKYBOX_HELP": "Mostra uno skybox se non c'è un'app di scena o passthrough", - "XR_CLICK_SENSITIVITY": "Sensibilità del click XR", - "XR_CLICK_SENSITIVITY_HELP": "Sensibilità del trigger analogico", - "XR_CLICK_SENSITIVITY_RELEASE": "Sensibilità di rilascio XR", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Deve essere inferiore al clic", - "XWAYLAND_BY_DEFAULT": "Esegui le app in modalità Compatibilità per impostazione predefinita", - "AUTOSTART_APPS": "App da avviare all'avvio", - "HANDSFREE_POINTER": "Modalità a mani libere", - "HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.", - "UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente", - "RESET_PLAYSPACE": "Ripristina playspace", - "RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco.", - "BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato", - "LANGUAGE": "Lingua", - "REQUIRES_RESTART": "Richiede riavvio", - "GRID_OPACITY": "Opacità della griglia del pavimento", - "GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skybox è abilitato" - }, - "APPLICATION_LAUNCHER": "Lanciatore applicazioni", - "APPLICATION_STARTED": "Applicazione avviata", - "APPLICATIONS": "Applicazioni", - "AUDIO": { - "AUTO_SWITCH_TO_VR_AUDIO": "Passa automaticamente all'audio VR", - "CARDS": "Schede", - "FAILED_TO_SWITCH_MICROPHONE": "Impossibile cambiare microfono", - "MICROPHONE_SET_SUCCESSFULLY": "Microfono impostato con successo", - "MICROPHONES": "Microfoni", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Nessun microfono VR trovato. Attivalo manualmente.", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Nessun altoparlante VR trovato. Selezionali manualmente.", - "SELECT_AUDIO_CARD_PROFILE": "Seleziona profilo scheda audio", - "SETTINGS": "Impostazioni audio", - "SPEAKERS": "Altoparlanti", - "SPEAKERS_SET_SUCCESSFULLY": "Altoparlanti impostati correttamente", - "VOLUME": "Volume" - }, - "CLOSE_WINDOW": "Chiudi finestra", - "DISPLAY_BRIGHTNESS": "Luminosità display", - "FAILED_TO_LAUNCH_APPLICATION": "Impossibile avviare l'applicazione:", - "GAME_LAUNCHED": "Gioco lanciato", - "GAME_LIST": { - "NO_GAMES_FOUND": "Nessun gioco trovato", - "RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione" - }, - "GAMES": "Giochi", - "GENERAL_SETTINGS": "Impostazioni generali", - "HEIGHT": "Altezza", - "HELLO": "Ciao!", - "HELLO_USER": "Ciao, {USER}!", - "HIDE": "Nascondi", - "HOME_SCREEN": "Home", - "MONADO_RUNTIME": "Runtime Monado", - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "Risoluzione" - }, - "REMOVE": "Rimuovi", - "SETTINGS": "Impostazioni", - "SHOW": "Mostra", - "TERMINATE_PROCESS": "Termina processo", - "WIDTH": "Larghezza", - "PROCESS_LIST": "Elenco processi", - "REFRESH": "Aggiorna", - "PROCESS": { - "STOP": "Interrompi", - "FORCE_KILL": "Uccidi forzatamente" - }, - "DEBUG_INFO": "Informazioni di debug" -} \ No newline at end of file + "ACTIONS": { + "RECENTER_PLAYSPACE": "Recentra lo spazio di gioco" + }, + "APP_LAUNCHER": { + "ASPECT": { + "SEMI_TALL": "Semi-alto", + "SEMI_WIDE": "Semi-ampio", + "SQUARE": "Quadrato", + "TALL": "Alto", + "WIDE": "Ampio" + }, + "ASPECT_TITLE": "Proporzioni", + "AUTOSTART": "Avvia automaticamente all'avvio", + "LAUNCH": "Avvia", + "MODE": { + "CAGE": "Modalità compatibilità (Cage)", + "NATIVE": "Modalità nativa" + }, + "POS": { + "ANCHORED": "Ancorato", + "ANCHORED_HELP": "Rimane fermo rispetto al marcatore centrale.", + "FLOATING": "Fluttuante", + "FLOATING_HELP": "Si muove in modo indipendente, si recentra quando viene mostrato.", + "STATIC": "Statico", + "STATIC_HELP": "Non fa parte di alcun set. Non recentra." + }, + "POS_TITLE": "Posizionamento", + "RES_TITLE": "Risoluzione" + }, + "APP_SETTINGS": { + "ALLOW_SLIDING": "Interazione tramite stick durante l'afferrare", + "ANIMATION_SPEED": "Velocità dell'animazione dell'interfaccia utente", + "BLOCK_GAME_INPUT": "Blocca l'input di gioco", + "BLOCK_GAME_INPUT_HELP": "Blocca tutti gli input quando il cursore è sopra un overlay", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignora il Watch quando si blocca l'input", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Non bloccare l'input quando il watch è evidenziato", + "CAPTURE_METHOD": "Acquisizione dello schermo Wayland", + "CAPTURE_METHOD_HELP": "Prova a modificare questa impostazione se riscontri schermate nere o con artefatti.", + "CLEAR_PIPEWIRE_TOKENS": "Cancella i token PipeWire", + "CLEAR_PIPEWIRE_TOKENS_HELP": "Richiedi la selezione dello schermo all'avvio successivo", + "CLEAR_SAVED_STATE": "Cancella stato salvato", + "CLEAR_SAVED_STATE_HELP": "Ripristina set e posizioni degli overlay", + "CLICK_FREEZE_TIME_MS": "Tempo di congelamento del click (ms)", + "CLICK_FREEZE_TIME_MS_HELP": "Aiuta con la precisione dei doppi clic", + "CLOCK_12H": "Orologio a 12 ore", + "CONTROLS": "Controlli", + "DELETE_ALL_CONFIGS": "Elimina configurazione", + "DELETE_ALL_CONFIGS_HELP": "Rimuovi tutti i file di configurazione da conf.d", + "DOUBLE_CURSOR_FIX": "Correzione doppio cursore", + "DOUBLE_CURSOR_FIX_HELP": "Abilita questa opzione se vedi 2 cursori", + "FEATURES": "Funzionalità", + "FOCUS_FOLLOWS_MOUSE_MODE": "Movimento del mouse al tocco del grilletto", + "HIDE_GRAB_HELP": "Nascondi l'aiuto per l'afferrare", + "HIDE_USERNAME": "Nascondi nome utente", + "INVERT_SCROLL_DIRECTION_X": "Inverti direzione dello scorrimento orizzontale", + "INVERT_SCROLL_DIRECTION_Y": "Inverti direzione dello scorrimento verticale", + "KEYBOARD_MIDDLE_CLICK": "Click centrale del tasto", + "KEYBOARD_MIDDLE_CLICK_HELP": "Modificatore da usare durante la digitazione\ncon laser viola", + "KEYBOARD_SOUND_ENABLED": "Suoni della tastiera", + "LEFT_HANDED_MOUSE": "Mouse per mancini", + "LEFT_HANDED_MOUSE_HELP": "Usalo se i pulsanti del mouse sono invertiti", + "LONG_PRESS_DURATION": "Durata pressione prolungata", + "LOOK_AND_FEEL": "Aspetto", + "MISC": "Varie", + "NOTIFICATIONS_ENABLED": "Abilita notifiche", + "NOTIFICATIONS_SOUND_ENABLED": "Suoni di notifica", + "OPAQUE_BACKGROUND": "Sfondo opaco", + "OPTION": { + "AUTO": "Automatico", + "AUTO_HELP": "Copia dello schermo tramite GPU se supportata,\naltrimenti tramite GPU PipeWire.", + "PIPEWIRE_HELP": "Acquisizione GPU veloce,\nstandard su tutti i desktop.", + "PW_FALLBACK_HELP": "Metodo lento con elevato utilizzo della CPU.\nProva in caso PipeWire GPU non funzioni", + "SCREENCOPY_GPU_HELP": "Veloce, senza popup di condivisione schermo.\nFunziona su: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "Lento, nessuna finestra pop-up per la condivisione dello schermo.\nFunziona su: Hyprland, Niri, River, Sway", + "NONE": "Nessuno", + "HMD_PINCH": "HMD + pizzico", + "EYE_PINCH": "Occhio + pizzico", + "EYE_ONLY": "Solo occhio", + "HMD_ONLY": "Solo HMD" + }, + "POINTER_LERP_FACTOR": "Smussamento puntatore", + "RESTART_SOFTWARE": "Riavvia il software", + "RESTART_SOFTWARE_HELP": "Applica impostazioni che richiedono un riavvio", + "ROUND_MULTIPLIER": "Arrotondamento bordi UI", + "SCREEN_RENDER_DOWN": "Rendering dello schermo a risoluzione inferiore", + "SCREEN_RENDER_DOWN_HELP": "Aiuta a ridurre l'aliasing su schermi ad alta risoluzione", + "SCROLL_SPEED": "Velocità di scorrimento", + "SETS_ON_WATCH": "Set sul Watch", + "SPACE_DRAG_MULTIPLIER": "Moltiplicatore space-drag", + "SPACE_DRAG_UNLOCKED": "Consenti lo space-drag su tutti gli assi", + "SPACE_ROTATE_UNLOCKED": "Consenti la rotazione dello spazio su tutti gli assi", + "TROUBLESHOOTING": "Risoluzione dei problemi", + "UPRIGHT_SCREEN_FIX": "Correzione schermo verticale", + "UPRIGHT_SCREEN_FIX_HELP": "Corregge schermi verticali su alcuni desktop", + "USE_PASSTHROUGH": "Abilita passthrough", + "USE_PASSTHROUGH_HELP": "Consenti il passthrough se supportato dal runtime XR", + "USE_SKYBOX": "Abilita skybox", + "USE_SKYBOX_HELP": "Mostra uno skybox se non c'è un'app di scena o passthrough", + "WATCH_VIEW_ANGLE_MIN": "Watch view angle min", + "WATCH_VIEW_ANGLE_MIN_HELP": "Angle where the watch starts to fade away", + "WATCH_VIEW_ANGLE_MAX": "Watch view angle max", + "WATCH_VIEW_ANGLE_MAX_HELP": "Angle where the watch is fully visible", + "XR_CLICK_SENSITIVITY": "Sensibilità del click XR", + "XR_CLICK_SENSITIVITY_HELP": "Sensibilità del trigger analogico", + "XR_CLICK_SENSITIVITY_RELEASE": "Sensibilità di rilascio XR", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Deve essere inferiore al clic", + "XWAYLAND_BY_DEFAULT": "Esegui le app in modalità Compatibilità per impostazione predefinita", + "AUTOSTART_APPS": "App da avviare all'avvio", + "HANDSFREE_POINTER": "Modalità a mani libere", + "HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.", + "UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente", + "RESET_PLAYSPACE": "Ripristina playspace", + "RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco.", + "BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato", + "LANGUAGE": "Lingua", + "REQUIRES_RESTART": "Richiede riavvio", + "GRID_OPACITY": "Opacità della griglia del pavimento", + "GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skybox è abilitato" + }, + "APPLICATION_LAUNCHER": "Lanciatore applicazioni", + "APPLICATION_STARTED": "Applicazione avviata", + "APPLICATIONS": "Applicazioni", + "AUDIO": { + "AUTO_SWITCH_TO_VR_AUDIO": "Passa automaticamente all'audio VR", + "CARDS": "Schede", + "FAILED_TO_SWITCH_MICROPHONE": "Impossibile cambiare microfono", + "MICROPHONE_SET_SUCCESSFULLY": "Microfono impostato con successo", + "MICROPHONES": "Microfoni", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Nessun microfono VR trovato. Attivalo manualmente.", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Nessun altoparlante VR trovato. Selezionali manualmente.", + "SELECT_AUDIO_CARD_PROFILE": "Seleziona profilo scheda audio", + "SETTINGS": "Impostazioni audio", + "SPEAKERS": "Altoparlanti", + "SPEAKERS_SET_SUCCESSFULLY": "Altoparlanti impostati correttamente", + "VOLUME": "Volume" + }, + "CLOSE_WINDOW": "Chiudi finestra", + "DISPLAY_BRIGHTNESS": "Luminosità display", + "FAILED_TO_LAUNCH_APPLICATION": "Impossibile avviare l'applicazione:", + "GAME_LAUNCHED": "Gioco lanciato", + "GAME_LIST": { + "NO_GAMES_FOUND": "Nessun gioco trovato", + "RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione" + }, + "GAMES": "Giochi", + "GENERAL_SETTINGS": "Impostazioni generali", + "HEIGHT": "Altezza", + "HELLO": "Ciao!", + "HELLO_USER": "Ciao, {USER}!", + "HIDE": "Nascondi", + "HOME_SCREEN": "Home", + "MONADO_RUNTIME": "Runtime Monado", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Risoluzione" + }, + "REMOVE": "Rimuovi", + "SETTINGS": "Impostazioni", + "SHOW": "Mostra", + "TERMINATE_PROCESS": "Termina processo", + "WIDTH": "Larghezza", + "PROCESS_LIST": "Elenco processi", + "REFRESH": "Aggiorna", + "PROCESS": { + "STOP": "Interrompi", + "FORCE_KILL": "Uccidi forzatamente" + }, + "DEBUG_INFO": "Informazioni di debug" +} diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 92acc7bf..e9658031 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -1,166 +1,170 @@ { - "HOME_SCREEN": "ホーム", - "MONADO_RUNTIME": "「Monado」ランタイム", - "APPLICATIONS": "アプリ", - "GAMES": "ゲーム", - "SETTINGS": "設定", - "HELLO_USER": "こんにちは、{USER}!", - "GENERAL_SETTINGS": "全般設定", - "APPLICATION_LAUNCHER": "アプリケーションランチャー", - "APP_SETTINGS": { - "HIDE_USERNAME": "ユーザー名を非表示", - "OPAQUE_BACKGROUND": "不透明な背景", - "LOOK_AND_FEEL": "外観", - "HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示", - "ANIMATION_SPEED": "UIアニメーション速度", - "ROUND_MULTIPLIER": "UI の丸み", - "USE_SKYBOX": "スカイボックスを有効", - "USE_PASSTHROUGH": "パススルーを有効", - "CLOCK_12H": "12時間制", - "FEATURES": "機能", - "NOTIFICATIONS_ENABLED": "通知を有効にする", - "NOTIFICATIONS_SOUND_ENABLED": "通知音", - "KEYBOARD_SOUND_ENABLED": "キーボード音", - "SPACE_DRAG_MULTIPLIER": "スペースドラッグ倍率", - "SPACE_DRAG_UNLOCKED": "全軸のスペースドラッグ", - "SPACE_ROTATE_UNLOCKED": "全軸のスペース回転", - "BLOCK_GAME_INPUT": "ゲームの入力をブロック", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "入力ブロック時に監視を無視", - "CONTROLS": "コントロール", - "FOCUS_FOLLOWS_MOUSE_MODE": "トリガータッチでマウス移動", - "LEFT_HANDED_MOUSE": "左利きマウス", - "ALLOW_SLIDING": "グリップ動作中にスティックを有効", - "INVERT_SCROLL_DIRECTION_X": "水平スクロール方向を反転", - "INVERT_SCROLL_DIRECTION_Y": "垂直スクロール方向を反転", - "SCROLL_SPEED": "スクロール速度", - "LONG_PRESS_DURATION": "長押し時間", - "POINTER_LERP_FACTOR": "ポインターのスムージング", - "XR_CLICK_SENSITIVITY": "XRクリック感度", - "XR_CLICK_SENSITIVITY_RELEASE": "XRリリース感度", - "CLICK_FREEZE_TIME_MS": "クリックで一時停止時間 (ms)", - "MISC": "その他", - "XWAYLAND_BY_DEFAULT": "アプリ実行のデフォルトは互換モード", - "UPRIGHT_SCREEN_FIX": "画面の縦向き修正", - "DOUBLE_CURSOR_FIX": "ダブルカーソル修正", - "SCREEN_RENDER_DOWN": "画面の解像度を縮小", - "UPRIGHT_SCREEN_FIX_HELP": "一部のデスクトップで縦向きの画面を修正", - "DOUBLE_CURSOR_FIX_HELP": "2つのカーソルが表示される場合は、これを有効にします", - "XR_CLICK_SENSITIVITY_HELP": "アナログトリガの感度", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "クリックより低くする必要があります", - "CLICK_FREEZE_TIME_MS_HELP": "ダブルクリックの精度向上に役立ちます", - "LEFT_HANDED_MOUSE_HELP": "マウスボタンが入れ替わっている場合に有効にします", - "BLOCK_GAME_INPUT_HELP": "オーバーレイ上にマウスカーソルがあるときに入力をブロックします", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "ウォッチがホバーされているときは入力をブロックしない", - "USE_SKYBOX_HELP": "シーンアプリまたはパススルーがない場合にスカイボックスを表示します", - "USE_PASSTHROUGH_HELP": "XRランタイムがサポートしていれば、パススルーを有効にします", - "SCREEN_RENDER_DOWN_HELP": "高解像度スクリーンでのエイリアシングを軽減します", - "SETS_ON_WATCH": "ウォッチのセット", - "TROUBLESHOOTING": "トラブルシューティング", - "CLEAR_SAVED_STATE": "保存された状態をクリア", - "CLEAR_PIPEWIRE_TOKENS": "PipeWire トークンをクリア", - "DELETE_ALL_CONFIGS": "設定を消去", - "RESTART_SOFTWARE": "ソフトウェアを再起動", - "CLEAR_SAVED_STATE_HELP": "セットとオーバーレイの位置をリセット", - "CLEAR_PIPEWIRE_TOKENS_HELP": "次の起動時に画面選択のプロンプトを表示", - "DELETE_ALL_CONFIGS_HELP": "conf.d 内のすべての設定ファイルを削除", - "RESTART_SOFTWARE_HELP": "再起動が必要な設定を適用", - "CAPTURE_METHOD": "Waylandスクリーンキャプチャ", - "CAPTURE_METHOD_HELP": "画面が黒くなる、または乱れる場合は、\nこの設定を変更してみてください。", - "KEYBOARD_MIDDLE_CLICK": "キーボードの中ボタンクリック", - "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで入力する際の修飾キー", - "OPTION": { - "AUTO": "自動", - "AUTO_HELP": "サポートされている場合はScreenCopy GPU、\nそうでない場合はPipeWire GPU。", - "PIPEWIRE_HELP": "GPU高速キャプチャ。\nすべてのデスクトップで標準です。", - "PW_FALLBACK_HELP": "CPU使用量が多い低速な方法です。\nPipeWire GPUが動作しない場合に試してください。", - "SCREENCOPY_GPU_HELP": "高速で、スクリーン共有ポップアップはありません。\n動作する環境: Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway", - "NONE": "なし", - "HMD_PINCH": "HMD + ピンチ", - "EYE_PINCH": "つまんで目を合わせる", - "EYE_ONLY": "視野のみ", - "HMD_ONLY": "HMDのみ" - }, - "AUTOSTART_APPS": "起動時に実行するアプリ", - "HANDSFREE_POINTER": "ハンズフリーモード", - "HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。", - "UI_GRADIENT_INTENSITY": "UIグラデーションの強さ", - "RESET_PLAYSPACE": "プレイエリアをリセット", - "RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。", - "BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします", - "LANGUAGE": "言語", - "REQUIRES_RESTART": "再起動が必要です", - "GRID_OPACITY": "フロアグリッドの不透明度", - "GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度" - }, - "HELLO": "こんにちは!", - "AUDIO": { - "VOLUME": "音量", - "SETTINGS": "オーディオ設定", - "AUTO_SWITCH_TO_VR_AUDIO": "VRオーディオに自動切り替え", - "SPEAKERS": "スピーカー", - "MICROPHONES": "マイク", - "CARDS": "カード", - "SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "VRスピーカーが見つかりませんでした。手動で切り替えてください。", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "VRマイクが見つかりませんでした。手動で切り替えてください。", - "FAILED_TO_SWITCH_MICROPHONE": "マイクの切り替えに失敗しました", - "MICROPHONE_SET_SUCCESSFULLY": "マイクの設定が完了しました", - "SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定しました" - }, - "ACTIONS": { - "RECENTER_PLAYSPACE": "プレイスペースを再中央" - }, - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "解像度" - }, - "WIDTH": "幅", - "HEIGHT": "高さ", - "HIDE": "隠す", - "REMOVE": "削除", - "SHOW": "表示", - "FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:", - "APPLICATION_STARTED": "アプリケーションが起動しました", - "CLOSE_WINDOW": "ウィンドウを閉じる", - "GAME_LIST": { - "NO_GAMES_FOUND": "ゲームが見つかりませんでした", - "RUNNING_GAMES_LIST": "実行中のゲーム一覧" - }, - "TERMINATE_PROCESS": "プロセスを終了する", - "GAME_LAUNCHED": "ゲームが起動しました", - "APP_LAUNCHER": { - "MODE": { - "NATIVE": "ネイティブモード", - "CAGE": "互換モード(Cage)" - }, - "RES_TITLE": "解像度", - "ASPECT_TITLE": "アスペクト", - "ASPECT": { - "WIDE": "ワイド", - "SEMI_WIDE": "半広角", - "SQUARE": "正方形", - "SEMI_TALL": "半縦長", - "TALL": "縦長" - }, - "POS_TITLE": "配置", - "POS": { - "FLOATING": "フローティング", - "ANCHORED": "固定", - "STATIC": "固定", - "FLOATING_HELP": "独立して移動し、表示時に中央に再配置されます。", - "ANCHORED_HELP": "中央マーカーに対して固定された位置に留まります。", - "STATIC_HELP": "どのセットにも属していません。リセンターされません。" - }, - "AUTOSTART": "起動時に自動実行", - "LAUNCH": "起動" - }, - "DISPLAY_BRIGHTNESS": "ディスプレイの明るさ", - "PROCESS_LIST": "プロセスリスト", - "REFRESH": "更新", - "PROCESS": { - "STOP": "停止", - "FORCE_KILL": "強制終了" - }, - "DEBUG_INFO": "デバッグ情報" -} \ No newline at end of file + "HOME_SCREEN": "ホーム", + "MONADO_RUNTIME": "「Monado」ランタイム", + "APPLICATIONS": "アプリ", + "GAMES": "ゲーム", + "SETTINGS": "設定", + "HELLO_USER": "こんにちは、{USER}!", + "GENERAL_SETTINGS": "全般設定", + "APPLICATION_LAUNCHER": "アプリケーションランチャー", + "APP_SETTINGS": { + "HIDE_USERNAME": "ユーザー名を非表示", + "OPAQUE_BACKGROUND": "不透明な背景", + "LOOK_AND_FEEL": "外観", + "HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示", + "ANIMATION_SPEED": "UIアニメーション速度", + "ROUND_MULTIPLIER": "UI の丸み", + "USE_SKYBOX": "スカイボックスを有効", + "USE_PASSTHROUGH": "パススルーを有効", + "CLOCK_12H": "12時間制", + "FEATURES": "機能", + "NOTIFICATIONS_ENABLED": "通知を有効にする", + "NOTIFICATIONS_SOUND_ENABLED": "通知音", + "KEYBOARD_SOUND_ENABLED": "キーボード音", + "SPACE_DRAG_MULTIPLIER": "スペースドラッグ倍率", + "SPACE_DRAG_UNLOCKED": "全軸のスペースドラッグ", + "SPACE_ROTATE_UNLOCKED": "全軸のスペース回転", + "BLOCK_GAME_INPUT": "ゲームの入力をブロック", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "入力ブロック時に監視を無視", + "CONTROLS": "コントロール", + "FOCUS_FOLLOWS_MOUSE_MODE": "トリガータッチでマウス移動", + "LEFT_HANDED_MOUSE": "左利きマウス", + "ALLOW_SLIDING": "グリップ動作中にスティックを有効", + "INVERT_SCROLL_DIRECTION_X": "水平スクロール方向を反転", + "INVERT_SCROLL_DIRECTION_Y": "垂直スクロール方向を反転", + "SCROLL_SPEED": "スクロール速度", + "LONG_PRESS_DURATION": "長押し時間", + "POINTER_LERP_FACTOR": "ポインターのスムージング", + "XR_CLICK_SENSITIVITY": "XRクリック感度", + "XR_CLICK_SENSITIVITY_RELEASE": "XRリリース感度", + "CLICK_FREEZE_TIME_MS": "クリックで一時停止時間 (ms)", + "MISC": "その他", + "XWAYLAND_BY_DEFAULT": "アプリ実行のデフォルトは互換モード", + "UPRIGHT_SCREEN_FIX": "画面の縦向き修正", + "DOUBLE_CURSOR_FIX": "ダブルカーソル修正", + "SCREEN_RENDER_DOWN": "画面の解像度を縮小", + "UPRIGHT_SCREEN_FIX_HELP": "一部のデスクトップで縦向きの画面を修正", + "DOUBLE_CURSOR_FIX_HELP": "2つのカーソルが表示される場合は、これを有効にします", + "XR_CLICK_SENSITIVITY_HELP": "アナログトリガの感度", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "クリックより低くする必要があります", + "CLICK_FREEZE_TIME_MS_HELP": "ダブルクリックの精度向上に役立ちます", + "LEFT_HANDED_MOUSE_HELP": "マウスボタンが入れ替わっている場合に有効にします", + "BLOCK_GAME_INPUT_HELP": "オーバーレイ上にマウスカーソルがあるときに入力をブロックします", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "ウォッチがホバーされているときは入力をブロックしない", + "USE_SKYBOX_HELP": "シーンアプリまたはパススルーがない場合にスカイボックスを表示します", + "WATCH_VIEW_ANGLE_MIN": "Watch view angle min", + "WATCH_VIEW_ANGLE_MIN_HELP": "Angle where the watch starts to fade away", + "WATCH_VIEW_ANGLE_MAX": "Watch view angle max", + "WATCH_VIEW_ANGLE_MAX_HELP": "Angle where the watch is fully visible", + "USE_PASSTHROUGH_HELP": "XRランタイムがサポートしていれば、パススルーを有効にします", + "SCREEN_RENDER_DOWN_HELP": "高解像度スクリーンでのエイリアシングを軽減します", + "SETS_ON_WATCH": "ウォッチのセット", + "TROUBLESHOOTING": "トラブルシューティング", + "CLEAR_SAVED_STATE": "保存された状態をクリア", + "CLEAR_PIPEWIRE_TOKENS": "PipeWire トークンをクリア", + "DELETE_ALL_CONFIGS": "設定を消去", + "RESTART_SOFTWARE": "ソフトウェアを再起動", + "CLEAR_SAVED_STATE_HELP": "セットとオーバーレイの位置をリセット", + "CLEAR_PIPEWIRE_TOKENS_HELP": "次の起動時に画面選択のプロンプトを表示", + "DELETE_ALL_CONFIGS_HELP": "conf.d 内のすべての設定ファイルを削除", + "RESTART_SOFTWARE_HELP": "再起動が必要な設定を適用", + "CAPTURE_METHOD": "Waylandスクリーンキャプチャ", + "CAPTURE_METHOD_HELP": "画面が黒くなる、または乱れる場合は、\nこの設定を変更してみてください。", + "KEYBOARD_MIDDLE_CLICK": "キーボードの中ボタンクリック", + "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで入力する際の修飾キー", + "OPTION": { + "AUTO": "自動", + "AUTO_HELP": "サポートされている場合はScreenCopy GPU、\nそうでない場合はPipeWire GPU。", + "PIPEWIRE_HELP": "GPU高速キャプチャ。\nすべてのデスクトップで標準です。", + "PW_FALLBACK_HELP": "CPU使用量が多い低速な方法です。\nPipeWire GPUが動作しない場合に試してください。", + "SCREENCOPY_GPU_HELP": "高速で、スクリーン共有ポップアップはありません。\n動作する環境: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway", + "NONE": "なし", + "HMD_PINCH": "HMD + ピンチ", + "EYE_PINCH": "つまんで目を合わせる", + "EYE_ONLY": "視野のみ", + "HMD_ONLY": "HMDのみ" + }, + "AUTOSTART_APPS": "起動時に実行するアプリ", + "HANDSFREE_POINTER": "ハンズフリーモード", + "HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。", + "UI_GRADIENT_INTENSITY": "UIグラデーションの強さ", + "RESET_PLAYSPACE": "プレイエリアをリセット", + "RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。", + "BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします", + "LANGUAGE": "言語", + "REQUIRES_RESTART": "再起動が必要です", + "GRID_OPACITY": "フロアグリッドの不透明度", + "GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度" + }, + "HELLO": "こんにちは!", + "AUDIO": { + "VOLUME": "音量", + "SETTINGS": "オーディオ設定", + "AUTO_SWITCH_TO_VR_AUDIO": "VRオーディオに自動切り替え", + "SPEAKERS": "スピーカー", + "MICROPHONES": "マイク", + "CARDS": "カード", + "SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "VRスピーカーが見つかりませんでした。手動で切り替えてください。", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "VRマイクが見つかりませんでした。手動で切り替えてください。", + "FAILED_TO_SWITCH_MICROPHONE": "マイクの切り替えに失敗しました", + "MICROPHONE_SET_SUCCESSFULLY": "マイクの設定が完了しました", + "SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定しました" + }, + "ACTIONS": { + "RECENTER_PLAYSPACE": "プレイスペースを再中央" + }, + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "解像度" + }, + "WIDTH": "幅", + "HEIGHT": "高さ", + "HIDE": "隠す", + "REMOVE": "削除", + "SHOW": "表示", + "FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:", + "APPLICATION_STARTED": "アプリケーションが起動しました", + "CLOSE_WINDOW": "ウィンドウを閉じる", + "GAME_LIST": { + "NO_GAMES_FOUND": "ゲームが見つかりませんでした", + "RUNNING_GAMES_LIST": "実行中のゲーム一覧" + }, + "TERMINATE_PROCESS": "プロセスを終了する", + "GAME_LAUNCHED": "ゲームが起動しました", + "APP_LAUNCHER": { + "MODE": { + "NATIVE": "ネイティブモード", + "CAGE": "互換モード(Cage)" + }, + "RES_TITLE": "解像度", + "ASPECT_TITLE": "アスペクト", + "ASPECT": { + "WIDE": "ワイド", + "SEMI_WIDE": "半広角", + "SQUARE": "正方形", + "SEMI_TALL": "半縦長", + "TALL": "縦長" + }, + "POS_TITLE": "配置", + "POS": { + "FLOATING": "フローティング", + "ANCHORED": "固定", + "STATIC": "固定", + "FLOATING_HELP": "独立して移動し、表示時に中央に再配置されます。", + "ANCHORED_HELP": "中央マーカーに対して固定された位置に留まります。", + "STATIC_HELP": "どのセットにも属していません。リセンターされません。" + }, + "AUTOSTART": "起動時に自動実行", + "LAUNCH": "起動" + }, + "DISPLAY_BRIGHTNESS": "ディスプレイの明るさ", + "PROCESS_LIST": "プロセスリスト", + "REFRESH": "更新", + "PROCESS": { + "STOP": "停止", + "FORCE_KILL": "強制終了" + }, + "DEBUG_INFO": "デバッグ情報" +} diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index 2f5f83fa..293341ad 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -1,166 +1,170 @@ { - "ACTIONS": { - "RECENTER_PLAYSPACE": "Wycentruj przestrzeń" - }, - "APP_SETTINGS": { - "HIDE_USERNAME": "Ukryj nazwę użytkownika", - "OPAQUE_BACKGROUND": "Nieprzezroczyste tło", - "LOOK_AND_FEEL": "Wygląd i działanie", - "HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania", - "ANIMATION_SPEED": "Prędkość animacji UI", - "ROUND_MULTIPLIER": "Zaokrąglenie krawędzi UI", - "USE_SKYBOX": "Włącz niebo", - "USE_PASSTHROUGH": "Włącz passthrough", - "CLOCK_12H": "Zegar 12-godzinny", - "FEATURES": "Funkcje", - "NOTIFICATIONS_ENABLED": "Włącz powiadomienia", - "NOTIFICATIONS_SOUND_ENABLED": "Dźwięki powiadomień", - "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", - "SPACE_DRAG_MULTIPLIER": "Mnożnik przesuwania przestrzeni", - "SPACE_DRAG_UNLOCKED": "Pozwól na przesuwanie przestrzeni na wszystkich osiach", - "SPACE_ROTATE_UNLOCKED": "Pozwól na rotację przestrzeni na wszystkich osiach", - "BLOCK_GAME_INPUT": "Blokuj input z gry", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "Nie blokuj inputu gry, gdy zegarek jest używany", - "CONTROLS": "Sterowanie", - "FOCUS_FOLLOWS_MOUSE_MODE": "Ruch myszą po dotknięciu spustu", - "LEFT_HANDED_MOUSE": "Myszka dla leworęcznych", - "ALLOW_SLIDING": "Interakcja z drążkami podczas chwytania", - "INVERT_SCROLL_DIRECTION_X": "Odwróć kierunek przewijania w poziomie", - "INVERT_SCROLL_DIRECTION_Y": "Odwróć kierunek przewijania w pionie", - "SCROLL_SPEED": "Prędkość przewijania", - "LONG_PRESS_DURATION": "Czas długiego przytrzymania", - "POINTER_LERP_FACTOR": "Wygładzanie wskaźnika", - "XR_CLICK_SENSITIVITY": "Czułość kliknięć XR", - "XR_CLICK_SENSITIVITY_RELEASE": "Czułość zwalniania XR", - "CLICK_FREEZE_TIME_MS": "Czas zamrożenia po kliknięciu (ms)", - "XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności", - "UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu", - "DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora", - "SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości", - "UPRIGHT_SCREEN_FIX_HELP": "Naprawia pionowe ekrany na niektórych komputerach", - "DOUBLE_CURSOR_FIX_HELP": "Włącz to, jeśli widzisz 2 kursory", - "XR_CLICK_SENSITIVITY_HELP": "Czułość analogowego spustu", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Musi być niższa niż kliknięcie", - "CLICK_FREEZE_TIME_MS_HELP": "Pomaga w precyzji podwójnego kliknięcia", - "LEFT_HANDED_MOUSE_HELP": "Użyj tego, jeśli przyciski myszy są zamienione", - "BLOCK_GAME_INPUT_HELP": "Blokuje wszystkie dane wejściowe, gdy kursor najedzie na nakładkę", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Nie blokuj inputu, gdy wskaźnik jest najedzony", - "USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough", - "USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje", - "SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości", - "SETS_ON_WATCH": "Lista zestawów na zegarku", - "TROUBLESHOOTING": "Rozwiązywanie problemów", - "CLEAR_SAVED_STATE": "Wyczyść zapisany stan", - "CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire", - "DELETE_ALL_CONFIGS": "Wyczyść konfigurację", - "RESTART_SOFTWARE": "Restartuj WayVR", - "CLEAR_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek", - "CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu", - "DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d", - "RESTART_SOFTWARE_HELP": "Zastosuj ustawienia wymagające ponownego uruchomienia", - "CAPTURE_METHOD": "Przechwytywanie ekranu Wayland", - "CAPTURE_METHOD_HELP": "Spróbuj zmienić tę opcję, jeśli masz\nproblemy z czarnym lub migoczącym ekranem", - "KEYBOARD_MIDDLE_CLICK": "Środkowy przycisk myszy na klawiaturze", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator, który ma zostać użyty podczas pisania\nfioletową wiązką lasera", - "OPTION": { - "AUTO": "Automatycznie", - "AUTO_HELP": "Skopiuj ekran za pomocą GPU, jeśli obsługiwane,\nw przeciwnym razie GPU PipeWire.", - "PIPEWIRE_HELP": "Szybkie przechwytywanie GPU,\nstandard na wszystkich komputerach.", - "PW_FALLBACK_HELP": "Powolna metoda z wysokim użyciem procesora.\nWypróbuj w przypadku, gdy PipeWire GPU nie działa", - "SCREENCOPY_GPU_HELP": "Szybkie działanie, brak wyskakujących okien z informacją o udostępnianiu ekranu.\nDziała na: Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway", - "NONE": "Brak", - "HMD_PINCH": "HMD + ściśnięcie placami", - "EYE_PINCH": "Ściśnięcie palcami + oko", - "EYE_ONLY": "Tylko oko", - "HMD_ONLY": "Tylko HMD" - }, - "AUTOSTART_APPS": "Aplikacje auto-start", - "HANDSFREE_POINTER": "Tryb bez użycia rąk", - "HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.", - "UI_GRADIENT_INTENSITY": "Intensywność gradientu UI", - "RESET_PLAYSPACE": "Zresetuj przestrzeń gry", - "RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry.", - "BLOCK_POSES_ON_KBD_INTERACTION": "Blokuj pozy podczas interakcji z klawiaturą", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'", - "LANGUAGE": "Język", - "REQUIRES_RESTART": "Wymaga restartu", - "MISC": "Różne", - "GRID_OPACITY": "Przezroczystość siatki podłogowej", - "GRID_OPACITY_HELP": "Przezroczystość siatki podłogowej, gdy włączony jest skybox" - }, - "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", - "APPLICATIONS": "Aplikacje", - "AUDIO": { - "AUTO_SWITCH_TO_VR_AUDIO": "Automatyczne przełączanie na dźwięk VR", - "CARDS": "Karty", - "FAILED_TO_SWITCH_MICROPHONE": "Nie udało się przełączyć mikrofon", - "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon ustawiono pomyślnie", - "MICROPHONES": "Mikrofony", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Brak mikrofonu VR. Włącz go ręcznie.", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Brak głośników VR. Włącz je ręcznie.", - "SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej", - "SETTINGS": "Ustawienia dźwięku", - "SPEAKERS": "Głośniki", - "SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawiono pomyślnie", - "VOLUME": "Głośność" - }, - "GAMES": "Gry", - "GENERAL_SETTINGS": "Ustawienia ogólne", - "HEIGHT": "Wysokość", - "HELLO": "Witaj!", - "HELLO_USER": "Witaj, {USER}!", - "HIDE": "Ukryj", - "HOME_SCREEN": "Ekran główny", - "MONADO_RUNTIME": "Środowisko Monado", - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "Rozdzielczość" - }, - "REMOVE": "Usuń", - "SETTINGS": "Ustawienia", - "SHOW": "Pokaż", - "WIDTH": "Szerokość", - "FAILED_TO_LAUNCH_APPLICATION": "Nie udało się uruchomić aplikacji:", - "APPLICATION_STARTED": "Aplikacja uruchomiona", - "CLOSE_WINDOW": "Zamknij okno", - "GAME_LIST": { - "NO_GAMES_FOUND": "Nie znaleziono gier", - "RUNNING_GAMES_LIST": "Lista uruchomionych gier" - }, - "TERMINATE_PROCESS": "Zakończ proces", - "GAME_LAUNCHED": "Gra uruchomiona", - "APP_LAUNCHER": { - "MODE": { - "NATIVE": "Tryb natywny", - "CAGE": "Tryb kompatybilności (Cage)" - }, - "RES_TITLE": "Rozdzielczość", - "ASPECT_TITLE": "Proporcje", - "ASPECT": { - "WIDE": "Szeroki", - "SEMI_WIDE": "Półszeroki", - "SQUARE": "Kwadrat", - "SEMI_TALL": "Pół-wysoki", - "TALL": "Wysoki" - }, - "POS_TITLE": "Pozycjonowanie", - "POS": { - "FLOATING": "Pływający", - "ANCHORED": "Przymocowane", - "STATIC": "Statyczny", - "FLOATING_HELP": "Porusza się niezależnie, wycentrowuje się po otwarciu.", - "ANCHORED_HELP": "Pozostaje nieruchoma względem centralnego znacznika.", - "STATIC_HELP": "Nie należy do żadnego zestawu. Nie wyśrodkowuje." - }, - "AUTOSTART": "Uruchom automatycznie przy starcie", - "LAUNCH": "Uruchom" - }, - "DISPLAY_BRIGHTNESS": "Jasność wyświetlacza", - "PROCESS_LIST": "Lista procesów", - "REFRESH": "Odśwież", - "PROCESS": { - "STOP": "Zatrzymaj", - "FORCE_KILL": "Wymuś zakończenie" - }, - "DEBUG_INFO": "Informacje debugowania" -} \ No newline at end of file + "ACTIONS": { + "RECENTER_PLAYSPACE": "Wycentruj przestrzeń" + }, + "APP_SETTINGS": { + "HIDE_USERNAME": "Ukryj nazwę użytkownika", + "OPAQUE_BACKGROUND": "Nieprzezroczyste tło", + "LOOK_AND_FEEL": "Wygląd i działanie", + "HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania", + "ANIMATION_SPEED": "Prędkość animacji UI", + "ROUND_MULTIPLIER": "Zaokrąglenie krawędzi UI", + "USE_SKYBOX": "Włącz niebo", + "USE_PASSTHROUGH": "Włącz passthrough", + "CLOCK_12H": "Zegar 12-godzinny", + "FEATURES": "Funkcje", + "NOTIFICATIONS_ENABLED": "Włącz powiadomienia", + "NOTIFICATIONS_SOUND_ENABLED": "Dźwięki powiadomień", + "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", + "SPACE_DRAG_MULTIPLIER": "Mnożnik przesuwania przestrzeni", + "SPACE_DRAG_UNLOCKED": "Pozwól na przesuwanie przestrzeni na wszystkich osiach", + "SPACE_ROTATE_UNLOCKED": "Pozwól na rotację przestrzeni na wszystkich osiach", + "BLOCK_GAME_INPUT": "Blokuj input z gry", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "Nie blokuj inputu gry, gdy zegarek jest używany", + "CONTROLS": "Sterowanie", + "FOCUS_FOLLOWS_MOUSE_MODE": "Ruch myszą po dotknięciu spustu", + "LEFT_HANDED_MOUSE": "Myszka dla leworęcznych", + "ALLOW_SLIDING": "Interakcja z drążkami podczas chwytania", + "INVERT_SCROLL_DIRECTION_X": "Odwróć kierunek przewijania w poziomie", + "INVERT_SCROLL_DIRECTION_Y": "Odwróć kierunek przewijania w pionie", + "SCROLL_SPEED": "Prędkość przewijania", + "LONG_PRESS_DURATION": "Czas długiego przytrzymania", + "POINTER_LERP_FACTOR": "Wygładzanie wskaźnika", + "XR_CLICK_SENSITIVITY": "Czułość kliknięć XR", + "XR_CLICK_SENSITIVITY_RELEASE": "Czułość zwalniania XR", + "CLICK_FREEZE_TIME_MS": "Czas zamrożenia po kliknięciu (ms)", + "XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności", + "UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu", + "DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora", + "SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości", + "UPRIGHT_SCREEN_FIX_HELP": "Naprawia pionowe ekrany na niektórych komputerach", + "DOUBLE_CURSOR_FIX_HELP": "Włącz to, jeśli widzisz 2 kursory", + "XR_CLICK_SENSITIVITY_HELP": "Czułość analogowego spustu", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "Musi być niższa niż kliknięcie", + "CLICK_FREEZE_TIME_MS_HELP": "Pomaga w precyzji podwójnego kliknięcia", + "LEFT_HANDED_MOUSE_HELP": "Użyj tego, jeśli przyciski myszy są zamienione", + "BLOCK_GAME_INPUT_HELP": "Blokuje wszystkie dane wejściowe, gdy kursor najedzie na nakładkę", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Nie blokuj inputu, gdy wskaźnik jest najedzony", + "USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough", + "WATCH_VIEW_ANGLE_MIN": "Kąt widzenia zegarka min", + "WATCH_VIEW_ANGLE_MIN_HELP": "Kąt, przy którym zegarek zaczyna zanikać", + "WATCH_VIEW_ANGLE_MAX": "Kąt widzenia zegarka max", + "WATCH_VIEW_ANGLE_MAX_HELP": "Kąt, przy którym zegarek staje się w pełni widoczny", + "USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje", + "SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości", + "SETS_ON_WATCH": "Lista zestawów na zegarku", + "TROUBLESHOOTING": "Rozwiązywanie problemów", + "CLEAR_SAVED_STATE": "Wyczyść zapisany stan", + "CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire", + "DELETE_ALL_CONFIGS": "Wyczyść konfigurację", + "RESTART_SOFTWARE": "Restartuj WayVR", + "CLEAR_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek", + "CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu", + "DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d", + "RESTART_SOFTWARE_HELP": "Zastosuj ustawienia wymagające ponownego uruchomienia", + "CAPTURE_METHOD": "Przechwytywanie ekranu Wayland", + "CAPTURE_METHOD_HELP": "Spróbuj zmienić tę opcję, jeśli masz\nproblemy z czarnym lub migoczącym ekranem", + "KEYBOARD_MIDDLE_CLICK": "Środkowy przycisk myszy na klawiaturze", + "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator, który ma zostać użyty podczas pisania\nfioletową wiązką lasera", + "OPTION": { + "AUTO": "Automatycznie", + "AUTO_HELP": "Skopiuj ekran za pomocą GPU, jeśli obsługiwane,\nw przeciwnym razie GPU PipeWire.", + "PIPEWIRE_HELP": "Szybkie przechwytywanie GPU,\nstandard na wszystkich komputerach.", + "PW_FALLBACK_HELP": "Powolna metoda z wysokim użyciem procesora.\nWypróbuj w przypadku, gdy PipeWire GPU nie działa", + "SCREENCOPY_GPU_HELP": "Szybkie działanie, brak wyskakujących okien z informacją o udostępnianiu ekranu.\nDziała na: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway", + "NONE": "Brak", + "HMD_PINCH": "HMD + ściśnięcie placami", + "EYE_PINCH": "Ściśnięcie palcami + oko", + "EYE_ONLY": "Tylko oko", + "HMD_ONLY": "Tylko HMD" + }, + "AUTOSTART_APPS": "Aplikacje auto-start", + "HANDSFREE_POINTER": "Tryb bez użycia rąk", + "HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.", + "UI_GRADIENT_INTENSITY": "Intensywność gradientu UI", + "RESET_PLAYSPACE": "Zresetuj przestrzeń gry", + "RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry.", + "BLOCK_POSES_ON_KBD_INTERACTION": "Blokuj pozy podczas interakcji z klawiaturą", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'", + "LANGUAGE": "Język", + "REQUIRES_RESTART": "Wymaga restartu", + "MISC": "Różne", + "GRID_OPACITY": "Przezroczystość siatki podłogowej", + "GRID_OPACITY_HELP": "Przezroczystość siatki podłogowej, gdy włączony jest skybox" + }, + "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", + "APPLICATIONS": "Aplikacje", + "AUDIO": { + "AUTO_SWITCH_TO_VR_AUDIO": "Automatyczne przełączanie na dźwięk VR", + "CARDS": "Karty", + "FAILED_TO_SWITCH_MICROPHONE": "Nie udało się przełączyć mikrofon", + "MICROPHONE_SET_SUCCESSFULLY": "Mikrofon ustawiono pomyślnie", + "MICROPHONES": "Mikrofony", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "Brak mikrofonu VR. Włącz go ręcznie.", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Brak głośników VR. Włącz je ręcznie.", + "SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej", + "SETTINGS": "Ustawienia dźwięku", + "SPEAKERS": "Głośniki", + "SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawiono pomyślnie", + "VOLUME": "Głośność" + }, + "GAMES": "Gry", + "GENERAL_SETTINGS": "Ustawienia ogólne", + "HEIGHT": "Wysokość", + "HELLO": "Witaj!", + "HELLO_USER": "Witaj, {USER}!", + "HIDE": "Ukryj", + "HOME_SCREEN": "Ekran główny", + "MONADO_RUNTIME": "Środowisko Monado", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "Rozdzielczość" + }, + "REMOVE": "Usuń", + "SETTINGS": "Ustawienia", + "SHOW": "Pokaż", + "WIDTH": "Szerokość", + "FAILED_TO_LAUNCH_APPLICATION": "Nie udało się uruchomić aplikacji:", + "APPLICATION_STARTED": "Aplikacja uruchomiona", + "CLOSE_WINDOW": "Zamknij okno", + "GAME_LIST": { + "NO_GAMES_FOUND": "Nie znaleziono gier", + "RUNNING_GAMES_LIST": "Lista uruchomionych gier" + }, + "TERMINATE_PROCESS": "Zakończ proces", + "GAME_LAUNCHED": "Gra uruchomiona", + "APP_LAUNCHER": { + "MODE": { + "NATIVE": "Tryb natywny", + "CAGE": "Tryb kompatybilności (Cage)" + }, + "RES_TITLE": "Rozdzielczość", + "ASPECT_TITLE": "Proporcje", + "ASPECT": { + "WIDE": "Szeroki", + "SEMI_WIDE": "Półszeroki", + "SQUARE": "Kwadrat", + "SEMI_TALL": "Pół-wysoki", + "TALL": "Wysoki" + }, + "POS_TITLE": "Pozycjonowanie", + "POS": { + "FLOATING": "Pływający", + "ANCHORED": "Przymocowane", + "STATIC": "Statyczny", + "FLOATING_HELP": "Porusza się niezależnie, wycentrowuje się po otwarciu.", + "ANCHORED_HELP": "Pozostaje nieruchoma względem centralnego znacznika.", + "STATIC_HELP": "Nie należy do żadnego zestawu. Nie wyśrodkowuje." + }, + "AUTOSTART": "Uruchom automatycznie przy starcie", + "LAUNCH": "Uruchom" + }, + "DISPLAY_BRIGHTNESS": "Jasność wyświetlacza", + "PROCESS_LIST": "Lista procesów", + "REFRESH": "Odśwież", + "PROCESS": { + "STOP": "Zatrzymaj", + "FORCE_KILL": "Wymuś zakończenie" + }, + "DEBUG_INFO": "Informacje debugowania" +} diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index 915487e2..745cbbbb 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -1,166 +1,170 @@ { - "ACTIONS": { - "RECENTER_PLAYSPACE": "重置游玩区中心" - }, - "APP_LAUNCHER": { - "ASPECT": { - "SEMI_TALL": "略高", - "SEMI_WIDE": "略宽", - "SQUARE": "正方形", - "TALL": "高", - "WIDE": "宽" - }, - "ASPECT_TITLE": "比例", - "AUTOSTART": "启动时自动运行", - "LAUNCH": "启动", - "MODE": { - "CAGE": "兼容模式 (Cage)", - "NATIVE": "原生模式" - }, - "POS": { - "ANCHORED": "锚定", - "ANCHORED_HELP": "相对于中心标记保持固定。", - "FLOATING": "悬浮", - "FLOATING_HELP": "独立移动,显示时自动重置中心。", - "STATIC": "静态", - "STATIC_HELP": "不属于任何集合。不会重置中心。" - }, - "POS_TITLE": "定位", - "RES_TITLE": "分辨率" - }, - "APP_SETTINGS": { - "ALLOW_SLIDING": "抓取时允许摇杆交互", - "ANIMATION_SPEED": "UI 动画速度", - "BLOCK_GAME_INPUT": "屏蔽游戏输入", - "BLOCK_GAME_INPUT_HELP": "悬停在覆盖层上时屏蔽所有输入", - "BLOCK_GAME_INPUT_IGNORE_WATCH": "屏蔽输入时忽略手表", - "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "悬停在手表上时不屏蔽输入", - "CAPTURE_METHOD": "Wayland 屏幕采集", - "CAPTURE_METHOD_HELP": "如果您遇到黑屏或花屏,请尝试更改此项", - "CLEAR_PIPEWIRE_TOKENS": "清除 PipeWire 令牌", - "CLEAR_PIPEWIRE_TOKENS_HELP": "下次启动时提示选择屏幕", - "CLEAR_SAVED_STATE": "清除保存的状态", - "CLEAR_SAVED_STATE_HELP": "重置集合与覆盖层位置", - "CLICK_FREEZE_TIME_MS": "点击冻结时间 (毫秒)", - "CLICK_FREEZE_TIME_MS_HELP": "有助于提高双击精度", - "CLOCK_12H": "12 小时制时钟", - "CONTROLS": "控制", - "DELETE_ALL_CONFIGS": "擦除配置", - "DELETE_ALL_CONFIGS_HELP": "从 conf.d 中删除所有配置文件", - "DOUBLE_CURSOR_FIX": "双光标修复", - "DOUBLE_CURSOR_FIX_HELP": "如果您看到两个光标,请启用此项", - "FEATURES": "功能", - "FOCUS_FOLLOWS_MOUSE_MODE": "触摸扳机时移动鼠标", - "HIDE_GRAB_HELP": "隐藏抓取帮助", - "HIDE_USERNAME": "隐藏用户名", - "INVERT_SCROLL_DIRECTION_X": "反转水平滚动方向", - "INVERT_SCROLL_DIRECTION_Y": "反转垂直滚动方向", - "KEYBOARD_MIDDLE_CLICK": "键盘中键点击", - "KEYBOARD_MIDDLE_CLICK_HELP": "使用紫色激光打字时的修饰键", - "KEYBOARD_SOUND_ENABLED": "键盘音效", - "LEFT_HANDED_MOUSE": "左手鼠标", - "LEFT_HANDED_MOUSE_HELP": "如果鼠标按钮反置,请使用此项", - "LONG_PRESS_DURATION": "长按持续时间", - "LOOK_AND_FEEL": "外观与体验", - "MISC": "杂项", - "NOTIFICATIONS_ENABLED": "启用通知", - "NOTIFICATIONS_SOUND_ENABLED": "通知音效", - "OPAQUE_BACKGROUND": "不透明背景", - "OPTION": { - "AUTO": "自动", - "AUTO_HELP": "如果支持,则使用 ScreenCopy GPU,\n否则使用 PipeWire GPU。", - "PIPEWIRE_HELP": "快速 GPU 采集,\n所有桌面环境的标准方式。", - "PW_FALLBACK_HELP": "高 CPU 占用的慢速方法。\n在 PipeWire GPU 不起作用时尝试。", - "SCREENCOPY_GPU_HELP": "快速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway", - "SCREENCOPY_HELP": "慢速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway", - "NONE": "无", - "HMD_PINCH": "HMD + 捏合", - "EYE_PINCH": "眼睛 + 捏合", - "EYE_ONLY": "仅眼球", - "HMD_ONLY": "仅限头显" - }, - "POINTER_LERP_FACTOR": "指针平滑", - "RESTART_SOFTWARE": "重启软件", - "RESTART_SOFTWARE_HELP": "应用需要重启的设置", - "ROUND_MULTIPLIER": "UI 边缘圆角", - "SCREEN_RENDER_DOWN": "以较低分辨率渲染屏幕", - "SCREEN_RENDER_DOWN_HELP": "有助于解决高分辨率屏幕的锯齿问题", - "SCROLL_SPEED": "滚动速度", - "SETS_ON_WATCH": "在手表上显示集合", - "SPACE_DRAG_MULTIPLIER": "空间拖拽倍率", - "SPACE_DRAG_UNLOCKED": "允许在所有轴上进行空间拖拽", - "SPACE_ROTATE_UNLOCKED": "允许在所有轴上进行空间旋转", - "TROUBLESHOOTING": "故障排除", - "UPRIGHT_SCREEN_FIX": "垂直屏幕修复", - "UPRIGHT_SCREEN_FIX_HELP": "修复某些桌面上的垂直屏幕问题", - "USE_PASSTHROUGH": "启用穿透", - "USE_PASSTHROUGH_HELP": "如果 XR 运行时支持,则允许穿透", - "USE_SKYBOX": "启用天空盒", - "USE_SKYBOX_HELP": "如果没有场景应用或穿透,则显示天空盒", - "XR_CLICK_SENSITIVITY": "XR 点击灵敏度", - "XR_CLICK_SENSITIVITY_HELP": "模拟扳机灵敏度", - "XR_CLICK_SENSITIVITY_RELEASE": "XR 释放灵敏度", - "XR_CLICK_SENSITIVITY_RELEASE_HELP": "必须低于点击灵敏度", - "XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用", - "AUTOSTART_APPS": "开机启动应用", - "HANDSFREE_POINTER": "免提模式", - "HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。", - "UI_GRADIENT_INTENSITY": "UI 渐变强度", - "RESET_PLAYSPACE": "重置游戏空间", - "RESET_PLAYSPACE_HELP": "清除舞台空间偏移。", - "BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势", - "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势", - "LANGUAGE": "语言", - "REQUIRES_RESTART": "需要重启", - "GRID_OPACITY": "地面网格不透明度", - "GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度" - }, - "APPLICATION_LAUNCHER": "应用启动器", - "APPLICATION_STARTED": "应用已启动", - "APPLICATIONS": "应用", - "AUDIO": { - "AUTO_SWITCH_TO_VR_AUDIO": "自动切换到 VR 音频", - "CARDS": "声卡", - "FAILED_TO_SWITCH_MICROPHONE": "切换麦克风失败", - "MICROPHONE_SET_SUCCESSFULLY": "麦克风设置成功", - "MICROPHONES": "麦克风", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "未找到 VR 麦克风。请手动切换。", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "未找到 VR 扬声器。请手动切换。", - "SELECT_AUDIO_CARD_PROFILE": "选择声卡配置", - "SETTINGS": "音频设置", - "SPEAKERS": "扬声器", - "SPEAKERS_SET_SUCCESSFULLY": "扬声器设置成功", - "VOLUME": "音量" - }, - "CLOSE_WINDOW": "关闭窗口", - "DISPLAY_BRIGHTNESS": "显示亮度", - "FAILED_TO_LAUNCH_APPLICATION": "启动应用失败:", - "GAME_LAUNCHED": "游戏已启动", - "GAME_LIST": { - "NO_GAMES_FOUND": "未找到游戏", - "RUNNING_GAMES_LIST": "正在运行的游戏列表" - }, - "GAMES": "游戏", - "GENERAL_SETTINGS": "通用设置", - "HEIGHT": "高度", - "HELLO": "你好!", - "HELLO_USER": "你好,{USER}!", - "HIDE": "隐藏", - "HOME_SCREEN": "主页", - "MONADO_RUNTIME": "Monado 运行时", - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "分辨率" - }, - "REMOVE": "移除", - "SETTINGS": "设置", - "SHOW": "显示", - "TERMINATE_PROCESS": "终止进程", - "WIDTH": "宽度", - "PROCESS_LIST": "进程列表", - "REFRESH": "刷新", - "PROCESS": { - "STOP": "停止", - "FORCE_KILL": "强制关闭" - }, - "DEBUG_INFO": "调试信息" -} \ No newline at end of file + "ACTIONS": { + "RECENTER_PLAYSPACE": "重置游玩区中心" + }, + "APP_LAUNCHER": { + "ASPECT": { + "SEMI_TALL": "略高", + "SEMI_WIDE": "略宽", + "SQUARE": "正方形", + "TALL": "高", + "WIDE": "宽" + }, + "ASPECT_TITLE": "比例", + "AUTOSTART": "启动时自动运行", + "LAUNCH": "启动", + "MODE": { + "CAGE": "兼容模式 (Cage)", + "NATIVE": "原生模式" + }, + "POS": { + "ANCHORED": "锚定", + "ANCHORED_HELP": "相对于中心标记保持固定。", + "FLOATING": "悬浮", + "FLOATING_HELP": "独立移动,显示时自动重置中心。", + "STATIC": "静态", + "STATIC_HELP": "不属于任何集合。不会重置中心。" + }, + "POS_TITLE": "定位", + "RES_TITLE": "分辨率" + }, + "APP_SETTINGS": { + "ALLOW_SLIDING": "抓取时允许摇杆交互", + "ANIMATION_SPEED": "UI 动画速度", + "BLOCK_GAME_INPUT": "屏蔽游戏输入", + "BLOCK_GAME_INPUT_HELP": "悬停在覆盖层上时屏蔽所有输入", + "BLOCK_GAME_INPUT_IGNORE_WATCH": "屏蔽输入时忽略手表", + "BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "悬停在手表上时不屏蔽输入", + "CAPTURE_METHOD": "Wayland 屏幕采集", + "CAPTURE_METHOD_HELP": "如果您遇到黑屏或花屏,请尝试更改此项", + "CLEAR_PIPEWIRE_TOKENS": "清除 PipeWire 令牌", + "CLEAR_PIPEWIRE_TOKENS_HELP": "下次启动时提示选择屏幕", + "CLEAR_SAVED_STATE": "清除保存的状态", + "CLEAR_SAVED_STATE_HELP": "重置集合与覆盖层位置", + "CLICK_FREEZE_TIME_MS": "点击冻结时间 (毫秒)", + "CLICK_FREEZE_TIME_MS_HELP": "有助于提高双击精度", + "CLOCK_12H": "12 小时制时钟", + "CONTROLS": "控制", + "DELETE_ALL_CONFIGS": "擦除配置", + "DELETE_ALL_CONFIGS_HELP": "从 conf.d 中删除所有配置文件", + "DOUBLE_CURSOR_FIX": "双光标修复", + "DOUBLE_CURSOR_FIX_HELP": "如果您看到两个光标,请启用此项", + "FEATURES": "功能", + "FOCUS_FOLLOWS_MOUSE_MODE": "触摸扳机时移动鼠标", + "HIDE_GRAB_HELP": "隐藏抓取帮助", + "HIDE_USERNAME": "隐藏用户名", + "INVERT_SCROLL_DIRECTION_X": "反转水平滚动方向", + "INVERT_SCROLL_DIRECTION_Y": "反转垂直滚动方向", + "KEYBOARD_MIDDLE_CLICK": "键盘中键点击", + "KEYBOARD_MIDDLE_CLICK_HELP": "使用紫色激光打字时的修饰键", + "KEYBOARD_SOUND_ENABLED": "键盘音效", + "LEFT_HANDED_MOUSE": "左手鼠标", + "LEFT_HANDED_MOUSE_HELP": "如果鼠标按钮反置,请使用此项", + "LONG_PRESS_DURATION": "长按持续时间", + "LOOK_AND_FEEL": "外观与体验", + "MISC": "杂项", + "NOTIFICATIONS_ENABLED": "启用通知", + "NOTIFICATIONS_SOUND_ENABLED": "通知音效", + "OPAQUE_BACKGROUND": "不透明背景", + "OPTION": { + "AUTO": "自动", + "AUTO_HELP": "如果支持,则使用 ScreenCopy GPU,\n否则使用 PipeWire GPU。", + "PIPEWIRE_HELP": "快速 GPU 采集,\n所有桌面环境的标准方式。", + "PW_FALLBACK_HELP": "高 CPU 占用的慢速方法。\n在 PipeWire GPU 不起作用时尝试。", + "SCREENCOPY_GPU_HELP": "快速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "慢速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway", + "NONE": "无", + "HMD_PINCH": "HMD + 捏合", + "EYE_PINCH": "眼睛 + 捏合", + "EYE_ONLY": "仅眼球", + "HMD_ONLY": "仅限头显" + }, + "POINTER_LERP_FACTOR": "指针平滑", + "RESTART_SOFTWARE": "重启软件", + "RESTART_SOFTWARE_HELP": "应用需要重启的设置", + "ROUND_MULTIPLIER": "UI 边缘圆角", + "SCREEN_RENDER_DOWN": "以较低分辨率渲染屏幕", + "SCREEN_RENDER_DOWN_HELP": "有助于解决高分辨率屏幕的锯齿问题", + "SCROLL_SPEED": "滚动速度", + "SETS_ON_WATCH": "在手表上显示集合", + "SPACE_DRAG_MULTIPLIER": "空间拖拽倍率", + "SPACE_DRAG_UNLOCKED": "允许在所有轴上进行空间拖拽", + "SPACE_ROTATE_UNLOCKED": "允许在所有轴上进行空间旋转", + "TROUBLESHOOTING": "故障排除", + "UPRIGHT_SCREEN_FIX": "垂直屏幕修复", + "UPRIGHT_SCREEN_FIX_HELP": "修复某些桌面上的垂直屏幕问题", + "USE_PASSTHROUGH": "启用穿透", + "USE_PASSTHROUGH_HELP": "如果 XR 运行时支持,则允许穿透", + "USE_SKYBOX": "启用天空盒", + "USE_SKYBOX_HELP": "如果没有场景应用或穿透,则显示天空盒", + "WATCH_VIEW_ANGLE_MIN": "手表视角角度最小值", + "WATCH_VIEW_ANGLE_MIN_HELP": "手表开始淡出的角度", + "WATCH_VIEW_ANGLE_MAX": "手表视角角度最大值", + "WATCH_VIEW_ANGLE_MAX_HELP": "手表完全可见的角度", + "XR_CLICK_SENSITIVITY": "XR 点击灵敏度", + "XR_CLICK_SENSITIVITY_HELP": "模拟扳机灵敏度", + "XR_CLICK_SENSITIVITY_RELEASE": "XR 释放灵敏度", + "XR_CLICK_SENSITIVITY_RELEASE_HELP": "必须低于点击灵敏度", + "XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用", + "AUTOSTART_APPS": "开机启动应用", + "HANDSFREE_POINTER": "免提模式", + "HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。", + "UI_GRADIENT_INTENSITY": "UI 渐变强度", + "RESET_PLAYSPACE": "重置游戏空间", + "RESET_PLAYSPACE_HELP": "清除舞台空间偏移。", + "BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势", + "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势", + "LANGUAGE": "语言", + "REQUIRES_RESTART": "需要重启", + "GRID_OPACITY": "地面网格不透明度", + "GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度" + }, + "APPLICATION_LAUNCHER": "应用启动器", + "APPLICATION_STARTED": "应用已启动", + "APPLICATIONS": "应用", + "AUDIO": { + "AUTO_SWITCH_TO_VR_AUDIO": "自动切换到 VR 音频", + "CARDS": "声卡", + "FAILED_TO_SWITCH_MICROPHONE": "切换麦克风失败", + "MICROPHONE_SET_SUCCESSFULLY": "麦克风设置成功", + "MICROPHONES": "麦克风", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "未找到 VR 麦克风。请手动切换。", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "未找到 VR 扬声器。请手动切换。", + "SELECT_AUDIO_CARD_PROFILE": "选择声卡配置", + "SETTINGS": "音频设置", + "SPEAKERS": "扬声器", + "SPEAKERS_SET_SUCCESSFULLY": "扬声器设置成功", + "VOLUME": "音量" + }, + "CLOSE_WINDOW": "关闭窗口", + "DISPLAY_BRIGHTNESS": "显示亮度", + "FAILED_TO_LAUNCH_APPLICATION": "启动应用失败:", + "GAME_LAUNCHED": "游戏已启动", + "GAME_LIST": { + "NO_GAMES_FOUND": "未找到游戏", + "RUNNING_GAMES_LIST": "正在运行的游戏列表" + }, + "GAMES": "游戏", + "GENERAL_SETTINGS": "通用设置", + "HEIGHT": "高度", + "HELLO": "你好!", + "HELLO_USER": "你好,{USER}!", + "HIDE": "隐藏", + "HOME_SCREEN": "主页", + "MONADO_RUNTIME": "Monado 运行时", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "分辨率" + }, + "REMOVE": "移除", + "SETTINGS": "设置", + "SHOW": "显示", + "TERMINATE_PROCESS": "终止进程", + "WIDTH": "宽度", + "PROCESS_LIST": "进程列表", + "REFRESH": "刷新", + "PROCESS": { + "STOP": "停止", + "FORCE_KILL": "强制关闭" + }, + "DEBUG_INFO": "调试信息" +} diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 32ec394a..50930517 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -284,6 +284,8 @@ enum SettingType { UsePassthrough, UseSkybox, GridOpacity, + WatchViewAngleMax, + WatchViewAngleMin, XrClickSensitivity, XrClickSensitivityRelease, XwaylandByDefault, @@ -339,6 +341,8 @@ impl SettingType { Self::SpaceDragMultiplier => &mut config.space_drag_multiplier, Self::PointerLerpFactor => &mut config.pointer_lerp_factor, Self::GridOpacity => &mut config.grid_opacity, + Self::WatchViewAngleMax => &mut config.watch_view_angle_max, + Self::WatchViewAngleMin => &mut config.watch_view_angle_min, _ => panic!("Requested f32 for non-f32 SettingType"), } } @@ -440,6 +444,8 @@ impl SettingType { Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"), Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"), Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"), + Self::WatchViewAngleMax => Ok("APP_SETTINGS.WATCH_VIEW_ANGLE_MAX"), + Self::WatchViewAngleMin => Ok("APP_SETTINGS.WATCH_VIEW_ANGLE_MIN"), Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"), Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"), Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"), @@ -464,6 +470,8 @@ impl SettingType { Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"), Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"), Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"), + Self::WatchViewAngleMin => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_MIN_HELP"), + Self::WatchViewAngleMax => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_MAX_HELP"), _ => None, } } diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 992bf06c..2f5b0da6 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -19,6 +19,8 @@ impl State { options_checkbox(par.mp, c, SettingType::BlockGameInput)?; options_checkbox(par.mp, c, SettingType::BlockGameInputIgnoreWatch)?; options_checkbox(par.mp, c, SettingType::BlockPosesOnKbdInteraction)?; + options_slider_f32(par.mp, c, SettingType::WatchViewAngleMin, 0.0, 1.0, 0.01)?; + options_slider_f32(par.mp, c, SettingType::WatchViewAngleMax, 0.0, 1.0, 0.01)?; Ok(State {}) } } diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 237fca9d..32fd7c1b 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -153,6 +153,8 @@ pub struct AutoSettings { pub block_game_input_ignore_watch: bool, pub block_poses_on_kbd_interaction: bool, pub space_drag_multiplier: f32, + pub watch_view_angle_min: f32, + pub watch_view_angle_max: f32, pub use_skybox: bool, pub grid_opacity: f32, pub use_passthrough: bool, @@ -205,6 +207,8 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { block_game_input_ignore_watch: config.block_game_input_ignore_watch, block_poses_on_kbd_interaction: config.block_poses_on_kbd_interaction, space_drag_multiplier: config.space_drag_multiplier, + watch_view_angle_min: config.watch_view_angle_min, + watch_view_angle_max: config.watch_view_angle_max, use_skybox: config.use_skybox, grid_opacity: config.grid_opacity, use_passthrough: config.use_passthrough, From 5cf7b986f30a1f84cdc42665329efae7adadda63 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Tue, 12 May 2026 21:39:04 +0200 Subject: [PATCH 047/307] wayvr: Remember skymap texture across restarts --- wayvr/src/config.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 32fd7c1b..eed2709c 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -1,7 +1,10 @@ use config::{Config, File}; use log::error; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use wayvr_ipc::packet_client::WvrProcessLaunchParams; use wlx_common::{ astr_containers::AStrMap, @@ -156,6 +159,7 @@ pub struct AutoSettings { pub watch_view_angle_min: f32, pub watch_view_angle_max: f32, pub use_skybox: bool, + pub skybox_texture: Arc, pub grid_opacity: f32, pub use_passthrough: bool, pub screen_render_down: bool, @@ -210,6 +214,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { watch_view_angle_min: config.watch_view_angle_min, watch_view_angle_max: config.watch_view_angle_max, use_skybox: config.use_skybox, + skybox_texture: config.skybox_texture.clone(), grid_opacity: config.grid_opacity, use_passthrough: config.use_passthrough, screen_render_down: config.screen_render_down, From 20743f91eb970fe76f67c3f4844b2205a8e5b1cd Mon Sep 17 00:00:00 2001 From: Aleksander Date: Tue, 12 May 2026 21:59:54 +0200 Subject: [PATCH 048/307] clippy --- dash-frontend/src/tab/apps.rs | 11 ------ dash-frontend/src/tab/monado.rs | 12 +++---- dash-frontend/src/tab/settings/mod.rs | 2 +- .../src/util/networking/http_client.rs | 13 ++++--- dash-frontend/src/util/networking/mod.rs | 4 +-- .../src/util/networking/skymap_catalog.rs | 12 ++----- dash-frontend/src/util/popup_manager.rs | 23 +++++------- dash-frontend/src/util/wgui_simple.rs | 6 ++-- dash-frontend/src/views/download_file.rs | 2 +- dash-frontend/src/views/game_launcher.rs | 8 ++--- dash-frontend/src/views/game_list.rs | 4 +-- .../src/views/remote_skymap_downloader.rs | 9 ++--- dash-frontend/src/views/remote_skymap_list.rs | 35 ++++++------------ dash-frontend/src/views/skymap_list.rs | 12 +++---- dash-frontend/src/views/skymap_list_cell.rs | 4 +-- wayvr/src/backend/openxr/playspace.rs | 2 +- wayvr/src/backend/openxr/skybox.rs | 4 +-- wayvr/src/config.rs | 2 +- wayvr/src/gui/panel/mod.rs | 6 ++-- wayvr/src/ipc/ipc_server.rs | 10 +++--- wayvr/src/overlays/dashboard.rs | 2 +- wayvr/src/state.rs | 1 - wayvr/src/subsystem/hid/mod.rs | 15 ++++---- wgui/src/components/color_selector.rs | 17 ++++----- wgui/src/components/slider.rs | 36 +++++++++---------- wgui/src/drawing.rs | 1 + wgui/src/layout.rs | 4 +-- wgui/src/parser/component_slider.rs | 2 +- wgui/src/parser/mod.rs | 6 ++-- wgui/src/widget/mod.rs | 26 ++++++-------- 30 files changed, 126 insertions(+), 165 deletions(-) diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs index cf093f84..a55cf356 100644 --- a/dash-frontend/src/tab/apps.rs +++ b/dash-frontend/src/tab/apps.rs @@ -11,7 +11,6 @@ use wgui::{ globals::WguiGlobals, layout::{WidgetID, WidgetPair}, parser::{Fetchable, ParseDocumentParams, ParserState}, - task::Tasks, }; use wlx_common::desktop_finder::DesktopEntry; @@ -22,9 +21,6 @@ use crate::{ views::{self}, }; -#[derive(Clone)] -enum Task {} - struct State { view_launcher: PopupHolder, } @@ -35,7 +31,6 @@ pub struct TabApps { state: Rc>, app_list: AppList, - tasks: Tasks, marker: PhantomData, } @@ -47,10 +42,6 @@ impl Tab for TabApps { fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> { let state = self.state.borrow_mut(); - for task in self.tasks.drain() { - match task {} - } - self.app_list.tick(frontend, &self.state, &mut self.parser_state)?; state @@ -96,7 +87,6 @@ fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> { impl TabApps { pub fn new(frontend: &mut Frontend, parent_id: WidgetID, data: &mut T) -> anyhow::Result { let globals = frontend.layout.state.globals.clone(); - let tasks = Tasks::new(); let state = Rc::new(RefCell::new(State { view_launcher: Default::default(), })); @@ -127,7 +117,6 @@ impl TabApps { app_list, parser_state, state, - tasks, marker: PhantomData, }) } diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index a9c5332e..e850bc58 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -333,12 +333,12 @@ impl SubtabGeneralSettings { } }) } - slider_keying_curve.on_value_changed(get_slider_callback(&tasks)); - slider_keying_despill.on_value_changed(get_slider_callback(&tasks)); - slider_keying_hue_range.on_value_changed(get_slider_callback(&tasks)); - slider_keying_saturation_range.on_value_changed(get_slider_callback(&tasks)); - slider_keying_value_range.on_value_changed(get_slider_callback(&tasks)); - cs_keying.on_changed(get_color_selector_callback(&tasks)); + slider_keying_curve.on_value_changed(get_slider_callback(tasks)); + slider_keying_despill.on_value_changed(get_slider_callback(tasks)); + slider_keying_hue_range.on_value_changed(get_slider_callback(tasks)); + slider_keying_saturation_range.on_value_changed(get_slider_callback(tasks)); + slider_keying_value_range.on_value_changed(get_slider_callback(tasks)); + cs_keying.on_changed(get_color_selector_callback(tasks)); } Ok(Self { diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 50930517..d0fdaad7 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -220,7 +220,7 @@ impl Tab for TabSettings { let mut common = frontend.layout.common(); let mut label = self .state - .fetch_widget_as::(&common.state, &format!("{id}_value"))?; + .fetch_widget_as::(common.state, &format!("{id}_value"))?; let translation = Translation { text: text.into(), diff --git a/dash-frontend/src/util/networking/http_client.rs b/dash-frontend/src/util/networking/http_client.rs index 812620c3..8500dc92 100644 --- a/dash-frontend/src/util/networking/http_client.rs +++ b/dash-frontend/src/util/networking/http_client.rs @@ -19,7 +19,7 @@ pub struct HttpClientResponse { } impl HttpClientResponse { - pub fn as_json(self) -> anyhow::Result + pub fn into_json(self) -> anyhow::Result where T: for<'a> serde::Deserialize<'a>, { @@ -66,12 +66,11 @@ pub async fn get(params: GetParams<'_>) -> anyhow::Result { let (parts, body) = resp.into_parts(); // that's a pretty interesting way to get file size :] - if let Some(val) = parts.headers.get("Content-Length") { - if let Ok(str) = val.to_str() { - if let Ok(s) = str.parse() { - file_size = s; - } - } + if let Some(val) = parts.headers.get("Content-Length") + && let Ok(str) = val.to_str() + && let Ok(s) = str.parse() + { + file_size = s; } let mut on_progress = params.on_progress; diff --git a/dash-frontend/src/util/networking/mod.rs b/dash-frontend/src/util/networking/mod.rs index b6c625a8..3c83a713 100644 --- a/dash-frontend/src/util/networking/mod.rs +++ b/dash-frontend/src/util/networking/mod.rs @@ -2,5 +2,5 @@ pub mod http_client; pub mod image_fetch; pub mod skymap_catalog; -// pub const WAYVR_ROOT_URL: &'static str = "https://wayvr.org"; -pub const WAYVR_SKYMAPS_ROOT: &'static str = "https://wayvr.org/skymaps"; +// pub const WAYVR_ROOT_URL: &str = "https://wayvr.org"; +pub const WAYVR_SKYMAPS_ROOT: &str = "https://wayvr.org/skymaps"; diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs index d7d21ca2..02af4612 100644 --- a/dash-frontend/src/util/networking/skymap_catalog.rs +++ b/dash-frontend/src/util/networking/skymap_catalog.rs @@ -67,10 +67,7 @@ impl SkymapCatalogEntryFiles { // example result: "https://wayvr.org/skymaps/files/my_skymap_8k.dds" pub fn get_url_from_res(&self, res: SkymapResolution) -> Option { - let Some(filename) = self.get_filename_from_res(res) else { - return None; - }; - + let filename = self.get_filename_from_res(res)?; Some(format!("{}/files/{}", WAYVR_SKYMAPS_ROOT, filename)) } @@ -102,10 +99,7 @@ pub struct SkymapCatalogEntry { impl SkymapCatalogEntry { pub fn get_destination_path(&self, resolution: SkymapResolution) -> Option { - let Some(filename) = self.files.get_filename_from_res(resolution) else { - return None; - }; - + let filename = self.files.get_filename_from_res(resolution)?; Some(config_io::get_skymaps_root().join(filename)) } @@ -170,7 +164,7 @@ pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result()?; + let catalog = res.into_json::()?; catalog.validate()?; Ok(catalog) diff --git a/dash-frontend/src/util/popup_manager.rs b/dash-frontend/src/util/popup_manager.rs index fa7b7cfd..e7c62151 100644 --- a/dash-frontend/src/util/popup_manager.rs +++ b/dash-frontend/src/util/popup_manager.rs @@ -135,11 +135,7 @@ impl PopupHolder { F: FnOnce(&mut ViewType) -> R, { let mut state = self.state.borrow_mut(); - if let Some(view) = state.view.as_mut() { - Some(f(view)) - } else { - None - } + state.view.as_mut().map(f) } // Same as with_view, but the closure expects a simple anyhow::Result<()> type @@ -190,20 +186,18 @@ pub struct PopupContentFuncData<'a> { } type PopupClosedCallback = Box; +type OnContentCallback = Box anyhow::Result>; // we need to implement Clone here, but the underlying function can be called only once. // on_content will be cleared after the first call #[derive(Clone)] pub struct MountPopupOnceParams { title: Translation, - on_content: Rc anyhow::Result>>>>, + on_content: Rc>>, } impl MountPopupOnceParams { - pub fn new( - title: Translation, - on_content: Box anyhow::Result>, - ) -> Self { + pub fn new(title: Translation, on_content: OnContentCallback) -> Self { Self { title, on_content: Rc::new(RefCell::new(Some(on_content))), @@ -313,15 +307,14 @@ impl PopupManager { but_back.on_click({ let popup_handle = Rc::downgrade(&popup_handle.state); Rc::new(move |_common, _evt| { - if let Some(popup_handle) = popup_handle.upgrade() { - if let Some(closed_callback) = { + if let Some(popup_handle) = popup_handle.upgrade() + && let Some(closed_callback) = { let mut state = popup_handle.borrow_mut(); state.mounted_popup = None; // will call Drop state.closed_callback.take() } { - log::debug!("closed_callback called"); - closed_callback(); - } + log::debug!("closed_callback called"); + closed_callback(); } Ok(()) }) diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs index af4d393c..8fe9a1c3 100644 --- a/dash-frontend/src/util/wgui_simple.rs +++ b/dash-frontend/src/util/wgui_simple.rs @@ -97,9 +97,9 @@ pub fn create_icon(layout: &mut Layout, id_parent: WidgetID, size: Vec2, path: A id_parent, widget_sprite, taffy::Style { - min_size: size.clone(), - max_size: size.clone(), - size: size.clone(), + min_size: size, + max_size: size, + size, ..Default::default() }, )?; diff --git a/dash-frontend/src/views/download_file.rs b/dash-frontend/src/views/download_file.rs index 30b299ff..439f7b94 100644 --- a/dash-frontend/src/views/download_file.rs +++ b/dash-frontend/src/views/download_file.rs @@ -158,7 +158,7 @@ impl View { wgui_simple::create_loading(wgui_simple::CreateLoadingParams { parent_id: id_loading_parent, - layout: layout, + layout, with_text: false, })?; diff --git a/dash-frontend/src/views/game_launcher.rs b/dash-frontend/src/views/game_launcher.rs index 17722bc1..f16ca69b 100644 --- a/dash-frontend/src/views/game_launcher.rs +++ b/dash-frontend/src/views/game_launcher.rs @@ -59,12 +59,12 @@ impl ViewTrait for View { } for task in tasks { match task { - Task::FillAppDetails(details) => self.action_fill_app_details(&mut par.layout, details)?, + Task::FillAppDetails(details) => self.action_fill_app_details(par.layout, details)?, Task::Launch => self.action_launch(), Task::SetCoverArt(cover_art) => { let _ = self .view_cover - .set_cover_art(&mut self.game_cover_view_common, &mut par.layout, &cover_art); + .set_cover_art(&mut self.game_cover_view_common, par.layout, &cover_art); } } } @@ -145,8 +145,8 @@ impl View { ) -> anyhow::Result<()> { { let mut c = layout.common(); - let label_author = self.state.fetch_widget(&c.state, "label_author")?.widget; - let label_description = self.state.fetch_widget(&c.state, "label_description")?.widget; + let label_author = self.state.fetch_widget(c.state, "label_author")?.widget; + let label_description = self.state.fetch_widget(c.state, "label_description")?.widget; if let Some(developer) = details.developers.pop() { label_author diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index ab7e6448..1097f5f2 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -78,9 +78,9 @@ impl ViewTrait for View { for task in tasks { match task { Task::LoadManifests => self.load_manifests(), - Task::FillPage(page_idx) => self.fill_page(&mut par.layout, &mut par.executor, page_idx)?, + Task::FillPage(page_idx) => self.fill_page(par.layout, par.executor, page_idx)?, Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?, - Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(&mut par.layout, app_id, cover_art), + Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(par.layout, app_id, cover_art), Task::PrevPage => self.page_prev(), Task::NextPage => self.page_next(), } diff --git a/dash-frontend/src/views/remote_skymap_downloader.rs b/dash-frontend/src/views/remote_skymap_downloader.rs index 4a2aa537..5dbaa9cf 100644 --- a/dash-frontend/src/views/remote_skymap_downloader.rs +++ b/dash-frontend/src/views/remote_skymap_downloader.rs @@ -144,7 +144,7 @@ impl View { pub fn new(par: Params) -> anyhow::Result { let tasks = Tasks::::new(); - let parser_state = wgui::parser::parse_from_assets(&doc_params(&par.globals), par.layout, par.parent_id)?; + let parser_state = wgui::parser::parse_from_assets(&doc_params(par.globals), par.layout, par.parent_id)?; let id_resolution_buttons = parser_state.get_widget_id("resolution_buttons")?; let str_version = par.globals.i18n().translate("VERSION"); @@ -249,9 +249,9 @@ impl View { } fn show_dialog_box_action(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> { - const ACTION_REMOVE: &'static str = "remove"; - const ACTION_DOWNLOAD_AGAIN: &'static str = "download_again"; - const ACTION_APPLY: &'static str = "apply"; + const ACTION_REMOVE: &str = "remove"; + const ACTION_DOWNLOAD_AGAIN: &str = "download_again"; + const ACTION_APPLY: &str = "apply"; let tasks = self.tasks.clone(); @@ -362,6 +362,7 @@ impl View { } } +#[allow(clippy::too_many_arguments)] pub fn mount_popup( frontend_tasks: FrontendTasks, executor: AsyncExecutor, diff --git a/dash-frontend/src/views/remote_skymap_list.rs b/dash-frontend/src/views/remote_skymap_list.rs index 76c171dc..0ba02497 100644 --- a/dash-frontend/src/views/remote_skymap_list.rs +++ b/dash-frontend/src/views/remote_skymap_list.rs @@ -34,18 +34,12 @@ pub struct Params<'a> { pub on_updated_library: Rc, } +type SetSkymapPreview = (Uuid, Option<(CustomGlyphData, Rc>)>); + #[derive(Clone)] enum Task { SetSkymapCatalog(Rc>), - SetSkymapPreview( - ( - SkymapUuid, - Option<( - CustomGlyphData, /* ready-to-use preview image data */ - Rc>, /* compressed preview image data (should weigh about 10-15 KiB) */ - )>, - ), - ), + SetSkymapPreview(SetSkymapPreview), ShowRemoteSkymapDownloader(SkymapUuid), RefreshCells, } @@ -70,9 +64,7 @@ pub struct View { } fn get_entry_by_uuid(catalog: &SkymapCatalog, skymap_uuid: Uuid) -> Option<&SkymapCatalogEntry> { - let Some(entry) = catalog.entries.iter().find(|entry| entry.uuid == skymap_uuid) else { - return None; - }; + let entry = catalog.entries.iter().find(|entry| entry.uuid == skymap_uuid)?; Some(entry) } @@ -176,7 +168,7 @@ impl View { }; for cell in &mut self.mounted_cells { - if let Some(entry) = get_entry_by_uuid(&catalog, cell.skymap_uuid) { + if let Some(entry) = get_entry_by_uuid(catalog, cell.skymap_uuid) { cell.view.refresh_resolution_pips(layout, entry)?; } } @@ -195,7 +187,7 @@ impl View { extra: Default::default(), }; - let parser_state = wgui::parser::parse_from_assets(&doc_params, layout, self.id_parent)?; + let parser_state = wgui::parser::parse_from_assets(doc_params, layout, self.id_parent)?; let id_list = parser_state.fetch_widget(&layout.state, "list")?.id; @@ -207,11 +199,11 @@ impl View { self.tasks.clone(), ); - let skymap_uuid = entry.uuid.clone(); + let skymap_uuid = entry.uuid; self.mounted_cells.push(MountedCell { preview_image_compressed: None, - skymap_uuid: entry.uuid.clone(), + skymap_uuid: entry.uuid, view: views::skymap_list_cell::View::new(views::skymap_list_cell::Params { id_parent: id_list, layout, @@ -240,7 +232,7 @@ impl View { return Ok(()); }; - let Some(entry) = get_entry_by_uuid(&catalog, uuid) else { + let Some(entry) = get_entry_by_uuid(catalog, uuid) else { return Ok(()); }; @@ -273,13 +265,8 @@ impl View { skymap_uuid: SkymapUuid, ) -> Option<(CustomGlyphData, Rc> /* preview_image_compressed */)> { if let Some(cell) = &self.mounted_cells.iter().find(|mc| mc.skymap_uuid == skymap_uuid) { - let Some(image) = cell.view.get_image() else { - return None; - }; - - let Some(preview_image_compressed) = cell.preview_image_compressed.clone() else { - return None; - }; + let image = cell.view.get_image()?; + let preview_image_compressed = cell.preview_image_compressed.clone()?; return Some((image, preview_image_compressed)); } None diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs index cf97aa2d..48999bca 100644 --- a/dash-frontend/src/views/skymap_list.rs +++ b/dash-frontend/src/views/skymap_list.rs @@ -66,10 +66,10 @@ impl ViewTrait for View { for task in tasks { match task { Task::DownloadSkymaps => { - self.download_skymaps(&par.executor)?; + self.download_skymaps(par.executor)?; } Task::Refresh => { - self.refresh(&mut par.layout)?; + self.refresh(par.layout)?; } Task::ShowSkymapResolutionSelector(entry) => { self.show_skymap_resolution_selector(entry); @@ -236,10 +236,10 @@ impl View { })?; // load preview image - if let Ok(data) = std::fs::read(skymaps_root.join(&entry.files.preview)) { - if let Ok(glyph_data) = CustomGlyphData::from_bytes_raster(&self.globals, &entry.files.preview, &data) { - view.set_image(layout, Some(glyph_data))?; - } + if let Ok(data) = std::fs::read(skymaps_root.join(&entry.files.preview)) + && let Ok(glyph_data) = CustomGlyphData::from_bytes_raster(&self.globals, &entry.files.preview, &data) + { + view.set_image(layout, Some(glyph_data))?; } self.cells.push(Cell { view }); diff --git a/dash-frontend/src/views/skymap_list_cell.rs b/dash-frontend/src/views/skymap_list_cell.rs index f0267ce5..fa8375de 100644 --- a/dash-frontend/src/views/skymap_list_cell.rs +++ b/dash-frontend/src/views/skymap_list_cell.rs @@ -146,7 +146,7 @@ impl View { } pub fn refresh_resolution_pips(&mut self, layout: &mut Layout, entry: &SkymapCatalogEntry) -> anyhow::Result<()> { - populate_res_pips(layout, self.id_resolution_pips, &mut self.parser_state, &entry)?; + populate_res_pips(layout, self.id_resolution_pips, &mut self.parser_state, entry)?; Ok(()) } @@ -163,6 +163,6 @@ impl View { } pub fn get_image(&self) -> Option { - return self.image.clone(); + self.image.clone() } } diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs index 0bc370b8..0f6d95b5 100644 --- a/wayvr/src/backend/openxr/playspace.rs +++ b/wayvr/src/backend/openxr/playspace.rs @@ -30,7 +30,7 @@ impl PlayspaceMover { Err(MndResult::ErrorInvalidVersion) ) { anyhow::bail!("Space offsets not supported."); - }; + } Ok(Self { drag: None, diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index 2d0215e2..21d2e9d5 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -23,6 +23,7 @@ use super::{ swapchain::{WlxSwapchain, create_swapchain}, }; +#[allow(clippy::struct_field_names)] pub(super) struct Skybox { view: Option>, sky: Option, @@ -126,8 +127,7 @@ impl Skybox { let extent = self .view .as_ref() - .map(|v| v.extent_u32arr()) - .unwrap_or([4096, 4096]); + .map_or([4096, 4096], ExtentExt::extent_u32arr); let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, 1, opts)?; let tgt = swapchain diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index eed2709c..a42526db 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -48,7 +48,7 @@ where panic!("No usable config found."); } -const SUPPORTED_EXTESIONS: [&'static str; 4] = ["yaml", "yml", "json", "json5"]; +const SUPPORTED_EXTESIONS: [&str; 4] = ["yaml", "yml", "json", "json5"]; fn is_supported_config_file(path: &Path) -> bool { if path.is_dir() { diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs index c135e58a..7726ffcc 100644 --- a/wayvr/src/gui/panel/mod.rs +++ b/wayvr/src/gui/panel/mod.rs @@ -498,7 +498,7 @@ pub fn apply_custom_command( ModifyPanelCommand::SetText(text) => { if let Ok(mut label) = panel .parser_state - .fetch_widget_as::(&com.state, element) + .fetch_widget_as::(com.state, element) { label.set_text(&mut com, Translation::from_raw_text(text)); } else if let Ok(button) = panel @@ -511,7 +511,7 @@ pub fn apply_custom_command( } } ModifyPanelCommand::SetImage(path) => { - if let Ok(pair) = panel.parser_state.fetch_widget(&com.state, element) { + if let Ok(pair) = panel.parser_state.fetch_widget(com.state, element) { let data = CustomGlyphData::from_assets( &app.wgui_globals, wgui::assets::AssetPath::File(path), @@ -533,7 +533,7 @@ pub fn apply_custom_command( let color = parse_color_hex(color) .context("Invalid color format, must be a html hex color!")?; - if let Ok(pair) = panel.parser_state.fetch_widget(&com.state, element) { + if let Ok(pair) = panel.parser_state.fetch_widget(com.state, element) { if let Some(mut rect) = pair.widget.get_as::() { rect.set_color(&mut com, color); } else if let Some(mut label) = pair.widget.get_as::() { diff --git a/wayvr/src/ipc/ipc_server.rs b/wayvr/src/ipc/ipc_server.rs index 8a33c87f..7dead59d 100644 --- a/wayvr/src/ipc/ipc_server.rs +++ b/wayvr/src/ipc/ipc_server.rs @@ -49,19 +49,19 @@ fn read_check(expected_size: u32, res: std::io::Result) -> anyhow::Result anyhow::bail!("End of stream"); } if count as u32 == expected_size { - return Ok(true); // read succeeded + Ok(true) // read succeeded } else { log::error!("count {count} is not {expected_size}"); - return Ok(false); + Ok(false) } } Err(e) => match e.kind() { std::io::ErrorKind::WouldBlock => { // no incoming data (this socket is non-blocking), try again later - return Ok(false); + Ok(false) } _ => { - anyhow::bail!("Connection error: {:?}", e); + anyhow::bail!("Connection error: {e:?}"); } }, } @@ -500,7 +500,7 @@ impl Connection { } } Err(e) => { - log::debug!("Disconnecting client: {:?}", e); + log::debug!("Disconnecting client: {e:?}"); self.alive = false; break; } diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index a3eaf6b0..d1a3f038 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -74,7 +74,7 @@ impl DashFrontend { let mut interface = DashInterfaceLive::new(); if app.session.no_autostart { - log::info!("Not starting apps due to --no-autostart") + log::info!("Not starting apps due to --no-autostart"); } else { for p in app.session.config.autostart_apps.clone() { let _ = interface.process_launch(app, false, p); diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 478fb261..49a5f3f1 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -154,7 +154,6 @@ impl AppState { desktop_finder.refresh(); let lang_provider = WayVRLangProvider::from_config(&session.config); - let executor = Rc::new(smol::LocalExecutor::new()); let mut app_state = Self { session, diff --git a/wayvr/src/subsystem/hid/mod.rs b/wayvr/src/subsystem/hid/mod.rs index b3d5e611..5a8ee423 100644 --- a/wayvr/src/subsystem/hid/mod.rs +++ b/wayvr/src/subsystem/hid/mod.rs @@ -25,6 +25,14 @@ mod x11; pub static USE_UINPUT: AtomicBool = AtomicBool::new(true); pub(super) fn initialize() -> Result { + const CHECK_UINPUT_MESSAGE: &str = + "Could not create uinput provider. Keyboard/Mouse input will not work! + +Check if the uinput kernel module is loaded: lsmod | grep uinput + - If not loaded, follow your distro's instructions to load the uinput kernel module. + +Check if you're in input group, run: id -nG"; + if !USE_UINPUT.load(std::sync::atomic::Ordering::Relaxed) { const UINPUT_DISABLED: &str = "Uinput disabled by user."; log::info!("{UINPUT_DISABLED}"); @@ -40,13 +48,6 @@ pub(super) fn initialize() -> Result { log::info!("Initialized uinput."); return Ok(uinput); } - const CHECK_UINPUT_MESSAGE: &str = - "Could not create uinput provider. Keyboard/Mouse input will not work! - -Check if the uinput kernel module is loaded: lsmod | grep uinput - - If not loaded, follow your distro's instructions to load the uinput kernel module. - -Check if you're in input group, run: id -nG"; let mut full_uinput_error = String::from(CHECK_UINPUT_MESSAGE); if let Ok(user) = std::env::var("USER") { let check_group_message = format!( diff --git a/wgui/src/components/color_selector.rs b/wgui/src/components/color_selector.rs index 16631c58..a7ef1b4d 100644 --- a/wgui/src/components/color_selector.rs +++ b/wgui/src/components/color_selector.rs @@ -46,6 +46,7 @@ struct PopupState { id_rect_color: WidgetID, } +#[allow(clippy::struct_field_names)] struct State { color: drawing::Color, self_ref: Weak, @@ -83,16 +84,16 @@ impl ComponentTrait for ComponentColorSelector { fn refresh(&self, data: &mut RefreshData) { let mut state = self.state.borrow_mut(); - if let Some(wants_open) = state.wants_open.take() { - if let Err(e) = self.open(data.layout, &mut state, wants_open.position) { - log::error!("{:?}", e); - debug_assert!(false); - } + if let Some(wants_open) = state.wants_open.take() + && let Err(e) = self.open(data.layout, &mut state, wants_open.position) + { + log::error!("{e:?}"); + debug_assert!(false); } self.data.button.set_text( &mut data.layout.common(), - Translation::from_raw_text_string(format!("{}", state.color.to_hex_rgb())), + Translation::from_raw_text_string(state.color.to_hex_rgb()), ); self.data.button.set_color(&mut data.layout.common(), state.color); @@ -111,7 +112,7 @@ fn set_color_internal(state: &mut State, common: &mut CallbackDataCommon, color: } if let Some(on_changed) = &state.on_changed { - on_changed(common, ColorSelectorChangedEvent { color }) + on_changed(common, ColorSelectorChangedEvent { color }); } state.color = color; @@ -148,7 +149,7 @@ impl ComponentColorSelector { let id_content = self.window.get_content().id; let parser_state = parser::parse_from_assets( - &mut ParseDocumentParams { + &ParseDocumentParams { globals: layout.state.globals.clone(), path: AssetPath::WguiInternal("wgui/color_selector.xml"), extra: Default::default(), diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index fa61b8bf..e1774baa 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -40,7 +40,7 @@ pub struct Limits { } impl Value { - pub fn get(&self) -> f32 { + pub const fn get(&self) -> f32 { self.0 } @@ -113,6 +113,7 @@ impl TooltipTrait for State { } } +#[allow(clippy::struct_field_names)] struct SliderHandleData { id_handle_rect: WidgetID, // Rectangle id_text: Option, // Text @@ -146,7 +147,7 @@ impl ComponentTrait for ComponentSlider { let value1 = state.value1.get(); state.set_value(&mut common, &self.data, ValueIndex::Primary, value1); - if let Some(value2) = state.value2.as_ref().map(|v| v.get()) { + if let Some(value2) = state.value2.as_ref().map(Value::get) { state.set_value(&mut common, &self.data, ValueIndex::Secondary, value2); } } @@ -169,7 +170,7 @@ impl ComponentSlider { let state = self.state.borrow(); match index { ValueIndex::Primary => Some(state.value1.get()), - ValueIndex::Secondary => state.value2.as_ref().map(|v| v.get()), + ValueIndex::Secondary => state.value2.as_ref().map(Value::get), } } @@ -231,7 +232,7 @@ const HANDLE_WIDTH: f32 = 32.0; const HANDLE_HEIGHT: f32 = 24.0; impl State { - fn get_hovered_index(&self) -> Option { + const fn get_hovered_index(&self) -> Option { if self.hovered1 { Some(ValueIndex::Primary) } else if self.hovered2 { @@ -336,10 +337,10 @@ impl State { on_value_changed( common, SliderValueChangedEvent { - index: index, + index, value: value.get(), }, - ) + ); } } } @@ -447,17 +448,13 @@ fn update_handle_hovers( let hovered1_prev = state.hovered1; let hovered2_prev = state.hovered2; - if !state.hovered_body { - state.hovered1 = false; - state.hovered2 = false; - } else { + if state.hovered_body { let dist1 = get_handle_dist(common, &data.handle1, mouse_pos); let dist2 = data .handle2 .as_ref() - .map(|h| get_handle_dist(common, h, mouse_pos)) - .unwrap_or(std::f32::MAX); + .map_or(f32::MAX, |h| get_handle_dist(common, h, mouse_pos)); state.hovered1 = dist1 <= MAX_HOVER_DIST; state.hovered2 = dist2 <= MAX_HOVER_DIST; @@ -470,6 +467,9 @@ fn update_handle_hovers( state.hovered1 = false; } } + } else { + state.hovered1 = false; + state.hovered2 = false; } // hover state changed, run animations @@ -516,12 +516,12 @@ fn register_event_mouse_motion( update_handle_hovers(common, &data, &mut state, anim_mult, pos_relative); - if let Some(dragged_by) = &state.dragged_by { - if dragged_by.device == pos.device { - let index = dragged_by.index; - state.update_value_to_mouse(event_data, &data, common, index); - return Ok(EventResult::Consumed); - } + if let Some(dragged_by) = &state.dragged_by + && dragged_by.device == pos.device + { + let index = dragged_by.index; + state.update_value_to_mouse(event_data, &data, common, index); + return Ok(EventResult::Consumed); } Ok(EventResult::Pass) diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index ef8fa550..7acc52e9 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -207,6 +207,7 @@ pub struct HsvColor { pub a: f32, } +#[allow(clippy::many_single_char_names)] impl HsvColor { pub const fn new(h: f32, s: f32, v: f32, a: f32) -> Self { Self { h, s, v, a } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 0ff8f588..2e202007 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -229,7 +229,7 @@ fn add_child_internal( } impl Layout { - pub fn common(&mut self) -> CallbackDataCommon<'_> { + pub const fn common(&mut self) -> CallbackDataCommon<'_> { CallbackDataCommon { alterables: &mut self.alterables, state: &self.state, @@ -480,7 +480,7 @@ impl Layout { Ok(()) } - fn get_event_params<'a>( + const fn get_event_params<'a>( &'a self, l: &'a taffy::Layout, node_id: taffy::NodeId, diff --git a/wgui/src/parser/component_slider.rs b/wgui/src/parser/component_slider.rs index d38a738a..f14d21a6 100644 --- a/wgui/src/parser/component_slider.rs +++ b/wgui/src/parser/component_slider.rs @@ -69,7 +69,7 @@ pub fn parse_component_slider( step, }, value1: slider::Value(initial_value1), - value2: initial_value2.map(|v| slider::Value(v)), + value2: initial_value2.map(slider::Value), show_value: show_value != 0, tooltip: tooltip.get_info(), }, diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index e8bac585..1020ba47 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -225,7 +225,7 @@ impl ParserState { /// but it keeps components data in this `ParserState` object for you. /// The result can be safely dropped, all required event listeners and components /// will be kept intact in this `ParserState`. - /// Resulting ParserData::components Vec will be left empty (they are moved into this `ParserState::data`) + /// Resulting `ParserData::components` Vec will be left empty (they are moved into this `ParserState::data`) pub fn realize_template( &mut self, doc_params: &ParseDocumentParams, @@ -245,7 +245,7 @@ impl ParserState { /// Semi-internal - This function is suitable in cases if you don't want to pollute /// the main parser state state with dynamic IDs (this won't propagate components!) /// Use `realize_template` (or in some rare cases: `instantiate_template`) instead unless you want to handle `components` results yourself. - /// Make sure not to drop resulting ParserData if you want to have your listener handles valid + /// Make sure not to drop resulting `ParserData` if you want to have your listener handles valid /// (they are contained in components). Use `realize_template` instead if you don't want to think about it. pub fn parse_template_only( &self, @@ -282,7 +282,7 @@ impl ParserState { /// Parse named + + + + @@ -192,7 +192,7 @@ diff --git a/wayvr/src/assets/gui/watch.xml b/wayvr/src/assets/gui/watch.xml index 72e531cb..584c481c 100644 --- a/wayvr/src/assets/gui/watch.xml +++ b/wayvr/src/assets/gui/watch.xml @@ -38,7 +38,7 @@ @@ -51,7 +51,7 @@ @@ -65,7 +65,7 @@ diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index d3058b54..ec104caa 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -166,7 +166,12 @@ impl ComponentTrait for ComponentButton { state: &data.layout.state, alterables: &mut data.layout.alterables, }; - widget.set_color(common, fg_color, true); + let label_color = if widget.uses_bg_color() { + state.colors.color + } else { + fg_color + }; + widget.set_color(common, label_color, true); } state.id_label = child; } @@ -218,7 +223,8 @@ impl ComponentButton { if let Some(fg_color) = color.fg_color() { if let Some(mut label) = common.state.widgets.get_as::(state.id_label) { - label.set_color(common, fg_color, true); + let label_color = if label.uses_bg_color() { color } else { fg_color }; + label.set_color(common, label_color, true); } if let Some(mut sprite) = common.state.widgets.get_as::(state.id_sprite) { sprite.set_color(common, fg_color); @@ -279,10 +285,10 @@ impl ComponentButton { }; { - let bgcolor = colors.color.lerp(&common.globals().palette, alt_color, mult); + let bg_color = colors.color.lerp(&common.globals().palette, alt_color, mult); - rect.params.color = bgcolor; - rect.params.color2 = get_color2(&bgcolor, gradient_intensity); + rect.params.color = bg_color; + rect.params.color2 = get_color2(&bg_color, gradient_intensity); rect.params.border_color = colors .border_color .lerp(&common.globals().palette, alt_border_color, mult); @@ -292,7 +298,8 @@ impl ComponentButton { { let fg_color = fg_color0.lerp(&common.globals().palette, &fg_color1, mult); if let Some(mut label) = common.state.widgets.get_as::(state.id_label) { - label.set_color(common, fg_color, true); + let label_color = if label.uses_bg_color() { bg_color } else { fg_color }; + label.set_color(common, label_color, true); } if let Some(mut sprite) = common.state.widgets.get_as::(state.id_sprite) { sprite.set_color(common, fg_color); @@ -328,12 +335,15 @@ fn anim_hover( (colors.border_color, colors.color) }; + let bg_color = init_color.lerp(&common.globals().palette, &colors.hover_color, mult); + if let Some(fg_color0) = init_color.fg_color() && let Some(fg_color1) = colors.hover_color.fg_color() { let fg_color = fg_color0.lerp(&common.globals().palette, &fg_color1, mult); if let Some(mut label) = common.state.widgets.get_as::(label) { - label.set_color(common, fg_color, true); + let label_color = if label.uses_bg_color() { bg_color } else { fg_color }; + label.set_color(common, label_color, true); } if let Some(mut sprite) = common.state.widgets.get_as::(sprite) { sprite.set_color(common, fg_color); @@ -342,8 +352,6 @@ fn anim_hover( let globals = common.globals(); - let bgcolor = init_color.lerp(&globals.palette, &colors.hover_color, mult); - let gradient_intensity = common.state.theme.gradient_intensity; //let t = Mat4::from_scale(Vec3::splat(1.0 + pos * 0.5)) * Mat4::from_rotation_z(pos * 1.0); @@ -351,8 +359,8 @@ fn anim_hover( let t = Mat4::from_scale(Vec3::splat(1.0 + pos * 0.05)); widget_data.transform = centered_matrix(widget_boundary.size, &t); - rect.params.color = bgcolor; - rect.params.color2 = get_color2(&bgcolor, gradient_intensity); + rect.params.color = bg_color; + rect.params.color2 = get_color2(&bg_color, gradient_intensity); rect.params.border_color = init_border_color.lerp(&globals.palette, &colors.hover_border_color, mult); } diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index c54ba402..903efb34 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -120,18 +120,16 @@ impl WidgetLabel { } pub fn set_color(&mut self, common: &mut CallbackDataCommon, color: WguiColor, apply_to_existing_text: bool) { - if self.params.use_bg_color { - if let Some(bg_color) = color.bg_color() { - self.params.style.color = Some(bg_color); - } - } else { - self.params.style.color = Some(color); - } + self.params.style.color = Some(color); if apply_to_existing_text { self.update_attrs(&common.globals().palette); common.mark_widget_dirty(self.id); } } + + pub fn uses_bg_color(&self) -> bool { + self.params.use_bg_color + } } impl WidgetObj for WidgetLabel { From f3438b1867b117d591ca5e2acf61581a121fa44c Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Mon, 13 Jul 2026 04:04:14 +0900 Subject: [PATCH 243/307] screencast.xml colors --- wayvr/src/assets/gui/screencast.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wayvr/src/assets/gui/screencast.xml b/wayvr/src/assets/gui/screencast.xml index e3baf388..58d9c8be 100644 --- a/wayvr/src/assets/gui/screencast.xml +++ b/wayvr/src/assets/gui/screencast.xml @@ -1,15 +1,15 @@ - +
-
-
-
From 962ed3245255ba65c493bd80e8aa09284c97f6f4 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Mon, 13 Jul 2026 04:50:22 +0900 Subject: [PATCH 244/307] parent_color attrib to fix app icons --- dash-frontend/assets/gui/tab/apps.xml | 2 +- dash-frontend/src/util/wgui_simple.rs | 1 + wayvr/src/assets/gui/keyboard.xml | 20 +++---- wayvr/src/assets/gui/watch.xml | 26 ++++----- wgui/src/color.rs | 9 ++++ wgui/src/components/button.rs | 78 +++++++++++++++++---------- wgui/src/parser/widget_label.rs | 5 +- wgui/src/parser/widget_sprite.rs | 4 ++ wgui/src/widget/label.rs | 8 +-- wgui/src/widget/sprite.rs | 7 ++- 10 files changed, 100 insertions(+), 60 deletions(-) diff --git a/dash-frontend/assets/gui/tab/apps.xml b/dash-frontend/assets/gui/tab/apps.xml index 893d9a19..ae2eff8d 100644 --- a/dash-frontend/assets/gui/tab/apps.xml +++ b/dash-frontend/assets/gui/tab/apps.xml @@ -8,7 +8,7 @@ flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="4" >
- +