diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f7453e3..788d6f91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,10 +98,16 @@ jobs: - name: Compile Rust backend unit tests (Windows) # Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime # DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link - # 覆盖;共享单测在 macOS / Linux 上实际执行。 + # 覆盖;Rust-only 后端单测由下一步实际执行。 if: runner.os == 'Windows' run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run + - name: Run Rust-only backend unit tests (Windows) + # 只把纯 Rust 后端模块编进独立 test crate,不链接完整 Tauri app lib。 + # 这样 Windows CI 能实际执行 coordinator/hotkey/recorder/insertion 逻辑单测。 + if: runner.os == 'Windows' + run: cargo test --manifest-path src-tauri/backend-tests/Cargo.toml + - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 # 一旦版本号 drift 立刻 fail,避免发版时再发现漏改。 diff --git a/openless-all/app/src-tauri/backend-tests/Cargo.lock b/openless-all/app/src-tauri/backend-tests/Cargo.lock new file mode 100644 index 00000000..a10b693e --- /dev/null +++ b/openless-all/app/src-tauri/backend-tests/Cargo.lock @@ -0,0 +1,2242 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.11.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" +dependencies = [ + "block-sys", + "objc2 0.5.2", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "cocoa" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667fdc068627a2816b9ff831201dd9864249d6ee8d190b9532357f1fc0f61ea7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics 0.21.0", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.7.0", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a67c4378cf203eace8fb6567847eb641fd6ff933c1145a115c6ee820ebb978" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys 0.8.7", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.7", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enigo" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0087a01fc8591217447d28005379fb5a183683cc83f0a4707af28cc6603f70fb" +dependencies = [ + "core-graphics 0.23.2", + "foreign-types-shared 0.3.1", + "icrate", + "libc", + "log", + "objc2 0.5.2", + "windows 0.56.0", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "global-hotkey" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fbb3a4e56c901ee66c190fdb3fa08344e6d09593cc6c61f8eb9add7144b271" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.4", + "objc2-app-kit", + "once_cell", + "thiserror 2.0.18", + "windows-sys 0.59.0", + "x11-dl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "icrate" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" +dependencies = [ + "block2", + "objc2 0.5.2", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.11.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openless-backend-tests" +version = "0.1.0" +dependencies = [ + "arboard", + "cpal", + "enigo", + "global-hotkey", + "log", + "once_cell", + "parking_lot", + "rdev", + "serde", + "serde_json", + "thiserror 1.0.69", + "uuid", + "windows 0.58.0", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rdev" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00552ca2dc2f93b84cd7b5581de49549411e4e41d89e1c691bcb93dc4be360c3" +dependencies = [ + "cocoa", + "core-foundation 0.7.0", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "lazy_static", + "libc", + "winapi", + "x11", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement 0.56.0", + "windows-interface 0.56.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xkbcommon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/openless-all/app/src-tauri/backend-tests/Cargo.toml b/openless-all/app/src-tauri/backend-tests/Cargo.toml new file mode 100644 index 00000000..d3dc05c1 --- /dev/null +++ b/openless-all/app/src-tauri/backend-tests/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "openless-backend-tests" +version = "0.1.0" +edition = "2021" +rust-version = "1.77" +publish = false + +[[test]] +name = "backend_rust" +path = "tests/backend_rust.rs" + +[dependencies] +arboard = "3" +cpal = "0.15" +enigo = "0.2" +global-hotkey = "0.6" +log = "0.4" +once_cell = "1" +parking_lot = "0.12" +rdev = "0.5" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "1" +uuid = { version = "1", features = ["v4", "serde"] } + +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.58", features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_WindowsAndMessaging", +] } diff --git a/openless-all/app/src-tauri/backend-tests/tests/backend_rust.rs b/openless-all/app/src-tauri/backend-tests/tests/backend_rust.rs new file mode 100644 index 00000000..08ef2f2a --- /dev/null +++ b/openless-all/app/src-tauri/backend-tests/tests/backend_rust.rs @@ -0,0 +1,40 @@ +//! Rust-only backend unit harness. +//! +//! 这个测试 crate 只把纯 Rust 后端模块按源码路径编进来,不链接完整 Tauri +//! `openless_lib`,避免 Windows CI 在 test harness 启动前被桌面运行时 DLL 拦截。 +//! Cargo 以 `cfg(test)` 编译这些 path-included 模块,所以各模块自己的 +//! `#[cfg(test)]` 单测会在这里实际执行(见 hotkey / recorder / insertion)。 + +#![allow(dead_code, unused_variables)] + +mod asr { + pub mod local { + pub mod foundry { + pub const DEFAULT_MODEL_ALIAS: &str = "whisper-large-v3-turbo"; + pub const PROVIDER_ID: &str = "foundry-local-whisper"; + } + + pub mod foundry_native { + pub fn normalize_runtime_source_str(value: &str) -> String { + match value.trim() { + "nuget" | "ort-nightly" => value.trim().to_string(), + _ => "auto".to_string(), + } + } + } + } +} + +#[path = "../../src/coordinator_state.rs"] +mod coordinator_state; +#[path = "../../src/hotkey.rs"] +mod hotkey; +#[cfg(not(target_os = "macos"))] +#[path = "../../src/insertion.rs"] +mod insertion; +#[path = "../../src/recorder.rs"] +mod recorder; +#[path = "../../src/shortcut_binding.rs"] +mod shortcut_binding; +#[path = "../../src/types.rs"] +mod types; diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index fd85a4cf..8cd0d2f3 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -16,16 +16,6 @@ use parking_lot::Mutex; use tauri::{async_runtime, AppHandle, Emitter, Manager}; use uuid::Uuid; -type SessionId = Uuid; - -fn new_session_id() -> SessionId { - Uuid::new_v4() -} - -fn initial_session_id() -> SessionId { - Uuid::nil() -} - #[cfg(target_os = "windows")] use crate::asr::local::{foundry, FoundryLocalRuntime, FoundryLocalWhisperAsr}; use crate::asr::{ @@ -33,6 +23,13 @@ use crate::asr::{ WhisperBatchASR, }; use crate::combo_hotkey::{ComboHotkeyError, ComboHotkeyEvent, ComboHotkeyMonitor}; +use crate::coordinator_state::{ + begin_cancel_session_state, begin_recording_abort_before_restore, begin_session_state, + finish_cancel_session_state, finish_starting_session_state, initial_session_id, new_session_id, + publish_abort_idle_after_restore, request_stop_during_starting_state, + start_processing_if_listening, startup_race_status, BeginOutcome, SessionId, SessionPhase, + SessionState, StartupRaceStatus, +}; use crate::hotkey::{HotkeyEvent, HotkeyMonitor}; use crate::insertion::TextInserter; use crate::persistence::{ @@ -53,19 +50,6 @@ use crate::windows_ime_ipc::ImeSubmitTarget; #[cfg(target_os = "windows")] use crate::windows_ime_session::{PreparedWindowsImeSession, WindowsImeSessionController}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum SessionPhase { - Idle, - Starting, - Listening, - Processing, - /// 已经过了最后一次 cancel 检查、即将 / 正在调用 inserter.insert 的窗口。 - /// cancel_session 在此阶段拒绝介入:Cmd+V 模拟点击已开始或已发出, - /// 无法撤销,硬把 cancelled=true 也救不回来,只会让 UI 出现 cancelled - /// 但实际还是插入了的诡异状态。详见 PR 修 Codex audit HIGH #2。 - Inserting, -} - enum ActiveAsr { Volcengine(Arc), Whisper(Arc), @@ -102,25 +86,6 @@ impl SessionResource { } } -struct SessionState { - phase: SessionPhase, - started_at: Instant, - /// Starting 阶段(ASR 握手中)按下 stop 边沿(toggle 第二次按 / hold 松开)→ - /// 等握手完成 phase=Listening 后立刻 end_session,不丢边沿。issue #51。 - pending_stop: bool, - /// 用户在 Processing 阶段按 Esc 取消:end_session 在 polish/insert 检查点跳过插入 + - /// 跳过 history.append。issue #52。 - cancelled: bool, - focus_target: Option, - /// 每次 begin_session 生成新的 UUID session id。 - /// recorder error monitor 持有 captured id,处理时若与当前不等说明 - /// 是上一 session 的迟到错误,必须 drop,不要 abort 当前 active session。 - session_id: SessionId, - /// 用户开始 dictation 时所处的前台 app 标签("Mail (com.apple.mail)" / Windows 窗口标题)。 - /// 用作 LLM polish/translate 的上下文前提,让模型按 app 调风格。详见 issue #116。 - front_app: Option, -} - struct SharedRecordingMuteState { guard: Option, holders: u32, @@ -135,20 +100,6 @@ impl SharedRecordingMuteState { } } -impl Default for SessionState { - fn default() -> Self { - Self { - phase: SessionPhase::Idle, - started_at: Instant::now(), - pending_stop: false, - cancelled: false, - focus_target: None, - session_id: initial_session_id(), - front_app: None, - } - } -} - pub struct Coordinator { inner: Arc, } @@ -1704,10 +1655,9 @@ async fn handle_released(inner: &Arc) { fn request_stop_during_starting(inner: &Arc, reason: &str) { { let mut state = inner.state.lock(); - if state.phase != SessionPhase::Starting { + if !request_stop_during_starting_state(&mut state) { return; } - state.pending_stop = true; } log::info!("[coord] {reason} during Starting — queued"); stop_recorder_if_pending_start_stop(inner); @@ -1956,23 +1906,15 @@ fn window_key_matches_trigger(trigger: crate::types::HotkeyTrigger, key: &str, c async fn begin_session(inner: &Arc) -> Result<(), String> { let current_session_id = { let mut state = inner.state.lock(); - if state.phase != SessionPhase::Idle { + let Some(session_id) = + begin_session_state(&mut state, capture_focus_target(), capture_frontmost_app()) + else { return Ok(()); - } - state.phase = SessionPhase::Starting; - state.started_at = Instant::now(); - // 新会话清掉旧 pending_stop / cancelled,避免上一会话遗留触发奇怪行为 - state.pending_stop = false; - state.cancelled = false; - state.focus_target = capture_focus_target(); - // 新建 UUID session_id;spawn 出去的 recorder error monitor 会捕获这个值, - // 如果迟到错误到达时 id 已不匹配就 drop,不会误中止后续 session。 - state.session_id = new_session_id(); - state.front_app = capture_frontmost_app(); + }; if let Some(label) = state.front_app.as_deref() { log::info!("[coord] front_app captured: {label}"); } - state.session_id + session_id }; #[cfg(target_os = "windows")] { @@ -2356,33 +2298,6 @@ fn abort_recording_with_error(inner: &Arc, message: String) { schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); } -struct RecordingAbort { - elapsed: u64, - session_id: SessionId, -} - -fn begin_recording_abort_before_restore(state: &mut SessionState) -> Option { - if state.cancelled - || !matches!( - state.phase, - SessionPhase::Starting | SessionPhase::Listening - ) - { - return None; - } - state.cancelled = true; - Some(RecordingAbort { - elapsed: state.started_at.elapsed().as_millis() as u64, - session_id: state.session_id, - }) -} - -fn publish_abort_idle_after_restore(state: &mut SessionState, session_id: SessionId) { - if state.session_id == session_id { - state.phase = SessionPhase::Idle; - } -} - async fn start_recorder_and_enter_listening( inner: &Arc, session_id: SessionId, @@ -2400,19 +2315,7 @@ async fn finish_starting_session(inner: &Arc, session_id: SessionId) { // 反向覆盖回 Listening → 用户的 cancel 边沿被吞掉。 let outcome = { let mut state = inner.state.lock(); - if state.session_id != session_id { - BeginOutcome::StaleContinuation - } else if state.cancelled || state.phase != SessionPhase::Starting { - BeginOutcome::CancelRaced - } else { - state.phase = SessionPhase::Listening; - let pending = std::mem::replace(&mut state.pending_stop, false); - if pending { - BeginOutcome::PendingStop - } else { - BeginOutcome::Started - } - } + finish_starting_session_state(&mut state, session_id) }; match outcome { BeginOutcome::StaleContinuation => { @@ -2441,11 +2344,10 @@ async fn finish_starting_session(inner: &Arc, session_id: SessionId) { async fn end_session(inner: &Arc) -> Result<(), String> { let current_session_id = { let mut state = inner.state.lock(); - if state.phase != SessionPhase::Listening { + let Some(session_id) = start_processing_if_listening(&mut state) else { return Ok(()); - } - state.phase = SessionPhase::Processing; - state.session_id + }; + session_id }; let elapsed = inner.state.lock().started_at.elapsed().as_millis() as u64; @@ -2946,37 +2848,29 @@ fn dictation_error_code( } fn cancel_session(inner: &Arc) { - let (phase, session_id) = { + let Some(decision) = ({ let mut state = inner.state.lock(); let phase = state.phase; - if phase == SessionPhase::Idle { - return; - } - // Inserting 阶段已经过了最后一次 cancel 检查 + 锁内转换,inserter.insert 即将 - // 或正在执行 → Cmd+V 已发出无法撤销。这里硬设 cancelled=true 只会让 UI 显示 - // "已取消" 但文本仍被插入,与用户预期相反。直接拒绝,让本次 session 走完。 + let decision = begin_cancel_session_state(&mut state); if phase == SessionPhase::Inserting { log::info!("[coord] cancel ignored — already in Inserting phase, can't undo paste"); - return; } - // Processing 阶段 cancel 不能直接干掉 in-flight polish task(已经 await 了), - // 但可以打 cancelled 标记,让 end_session 在插入前检查并丢弃结果。 - state.cancelled = true; - (phase, state.session_id) + decision + }) else { + return; }; - stop_recorder_for_session(inner, session_id); - cancel_asr_for_session(inner, session_id); - restore_prepared_windows_ime_session(inner, session_id); + stop_recorder_for_session(inner, decision.session_id); + cancel_asr_for_session(inner, decision.session_id); + restore_prepared_windows_ime_session(inner, decision.session_id); // Processing 阶段保持 phase=Processing 让 end_session 自己走完检查 + 收尾; // 其他阶段直接转 Idle。 - if phase != SessionPhase::Processing { + if decision.phase != SessionPhase::Processing { let mut state = inner.state.lock(); - state.phase = SessionPhase::Idle; - state.focus_target = None; + finish_cancel_session_state(&mut state, decision); } emit_capsule(inner, CapsuleState::Cancelled, 0.0, 0, None, None); - log::info!("[coord] session cancelled (was {phase:?})"); + log::info!("[coord] session cancelled (was {:?})", decision.phase); schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); } @@ -4701,36 +4595,6 @@ fn foundry_audio_transcribe_timeout_duration() -> std::time::Duration { std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) } -/// begin_session 中各 await 之间的 cancel race 检查结果。 -enum BeginOutcome { - /// 启动 continuation 属于旧 session;不能改动当前 session 状态。 - StaleContinuation, - /// 正常进入 Listening。 - Started, - /// Starting 阶段积累了 pending_stop 边沿,应立即 end_session(hold 快速松开 / toggle 快速双击)。 - PendingStop, - /// 期间 cancel_session 触发(cancelled=true 或 phase 被外部改回 Idle)。 - /// 必须回滚 recorder + ASR 资源,不进 Listening。 - CancelRaced, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum StartupRaceStatus { - ActiveStarting, - CancelRaced, - StaleContinuation, -} - -fn startup_race_status(state: &SessionState, captured_session_id: SessionId) -> StartupRaceStatus { - if state.session_id != captured_session_id { - StartupRaceStatus::StaleContinuation - } else if state.cancelled || state.phase != SessionPhase::Starting { - StartupRaceStatus::CancelRaced - } else { - StartupRaceStatus::ActiveStarting - } -} - /// 检查 begin_session 的 await 间隙是否被 cancel_session 打断。 /// 必须在持有 state lock 的瞬间读,结果一拿就过期,所以用 helper 名字提醒只在 /// 「准备做下一步副作用前」用。 diff --git a/openless-all/app/src-tauri/src/coordinator_state.rs b/openless-all/app/src-tauri/src/coordinator_state.rs new file mode 100644 index 00000000..70733327 --- /dev/null +++ b/openless-all/app/src-tauri/src/coordinator_state.rs @@ -0,0 +1,484 @@ +//! Coordinator 的纯状态转移层。 +//! +//! 这里不依赖 Tauri / 音频 / 系统剪贴板,只描述 dictation session 的 Rust +//! 状态机。这样 Windows CI 可以在不启动完整 Tauri test harness 的情况下实际运行 +//! 后端单测。 + +use std::time::Instant; + +use uuid::Uuid; + +pub type SessionId = Uuid; + +pub fn new_session_id() -> SessionId { + Uuid::new_v4() +} + +pub fn initial_session_id() -> SessionId { + Uuid::nil() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SessionPhase { + Idle, + Starting, + Listening, + Processing, + /// 已经过了最后一次 cancel 检查、即将 / 正在调用 inserter.insert 的窗口。 + /// cancel_session 在此阶段拒绝介入:Cmd+V 模拟点击已开始或已发出, + /// 无法撤销,硬把 cancelled=true 也救不回来,只会让 UI 出现 cancelled + /// 但实际还是插入了的诡异状态。详见 PR 修 Codex audit HIGH #2。 + Inserting, +} + +pub(crate) struct SessionState { + pub(crate) phase: SessionPhase, + pub(crate) started_at: Instant, + /// Starting 阶段(ASR 握手中)按下 stop 边沿(toggle 第二次按 / hold 松开)→ + /// 等握手完成 phase=Listening 后立刻 end_session,不丢边沿。issue #51。 + pub(crate) pending_stop: bool, + /// 用户在 Processing 阶段按 Esc 取消:end_session 在 polish/insert 检查点跳过插入 + + /// 跳过 history.append。issue #52。 + pub(crate) cancelled: bool, + pub(crate) focus_target: Option, + /// 每次 begin_session 生成新的 UUID session id。 + /// recorder error monitor 持有 captured id,处理时若与当前不等说明 + /// 是上一 session 的迟到错误,必须 drop,不要 abort 当前 active session。 + pub(crate) session_id: SessionId, + /// 用户开始 dictation 时所处的前台 app 标签("Mail (com.apple.mail)" / Windows 窗口标题)。 + /// 用作 LLM polish/translate 的上下文前提,让模型按 app 调风格。详见 issue #116。 + pub(crate) front_app: Option, +} + +impl Default for SessionState { + fn default() -> Self { + Self { + phase: SessionPhase::Idle, + started_at: Instant::now(), + pending_stop: false, + cancelled: false, + focus_target: None, + session_id: initial_session_id(), + front_app: None, + } + } +} + +/// begin_session 的锁内转移:只有 Idle 能进入 Starting,并生成新 session id。 +pub(crate) fn begin_session_state( + state: &mut SessionState, + focus_target: Option, + front_app: Option, +) -> Option { + if state.phase != SessionPhase::Idle { + return None; + } + state.phase = SessionPhase::Starting; + state.started_at = Instant::now(); + state.pending_stop = false; + state.cancelled = false; + state.focus_target = focus_target; + state.session_id = new_session_id(); + state.front_app = front_app; + Some(state.session_id) +} + +/// stop_dictation / hold release 在 Starting 阶段只记录 pending_stop,等待启动完成后处理。 +pub(crate) fn request_stop_during_starting_state(state: &mut SessionState) -> bool { + if state.phase != SessionPhase::Starting { + return false; + } + state.pending_stop = true; + true +} + +/// begin_session 中各 await 之间的 cancel race 检查结果。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum BeginOutcome { + /// 启动 continuation 属于旧 session;不能改动当前 session 状态。 + StaleContinuation, + /// 正常进入 Listening。 + Started, + /// Starting 阶段积累了 pending_stop 边沿,应立即 end_session(hold 快速松开 / toggle 快速双击)。 + PendingStop, + /// 期间 cancel_session 触发(cancelled=true 或 phase 被外部改回 Idle)。 + /// 必须回滚 recorder + ASR 资源,不进 Listening。 + CancelRaced, +} + +pub(crate) fn finish_starting_session_state( + state: &mut SessionState, + session_id: SessionId, +) -> BeginOutcome { + if state.session_id != session_id { + BeginOutcome::StaleContinuation + } else if state.cancelled || state.phase != SessionPhase::Starting { + BeginOutcome::CancelRaced + } else { + state.phase = SessionPhase::Listening; + let pending = std::mem::replace(&mut state.pending_stop, false); + if pending { + BeginOutcome::PendingStop + } else { + BeginOutcome::Started + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum StartupRaceStatus { + ActiveStarting, + CancelRaced, + StaleContinuation, +} + +pub(crate) fn startup_race_status( + state: &SessionState, + captured_session_id: SessionId, +) -> StartupRaceStatus { + if state.session_id != captured_session_id { + StartupRaceStatus::StaleContinuation + } else if state.cancelled || state.phase != SessionPhase::Starting { + StartupRaceStatus::CancelRaced + } else { + StartupRaceStatus::ActiveStarting + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct CancelDecision { + pub(crate) phase: SessionPhase, + pub(crate) session_id: SessionId, +} + +/// cancel_session 的锁内前半段。Idle / Inserting 不可取消;其他阶段设置 cancelled。 +pub(crate) fn begin_cancel_session_state(state: &mut SessionState) -> Option { + let phase = state.phase; + if matches!(phase, SessionPhase::Idle | SessionPhase::Inserting) { + return None; + } + state.cancelled = true; + Some(CancelDecision { + phase, + session_id: state.session_id, + }) +} + +/// cancel_session 外部资源清理后的锁内收尾。Processing 交给 end_session 自己收尾。 +pub(crate) fn finish_cancel_session_state(state: &mut SessionState, decision: CancelDecision) { + if decision.phase != SessionPhase::Processing { + state.phase = SessionPhase::Idle; + state.focus_target = None; + } +} + +pub(crate) fn start_processing_if_listening(state: &mut SessionState) -> Option { + if state.phase != SessionPhase::Listening { + return None; + } + state.phase = SessionPhase::Processing; + Some(state.session_id) +} + +pub(crate) struct RecordingAbort { + pub(crate) elapsed: u64, + pub(crate) session_id: SessionId, +} + +pub(crate) fn begin_recording_abort_before_restore( + state: &mut SessionState, +) -> Option { + if state.cancelled + || !matches!( + state.phase, + SessionPhase::Starting | SessionPhase::Listening + ) + { + return None; + } + state.cancelled = true; + Some(RecordingAbort { + elapsed: state.started_at.elapsed().as_millis() as u64, + session_id: state.session_id, + }) +} + +pub(crate) fn publish_abort_idle_after_restore(state: &mut SessionState, session_id: SessionId) { + if state.session_id == session_id { + state.phase = SessionPhase::Idle; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn session_id(n: u128) -> SessionId { + Uuid::from_u128(n) + } + + #[test] + fn begin_session_enters_starting_and_clears_stale_edges() { + let mut state = SessionState { + pending_stop: true, + cancelled: true, + ..Default::default() + }; + + let id = begin_session_state( + &mut state, + Some(7), + Some("Terminal (com.apple.Terminal)".into()), + ) + .unwrap(); + + assert_eq!(state.phase, SessionPhase::Starting); + assert!(!state.pending_stop); + assert!(!state.cancelled); + assert_eq!(state.focus_target, Some(7)); + assert_eq!( + state.front_app.as_deref(), + Some("Terminal (com.apple.Terminal)") + ); + assert_eq!(state.session_id, id); + assert_ne!(id, initial_session_id()); + } + + #[test] + fn begin_session_ignores_non_idle_phase() { + let mut state = SessionState { + phase: SessionPhase::Processing, + session_id: session_id(99), + ..Default::default() + }; + + assert!(begin_session_state(&mut state, Some(1), Some("Mail".into())).is_none()); + + assert_eq!(state.phase, SessionPhase::Processing); + assert_eq!(state.session_id, session_id(99)); + assert!(state.focus_target.is_none()); + assert!(state.front_app.is_none()); + } + + #[test] + fn stop_during_starting_sets_pending_stop_only_for_starting() { + let mut state = SessionState { + phase: SessionPhase::Starting, + ..Default::default() + }; + + assert!(request_stop_during_starting_state(&mut state)); + assert!(state.pending_stop); + + state.phase = SessionPhase::Listening; + state.pending_stop = false; + assert!(!request_stop_during_starting_state(&mut state)); + assert!(!state.pending_stop); + } + + #[test] + fn finish_starting_is_table_driven_for_pending_cancel_and_stale_edges() { + let cases = [ + ( + SessionPhase::Starting, + false, + false, + session_id(7), + BeginOutcome::Started, + SessionPhase::Listening, + ), + ( + SessionPhase::Starting, + false, + true, + session_id(7), + BeginOutcome::PendingStop, + SessionPhase::Listening, + ), + ( + SessionPhase::Starting, + true, + false, + session_id(7), + BeginOutcome::CancelRaced, + SessionPhase::Starting, + ), + ( + SessionPhase::Idle, + false, + false, + session_id(7), + BeginOutcome::CancelRaced, + SessionPhase::Idle, + ), + ( + SessionPhase::Starting, + false, + false, + session_id(8), + BeginOutcome::StaleContinuation, + SessionPhase::Starting, + ), + ]; + + for (phase, cancelled, pending_stop, actual_id, expected, expected_phase) in cases { + let mut state = SessionState { + phase, + cancelled, + pending_stop, + session_id: actual_id, + ..Default::default() + }; + + assert_eq!( + finish_starting_session_state(&mut state, session_id(7)), + expected, + "phase={phase:?} cancelled={cancelled} pending_stop={pending_stop} actual_id={actual_id}" + ); + assert_eq!(state.phase, expected_phase); + if expected == BeginOutcome::PendingStop { + assert!(!state.pending_stop); + } + } + } + + #[test] + fn cancel_session_state_machine_is_table_driven() { + let cases = [ + (SessionPhase::Idle, SessionPhase::Idle, false), + (SessionPhase::Starting, SessionPhase::Idle, true), + (SessionPhase::Listening, SessionPhase::Idle, true), + (SessionPhase::Processing, SessionPhase::Processing, true), + (SessionPhase::Inserting, SessionPhase::Inserting, false), + ]; + + for (initial, expected_phase, expected_cancelled) in cases { + let mut state = SessionState { + phase: initial, + cancelled: false, + focus_target: Some(1), + session_id: session_id(42), + ..Default::default() + }; + + if let Some(decision) = begin_cancel_session_state(&mut state) { + finish_cancel_session_state(&mut state, decision); + } + + assert_eq!(state.phase, expected_phase, "initial={initial:?}"); + assert_eq!(state.cancelled, expected_cancelled, "initial={initial:?}"); + if matches!(initial, SessionPhase::Starting | SessionPhase::Listening) { + assert!(state.focus_target.is_none(), "initial={initial:?}"); + } + } + } + + #[test] + fn stop_dictation_from_listening_enters_processing_once() { + let mut state = SessionState { + phase: SessionPhase::Listening, + session_id: session_id(123), + ..Default::default() + }; + + assert_eq!( + start_processing_if_listening(&mut state), + Some(session_id(123)) + ); + assert_eq!(state.phase, SessionPhase::Processing); + assert_eq!(start_processing_if_listening(&mut state), None); + assert_eq!(state.phase, SessionPhase::Processing); + } + + #[test] + fn startup_race_check_is_table_driven_for_begin_session_edges() { + let cases = [ + ( + SessionPhase::Starting, + false, + session_id(7), + StartupRaceStatus::ActiveStarting, + ), + ( + SessionPhase::Starting, + true, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Idle, + false, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Listening, + false, + session_id(7), + StartupRaceStatus::CancelRaced, + ), + ( + SessionPhase::Starting, + false, + session_id(8), + StartupRaceStatus::StaleContinuation, + ), + ]; + + for (phase, cancelled, actual_session_id, expected) in cases { + let state = SessionState { + phase, + cancelled, + session_id: actual_session_id, + ..Default::default() + }; + + assert_eq!( + startup_race_status(&state, session_id(7)), + expected, + "phase={phase:?} cancelled={cancelled} actual_session={actual_session_id}" + ); + } + } + + #[test] + fn recording_abort_keeps_session_non_idle_until_restore_can_run() { + let mut state = SessionState { + phase: SessionPhase::Listening, + cancelled: false, + session_id: session_id(7), + ..Default::default() + }; + + let abort = begin_recording_abort_before_restore(&mut state).unwrap(); + + assert_eq!(abort.session_id, session_id(7)); + assert!(state.cancelled); + assert_eq!(state.phase, SessionPhase::Listening); + + publish_abort_idle_after_restore(&mut state, abort.session_id); + + assert_eq!(state.phase, SessionPhase::Idle); + } + + #[test] + fn recording_abort_is_noop_after_prior_cancel_or_idle() { + let cases = [ + (SessionPhase::Idle, false), + (SessionPhase::Processing, false), + (SessionPhase::Listening, true), + ]; + + for (phase, cancelled) in cases { + let mut state = SessionState { + phase, + cancelled, + ..Default::default() + }; + + assert!(begin_recording_abort_before_restore(&mut state).is_none()); + assert_eq!(state.phase, phase); + assert_eq!(state.cancelled, cancelled); + } + } +} diff --git a/openless-all/app/src-tauri/src/hotkey.rs b/openless-all/app/src-tauri/src/hotkey.rs index 38bc516f..6babad78 100644 --- a/openless-all/app/src-tauri/src/hotkey.rs +++ b/openless-all/app/src-tauri/src/hotkey.rs @@ -1042,6 +1042,27 @@ mod platform { ); } + #[test] + fn windows_modifier_edges_ignore_unrelated_keys_and_reemit_after_release() { + let shared = shared(HotkeyTrigger::RightControl); + let (ctx, rx) = callback_context(shared); + + assert!(!dispatch_keyboard_event(&ctx, VK_LCONTROL, WM_KEYDOWN)); + assert!(dispatch_keyboard_event(&ctx, VK_RCONTROL, WM_KEYUP)); + assert!(dispatch_keyboard_event(&ctx, VK_RCONTROL, WM_KEYDOWN)); + assert!(dispatch_keyboard_event(&ctx, VK_RCONTROL, WM_KEYUP)); + assert!(dispatch_keyboard_event(&ctx, VK_RCONTROL, WM_KEYDOWN)); + + assert_eq!( + drain(&rx), + vec![ + HotkeyEvent::Pressed, + HotkeyEvent::Released, + HotkeyEvent::Pressed + ] + ); + } + #[test] fn windows_optional_modifier_shortcuts_use_independent_latches() { let shared = shared(HotkeyTrigger::RightControl); @@ -1352,6 +1373,47 @@ mod platform { ); } + #[test] + fn rdev_modifier_edges_ignore_unrelated_keys_and_reemit_after_release() { + let shared = shared(HotkeyTrigger::RightControl); + let (tx, rx) = mpsc::channel(); + + dispatch_event( + &shared, + &tx, + key_event(EventType::KeyPress(Key::ControlLeft)), + ); + dispatch_event( + &shared, + &tx, + key_event(EventType::KeyRelease(Key::ControlRight)), + ); + dispatch_event( + &shared, + &tx, + key_event(EventType::KeyPress(Key::ControlRight)), + ); + dispatch_event( + &shared, + &tx, + key_event(EventType::KeyRelease(Key::ControlRight)), + ); + dispatch_event( + &shared, + &tx, + key_event(EventType::KeyPress(Key::ControlRight)), + ); + + assert_eq!( + drain(&rx), + vec![ + HotkeyEvent::Pressed, + HotkeyEvent::Released, + HotkeyEvent::Pressed + ] + ); + } + #[test] fn rdev_optional_modifier_shortcuts_use_independent_latches() { let shared = shared(HotkeyTrigger::RightControl); diff --git a/openless-all/app/src-tauri/src/insertion.rs b/openless-all/app/src-tauri/src/insertion.rs index da36ccd4..ecfc4490 100644 --- a/openless-all/app/src-tauri/src/insertion.rs +++ b/openless-all/app/src-tauri/src/insertion.rs @@ -479,6 +479,13 @@ mod tests { let inserter = TextInserter::new(); assert_eq!(inserter.insert("", true), InsertStatus::CopiedFallback); + #[cfg(not(target_os = "macos"))] + { + assert_eq!( + inserter.insert_via_clipboard_fallback("", true), + InsertStatus::CopiedFallback + ); + } assert_eq!(inserter.copy_fallback(""), InsertStatus::CopiedFallback); } @@ -520,13 +527,13 @@ mod tests { #[test] #[cfg(target_os = "macos")] - fn macos_paste_failure_keeps_copied_fallback_available() { + fn macos_direct_write_or_paste_failure_keeps_copied_fallback_available() { assert_eq!( macos_insert_status_after_paste(Ok(())), InsertStatus::Inserted ); assert_eq!( - macos_insert_status_after_paste(Err("AX write unavailable".to_string())), + macos_insert_status_after_paste(Err("AX direct write unavailable".to_string())), InsertStatus::CopiedFallback ); } diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 7bb94ea4..1e47e16a 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -15,6 +15,7 @@ mod audio_mute; mod combo_hotkey; mod commands; mod coordinator; +mod coordinator_state; mod global_hotkey_runtime; mod hotkey; mod insertion; diff --git a/openless-all/app/src-tauri/src/recorder.rs b/openless-all/app/src-tauri/src/recorder.rs index c135a115..eb1b1d49 100644 --- a/openless-all/app/src-tauri/src/recorder.rs +++ b/openless-all/app/src-tauri/src/recorder.rs @@ -619,6 +619,13 @@ mod tests { } } + fn decode_i16_le(bytes: &[u8]) -> Vec { + bytes + .chunks_exact(2) + .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]])) + .collect() + } + #[test] fn downmix_to_mono_averages_complete_interleaved_frames() { let mono = downmix_to_mono(&[1.0, -1.0, 0.5, 0.25, 0.0], 2); @@ -655,6 +662,63 @@ mod tests { assert_eq!(*state.resample_phase.lock(), 0.5); } + #[test] + fn resample_upsamples_with_linear_interpolation_and_tail_state() { + let state = StreamState::new(); + + let out = resample_to_target(&[0.0, 1.0], 8_000, TARGET_SAMPLE_RATE, &state); + + assert_eq!(out, vec![0.0, 0.5, 1.0]); + assert_eq!(*state.last_sample.lock(), 1.0); + assert_eq!(*state.resample_phase.lock(), 0.0); + } + + #[test] + fn process_callback_resamples_non_target_input_before_emitting_pcm() { + let consumer = RecordingConsumer::default(); + let levels = Arc::new(StdMutex::new(Vec::new())); + let levels_for_handler = Arc::clone(&levels); + let state = StreamState::new(); + + process_callback( + &[0.0, 1.0], + 1, + 8_000, + &consumer, + &move |level| levels_for_handler.lock().unwrap().push(level), + &state, + ); + + let chunks = consumer.chunks.lock().unwrap(); + assert_eq!(chunks.len(), 1); + assert_eq!(decode_i16_le(&chunks[0]), vec![0, 16383, 32767]); + assert_eq!(*levels.lock().unwrap(), vec![1.0]); + assert_eq!(state.callback_count.load(Ordering::Relaxed), 1); + } + + #[test] + fn process_callback_reports_scaled_rms_level_and_peaks() { + let consumer = RecordingConsumer::default(); + let levels = Arc::new(StdMutex::new(Vec::new())); + let levels_for_handler = Arc::clone(&levels); + let state = StreamState::new(); + + process_callback( + &[0.125, -0.125], + 1, + TARGET_SAMPLE_RATE, + &consumer, + &move |level| levels_for_handler.lock().unwrap().push(level), + &state, + ); + + let levels = levels.lock().unwrap(); + assert_eq!(levels.len(), 1); + assert!((levels[0] - 0.5).abs() < 0.0001); + assert_eq!(state.peak_input_rms_milli.load(Ordering::Relaxed), 125); + assert_eq!(state.peak_output_rms_milli.load(Ordering::Relaxed), 125); + } + #[test] fn process_callback_emits_pcm_level_and_liveness_marker() { let consumer = RecordingConsumer::default(); @@ -678,4 +742,33 @@ mod tests { assert!(state.last_callback_time.lock().is_some()); assert_eq!(state.callback_count.load(Ordering::Relaxed), 1); } + + #[test] + fn process_callback_ignores_empty_or_zero_channel_input_without_liveness_marker() { + let consumer = RecordingConsumer::default(); + let levels = Arc::new(StdMutex::new(Vec::new())); + let levels_for_handler = Arc::clone(&levels); + let state = StreamState::new(); + + process_callback( + &[], + 1, + TARGET_SAMPLE_RATE, + &consumer, + &move |level| levels_for_handler.lock().unwrap().push(level), + &state, + ); + process_callback( + &[0.25, -0.25], + 0, + TARGET_SAMPLE_RATE, + &consumer, + &move |level| levels.lock().unwrap().push(level), + &state, + ); + + assert!(consumer.chunks.lock().unwrap().is_empty()); + assert!(state.last_callback_time.lock().is_none()); + assert_eq!(state.callback_count.load(Ordering::Relaxed), 0); + } } diff --git a/openless-all/app/src-tauri/src/shortcut_binding.rs b/openless-all/app/src-tauri/src/shortcut_binding.rs index 731f8532..bc2bd85b 100644 --- a/openless-all/app/src-tauri/src/shortcut_binding.rs +++ b/openless-all/app/src-tauri/src/shortcut_binding.rs @@ -204,6 +204,9 @@ mod tests { modifiers: vec!["cmd".into(), "shift".into()], }; let parsed = parse_global_hotkey(&combo).expect("combo parses"); + #[cfg(target_os = "windows")] + assert!(parsed.mods.contains(Modifiers::CONTROL)); + #[cfg(not(target_os = "windows"))] assert!(parsed.mods.contains(Modifiers::SUPER)); assert!(parsed.mods.contains(Modifiers::SHIFT)); assert_eq!(parsed.key, Code::KeyD);