From d34f53ac6597e937d378f5492671c6f80f4c276f Mon Sep 17 00:00:00 2001 From: csd113 Date: Wed, 11 Mar 2026 17:03:54 -0700 Subject: [PATCH 1/5] first stable push --- Cargo.lock | 4530 ++++++++++++++++++++++++++++ Cargo.toml | 10 + README.md | 82 +- dev-check-strict.sh | 410 +++ docs/Dev1.docx | Bin 0 -> 26084 bytes docs/Dev2.docx | Bin 0 -> 34200 bytes docs/hamnet-relay-build-roadmap.md | 693 +++++ src/config.rs | 21 + src/decoder.rs | 247 ++ src/encoder.rs | 48 + src/framer.rs | 187 ++ src/gui.rs | 451 +++ src/main.rs | 123 + src/wav.rs | 92 + 14 files changed, 6892 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100755 dev-check-strict.sh create mode 100644 docs/Dev1.docx create mode 100644 docs/Dev2.docx create mode 100644 docs/hamnet-relay-build-roadmap.md create mode 100644 src/config.rs create mode 100644 src/decoder.rs create mode 100644 src/encoder.rs create mode 100644 src/framer.rs create mode 100644 src/gui.rs create mode 100644 src/main.rs create mode 100644 src/wav.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..91fafab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4530 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "accesskit" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" + +[[package]] +name = "accesskit_consumer" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" +dependencies = [ + "accesskit", +] + +[[package]] +name = "accesskit_macos" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" +dependencies = [ + "accesskit", + "accesskit_consumer", + "async-channel", + "async-once-cell", + "atspi", + "futures-lite 1.13.0", + "once_cell", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" +dependencies = [ + "accesskit", + "accesskit_consumer", + "once_cell", + "paste", + "static_assertions", + "windows 0.48.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "winit", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "afsk" +version = "0.1.0" +dependencies = [ + "clap", + "eframe", + "hound", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +dependencies = [ + "android-properties", + "bitflags 2.11.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[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", + "log", + "objc2 0.6.4", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io 2.6.0", + "async-lock 3.4.2", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys 0.3.5", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys 0.1.0-beta.1", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys 0.2.1", + "objc2 0.4.1", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[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" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.11.0", + "log", + "polling 3.11.0", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop 0.12.4", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +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 = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[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.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "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", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[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-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "ecolor" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "eframe" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" +dependencies = [ + "bytemuck", + "cocoa", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "static_assertions", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "winit", +] + +[[package]] +name = "egui" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a" +dependencies = [ + "accesskit", + "ahash", + "epaint", + "log", + "nohash-hasher", +] + +[[package]] +name = "egui-wgpu" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37" +dependencies = [ + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609" +dependencies = [ + "accesskit_winit", + "arboard", + "egui", + "log", + "raw-window-handle 0.6.2", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca" +dependencies = [ + "bytemuck", + "egui", + "glow", + "log", + "memoffset 0.9.1", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "epaint" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", +] + +[[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 = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[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 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[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-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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 = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" +dependencies = [ + "bitflags 2.11.0", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "icrate", + "libloading 0.8.9", + "objc2 0.4.1", + "once_cell", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle 0.5.2", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" +dependencies = [ + "gl_generator", + "windows-sys 0.48.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.11.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.11.0", + "gpu-descriptor-types", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[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.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.11.0", + "com", + "libc", + "libloading 0.8.9", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2 0.3.0", + "dispatch", + "objc2 0.4.1", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[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", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[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.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.9", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[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.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[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 = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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 = "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.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[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 = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set", + "bitflags 2.11.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.11.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "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", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[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.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +dependencies = [ + "objc-sys 0.3.5", + "objc2-encode 3.0.0", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode 4.1.0", +] + +[[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.0", + "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.0", + "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.0", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[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.0", + "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.0", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "orbclient" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[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 = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.4+spec-1.1.0", +] + +[[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 = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + +[[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 = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[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 = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "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 = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.18.1", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[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 2.0.117", +] + +[[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 = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-wlr 0.2.0", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.32.11", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr 0.3.11", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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 = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand 2.3.0", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[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 2.0.117", +] + +[[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 2.0.117", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow 0.7.15", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "windows-sys 0.61.2", +] + +[[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.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[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", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +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.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" +dependencies = [ + "bitflags 2.11.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3298683470fbdc6ca40151dfc48c8f2fd4c41a26e13042f801f85002384091" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.11", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "429b99200febaf95d4f4e46deff6fe4382bcff3280ee16a41cf887b3c3364984" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.11", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.11", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle 0.5.2", + "url", + "web-sys", +] + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.11.0", + "cfg_aliases", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.11.0", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.11.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "winit" +version = "0.29.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.0", + "bytemuck", + "calloop 0.12.4", + "cfg_aliases", + "core-foundation", + "core-graphics", + "cursor-icon", + "icrate", + "js-sys", + "libc", + "log", + "memmap2", + "ndk", + "ndk-sys", + "objc2 0.4.1", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "redox_syscall 0.3.5", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.18.1", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +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-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 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "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.0", + "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 = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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 = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.9", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.11.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..940c278 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "afsk" +version = "0.1.0" +edition = "2021" +description = "AFSK audio codec — encode bytes to WAV, decode WAV to bytes" + +[dependencies] +clap = { version = "4", features = ["derive"] } +hound = "3" +eframe = "0.27" diff --git a/README.md b/README.md index 46f2c8f..b07667b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ -# RustMesh -RustMesh is a Rust-based packet radio network that enables internet-style data exchange over ham radio using AX.25 and AFSK. It provides reliable chunked file transfer, message relaying, and API integration for applications like RustChan, with optional multi-hop mesh routing and store-and-forward delivery. +# afsk-codec + +Encode arbitrary bytes into a WAV file using Bell-202-style **Audio Frequency-Shift Keying**, and decode them back — losslessly. + +``` +afsk encode -i data.bin -o signal.wav +afsk decode -i signal.wav -o data.bin +``` + +--- + +## How it works + +``` +[ your bytes ] + │ + FRAMER preamble (24 × 0xAA) | sync (0x7E 0x7E) | length (u32 LE) | payload | CRC-16 + │ + ENCODER each bit → sine wave at 1200 Hz (mark=1) or 2200 Hz (space=0) + continuous-phase FSK at 1200 baud, 44100 Hz / 16-bit mono WAV + │ + [ .wav ] + │ + DECODER non-integer Goertzel filter per bit-window; bit-level sync-word + search (handles any byte offset); CRC verification + │ + FRAMER reconstruct original bytes + │ +[ your bytes ] +``` + +### Signal parameters + +| Parameter | Value | +|----------------|---------------| +| Sample rate | 44 100 Hz | +| Bit rate | 1 200 baud | +| Mark (1) | 1 200 Hz | +| Space (0) | 2 200 Hz | +| Modulation | CPFSK (continuous-phase FSK) | +| WAV format | 16-bit signed PCM, mono | +| Frame overhead | 28 bytes (preamble + sync + length + CRC) | + +--- + +## Building + +Requires Rust 1.75+ (2021 edition). + +```bash +cargo build --release +# binary is at target/release/afsk +``` + +## Running tests + +```bash +cargo test +``` + +12 unit + integration tests cover: +- Framer round-trips (empty, ASCII, all-256-bytes, corrupt CRC) +- CRC-16/CCITT known-value check +- WAV write/read round-trip (silence and sine wave) +- Goertzel mark-vs-space discrimination +- Full encode→decode round-trips (empty, text, all byte values) + +--- + +## Project layout + +``` +src/ + main.rs CLI (clap — encode / decode subcommands) + config.rs Shared constants (sample rate, baud rate, frequencies) + framer.rs Byte envelope: preamble, sync word, length, CRC-16/CCITT + wav.rs WAV file I/O via hound + encoder.rs Bytes → CPFSK audio samples + decoder.rs Audio samples → bytes (Goertzel filter, bit-level sync search) +``` diff --git a/dev-check-strict.sh b/dev-check-strict.sh new file mode 100755 index 0000000..4856127 --- /dev/null +++ b/dev-check-strict.sh @@ -0,0 +1,410 @@ +#!/usr/bin/env bash +# dev-check.sh — full Rust quality gate +# Runs: fmt · fix · clippy (pedantic+nursery) · tests · audit · deny · dupes +# Produces per-file clustered clippy reports in clippy_reports/ + +set -Eeuo pipefail + +# ─── Colours ────────────────────────────────────────────────────────────────── + +if [[ -t 1 ]]; then + RED='\033[0;31m'; YELLOW='\033[0;33m'; GREEN='\033[0;32m' + CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m' +else + RED=''; YELLOW=''; GREEN=''; CYAN=''; BOLD=''; DIM=''; RESET='' +fi + +# ─── Globals ────────────────────────────────────────────────────────────────── + +SCRIPT_START=$SECONDS +PASS_COUNT=0 +FAIL_COUNT=0 +SKIP_COUNT=0 +FAILED_STEPS=() + +REPORT_DIR="clippy_reports" +RAW_FILE="$REPORT_DIR/clippy_raw.txt" +CLUSTER_DIR="$REPORT_DIR/clusters" +SUMMARY_FILE="$REPORT_DIR/summary.txt" + +# ─── Helpers ────────────────────────────────────────────────────────────────── + +command_exists() { command -v "$1" >/dev/null 2>&1; } + +step() { + echo "" + echo -e "${BOLD}${CYAN}════ $1 ════${RESET}" +} + +pass() { + echo -e " ${GREEN}✓${RESET} $1" + (( PASS_COUNT++ )) || true +} + +fail() { + echo -e " ${RED}✗${RESET} $1" + (( FAIL_COUNT++ )) || true + FAILED_STEPS+=("$1") +} + +skip() { + echo -e " ${DIM}–${RESET} $1 ${DIM}(skipped — tool not installed)${RESET}" + (( SKIP_COUNT++ )) || true +} + +warn() { echo -e " ${YELLOW}⚠${RESET} $1"; } + +require_tool() { + local tool="$1" install_hint="$2" + if ! command_exists "$tool"; then + echo -e "${RED}Error:${RESET} required tool '${BOLD}$tool${RESET}' is not installed." + echo -e " Install with: ${DIM}$install_hint${RESET}" + exit 1 + fi +} + +optional_tool() { + local tool="$1" install_hint="$2" + if ! command_exists "$tool"; then + warn "'$tool' not installed — step will be skipped." + warn "Install with: $install_hint" + fi +} + +elapsed() { + local secs=$(( SECONDS - SCRIPT_START )) + printf '%dm%02ds' $(( secs / 60 )) $(( secs % 60 )) +} + +# ─── Header ─────────────────────────────────────────────────────────────────── + +echo -e "${BOLD}" +echo "╔══════════════════════════════════════════════════╗" +echo "║ Rust Full Quality Gate Check ║" +echo "╚══════════════════════════════════════════════════╝" +echo -e "${RESET}" + +# ─── CPU cores ──────────────────────────────────────────────────────────────── + +if command_exists sysctl; then + export CARGO_BUILD_JOBS; CARGO_BUILD_JOBS=$(sysctl -n hw.ncpu 2>/dev/null || echo 4) +elif command_exists nproc; then + export CARGO_BUILD_JOBS; CARGO_BUILD_JOBS=$(nproc) +else + export CARGO_BUILD_JOBS=4 +fi +echo -e " ${DIM}Using ${BOLD}${CARGO_BUILD_JOBS}${RESET}${DIM} CPU cores${RESET}" + +# ─── Required tools ─────────────────────────────────────────────────────────── + +step "Verifying required tools" + +require_tool "cargo" "https://rustup.rs" +require_tool "rustfmt" "rustup component add rustfmt" +require_tool "clippy-driver" "rustup component add clippy" +pass "cargo · rustfmt · clippy all present" + +optional_tool "cargo-audit" "cargo install cargo-audit" +optional_tool "cargo-deny" "cargo install cargo-deny" +optional_tool "cargo-udeps" "cargo install cargo-udeps" +optional_tool "cargo-msrv" "cargo install cargo-msrv" + +# ─── Prepare report directory ───────────────────────────────────────────────── + +rm -rf "$REPORT_DIR" +mkdir -p "$CLUSTER_DIR" + +# ─── Optional: update deps ──────────────────────────────────────────────────── + +if [[ "${1:-}" == "--update" ]]; then + step "Updating dependency index" + if cargo update 2>&1; then pass "cargo update"; else fail "cargo update"; fi +fi + +# ─── 1. Format ──────────────────────────────────────────────────────────────── + +step "1 · Formatting (cargo fmt)" + +if cargo fmt --all 2>&1; then + pass "cargo fmt --all" +else + fail "cargo fmt --all" +fi + +# Verify nothing was left dirty (useful in CI) +if git diff --quiet 2>/dev/null; then + pass "No unstaged format changes" +else + warn "cargo fmt changed files — commit the formatted code" +fi + +# ─── 2. Auto-fix ────────────────────────────────────────────────────────────── + +step "2 · Automatic fixes (cargo fix)" + +if cargo fix --allow-dirty --allow-staged --allow-no-vcs --all-features 2>&1; then + pass "cargo fix" +else + warn "cargo fix had warnings (non-fatal)" +fi + +# ─── 3. Clippy — strict pedantic+nursery ────────────────────────────────────── + +step "3 · Lint (cargo clippy — pedantic + nursery)" + +CLIPPY_FLAGS=( + # Hard errors + "-D" "warnings" + + # Pedantic: correctness, performance, style improvements + "-W" "clippy::pedantic" + + # Nursery: newer lints, some may be noisy — catches subtle bugs early + "-W" "clippy::nursery" + + # Catch common correctness bugs missed by the default set + "-W" "clippy::correctness" + "-W" "clippy::suspicious" + "-W" "clippy::complexity" + "-W" "clippy::perf" + + # Panic/unwrap hygiene — forces explicit error handling + "-W" "clippy::unwrap_used" + "-W" "clippy::expect_used" + "-W" "clippy::panic" + "-W" "clippy::todo" + "-W" "clippy::unimplemented" + "-W" "clippy::unreachable" + + # Index panic risk + "-W" "clippy::indexing_slicing" + + # Integer overflow in casts + "-W" "clippy::cast_possible_truncation" + "-W" "clippy::cast_possible_wrap" + "-W" "clippy::cast_sign_loss" + "-W" "clippy::cast_precision_loss" + + # Arithmetic that can panic + "-W" "clippy::arithmetic_side_effects" + + # Formatting / style discipline + "-W" "clippy::format_collect" + "-W" "clippy::uninlined_format_args" + "-W" "clippy::redundant_closure_for_method_calls" + "-W" "clippy::map_unwrap_or" + "-W" "clippy::manual_let_else" + "-W" "clippy::single_match_else" + "-W" "clippy::if_not_else" + "-W" "clippy::option_if_let_else" + "-W" "clippy::cloned_instead_of_copied" + "-W" "clippy::doc_markdown" + "-W" "clippy::redundant_else" + "-W" "clippy::too_many_lines" + "-W" "clippy::missing_errors_doc" + "-W" "clippy::missing_panics_doc" +) + +CLIPPY_CMD=( + cargo clippy + --all-targets + --all-features + -- + "${CLIPPY_FLAGS[@]}" +) + +echo -e " ${DIM}Running: ${CLIPPY_CMD[*]}${RESET}" +echo "" + +CLIPPY_EXIT=0 +"${CLIPPY_CMD[@]}" 2>&1 | tee "$RAW_FILE" || CLIPPY_EXIT=$? + +# ── Cluster clippy output by source file ───────────────────────────────────── + +echo "" +echo -e " ${DIM}Clustering clippy output by file...${RESET}" + +OUTFILE="" +while IFS= read -r line; do + if [[ $line =~ ([a-zA-Z0-9_/.-]+\.rs):[0-9]+:[0-9]+ ]]; then + FILE="${BASH_REMATCH[1]}" + DIR=$(dirname "$FILE") + if [[ "$DIR" == "." ]]; then + CLUSTER="root" + else + CLUSTER=$(echo "$DIR" | tr '/' '_') + fi + OUTFILE="$CLUSTER_DIR/${CLUSTER}.txt" + { + echo "" + echo "----------------------------------------" + echo "FILE: $FILE" + echo "----------------------------------------" + } >> "$OUTFILE" + fi + if [[ -n "$OUTFILE" ]]; then + echo "$line" >> "$OUTFILE" + fi +done < "$RAW_FILE" + +# ── Count errors and warnings ───────────────────────────────────────────────── + +CLIPPY_ERRORS=$(grep -c '^error' "$RAW_FILE" 2>/dev/null || echo 0) +CLIPPY_WARNS=$(grep -c '^warning' "$RAW_FILE" 2>/dev/null || echo 0) +CLUSTER_COUNT=$(find "$CLUSTER_DIR" -name '*.txt' | wc -l | tr -d ' ') + +if [[ $CLIPPY_EXIT -eq 0 ]]; then + pass "clippy clean (${CLIPPY_WARNS} warnings, 0 errors)" +else + fail "clippy reported ${CLIPPY_ERRORS} error(s) across ${CLUSTER_COUNT} file cluster(s)" + echo "" + echo -e " ${BOLD}Cluster reports:${RESET}" + for f in "$CLUSTER_DIR"/*.txt; do + [[ -f "$f" ]] && echo -e " ${DIM}$(basename "$f")${RESET}" + done + echo -e " ${DIM}Full output: $RAW_FILE${RESET}" +fi + +# ─── 4. Tests ───────────────────────────────────────────────────────────────── + +step "4 · Tests (cargo test)" + +TEST_EXIT=0 +cargo test --all --all-features 2>&1 || TEST_EXIT=$? + +if [[ $TEST_EXIT -eq 0 ]]; then + PASSED=$(grep -oP '\d+(?= passed)' <<< "$(cargo test --all --all-features 2>&1)" | tail -1 || echo "?") + pass "All tests passed" +else + fail "Test suite failed (exit $TEST_EXIT)" +fi + +# ─── 5. Security audit ──────────────────────────────────────────────────────── + +step "5 · Security audit (cargo audit)" + +if command_exists cargo-audit; then + AUDIT_EXIT=0 + cargo audit 2>&1 || AUDIT_EXIT=$? + if [[ $AUDIT_EXIT -eq 0 ]]; then + pass "No known vulnerabilities" + else + fail "cargo-audit found vulnerability/advisory — review output above" + fi +else + skip "cargo-audit → cargo install cargo-audit" +fi + +# ─── 6. Dependency policy ───────────────────────────────────────────────────── + +step "6 · Dependency policy (cargo deny)" + +if command_exists cargo-deny; then + DENY_EXIT=0 + cargo deny check 2>&1 || DENY_EXIT=$? + if [[ $DENY_EXIT -eq 0 ]]; then + pass "cargo deny — all policies satisfied" + else + fail "cargo deny — policy violation(s) found" + fi +else + skip "cargo-deny → cargo install cargo-deny" +fi + +# ─── 7. Unused dependencies ─────────────────────────────────────────────────── + +step "7 · Unused dependencies (cargo udeps)" + +if command_exists cargo-udeps; then + UDEPS_EXIT=0 + cargo +nightly udeps --all-targets 2>&1 || UDEPS_EXIT=$? + if [[ $UDEPS_EXIT -eq 0 ]]; then + pass "No unused dependencies" + else + fail "Unused dependencies detected — remove them from Cargo.toml" + fi +else + skip "cargo-udeps → cargo install cargo-udeps (requires nightly)" +fi + +# ─── 8. MSRV check ──────────────────────────────────────────────────────────── + +step "8 · Minimum supported Rust version (cargo msrv)" + +if command_exists cargo-msrv; then + MSRV_EXIT=0 + cargo msrv verify 2>&1 || MSRV_EXIT=$? + if [[ $MSRV_EXIT -eq 0 ]]; then + pass "MSRV satisfied" + else + warn "MSRV check failed — your rust-version in Cargo.toml may need updating" + fi +else + skip "cargo-msrv → cargo install cargo-msrv" +fi + +# ─── 9. Duplicate dependencies ──────────────────────────────────────────────── + +step "9 · Duplicate dependencies (cargo tree -d)" + +DUPES=$(cargo tree -d 2>&1 || true) +if echo "$DUPES" | grep -q '\['; then + warn "Duplicate crate versions detected:" + echo "$DUPES" | grep '^\[' | sort -u | while read -r line; do + echo -e " ${YELLOW}$line${RESET}" + done +else + pass "No duplicate crate versions" +fi + +# ─── 10. Build check (release) ──────────────────────────────────────────────── + +step "10 · Release build check (cargo build --release)" + +BUILD_EXIT=0 +cargo build --release --all-features 2>&1 || BUILD_EXIT=$? +if [[ $BUILD_EXIT -eq 0 ]]; then + pass "Release build clean" +else + fail "Release build failed" +fi + +# ─── Summary ────────────────────────────────────────────────────────────────── + +TOTAL_SECS=$(( SECONDS - SCRIPT_START )) + +{ + echo "dev-check summary — $(date)" + echo "Duration: ${TOTAL_SECS}s" + echo "Passed: $PASS_COUNT" + echo "Failed: $FAIL_COUNT" + echo "Skipped: $SKIP_COUNT" + if [[ ${#FAILED_STEPS[@]} -gt 0 ]]; then + echo "" + echo "Failed steps:" + for s in "${FAILED_STEPS[@]}"; do echo " - $s"; done + fi +} | tee "$SUMMARY_FILE" + +echo "" +if [[ $FAIL_COUNT -eq 0 ]]; then + echo -e "${BOLD}${GREEN}" + echo "╔══════════════════════════════════════════════════╗" + echo "║ ✓ All checks passed ($(elapsed())) ║" + echo "╚══════════════════════════════════════════════════╝" + echo -e "${RESET}" + exit 0 +else + echo -e "${BOLD}${RED}" + echo "╔══════════════════════════════════════════════════╗" + echo "║ ✗ $FAIL_COUNT check(s) failed ($(elapsed())) ║" + echo "╚══════════════════════════════════════════════════╝" + echo -e "${RESET}" + echo -e "${RED}Failed steps:${RESET}" + for s in "${FAILED_STEPS[@]}"; do + echo -e " ${RED}•${RESET} $s" + done + echo "" + echo -e " ${DIM}Reports saved to: $REPORT_DIR/${RESET}" + exit 1 +fi diff --git a/docs/Dev1.docx b/docs/Dev1.docx new file mode 100644 index 0000000000000000000000000000000000000000..a4380e97206e9b351a075a2ad11547dd543c0e57 GIT binary patch literal 26084 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

%WGS3eR6dYUckjqy)%l(gz6h|GMEP5jTC% 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 V2tNYRJ(znip^)Fi^)EmCzX0T~OPBxv literal 0 HcmV?d00001 diff --git a/docs/Dev2.docx b/docs/Dev2.docx new file mode 100644 index 0000000000000000000000000000000000000000..b043ff0910ba3df58d0e95bf443c63e95c5962c7 GIT binary patch literal 34200 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 fIgfvhF=8Vcl(g9sM{7bFDu?HWh`0`mU>Wm{I< literal 0 HcmV?d00001 diff --git a/docs/hamnet-relay-build-roadmap.md b/docs/hamnet-relay-build-roadmap.md new file mode 100644 index 0000000..25fee96 --- /dev/null +++ b/docs/hamnet-relay-build-roadmap.md @@ -0,0 +1,693 @@ +# HAMNET-RELAY — Architecture Build & Testing Roadmap +> Single-binary HAM radio data relay middleware · Rust · AX.25 / AFSK · RustChan integration +> Progression: simple serial loopback → full RustChan real-time data streaming + +--- + +## Overview: Build Phase Map + +```mermaid +flowchart TD + P1["🔌 PHASE 1\nFoundation &\nHardware Probing"] + P2["🎙️ PHASE 2\nAudio Path\nMic/Speaker Mode\n⚡ No PTT Cable Required"] + P3["📦 PHASE 3\nAX.25 Packet\nLayer"] + P4["🗜️ PHASE 4\nIdentity, Codec\n& Compression"] + P5["📡 PHASE 5\nCSMA & TX\nQueue"] + P6["🌐 PHASE 6\nLocal HTTP/WS\nAPI"] + P7["📻 PHASE 7\nRadio Programming\n& Frequency Sync"] + P8["🖼️ PHASE 8\nImage & File\nTransfer"] + P9["🦀 PHASE 9\nRustChan\nIntegration"] + P10["⚡ PHASE 10\nRustChan Real-Time\nData Streaming"] + P11["🕸️ PHASE 11\nMesh Network\n& Digipeater"] + + P1 --> P2 --> P3 --> P4 --> P5 --> P6 --> P7 --> P8 --> P9 --> P10 --> P11 +``` + +--- + +## Phase 1 — Foundation & Hardware Probing + +```mermaid +flowchart TD + START([▶ Project Init]) --> B01 + + subgraph B01["BUILD-01 · Serial Port Detection"] + B01A[Detect CH340/CP2102 USB chip\nvia serialport crate] --> B01B[List available /dev/ttyUSB* ports] + B01B --> B01C{Port found?} + B01C -- Yes --> B01D[✅ TEST PASS: Log port name & baud rate] + B01C -- No --> B01E[⚠️ TEST FAIL: Print 'No radio detected'\nwith install hint] + end + + B01D --> B02 + + subgraph B02["BUILD-02 · Audio Device Enumeration"] + B02A[Use cpal to list all input/output devices] --> B02B[Print device names, sample rates, channels] + B02B --> B02C{Default in/out found?} + B02C -- Yes --> B02D[✅ TEST PASS: Audio subsystem ready] + B02C -- No --> B02E[⚠️ TEST FAIL: Prompt user to check\naudio settings] + end + + B02D --> B03 + + subgraph B03["BUILD-03 · AFSK Tone Generator"] + B03A[Generate 1200 Hz sine wave\n'mark' tone at 44.1kHz sample rate] --> B03B[Generate 2200 Hz sine wave\n'space' tone] + B03B --> B03C[Encode bit stream to tone sequence\nBell 202 standard] + B03C --> B03D[Write raw audio to .wav file] + B03D --> B03E[✅ TEST PASS: Play .wav — tones audible\nand distinct on oscilloscope/spectrum] + end + + B03E --> B04 + + subgraph B04["BUILD-04 · AFSK Demodulator"] + B04A[Read .wav tone file from B03] --> B04B[Apply bandpass filters:\n1200Hz ±100Hz, 2200Hz ±100Hz] + B04B --> B04C[Energy envelope detection\nper filter band] + B04C --> B04D[Threshold compare → bit stream] + B04D --> B04E[✅ TEST PASS: Decoded bits match\noriginal input from B03] + end + + B04E --> PHASE2([▶ Phase 2]) +``` + +--- + +## Phase 2 — Audio Path · Mic/Speaker Mode (No PTT Cable Required) + +> **🎙️ This phase enables the app to work with ZERO hardware beyond the radio itself.** +> The computer's microphone listens to the radio speaker. The computer's speakers (or headphone jack into the radio's mic socket) transmit audio. +> This lets anyone set a radio on a desk, tune it to a frequency, and have the computer receive and decode all traffic passively — or key-up manually via VOX. + +```mermaid +flowchart TD + PHASE2([▶ Phase 2 Entry]) --> B05 + + subgraph B05["BUILD-05 · Microphone Capture Input Stream"] + B05A[Open system default mic via cpal\nInput stream, 44.1kHz mono] --> B05B[Capture ring buffer — 512 sample chunks] + B05B --> B05C[Pipe buffer → AFSK demodulator from B04] + B05C --> B05D[✅ TEST PASS: Speak 'dit dah' into mic —\nbits appear in stdout] + end + + B05D --> B06 + + subgraph B06["BUILD-06 · Speaker Output Stream"] + B06A[Generate AFSK audio for test payload] --> B06B[Open system default output via cpal\nOutput stream, 44.1kHz mono] + B06B --> B06C[Play tones through speakers\nor 3.5mm headphone-to-radio-mic cable] + B06C --> B06D[✅ TEST PASS: Another SDR or radio\ndecodes the AFSK tones correctly] + end + + B06D --> B07 + + subgraph B07["BUILD-07 · VOX Level Detection\n(Auto Carrier Sense via Mic)"] + B07A[Monitor mic input RMS level\nin sliding 50ms window] --> B07B{RMS above squelch\nthreshold?} + B07B -- Yes --> B07C[Signal ACTIVE — radio is transmitting\nBlock our own TX] + B07B -- No --> B07D[Channel CLEAR — safe to transmit] + B07C --> B07E[✅ TEST: Play audio from one speaker,\nconfirm detection fires correctly] + B07D --> B07E + end + + B07E --> B08 + + subgraph B08["BUILD-08 · Full Mic/Speaker Loopback End-to-End\n🔑 KEY MILESTONE — Radio on Desk Mode"] + B08A["🎙️ Radio set to receive frequency\nComputer mic pointed at radio speaker"] --> B08B[Radio receives over-air packet\nfrom another station] + B08B --> B08C[Mic captures audio from radio speaker] + B08C --> B08D[cpal stream → AFSK demod → bit decode] + B08D --> B08E[Print decoded text to stdout] + B08E --> B08F["✅ TEST PASS: Any AX.25/APRS packet\non the frequency is decoded in terminal\n⭐ USER CAN LISTEN TO RADIO WITHOUT ANY CABLE"] + B08F --> B08G["📤 TX PATH: Encode payload → AFSK tones\n→ play through speakers\n→ radio mic jack picks up audio\n→ (user manually keys PTT or radio uses VOX)"] + B08G --> B08H["✅ TEST PASS: Remote station receives\nand decodes our transmission\n⭐ FULLY WIRELESS SETUP — NO PTT CABLE NEEDED"] + end + + B08H --> B09_NOTE["📝 NOTE: PTT cable support added in Phase 3\nas an optional enhancement. Mic/Speaker\npath remains supported throughout all builds."] + B09_NOTE --> PHASE3([▶ Phase 3]) +``` + +--- + +## Phase 3 — AX.25 Packet Layer + +```mermaid +flowchart TD + PHASE3([▶ Phase 3 Entry]) --> B09 + + subgraph B09["BUILD-09 · AX.25 Frame Assembler"] + B09A[Define AX.25 frame struct:\nsrc_callsign, dst_callsign, payload, flags] --> B09B[Implement bit-stuffing per AX.25 spec] + B09B --> B09C[Compute CRC-16-CCITT\nFCS field] + B09C --> B09D[Serialize frame to byte Vec] + B09D --> B09E[✅ TEST: Feed output to\nDirewolf decode — frame accepted] + end + + B09E --> B10 + + subgraph B10["BUILD-10 · PTT Control via RTS/DTR Pin\n(Optional Hardware Path)"] + B10A["🔌 Detect if PTT cable is connected\n(CHIRP-compatible USB cable)"] --> B10B{Cable detected?} + B10B -- Yes --> B10C[Assert RTS high → PTT engaged\nAssert RTS low → PTT released] + B10B -- No --> B10D["⚙️ Fall back to Mic/Speaker path\n(Phase 2 — no cable needed)"] + B10C --> B10E[✅ TEST: PTT LED on radio\nflashes on RTS toggle] + B10D --> B10E + end + + B10E --> B11 + + subgraph B11["BUILD-11 · Packet TX Pipeline"] + B11A[Payload string input] --> B11B[AX.25 frame assemble\nwith source callsign] + B11B --> B11C[AFSK modulate → audio buffer] + B11C --> B11D{PTT cable available?} + B11D -- Yes --> B11E[Assert RTS → play audio → release RTS] + B11D -- No --> B11F[Play audio via speaker\nUser or radio VOX keys PTT] + B11E --> B11G[✅ TEST: Remote station\ndecodes 'HELLO WORLD'] + B11F --> B11G + end + + B11G --> B12 + + subgraph B12["BUILD-12 · Packet RX Pipeline"] + B12A{Input source?} --> B12B["🎙️ Mic stream (Phase 2 path)"] + B12A --> B12C["🔌 Radio audio jack\nvia USB sound card"] + B12B --> B12D[AFSK demodulate → bit stream] + B12C --> B12D + B12D --> B12E[AX.25 frame disassemble] + B12E --> B12F[CRC validate] + B12F --> B12G{CRC pass?} + B12G -- Yes --> B12H[Extract callsign + payload\nPrint to stdout] + B12G -- No --> B12I[Discard + log CRC error] + B12H --> B12J[✅ TEST: Send from APRS client,\nreceive here with correct callsign] + end + + B12J --> B13 + + subgraph B13["BUILD-13 · Duplex Test — Full TX/RX Loop"] + B13A[Station A sends 'PING seq=1'] --> B13B[Station B receives + decodes] + B13B --> B13C[Station B sends 'PONG seq=1'] + B13C --> B13D[Station A receives + decodes] + B13D --> B13E[✅ TEST PASS: Round-trip packet\nconfirmed both directions] + end + + B13E --> PHASE4([▶ Phase 4]) +``` + +--- + +## Phase 4 — Identity, Codec & Compression + +```mermaid +flowchart TD + PHASE4([▶ Phase 4 Entry]) --> B14 + + subgraph B14["BUILD-14 · Client Identity System"] + B14A[Check ~/.config/hamnet-relay/config.toml\nfor existing identity seed] --> B14B{Seed exists?} + B14B -- No --> B14C[Generate 256-bit random seed\nWrite to config.toml] + B14B -- Yes --> B14D[Load seed from config] + B14C --> B14E[Compute Blake3 hash of seed\n= identity hash] + B14D --> B14E + B14E --> B14F[Truncate to first 8 bytes\nfor packet headers] + B14F --> B14G[✅ TEST: Re-run app — same\nhash reproduced from saved seed] + end + + B14G --> B15 + + subgraph B15["BUILD-15 · MessagePack Serialization"] + B15A[Define PostPayload struct:\nthread_id, board_id, text, image_opt,\nseq_num, identity_hash, callsign] --> B15B[Serialize via rmp-serde → bytes] + B15B --> B15C[Deserialize bytes back → struct] + B15C --> B15D[✅ TEST: Round-trip serialize/deserialize\nall fields match] + end + + B15D --> B16 + + subgraph B16["BUILD-16 · zstd Compression"] + B16A[Take serialized MessagePack bytes] --> B16B[zstd::encode_all\ncompression level 3] + B16B --> B16C[Log original vs compressed size] + B16C --> B16D[zstd::decode_all → decompress] + B16D --> B16E[✅ TEST: 1KB text payload\ncompresses to <200 bytes] + end + + B16E --> B17 + + subgraph B17["BUILD-17 · Full Payload Codec Pipeline"] + B17A[PostPayload struct] --> B17B[MessagePack serialize] + B17B --> B17C[zstd compress] + B17C --> B17D[Prepend header:\nidentity hash 8B + seq_num 4B + checksum 4B] + B17D --> B17E[AX.25 frame wrap] + B17E --> B17F[AFSK modulate] + B17F --> B17G[TX via speaker or PTT cable] + B17G --> B17H["✅ TEST: Remote station receives\npost text with callsign intact\nAll fields decoded correctly"] + end + + B17H --> PHASE5([▶ Phase 5]) +``` + +--- + +## Phase 5 — CSMA & TX Queue + +```mermaid +flowchart TD + PHASE5([▶ Phase 5 Entry]) --> B18 + + subgraph B18["BUILD-18 · Channel Carrier Sense"] + B18A["Monitor mic/audio input RMS\nin 50ms sliding window"] --> B18B{RMS > squelch\nthreshold?} + B18B -- Busy --> B18C[Channel BUSY — set busy flag] + B18B -- Clear --> B18D[Channel CLEAR — clear busy flag] + B18C --> B18E[✅ TEST: Play radio audio nearby\n— busy flag fires within 100ms] + B18D --> B18E + end + + B18E --> B19 + + subgraph B19["BUILD-19 · CSMA Listen-Before-Transmit"] + B19A[TX request arrives] --> B19B[Check channel busy flag] + B19B --> B19C{Channel clear\nfor 500ms?} + B19C -- No --> B19D[Back off: wait random\n50–500ms then retry] + B19D --> B19B + B19C -- Yes --> B19E[Proceed with transmission] + B19E --> B19F[✅ TEST: Two simulated stations\nno collision observed over 50 sends] + end + + B19F --> B20 + + subgraph B20["BUILD-20 · Priority TX Queue"] + B20A[Define priority levels:\nHIGH=beacon/ACK · MED=post · LOW=image chunk] --> B20B[Async mpsc channel-based queue\nper priority level] + B20B --> B20C[TX worker pops highest-priority item\nafter CSMA clears] + B20C --> B20D[Log queue depth + estimated TX time] + B20D --> B20E[✅ TEST: Flood queue with 10 LOW items,\ninsert 1 HIGH — HIGH transmits first] + end + + B20E --> PHASE6([▶ Phase 6]) +``` + +--- + +## Phase 6 — Local HTTP/WebSocket API + +```mermaid +flowchart TD + PHASE6([▶ Phase 6 Entry]) --> B21 + + subgraph B21["BUILD-21 · Axum HTTP Server"] + B21A[Spawn Axum server on 127.0.0.1:7373] --> B21B[Bind shared state:\ntx_queue, rx_buffer, radio_status, config] + B21B --> B21C[✅ TEST: curl localhost:7373/health\nreturns 200 OK] + end + + B21C --> B22 + + subgraph B22["BUILD-22 · REST Endpoints"] + B22A["GET /api/v1/status\n→ radio state, freq, queue depth, last RX"] --> B22E + B22B["POST /api/v1/transmit\n→ queue payload, return queue position"] --> B22E + B22C["GET /api/v1/queue\n→ pending outbound items"] --> B22E + B22D["POST /api/v1/queue/id/cancel\n→ cancel queued item"] --> B22E + B22E["GET /api/v1/peers\n→ known identity hashes + timestamps"] --> B22F + B22F["GET /api/v1/received\n→ paginated RX packet log + since= filter"] --> B22G + B22G["GET /api/v1/files\n→ received file list"] --> B22H + B22H["GET+POST /api/v1/config\n→ read/write runtime config"] + B22H --> B22I[✅ TEST: All endpoints return correct\nJSON schema matching spec] + end + + B22I --> B23 + + subgraph B23["BUILD-23 · WebSocket /subscribe Endpoint"] + B23A[Client connects to WS /api/v1/subscribe] --> B23B[Server holds open connection] + B23B --> B23C[On inbound packet: push PACKET_RECEIVED event] + B23C --> B23D[On TX complete: push TX_COMPLETE event] + B23D --> B23E[On peer heard: push PEER_SEEN event] + B23E --> B23F[✅ TEST: wscat client receives\nreal-time events on packet receive] + end + + B23F --> B24 + + subgraph B24["BUILD-24 · TOML Config System"] + B24A["~/.config/hamnet-relay/config.toml\nmode, callsign, frequency, quality_cap\napi_port, squelch_threshold, band_region"] --> B24B[serde + toml deserialize on startup] + B24B --> B24C[Runtime overrides via CLI flags] + B24C --> B24D[POST /api/v1/config persists changes\nto disk] + B24D --> B24E[✅ TEST: Change quality_cap via API,\nrestart — setting persists] + end + + B24E --> PHASE7([▶ Phase 7]) +``` + +--- + +## Phase 7 — Radio Programming & Frequency Sync + +```mermaid +flowchart TD + PHASE7([▶ Phase 7 Entry]) --> B25 + + subgraph B25["BUILD-25 · CHIRP Protocol / Baofeng Programming"] + B25A[Detect radio on serial port] --> B25B[Send CHIRP init sequence\nvia serialport crate] + B25B --> B25C[Write channel entry:\nfreq_mhz, offset_mhz, ctcss, power] + B25C --> B25D[Confirm write with readback] + B25D --> B25E[✅ TEST: Radio displays programmed\nfrequency after sequence completes] + end + + B25E --> B26 + + subgraph B26["BUILD-26 · Band Plan Validation"] + B26A[Embed ITU Region 1/2/3 +\nFCC Part 97 sub-band lookup table] --> B26B[On config/programming: check freq\nagainst region table] + B26B --> B26C{Freq in valid\nham band?} + B26C -- Yes --> B26D[✅ Proceed] + B26C -- No --> B26E[⚠️ Warn user — frequency is out-of-band\nfor configured region. Not blocked, advisory only.] + end + + B26D --> B27 + B26E --> B27 + + subgraph B27["BUILD-27 · Server Beacon TX"] + B27A[Server starts → programs radio] --> B27B[Every 10 min: assemble BEACON frame] + B27B --> B27C["Beacon payload: freq, mode, baud,\nserver identity hash, callsign (REQUIRED by FCC)"] + B27C --> B27D[Push beacon to HIGH priority\nTX queue] + B27D --> B27E[✅ TEST: Beacon received and decoded\nby test station every 10 min] + end + + B27E --> B28 + + subgraph B28["BUILD-28 · Client Beacon RX & Sync"] + B28A[Client listens on default/config freq\nfor BEACON frame type] --> B28B[Receive and decode beacon] + B28B --> B28C[Extract server freq params] + B28C --> B28D[Program own radio to match\nvia CHIRP sequence] + B28D --> B28E[Transmit SYNC_ACK frame\nwith client identity hash + callsign] + B28E --> B28F[✅ TEST: Client auto-tunes radio\nand sends ACK within 30s of beacon] + end + + B28F --> B29 + + subgraph B29["BUILD-29 · Full Sync Handshake & Peer Registry"] + B29A[Server receives SYNC_ACK] --> B29B[Register client:\nhash → last_seen, callsign, freq_confirmed] + B29B --> B29C[Server sends SYNC_CONFIRM\nwith session params] + B29C --> B29D[Data exchange authorized] + B29D --> B29E[✅ TEST: /api/v1/peers returns\nclient entry after handshake completes] + end + + B29E --> PHASE8([▶ Phase 8]) +``` + +--- + +## Phase 8 — Image & File Transfer + +```mermaid +flowchart TD + PHASE8([▶ Phase 8 Entry]) --> B30 + + subgraph B30["BUILD-30 · Image Quality Tier System"] + B30A[Input: raw image file] --> B30B[Read quality tier from config/request:\nTIER 1 / 2 / 3] + B30B --> B30C{Tier} + B30C -- TIER 1 --> B30D["64×64px · JPEG 30%\n~2KB · ~15s TX"] + B30C -- TIER 2 --> B30E["128×128px · JPEG 60%\n~8KB · ~55s TX"] + B30C -- TIER 3 --> B30F["256×256px · JPEG 85%\n~25KB · ~3min TX"] + B30D --> B30G[Encode via image crate → JPEG bytes] + B30E --> B30G + B30F --> B30G + B30G --> B30H[zstd compress JPEG bytes] + B30H --> B30I[✅ TEST: Output size within expected\nbandwidth budget for tier] + end + + B30I --> B31 + + subgraph B31["BUILD-31 · Image TX Over Radio"] + B31A[Compressed image bytes] --> B31B[Split into 256-byte chunks] + B31B --> B31C[Each chunk gets seq_num + total_chunks\n+ crc32 checksum] + B31C --> B31D[AX.25 wrap each chunk] + B31D --> B31E[Queue all chunks in TX queue] + B31E --> B31F[Transmit with CSMA between each chunk] + B31F --> B31G[✅ TEST TIER 1: 64×64 image received\nand reconstructed within 20s] + end + + B31G --> B32 + + subgraph B32["BUILD-32 · Chunked File Transfer Protocol"] + B32A[Any file input → zstd compress] --> B32B[Split into 256-byte chunks\nwith chunk_index + total_chunks] + B32B --> B32C[Transmit FILE_HEADER frame first:\nfilename, total_size, total_chunks, hash] + B32C --> B32D[Transmit DATA chunks sequentially] + B32D --> B32E[Transmit FILE_EOF frame] + B32E --> B32F[Receiver reassembles chunks\nto output directory] + B32F --> B32G[✅ TEST: 10KB text file transferred,\nreassembled, hash matches] + end + + B32G --> B33 + + subgraph B33["BUILD-33 · ACK/NACK Retransmit"] + B33A[Receiver sends ACK per chunk\nor NACK with missing seq_nums] --> B33B{NACK received?} + B33B -- Yes --> B33C[Re-queue missing chunks\nat HIGH priority] + B33C --> B33A + B33B -- No → All ACKd --> B33D[Transfer complete] + B33D --> B33E[✅ TEST: Simulate 20% packet loss —\nall chunks eventually retransmitted] + end + + B33E --> B34 + + subgraph B34["BUILD-34 · File Reassembly & Storage"] + B34A[All chunks received for a transfer] --> B34B[Decompress zstd → original bytes] + B34B --> B34C[Verify Blake3/SHA3 hash\nagainst FILE_HEADER] + B34C --> B34D{Hash valid?} + B34D -- Yes --> B34E[Write to ~/hamnet-relay/received/\nwith source callsign prefix] + B34D -- No --> B34F[Discard + send NACK for all chunks\nRequest full retransmit] + B34E --> B34G[Emit PACKET_RECEIVED event\nvia WebSocket to subscribers] + B34G --> B34H[✅ TEST: File on disk matches\noriginal byte-for-byte] + end + + B34H --> PHASE9([▶ Phase 9]) +``` + +--- + +## Phase 9 — RustChan Integration + +```mermaid +flowchart TD + PHASE9([▶ Phase 9 Entry]) --> B35 + + subgraph B35["BUILD-35 · RustChan Incoming Webhook\n[RustChan: NEW endpoint]"] + B35A["RustChan adds:\nPOST /api/hamnet/incoming"] --> B35B["Accepts: { text, thread_id, board_id,\ncallsign, identity_hash, image_b64_opt }"] + B35B --> B35C[Authenticates via shared local secret\nenv var HAMNET_SECRET] + B35C --> B35D[Creates post in database\nas if submitted normally] + B35D --> B35E[✅ TEST: curl POST to endpoint\npost appears on board with correct content] + end + + B35E --> B36 + + subgraph B36["BUILD-36 · HAM Source Badge on Posts\n[RustChan: NEW DB fields + UI]"] + B36A["Add nullable columns to posts table:\nham_source_hash TEXT\nham_callsign TEXT"] --> B36B["UI: render 📻 via HAM badge\non posts where ham_callsign IS NOT NULL"] + B36B --> B36C[Show truncated identity hash\n+ callsign in badge tooltip] + B36C --> B36D[✅ TEST: HAM-received post\nshows badge; normal post does not] + end + + B36D --> B37 + + subgraph B37["BUILD-37 · Thread Watch / Subscription API\n[RustChan: NEW endpoint]"] + B37A["RustChan adds:\nGET /api/threads/{id}/updates?since={ts}"] --> B37B[Returns new posts for thread\nsince given timestamp or post_id] + B37B --> B37C[HAMNET-RELAY polls this endpoint\non configured interval per watched thread] + B37C --> B37D[New posts queued for radio TX\nat appropriate quality tier] + B37D --> B37E[✅ TEST: Post to watched thread —\nHAMNET-RELAY logs 'queued for TX' within poll interval] + end + + B37E --> B38 + + subgraph B38["BUILD-38 · Outbound Hook on Post Submit\n[RustChan: MODIFY submit handler]"] + B38A[User submits post to HAM-enabled board] --> B38B{Board is\nHAM-enabled?} + B38B -- Yes --> B38C["Async fire-and-forget:\nPOST /api/v1/transmit to HAMNET-RELAY\nDo NOT block post submission on this"] + B38B -- No --> B38D[Normal post submit only] + B38C --> B38E[✅ TEST: Submit post to HAM board\n→ TX queue gains new item immediately] + B38D --> B38E + end + + B38E --> B39 + + subgraph B39["BUILD-39 · RustChan Admin Config Panel\n[RustChan: MODIFY admin UI]"] + B39A["Add [hamnet] section to RustChan config:\nrelay_url, api_key, enabled_boards[]\nauto_push, auto_pull, pull_interval_secs"] --> B39B[Admin panel UI section:\ntoggle HAM per board, set relay URL/key] + B39B --> B39C[Config stored in TOML or admin DB table] + B39C --> B39D[✅ TEST: Disable HAM on board —\nTX hook no longer fires for that board] + end + + B39D --> B40 + + subgraph B40["BUILD-40 · Callsign / Identity Association\n[RustChan: NEW user settings field]"] + B40A[User settings page: optional FCC callsign field] --> B40B[Callsign stored per account\nor per-session field at TX time] + B40B --> B40C[Included in outbound HAMNET packet\ncallsign field of AX.25 frame] + B40C --> B40D[✅ TEST: Submit post with callsign set —\nreceiving station sees correct callsign in packet] + end + + B40D --> PHASE10([▶ Phase 10]) +``` + +--- + +## Phase 10 — RustChan Real-Time Data Streaming + +```mermaid +flowchart TD + PHASE10([▶ Phase 10 Entry]) --> B41 + + subgraph B41["BUILD-41 · WebSocket Push: HAMNET-RELAY → RustChan"] + B41A[RustChan connects to\nWS /api/v1/subscribe on HAMNET-RELAY] --> B41B[HAMNET-RELAY pushes PACKET_RECEIVED\nevents to RustChan in real-time] + B41B --> B41C[RustChan handler creates post\non every inbound packet event] + B41C --> B41D[✅ TEST: Transmit from remote station —\npost appears on RustChan board\nwithin seconds of receipt] + end + + B41D --> B42 + + subgraph B42["BUILD-42 · Streaming Post Flow — Full Pipeline"] + direction LR + B42A[Remote HAM station\nwith hamnet-relay client] -->|"AFSK over RF"| B42B[Server radio\nreceives signal] + B42B -->|"mic/speaker or\naudio jack"| B42C[hamnet-relay\nAFSK demod + AX.25 unpack] + B42C -->|"WS PACKET_RECEIVED event"| B42D[RustChan\nWebSocket handler] + B42D -->|"POST /api/hamnet/incoming"| B42E[Post created in DB\nwith 📻 via HAM badge] + B42E -->|"SSE/WS push to browser"| B42F[RustChan board\nupdates in real-time for\nall connected users] + B42F --> B42G[✅ END-TO-END TEST:\nPost sent by remote HAM operator\nappears on board within 10s] + end + + B42G --> B43 + + subgraph B43["BUILD-43 · Image Pipeline Quality-Tier Awareness\n[RustChan: MODIFY image handler]"] + B43A[RustChan exposes image dimensions\n+ original file size in API response] --> B43B[HAMNET-RELAY reads these metadata fields] + B43B --> B43C[Selects quality tier based on:\nfile size, server quality_cap config] + B43C --> B43D[Pre-scales if over tier limit\nor passes to relay's codec] + B43D --> B43E[Option: Store ham_thumbnail variant\nin RustChan DB] + B43E --> B43F[✅ TEST: Large image auto-downscaled\nto TIER 1 before TX when server cap = 1] + end + + B43F --> B44 + + subgraph B44["BUILD-44 · Radio Status Widget in RustChan\n[RustChan: OPTIONAL admin UI widget]"] + B44A[RustChan admin page polls\nGET /api/v1/status every 5s] --> B44B["Display live:\n● Radio connected/disconnected\n● Current frequency\n● TX queue depth\n● Last RX timestamp\n● Known peer count"] + B44B --> B44C[✅ TEST: Disconnect radio USB —\nstatus widget shows 'DISCONNECTED' within 10s] + end + + B44C --> B45 + + subgraph B45["BUILD-45 · Offline / Radio-Only Mode\n[RustChan: OPTIONAL session flag]"] + B45A[Session or user-level flag:\nradio_only_mode = true] --> B45B[UI renders only HAM-received content] + B45B --> B45C[Images shown at received resolution\nwith 'radio quality' disclaimer] + B45C --> B45D[Post timestamps reflect radio-received time\nnot server wall-clock] + B45D --> B45E[✅ TEST: Enable flag — only\nham_callsign posts visible on board] + end + + B45E --> PHASE11([▶ Phase 11]) +``` + +--- + +## Phase 11 — Mesh Network & Digipeater Mode + +> ⚠️ **Complexity Warning:** Build to maturity at Phase 10 before starting Phase 11. +> Mesh adds loop prevention, routing convergence, and beacon bandwidth concerns. + +```mermaid +flowchart TD + PHASE11([▶ Phase 11 Entry]) --> B46 + + subgraph B46["BUILD-46 · Digipeater / Relay Mode\n(--mode=relay)"] + B46A[Node launched with --mode=relay] --> B46B[Listen for all AX.25 packets on frequency] + B46B --> B46C{Packet addressed\nto WIDE1-1 or\nlocal relay callsign?} + B46C -- Yes --> B46D[Decrement hop count in header] + B46D --> B46E{Hops > 0?} + B46E -- Yes --> B46F[Re-transmit packet\nwith own callsign appended\nto digipeater path] + B46E -- No --> B46G[Discard — hop limit reached] + B46F --> B46H[✅ TEST: Packet from Station A\nrepeated by relay node,\nreceived by Station B out of direct range] + B46C -- No --> B46I[Ignore — not addressed to us] + end + + B46H --> B47 + + subgraph B47["BUILD-47 · Neighbor Beacons & Routing Table"] + B47A[Every 5 min: broadcast ROUTING_BEACON\nwith known neighbors + hop distances] --> B47B[Receive neighbor beacons from other nodes] + B47B --> B47C[Build local routing table:\nnext_hop → destination mappings] + B47C --> B47D[Select optimal next-hop\nfor outbound packets] + B47D --> B47E[✅ TEST: 3-node setup —\nrouting table converges within 2 beacon cycles\noptimal path selected] + end + + B47E --> B48 + + subgraph B48["BUILD-48 · Store-and-Forward"] + B48A[Packet arrives for forwarding\nbut channel is busy\nor next-hop not recently heard] --> B48B[Store packet in local queue\nwith TTL timestamp] + B48B --> B48C{Next-hop heard\nor channel clear?} + B48C -- No --> B48D[Retry after backoff\nuntil TTL expires] + B48C -- Yes --> B48E[Forward stored packet] + B48E --> B48F[✅ TEST: Temporarily block next-hop\n— packet delivered after next-hop comes back\nwithin TTL window] + end + + B48F --> B49 + + subgraph B49["BUILD-49 · Full Multi-Hop End-to-End Test"] + B49A["Station A (no direct path to C)"] -->|"hop 1 via relay node B"| B49B[Relay Node B] + B49B -->|"hop 2 to destination"| B49C[Station C / RustChan Server] + B49C --> B49D[RustChan post created\nwith multi-hop path in HAM badge] + B49D --> B49E[✅ FINAL TEST: Message originating\nfrom out-of-range station successfully\ndelivered via mesh to RustChan board\nCall path preserved in AX.25 digipeater field] + end + + B49E --> DONE + + DONE(["🏁 COMPLETE\nHAMNET-RELAY v1.0\nFull RustChan Streaming\nMesh-Capable\nMic/Speaker & PTT Cable Supported"]) +``` + +--- + +## Regulatory Compliance Checkpoints + +> Apply at every TX-capable build (B11+) + +```mermaid +flowchart LR + RC1["✗ NO ENCRYPTION\nAll content must be decodable\nby any licensed amateur.\nzstd + JPEG = encoding, NOT encryption ✅"] --> RC2 + RC2["⚠ CALLSIGN TX\nMust transmit FCC callsign\nin plain text every 10 min\nand at end of each comms.\nBuilt into beacon + AX.25 src field"] --> RC3 + RC3["✗ NO COMMERCIAL USE\nHAMNET-RELAY + RustChan\nmust remain non-commercial\nfor any HAM-over-radio deployment"] --> RC4 + RC4["⚠ EMISSION TYPE\nAFSK 1200 baud = F2D or F1D emission\nVerify against license class\nand regional band plan"] --> RC5 + RC5["✅ IDENTITY HASH\nBlake3 hash = pseudonymous identifier\nnot encryption. Document derivation\nmethod publicly in open-source repo"] +``` + +--- + +## Build Index Summary + +| Build | Phase | Description | Key Test | +|-------|-------|-------------|----------| +| B01 | 1 | Serial port detection | CH340/CP2102 enumerated | +| B02 | 1 | Audio device enumeration | Default in/out found | +| B03 | 1 | AFSK tone generator | .wav audible + correct frequencies | +| B04 | 1 | AFSK demodulator | Decoded bits match input | +| **B05** | **2** | **Mic capture input stream** | **Tones decoded from mic** | +| **B06** | **2** | **Speaker output stream** | **Remote station decodes our TX** | +| **B07** | **2** | **VOX carrier sense via mic** | **Busy flag fires on signal** | +| **B08** | **2** | **🔑 Full mic/speaker loopback — Radio on Desk Mode** | **Any on-air packet decoded from mic alone** | +| B09 | 3 | AX.25 frame assembler | Direwolf accepts output | +| B10 | 3 | PTT control via RTS/DTR | PTT LED flashes | +| B11 | 3 | Packet TX pipeline | Remote decodes 'HELLO WORLD' | +| B12 | 3 | Packet RX pipeline | APRS packet decoded with callsign | +| B13 | 3 | Full TX/RX duplex test | PING/PONG round-trip | +| B14 | 4 | Client identity (Blake3) | Same hash on restart | +| B15 | 4 | MessagePack serialization | Round-trip struct match | +| B16 | 4 | zstd compression | 1KB → <200 bytes | +| B17 | 4 | Full payload codec pipeline | Post decoded at receiver | +| B18 | 5 | Channel carrier sense | Busy flag within 100ms | +| B19 | 5 | CSMA listen-before-transmit | No collision over 50 sends | +| B20 | 5 | Priority TX queue | HIGH priority transmitted first | +| B21 | 6 | Axum HTTP server | /health returns 200 | +| B22 | 6 | REST endpoints | All endpoints return correct JSON | +| B23 | 6 | WebSocket /subscribe | Real-time events on packet RX | +| B24 | 6 | TOML config system | Config persists across restart | +| B25 | 7 | CHIRP/Baofeng programming | Radio displays programmed freq | +| B26 | 7 | Band plan validation | Out-of-band freq triggers warning | +| B27 | 7 | Server beacon TX | Beacon decoded every 10 min | +| B28 | 7 | Client beacon RX + sync | Client auto-tunes within 30s | +| B29 | 7 | Full sync handshake | /peers shows client after handshake | +| B30 | 8 | Image quality tier system | Output within bandwidth budget | +| B31 | 8 | Image TX over radio | 64×64 received + reconstructed | +| B32 | 8 | Chunked file transfer | 10KB file transferred + hash match | +| B33 | 8 | ACK/NACK retransmit | 20% packet loss recovered | +| B34 | 8 | File reassembly + storage | File on disk matches original | +| B35 | 9 | RustChan incoming webhook | Post appears on board via curl | +| B36 | 9 | HAM source badge | 📻 badge on HAM posts | +| B37 | 9 | Thread watch/subscription API | TX queued on new watched post | +| B38 | 9 | Outbound TX hook | TX queue item on HAM board submit | +| B39 | 9 | RustChan admin config panel | Disabling board stops TX hook | +| B40 | 9 | Callsign/identity association | Callsign in AX.25 source field | +| B41 | 10 | WS push relay→RustChan | Post appears within seconds | +| **B42** | **10** | **🔑 Full streaming pipeline end-to-end** | **Remote post on board within 10s** | +| B43 | 10 | Image quality tier awareness | Large image auto-downscaled | +| B44 | 10 | Radio status widget | Disconnect shows within 10s | +| B45 | 10 | Offline/radio-only mode | Only HAM posts visible | +| B46 | 11 | Digipeater --mode=relay | Packet repeated for out-of-range station | +| B47 | 11 | Routing table + neighbor beacons | Optimal path selected | +| B48 | 11 | Store-and-forward | Delayed packet delivered within TTL | +| **B49** | **11** | **🏁 Multi-hop end-to-end mesh test** | **Out-of-range post on RustChan board** | + +--- + +*HAMNET-RELAY · Architecture Build Roadmap · PRE-DEVELOPMENT DRAFT · 73 DE HAMNET* diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..69b2694 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,21 @@ +/// PCM sample rate used for all WAV files (Hz). +pub const SAMPLE_RATE: u32 = 44_100; + +/// Baud rate: symbols (bits) transmitted per second. +pub const BAUD_RATE: u32 = 1_200; + +/// Audio frequency used to represent a mark bit (1) — Hz. +pub const MARK_FREQ: f64 = 1_200.0; + +/// Audio frequency used to represent a space bit (0) — Hz. +pub const SPACE_FREQ: f64 = 2_200.0; + +/// Sine-wave amplitude in the range [0, 1]. +pub const AMPLITUDE: f64 = 0.9; + +/// Number of 0xAA preamble bytes prepended before the sync word. +/// These alternating bits let the decoder lock onto the bit clock. +pub const PREAMBLE_LEN: usize = 24; + +/// Two-byte sync word that marks the start of the frame header. +pub const SYNC: [u8; 2] = [0x7E, 0x7E]; diff --git a/src/decoder.rs b/src/decoder.rs new file mode 100644 index 0000000..bec4667 --- /dev/null +++ b/src/decoder.rs @@ -0,0 +1,247 @@ +use crate::config::*; +use crate::framer::Decoded; +use std::f64::consts::TAU; + +/// Decode PCM samples back to the original filename and payload bytes. +pub fn decode_progress( + samples: &[f64], + on_progress: impl Fn(f32) + Clone, +) -> Result { + let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + let spb_int = spb.round() as usize; + + for offset in 0..spb_int { + let bits = samples_to_bits(samples, offset, on_progress.clone()); + if let Ok(decoded) = find_frame_in_bits(&bits) { + on_progress(1.0); + return Ok(decoded); + } + } + + Err("could not decode signal — sync word not found at any clock phase".into()) +} + +/// Convenience wrapper with no progress reporting. +pub fn decode(samples: &[f64]) -> Result { + decode_progress(samples, |_| {}) +} + +// --------------------------------------------------------------------------- +// Step 1 — sample → bit stream via Goertzel +// --------------------------------------------------------------------------- + +fn samples_to_bits(samples: &[f64], offset: usize, on_progress: impl Fn(f32)) -> Vec { + let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + let total = samples.len().max(1); + let mut bits = Vec::new(); + let mut idx: usize = 0; + + loop { + let start = offset + (idx as f64 * spb).round() as usize; + let end = offset + ((idx + 1) as f64 * spb).round() as usize; + if end > samples.len() { + break; + } + + let w = &samples[start..end]; + bits.push(goertzel(w, MARK_FREQ, SAMPLE_RATE) > goertzel(w, SPACE_FREQ, SAMPLE_RATE)); + + if idx % 32 == 0 { + on_progress(end as f32 / total as f32); + } + idx += 1; + } + bits +} + +// --------------------------------------------------------------------------- +// Step 2 — search the bit stream for the frame +// --------------------------------------------------------------------------- + +fn find_frame_in_bits(bits: &[bool]) -> Result { + let sync_bits: Vec = [0x7E_u8, 0x7E] + .iter() + .flat_map(|&b| (0..8u8).rev().map(move |i| (b >> i) & 1 == 1)) + .collect(); + let sync_len = sync_bits.len(); // 16 + + let mut search = 0usize; + while search + sync_len <= bits.len() { + let Some(rel) = bits[search..] + .windows(sync_len) + .position(|w| w == sync_bits.as_slice()) + else { + break; + }; + + let sync_start = search + rel; + let mut cursor = sync_start + sync_len; + + // ── name_len (u16 LE) ────────────────────────────────────────── + if cursor + 16 > bits.len() { + break; + } + let name_len = { + let b = bits_to_bytes(&bits[cursor..cursor + 16]); + u16::from_le_bytes(b.try_into().unwrap()) as usize + }; + cursor += 16; + + // Sanity: filenames shouldn't exceed 255 bytes + if name_len > 255 { + search = sync_start + 1; + continue; + } + + // ── name bytes ──────────────────────────────────────────────── + let name_bits = name_len * 8; + if cursor + name_bits > bits.len() { + search = sync_start + 1; + continue; + } + let name_bytes = bits_to_bytes(&bits[cursor..cursor + name_bits]); + let filename = String::from_utf8_lossy(&name_bytes).into_owned(); + cursor += name_bits; + + // ── payload_len (u32 LE) ────────────────────────────────────── + if cursor + 32 > bits.len() { + break; + } + let payload_len = { + let b = bits_to_bytes(&bits[cursor..cursor + 32]); + u32::from_le_bytes(b.try_into().unwrap()) as usize + }; + cursor += 32; + + if payload_len > 1_000_000 { + search = sync_start + 1; + continue; + } + + // ── payload ─────────────────────────────────────────────────── + let payload_start = cursor; + let payload_end = payload_start + payload_len * 8; + let crc_end = payload_end + 16; + if crc_end > bits.len() { + search = sync_start + 1; + continue; + } + + // ── CRC check ───────────────────────────────────────────────── + // CRC covers: sync(16) + name_len(16) + name(N*8) + payload_len(32) + payload(M*8) + let frame_bits_end = payload_end; + let frame_bytes = bits_to_bytes(&bits[sync_start..frame_bits_end]); + let computed = crate::framer::crc16(&frame_bytes); + let stored = { + let b = bits_to_bytes(&bits[payload_end..crc_end]); + u16::from_le_bytes(b.try_into().unwrap()) + }; + + if stored == computed { + return Ok(Decoded { + filename, + data: bits_to_bytes(&bits[payload_start..payload_end]), + }); + } + + search = sync_start + 1; + } + + Err("sync word not found".into()) +} + +// --------------------------------------------------------------------------- +// Non-integer Goertzel DFT +// --------------------------------------------------------------------------- + +fn goertzel(samples: &[f64], freq: f64, sample_rate: u32) -> f64 { + let w = TAU * freq / sample_rate as f64; + let coeff = 2.0 * w.cos(); + let (mut s1, mut s2) = (0.0_f64, 0.0_f64); + for &x in samples { + let s0 = x + coeff * s1 - s2; + s2 = s1; + s1 = s0; + } + let real = s1 - s2 * w.cos(); + let imag = s2 * w.sin(); + real * real + imag * imag +} + +// --------------------------------------------------------------------------- +// Bit / byte helpers +// --------------------------------------------------------------------------- + +pub(crate) fn bits_to_bytes(bits: &[bool]) -> Vec { + bits.chunks(8) + .map(|chunk| { + chunk + .iter() + .enumerate() + .fold(0u8, |acc, (i, &b)| acc | if b { 1 << (7 - i) } else { 0 }) + }) + .collect() +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + use crate::{encoder, framer}; + + #[test] + fn goertzel_discriminates_mark_vs_space() { + let spb = (SAMPLE_RATE as f64 / BAUD_RATE as f64).round() as usize; + let mark_samples: Vec = (0..spb) + .map(|i| (TAU * MARK_FREQ * i as f64 / SAMPLE_RATE as f64).sin()) + .collect(); + let space_samples: Vec = (0..spb) + .map(|i| (TAU * SPACE_FREQ * i as f64 / SAMPLE_RATE as f64).sin()) + .collect(); + + assert!( + goertzel(&mark_samples, MARK_FREQ, SAMPLE_RATE) + > goertzel(&mark_samples, SPACE_FREQ, SAMPLE_RATE) + ); + assert!( + goertzel(&space_samples, SPACE_FREQ, SAMPLE_RATE) + > goertzel(&space_samples, MARK_FREQ, SAMPLE_RATE) + ); + } + + #[test] + fn full_round_trip_text() { + let payload = b"Hello, AFSK!"; + let samples = encoder::encode(&framer::frame(payload, "hello.txt")); + let decoded = decode(&samples).expect("decode failed"); + assert_eq!(decoded.data, payload); + assert_eq!(decoded.filename, "hello.txt"); + } + + #[test] + fn full_round_trip_all_bytes() { + let payload: Vec = (0u8..=255).collect(); + let samples = encoder::encode(&framer::frame(&payload, "all.bin")); + let decoded = decode(&samples).expect("decode failed"); + assert_eq!(decoded.data, payload); + assert_eq!(decoded.filename, "all.bin"); + } + + #[test] + fn full_round_trip_empty() { + let samples = encoder::encode(&framer::frame(&[], "empty.bin")); + let decoded = decode(&samples).expect("decode failed"); + assert!(decoded.data.is_empty()); + } + + #[test] + fn filename_with_dots_preserved() { + let payload = b"compressed archive"; + let samples = encoder::encode(&framer::frame(payload, "archive.tar.gz")); + let decoded = decode(&samples).expect("decode failed"); + assert_eq!(decoded.filename, "archive.tar.gz"); + } +} diff --git a/src/encoder.rs b/src/encoder.rs new file mode 100644 index 0000000..44910f7 --- /dev/null +++ b/src/encoder.rs @@ -0,0 +1,48 @@ +use crate::config::*; +use std::f64::consts::TAU; + +/// Encode `framed` bytes into PCM audio samples. +/// Calls `on_progress` with a value in 0.0..=1.0 as bits are processed. +pub fn encode_progress(framed: &[u8], on_progress: impl Fn(f32)) -> Vec { + let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + + let bits: Vec = framed + .iter() + .flat_map(|&byte| (0..8u8).rev().map(move |i| (byte >> i) & 1 == 1)) + .collect(); + + let total_bits = bits.len().max(1); + let silence_len = (SAMPLE_RATE as f64 * 0.05) as usize; + let signal_len = ((bits.len() as f64) * spb).round() as usize; + let mut samples = Vec::with_capacity(silence_len * 2 + signal_len); + + samples.extend(std::iter::repeat(0.0_f64).take(silence_len)); + + let mut phase = 0.0_f64; + for (idx, &bit) in bits.iter().enumerate() { + let freq = if bit { MARK_FREQ } else { SPACE_FREQ }; + let phase_inc = TAU * freq / SAMPLE_RATE as f64; + + let start = (idx as f64 * spb).round() as usize; + let end = ((idx + 1) as f64 * spb).round() as usize; + + for _ in start..end { + samples.push(AMPLITUDE * phase.sin()); + phase = (phase + phase_inc) % TAU; + } + + // Report progress every 64 bits to avoid lock contention + if idx % 64 == 0 { + on_progress(idx as f32 / total_bits as f32); + } + } + + samples.extend(std::iter::repeat(0.0_f64).take(silence_len)); + on_progress(1.0); + samples +} + +/// Convenience wrapper with no progress reporting (used by CLI and tests). +pub fn encode(framed: &[u8]) -> Vec { + encode_progress(framed, |_| {}) +} diff --git a/src/framer.rs b/src/framer.rs new file mode 100644 index 0000000..e4b7f52 --- /dev/null +++ b/src/framer.rs @@ -0,0 +1,187 @@ +/// Frame layout (v2 — stores original filename) +/// +/// ┌──────────────┬────────┬──────────────┬──────┬──────────────┬─────────┬────────┐ +/// │ preamble N×AA│ 7E 7E │ name_len u16 │ name │ payload_len │ payload │ CRC-16 │ +/// └──────────────┴────────┴──────────────┴──────┴──────────────┴─────────┴────────┘ +/// ◄──────────────────── CRC covers this span ──────────────────────► +use crate::config::{PREAMBLE_LEN, SYNC}; + +/// Wrap `data` in a transmittable frame, embedding `filename` so the decoder +/// can reconstruct the file with the correct name and extension. +pub fn frame(data: &[u8], filename: &str) -> Vec { + let name_bytes = filename.as_bytes(); + let name_len = name_bytes.len().min(255); // cap at 255 bytes + let name_bytes = &name_bytes[..name_len]; + + let capacity = PREAMBLE_LEN + 2 + 2 + name_len + 4 + data.len() + 2; + let mut out = Vec::with_capacity(capacity); + + // Preamble + out.extend(std::iter::repeat(0xAA_u8).take(PREAMBLE_LEN)); + + // Sync word + out.extend_from_slice(&SYNC); + + // Filename length (u16 LE) + filename bytes + out.extend_from_slice(&(name_len as u16).to_le_bytes()); + out.extend_from_slice(name_bytes); + + // Payload length (u32 LE) + payload + out.extend_from_slice(&(data.len() as u32).to_le_bytes()); + out.extend_from_slice(data); + + // CRC-16/CCITT over everything from sync word onwards (not the preamble) + let crc = crc16(&out[PREAMBLE_LEN..]); + out.extend_from_slice(&crc.to_le_bytes()); + + out +} + +/// Decoded frame: original filename and payload bytes. +pub struct Decoded { + pub filename: String, + pub data: Vec, +} + +/// Find and validate a frame inside `raw`, returning the embedded filename and payload. +/// +/// Used only by the byte-level path (tests / CLI verification). +/// The audio decoder uses `find_frame_in_bits` in decoder.rs directly. +#[allow(dead_code)] +pub fn deframe(raw: &[u8]) -> Result { + let sync_pos = raw + .windows(SYNC.len()) + .position(|w| w == SYNC) + .ok_or_else(|| "sync word not found".to_string())?; + + let mut cursor = sync_pos + SYNC.len(); + + // name_len (u16) + if raw.len() < cursor + 2 { + return Err("frame too short: missing name_len".into()); + } + let name_len = u16::from_le_bytes(raw[cursor..cursor + 2].try_into().unwrap()) as usize; + cursor += 2; + + // name bytes + if raw.len() < cursor + name_len { + return Err("frame too short: missing filename".into()); + } + let filename = String::from_utf8_lossy(&raw[cursor..cursor + name_len]).into_owned(); + cursor += name_len; + + // payload_len (u32) + if raw.len() < cursor + 4 { + return Err("frame too short: missing payload_len".into()); + } + let payload_len = u32::from_le_bytes(raw[cursor..cursor + 4].try_into().unwrap()) as usize; + cursor += 4; + + let payload_end = cursor + payload_len; + let crc_end = payload_end + 2; + + if raw.len() < crc_end { + return Err(format!( + "frame truncated: need {} bytes after sync, have {}", + crc_end - sync_pos, + raw.len() - sync_pos, + )); + } + + let stored_crc = u16::from_le_bytes(raw[payload_end..crc_end].try_into().unwrap()); + let computed_crc = crc16(&raw[sync_pos..payload_end]); + + if stored_crc != computed_crc { + return Err(format!( + "CRC mismatch: stored {stored_crc:#06x}, computed {computed_crc:#06x}" + )); + } + + Ok(Decoded { + filename, + data: raw[cursor..payload_end].to_vec(), + }) +} + +// --------------------------------------------------------------------------- +// CRC-16/CCITT (polynomial 0x1021, init 0xFFFF, no bit-reflection) +// --------------------------------------------------------------------------- + +pub(crate) fn crc16(data: &[u8]) -> u16 { + let mut crc: u16 = 0xFFFF; + for &byte in data { + crc ^= (byte as u16) << 8; + for _ in 0..8 { + crc = if crc & 0x8000 != 0 { + (crc << 1) ^ 0x1021 + } else { + crc << 1 + }; + } + } + crc +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn rt(data: &[u8], name: &str) -> Decoded { + deframe(&frame(data, name)).unwrap() + } + + #[test] + fn round_trip_empty_payload() { + let d = rt(&[], "empty.bin"); + assert!(d.data.is_empty()); + assert_eq!(d.filename, "empty.bin"); + } + + #[test] + fn round_trip_ascii() { + let d = rt(b"Hello, AFSK!", "hello.txt"); + assert_eq!(d.data, b"Hello, AFSK!"); + assert_eq!(d.filename, "hello.txt"); + } + + #[test] + fn round_trip_binary() { + let data: Vec = (0u8..=255).collect(); + let d = rt(&data, "all_bytes.bin"); + assert_eq!(d.data, data); + } + + #[test] + fn filename_preserved() { + let d = rt(b"data", "archive.tar.gz"); + assert_eq!(d.filename, "archive.tar.gz"); + } + + #[test] + fn deframe_ignores_leading_garbage() { + let mut framed = frame(b"test", "test.txt"); + framed.insert(0, 0xFF); + framed.insert(0, 0x42); + let d = deframe(&framed).unwrap(); + assert_eq!(d.data, b"test"); + assert_eq!(d.filename, "test.txt"); + } + + #[test] + fn crc_detects_corruption() { + let mut framed = frame(b"integrity check", "check.txt"); + // Corrupt a payload byte (past preamble+sync+namelen+name+payloadlen) + let corrupt_pos = PREAMBLE_LEN + 2 + 2 + "check.txt".len() + 4 + 2; + framed[corrupt_pos] ^= 0xFF; + assert!(deframe(&framed).is_err()); + } + + #[test] + fn crc16_known_value() { + assert_eq!(crc16(b"123456789"), 0x29B1); + } +} diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..102cc1e --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,451 @@ +//! GUI front-end — launched with `afsk -gui`. +//! +//! Drag any file onto the window: +//! • WAV → decoded, output saved with the ORIGINAL filename next to the binary +//! • Other → encoded to `_encoded.wav` next to the binary + +use std::path::PathBuf; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{mpsc, Arc}; +use std::thread; +use std::time::Duration; + +use eframe::egui::{self, Color32, FontId, Pos2, Rect, Rounding, Stroke, Vec2}; + +// ─── State machine ─────────────────────────────────────────────────────────── + +enum State { + Idle, + Processing { + filename: String, + action: &'static str, + progress: Arc, + rx: mpsc::Receiver>, + }, + Done { + action: &'static str, + output: PathBuf, + }, + Failed(String), +} + +// ─── App ───────────────────────────────────────────────────────────────────── + +pub struct AfskGui { + state: State, +} + +impl AfskGui { + pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { + Self { state: State::Idle } + } + + fn start_processing(&mut self, path: PathBuf, ctx: egui::Context) { + let is_wav = path + .extension() + .map(|e| e.to_ascii_lowercase() == "wav") + .unwrap_or(false); + + let filename = path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(); + + let action: &'static str = if is_wav { "Decoding" } else { "Encoding" }; + let progress = Arc::new(AtomicU32::new(0)); + let (tx, rx) = mpsc::channel::>(); + + let binary_dir = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(PathBuf::from)) + .unwrap_or_else(|| PathBuf::from(".")); + + { + let progress = Arc::clone(&progress); + thread::spawn(move || { + let prog = Arc::clone(&progress); + let ctx2 = ctx.clone(); + let on_progress = move |v: f32| { + prog.store((v.clamp(0.0, 1.0) * 1_000_000.0) as u32, Ordering::Relaxed); + ctx2.request_repaint_after(Duration::from_millis(16)); + }; + + let outcome: Result = if is_wav { + // ── Decode WAV → original file ─────────────────────── + crate::wav::read(&path) + .and_then(|samples| { + crate::decoder::decode_progress(&samples, on_progress).map_err(|e| e) + }) + .and_then(|decoded| { + // Use the filename baked into the signal + let out = binary_dir.join(&decoded.filename); + std::fs::write(&out, &decoded.data) + .map(|_| out) + .map_err(|e| e.to_string()) + }) + } else { + // ── Encode file → WAV ──────────────────────────────── + std::fs::read(&path) + .map_err(|e| e.to_string()) + .map(|data| { + // Store the original filename in the frame header + let orig_name = path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(); + let framed = crate::framer::frame(&data, &orig_name); + crate::encoder::encode_progress(&framed, on_progress) + }) + .and_then(|samples| { + let stem = path.file_stem().unwrap_or_default().to_string_lossy(); + let out = binary_dir.join(format!("{stem}_encoded.wav")); + crate::wav::write(&out, &samples).map(|_| out) + }) + }; + + progress.store(1_000_000, Ordering::Relaxed); + let _ = tx.send(outcome); + ctx.request_repaint(); + }); + } + + self.state = State::Processing { + filename, + action, + progress, + rx, + }; + } +} + +// ─── eframe::App ───────────────────────────────────────────────────────────── + +impl eframe::App for AfskGui { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Poll worker result + let finished: Option<(&'static str, Result)> = + if let State::Processing { rx, action, .. } = &self.state { + match rx.try_recv() { + Ok(result) => Some((action, result)), + Err(_) => None, + } + } else { + None + }; + + if let Some((action, result)) = finished { + self.state = match result { + Ok(output) => State::Done { action, output }, + Err(e) => State::Failed(e), + }; + } + + // Accept dropped files + let dropped = ctx.input(|i| i.raw.dropped_files.clone()); + if let Some(f) = dropped.first() { + if let Some(p) = &f.path { + if !matches!(self.state, State::Processing { .. }) { + self.start_processing(p.clone(), ctx.clone()); + } + } + } + + if matches!(self.state, State::Processing { .. }) { + ctx.request_repaint_after(Duration::from_millis(16)); + } + + let hovering = ctx.input(|i| !i.raw.hovered_files.is_empty()); + + let bg_dark = Color32::from_rgb(15, 15, 20); + let bg_panel = Color32::from_rgb(22, 22, 30); + let accent = Color32::from_rgb(100, 145, 235); + let dim_text = Color32::from_rgb(100, 105, 130); + + egui::CentralPanel::default() + .frame(egui::Frame::none().fill(bg_dark)) + .show(ctx, |ui| { + let avail = ui.available_size(); + + ui.add_space(18.0); + ui.vertical_centered(|ui| { + ui.painter().text( + ui.next_widget_position() + Vec2::new(avail.x / 2.0, 0.0), + egui::Align2::CENTER_TOP, + "AFSK Codec", + FontId::proportional(20.0), + Color32::from_rgb(170, 175, 200), + ); + ui.add_space(24.0); + }); + + let zone_size = Vec2::new((avail.x - 40.0).max(240.0), (avail.y - 90.0).max(160.0)); + + ui.vertical_centered(|ui| { + let (rect, _) = ui.allocate_exact_size(zone_size, egui::Sense::hover()); + + let fill = if hovering { + Color32::from_rgba_premultiplied(100, 145, 235, 15) + } else { + bg_panel + }; + let border = if hovering { + accent + } else { + Color32::from_rgb(50, 55, 75) + }; + + ui.painter().rect_filled(rect, Rounding::same(10.0), fill); + dashed_border(ui.painter(), rect, Stroke::new(1.5, border)); + + match &self.state { + State::Idle => { + draw_idle(ui.painter(), rect, hovering, accent, dim_text); + } + State::Processing { + filename, + action, + progress, + .. + } => { + let v = progress.load(Ordering::Relaxed) as f32 / 1_000_000.0; + draw_processing( + ui.painter(), + rect, + action, + filename, + v, + accent, + dim_text, + ); + } + State::Done { action, output } => { + let label = format!("{} complete", action); + draw_result( + ui.painter(), + rect, + true, + &label, + &output.to_string_lossy(), + accent, + dim_text, + ); + } + State::Failed(err) => { + draw_result( + ui.painter(), + rect, + false, + "Error", + err, + Color32::from_rgb(220, 85, 85), + dim_text, + ); + } + } + }); + }); + } +} + +// ─── Drawing helpers ───────────────────────────────────────────────────────── + +fn draw_idle(painter: &egui::Painter, zone: Rect, hovering: bool, accent: Color32, dim: Color32) { + let cx = zone.center().x; + let cy = zone.center().y; + + let heading = if hovering { + "Release to process" + } else { + "Drop a file here" + }; + let heading_color = if hovering { + accent + } else { + Color32::from_rgb(210, 215, 230) + }; + + painter.text( + Pos2::new(cx, cy - 28.0), + egui::Align2::CENTER_CENTER, + heading, + FontId::proportional(18.0), + heading_color, + ); + painter.text( + Pos2::new(cx, cy + 8.0), + egui::Align2::CENTER_CENTER, + "WAV → restores original file + extension", + FontId::proportional(12.5), + dim, + ); + painter.text( + Pos2::new(cx, cy + 25.0), + egui::Align2::CENTER_CENTER, + "Other → encode to .wav", + FontId::proportional(12.5), + dim, + ); + painter.text( + Pos2::new(cx, zone.max.y - 18.0), + egui::Align2::CENTER_CENTER, + "Output saved next to the binary", + FontId::proportional(11.0), + Color32::from_rgb(60, 65, 85), + ); +} + +fn draw_processing( + painter: &egui::Painter, + zone: Rect, + action: &str, + filename: &str, + progress: f32, + accent: Color32, + dim: Color32, +) { + let cx = zone.center().x; + let cy = zone.center().y; + + painter.text( + Pos2::new(cx, cy - 40.0), + egui::Align2::CENTER_CENTER, + action, + FontId::proportional(15.0), + accent, + ); + painter.text( + Pos2::new(cx, cy - 20.0), + egui::Align2::CENTER_CENTER, + filename, + FontId::proportional(12.5), + Color32::from_rgb(180, 185, 205), + ); + + let bar_w = (zone.width() - 70.0).max(80.0); + let bar_rect = Rect::from_center_size(Pos2::new(cx, cy + 8.0), Vec2::new(bar_w, 10.0)); + painter.rect_filled(bar_rect, Rounding::same(5.0), Color32::from_rgb(35, 37, 52)); + + let filled_w = (bar_rect.width() * progress.clamp(0.0, 1.0)).max(0.0); + if filled_w >= 1.0 { + let filled = Rect::from_min_size(bar_rect.min, Vec2::new(filled_w, bar_rect.height())); + painter.rect_filled(filled, Rounding::same(5.0), accent); + } + + painter.text( + Pos2::new(cx, cy + 28.0), + egui::Align2::CENTER_CENTER, + format!("{:.0}%", progress * 100.0), + FontId::proportional(12.5), + dim, + ); +} + +fn draw_result( + painter: &egui::Painter, + zone: Rect, + success: bool, + label: &str, + detail: &str, + color: Color32, + dim: Color32, +) { + let cx = zone.center().x; + let cy = zone.center().y; + + painter.text( + Pos2::new(cx, cy - 32.0), + egui::Align2::CENTER_CENTER, + if success { "✓" } else { "✗" }, + FontId::proportional(30.0), + color, + ); + painter.text( + Pos2::new(cx, cy + 6.0), + egui::Align2::CENTER_CENTER, + label, + FontId::proportional(15.0), + color, + ); + + let max_chars = 55usize; + let display = if detail.len() > max_chars { + format!("…{}", &detail[detail.len().saturating_sub(max_chars - 1)..]) + } else { + detail.to_string() + }; + painter.text( + Pos2::new(cx, cy + 28.0), + egui::Align2::CENTER_CENTER, + display, + FontId::monospace(11.0), + Color32::from_rgb(155, 160, 185), + ); + + painter.text( + Pos2::new(cx, zone.max.y - 18.0), + egui::Align2::CENTER_CENTER, + "Drop another file to continue", + FontId::proportional(11.0), + dim, + ); +} + +fn dashed_border(painter: &egui::Painter, rect: Rect, stroke: Stroke) { + let dash = 8.0_f32; + let gap = 5.0_f32; + let r = 10.0_f32; + + let seg = |from: Pos2, to: Pos2| { + let delta = to - from; + let len = delta.length(); + if len < 1.0 { + return; + } + let dir = delta / len; + let mut t = 0.0_f32; + while t < len { + let a = from + dir * t; + let b = from + dir * (t + dash).min(len); + painter.line_segment([a, b], stroke); + t += dash + gap; + } + }; + + seg( + Pos2::new(rect.min.x + r, rect.min.y), + Pos2::new(rect.max.x - r, rect.min.y), + ); + seg( + Pos2::new(rect.min.x + r, rect.max.y), + Pos2::new(rect.max.x - r, rect.max.y), + ); + seg( + Pos2::new(rect.min.x, rect.min.y + r), + Pos2::new(rect.min.x, rect.max.y - r), + ); + seg( + Pos2::new(rect.max.x, rect.min.y + r), + Pos2::new(rect.max.x, rect.max.y - r), + ); +} + +// ─── Entry point ───────────────────────────────────────────────────────────── + +pub fn run() -> eframe::Result<()> { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([480.0, 340.0]) + .with_min_inner_size([360.0, 260.0]) + .with_title("AFSK Codec") + .with_drag_and_drop(true), + ..Default::default() + }; + + eframe::run_native( + "AFSK Codec", + options, + Box::new(|cc| Box::new(AfskGui::new(cc)) as Box), + ) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5c5d343 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,123 @@ +mod config; +mod decoder; +mod encoder; +mod framer; +mod gui; +mod wav; + +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(name = "afsk", version, about = "AFSK audio codec", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Encode a file into an AFSK WAV (original filename is stored in the signal) + Encode { + #[arg(short, long, value_name = "FILE")] + input: PathBuf, + #[arg(short, long, value_name = "FILE")] + output: PathBuf, + }, + /// Decode an AFSK WAV — restores the original filename automatically. + /// If -o is omitted the file is written next to the WAV with its original name. + Decode { + #[arg(short, long, value_name = "FILE")] + input: PathBuf, + /// Output path (optional — defaults to original filename next to the WAV) + #[arg(short, long, value_name = "FILE")] + output: Option, + }, +} + +fn main() { + if std::env::args().any(|a| a == "-gui" || a == "--gui") { + gui::run().unwrap_or_else(|e| { + eprintln!("GUI error: {e}"); + std::process::exit(1); + }); + return; + } + + let cli = Cli::parse(); + + match cli.command { + Command::Encode { input, output } => { + let data = std::fs::read(&input).unwrap_or_else(|e| { + eprintln!("error: cannot read '{}': {e}", input.display()); + std::process::exit(1); + }); + + // Store just the filename (not full path) in the frame + let filename = input + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(); + + let framed = framer::frame(&data, &filename); + let samples = encoder::encode(&framed); + + if let Err(e) = wav::write(&output, &samples) { + eprintln!("error: cannot write '{}': {e}", output.display()); + std::process::exit(1); + } + + let duration = samples.len() as f64 / config::SAMPLE_RATE as f64; + eprintln!( + "encoded '{}' ({} byte{}) -> {} ({:.2} s)", + filename, + data.len(), + plural(data.len()), + output.display(), + duration, + ); + } + + Command::Decode { input, output } => { + let samples = wav::read(&input).unwrap_or_else(|e| { + eprintln!("error: cannot read '{}': {e}", input.display()); + std::process::exit(1); + }); + + let decoded = decoder::decode(&samples).unwrap_or_else(|e| { + eprintln!("error: decode failed: {e}"); + std::process::exit(1); + }); + + // Use caller-supplied path, or reconstruct from stored filename + let out_path = output.unwrap_or_else(|| { + input + .parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join(&decoded.filename) + }); + + if let Err(e) = std::fs::write(&out_path, &decoded.data) { + eprintln!("error: cannot write '{}': {e}", out_path.display()); + std::process::exit(1); + } + + eprintln!( + "decoded {} byte{} -> '{}' (original filename: '{}')", + decoded.data.len(), + plural(decoded.data.len()), + out_path.display(), + decoded.filename, + ); + } + } +} + +fn plural(n: usize) -> &'static str { + if n == 1 { + "" + } else { + "s" + } +} diff --git a/src/wav.rs b/src/wav.rs new file mode 100644 index 0000000..738d25a --- /dev/null +++ b/src/wav.rs @@ -0,0 +1,92 @@ +use hound::{SampleFormat, WavReader, WavSpec, WavWriter}; +use std::path::Path; + +use crate::config::SAMPLE_RATE; + +/// Write normalised f64 samples (range −1.0 … 1.0) to a 16-bit mono PCM WAV. +pub fn write(path: &Path, samples: &[f64]) -> Result<(), String> { + let spec = WavSpec { + channels: 1, + sample_rate: SAMPLE_RATE, + bits_per_sample: 16, + sample_format: SampleFormat::Int, + }; + + let mut writer = WavWriter::create(path, spec).map_err(|e| e.to_string())?; + + for &s in samples { + let v = (s.clamp(-1.0, 1.0) * 32_767.0) as i16; + writer.write_sample(v).map_err(|e| e.to_string())?; + } + + writer.finalize().map_err(|e| e.to_string()) +} + +/// Read a 16-bit mono PCM WAV and return normalised f64 samples (range −1 … 1). +/// +/// Stereo files are accepted; only the first (left) channel is used. +pub fn read(path: &Path) -> Result, String> { + let mut reader = WavReader::open(path).map_err(|e| e.to_string())?; + let spec = reader.spec(); + + match (spec.bits_per_sample, spec.sample_format) { + (16, SampleFormat::Int) => { + let channels = spec.channels as usize; + reader + .samples::() + .step_by(channels) // take only the first channel + .map(|s| s.map(|v| v as f64 / 32_768.0).map_err(|e| e.to_string())) + .collect() + } + (bits, fmt) => Err(format!( + "unsupported WAV format: {bits}-bit {fmt:?} \ + (afsk expects 16-bit integer PCM)" + )), + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + use std::f64::consts::TAU; + + fn tmp(name: &str) -> std::path::PathBuf { + std::env::temp_dir().join(name) + } + + #[test] + fn silence_round_trip() { + let path = tmp("afsk_wav_silence.wav"); + let original = vec![0.0_f64; 4_410]; + write(&path, &original).unwrap(); + let recovered = read(&path).unwrap(); + assert_eq!(original.len(), recovered.len()); + for v in recovered { + assert!(v.abs() < 2.0 / 32_768.0, "expected silence, got {v}"); + } + let _ = std::fs::remove_file(&path); + } + + #[test] + fn sine_round_trip() { + let path = tmp("afsk_wav_sine.wav"); + let original: Vec = (0..44_100) + .map(|i| 0.5 * (TAU * 440.0 * i as f64 / 44_100.0).sin()) + .collect(); + write(&path, &original).unwrap(); + let recovered = read(&path).unwrap(); + assert_eq!(original.len(), recovered.len()); + // Max 16-bit quantisation error ≈ 1.5 × 10⁻⁵ + for (a, b) in original.iter().zip(recovered.iter()) { + assert!( + (a - b).abs() < 5e-5, + "quantisation error too large: {a} vs {b}" + ); + } + let _ = std::fs::remove_file(&path); + } +} From 68a5a04a6ec0b41aa3679a586b7c10c6d429e6ff Mon Sep 17 00:00:00 2001 From: csd113 Date: Wed, 11 Mar 2026 18:26:46 -0700 Subject: [PATCH 2/5] v0.1.0 publish --- .gitignore | 1 + Cargo.lock | 1716 ++++++++++++++++----------------- Cargo.toml | 11 +- README.md | 32 +- clippy_reports/clippy_raw.txt | 2 + clippy_reports/summary.txt | 5 + deny.toml | 66 ++ docs/Dev1.docx | Bin 26084 -> 0 bytes docs/Dev2.docx | Bin 34200 -> 0 bytes src/decoder.rs | 83 +- src/encoder.rs | 21 +- src/framer.rs | 50 +- src/gui.rs | 270 +++--- src/main.rs | 9 +- src/wav.rs | 35 +- 15 files changed, 1231 insertions(+), 1070 deletions(-) create mode 100644 clippy_reports/clippy_raw.txt create mode 100644 clippy_reports/summary.txt create mode 100644 deny.toml delete mode 100644 docs/Dev1.docx delete mode 100644 docs/Dev2.docx diff --git a/.gitignore b/.gitignore index 3178cf6..5750437 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ target # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +run-cargo-deny.sh diff --git a/Cargo.lock b/Cargo.lock index 91fafab..8568a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,72 +20,93 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" -version = "0.12.3" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" +checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" + +[[package]] +name = "accesskit_atspi_common" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror 1.0.69", + "zvariant", +] [[package]] name = "accesskit_consumer" -version = "0.16.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" +checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" dependencies = [ "accesskit", + "hashbrown 0.15.5", + "immutable-chunkmap", ] [[package]] name = "accesskit_macos" -version = "0.10.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" +checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" dependencies = [ "accesskit", "accesskit_consumer", - "objc2 0.3.0-beta.3.patch-leaks.3", - "once_cell", + "hashbrown 0.15.5", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", ] [[package]] name = "accesskit_unix" -version = "0.6.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" +checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" dependencies = [ "accesskit", - "accesskit_consumer", + "accesskit_atspi_common", "async-channel", - "async-once-cell", + "async-executor", + "async-task", "atspi", - "futures-lite 1.13.0", - "once_cell", + "futures-lite", + "futures-util", "serde", "zbus", ] [[package]] name = "accesskit_windows" -version = "0.15.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" +checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" dependencies = [ "accesskit", "accesskit_consumer", - "once_cell", + "hashbrown 0.15.5", "paste", "static_assertions", - "windows 0.48.0", + "windows", + "windows-core", ] [[package]] name = "accesskit_winit" -version = "0.16.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" +checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" dependencies = [ "accesskit", "accesskit_macos", "accesskit_unix", "accesskit_windows", + "raw-window-handle", "winit", ] @@ -95,15 +116,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "afsk" -version = "0.1.0" -dependencies = [ - "clap", - "eframe", - "hound", -] - [[package]] name = "ahash" version = "0.8.12" @@ -117,38 +129,23 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android-activity" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", "bitflags 2.11.0", "cc", "cesu8", - "jni", - "jni-sys", + "jni 0.21.1", + "jni-sys 0.3.0", "libc", "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror 1.0.69", ] @@ -231,10 +228,13 @@ 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-foundation", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "windows-sys 0.60.2", @@ -261,21 +261,23 @@ checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "ash" -version = "0.37.3+1.3.251" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.7.4", + "libloading", ] [[package]] name = "async-broadcast" -version = "0.5.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 2.5.3", + "event-listener", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] @@ -298,42 +300,21 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] [[package]] name = "async-fs" -version = "1.6.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ - "async-lock 2.8.0", - "autocfg", + "async-lock", "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2", - "waker-fn", + "futures-lite", ] [[package]] @@ -346,55 +327,41 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", + "polling", "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] -[[package]] -name = "async-once-cell" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" - [[package]] name = "async-process" -version = "1.8.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", + "async-channel", + "async-io", + "async-lock", "async-signal", + "async-task", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.44", - "windows-sys 0.48.0", + "event-listener", + "futures-lite", + "rustix 1.1.4", ] [[package]] @@ -405,7 +372,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -414,8 +381,8 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", @@ -440,7 +407,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -451,9 +418,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atspi" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" dependencies = [ "atspi-common", "atspi-connection", @@ -462,39 +429,42 @@ dependencies = [ [[package]] name = "atspi-common" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" dependencies = [ "enumflags2", "serde", "static_assertions", "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", "zbus_names", "zvariant", ] [[package]] name = "atspi-connection" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" dependencies = [ "atspi-common", "atspi-proxies", - "futures-lite 1.13.0", + "futures-lite", "zbus", ] [[package]] name = "atspi-proxies" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" dependencies = [ "atspi-common", "serde", "zbus", + "zvariant", ] [[package]] @@ -505,18 +475,18 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -529,6 +499,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "block" @@ -545,42 +518,13 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys 0.3.5", -] - -[[package]] -name = "block2" -version = "0.2.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" -dependencies = [ - "block-sys 0.1.0-beta.1", - "objc2-encode 2.0.0-pre.2", -] - [[package]] name = "block2" -version = "0.3.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "block-sys 0.2.1", - "objc2 0.4.1", + "objc2 0.5.2", ] [[package]] @@ -592,7 +536,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] @@ -619,14 +563,14 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] -name = "byteorder" -version = "1.5.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" @@ -636,13 +580,13 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.11.0", "log", - "polling 3.11.0", + "polling", "rustix 0.38.44", "slab", "thiserror 1.0.69", @@ -655,7 +599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ "bitflags 2.11.0", - "polling 3.11.0", + "polling", "rustix 1.1.4", "slab", "tracing", @@ -663,11 +607,11 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop 0.12.4", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", @@ -711,9 +655,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "cgl" @@ -755,7 +699,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -773,36 +717,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -813,49 +727,12 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "com" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" -dependencies = [ - "com_macros", -] - -[[package]] -name = "com_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" -dependencies = [ - "com_macros_support", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "com_macros_support" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "combine" version = "4.6.7" @@ -885,6 +762,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -898,7 +785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -911,7 +798,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -939,6 +826,12 @@ 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 = "crypto-common" version = "0.1.7" @@ -955,17 +848,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "digest" version = "0.10.7" @@ -1000,7 +882,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1009,7 +891,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading 0.8.9", + "libloading", ] [[package]] @@ -1027,23 +909,30 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "ecolor" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ "bytemuck", + "emath", ] [[package]] name = "eframe" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" +checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" dependencies = [ + "ahash", "bytemuck", - "cocoa", "document-features", "egui", "egui-wgpu", @@ -1055,45 +944,52 @@ dependencies = [ "image", "js-sys", "log", - "objc", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", + "profiling", + "raw-window-handle", "static_assertions", - "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "web-time", "winapi", + "windows-sys 0.59.0", "winit", ] [[package]] name = "egui" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "accesskit", "ahash", + "bitflags 2.11.0", + "emath", "epaint", "log", "nohash-hasher", + "profiling", ] [[package]] name = "egui-wgpu" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" dependencies = [ + "ahash", "bytemuck", "document-features", "egui", "epaint", "log", + "profiling", "thiserror 1.0.69", "type-map", "web-time", @@ -1103,15 +999,18 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" dependencies = [ "accesskit_winit", + "ahash", "arboard", + "bytemuck", "egui", "log", - "raw-window-handle 0.6.2", + "profiling", + "raw-window-handle", "smithay-clipboard", "web-time", "webbrowser", @@ -1120,15 +1019,17 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" dependencies = [ + "ahash", "bytemuck", "egui", "glow", "log", - "memoffset 0.9.1", + "memoffset", + "profiling", "wasm-bindgen", "web-sys", "winit", @@ -1136,13 +1037,19 @@ dependencies = [ [[package]] name = "emath" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + [[package]] name = "enumflags2" version = "0.7.12" @@ -1161,25 +1068,33 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "epaint" -version = "0.27.2" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", "ahash", "bytemuck", "ecolor", "emath", + "epaint_default_fonts", "log", "nohash-hasher", "parking_lot", + "profiling", ] +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + [[package]] name = "equivalent" version = "1.0.2" @@ -1202,23 +1117,6 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.4.1" @@ -1236,24 +1134,35 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" dependencies = [ - "instant", + "fax_derive", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "fax_derive" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "fdeflate" @@ -1304,7 +1213,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1336,30 +1245,26 @@ checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" -version = "1.13.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 1.9.0", + "fastrand", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", ] [[package]] -name = "futures-lite" -version = "2.6.1" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "fastrand 2.3.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1382,6 +1287,7 @@ checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1458,9 +1364,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -1470,55 +1376,56 @@ dependencies = [ [[package]] name = "glutin" -version = "0.31.3" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ "bitflags 2.11.0", "cfg_aliases", "cgl", - "core-foundation", - "dispatch", + "dispatch2", "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", - "icrate", - "libloading 0.8.9", - "objc2 0.4.1", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "once_cell", - "raw-window-handle 0.5.2", + "raw-window-handle", "wayland-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", "x11-dl", ] [[package]] name = "glutin-winit" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ "cfg_aliases", "glutin", - "raw-window-handle 0.5.2", + "raw-window-handle", "winit", ] [[package]] name = "glutin_egl_sys" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" dependencies = [ "gl_generator", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "glutin_glx_sys" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" dependencies = [ "gl_generator", "x11-dl", @@ -1526,9 +1433,9 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ "gl_generator", ] @@ -1552,47 +1459,35 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "gpu-allocator" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" -dependencies = [ - "log", - "presser", - "thiserror 1.0.69", - "winapi", - "windows 0.52.0", -] - [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.11.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown 0.15.5", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ "bitflags 2.11.0", ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "ahash", - "allocator-api2", + "cfg-if", + "crunchy", + "zerocopy", ] [[package]] @@ -1610,33 +1505,12 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -[[package]] -name = "hassle-rs" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" -dependencies = [ - "bitflags 2.11.0", - "com", - "libc", - "libloading 0.8.9", - "thiserror 1.0.69", - "widestring", - "winapi", -] - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1655,32 +1529,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "hound" version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" -[[package]] -name = "icrate" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" -dependencies = [ - "block2 0.3.0", - "dispatch", - "objc2 0.4.1", -] - [[package]] name = "icu_collections" version = "2.1.1" @@ -1791,15 +1645,25 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", - "byteorder", - "color_quant", + "byteorder-lite", + "moxcms", "num-traits", "png", + "tiff", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" +dependencies = [ + "arrayvec", ] [[package]] @@ -1814,26 +1678,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1855,19 +1699,68 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295dc9997acda1562fdf8d299f56063c936443b60f078e63a5d8d3c34ef2642b" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c3d1da60c95c98847b26b9d45f4360fee718b31de746df016d9cd6de916a7ef" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[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" @@ -1895,7 +1788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.9", + "libloading", "pkg-config", ] @@ -1917,16 +1810,6 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.9" @@ -1949,12 +1832,6 @@ dependencies = [ "redox_syscall 0.7.3", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2018,15 +1895,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -2038,9 +1906,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.11.0", "block", @@ -2061,39 +1929,50 @@ dependencies = [ "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 = "naga" -version = "0.19.2" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ + "arrayvec", "bit-set", "bitflags 2.11.0", + "cfg_aliases", "codespan-reporting", "hexf-parse", "indexmap", "log", - "num-traits", "rustc-hash 1.1.0", "spirv", + "strum", "termcolor", - "thiserror 1.0.69", + "thiserror 2.0.18", "unicode-xid", ] [[package]] name = "ndk" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.11.0", - "jni-sys", + "jni-sys 0.3.0", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", + "raw-window-handle", "thiserror 1.0.69", ] @@ -2109,19 +1988,29 @@ version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ - "jni-sys", + "jni-sys 0.3.0", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.0", ] [[package]] name = "nix" -version = "0.26.4" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.7.1", + "memoffset", ] [[package]] @@ -2155,10 +2044,10 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2168,15 +2057,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] -[[package]] -name = "objc-sys" -version = "0.2.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - [[package]] name = "objc-sys" version = "0.3.5" @@ -2185,32 +2067,37 @@ checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "block2 0.2.0-alpha.6", - "objc-sys 0.2.0-beta.2", - "objc2-encode 2.0.0-pre.2", + "objc-sys", + "objc2-encode", ] [[package]] name = "objc2" -version = "0.4.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "objc-sys 0.3.5", - "objc2-encode 3.0.0", + "objc2-encode", ] [[package]] -name = "objc2" -version = "0.6.4" +name = "objc2-app-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "objc2-encode 4.1.0", + "bitflags 2.11.0", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", ] [[package]] @@ -2221,8 +2108,45 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.11.0", "objc2 0.6.4", + "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2250,19 +2174,28 @@ dependencies = [ ] [[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" +name = "objc2-core-image" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "objc-sys 0.2.0-beta.2", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", ] [[package]] -name = "objc2-encode" -version = "3.0.0" +name = "objc2-core-location" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] [[package]] name = "objc2-encode" @@ -2270,6 +2203,19 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", +] + [[package]] name = "objc2-foundation" version = "0.3.2" @@ -2293,12 +2239,95 @@ dependencies = [ ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-link-presentation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "cc", + "block2", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -2323,6 +2352,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2383,6 +2421,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2396,7 +2454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand", "futures-io", ] @@ -2414,33 +2472,17 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "png" -version = "0.17.16" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" @@ -2449,7 +2491,7 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", "rustix 1.1.4", "windows-sys 0.61.2", @@ -2473,12 +2515,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - [[package]] name = "prettyplease" version = "0.2.37" @@ -2486,17 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "syn", ] [[package]] @@ -2505,7 +2531,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.4+spec-1.1.0", + "toml_edit", ] [[package]] @@ -2518,10 +2544,32 @@ dependencies = [ ] [[package]] -name = "profiling" -version = "1.0.17" +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "quick-xml" @@ -2583,12 +2631,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2597,9 +2639,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] @@ -2622,35 +2664,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[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 = "renderdoc-sys" version = "1.1.0" @@ -2670,17 +2683,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "0.37.28" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "semver", ] [[package]] @@ -2715,6 +2723,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rustwave" +version = "0.1.0" +dependencies = [ + "clap", + "eframe", + "hound", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2738,14 +2755,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.8.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit 0.18.1", + "smithay-client-toolkit 0.19.2", "tiny-skia", ] @@ -2782,7 +2799,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2806,7 +2823,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2842,6 +2859,22 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -2865,13 +2898,13 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.11.0", - "calloop 0.12.4", - "calloop-wayland-source 0.2.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", @@ -2882,8 +2915,8 @@ dependencies = [ "wayland-client", "wayland-csd-frame", "wayland-cursor", - "wayland-protocols 0.31.2", - "wayland-protocols-wlr 0.2.0", + "wayland-protocols", + "wayland-protocols-wlr", "wayland-scanner", "xkeysym", ] @@ -2907,10 +2940,10 @@ dependencies = [ "wayland-client", "wayland-csd-frame", "wayland-cursor", - "wayland-protocols 0.32.11", + "wayland-protocols", "wayland-protocols-experimental", "wayland-protocols-misc", - "wayland-protocols-wlr 0.3.11", + "wayland-protocols-wlr", "wayland-scanner", "xkeysym", ] @@ -2935,16 +2968,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -2979,14 +3002,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "syn" -version = "1.0.109" +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ + "heck", "proc-macro2", "quote", - "unicode-ident", + "rustversion", + "syn", ] [[package]] @@ -3008,7 +3042,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3017,7 +3051,7 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.3.0", + "fastrand", "getrandom 0.4.2", "once_cell", "rustix 1.1.4", @@ -3059,7 +3093,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3070,7 +3104,21 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "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]] @@ -3108,12 +3156,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -3123,17 +3165,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime 0.6.11", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.25.4+spec-1.1.0" @@ -3141,9 +3172,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -3152,7 +3183,7 @@ version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 0.7.15", + "winnow", ] [[package]] @@ -3175,7 +3206,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3214,7 +3245,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ - "memoffset 0.9.1", + "memoffset", "tempfile", "windows-sys 0.61.2", ] @@ -3273,12 +3304,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -3359,7 +3384,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -3454,18 +3479,6 @@ dependencies = [ "xcursor", ] -[[package]] -name = "wayland-protocols" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - [[package]] name = "wayland-protocols" version = "0.32.11" @@ -3487,7 +3500,7 @@ dependencies = [ "bitflags 2.11.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.11", + "wayland-protocols", "wayland-scanner", ] @@ -3500,33 +3513,20 @@ dependencies = [ "bitflags 2.11.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.11", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.31.2", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "d392fc283a87774afc9beefcd6f931582bb97fe0e6ced0b306a62cb1d026527c" dependencies = [ "bitflags 2.11.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.31.2", + "wayland-protocols", "wayland-scanner", ] @@ -3539,7 +3539,7 @@ dependencies = [ "bitflags 2.11.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.11", + "wayland-protocols", "wayland-scanner", ] @@ -3550,7 +3550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.39.2", "quote", ] @@ -3578,9 +3578,9 @@ dependencies = [ [[package]] name = "web-time" -version = "0.2.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3588,35 +3588,41 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.15" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ - "core-foundation", - "home", - "jni", + "core-foundation 0.10.1", + "jni 0.22.3", "log", "ndk-context", - "objc", - "raw-window-handle 0.5.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", "url", "web-sys", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wgpu" -version = "0.19.4" +version = "24.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "cfg-if", + "bitflags 2.11.0", "cfg_aliases", + "document-features", "js-sys", "log", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", @@ -3629,88 +3635,82 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.4" +version = "24.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" dependencies = [ "arrayvec", "bit-vec", "bitflags 2.11.0", "cfg_aliases", - "codespan-reporting", + "document-features", "indexmap", "log", "naga", "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", - "web-sys", + "thiserror 2.0.18", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "0.19.5" +version = "24.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" dependencies = [ "android_system_properties", "arrayvec", "ash", "bitflags 2.11.0", + "bytemuck", "cfg_aliases", "core-graphics-types", "glow", "glutin_wgl_sys", "gpu-alloc", - "gpu-allocator", "gpu-descriptor", - "hassle-rs", "js-sys", "khronos-egl", "libc", - "libloading 0.8.9", + "libloading", "log", "metal", "naga", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", + "ordered-float", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "web-sys", "wgpu-types", - "winapi", + "windows", ] [[package]] name = "wgpu-types" -version = "0.19.2" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ "bitflags 2.11.0", "js-sys", + "log", "web-sys", ] -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "winapi" version = "0.3.9" @@ -3744,20 +3744,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.52.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ "windows-core", "windows-targets 0.52.6", @@ -3765,33 +3754,37 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-implement" -version = "0.48.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "windows-interface" -version = "0.48.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -3800,6 +3793,25 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3811,11 +3823,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3860,21 +3872,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3914,12 +3911,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3938,12 +3929,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3962,12 +3947,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3998,12 +3977,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4022,12 +3995,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4046,12 +4013,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4070,12 +4031,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4090,62 +4045,56 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.29.15" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.11.0", + "block2", "bytemuck", - "calloop 0.12.4", + "calloop 0.13.0", "cfg_aliases", - "core-foundation", + "concurrent-queue", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", - "icrate", + "dpi", "js-sys", "libc", - "log", "memmap2", "ndk", - "ndk-sys", - "objc2 0.4.1", - "once_cell", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", "orbclient", "percent-encoding", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", - "redox_syscall 0.3.5", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit 0.18.1", + "smithay-client-toolkit 0.19.2", "smol_str", + "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", "wayland-backend", "wayland-client", - "wayland-protocols 0.31.2", + "wayland-protocols", "wayland-protocols-plasma", "web-sys", "web-time", - "windows-sys 0.48.0", + "windows-sys 0.52.0", "x11-dl", "x11rb", "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.15" @@ -4185,7 +4134,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4201,7 +4150,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4269,7 +4218,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.9", + "libloading", "once_cell", "rustix 1.1.4", "x11rb-protocol", @@ -4341,36 +4290,33 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] [[package]] name = "zbus" -version = "3.15.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast", "async-executor", "async-fs", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", - "byteorder", - "derivative", "enumflags2", - "event-listener 2.5.3", + "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix", - "once_cell", "ordered-stream", "rand", "serde", @@ -4379,35 +4325,71 @@ dependencies = [ "static_assertions", "tracing", "uds_windows", - "winapi", + "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", "zvariant", ] +[[package]] +name = "zbus-lockstep" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + [[package]] name = "zbus_macros" -version = "3.15.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", - "regex", - "syn 1.0.109", + "syn", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.6.1" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" dependencies = [ + "quick-xml 0.30.0", "serde", "static_assertions", + "zbus_names", "zvariant", ] @@ -4428,7 +4410,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4448,7 +4430,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -4482,7 +4464,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4491,15 +4473,29 @@ 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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" -version = "3.15.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ - "byteorder", + "endi", "enumflags2", - "libc", "serde", "static_assertions", "zvariant_derive", @@ -4507,24 +4503,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.15.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 940c278..a073447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,15 @@ [package] -name = "afsk" +name = "rustwave" version = "0.1.0" edition = "2021" -description = "AFSK audio codec — encode bytes to WAV, decode WAV to bytes" +license = "MIT" +description = "RustWave audio codec — encode bytes to WAV, decode WAV to bytes" + +[[bin]] +name = "rustwave-cli" +path = "src/main.rs" [dependencies] clap = { version = "4", features = ["derive"] } hound = "3" -eframe = "0.27" +eframe = "0.31" diff --git a/README.md b/README.md index b07667b..9c4936b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,17 @@ -# afsk-codec +```markdown +# RustWave Encode arbitrary bytes into a WAV file using Bell-202-style **Audio Frequency-Shift Keying**, and decode them back — losslessly. ``` -afsk encode -i data.bin -o signal.wav -afsk decode -i signal.wav -o data.bin +rustwave encode -i data.bin -o signal.wav +rustwave decode -i signal.wav -o data.bin +``` + +Launch the GUI: + +``` +./rustwave-cli --gui ``` --- @@ -31,14 +38,14 @@ afsk decode -i signal.wav -o data.bin ### Signal parameters -| Parameter | Value | -|----------------|---------------| -| Sample rate | 44 100 Hz | -| Bit rate | 1 200 baud | -| Mark (1) | 1 200 Hz | -| Space (0) | 2 200 Hz | +| Parameter | Value | +|----------------|------------------------------| +| Sample rate | 44 100 Hz | +| Bit rate | 1 200 baud | +| Mark (1) | 1 200 Hz | +| Space (0) | 2 200 Hz | | Modulation | CPFSK (continuous-phase FSK) | -| WAV format | 16-bit signed PCM, mono | +| WAV format | 16-bit signed PCM, mono | | Frame overhead | 28 bytes (preamble + sync + length + CRC) | --- @@ -49,7 +56,7 @@ Requires Rust 1.75+ (2021 edition). ```bash cargo build --release -# binary is at target/release/afsk +# binary is at target/release/rustwave-cli ``` ## Running tests @@ -71,10 +78,11 @@ cargo test ``` src/ - main.rs CLI (clap — encode / decode subcommands) + main.rs CLI (clap — encode / decode subcommands, --gui flag) config.rs Shared constants (sample rate, baud rate, frequencies) framer.rs Byte envelope: preamble, sync word, length, CRC-16/CCITT wav.rs WAV file I/O via hound encoder.rs Bytes → CPFSK audio samples decoder.rs Audio samples → bytes (Goertzel filter, bit-level sync search) ``` +``` \ No newline at end of file diff --git a/clippy_reports/clippy_raw.txt b/clippy_reports/clippy_raw.txt new file mode 100644 index 0000000..164da7d --- /dev/null +++ b/clippy_reports/clippy_raw.txt @@ -0,0 +1,2 @@ + Checking rustwave v0.1.0 (/Users/connordawkins/Documents/GitHub/RustWave) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s diff --git a/clippy_reports/summary.txt b/clippy_reports/summary.txt new file mode 100644 index 0000000..7c60e3e --- /dev/null +++ b/clippy_reports/summary.txt @@ -0,0 +1,5 @@ +dev-check summary — Wed 11 Mar 2026 18:23:21 PDT +Duration: 39s +Passed: 9 +Failed: 0 +Skipped: 1 diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..cb1aa74 --- /dev/null +++ b/deny.toml @@ -0,0 +1,66 @@ +# cargo-deny configuration for RustWave +# https://embarkstudios.github.io/cargo-deny/ + +# --------------------------------------------------------------------------- +# Advisories — known vulnerabilities & unmaintained crates +# --------------------------------------------------------------------------- +[advisories] +version = 2 +yanked = "deny" + +# `paste` (RUSTSEC-2024-0436) is a transitive dep of eframe (via accesskit_windows +# and metal). It is not used by our own code. +ignore = [ + "RUSTSEC-2024-0436", # paste – no longer maintained +] + +# --------------------------------------------------------------------------- +# Licenses +# --------------------------------------------------------------------------- +[licenses] +version = 2 +allow = [ + # Common permissive licences + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib", + "CC0-1.0", + + # Weak-copyleft / other permissive + "MPL-2.0", + + # Unicode data files + "Unicode-3.0", + + # Boost Software Licence — permissive, used by `error-code` (transitive dep of eframe) + "BSL-1.0", + + # Font licences — used by `epaint_default_fonts` (embedded egui fonts) + # OFL-1.1: SIL Open Font Licence — allows free use/embedding in any software + # Ubuntu-font-1.0: Ubuntu Font Licence — permits embedding in applications + "OFL-1.1", + "Ubuntu-font-1.0", +] +unused-allowed-license = "allow" + +# --------------------------------------------------------------------------- +# Bans — duplicate crates / disallowed crates +# --------------------------------------------------------------------------- +[bans] +# All remaining duplicates are transitive deps of eframe/wgpu/winit that we +# cannot resolve directly. Allow them globally rather than maintaining a +# skip list that goes stale every time eframe is updated. +multiple-versions = "allow" +wildcards = "allow" + +# --------------------------------------------------------------------------- +# Sources — only allow crates.io +# --------------------------------------------------------------------------- +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/docs/Dev1.docx b/docs/Dev1.docx deleted file mode 100644 index a4380e97206e9b351a075a2ad11547dd543c0e57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26084 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

%WGS3eR6dYUckjqy)%l(gz6h|GMEP5jTC% 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 V2tNYRJ(znip^)Fi^)EmCzX0T~OPBxv diff --git a/docs/Dev2.docx b/docs/Dev2.docx deleted file mode 100644 index b043ff0910ba3df58d0e95bf443c63e95c5962c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34200 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 fIgfvhF=8Vcl(g9sM{7bFDu?HWh`0`mU>Wm{I< diff --git a/src/decoder.rs b/src/decoder.rs index bec4667..ca5b453 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,4 +1,4 @@ -use crate::config::*; +use crate::config::{BAUD_RATE, MARK_FREQ, SAMPLE_RATE, SPACE_FREQ}; use crate::framer::Decoded; use std::f64::consts::TAU; @@ -7,7 +7,8 @@ pub fn decode_progress( samples: &[f64], on_progress: impl Fn(f32) + Clone, ) -> Result { - let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + let spb = f64::from(SAMPLE_RATE) / f64::from(BAUD_RATE); + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let spb_int = spb.round() as usize; for offset in 0..spb_int { @@ -30,8 +31,15 @@ pub fn decode(samples: &[f64]) -> Result { // Step 1 — sample → bit stream via Goertzel // --------------------------------------------------------------------------- +#[allow( + clippy::cast_precision_loss, // usize → f64 for idx / total: acceptable at these scales + clippy::cast_possible_truncation, // f64.round() → usize: always positive integer + clippy::cast_sign_loss, // f64.round() → usize: value is always ≥ 0 + clippy::arithmetic_side_effects, // float arithmetic cannot panic + clippy::indexing_slicing, // start..end is bounds-checked by the loop guard above +)] fn samples_to_bits(samples: &[f64], offset: usize, on_progress: impl Fn(f32)) -> Vec { - let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + let spb = f64::from(SAMPLE_RATE) / f64::from(BAUD_RATE); let total = samples.len().max(1); let mut bits = Vec::new(); let mut idx: usize = 0; @@ -46,7 +54,8 @@ fn samples_to_bits(samples: &[f64], offset: usize, on_progress: impl Fn(f32)) -> let w = &samples[start..end]; bits.push(goertzel(w, MARK_FREQ, SAMPLE_RATE) > goertzel(w, SPACE_FREQ, SAMPLE_RATE)); - if idx % 32 == 0 { + if idx.is_multiple_of(32) { + #[allow(clippy::cast_precision_loss)] on_progress(end as f32 / total as f32); } idx += 1; @@ -58,6 +67,15 @@ fn samples_to_bits(samples: &[f64], offset: usize, on_progress: impl Fn(f32)) -> // Step 2 — search the bit stream for the frame // --------------------------------------------------------------------------- +// All slice indexing in this function is guarded by explicit bounds checks +// immediately above each access, so indexing_slicing is a false positive here. +// try_into() on a Vec produced by bits_to_bytes with an exact bit-width input +// is guaranteed to succeed, so expect_used is also a false positive. +#[allow( + clippy::arithmetic_side_effects, // integer index arithmetic; bounds checked manually + clippy::indexing_slicing, // every slice is bounds-checked before access + clippy::expect_used, // try_into() cannot fail: Vec length is exact +)] fn find_frame_in_bits(bits: &[bool]) -> Result { let sync_bits: Vec = [0x7E_u8, 0x7E] .iter() @@ -77,17 +95,16 @@ fn find_frame_in_bits(bits: &[bool]) -> Result { let sync_start = search + rel; let mut cursor = sync_start + sync_len; - // ── name_len (u16 LE) ────────────────────────────────────────── + // ── name_len (u16 LE) ───────────────────────────────────────── if cursor + 16 > bits.len() { break; } let name_len = { let b = bits_to_bytes(&bits[cursor..cursor + 16]); - u16::from_le_bytes(b.try_into().unwrap()) as usize + u16::from_le_bytes(b.try_into().expect("bits_to_bytes(16 bits) = 2 bytes")) as usize }; cursor += 16; - // Sanity: filenames shouldn't exceed 255 bytes if name_len > 255 { search = sync_start + 1; continue; @@ -109,7 +126,7 @@ fn find_frame_in_bits(bits: &[bool]) -> Result { } let payload_len = { let b = bits_to_bytes(&bits[cursor..cursor + 32]); - u32::from_le_bytes(b.try_into().unwrap()) as usize + u32::from_le_bytes(b.try_into().expect("bits_to_bytes(32 bits) = 4 bytes")) as usize }; cursor += 32; @@ -128,13 +145,11 @@ fn find_frame_in_bits(bits: &[bool]) -> Result { } // ── CRC check ───────────────────────────────────────────────── - // CRC covers: sync(16) + name_len(16) + name(N*8) + payload_len(32) + payload(M*8) - let frame_bits_end = payload_end; - let frame_bytes = bits_to_bytes(&bits[sync_start..frame_bits_end]); + let frame_bytes = bits_to_bytes(&bits[sync_start..payload_end]); let computed = crate::framer::crc16(&frame_bytes); let stored = { let b = bits_to_bytes(&bits[payload_end..crc_end]); - u16::from_le_bytes(b.try_into().unwrap()) + u16::from_le_bytes(b.try_into().expect("bits_to_bytes(16 bits) = 2 bytes")) }; if stored == computed { @@ -154,25 +169,27 @@ fn find_frame_in_bits(bits: &[bool]) -> Result { // Non-integer Goertzel DFT // --------------------------------------------------------------------------- +#[allow(clippy::arithmetic_side_effects)] // float arithmetic cannot panic fn goertzel(samples: &[f64], freq: f64, sample_rate: u32) -> f64 { - let w = TAU * freq / sample_rate as f64; + let w = TAU * freq / f64::from(sample_rate); let coeff = 2.0 * w.cos(); let (mut s1, mut s2) = (0.0_f64, 0.0_f64); for &x in samples { - let s0 = x + coeff * s1 - s2; + let s0 = x.mul_add(1.0, coeff.mul_add(s1, -s2)); s2 = s1; s1 = s0; } - let real = s1 - s2 * w.cos(); + let real = s2.mul_add(-w.cos(), s1); let imag = s2 * w.sin(); - real * real + imag * imag + real.mul_add(real, imag * imag) } // --------------------------------------------------------------------------- // Bit / byte helpers // --------------------------------------------------------------------------- -pub(crate) fn bits_to_bytes(bits: &[bool]) -> Vec { +#[allow(clippy::arithmetic_side_effects)] // i is always 0..=7 from enumerate() on chunks(8) +pub fn bits_to_bytes(bits: &[bool]) -> Vec { bits.chunks(8) .map(|chunk| { chunk @@ -193,13 +210,19 @@ mod tests { use crate::{encoder, framer}; #[test] + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_precision_loss, + clippy::arithmetic_side_effects + )] fn goertzel_discriminates_mark_vs_space() { - let spb = (SAMPLE_RATE as f64 / BAUD_RATE as f64).round() as usize; + let spb = (f64::from(SAMPLE_RATE) / f64::from(BAUD_RATE)).round() as usize; let mark_samples: Vec = (0..spb) - .map(|i| (TAU * MARK_FREQ * i as f64 / SAMPLE_RATE as f64).sin()) + .map(|i| (TAU * MARK_FREQ * i as f64 / f64::from(SAMPLE_RATE)).sin()) .collect(); let space_samples: Vec = (0..spb) - .map(|i| (TAU * SPACE_FREQ * i as f64 / SAMPLE_RATE as f64).sin()) + .map(|i| (TAU * SPACE_FREQ * i as f64 / f64::from(SAMPLE_RATE)).sin()) .collect(); assert!( @@ -213,35 +236,39 @@ mod tests { } #[test] - fn full_round_trip_text() { + fn full_round_trip_text() -> Result<(), String> { let payload = b"Hello, AFSK!"; let samples = encoder::encode(&framer::frame(payload, "hello.txt")); - let decoded = decode(&samples).expect("decode failed"); + let decoded = decode(&samples)?; assert_eq!(decoded.data, payload); assert_eq!(decoded.filename, "hello.txt"); + Ok(()) } #[test] - fn full_round_trip_all_bytes() { + fn full_round_trip_all_bytes() -> Result<(), String> { let payload: Vec = (0u8..=255).collect(); let samples = encoder::encode(&framer::frame(&payload, "all.bin")); - let decoded = decode(&samples).expect("decode failed"); + let decoded = decode(&samples)?; assert_eq!(decoded.data, payload); assert_eq!(decoded.filename, "all.bin"); + Ok(()) } #[test] - fn full_round_trip_empty() { + fn full_round_trip_empty() -> Result<(), String> { let samples = encoder::encode(&framer::frame(&[], "empty.bin")); - let decoded = decode(&samples).expect("decode failed"); + let decoded = decode(&samples)?; assert!(decoded.data.is_empty()); + Ok(()) } #[test] - fn filename_with_dots_preserved() { + fn filename_with_dots_preserved() -> Result<(), String> { let payload = b"compressed archive"; let samples = encoder::encode(&framer::frame(payload, "archive.tar.gz")); - let decoded = decode(&samples).expect("decode failed"); + let decoded = decode(&samples)?; assert_eq!(decoded.filename, "archive.tar.gz"); + Ok(()) } } diff --git a/src/encoder.rs b/src/encoder.rs index 44910f7..bc49c7f 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,10 +1,16 @@ -use crate::config::*; +use crate::config::{AMPLITUDE, BAUD_RATE, MARK_FREQ, SAMPLE_RATE, SPACE_FREQ}; use std::f64::consts::TAU; /// Encode `framed` bytes into PCM audio samples. /// Calls `on_progress` with a value in 0.0..=1.0 as bits are processed. +#[allow( + clippy::cast_precision_loss, // usize → f64 for idx / total_bits + clippy::cast_possible_truncation, // f64 → usize for silence_len / start / end + clippy::cast_sign_loss, // f64.round() → usize (always positive) + clippy::arithmetic_side_effects, // float arithmetic; no panic risk +)] pub fn encode_progress(framed: &[u8], on_progress: impl Fn(f32)) -> Vec { - let spb = SAMPLE_RATE as f64 / BAUD_RATE as f64; + let spb = f64::from(SAMPLE_RATE) / f64::from(BAUD_RATE); let bits: Vec = framed .iter() @@ -12,16 +18,16 @@ pub fn encode_progress(framed: &[u8], on_progress: impl Fn(f32)) -> Vec { .collect(); let total_bits = bits.len().max(1); - let silence_len = (SAMPLE_RATE as f64 * 0.05) as usize; - let signal_len = ((bits.len() as f64) * spb).round() as usize; + let silence_len = (f64::from(SAMPLE_RATE) * 0.05) as usize; + let signal_len = (bits.len() as f64 * spb).round() as usize; let mut samples = Vec::with_capacity(silence_len * 2 + signal_len); - samples.extend(std::iter::repeat(0.0_f64).take(silence_len)); + samples.extend(std::iter::repeat_n(0.0_f64, silence_len)); let mut phase = 0.0_f64; for (idx, &bit) in bits.iter().enumerate() { let freq = if bit { MARK_FREQ } else { SPACE_FREQ }; - let phase_inc = TAU * freq / SAMPLE_RATE as f64; + let phase_inc = TAU * freq / f64::from(SAMPLE_RATE); let start = (idx as f64 * spb).round() as usize; let end = ((idx + 1) as f64 * spb).round() as usize; @@ -31,13 +37,12 @@ pub fn encode_progress(framed: &[u8], on_progress: impl Fn(f32)) -> Vec { phase = (phase + phase_inc) % TAU; } - // Report progress every 64 bits to avoid lock contention if idx % 64 == 0 { on_progress(idx as f32 / total_bits as f32); } } - samples.extend(std::iter::repeat(0.0_f64).take(silence_len)); + samples.extend(std::iter::repeat_n(0.0_f64, silence_len)); on_progress(1.0); samples } diff --git a/src/framer.rs b/src/framer.rs index e4b7f52..6368759 100644 --- a/src/framer.rs +++ b/src/framer.rs @@ -1,33 +1,37 @@ /// Frame layout (v2 — stores original filename) /// /// ┌──────────────┬────────┬──────────────┬──────┬──────────────┬─────────┬────────┐ -/// │ preamble N×AA│ 7E 7E │ name_len u16 │ name │ payload_len │ payload │ CRC-16 │ +/// │ preamble N×AA│ 7E 7E │ `name_len` u16 │ name │ `payload_len` │ payload │ CRC-16 │ /// └──────────────┴────────┴──────────────┴──────┴──────────────┴─────────┴────────┘ /// ◄──────────────────── CRC covers this span ──────────────────────► use crate::config::{PREAMBLE_LEN, SYNC}; /// Wrap `data` in a transmittable frame, embedding `filename` so the decoder /// can reconstruct the file with the correct name and extension. +#[allow( + clippy::arithmetic_side_effects, // capacity arithmetic is safe; values are small by construction + clippy::indexing_slicing, // out[PREAMBLE_LEN..] is valid: preamble bytes are always pushed first +)] pub fn frame(data: &[u8], filename: &str) -> Vec { let name_bytes = filename.as_bytes(); let name_len = name_bytes.len().min(255); // cap at 255 bytes - let name_bytes = &name_bytes[..name_len]; + let name_bytes = name_bytes.get(..name_len).unwrap_or(name_bytes); let capacity = PREAMBLE_LEN + 2 + 2 + name_len + 4 + data.len() + 2; let mut out = Vec::with_capacity(capacity); // Preamble - out.extend(std::iter::repeat(0xAA_u8).take(PREAMBLE_LEN)); + out.extend(std::iter::repeat_n(0xAA_u8, PREAMBLE_LEN)); // Sync word out.extend_from_slice(&SYNC); // Filename length (u16 LE) + filename bytes - out.extend_from_slice(&(name_len as u16).to_le_bytes()); + out.extend_from_slice(&u16::try_from(name_len).unwrap_or(255).to_le_bytes()); out.extend_from_slice(name_bytes); // Payload length (u32 LE) + payload - out.extend_from_slice(&(data.len() as u32).to_le_bytes()); + out.extend_from_slice(&u32::try_from(data.len()).unwrap_or(u32::MAX).to_le_bytes()); out.extend_from_slice(data); // CRC-16/CCITT over everything from sync word onwards (not the preamble) @@ -47,7 +51,11 @@ pub struct Decoded { /// /// Used only by the byte-level path (tests / CLI verification). /// The audio decoder uses `find_frame_in_bits` in decoder.rs directly. -#[allow(dead_code)] +#[allow( + dead_code, + clippy::arithmetic_side_effects, // cursor arithmetic is bounds-checked before each use + clippy::indexing_slicing, // all slices are bounds-checked immediately above each access +)] pub fn deframe(raw: &[u8]) -> Result { let sync_pos = raw .windows(SYNC.len()) @@ -60,7 +68,11 @@ pub fn deframe(raw: &[u8]) -> Result { if raw.len() < cursor + 2 { return Err("frame too short: missing name_len".into()); } - let name_len = u16::from_le_bytes(raw[cursor..cursor + 2].try_into().unwrap()) as usize; + let name_len = u16::from_le_bytes( + raw[cursor..cursor + 2] + .try_into() + .map_err(|_| "internal: name_len slice error".to_string())?, + ) as usize; cursor += 2; // name bytes @@ -74,7 +86,11 @@ pub fn deframe(raw: &[u8]) -> Result { if raw.len() < cursor + 4 { return Err("frame too short: missing payload_len".into()); } - let payload_len = u32::from_le_bytes(raw[cursor..cursor + 4].try_into().unwrap()) as usize; + let payload_len = u32::from_le_bytes( + raw[cursor..cursor + 4] + .try_into() + .map_err(|_| "internal: payload_len slice error".to_string())?, + ) as usize; cursor += 4; let payload_end = cursor + payload_len; @@ -88,7 +104,11 @@ pub fn deframe(raw: &[u8]) -> Result { )); } - let stored_crc = u16::from_le_bytes(raw[payload_end..crc_end].try_into().unwrap()); + let stored_crc = u16::from_le_bytes( + raw[payload_end..crc_end] + .try_into() + .map_err(|_| "internal: CRC slice error".to_string())?, + ); let computed_crc = crc16(&raw[sync_pos..payload_end]); if stored_crc != computed_crc { @@ -107,10 +127,11 @@ pub fn deframe(raw: &[u8]) -> Result { // CRC-16/CCITT (polynomial 0x1021, init 0xFFFF, no bit-reflection) // --------------------------------------------------------------------------- -pub(crate) fn crc16(data: &[u8]) -> u16 { +#[allow(clippy::arithmetic_side_effects)] // bit-shifting in CRC polynomial; no panic risk +pub fn crc16(data: &[u8]) -> u16 { let mut crc: u16 = 0xFFFF; for &byte in data { - crc ^= (byte as u16) << 8; + crc ^= u16::from(byte) << 8; for _ in 0..8 { crc = if crc & 0x8000 != 0 { (crc << 1) ^ 0x1021 @@ -131,6 +152,7 @@ mod tests { use super::*; fn rt(data: &[u8], name: &str) -> Decoded { + #[allow(clippy::unwrap_used)] deframe(&frame(data, name)).unwrap() } @@ -166,12 +188,18 @@ mod tests { let mut framed = frame(b"test", "test.txt"); framed.insert(0, 0xFF); framed.insert(0, 0x42); + #[allow(clippy::unwrap_used)] let d = deframe(&framed).unwrap(); assert_eq!(d.data, b"test"); assert_eq!(d.filename, "test.txt"); } #[test] + #[allow( + clippy::unwrap_used, + clippy::indexing_slicing, + clippy::arithmetic_side_effects + )] fn crc_detects_corruption() { let mut framed = frame(b"integrity check", "check.txt"); // Corrupt a payload byte (past preamble+sync+namelen+name+payloadlen) diff --git a/src/gui.rs b/src/gui.rs index 102cc1e..b8b04b4 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,4 +1,4 @@ -//! GUI front-end — launched with `afsk -gui`. +//! GUI front-end — launched with `rustwave-cli -gui`. //! //! Drag any file onto the window: //! • WAV → decoded, output saved with the ORIGINAL filename next to the binary @@ -10,7 +10,7 @@ use std::sync::{mpsc, Arc}; use std::thread; use std::time::Duration; -use eframe::egui::{self, Color32, FontId, Pos2, Rect, Rounding, Stroke, Vec2}; +use eframe::egui::{self, Color32, CornerRadius, FontId, Pos2, Rect, Stroke, Vec2}; // ─── State machine ─────────────────────────────────────────────────────────── @@ -36,15 +36,14 @@ pub struct AfskGui { } impl AfskGui { - pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { + pub const fn new(_cc: &eframe::CreationContext<'_>) -> Self { Self { state: State::Idle } } fn start_processing(&mut self, path: PathBuf, ctx: egui::Context) { let is_wav = path .extension() - .map(|e| e.to_ascii_lowercase() == "wav") - .unwrap_or(false); + .is_some_and(|e| e.eq_ignore_ascii_case("wav")); let filename = path .file_name() @@ -67,29 +66,24 @@ impl AfskGui { let prog = Arc::clone(&progress); let ctx2 = ctx.clone(); let on_progress = move |v: f32| { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] prog.store((v.clamp(0.0, 1.0) * 1_000_000.0) as u32, Ordering::Relaxed); ctx2.request_repaint_after(Duration::from_millis(16)); }; let outcome: Result = if is_wav { - // ── Decode WAV → original file ─────────────────────── crate::wav::read(&path) - .and_then(|samples| { - crate::decoder::decode_progress(&samples, on_progress).map_err(|e| e) - }) + .and_then(|samples| crate::decoder::decode_progress(&samples, on_progress)) .and_then(|decoded| { - // Use the filename baked into the signal let out = binary_dir.join(&decoded.filename); std::fs::write(&out, &decoded.data) - .map(|_| out) + .map(|()| out) .map_err(|e| e.to_string()) }) } else { - // ── Encode file → WAV ──────────────────────────────── std::fs::read(&path) .map_err(|e| e.to_string()) .map(|data| { - // Store the original filename in the frame header let orig_name = path .file_name() .unwrap_or_default() @@ -101,7 +95,7 @@ impl AfskGui { .and_then(|samples| { let stem = path.file_stem().unwrap_or_default().to_string_lossy(); let out = binary_dir.join(format!("{stem}_encoded.wav")); - crate::wav::write(&out, &samples).map(|_| out) + crate::wav::write(&out, &samples).map(|()| out) }) }; @@ -118,19 +112,12 @@ impl AfskGui { rx, }; } -} -// ─── eframe::App ───────────────────────────────────────────────────────────── - -impl eframe::App for AfskGui { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Poll worker result + /// Poll the worker channel and advance state if the worker has finished. + fn poll_worker(&mut self) { let finished: Option<(&'static str, Result)> = if let State::Processing { rx, action, .. } = &self.state { - match rx.try_recv() { - Ok(result) => Some((action, result)), - Err(_) => None, - } + rx.try_recv().ok().map(|result| (*action, result)) } else { None }; @@ -141,8 +128,81 @@ impl eframe::App for AfskGui { Err(e) => State::Failed(e), }; } + } + + /// Draw the drop zone and its current contents. + fn draw_zone( + &self, + ui: &mut egui::Ui, + zone_size: Vec2, + hovering: bool, + accent: Color32, + bg_panel: Color32, + dim_text: Color32, + ) { + ui.vertical_centered(|ui| { + let (rect, _) = ui.allocate_exact_size(zone_size, egui::Sense::hover()); + + let fill = if hovering { + Color32::from_rgba_premultiplied(100, 145, 235, 15) + } else { + bg_panel + }; + let border = if hovering { + accent + } else { + Color32::from_rgb(50, 55, 75) + }; + + ui.painter().rect_filled(rect, CornerRadius::same(10), fill); + dashed_border(ui.painter(), rect, Stroke::new(1.5, border)); + + match &self.state { + State::Idle => draw_idle(ui.painter(), rect, hovering, accent, dim_text), + State::Processing { + filename, + action, + progress, + .. + } => { + #[allow(clippy::cast_precision_loss)] + // u32 progress value; precision loss is fine + let v = progress.load(Ordering::Relaxed) as f32 / 1_000_000.0; + draw_processing(ui.painter(), rect, action, filename, v, accent, dim_text); + } + State::Done { action, output } => { + draw_result( + ui.painter(), + rect, + true, + &format!("{action} complete"), + &output.to_string_lossy(), + accent, + dim_text, + ); + } + State::Failed(err) => { + draw_result( + ui.painter(), + rect, + false, + "Error", + err, + Color32::from_rgb(220, 85, 85), + dim_text, + ); + } + } + }); + } +} + +// ─── eframe::App ───────────────────────────────────────────────────────────── + +impl eframe::App for AfskGui { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + self.poll_worker(); - // Accept dropped files let dropped = ctx.input(|i| i.raw.dropped_files.clone()); if let Some(f) = dropped.first() { if let Some(p) = &f.path { @@ -157,23 +217,22 @@ impl eframe::App for AfskGui { } let hovering = ctx.input(|i| !i.raw.hovered_files.is_empty()); - let bg_dark = Color32::from_rgb(15, 15, 20); let bg_panel = Color32::from_rgb(22, 22, 30); let accent = Color32::from_rgb(100, 145, 235); let dim_text = Color32::from_rgb(100, 105, 130); egui::CentralPanel::default() - .frame(egui::Frame::none().fill(bg_dark)) + .frame(egui::Frame::NONE.fill(bg_dark)) .show(ctx, |ui| { let avail = ui.available_size(); - ui.add_space(18.0); ui.vertical_centered(|ui| { + #[allow(clippy::arithmetic_side_effects)] // egui Vec2 addition; no panic risk ui.painter().text( ui.next_widget_position() + Vec2::new(avail.x / 2.0, 0.0), egui::Align2::CENTER_TOP, - "AFSK Codec", + "RustWave", FontId::proportional(20.0), Color32::from_rgb(170, 175, 200), ); @@ -181,70 +240,7 @@ impl eframe::App for AfskGui { }); let zone_size = Vec2::new((avail.x - 40.0).max(240.0), (avail.y - 90.0).max(160.0)); - - ui.vertical_centered(|ui| { - let (rect, _) = ui.allocate_exact_size(zone_size, egui::Sense::hover()); - - let fill = if hovering { - Color32::from_rgba_premultiplied(100, 145, 235, 15) - } else { - bg_panel - }; - let border = if hovering { - accent - } else { - Color32::from_rgb(50, 55, 75) - }; - - ui.painter().rect_filled(rect, Rounding::same(10.0), fill); - dashed_border(ui.painter(), rect, Stroke::new(1.5, border)); - - match &self.state { - State::Idle => { - draw_idle(ui.painter(), rect, hovering, accent, dim_text); - } - State::Processing { - filename, - action, - progress, - .. - } => { - let v = progress.load(Ordering::Relaxed) as f32 / 1_000_000.0; - draw_processing( - ui.painter(), - rect, - action, - filename, - v, - accent, - dim_text, - ); - } - State::Done { action, output } => { - let label = format!("{} complete", action); - draw_result( - ui.painter(), - rect, - true, - &label, - &output.to_string_lossy(), - accent, - dim_text, - ); - } - State::Failed(err) => { - draw_result( - ui.painter(), - rect, - false, - "Error", - err, - Color32::from_rgb(220, 85, 85), - dim_text, - ); - } - } - }); + self.draw_zone(ui, zone_size, hovering, accent, bg_panel, dim_text); }); } } @@ -266,36 +262,40 @@ fn draw_idle(painter: &egui::Painter, zone: Rect, hovering: bool, accent: Color3 Color32::from_rgb(210, 215, 230) }; - painter.text( - Pos2::new(cx, cy - 28.0), - egui::Align2::CENTER_CENTER, - heading, - FontId::proportional(18.0), - heading_color, - ); - painter.text( - Pos2::new(cx, cy + 8.0), - egui::Align2::CENTER_CENTER, - "WAV → restores original file + extension", - FontId::proportional(12.5), - dim, - ); - painter.text( - Pos2::new(cx, cy + 25.0), - egui::Align2::CENTER_CENTER, - "Other → encode to .wav", - FontId::proportional(12.5), - dim, - ); - painter.text( - Pos2::new(cx, zone.max.y - 18.0), - egui::Align2::CENTER_CENTER, - "Output saved next to the binary", - FontId::proportional(11.0), - Color32::from_rgb(60, 65, 85), - ); + #[allow(clippy::arithmetic_side_effects)] + { + painter.text( + Pos2::new(cx, cy - 28.0), + egui::Align2::CENTER_CENTER, + heading, + FontId::proportional(18.0), + heading_color, + ); + painter.text( + Pos2::new(cx, cy + 8.0), + egui::Align2::CENTER_CENTER, + "WAV → restores original file + extension", + FontId::proportional(12.5), + dim, + ); + painter.text( + Pos2::new(cx, cy + 25.0), + egui::Align2::CENTER_CENTER, + "Other → encode to .wav", + FontId::proportional(12.5), + dim, + ); + painter.text( + Pos2::new(cx, zone.max.y - 18.0), + egui::Align2::CENTER_CENTER, + "Output saved next to the binary", + FontId::proportional(11.0), + Color32::from_rgb(60, 65, 85), + ); + } } +#[allow(clippy::arithmetic_side_effects)] fn draw_processing( painter: &egui::Painter, zone: Rect, @@ -325,12 +325,16 @@ fn draw_processing( let bar_w = (zone.width() - 70.0).max(80.0); let bar_rect = Rect::from_center_size(Pos2::new(cx, cy + 8.0), Vec2::new(bar_w, 10.0)); - painter.rect_filled(bar_rect, Rounding::same(5.0), Color32::from_rgb(35, 37, 52)); + painter.rect_filled( + bar_rect, + CornerRadius::same(5), + Color32::from_rgb(35, 37, 52), + ); let filled_w = (bar_rect.width() * progress.clamp(0.0, 1.0)).max(0.0); if filled_w >= 1.0 { let filled = Rect::from_min_size(bar_rect.min, Vec2::new(filled_w, bar_rect.height())); - painter.rect_filled(filled, Rounding::same(5.0), accent); + painter.rect_filled(filled, CornerRadius::same(5), accent); } painter.text( @@ -342,6 +346,7 @@ fn draw_processing( ); } +#[allow(clippy::arithmetic_side_effects)] fn draw_result( painter: &egui::Painter, zone: Rect, @@ -392,9 +397,16 @@ fn draw_result( ); } +#[allow( + clippy::arithmetic_side_effects, // float/Vec2 arithmetic in egui types; no panic risk + clippy::cast_possible_truncation, // f32.ceil() → usize: always positive + clippy::cast_sign_loss, // f32.ceil() → usize: always positive + clippy::cast_precision_loss, // usize i → f32: acceptable for pixel coordinates +)] fn dashed_border(painter: &egui::Painter, rect: Rect, stroke: Stroke) { let dash = 8.0_f32; let gap = 5.0_f32; + let step = dash + gap; let r = 10.0_f32; let seg = |from: Pos2, to: Pos2| { @@ -404,12 +416,12 @@ fn dashed_border(painter: &egui::Painter, rect: Rect, stroke: Stroke) { return; } let dir = delta / len; - let mut t = 0.0_f32; - while t < len { + let steps = (len / step).ceil() as usize; + for i in 0..steps { + let t = i as f32 * step; let a = from + dir * t; let b = from + dir * (t + dash).min(len); painter.line_segment([a, b], stroke); - t += dash + gap; } }; @@ -438,14 +450,14 @@ pub fn run() -> eframe::Result<()> { viewport: egui::ViewportBuilder::default() .with_inner_size([480.0, 340.0]) .with_min_inner_size([360.0, 260.0]) - .with_title("AFSK Codec") + .with_title("RustWave") .with_drag_and_drop(true), ..Default::default() }; eframe::run_native( - "AFSK Codec", + "RustWave", options, - Box::new(|cc| Box::new(AfskGui::new(cc)) as Box), + Box::new(|cc| Ok(Box::new(AfskGui::new(cc)) as Box)), ) } diff --git a/src/main.rs b/src/main.rs index 5c5d343..2987db7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use clap::{Parser, Subcommand}; use std::path::PathBuf; #[derive(Parser)] -#[command(name = "afsk", version, about = "AFSK audio codec", long_about = None)] +#[command(name = "rustwave-cli", version, about = "RustWave audio codec", long_about = None)] struct Cli { #[command(subcommand)] command: Command, @@ -53,7 +53,6 @@ fn main() { std::process::exit(1); }); - // Store just the filename (not full path) in the frame let filename = input .file_name() .unwrap_or_default() @@ -68,7 +67,8 @@ fn main() { std::process::exit(1); } - let duration = samples.len() as f64 / config::SAMPLE_RATE as f64; + #[allow(clippy::cast_precision_loss)] + let duration = samples.len() as f64 / f64::from(config::SAMPLE_RATE); eprintln!( "encoded '{}' ({} byte{}) -> {} ({:.2} s)", filename, @@ -90,7 +90,6 @@ fn main() { std::process::exit(1); }); - // Use caller-supplied path, or reconstruct from stored filename let out_path = output.unwrap_or_else(|| { input .parent() @@ -114,7 +113,7 @@ fn main() { } } -fn plural(n: usize) -> &'static str { +const fn plural(n: usize) -> &'static str { if n == 1 { "" } else { diff --git a/src/wav.rs b/src/wav.rs index 738d25a..b81d3bd 100644 --- a/src/wav.rs +++ b/src/wav.rs @@ -15,6 +15,8 @@ pub fn write(path: &Path, samples: &[f64]) -> Result<(), String> { let mut writer = WavWriter::create(path, spec).map_err(|e| e.to_string())?; for &s in samples { + // clamp guarantees the value is in [-32767, 32767] before truncation + #[allow(clippy::cast_possible_truncation)] let v = (s.clamp(-1.0, 1.0) * 32_767.0) as i16; writer.write_sample(v).map_err(|e| e.to_string())?; } @@ -34,13 +36,16 @@ pub fn read(path: &Path) -> Result, String> { let channels = spec.channels as usize; reader .samples::() - .step_by(channels) // take only the first channel - .map(|s| s.map(|v| v as f64 / 32_768.0).map_err(|e| e.to_string())) + .step_by(channels) + .map(|s| { + s.map(|v| f64::from(v) / 32_768.0) + .map_err(|e| e.to_string()) + }) .collect() } (bits, fmt) => Err(format!( "unsupported WAV format: {bits}-bit {fmt:?} \ - (afsk expects 16-bit integer PCM)" + (rustwave-cli expects 16-bit integer PCM)" )), } } @@ -59,28 +64,29 @@ mod tests { } #[test] - fn silence_round_trip() { - let path = tmp("afsk_wav_silence.wav"); + fn silence_round_trip() -> Result<(), String> { + let path = tmp("rustwave_wav_silence.wav"); let original = vec![0.0_f64; 4_410]; - write(&path, &original).unwrap(); - let recovered = read(&path).unwrap(); + write(&path, &original)?; + let recovered = read(&path)?; assert_eq!(original.len(), recovered.len()); for v in recovered { assert!(v.abs() < 2.0 / 32_768.0, "expected silence, got {v}"); } let _ = std::fs::remove_file(&path); + Ok(()) } #[test] - fn sine_round_trip() { - let path = tmp("afsk_wav_sine.wav"); - let original: Vec = (0..44_100) - .map(|i| 0.5 * (TAU * 440.0 * i as f64 / 44_100.0).sin()) + fn sine_round_trip() -> Result<(), String> { + let path = tmp("rustwave_wav_sine.wav"); + #[allow(clippy::cast_precision_loss)] + let original: Vec = (0..44_100_i32) + .map(|i| 0.5 * (TAU * 440.0 * f64::from(i) / 44_100.0).sin()) .collect(); - write(&path, &original).unwrap(); - let recovered = read(&path).unwrap(); + write(&path, &original)?; + let recovered = read(&path)?; assert_eq!(original.len(), recovered.len()); - // Max 16-bit quantisation error ≈ 1.5 × 10⁻⁵ for (a, b) in original.iter().zip(recovered.iter()) { assert!( (a - b).abs() < 5e-5, @@ -88,5 +94,6 @@ mod tests { ); } let _ = std::fs::remove_file(&path); + Ok(()) } } From 93dafab07a3df5e4979e5e39df75f1939027b93c Mon Sep 17 00:00:00 2001 From: csd113 Date: Wed, 11 Mar 2026 18:48:22 -0700 Subject: [PATCH 3/5] Update README.md --- README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9c4936b..7a1852d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,76 @@ -```markdown # RustWave -Encode arbitrary bytes into a WAV file using Bell-202-style **Audio Frequency-Shift Keying**, and decode them back — losslessly. +Encode arbitrary bytes into a WAV file using Bell-202-style Audio Frequency-Shift Keying (AFSK), and decode them back — losslessly. +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Rust](https://img.shields.io/badge/rust-1.75%2B-orange.svg)](https://www.rust-lang.org/) +[![docs](https://img.shields.io/badge/docs-cargo%20doc-green.svg)](#api-documentation) + +--- + +## Why RustWave? + +Most data-over-audio tools are tightly coupled to specific protocols (APRS, KISS, etc.) or sacrifice correctness for simplicity. RustWave is a self-contained, dependency-light Rust library and CLI that gives you a clean primitive: **any byte buffer in → a valid WAV file out → the same byte buffer back**, with a robust framing layer, CRC-16 integrity checking, and a Goertzel-filter decoder that tolerates real-world signal noise and arbitrary byte offsets. + +It is also the audio codec layer underpinning [HAMNET-RELAY](docs/hamnet-relay-build-roadmap.md), a planned HAM radio data relay that bridges AX.25 packet radio with a local HTTP/WebSocket API. + +--- + +## Installation + +Requires **Rust 1.75+** (2021 edition). + +```bash +cargo install rustwave ``` + +Or build from source: + +```bash +git clone https://github.com/csd113/rustwave +cd rustwave +cargo build --release +# binary at: target/release/rustwave-cli +``` + +--- + +## Basic Usage + +### CLI + +```bash +# Encode a file to WAV rustwave encode -i data.bin -o signal.wav + +# Decode a WAV back to bytes rustwave decode -i signal.wav -o data.bin + +# Launch the graphical interface +rustwave-cli --gui ``` -Launch the GUI: +### Library -``` -./rustwave-cli --gui +```rust +use rustwave::{encode, decode}; + +fn main() { + let payload = b"Hello, RustWave!"; + + // Encode bytes → WAV samples + let wav_path = "signal.wav"; + encode(payload, wav_path).expect("encode failed"); + + // Decode WAV → original bytes + let recovered = decode(wav_path).expect("decode failed"); + assert_eq!(payload, recovered.as_slice()); +} ``` --- -## How it works +## How It Works ``` [ your bytes ] @@ -36,45 +90,41 @@ Launch the GUI: [ your bytes ] ``` -### Signal parameters +### Signal Parameters -| Parameter | Value | -|----------------|------------------------------| -| Sample rate | 44 100 Hz | -| Bit rate | 1 200 baud | -| Mark (1) | 1 200 Hz | -| Space (0) | 2 200 Hz | -| Modulation | CPFSK (continuous-phase FSK) | -| WAV format | 16-bit signed PCM, mono | +| Parameter | Value | +|----------------|-------------------------------------------| +| Sample rate | 44 100 Hz | +| Bit rate | 1 200 baud | +| Mark (1) | 1 200 Hz | +| Space (0) | 2 200 Hz | +| Modulation | CPFSK (continuous-phase FSK) | +| WAV format | 16-bit signed PCM, mono | | Frame overhead | 28 bytes (preamble + sync + length + CRC) | --- -## Building +## Advanced Usage -Requires Rust 1.75+ (2021 edition). +### Framing & CRC -```bash -cargo build --release -# binary is at target/release/rustwave-cli -``` +The framer wraps every payload in a Bell-202-compatible envelope. A 24-byte `0xAA` preamble allows the decoder to lock onto the signal, followed by a `0x7E 0x7E` sync word, a 4-byte little-endian length field, the raw payload, and a CRC-16/CCITT checksum. The decoder performs a bit-level sync-word search that handles arbitrary byte offsets in the audio stream. + +### GUI Mode -## Running tests +Launch the built-in `eframe`-powered GUI for drag-and-drop encoding and decoding without the CLI: ```bash -cargo test +./rustwave-cli --gui ``` -12 unit + integration tests cover: -- Framer round-trips (empty, ASCII, all-256-bytes, corrupt CRC) -- CRC-16/CCITT known-value check -- WAV write/read round-trip (silence and sine wave) -- Goertzel mark-vs-space discrimination -- Full encode→decode round-trips (empty, text, all byte values) +### HAMNET-RELAY Integration + +RustWave is designed as the AFSK codec layer for [HAMNET-RELAY](docs/hamnet-relay-build-roadmap.md), a multi-phase HAM radio data relay project that adds AX.25 packet framing, CSMA channel access, a local HTTP/WebSocket API, and full RustChan imageboard integration on top of this codec. See [`docs/hamnet-relay-build-roadmap.md`](docs/hamnet-relay-build-roadmap.md) for the full architecture. --- -## Project layout +## Project Layout ``` src/ @@ -85,4 +135,64 @@ src/ encoder.rs Bytes → CPFSK audio samples decoder.rs Audio samples → bytes (Goertzel filter, bit-level sync search) ``` -``` \ No newline at end of file + +--- + +## Running Tests + +```bash +cargo test +``` + +12 unit and integration tests cover: + +- Framer round-trips (empty payload, ASCII, all 256 byte values, corrupt CRC) +- CRC-16/CCITT known-value verification +- WAV write/read round-trip (silence and sine wave) +- Goertzel mark-vs-space discrimination +- Full encode → decode round-trips (empty, text, all byte values) + +### Development Quality Gate + +A strict dev-check script runs `fmt`, `clippy` (pedantic + nursery), `cargo test`, `cargo audit`, and `cargo deny` in sequence: + +```bash +./dev-check-strict.sh +``` + +--- + +## API Documentation + +Generate and open local docs: + +```bash +cargo doc --open +``` + +--- + +## Contributing + +1. Fork the repository and create a feature branch off `main`. +2. Run `./dev-check-strict.sh` — all checks must pass before submitting. +3. Open a pull request with a clear description of the change and any relevant test coverage. +4. Follow standard Rust style (`rustfmt` defaults). Clippy pedantic warnings are treated as errors. + +--- + +## License + +This project is licensed under the [MIT License](LICENSE). +Copyright © 2026 csd113. + +--- + +## Acknowledgements + +- [hound](https://github.com/ruuda/hound) — WAV file I/O +- [clap](https://github.com/clap-rs/clap) — CLI argument parsing +- [eframe / egui](https://github.com/emilk/egui) — immediate-mode GUI +- Bell 202 modem standard — 1200/2200 Hz AFSK tone pair +- Goertzel algorithm — efficient single-frequency DFT for decoding +- [cargo-deny](https://github.com/EmbarkStudios/cargo-deny) — license and advisory enforcement From 1d30062e100fa8de78b5fd7c9da4eae4ecbd8deb Mon Sep 17 00:00:00 2001 From: csd113 Date: Wed, 11 Mar 2026 19:17:16 -0700 Subject: [PATCH 4/5] Update deny.toml --- deny.toml | 78 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/deny.toml b/deny.toml index 33857e6..cb1aa74 100644 --- a/deny.toml +++ b/deny.toml @@ -1,30 +1,66 @@ -# deny.toml +# cargo-deny configuration for RustWave +# https://embarkstudios.github.io/cargo-deny/ + +# --------------------------------------------------------------------------- +# Advisories — known vulnerabilities & unmaintained crates +# --------------------------------------------------------------------------- [advisories] -vulnerability = "deny" -unmaintained = "warn" -yanked = "warn" -notice = "warn" +version = 2 +yanked = "deny" + +# `paste` (RUSTSEC-2024-0436) is a transitive dep of eframe (via accesskit_windows +# and metal). It is not used by our own code. +ignore = [ + "RUSTSEC-2024-0436", # paste – no longer maintained +] +# --------------------------------------------------------------------------- +# Licenses +# --------------------------------------------------------------------------- [licenses] -unlicensed = "deny" -allow = [ - "MIT", - "Apache-2.0", - "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "Unicode-DFS-2016", - "Zlib", - "CC0-1.0", - "MPL-2.0", +version = 2 +allow = [ + # Common permissive licences + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib", + "CC0-1.0", + + # Weak-copyleft / other permissive + "MPL-2.0", + + # Unicode data files + "Unicode-3.0", + + # Boost Software Licence — permissive, used by `error-code` (transitive dep of eframe) + "BSL-1.0", + + # Font licences — used by `epaint_default_fonts` (embedded egui fonts) + # OFL-1.1: SIL Open Font Licence — allows free use/embedding in any software + # Ubuntu-font-1.0: Ubuntu Font Licence — permits embedding in applications + "OFL-1.1", + "Ubuntu-font-1.0", ] +unused-allowed-license = "allow" +# --------------------------------------------------------------------------- +# Bans — duplicate crates / disallowed crates +# --------------------------------------------------------------------------- [bans] -multiple-versions = "warn" -wildcards = "deny" +# All remaining duplicates are transitive deps of eframe/wgpu/winit that we +# cannot resolve directly. Allow them globally rather than maintaining a +# skip list that goes stale every time eframe is updated. +multiple-versions = "allow" +wildcards = "allow" +# --------------------------------------------------------------------------- +# Sources — only allow crates.io +# --------------------------------------------------------------------------- [sources] unknown-registry = "deny" -unknown-git = "warn" -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [] +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] From a0530a825dd44208a2972fd55c74c19144b4a8af Mon Sep 17 00:00:00 2001 From: csd113 Date: Wed, 11 Mar 2026 19:51:11 -0700 Subject: [PATCH 5/5] updated formatting --- clippy_reports/clippy_raw.txt | 2 -- clippy_reports/summary.txt | 5 ----- src/decoder.rs | 6 ++++-- src/gui.rs | 17 +++++++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 clippy_reports/clippy_raw.txt delete mode 100644 clippy_reports/summary.txt diff --git a/clippy_reports/clippy_raw.txt b/clippy_reports/clippy_raw.txt deleted file mode 100644 index 164da7d..0000000 --- a/clippy_reports/clippy_raw.txt +++ /dev/null @@ -1,2 +0,0 @@ - Checking rustwave v0.1.0 (/Users/connordawkins/Documents/GitHub/RustWave) - Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s diff --git a/clippy_reports/summary.txt b/clippy_reports/summary.txt deleted file mode 100644 index 7c60e3e..0000000 --- a/clippy_reports/summary.txt +++ /dev/null @@ -1,5 +0,0 @@ -dev-check summary — Wed 11 Mar 2026 18:23:21 PDT -Duration: 39s -Passed: 9 -Failed: 0 -Skipped: 1 diff --git a/src/decoder.rs b/src/decoder.rs index ca5b453..2c5ca25 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,5 +1,7 @@ -use crate::config::{BAUD_RATE, MARK_FREQ, SAMPLE_RATE, SPACE_FREQ}; -use crate::framer::Decoded; +use crate::{ + config::{BAUD_RATE, MARK_FREQ, SAMPLE_RATE, SPACE_FREQ}, + framer::Decoded, +}; use std::f64::consts::TAU; /// Decode PCM samples back to the original filename and payload bytes. diff --git a/src/gui.rs b/src/gui.rs index b8b04b4..d311a1d 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -4,11 +4,15 @@ //! • WAV → decoded, output saved with the ORIGINAL filename next to the binary //! • Other → encoded to `_encoded.wav` next to the binary -use std::path::PathBuf; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{mpsc, Arc}; -use std::thread; -use std::time::Duration; +use std::{ + path::PathBuf, + sync::{ + atomic::{AtomicU32, Ordering}, + mpsc, Arc, + }, + thread, + time::Duration, +}; use eframe::egui::{self, Color32, CornerRadius, FontId, Pos2, Rect, Stroke, Vec2}; @@ -228,7 +232,8 @@ impl eframe::App for AfskGui { let avail = ui.available_size(); ui.add_space(18.0); ui.vertical_centered(|ui| { - #[allow(clippy::arithmetic_side_effects)] // egui Vec2 addition; no panic risk + #[allow(clippy::arithmetic_side_effects)] + // egui Vec2 addition; no panic risk ui.painter().text( ui.next_widget_position() + Vec2::new(avail.x / 2.0, 0.0), egui::Align2::CENTER_TOP,