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] 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, }