diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index a973405a..dc58f894 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' diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..ab1f4164 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/Cargo.lock b/Cargo.lock index 0395e6f2..b36a9314 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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.0", +] + [[package]] name = "ahash" version = "0.8.12" @@ -698,6 +709,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "block2" version = "0.5.1" @@ -779,6 +800,37 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[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.2", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "tar", + "tempfile", + "thiserror 2.0.18", + "zip", +] + [[package]] name = "calloop" version = "0.13.0" @@ -857,6 +909,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.20.8" @@ -924,6 +987,16 @@ dependencies = [ "phf", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -984,6 +1057,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -1046,6 +1125,12 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -1066,6 +1151,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" @@ -1097,6 +1188,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1110,9 +1211,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1123,7 +1224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -1204,6 +1305,12 @@ dependencies = [ "windows", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1281,6 +1388,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -1467,6 +1592,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "defmt" version = "1.1.1" @@ -1523,8 +1654,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", + "zeroize", ] [[package]] @@ -1543,6 +1687,17 @@ dependencies = [ "objc2 0.6.4", ] +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "dlib" version = "0.5.3" @@ -1819,6 +1974,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1837,6 +2002,16 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.9" @@ -1845,6 +2020,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1903,6 +2079,15 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1910,7 +2095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1924,18 +2109,49 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + [[package]] name = "funty" version = "2.0.0" @@ -2104,9 +2320,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2261,6 +2479,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "hound" version = "3.5.1" @@ -2277,6 +2504,29 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -2289,6 +2539,74 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "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.65" @@ -2313,6 +2631,89 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2352,6 +2753,27 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.10" @@ -2435,6 +2857,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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "input" version = "0.9.1" @@ -2508,6 +2948,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2736,6 +3182,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" version = "0.2.186" @@ -2878,6 +3330,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -2930,6 +3388,15 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "lzma-rust2" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" +dependencies = [ + "sha2 0.11.0", +] + [[package]] name = "mach2" version = "0.5.0" @@ -2945,7 +3412,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata", + "regex-automata 0.4.14", ] [[package]] @@ -2964,6 +3431,16 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[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.11" @@ -3018,6 +3495,17 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + [[package]] name = "moveit" version = "0.6.0" @@ -3043,6 +3531,23 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3208,6 +3713,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + [[package]] name = "num_enum" version = "0.7.6" @@ -3582,6 +4097,49 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "openxr" version = "0.21.1" @@ -3731,6 +4289,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3939,12 +4507,27 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" 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" @@ -4451,8 +5034,18 @@ checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.14", + "regex-syntax 0.8.11", +] + +[[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]] @@ -4463,15 +5056,59 @@ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.11", ] +[[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.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[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.47.0" @@ -4573,6 +5210,104 @@ dependencies = [ "memchr", ] +[[package]] +name = "rten" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c230fa4ade87c913f61dbd911b7eb0d49460ceff3f1e4fabc837fac191137c" +dependencies = [ + "flatbuffers", + "num_cpus", + "rayon", + "rten-base", + "rten-gemm", + "rten-model-file", + "rten-onnx", + "rten-shape-inference", + "rten-simd", + "rten-tensor", + "rten-vecmath", + "rustc-hash", + "smallvec", + "typeid", + "wasm-bindgen", +] + +[[package]] +name = "rten-base" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2738cf8bb4c27f828ac788d01ccf4e367e8e773cfec6851f81851b5211de6a79" +dependencies = [ + "rayon", +] + +[[package]] +name = "rten-gemm" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a81a0ca209fb5ce21bd17efa0bd287d5881c6cebfbff0b21c4294a1a14a9e" +dependencies = [ + "rayon", + "rten-base", + "rten-simd", + "rten-tensor", +] + +[[package]] +name = "rten-model-file" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2f8d270f07ab1bbfff47250c6039f6caa5da59d6da7d74f66aa48559aa6fea" +dependencies = [ + "flatbuffers", + "rten-base", +] + +[[package]] +name = "rten-onnx" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23086eef75bfb55278cb0b45cf9f5a877d466d914914aafebee4ffca9b24d20c" + +[[package]] +name = "rten-shape-inference" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8a913c7ca40e2bfbb2a0cd447cce56b33ab19435f56693271a2ef37cf58984" +dependencies = [ + "rten-tensor", + "smallvec", +] + +[[package]] +name = "rten-simd" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19a0032dfcb70dd20960c1c51a37674b237586cbc1ce586f45b46605d108e82" + +[[package]] +name = "rten-tensor" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05dc744a270aa32d154f1a3df8e48740ccc1be9dfbcf23295ada66d83aa98de6" +dependencies = [ + "rayon", + "rten-base", + "smallvec", + "typeid", +] + +[[package]] +name = "rten-vecmath" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9574ddebf5671bc08ceb76e2e1638fadc57fdeff318634eab2c29e9a803cff64" +dependencies = [ + "rten-base", + "rten-simd", +] + [[package]] name = "rtrb" version = "0.3.4" @@ -4609,7 +5344,7 @@ version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ - "sha2", + "sha2 0.10.9", "walkdir", ] @@ -4735,6 +5470,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4766,6 +5510,29 @@ dependencies = [ "tiny-skia 0.11.4", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -4853,6 +5620,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" @@ -4866,6 +5645,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4874,7 +5664,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -5076,7 +5877,7 @@ dependencies = [ "rand 0.9.4", "rustix 1.1.4", "scopeguard", - "sha2", + "sha2 0.10.9", "smallvec", "tempfile", "thiserror 2.0.18", @@ -5187,6 +5988,16 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5241,6 +6052,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "super-swipe-type" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39d2eca9f0ac1a82ec03e8de1b7672a9a145f5bcdeae941efd943d9b9575b5c" +dependencies = [ + "anyhow", + "cached-path", + "fst", + "memmap", + "regex", + "regex-automata 0.1.10", + "rten", + "tar", + "vector2", +] + [[package]] name = "svgtypes" version = "0.16.1" @@ -5333,6 +6161,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -5388,6 +6247,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -5472,6 +6342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -5555,6 +6426,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -5570,6 +6451,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[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 = "toml" version = "1.1.2+spec-1.1.0" @@ -5621,6 +6526,51 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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" @@ -5674,7 +6624,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex-automata", + "regex-automata 0.4.14", "sharded-slab", "smallvec", "thread_local", @@ -5683,6 +6633,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -5698,6 +6654,12 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +[[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" @@ -5829,6 +6791,18 @@ dependencies = [ "log", ] +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "usvg" version = "0.47.0" @@ -5857,6 +6831,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -5893,6 +6873,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "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" @@ -5979,6 +6971,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -6256,6 +7257,7 @@ dependencies = [ "smithay-clipboard", "smol", "strum", + "super-swipe-type", "sysinfo", "thiserror 2.0.18", "tracing", @@ -6782,7 +7784,7 @@ dependencies = [ "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -6877,6 +7879,12 @@ dependencies = [ "xdg 3.0.0", ] +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + [[package]] name = "wyz" version = "0.5.1" @@ -6918,6 +7926,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.4", +] + [[package]] name = "xcb" version = "1.7.0" @@ -7029,6 +8047,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + [[package]] name = "zeno" version = "0.3.3" @@ -7055,18 +8096,145 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + [[package]] name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.3", + "hmac", + "indexmap 2.14.0", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[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.5.1" diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index a8f282a2..d5e48df3 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Tastatur-Mittelklick", "KEYBOARD_MIDDLE_CLICK_HELP": "Modifier-Taste bei der Eingabe\nmit dem lila Laser", "KEYBOARD_SOUND_ENABLED": "Tastatursounds", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Wischen zum Tippen", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Funktioniert nur für Englisch! Die Ergebnisse sind auf anderen Tastaturen als QWERTY deutlich schlechter.", "LANGUAGE": "Sprache", "LEFT_HANDED_MOUSE": "Linkshänder-Maus", "LEFT_HANDED_MOUSE_HELP": "Nutze dies, wenn die Maustasten vertauscht sind", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Sprache-zu-Text-Modell", "WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern präzisere\nErgebnisse, verbrauchen aber mehr VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Das Wisch-Tipp-Modell muss heruntergeladen werden.\nJetzt herunterladen?", + "MODEL_REQUIRED_TO_ENABLE": "Lade das Wisch-Tipp-Modell herunter, um diese Option zu aktivieren.", + "REMOVE_MODEL": "Wisch-Tipp-Modell entfernen", + "REMOVE_MODEL_HELP": "Löscht das Wisch-Tipp-Modell", + "DOWNLOAD_MODEL": "Wisch-Tipp-Modell herunterladen", + "DOWNLOAD_MODEL_HELP": "Wischen zum Tippen funktioniert nur auf Tastaturen mit QWERTY-Layout" + }, "GRID_OPACITY": "Bodenraster-Deckkraft", "GRID_OPACITY_HELP": "Deckkraft des Bodengitters, wenn die Skymap aktiviert ist", "XWAYLAND_BY_DEFAULT": "Apps standardmäßig im Kompatibilitätsmodus starten", diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index c1008b36..f271f236 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -149,6 +149,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", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Speech-to-text model", "WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "The swipe-to-type model needs to be downloaded.\nDownload it now?", + "MODEL_REQUIRED_TO_ENABLE": "Download the swipe-to-type model to enable this option.", + "REMOVE_MODEL": "Remove swipe to type model", + "REMOVE_MODEL_HELP": "Deletes the swipe to type model", + "DOWNLOAD_MODEL": "Download swipe to type model", + "DOWNLOAD_MODEL_HELP": "Swipe to type only works on qwerty layout keyboards" + }, "GRID_OPACITY": "Floor grid opacity", "GRID_OPACITY_HELP": "Opacity of the floor grid when the skymap is enabled", "XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index 6fea379a..402aaccf 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Clic central del teclado", "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador al escribir\ncon el láser púrpura", "KEYBOARD_SOUND_ENABLED": "Sonidos del teclado", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Deslizar para escribir", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "¡Solo funciona en inglés! Los resultados serán mucho peores en teclados que no sean QWERTY.", "LANGUAGE": "Idioma", "LEFT_HANDED_MOUSE": "Mouse para zurdos", "LEFT_HANDED_MOUSE_HELP": "Úsalo si los botones del ratón están invertidos", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Modelo de dictado", "WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes dan resultados\nmás precisos, pero consumen más VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "El modelo de deslizar para escribir debe descargarse.\n¿Descargarlo ahora?", + "MODEL_REQUIRED_TO_ENABLE": "Descarga el modelo de deslizar para escribir para habilitar esta opción.", + "REMOVE_MODEL": "Eliminar el modelo de deslizar para escribir", + "REMOVE_MODEL_HELP": "Borra el modelo de deslizar para escribir", + "DOWNLOAD_MODEL": "Descargar el modelo de deslizar para escribir", + "DOWNLOAD_MODEL_HELP": "Deslizar para escribir solo funciona en teclados con distribución QWERTY" + }, "GRID_OPACITY": "Opacidad de la rejilla del suelo", "GRID_OPACITY_HELP": "Opacidad de la rejilla del suelo cuando el skymap está activado", "XWAYLAND_BY_DEFAULT": "Ejecutar apps en modo de compatibilidad por defecto", diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index 4fffe6f5..491fe1a0 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Tasto centrale tastiera", "KEYBOARD_MIDDLE_CLICK_HELP": "Tasto modificatore da usare quando si scrive\ncon il laser viola", "KEYBOARD_SOUND_ENABLED": "Suoni tastiera", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Scorri per digitare", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Funziona solo con l'inglese! I risultati saranno drasticamente peggiori su tastiere non QWERTY.", "LANGUAGE": "Lingua", "LEFT_HANDED_MOUSE": "Mouse per mancini", "LEFT_HANDED_MOUSE_HELP": "Usalo se i tasti del mouse sono invertiti", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Modello speech-to-text", "WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nModelli più grandi offrono risultati\npiù precisi, ma consumano più VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Il modello per scorri per digitare deve essere scaricato.\nScaricarlo ora?", + "MODEL_REQUIRED_TO_ENABLE": "Scarica il modello per scorri per digitare per abilitare questa opzione.", + "REMOVE_MODEL": "Rimuovi il modello per scorri per digitare", + "REMOVE_MODEL_HELP": "Elimina il modello per scorri per digitare", + "DOWNLOAD_MODEL": "Scarica il modello per scorri per digitare", + "DOWNLOAD_MODEL_HELP": "Scorri per digitare funziona solo su tastiere con layout QWERTY" + }, "GRID_OPACITY": "Opacità griglia pavimento", "GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skymap è attivo", "XWAYLAND_BY_DEFAULT": "Avvia le app in modalità compatibilità per impostazione predefinita", diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 4aa9e508..72c937de 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "キーボード中クリック", "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで\n入力する際の修飾キー", "KEYBOARD_SOUND_ENABLED": "キーボード入力音", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "スワイプ入力", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "英語のみで動作します!QWERTY以外のキーボードでは結果が大幅に悪くなります。", "LANGUAGE": "言語", "LEFT_HANDED_MOUSE": "左利きマウス", "LEFT_HANDED_MOUSE_HELP": "マウスボタンを入れ替えて使用する場合にチェック", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "音声認識モデル", "WHISPER_MODEL_HELP": "使用するWhisperモデルを選択。\nモデルが大きいほど精度は向上するが、\nVRAMの消費量が増える。", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "スワイプ入力モデルをダウンロードする必要があります。\n今すぐダウンロードしますか?", + "MODEL_REQUIRED_TO_ENABLE": "このオプションを有効にするには、スワイプ入力モデルをダウンロードしてください。", + "REMOVE_MODEL": "スワイプ入力モデルを削除", + "REMOVE_MODEL_HELP": "スワイプ入力モデルを削除します", + "DOWNLOAD_MODEL": "スワイプ入力モデルをダウンロード", + "DOWNLOAD_MODEL_HELP": "スワイプ入力はQWERTYレイアウトのキーボードでのみ動作します" + }, "GRID_OPACITY": "フロアグリッドの不透明度", "GRID_OPACITY_HELP": "スカイマップ有効時のフロアグリッドの不透明度", "XWAYLAND_BY_DEFAULT": "デフォルトで互換モードでアプリを起動", diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index a0c382b6..d4aeef96 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "Środkowy przycisk myszy (klawiatura)", "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator używany podczas\npisania fioletowym laserem", "KEYBOARD_SOUND_ENABLED": "Dźwięki klawiatury", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "Przesuwaj, aby pisać", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "Działa tylko dla języka angielskiego! Wyniki będą znacznie gorsze na klawiaturach innych niż QWERTY.", "LANGUAGE": "Język", "LEFT_HANDED_MOUSE": "Mysz dla leworęcznych", "LEFT_HANDED_MOUSE_HELP": "Użyj, jeśli przyciski myszy są zamienione", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "Model mowy na tekst", "WHISPER_MODEL_HELP": "Model Whisper do użycia.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM.", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "Model do pisania przesuwaniem musi zostać pobrany.\nPobrać go teraz?", + "MODEL_REQUIRED_TO_ENABLE": "Pobierz model do pisania przesuwaniem, aby włączyć tę opcję.", + "REMOVE_MODEL": "Usuń model do pisania przesuwaniem", + "REMOVE_MODEL_HELP": "Usuwa model do pisania przesuwaniem", + "DOWNLOAD_MODEL": "Pobierz model do pisania przesuwaniem", + "DOWNLOAD_MODEL_HELP": "Pisanie przesuwaniem działa tylko na klawiaturach QWERTY" + }, "GRID_OPACITY": "Przezroczystość siatki podłogi", "GRID_OPACITY_HELP": "Przezroczystość siatki podłogi przy włączonym Skymapie", "XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności", diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index 94862c4d..d947f5cd 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -149,6 +149,8 @@ "KEYBOARD_MIDDLE_CLICK": "键盘鼠标中键", "KEYBOARD_MIDDLE_CLICK_HELP": "使用紫色激光输入时的\n修饰键", "KEYBOARD_SOUND_ENABLED": "键盘声音", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED": "滑动输入", + "KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP": "仅支持英语!在非 QWERTY 键盘上效果会明显变差。", "LANGUAGE": "语言", "LEFT_HANDED_MOUSE": "左手鼠标模式", "LEFT_HANDED_MOUSE_HELP": "若鼠标按键已互换,请启用此项", @@ -239,6 +241,14 @@ }, "WHISPER_MODEL": "语音转文本模型", "WHISPER_MODEL_HELP": "所使用的 Whisper 模型。\n模型越大,结果越准确,\n但占用的显存 (VRAM) 越多。", + "SWIPE_TYPE": { + "NEED_TO_DOWNLOAD_MODEL": "需要下载滑动输入模型。\n现在下载吗?", + "MODEL_REQUIRED_TO_ENABLE": "请下载滑动输入模型以启用此选项。", + "REMOVE_MODEL": "移除滑动输入模型", + "REMOVE_MODEL_HELP": "删除滑动输入模型", + "DOWNLOAD_MODEL": "下载滑动输入模型", + "DOWNLOAD_MODEL_HELP": "滑动输入仅适用于 QWERTY 布局的键盘" + }, "GRID_OPACITY": "地面网格不透明度", "GRID_OPACITY_HELP": "启用天空盒时地面网格的不透明度", "XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用程序", diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index d2f16356..aeeaad4e 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -83,6 +83,7 @@ pub(crate) enum Task { SavePlayspaceCenter, SetTab(TabNameEnum), SettingUpdated(SettingType), + SwipeTypeAction(Rc), UpdateBool(SettingType, bool), UpdateFloat(SettingType, f32), UpdateInt(SettingType, i32), @@ -110,6 +111,17 @@ trait SettingsTab { Ok(()) } + fn push_task_string( + &mut self, + _action: &str, + _config: &mut GeneralConfig, + _change_kind: &mut Option, + _layout: &mut Layout, + _state: &mut ParserState, + ) -> anyhow::Result<()> { + Ok(()) + } + fn context_menu_custom( &mut self, _action: Rc, @@ -254,6 +266,16 @@ impl Tab for TabSettings { _ => { /* do nothing */ } } } + Task::SwipeTypeAction(action) => { + if let Some(tab) = self.current_tab.as_mut() { + let config = frontend.interface.general_config(data); + let mut change_kind: Option = None; + tab.push_task_string(&action, config, &mut change_kind, &mut frontend.layout, &mut self.state)?; + if let Some(change_kind) = change_kind { + frontend.interface.config_changed(data, change_kind); + } + } + } } } @@ -329,6 +351,7 @@ pub(crate) enum SettingType { InvertScrollDirectionY, KeyboardMiddleClick, KeyboardSoundEnabled, + KeyboardSwipeToTypeEnabled, Language, LeftHandedMouse, LongPressDuration, @@ -387,6 +410,7 @@ impl SettingType { Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x, Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y, Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled, + Self::KeyboardSwipeToTypeEnabled => &mut config.keyboard_swipe_to_type_enabled, Self::LeftHandedMouse => &mut config.left_handed_mouse, Self::NotificationsEnabled => &mut config.notifications_enabled, Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled, @@ -535,6 +559,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"), @@ -589,6 +614,7 @@ impl SettingType { Self::InputCaptureMethod => Some("APP_SETTINGS.INPUT_CAPTURE_METHOD_HELP"), Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), + Self::KeyboardSwipeToTypeEnabled => Some("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::MouseAcceleration => Some("APP_SETTINGS.POINTER_ACCELERATION_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index cc2c9161..e6848e5d 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -1,7 +1,11 @@ +use std::path::PathBuf; use std::rc::Rc; use wgui::{ - components::button::{ButtonClickEvent, ComponentButton}, + components::{ + button::{ButtonClickEvent, ComponentButton}, + checkbox::ComponentCheckbox, + }, globals::WguiGlobals, i18n::Translation, layout::WidgetID, @@ -13,16 +17,19 @@ use wgui::{ }; use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind}; +use crate::util::downloadable_file::DownloadableFile; use crate::{ frontend::FrontendTasks, tab::settings::{ SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell, macros::{MacroParams, options_category, options_checkbox, options_range_f32}, + mount_requires_restart, }, util::{ popup_manager::PopupHolder, + swipe_type::{SWIPE_TYPE_MODEL, swipe_type_delete_all_models, swipe_type_model_path, swwipe_type_model_downloaded}, whisper::{ - WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, + WHISPER_MODELS, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, whisper_model_path, }, }, @@ -33,8 +40,12 @@ use crate::{ enum Task { WhisperDownloadClosed, WhisperRemoveUnused, - WhisperDownload(&'static WhisperModel), + WhisperDownload(&'static DownloadableFile), WhisperDownloadDone, + SwipeTypeDownloadClosed, + SwipeTypeRemoveAll, + SwipeTypeDownloadAll, + SwipeTypeDownloadDone, CloseDialog, ReloadTab, } @@ -46,7 +57,8 @@ pub struct State { globals: WguiGlobals, tasks: Tasks, parent_tasks: Tasks, - pending_download: Option<&'static WhisperModel>, + pending_whisper_download: Option<&'static DownloadableFile>, + pending_swipe_download: Option<&'static DownloadableFile>, } impl SettingsTab for State { @@ -57,7 +69,7 @@ impl SettingsTab for State { for task in self.tasks.drain() { match task { Task::WhisperDownloadClosed => { - if let Some(model) = self.pending_download.take() + if let Some(model) = self.pending_whisper_download.take() && !whisper_model_path(model.file_name).exists() { // download failed, set to selection to none @@ -71,11 +83,17 @@ impl SettingsTab for State { let _ = whisper_delete_all_models().log_err("could not remove whisper models"); } Task::WhisperDownload(model) => { - self.pending_download = Some(model); - self.show_whisper_download_dialog(model, par.executor.clone()); + self.pending_whisper_download = Some(model); + self.show_download_dialogue( + model, + par.executor.clone(), + whisper_model_path(model.file_name), + Task::WhisperDownloadClosed, + Task::WhisperDownloadDone, + ); } Task::WhisperDownloadDone => { - if let Some(model) = self.pending_download.take() { + if let Some(model) = self.pending_whisper_download.take() { par.general_config.whisper_model = model.file_name.into(); par.config_change_kind.replace(ConfigChangeKind::Other); @@ -83,6 +101,34 @@ impl SettingsTab for State { self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); } } + Task::SwipeTypeDownloadClosed => { + self.pending_swipe_download = None; + if !swwipe_type_model_downloaded().unwrap_or_default() { + par.general_config.keyboard_swipe_to_type_enabled = false; + par.config_change_kind.replace(ConfigChangeKind::Other); + self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); + } + } + Task::SwipeTypeRemoveAll => { + let _ = swipe_type_delete_all_models().log_err("could not remove swipe type models"); + par.general_config.keyboard_swipe_to_type_enabled = false; + } + Task::SwipeTypeDownloadAll => { + self.pending_swipe_download = Some(&SWIPE_TYPE_MODEL); + self.show_download_dialogue( + &SWIPE_TYPE_MODEL, + par.executor.clone(), + swipe_type_model_path(SWIPE_TYPE_MODEL.file_name), + Task::SwipeTypeDownloadClosed, + Task::SwipeTypeDownloadDone, + ); + } + Task::SwipeTypeDownloadDone => { + if let Some(_) = self.pending_swipe_download.take() { + par.config_change_kind.replace(ConfigChangeKind::Other); + self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); + } + } Task::CloseDialog => { let close_dialog = self.popup_dialog.get_close_callback(par.layout); close_dialog(); @@ -135,6 +181,27 @@ impl SettingsTab for State { Ok(()) } + + fn push_task_string( + &mut self, + action: &str, + _config: &mut GeneralConfig, + _change_kind: &mut Option, + _layout: &mut wgui::layout::Layout, + _state: &mut wgui::parser::ParserState, + ) -> anyhow::Result<()> { + match action { + "swipe_type_download" => { + self.show_swipe_type_model_dialog_box_download()?; + } + "swipe_type_remove" => { + self.tasks.push(Task::SwipeTypeRemoveAll); + self.tasks.push(Task::ReloadTab); + } + _ => {} + } + Ok(()) + } } impl State { @@ -149,6 +216,10 @@ impl State { whisper_models_dropdown(par.mp, c)?; } + if par.feats.swipe_to_type { + swipe_type_models_button(par.mp, c)?; + swipe_type_enabled_checkbox(par.mp, c)?; + } options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; @@ -178,30 +249,38 @@ impl State { popup_dialog, frontend_tasks: par.frontend_tasks.clone(), globals: par.mp.doc_params.globals.clone(), - pending_download: None, + pending_whisper_download: None, + pending_swipe_download: None, }) } - fn show_whisper_download_dialog(&mut self, model: &WhisperModel, executor: AsyncExecutor) { + fn show_download_dialogue( + &mut self, + file: &DownloadableFile, + executor: AsyncExecutor, + target_path: PathBuf, + on_closed: Task, + on_downloaded: Task, + ) { views::download_file::mount_popup( self.popup_download.clone(), self.frontend_tasks.clone(), - self.tasks.make_callback_box(Task::WhisperDownloadClosed), + self.tasks.make_callback_box(on_closed), views::download_file::Params { globals: self.globals.clone(), executor, - target_path: whisper_model_path(model.file_name), - url: model.url.into(), - on_downloaded: self.tasks.make_callback_box(Task::WhisperDownloadDone), + target_path, + url: file.url.into(), + on_downloaded: self.tasks.make_callback_box(on_downloaded), }, ); } - fn show_whisper_model_dialog_box_download(&mut self, model: &'static WhisperModel) -> anyhow::Result<()> { + fn show_whisper_model_dialog_box_download(&mut self, model: &'static DownloadableFile) -> anyhow::Result<()> { const ACTION_DOWNLOAD: &str = "download"; const ACTION_CANCEL: &str = "cancel"; - self.pending_download = Some(model); + self.pending_whisper_download = Some(model); let tasks = self.tasks.clone(); views::dialog_box::mount_popup( @@ -239,6 +318,45 @@ impl State { Ok(()) } + fn show_swipe_type_model_dialog_box_download(&mut self) -> anyhow::Result<()> { + const ACTION_DOWNLOAD: &str = "download"; + const ACTION_CANCEL: &str = "cancel"; + + let tasks = self.tasks.clone(); + views::dialog_box::mount_popup( + self.popup_dialog.clone(), + self.frontend_tasks.clone(), + views::dialog_box::Params { + globals: self.globals.clone(), + message: Translation::from_translation_key("APP_SETTINGS.SWIPE_TYPE.NEED_TO_DOWNLOAD_MODEL"), + entries: vec![ + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), + icon: "dashboard/close.svg", + action: ACTION_CANCEL, + }, + views::dialog_box::ButtonEntry { + content: Translation::from_translation_key("DOWNLOAD"), + icon: "dashboard/download.svg", + action: ACTION_DOWNLOAD, + }, + ], + on_action_click: Box::new(move |action| match action { + ACTION_DOWNLOAD => { + tasks.push(Task::SwipeTypeDownloadAll); + } + ACTION_CANCEL => { + tasks.push(Task::CloseDialog); + tasks.push(Task::SwipeTypeDownloadClosed); + } + _ => unreachable!(), + }), + }, + ); + + Ok(()) + } + fn show_whisper_model_dialog_box_cleanup(&mut self) -> anyhow::Result<()> { const ACTION_REMOVE: &str = "remove"; const ACTION_CANCEL: &str = "cancel"; @@ -355,3 +473,101 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re Ok(()) } + +fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let id = mp.idx.to_string(); + mp.idx += 1; + + let id_cell = horiz_cell(mp.layout, parent)?; + + let all_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); + let (translation, icon, action) = if all_downloaded { + ( + "APP_SETTINGS.SWIPE_TYPE.REMOVE_MODEL", + "dashboard/trash.svg", + "swipe_type_remove", + ) + } else { + ( + "APP_SETTINGS.SWIPE_TYPE.DOWNLOAD_MODEL", + "dashboard/download.svg", + "swipe_type_download", + ) + }; + + let mut params = TemplateParams::new(); + params.insert("id", &id); + params.insert("translation", translation); + params.insert("icon", icon); + + mp.parser_state + .instantiate_template(mp.doc_params, "ButtonText", mp.layout, id_cell, params)?; + + let btn = mp.parser_state.fetch_component_as::(&id)?; + btn.on_click(Rc::new({ + let parent_tasks = mp.tasks.clone(); + let action = Rc::::from(action); + move |_common, _e: ButtonClickEvent| { + parent_tasks.push(ParentTask::SwipeTypeAction(action.clone())); + Ok(()) + } + })); + + Ok(()) +} + +fn swipe_type_enabled_checkbox(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let id = mp.idx.to_string(); + mp.idx += 1; + + let setting = SettingType::KeyboardSwipeToTypeEnabled; + + // cannot enable swipe-to-type until the model is downloaded + let model_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); + + let mut params = TemplateParams::new(); + params.insert("id", &id); + + match setting.get_translation() { + Ok(translation) => params.insert("translation", translation), + Err(raw_text) => params.insert("text", raw_text), + }; + + let tooltip = if model_downloaded { + setting.get_tooltip() + } else { + Some("APP_SETTINGS.SWIPE_TYPE.MODEL_REQUIRED_TO_ENABLE") + }; + if let Some(tooltip) = tooltip { + params.insert("tooltip", tooltip); + } + + let checked = if *setting.mut_bool(mp.config) { "1" } else { "0" }; + params.insert("checked", checked); + + let id_cell = horiz_cell(mp.layout, parent)?; + + mp.parser_state + .instantiate_template(mp.doc_params, "CheckBoxSetting", mp.layout, id_cell, params)?; + + if setting.requires_restart() { + mount_requires_restart(mp.layout, id_cell)?; + } + + let checkbox = mp.parser_state.fetch_component_as::(&id)?; + + if !model_downloaded { + let mut common = mp.layout.common(); + checkbox.set_disabled(&mut common, true); + } + + checkbox.on_toggle(Box::new({ + let tasks = mp.tasks.clone(); + move |_common, e| { + tasks.push(ParentTask::UpdateBool(setting, e.checked)); + Ok(()) + } + })); + + Ok(()) +} diff --git a/dash-frontend/src/util/downloadable_file.rs b/dash-frontend/src/util/downloadable_file.rs new file mode 100644 index 00000000..818cb59b --- /dev/null +++ b/dash-frontend/src/util/downloadable_file.rs @@ -0,0 +1,5 @@ +pub struct DownloadableFile { + pub file_name: &'static str, + pub display_name: &'static str, + pub url: &'static str, +} diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs index 7eda5e07..06f7e3af 100644 --- a/dash-frontend/src/util/mod.rs +++ b/dash-frontend/src/util/mod.rs @@ -1,9 +1,11 @@ pub mod cached_fetcher; +pub mod downloadable_file; pub mod networking; pub mod openxr_bindings; pub mod pactl_wrapper; pub mod popup_manager; pub mod steam_utils; +pub mod swipe_type; pub mod toast_manager; pub mod wgui_simple; pub mod whisper; diff --git a/dash-frontend/src/util/swipe_type.rs b/dash-frontend/src/util/swipe_type.rs new file mode 100644 index 00000000..70928e8e --- /dev/null +++ b/dash-frontend/src/util/swipe_type.rs @@ -0,0 +1,47 @@ +use std::{fs, io, path::PathBuf}; + +use crate::util::downloadable_file::DownloadableFile; +use wlx_common::data_dir; + +pub const SWIPE_TYPE_MODEL: DownloadableFile = DownloadableFile { + file_name: "en.tar", + display_name: "English Qwerty (15 MiB)", + url: "https://github.com/oneshinyboi/super-swipe-type/raw/refs/tags/v0.4.2/crates/super-swipe-type/assets/en.tar", +}; + +pub fn swipe_type_model_folder() -> PathBuf { + data_dir::get_path("swipe_type") +} + +pub fn swipe_type_model_path(file_name: &str) -> PathBuf { + swipe_type_model_folder().join(file_name) +} + +pub fn swwipe_type_model_downloaded() -> io::Result { + let path = swipe_type_model_folder(); + if !path.is_dir() { + return Ok(false); + } + if !path.join(SWIPE_TYPE_MODEL.file_name).exists() { + return Ok(false); + } + Ok(true) +} + +pub fn swipe_type_delete_all_models() -> io::Result<()> { + let path = swipe_type_model_folder(); + if !path.is_dir() { + return Ok(()); + } + + for entry in fs::read_dir(path)? { + let entry = entry?; + let file_type = entry.file_type()?; + + if file_type.is_file() || file_type.is_symlink() { + fs::remove_file(entry.path())?; + } + } + + Ok(()) +} diff --git a/dash-frontend/src/util/whisper.rs b/dash-frontend/src/util/whisper.rs index 9ae5a1f3..8464ac9e 100644 --- a/dash-frontend/src/util/whisper.rs +++ b/dash-frontend/src/util/whisper.rs @@ -1,42 +1,37 @@ use std::{fs, io, path::PathBuf}; +use crate::util::downloadable_file::DownloadableFile; use wlx_common::data_dir; -pub struct WhisperModel { - pub file_name: &'static str, - pub display_name: &'static str, - pub url: &'static str, -} - -pub const WHISPER_MODELS: &[WhisperModel] = &[ - WhisperModel { +pub const WHISPER_MODELS: &[DownloadableFile] = &[ + DownloadableFile { file_name: "ggml-base-q8_0.bin", display_name: "Base Q8 (78MiB)", url: "https://wayvr.org/files/whisper/ggml-base-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-small-q8_0.bin", display_name: "Small Q8 (252MiB)", url: "https://wayvr.org/files/whisper/ggml-small-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo-q5_0.bin", display_name: "Turbo Q5 (574MiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo-q5_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo-q8_0.bin", display_name: "Turbo Q8 (874MiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo-q8_0.bin", }, - WhisperModel { + DownloadableFile { file_name: "ggml-large-v3-turbo.bin", display_name: "Turbo (1.5GiB)", url: "https://wayvr.org/files/whisper/ggml-large-v3-turbo.bin", }, ]; -pub fn whisper_model_from_name(file_name: &str) -> Option<&'static WhisperModel> { +pub fn whisper_model_from_name(file_name: &str) -> Option<&'static DownloadableFile> { WHISPER_MODELS.iter().find(|x| x.file_name == file_name) } diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 81f998b8..cfa8d81f 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -127,12 +127,13 @@ xkbcommon = { calloop = { version = "0.14.4", optional = true } calloop-wayland-source = { version = "0.4.1", optional = true } evdev = "0.13.2" +super-swipe-type = { version = "0.4.2", optional = true } [build-dependencies] regex.workspace = true [features] -default = ["openvr", "openxr", "osc", "wayland", "whisper", "x11"] +default = ["openvr", "openxr", "osc", "wayland", "whisper", "x11", "swipe-to-type"] as-raw-xcb-connection = [] feat-monado-metrics = [] openvr = ["dep:json", "dep:ovr_overlay"] @@ -147,5 +148,6 @@ whisper = [ "dep:pipewire", "dep:whisper-rs" ] +swipe-to-type = ["dep:super-swipe-type"] x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"] xcb = ["dep:xcb"] diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 0d3a2888..22f6075b 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}" /> + + @@ -94,6 +99,13 @@ + + + @@ -246,7 +258,10 @@ -
+
+ + +
diff --git a/wayvr/src/backend/wayvr/client.rs b/wayvr/src/backend/wayvr/client.rs index 734e8b68..ddde3502 100644 --- a/wayvr/src/backend/wayvr/client.rs +++ b/wayvr/src/backend/wayvr/client.rs @@ -9,6 +9,7 @@ use std::{ use anyhow::Context; use glam::{DVec2, Vec2}; use slotmap::DenseSlotMap; +use smithay::wayland::selection::data_device::set_data_device_selection; use smithay::{ backend::input::{Axis, AxisSource, ButtonState, Keycode}, input::{ @@ -21,13 +22,13 @@ use smithay::{ use wgui::log::LogErr; 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, @@ -259,6 +260,17 @@ impl WayVRCompositor { } } + pub fn set_clipboard_text(&mut self, text: String) { + set_data_device_selection::( + &self.state.display_handle, + &self.state.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 0af1c39d..a48ced71 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -1201,6 +1201,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 04b5f948..e6414542 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -180,6 +180,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 pinned_apps: Vec, pub handsfree_pointer: HandsfreePointer, @@ -255,6 +256,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(), pinned_apps: config.pinned_apps.clone(), handsfree_pointer: config.handsfree_pointer, diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 815f6521..715c64ab 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -1,5 +1,11 @@ use std::{rc::Rc, time::Duration}; +#[cfg(feature = "swipe-to-type")] +use super::init_swipe_type_manager; +use super::{ + KeyButtonData, KeyState, KeyboardState, handle_mouse_motion, handle_press, handle_release, + layout::{self, KeyCapType}, +}; use crate::{ app_misc, gui::{ @@ -30,22 +36,18 @@ use wgui::{ sprite::WidgetSprite, }, }; - -use super::{ - KeyButtonData, KeyState, KeyboardState, handle_press, handle_release, - layout::{self, KeyCapType}, -}; +#[cfg(feature = "swipe-to-type")] +use wlx_common::data_dir; const PIXELS_PER_UNIT: f32 = 60.; -fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'static> { +pub(super) fn new_doc_params(panel: &mut GuiPanel) -> ParseDocumentParams<'static> { ParseDocumentParams { globals: panel.layout.state.globals.clone(), path: AssetPath::FileOrBuiltIn("gui/keyboard.xml"), extra: panel.doc_extra.take().unwrap_or_default(), } } - fn bool_to_rc_str(val: bool) -> Rc { if val { "1" } else { "0" }.into() } @@ -137,7 +139,7 @@ pub(super) fn create_keyboard_panel( params.insert_rc("width", key_width.to_string().into()); params.insert_rc("height", key_height.to_string().into()); - let mut label = key.label.into_iter(); + let mut label = key.label.clone().into_iter(); label .next() .and_then(|s| params.insert_rc("text", s.into())); @@ -197,6 +199,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( @@ -228,26 +233,69 @@ 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) } }), @@ -317,6 +365,23 @@ 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_slot = None; + + super::prediction_bar::set_visible(panel, false); + } + if app.session.config.keyboard_swipe_to_type_enabled + && panel.state.swipe_typing_manager.is_none() + { + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager( + &mut panel.state, + data_dir::get_path("swipe_type").join("en.tar"), + ); + + super::prediction_bar::set_visible(panel, true); + } } OverlayEventData::CustomCommand { element, command } => { @@ -411,7 +476,7 @@ fn set_anim_color( rect.params.border = key_state.border.lerp(key_state.border * 1.5, pos); } -fn on_enter_anim( +pub(super) fn on_enter_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &event::CallbackData, @@ -459,7 +524,7 @@ fn on_enter_anim( )); } -fn on_leave_anim( +pub(super) fn on_leave_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &event::CallbackData, @@ -509,7 +574,7 @@ fn on_leave_anim( )); } -fn on_press_anim( +pub(super) fn on_press_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, @@ -524,7 +589,7 @@ fn on_press_anim( key_state.drawn_state.set(true); } -fn on_release_anim( +pub(super) fn on_release_anim( key_state: Rc, common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, diff --git a/wayvr/src/overlays/keyboard/layout.rs b/wayvr/src/overlays/keyboard/layout.rs index 0582cf61..9136f874 100644 --- a/wayvr/src/overlays/keyboard/layout.rs +++ b/wayvr/src/overlays/keyboard/layout.rs @@ -246,7 +246,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 ff4fc44f..3de46499 100644 --- a/wayvr/src/overlays/keyboard/mod.rs +++ b/wayvr/src/overlays/keyboard/mod.rs @@ -5,6 +5,8 @@ use std::{ sync::{Arc, atomic::Ordering}, }; +use crate::overlays::keyboard::layout::KeyCapType; +use crate::overlays::keyboard::swipe_type::{PredictionSlot, SwipeTypingManager}; use crate::overlays::toast::Toast; use crate::{ KEYMAP_CHANGE, @@ -29,16 +31,19 @@ use crate::{ }, }; use anyhow::Context; -use glam::{Affine3A, Quat, Vec3, vec3}; +use glam::{Affine3A, Quat, Vec2, Vec3, vec3}; use regex::Regex; use slotmap::{SlotMap, new_key_type}; use smallvec::SmallVec; +use wgui::event::DeviceBitmask; use wgui::{ color::WguiColor, event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex}, i18n::Translation, layout::WidgetID, }; +#[cfg(feature = "swipe-to-type")] +use wlx_common::data_dir; use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::{ config::AltModifier, @@ -47,9 +52,122 @@ use wlx_common::{ pub mod builder; mod layout; +mod prediction_bar; + +#[cfg(feature = "swipe-to-type")] +mod swipe_type; + +#[cfg(not(feature = "swipe-to-type"))] +mod swipe_type { + use glam::Vec2; + use wgui::event::{DeviceBitmask, MouseButtonIndex}; + + pub struct SwipeTypingManager; + + #[derive(Clone, Default)] + pub struct PredictionSlot; + + #[allow(dead_code)] // stub used only to satisfy compilation when the feature is disabled + impl PredictionSlot { + pub fn new() -> Self { + Self + } + pub fn set(&self, _value: Option>) {} + pub fn take(&self) -> Option>> { + None + } + } + + #[allow(dead_code)] // stub used only to satisfy compilation when the feature is disabled + impl SwipeTypingManager { + pub fn new(_model_folder: std::path::PathBuf) -> anyhow::Result<(Self, PredictionSlot)> { + Ok((Self, PredictionSlot)) + } + pub fn add_swipe( + &mut self, + _within_key_pos_normalized: &Vec2, + _key_label: char, + _device: DeviceBitmask, + _index: Option, + ) { + } + pub fn predict(&mut self) -> anyhow::Result<()> { + Ok(()) + } + pub fn reset(&mut self) {} + pub fn did_swipe_leave_first_key(&self) -> bool { + false + } + pub fn is_current_swipe_empty(&self) -> bool { + true + } + pub fn current_swipe_mouse_button_index(&self) -> Option { + None + } + pub fn handle_key_press( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _within_key_pos: &Option, + _key_label: &[String], + _device: DeviceBitmask, + _index: MouseButtonIndex, + ) -> super::KeyPressOutcome { + super::KeyPressOutcome::Dispatch + } + pub fn handle_key_motion( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _within_key_pos: &Option, + _key_label: &[String], + _device: DeviceBitmask, + ) { + } + pub fn handle_key_release( + &mut self, + _key_cap_type: &super::layout::KeyCapType, + _alt_modifier: crate::subsystem::hid::KeyModifier, + ) -> super::KeyReleaseOutcome { + super::KeyReleaseOutcome::Normal + } + pub fn select_word( + &mut self, + _word: &String, + _app: &mut crate::state::AppState, + _original_keyboard_mods: crate::subsystem::hid::KeyModifier, + ) { + } + pub fn select_alternate_prediction( + &mut self, + _word: &String, + _app: &mut crate::state::AppState, + _original_keyboard_mods: crate::subsystem::hid::KeyModifier, + ) { + } + } +} pub const KEYBOARD_NAME: &str = "kbd"; const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR]; + +/// Outcome of handling a key press (or motion) against the swipe-to-type engine. +#[allow(dead_code)] +pub enum KeyPressOutcome { + /// The event was fed into swipe tracking. Do not dispatch a key. + Consumed, + /// Not a swipe target. Dispatch as a normal key event. + Dispatch, +} + +/// Outcome of handling a key release against the swipe-to-type engine. +#[allow(dead_code)] +pub enum KeyReleaseOutcome { + /// A swipe left the first key; prediction was already triggered. Nothing to dispatch. + Predict, + /// Pressed and released on the same key. Dispatch a tap with the given modifier. + TapKey { modifier: KeyModifier }, + /// Not a swipe target. Dispatch a normal key-up. + Normal, +} const SYSTEM_LAYOUT_ALIASES: [&str; 5] = ["mozc", "pinyin", "hangul", "sayura", "unikey"]; pub fn create_keyboard(app: &mut AppState) -> anyhow::Result { @@ -80,6 +198,8 @@ pub fn create_keyboard(app: &mut AppState) -> anyhow::Result anyhow::Result { + state.swipe_typing_manager = Some(engine); + state.swipe_candidate_slot = Some(slot); + } + Err(e) => { + log::error!("Error occurred while trying to load swipe engine: {}", e); + } + }; +} const fn alt_modifier_to_key(m: AltModifier) -> KeyModifier { match m { AltModifier::Shift => SHIFT, @@ -147,8 +278,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 { + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager(&mut state, data_dir::get_path("swipe_type").join("en.tar")); + 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 { + prediction_bar::set_visible(&mut panel, false); + } let id = self.layout_panels.insert(panel); if let Some(layout_name) = keymap.and_then(|k| k.get_name()) { @@ -173,10 +315,10 @@ 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::GlobalChange( @@ -185,20 +327,35 @@ impl KeyboardBackend { 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 { + #[cfg(feature = "swipe-to-type")] + init_swipe_type_manager( + &mut state_from, + data_dir::get_path("swipe_type").join("en.tar"), + ); + } + 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 { + prediction_bar::set_visible( + self.layout_panels.get_mut(self.active_layout).unwrap(), + false, + ); + } } fn get_effective_keymap(&mut self) -> anyhow::Result { @@ -232,6 +389,13 @@ impl KeyboardBackend { } } + fn update_swipe_prediction_bar(&mut self, app: &mut AppState) -> anyhow::Result<()> { + if prediction_bar::update(self.panel(), app)? { + self.panel().process_custom_elems(app); + } + Ok(()) + } + fn switch_keymap_by_name( &mut self, keymap_name: &str, @@ -335,6 +499,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<()> { @@ -399,6 +564,8 @@ struct KeyboardState { keymap_switch_layouts: Vec>, keymap_switch_index: usize, keymap_switch_pending: bool, + swipe_typing_manager: Option, + swipe_candidate_slot: Option, } macro_rules! take_and_leave_default { @@ -421,6 +588,8 @@ impl KeyboardState { keymap_switch_layouts: std::mem::take(&mut self.keymap_switch_layouts), keymap_switch_index: self.keymap_switch_index, keymap_switch_pending: false, + swipe_typing_manager: None, + swipe_candidate_slot: None, } } } @@ -471,26 +640,56 @@ enum KeyButtonData { }, } +fn handle_mouse_motion( + key: &KeyState, + key_label: &Vec, + key_cap_type: &KeyCapType, + keyboard: &mut KeyboardState, + within_key_pos: &Option, + device: DeviceBitmask, +) { + if let KeyButtonData::Key { .. } = &key.button_state + && let Some(swipe_manager) = keyboard.swipe_typing_manager.as_mut() + { + swipe_manager.handle_key_motion(key_cap_type, within_key_pos, key_label, 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: DeviceBitmask, ) { match &key.button_state { KeyButtonData::Key { vk, pressed } => { - keyboard.modifiers |= match button.index { - MouseButtonIndex::Right => SHIFT, - MouseButtonIndex::Middle => keyboard.alt_modifier, - _ => 0, + let outcome = match keyboard.swipe_typing_manager.as_mut() { + Some(swipe_manager) => swipe_manager.handle_key_press( + key_cap_type, + within_key_pos, + key_label, + device, + button.index, + ), + None => KeyPressOutcome::Dispatch, }; - 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 matches!(outcome, KeyPressOutcome::Dispatch) { + 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); @@ -537,20 +736,54 @@ 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); + let outcome = match keyboard.swipe_typing_manager.as_mut() { + Some(swipe_manager) => { + swipe_manager.handle_key_release(k_cap_type, keyboard.alt_modifier) + } + None => KeyReleaseOutcome::Normal, + }; - for m in &AUTO_RELEASE_MODS { - if keyboard.modifiers & *m != 0 { - keyboard.modifiers &= !*m; + match outcome { + KeyReleaseOutcome::Predict => {} + KeyReleaseOutcome::TapKey { modifier } => { + keyboard.modifiers |= modifier; + 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); + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), *vk, false); + + for m in &AUTO_RELEASE_MODS { + if keyboard.modifiers & *m != 0 { + keyboard.modifiers &= !*m; + } + } + play_key_click(app); + } + KeyReleaseOutcome::Normal => { + 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/prediction_bar.rs b/wayvr/src/overlays/keyboard/prediction_bar.rs new file mode 100644 index 00000000..0cf024e8 --- /dev/null +++ b/wayvr/src/overlays/keyboard/prediction_bar.rs @@ -0,0 +1,167 @@ +use std::{collections::HashMap, rc::Rc}; + +use super::{ + KeyButtonData, KeyState, KeyboardState, + builder::{new_doc_params, on_enter_anim, on_leave_anim, on_press_anim, on_release_anim}, +}; +use crate::{gui::panel::GuiPanel, state::AppState}; +use anyhow::bail; +use slotmap::Key; +use wgui::event::StyleSetRequest; +use wgui::{ + event::EventListenerKind, + layout::LayoutTask, + parser::{Fetchable, TemplateParams}, + taffy::Display, + widget::{EventResult, rectangle::WidgetRectangle}, +}; + +/// Root widget that hosts the swipe-to-type prediction candidates. +pub(super) const ROOT: &str = "swipe_predictions_root"; + +/// Render the prediction candidate bar from the latest received predictions. +pub(super) fn update( + panel: &mut GuiPanel, + app: &mut AppState, +) -> anyhow::Result { + let mut elements_changed = false; + + let anim_mult = app.wgui_theme.animation_mult; + + if let Some(slot) = panel.state.swipe_candidate_slot.as_mut() + && let Some(candidates) = slot.take() + { + let predictions_root = panel.parser_state.get_widget_id(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, + TemplateParams::from_hashmap(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(), + labels: Default::default(), + sprites: Default::default(), + }) + }; + 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, 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, 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) +} + +/// Show or hide the prediction candidate bar. +pub(super) fn set_visible(panel: &mut GuiPanel, visible: bool) { + let predictions_root = panel.parser_state.get_widget_id(ROOT).unwrap_or_default(); + if predictions_root.is_null() { + return; + } + + if !visible { + panel.layout.remove_children(predictions_root); + } + + panel.layout.tasks.push(LayoutTask::SetWidgetStyle( + predictions_root, + StyleSetRequest::Display(if visible { + Display::Flex + } else { + Display::None + }), + )); +} diff --git a/wayvr/src/overlays/keyboard/swipe_type.rs b/wayvr/src/overlays/keyboard/swipe_type.rs new file mode 100644 index 00000000..dc986f85 --- /dev/null +++ b/wayvr/src/overlays/keyboard/swipe_type.rs @@ -0,0 +1,411 @@ +use super::layout::KeyCapType; +use crate::state::AppState; +use crate::subsystem::clipboard; +use crate::subsystem::clipboard::ClipboardProvider; +use crate::subsystem::hid::{CTRL, KeyModifier, SHIFT, VirtualKey}; +use crate::subsystem::input::InputFocus; +use anyhow::bail; +use glam::Vec2; +use std::mem; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{Sender, channel}; +use std::sync::{Arc, Mutex}; +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 wgui::event::{DeviceBitmask, MouseButtonIndex}; +use wgui::log::LogErr; + +const PREDICTION_SUGGESTION_COUNT: usize = 5; + +enum PredictionTask { + Predict { + swipe: Vec, + last_word: Option, + }, + Shutdown, +} +#[derive(Clone)] +pub struct PredictionSlot(Arc); + +struct PredictionSlotInner { + present: AtomicBool, + value: Mutex>>, +} + +impl PredictionSlot { + pub fn new() -> Self { + Self(Arc::new(PredictionSlotInner { + present: AtomicBool::new(false), + value: Mutex::new(None), + })) + } + + pub fn set(&self, value: Option>) { + *self.0.value.lock().unwrap() = value; + self.0.present.store(true, Ordering::Release); + } + + /// Returns the latest pending value, if any, and clears the slot. + /// `Some(Some(words))` is a fresh prediction, `Some(None)` is an explicit + /// clear (mirrors the previous `send(None)`), `None` means nothing pending. + pub fn take(&self) -> Option>> { + if !self.0.present.swap(false, Ordering::Acquire) { + return None; + } + let value = self.0.value.lock().unwrap().take(); + Some(value) + } +} + +impl Default for PredictionSlot { + fn default() -> Self { + Self::new() + } +} + +pub struct SwipeTypingManager { + keyboard_gird: QwertyKeyboardGrid, + current_swipe: Vec, + prediction_slot: PredictionSlot, + prediction_task_sender: Sender, + worker_thread: Option>, + swipe_start_time: Option, + clipboard: Option>, + swipe_left_first_key: bool, + first_swipe_char: char, + current_swipe_device: Option, + current_swipe_mouse_button_index: 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.get_input_focus() { + InputFocus::PhysicalScreen => { + if let Some(clipboard) = self.clipboard.as_mut() { + clipboard.set_clipboard_utf8(&text_to_paste); + Self::paste(app, original_keyboard_mods); + } + } + InputFocus::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(model_path: PathBuf) -> anyhow::Result<(SwipeTypingManager, PredictionSlot)> { + let prediction_slot = PredictionSlot::new(); + let worker_slot = prediction_slot.clone(); + let (task_sender, task_receiver) = channel::(); + + // Spawn persistent worker thread + let worker_thread = thread::spawn(move || { + let mut swipe_engine = match SwipeOrchestrator::new_from_path(&model_path) { + 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(); + + worker_slot.set(Some(words)); + } + Err(e) => { + log::error!("Prediction failed: {e}"); + } + } + } + PredictionTask::Shutdown => break, + } + } + }); + let clipboard_provider: Option> = { + #[cfg(feature = "wayland")] + let wl = clipboard::wl::Provider::new() + .log_err("Could not create Wayland clipboard provider") + .ok() + .map(|p| Box::new(p) as Box); + #[cfg(not(feature = "wayland"))] + let wl = None; + + #[cfg(feature = "x11")] + let x11 = clipboard::x11::Provider::new() + .log_err("Could not create X11 clipboard provider") + .ok() + .map(|p| Box::new(p) as Box); + #[cfg(not(feature = "x11"))] + let x11 = None; + + wl.or(x11) + }; + Ok(( + Self { + keyboard_gird: QwertyKeyboardGrid::new(), + current_swipe: Vec::new(), + prediction_slot: prediction_slot.clone(), + prediction_task_sender: task_sender, + worker_thread: Some(worker_thread), + swipe_start_time: None, + clipboard: clipboard_provider, + swipe_left_first_key: false, + first_swipe_char: char::default(), + current_swipe_device: None, + current_swipe_mouse_button_index: None, + last_swiped_word: None, + }, + prediction_slot, + )) + } + + 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(); + self.prediction_slot.set(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; + self.current_swipe_mouse_button_index = None; + } + + fn start_swipe( + &mut self, + key_label: char, + device: DeviceBitmask, + index: Option, + ) -> 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); + self.current_swipe_mouse_button_index = index; + now + } + + pub const fn did_swipe_leave_first_key(&self) -> bool { + self.swipe_left_first_key + } + + pub const fn is_current_swipe_empty(&self) -> bool { + self.current_swipe.is_empty() + } + + pub const fn current_swipe_mouse_button_index(&self) -> Option { + self.current_swipe_mouse_button_index + } + + /// Handle a key press. Letter keys feed the swipe engine and are consumed; + /// anything else is dispatched as a normal key. + pub fn handle_key_press( + &mut self, + key_cap_type: &KeyCapType, + within_key_pos: &Option, + key_label: &[String], + device: DeviceBitmask, + index: MouseButtonIndex, + ) -> super::KeyPressOutcome { + if !matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) { + return super::KeyPressOutcome::Dispatch; + } + + if let Some(pos) = within_key_pos + && let Some(label) = key_label.first() + { + self.add_swipe( + pos, + label.chars().next().unwrap_or_default(), + device, + Some(index), + ); + } + + super::KeyPressOutcome::Consumed + } + + /// Handle pointer motion over a key while swiping. + pub fn handle_key_motion( + &mut self, + key_cap_type: &KeyCapType, + within_key_pos: &Option, + key_label: &[String], + device: DeviceBitmask, + ) { + if !self.is_current_swipe_empty() + && matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) + && let Some(pos) = within_key_pos + && pos.x >= 0.0 + && pos.x <= 1.0 + && pos.y >= 0.0 + && pos.y <= 1.0 + && let Some(label) = key_label.first() + { + self.add_swipe(pos, label.chars().next().unwrap_or_default(), device, None); + } + } + + /// Handle a key release. A swipe that left the first key triggers a + /// prediction; a tap on the same key dispatches the key; anything else is + /// a normal key-up. + pub fn handle_key_release( + &mut self, + key_cap_type: &KeyCapType, + alt_modifier: KeyModifier, + ) -> super::KeyReleaseOutcome { + if !matches!(key_cap_type, KeyCapType::Letter | KeyCapType::LetterAltGr) { + self.reset(); + return super::KeyReleaseOutcome::Normal; + } + + if self.did_swipe_leave_first_key() { + if let Err(e) = self.predict() { + log::error!("{}", e); + } + return super::KeyReleaseOutcome::Predict; + } + + let modifier = match self.current_swipe_mouse_button_index() { + Some(MouseButtonIndex::Right) => SHIFT, + Some(MouseButtonIndex::Middle) => alt_modifier, + _ => 0, + }; + self.reset(); + super::KeyReleaseOutcome::TapKey { modifier } + } + + pub fn add_swipe( + &mut self, + within_key_pos_normalized: &Vec2, + key_label: char, + device: DeviceBitmask, + index: Option, + ) { + if let Some(pos) = self + .keyboard_gird + .key_positions + .get(&key_label.to_ascii_lowercase()) + { + if let Some(current_device) = self.current_swipe_device + && 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, index), + }; + + 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/state.rs b/wayvr/src/state.rs index fb2363fc..f2ac99ba 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -192,6 +192,10 @@ impl AppState { { feats.whisper = true; } + #[cfg(feature = "swipe-to-type")] + { + feats.swipe_to_type = true; + } let mut app_state = Self { tasks, diff --git a/wayvr/src/subsystem/mod.rs b/wayvr/src/subsystem/mod.rs index 5909c060..8c214093 100644 --- a/wayvr/src/subsystem/mod.rs +++ b/wayvr/src/subsystem/mod.rs @@ -3,7 +3,7 @@ pub mod hid; pub mod input; pub mod notifications; -#[cfg(feature = "whisper")] +#[cfg(any(feature = "whisper", feature = "swipe-to-type"))] pub mod clipboard; #[cfg(feature = "whisper")] diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 1e078ade..465d12d0 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -66,6 +66,7 @@ struct State { checked: bool, hovered: bool, down: bool, + disabled: bool, on_toggle: Option, self_ref: Weak, active_tooltip: Option>, @@ -99,6 +100,32 @@ pub struct ComponentCheckbox { const COLOR_UNCHECKED: WguiColor = WguiColor::Raw(drawing::Color::new(0., 0., 0., 0.)); const COLOR_HOVERED: WguiColor = WguiColorName::Tertiary.to_wgui_color(); +fn disabled_color() -> WguiColor { + WguiColor::from(WguiColorName::OnBackground).with_alpha(0.35) +} + +/// Color for the inner checkbox box, for any checked/hover/disabled combination. +fn box_fill_color(checked: bool, hovered: bool, disabled: bool, checked_color: WguiColor) -> WguiColor { + if disabled { + if checked { disabled_color() } else { COLOR_UNCHECKED } + } else if checked { + if hovered { COLOR_HOVERED } else { checked_color } + } else { + COLOR_UNCHECKED + } +} + +/// Color for the checkbox border, for any hover/press/disabled combination. +fn box_border_color(hovered: bool, down: bool, disabled: bool) -> WguiColor { + if disabled { + disabled_color() + } else if hovered || down { + COLOR_HOVERED + } else { + WguiColorName::OnBackground.into() + } +} + impl ComponentTrait for ComponentCheckbox { fn base(&self) -> &ComponentBase { &self.base @@ -120,13 +147,9 @@ impl ComponentTrait for ComponentCheckbox { } } -fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool) { +fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool, disabled: bool) { widgets.call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = if checked { - if hovered { COLOR_HOVERED } else { data.color_checked } - } else { - COLOR_UNCHECKED - } + rect.params.color = box_fill_color(checked, hovered, disabled, data.color_checked); }); } @@ -141,6 +164,7 @@ impl ComponentCheckbox { pub fn set_checked(&self, common: &mut CallbackDataCommon, checked: bool) { let hovered; + let disabled; { let mut state = self.state.borrow_mut(); if state.checked == checked { @@ -148,8 +172,9 @@ impl ComponentCheckbox { } state.checked = checked; hovered = state.hovered; + disabled = state.disabled; } - set_box_checked(&common.state.widgets, &self.data, checked, hovered); + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); common.alterables.mark_redraw(); } @@ -170,39 +195,88 @@ impl ComponentCheckbox { pub fn on_toggle(&self, func: CheckboxToggleCallback) { self.state.borrow_mut().on_toggle = Some(func); } + + /// Enable or disable user interaction with the checkbox and dim its appearance. + pub fn set_disabled(&self, common: &mut CallbackDataCommon, disabled: bool) { + let checked; + let hovered; + let down; + { + let mut state = self.state.borrow_mut(); + if state.disabled == disabled { + return; + } + state.disabled = disabled; + checked = state.checked; + hovered = state.hovered; + down = state.down; + } + + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); + + common + .state + .widgets + .call(self.data.id_outer_box, |rect: &mut WidgetRectangle| { + rect.params.border_color = box_border_color(hovered, down, disabled); + }); + + if let Some(mut label) = common.state.widgets.get_as::(self.data.id_label) { + let color = if disabled { + disabled_color() + } else { + WguiColorName::OnBackground.into() + }; + label.set_color(common, color, true); + } + + common.alterables.mark_redraw(); + } + + pub fn get_disabled(&self) -> bool { + self.state.borrow().disabled + } } -fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, _pressed: bool) { +fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, pressed: bool, disabled: bool) { let rect = anim_data.obj.as_any_mut().downcast_mut::().unwrap(); rect.params.border = 2.0; - rect.params.border_color = if pos > 0.0 { - COLOR_HOVERED - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pos > 0.0, pressed, disabled); } fn anim_hover_in(state: &Rc>, data: &Rc, anim_mult: f32) -> Animation { - let down = state.borrow().down; + let down; + let disabled; + { + let state = state.borrow(); + down = state.down; + disabled = state.disabled; + } Animation::new( data.id_outer_box, (5. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, anim_data.pos, down); + anim_hover(anim_data, anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) } fn anim_hover_out(state: &Rc>, data: &Rc, anim_mult: f32) -> Animation { - let down = state.borrow().down; + let down; + let disabled; + { + let state = state.borrow(); + down = state.down; + disabled = state.disabled; + } Animation::new( data.id_outer_box, (8. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, 1.0 - anim_data.pos, down); + anim_hover(anim_data, 1.0 - anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) @@ -218,26 +292,31 @@ fn register_event_mouse_enter( listeners.register( EventListenerKind::MouseEnter, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common.alterables.animate(anim_hover_in(&state, &data, anim_mult)); + let checked; + let disabled; + { + let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; + state.hovered = true; + } + + if !disabled { + common.alterables.trigger_haptics(); + common.alterables.animate(anim_hover_in(&state, &data, anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, true, false, data.color_checked); + }); + } + } ComponentTooltip::register_hover_in(common, &tooltip_info, data.id_container, state.clone()); - let checked = { - let mut state = state.borrow_mut(); - state.hovered = true; - state.checked - }; - - if checked { - common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = COLOR_HOVERED; - }); - } - Ok(EventResult::Pass) }), ) @@ -252,23 +331,28 @@ fn register_event_mouse_leave( listeners.register( EventListenerKind::MouseLeave, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common.alterables.animate(anim_hover_out(&state, &data, anim_mult)); - - let checked = { + let checked; + let disabled; + { let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; state.hovered = false; state.active_tooltip = None; - state.checked - }; + } - if checked { - common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = data.color_checked; - }); + if !disabled { + common.alterables.trigger_haptics(); + common.alterables.animate(anim_hover_out(&state, &data, anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, false, false, data.color_checked); + }); + } } Ok(EventResult::Pass) @@ -303,18 +387,14 @@ fn register_event_mouse_press( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if pressed_hovered { - COLOR_HOVERED - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pressed_hovered, false, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); common.alterables.unfocus(); - if state.hovered { + if state.hovered && !state.disabled { state.down = true; Ok(EventResult::Consumed) } else { @@ -341,17 +421,13 @@ fn register_event_mouse_release( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if released_hovered { - COLOR_HOVERED - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(released_hovered, was_down, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); - if was_down { + if !state.disabled && was_down { state.down = false; if let Some(self_ref) = state.self_ref.upgrade() @@ -369,7 +445,13 @@ fn register_event_mouse_release( }); } - set_box_checked(&common.state.widgets, &data, state.checked, state.hovered); + set_box_checked( + &common.state.widgets, + &data, + state.checked, + state.hovered, + state.disabled, + ); if state.hovered && let Some(on_toggle) = &state.on_toggle { @@ -500,6 +582,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul checked: params.checked, down: false, hovered: false, + disabled: false, on_toggle: None, self_ref: Weak::new(), active_tooltip: None, diff --git a/wgui/src/event.rs b/wgui/src/event.rs index a74ab953..3add97aa 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -253,6 +253,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 c54c9619..9fb0ba9d 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -532,6 +532,9 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub grid_opacity: f32, + #[serde(default)] + pub keyboard_swipe_to_type_enabled: bool, + #[serde(default)] pub chroma_key_params: ChromaKeyParams, diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs index 5af7307f..d9f99bef 100644 --- a/wlx-common/src/dash_interface.rs +++ b/wlx-common/src/dash_interface.rs @@ -52,6 +52,7 @@ pub struct InterfaceFeats { pub monado: bool, pub passthru: bool, pub whisper: bool, + pub swipe_to_type: bool, } impl InterfaceFeats { @@ -62,6 +63,7 @@ impl InterfaceFeats { monado: false, passthru: false, whisper: false, + swipe_to_type: false, } } } diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs index 2b9d9143..4f17c7a0 100644 --- a/wlx-common/src/dash_interface_emulated.rs +++ b/wlx-common/src/dash_interface_emulated.rs @@ -240,6 +240,7 @@ impl DashInterface<()> for DashInterfaceEmulated { monado: true, passthru: false, whisper: true, + swipe_to_type: true, } }