From 232e127159fb7aacc19b053f0f7d39f33cc9b9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Tue, 28 Apr 2026 22:47:48 +0200 Subject: [PATCH 01/29] start creating mock GUI with egui --- Cargo.lock | 5045 ++++++++++++++++++++++++++++++------ Cargo.toml | 5 + src/gui/app.rs | 21 + src/gui/mod.rs | 12 + src/gui/panels/mod.rs | 3 + src/gui/panels/top_menu.rs | 24 + src/main.rs | 8 + 7 files changed, 4375 insertions(+), 743 deletions(-) create mode 100644 src/gui/app.rs create mode 100644 src/gui/mod.rs create mode 100644 src/gui/panels/mod.rs create mode 100644 src/gui/panels/top_menu.rs diff --git a/Cargo.lock b/Cargo.lock index c42b3d8f..becfe98b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,134 @@ # 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.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a" +dependencies = [ + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842fd8203e6dfcf531d24f5bac792088edfba7d6b35844fead191603fb32a260" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "phf", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534bc3fdc89a64a1db3c46b33c198fde2b7c3c7d094e5809c8c8bf2970c18243" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e549dd7c6562b6a2ea807b44726e6241707db054a817dc4c7e2b8d3b39bfac" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[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" @@ -29,6 +151,31 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.11.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.18", +] + +[[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" @@ -103,12 +250,233 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "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.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[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", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[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", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "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.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -151,11 +519,26 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.2", "shlex", "syn 2.0.117", ] +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "1.3.2" @@ -177,18 +560,75 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[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", + "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 = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -242,6 +682,8 @@ dependencies = [ "crossterm 0.27.0", "derive_more", "digest", + "eframe", + "egui", "flate2", "format-bytes", "futures", @@ -264,7 +706,7 @@ dependencies = [ "sha2", "shell-words", "test-case", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -276,27 +718,80 @@ dependencies = [ ] [[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.4" +name = "calloop" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "rustversion", + "bitflags 2.11.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", ] [[package]] -name = "cc" -version = "1.2.60" +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "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 = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -315,6 +810,21 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chrono" version = "0.4.44" @@ -378,12 +888,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[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 = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" +dependencies = [ + "bytemuck", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.7.1" @@ -397,6 +946,15 @@ dependencies = [ "static_assertions", ] +[[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 = "console" version = "0.15.11" @@ -415,12 +973,56 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[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" +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" 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 0.9.4", + "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 0.9.4", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -439,6 +1041,12 @@ 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 = "crossterm" version = "0.25.0" @@ -481,6 +1089,12 @@ dependencies = [ "winapi", ] +[[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" @@ -491,6 +1105,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "derive_more" version = "0.99.20" @@ -521,1346 +1141,3757 @@ dependencies = [ ] [[package]] -name = "dyn-clone" -version = "1.0.20" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "either" -version = "1.15.0" +name = "dispatch2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", +] [[package]] -name = "encode_unicode" -version = "1.0.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "equivalent" -version = "1.0.2" +name = "dlib" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] [[package]] -name = "errno" -version = "0.3.14" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "libc", - "windows-sys 0.61.2", + "litrs", ] [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "flate2" -version = "1.1.9" +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] -name = "foldhash" -version = "0.1.5" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "format-bytes" -version = "0.3.0" +name = "ecolor" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd" +checksum = "137c0ce4ce4152ff7e223a7ce22ee1057cdff61fce0a45c32459c3ccec64868d" dependencies = [ - "format-bytes-macros", + "bytemuck", + "emath", ] [[package]] -name = "format-bytes-macros" -version = "0.4.0" +name = "eframe" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f" +checksum = "d6e995b8e434d65aefd12c4519221be3e8f38efd77804ef39ca10553f4ad7063" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "pollster", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "wgpu", + "windows-sys 0.61.2", + "winit", ] [[package]] -name = "futures" -version = "0.3.32" +name = "egui" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "f34aaf627da598dfadd64b0fee6101d22e9c451d1e5348157312720b7f459f0f" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "accesskit", + "ahash", + "bitflags 2.11.0", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", ] [[package]] -name = "futures-channel" -version = "0.3.32" +name = "egui-wgpu" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "71033ff78b041c9c363450f4498ff95468ef3ecbcc71a62f67036a6207d98fa4" dependencies = [ - "futures-core", - "futures-sink", + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 2.0.18", + "type-map", + "web-time", + "wgpu", + "winit", ] [[package]] -name = "futures-core" -version = "0.3.32" +name = "egui-winit" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "11a2881b2bf1a305e413e644af63f836737a33d85077705ff808e88f902ff742" +dependencies = [ + "accesskit_winit", + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] [[package]] -name = "futures-executor" -version = "0.3.32" +name = "egui_glow" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "a3b28d39ab6c0cac238190e6cb1e8c9047d02cb470ab942a7a3302e4cb3a8e74" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", ] [[package]] -name = "futures-io" -version = "0.3.32" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "futures-macro" -version = "0.3.32" +name = "emath" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "0a05cd8bdf3b598488c627ca97c7fe8909448ffa26278dd3c7e535cdb554d721" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bytemuck", ] [[package]] -name = "futures-sink" -version = "0.3.32" +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "futures-task" -version = "0.3.32" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "futures-timer" -version = "3.0.3" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] [[package]] -name = "futures-util" -version = "0.3.32" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "epaint" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3017dd67f147a697ee0c8484fb568fd9553e2a0c114be5020dbbc11962841" +dependencies = [ + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "self_cell", + "skrifa", + "smallvec", + "vello_cpu", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3b85a2bb775a3ab02d077a65cc31575c11b2584581913253cc11ce49f48bba" + +[[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 = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[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", - "slab", ] [[package]] -name = "fxhash" -version = "0.2.1" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "byteorder", + "event-listener", + "pin-project-lite", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "fastrand" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ - "typenum", - "version_check", + "simd-adler32", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "fearless_simd" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" dependencies = [ - "cfg-if", - "libc", - "wasi", + "bytemuck", ] [[package]] -name = "glob" -version = "0.3.3" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "hashbrown" -version = "0.15.5" +name = "flate2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "crc32fast", + "miniz_oxide", ] [[package]] -name = "heck" +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "foreign-types" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "home" -version = "0.5.12" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "windows-sys 0.61.2", + "percent-encoding", ] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "format-bytes" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "format-bytes-macros", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "format-bytes-macros" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f" dependencies = [ - "cc", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "indicatif" -version = "0.17.11" +name = "futures" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "web-time", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "inquire" -version = "0.7.5" +name = "futures-channel" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ - "bitflags 2.11.0", - "crossterm 0.25.0", - "dyn-clone", - "fxhash", - "newline-converter", - "once_cell", - "unicode-segmentation", - "unicode-width", + "futures-core", + "futures-sink", ] [[package]] -name = "is-terminal" -version = "0.4.17" +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[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 = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[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 = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.11.0", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "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 = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "web-time", +] + +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.11.0", + "crossterm 0.25.0", + "dyn-clone", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "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", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[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 = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[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 = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[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 = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "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.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "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 0.5.1", + "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 0.5.1", + "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 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[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-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[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", + "block2 0.6.2", + "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 = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "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 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.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 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[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 0.5.1", + "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 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "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 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[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.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[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 = "peniko" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2b6aadb221872732e87d465213e9be5af2849b0e8cc5300a8ba98fffa2e00a" +dependencies = [ + "bytemuck", + "color", + "kurbo", + "linebender_resource_handle", + "smallvec", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[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 2.0.117", +] + +[[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", + "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.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[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", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +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 = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[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 = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "process_path" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f676f11eb0b3e2ea0fbaee218fa6b806689e2297b8c8adc5bf73df465c4f6171" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", + "serde", +] + +[[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 = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags 2.11.0", + "cassowary", + "compact_str", + "crossterm 0.27.0", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +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 = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rstest" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.117", + "unicode-ident", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[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 = "ruzstd" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" +dependencies = [ + "byteorder", + "derive_more", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", + "serde_derive", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "itertools" -version = "0.12.1" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "either", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] -name = "itertools" -version = "0.13.0" +name = "serde_repr" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ - "either", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] -name = "js-sys" -version = "0.3.95" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "once_cell", - "wasm-bindgen", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] [[package]] -name = "libbz2-rs-sys" -version = "0.2.2" +name = "shell-words" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] -name = "libc" -version = "0.2.184" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "libloading" -version = "0.8.9" +name = "signal-hook" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ - "cfg-if", - "windows-link", + "libc", + "signal-hook-registry", ] [[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" +name = "signal-hook-mio" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] [[package]] -name = "lock_api" -version = "0.4.14" +name = "signal-hook-registry" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "scopeguard", + "errno", + "libc", ] [[package]] -name = "log" -version = "0.4.29" +name = "simd-adler32" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "lru" -version = "0.12.5" +name = "simd_cesu8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "hashbrown", + "rustc_version", + "simdutf8", ] [[package]] -name = "lz4_flex" -version = "0.11.6" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" -dependencies = [ - "twox-hash", -] +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "lzma-sys" -version = "0.1.20" +name = "siphasher" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] -name = "matchers" -version = "0.2.0" +name = "skrifa" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" dependencies = [ - "regex-automata", + "bytemuck", + "read-fonts", ] [[package]] -name = "md-5" -version = "0.10.6" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "memchr" -version = "2.8.0" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "smithay-client-toolkit" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "adler2", - "simd-adler32", + "bitflags 2.11.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.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", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] -name = "mio" -version = "0.8.11" +name = "smithay-client-toolkit" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ + "bitflags 2.11.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", "libc", "log", - "wasi", - "windows-sys 0.48.0", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] -name = "mio" -version = "1.2.0" +name = "smithay-clipboard" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" dependencies = [ "libc", - "wasi", - "windows-sys 0.61.2", + "smithay-client-toolkit 0.20.0", + "wayland-backend", ] [[package]] -name = "newline-converter" -version = "0.3.0" +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ - "unicode-segmentation", + "serde", ] [[package]] -name = "nom" -version = "7.1.3" +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ - "memchr", - "minimal-lexical", + "bitflags 2.11.0", ] [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "stability" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ - "windows-sys 0.61.2", + "quote", + "syn 2.0.117", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "number_prefix" -version = "0.4.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "once_cell" -version = "1.21.4" +name = "strict-num" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "parking_lot" -version = "0.12.5" +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 = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[package]] +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "lock_api", - "parking_lot_core", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "syn" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "paste" -version = "1.0.15" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] [[package]] -name = "portable-atomic" -version = "1.13.1" +name = "terminal_size" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.61.2", +] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "test-case" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" dependencies = [ - "zerocopy", + "test-case-macros", ] [[package]] -name = "pretty_assertions" -version = "1.4.1" +name = "test-case-core" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" dependencies = [ - "diff", - "yansi", + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "test-case-macros" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", + "quote", "syn 2.0.117", + "test-case-core", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "unicode-ident", + "thiserror-impl 1.0.69", ] [[package]] -name = "process_path" -version = "0.1.4" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f676f11eb0b3e2ea0fbaee218fa6b806689e2297b8c8adc5bf73df465c4f6171" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "libc", - "winapi", + "thiserror-impl 2.0.18", ] [[package]] -name = "quote" -version = "1.0.45" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "rand" -version = "0.8.5" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "ppv-lite86", - "rand_core", + "cfg-if", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "tiff" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" dependencies = [ - "getrandom", + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", ] [[package]] -name = "ratatui" -version = "0.26.3" +name = "tiny-skia" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ - "bitflags 2.11.0", - "cassowary", - "compact_str", - "crossterm 0.27.0", - "itertools 0.12.1", - "lru", - "paste", - "stability", - "strum", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "tiny-skia-path" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ - "bitflags 2.11.0", + "arrayref", + "bytemuck", + "strict-num", ] [[package]] -name = "regex" -version = "1.12.3" +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "displaydoc", + "zerovec", ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "tokio" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "bytes", + "libc", + "mio 1.2.0", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "regex-syntax" -version = "0.8.10" +name = "tokio-macros" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "relative-path" -version = "1.9.3" +name = "tokio-stream" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] [[package]] -name = "rstest" -version = "0.19.0" +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", + "serde_core", ] [[package]] -name = "rstest_macros" -version = "0.19.0" +name = "toml_edit" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "cfg-if", - "glob", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.117", - "unicode-ident", + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", ] [[package]] -name = "rustc-hash" -version = "2.1.2" +name = "toml_parser" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] [[package]] -name = "rustc_version" -version = "0.4.1" +name = "tracing" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "semver", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "rustix" -version = "0.38.44" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "rustix" -version = "1.1.4" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "log", + "once_cell", + "tracing-core", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "tracing-panic" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "7bf1298a179837099f9309243af3b554e840f7f67f65e9f55294913299bd4cc5" +dependencies = [ + "tracing", + "tracing-subscriber", +] [[package]] -name = "ruzstd" -version = "0.6.0" +name = "tracing-subscriber" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ - "byteorder", - "derive_more", + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] -name = "ryu" -version = "1.0.23" +name = "tracing-unwrap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "c4e33415be97f5ae70322d6fefc696bbc08887d8835400d6c77f059469b30354" +dependencies = [ + "tracing", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "ttf-parser" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] -name = "semver" -version = "1.0.28" +name = "twox-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] -name = "serde" -version = "1.0.228" +name = "type-map" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "serde_core", - "serde_derive", + "rustc-hash 2.1.2", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] -name = "serde_derive" -version = "1.0.228" +name = "uds_windows" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "memoffset", + "tempfile", + "windows-sys 0.61.2", ] [[package]] -name = "sha1" -version = "0.10.6" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "sha2" -version = "0.10.9" +name = "unicode-segmentation" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "unicode-truncate" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "lazy_static", + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", ] [[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shlex" -version = "1.3.0" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "signal-hook" -version = "0.3.18" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "signal-hook-mio" -version = "0.2.5" +name = "url" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ - "libc", - "mio 0.8.11", - "signal-hook", + "form_urlencoded", + "idna", + "percent-encoding", + "serde", ] [[package]] -name = "signal-hook-registry" -version = "1.4.8" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "simd-adler32" -version = "0.3.9" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "slab" -version = "0.4.12" +name = "uuid" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] [[package]] -name = "smallvec" -version = "1.15.1" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "stability" -version = "0.2.1" +name = "vello_common" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +checksum = "1bd1a4c633ce09e7d713df1a6e036644a125e15e0c169cfb5180ddf5836ca04b" dependencies = [ - "quote", - "syn 2.0.117", + "bytemuck", + "fearless_simd", + "hashbrown 0.16.1", + "log", + "peniko", + "skrifa", + "smallvec", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "vello_cpu" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "0162bfe48aabf6a9fdcd401b628c7d9f260c2cbabb343c70a65feba6f7849edc" +dependencies = [ + "bytemuck", + "hashbrown 0.16.1", + "vello_common", +] [[package]] -name = "strsim" -version = "0.11.1" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "strum" -version = "0.26.3" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "strum_macros", + "same-file", + "winapi-util", ] [[package]] -name = "strum_macros" -version = "0.26.4" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.117", -] +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "syn" -version = "1.0.109" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "wit-bindgen 0.57.1", ] [[package]] -name = "syn" -version = "2.0.117" +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 = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "wit-bindgen 0.51.0", ] [[package]] -name = "terminal_size" -version = "0.4.4" +name = "wasm-bindgen" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ - "rustix 1.1.4", - "windows-sys 0.61.2", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "test-case" -version = "3.3.1" +name = "wasm-bindgen-futures" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "test-case-macros", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "test-case-core" -version = "3.3.1" +name = "wasm-bindgen-macro" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ - "cfg-if", - "proc-macro2", "quote", - "syn 2.0.117", + "wasm-bindgen-macro-support", ] [[package]] -name = "test-case-macros" -version = "3.3.1" +name = "wasm-bindgen-macro-support" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.117", - "test-case-core", + "wasm-bindgen-shared", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "wasm-bindgen-shared" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ - "thiserror-impl", + "unicode-ident", ] [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "leb128fmt", + "wasmparser", ] [[package]] -name = "thread_local" -version = "1.1.9" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "cfg-if", + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "tokio" -version = "1.51.1" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bytes", - "libc", - "mio 1.2.0", - "pin-project-lite", - "signal-hook-registry", - "tokio-macros", - "windows-sys 0.61.2", + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] -name = "tokio-macros" -version = "2.7.0" +name = "wayland-backend" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", ] [[package]] -name = "tokio-stream" -version = "0.1.18" +name = "wayland-client" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", + "bitflags 2.11.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "tracing" -version = "0.1.44" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "bitflags 2.11.0", + "cursor-icon", + "wayland-backend", ] [[package]] -name = "tracing-attributes" -version = "0.1.31" +name = "wayland-cursor" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "rustix 1.1.4", + "wayland-client", + "xcursor", ] [[package]] -name = "tracing-core" -version = "0.1.36" +name = "wayland-protocols" +version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "once_cell", - "valuable", + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "wayland-protocols-experimental" +version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "log", - "once_cell", - "tracing-core", + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "tracing-panic" -version = "0.1.2" +name = "wayland-protocols-misc" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bf1298a179837099f9309243af3b554e840f7f67f65e9f55294913299bd4cc5" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "tracing", - "tracing-subscriber", + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "tracing-subscriber" -version = "0.3.23" +name = "wayland-protocols-plasma" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "tracing-unwrap" -version = "1.0.1" +name = "wayland-protocols-wlr" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e33415be97f5ae70322d6fefc696bbc08887d8835400d6c77f059469b30354" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "tracing", + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "twox-hash" -version = "2.1.2" +name = "wayland-scanner" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] [[package]] -name = "typenum" -version = "1.19.0" +name = "wayland-sys" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "web-sys" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] -name = "unicode-segmentation" -version = "1.13.2" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] -name = "unicode-truncate" -version = "1.1.0" +name = "webbrowser" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ - "itertools 0.13.0", - "unicode-segmentation", - "unicode-width", + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "url", + "web-sys", ] [[package]] -name = "unicode-width" -version = "0.1.14" +name = "weezl" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] -name = "utf8parse" -version = "0.2.2" +name = "wgpu" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" +dependencies = [ + "arrayvec", + "bitflags 2.11.0", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] [[package]] -name = "valuable" -version = "0.1.1" +name = "wgpu-core" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.11.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] [[package]] -name = "version_check" -version = "0.9.5" +name = "wgpu-core-deps-apple" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" +dependencies = [ + "wgpu-hal", +] [[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" +name = "wgpu-core-deps-emscripten" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" +dependencies = [ + "wgpu-hal", +] [[package]] -name = "wasm-bindgen" -version = "0.2.118" +name = "wgpu-core-deps-wasm" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "2f7b75e72f49035f000dd5262e4126242e92a090a4fd75931ecfe7e60784e6fa" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "wgpu-hal", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.118" +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "wgpu-hal", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.118" +name = "wgpu-hal" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.117", - "wasm-bindgen-shared", + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.0", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.118" +name = "wgpu-naga-bridge" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51" dependencies = [ - "unicode-ident", + "naga", + "wgpu-types", ] [[package]] -name = "web-time" -version = "1.1.0" +name = "wgpu-types" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" dependencies = [ + "bitflags 2.11.0", + "bytemuck", "js-sys", - "wasm-bindgen", + "log", + "raw-window-handle", + "web-sys", ] [[package]] @@ -1891,12 +4922,42 @@ 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.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -1910,6 +4971,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -1938,6 +5010,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -1958,11 +5040,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +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.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]] @@ -2014,6 +5105,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2104,12 +5204,236 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + [[package]] name = "winsafe" version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[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", + "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 = "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 = "xz2" version = "0.1.7" @@ -2125,6 +5449,126 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8067892e940ed1727dea64690378601603b31d62dfde019a5335fbb7c0e0ed9" +dependencies = [ + "quick-xml", + "serde", + "zbus_names", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -2144,3 +5588,118 @@ dependencies = [ "quote", "syn 2.0.117", ] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +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 = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db0ecb8987cf5e92653c57c098f7f0e39a03112edb796f4fe089fb7eaa14ff" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b949b639ab1b4bed763aa7481ba0e368af68d8b55532f8ed4bec86a59f2ca98" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 1a51a845..bc486ab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ clap = { version = "4.5.49", features = ["derive", "cargo", "wrap_help"] } crossterm = { version = "0.27.0", features = ["event-stream"] } derive_more = "0.99.20" digest = "0.10.7" +eframe = { version = "0.34", optional = true } +egui = { version = "0.34", optional = true } flate2 = "1.1.4" format-bytes = "0.3.0" futures = { version = "0.3.31", default-features = false, features = ["std"] } @@ -53,6 +55,9 @@ tracing-unwrap = "1.0.1" which = "6.0.3" xz2 = { version = "0.1.7", features = ["static"] } +[features] +gui = ["dep:egui", "dep:eframe"] + [dev-dependencies] approx = "0.5.1" assert_matches = "1.5.0" diff --git a/src/gui/app.rs b/src/gui/app.rs new file mode 100644 index 00000000..c8dd7e3b --- /dev/null +++ b/src/gui/app.rs @@ -0,0 +1,21 @@ +use crate::gui::panels::add_top_menu_bar; + +pub struct App {} + +impl App { + pub fn new(_cc: &eframe::CreationContext) -> Self { + Self {} + } +} + +impl eframe::App for App { + fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + let _ = (ctx, frame); + } + + fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + add_top_menu_bar(ui); + } + + fn save(&mut self, _storage: &mut dyn eframe::Storage) {} +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 00000000..eb43d186 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,12 @@ +mod app; +mod panels; + +use app::App; + +pub fn run_gui() -> eframe::Result { + eframe::run_native( + "caligula-gui", + Default::default(), + Box::new(|cc| Ok(Box::new(App::new(cc)))), + ) +} diff --git a/src/gui/panels/mod.rs b/src/gui/panels/mod.rs new file mode 100644 index 00000000..ec608c0d --- /dev/null +++ b/src/gui/panels/mod.rs @@ -0,0 +1,3 @@ +mod top_menu; + +pub use top_menu::add_top_menu_bar; diff --git a/src/gui/panels/top_menu.rs b/src/gui/panels/top_menu.rs new file mode 100644 index 00000000..e98c23ae --- /dev/null +++ b/src/gui/panels/top_menu.rs @@ -0,0 +1,24 @@ +use egui::{Panel, ViewportCommand}; + +pub fn add_top_menu_bar(ui: &mut egui::Ui) { + Panel::top("top_menu").show_inside(ui, |ui| { + egui::MenuBar::new().ui(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("❌ Quit").clicked() { + ui.ctx().send_viewport_cmd(ViewportCommand::Close); + } + }); + ui.menu_button("Theme", |ui| { + if ui.button("Dark").clicked() { + // TODO: + } + if ui.button("Light").clicked() { + // TODO: + } + if ui.button("System").clicked() { + // TODO: + } + }); + }); + }); +} diff --git a/src/main.rs b/src/main.rs index b124947d..0daaff33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,9 @@ mod tty; mod ui; mod util; +#[cfg(feature = "gui")] +mod gui; + /// A lightweight, user-friendly disk imaging tool #[derive(clap::Parser, Debug)] #[command(author, version, about, long_about = None, flatten_help = true)] @@ -35,6 +38,8 @@ pub struct Args { pub enum Command { Burn(ui::BurnArgs), + Gui, + /// INTERNAL ONLY! /// /// This is a backend entrypoint that is used in implementing automatic root escalation. @@ -69,6 +74,9 @@ fn main() { Err(e) => handle_toplevel_error(e), } } + Command::Gui => { + gui::run_gui().unwrap(); + } Command::HerderDaemon(args) => { logging::init_logging_child(args.log_file); herder_daemon::main(); From e9ffd22ba0069802db29250eda900a40329a12e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 29 Apr 2026 06:46:59 +0200 Subject: [PATCH 02/29] only use glow backend, add rfd --- Cargo.lock | 323 ++++++----------------------------------------------- Cargo.toml | 3 +- 2 files changed, 38 insertions(+), 288 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index becfe98b..7590647e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,22 +2,6 @@ # 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.24.0" @@ -270,12 +254,6 @@ dependencies = [ "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" @@ -288,15 +266,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - [[package]] name = "assert_matches" version = "1.5.0" @@ -699,6 +668,7 @@ dependencies = [ "process_path", "rand", "ratatui", + "rfd", "rstest", "ruzstd", "serde", @@ -903,8 +873,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "serde", - "termcolor", "unicode-width", ] @@ -1153,6 +1121,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.11.0", + "block2 0.6.2", + "libc", "objc2 0.6.4", ] @@ -1226,6 +1196,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", + "glow", "glutin", "glutin-winit", "image", @@ -1236,7 +1207,6 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "pollster", "profiling", "raw-window-handle", "static_assertions", @@ -1244,7 +1214,6 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "web-time", - "wgpu", "windows-sys 0.61.2", "winit", ] @@ -1844,40 +1813,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gpu-allocator" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" -dependencies = [ - "ash", - "hashbrown 0.16.1", - "log", - "presser", - "thiserror 2.0.18", - "windows", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.11.0", - "gpu-descriptor-types", - "hashbrown 0.15.5", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "half" version = "2.7.1" @@ -1907,8 +1842,6 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "allocator-api2", - "equivalent", "foldhash 0.2.0", ] @@ -2259,17 +2192,6 @@ dependencies = [ "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", - "pkg-config", -] - [[package]] name = "khronos_api" version = "3.1.0" @@ -2526,7 +2448,6 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", - "spirv", "thiserror 2.0.18", "unicode-ident", ] @@ -2671,7 +2592,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", + "objc2-quartz-core", ] [[package]] @@ -2681,6 +2602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.11.0", + "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -2756,7 +2678,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", + "objc2-metal", ] [[package]] @@ -2837,18 +2759,6 @@ dependencies = [ "objc2-foundation 0.2.2", ] -[[package]] -name = "objc2-metal" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" -dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -2859,20 +2769,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", + "objc2-metal", ] [[package]] @@ -2900,7 +2797,7 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core 0.2.2", + "objc2-quartz-core", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -2964,15 +2861,6 @@ dependencies = [ "libredox", ] -[[package]] -name = "ordered-float" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-stream" version = "0.2.0" @@ -2983,15 +2871,6 @@ dependencies = [ "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" @@ -3204,12 +3083,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - [[package]] name = "pretty_assertions" version = "1.4.1" @@ -3337,12 +3210,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "range-alloc" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" - [[package]] name = "ratatui" version = "0.26.3" @@ -3369,18 +3236,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "raw-window-metal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" -dependencies = [ - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - [[package]] name = "read-fonts" version = "0.37.0" @@ -3459,6 +3314,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rstest" version = "0.19.0" @@ -3578,19 +3460,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit 0.19.2", - "tiny-skia", -] - [[package]] name = "self_cell" version = "1.2.2" @@ -3862,15 +3731,6 @@ dependencies = [ "serde", ] -[[package]] -name = "spirv" -version = "0.4.0+sdk-1.4.341.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "stability" version = "0.2.1" @@ -3893,12 +3753,6 @@ 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" @@ -3973,15 +3827,6 @@ dependencies = [ "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 = "terminal_size" version = "0.4.4" @@ -4088,31 +3933,6 @@ dependencies = [ "zune-jpeg", ] -[[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.3" @@ -4271,12 +4091,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - [[package]] name = "twox-hash" version = "2.1.2" @@ -4732,8 +4546,6 @@ dependencies = [ "hashbrown 0.16.1", "js-sys", "log", - "naga", - "parking_lot", "portable-atomic", "profiling", "raw-window-handle", @@ -4772,42 +4584,12 @@ dependencies = [ "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.18", - "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", - "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-naga-bridge", "wgpu-types", ] -[[package]] -name = "wgpu-core-deps-apple" -version = "29.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-emscripten" -version = "29.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-wasm" -version = "29.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7b75e72f49035f000dd5262e4126242e92a090a4fd75931ecfe7e60784e6fa" -dependencies = [ - "wgpu-hal", -] - [[package]] name = "wgpu-core-deps-windows-linux-android" version = "29.0.0" @@ -4823,51 +4605,19 @@ version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", "bitflags 2.11.0", - "block2 0.6.2", - "bytemuck", "cfg-if", "cfg_aliases", - "glow", - "glutin_wgl_sys", - "gpu-allocator", - "gpu-descriptor", - "hashbrown 0.16.1", - "js-sys", - "khronos-egl", - "libc", "libloading", "log", "naga", - "ndk-sys", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "once_cell", - "ordered-float", - "parking_lot", "portable-atomic", "portable-atomic-util", - "profiling", - "range-alloc", "raw-window-handle", - "raw-window-metal", "renderdoc-sys", - "smallvec", "thiserror 2.0.18", - "wasm-bindgen", - "wayland-sys", - "web-sys", "wgpu-naga-bridge", "wgpu-types", - "windows", - "windows-core", ] [[package]] @@ -5237,7 +4987,6 @@ dependencies = [ "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.44", - "sctk-adwaita", "smithay-client-toolkit 0.19.2", "smol_str", "tracing", diff --git a/Cargo.toml b/Cargo.toml index bc486ab5..a2e9d2ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ clap = { version = "4.5.49", features = ["derive", "cargo", "wrap_help"] } crossterm = { version = "0.27.0", features = ["event-stream"] } derive_more = "0.99.20" digest = "0.10.7" -eframe = { version = "0.34", optional = true } +eframe = { version = "0.34", optional = true, default-features = false, features = ["accesskit", "default_fonts", "wayland", "web_screen_reader", "x11", "glow"] } egui = { version = "0.34", optional = true } flate2 = "1.1.4" format-bytes = "0.3.0" @@ -38,6 +38,7 @@ lz4_flex = { version = "0.11.6", default-features = false, features = ["frame"] md5 = { package = "md-5", version = "0.10.6", default-features = false } process_path = "0.1.4" ratatui = { version = "0.26.3", default-features = false, features = ["crossterm"] } +rfd = "0.17" ruzstd = { version = "0.6.0", default-features = false, features = ["std"] } serde = { version = "1.0.228", features = ["derive"] } sha1 = "0.10.6" From 50743d0afaa6af3ed9481f26cd3c13eab97c827c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 29 Apr 2026 06:47:28 +0200 Subject: [PATCH 03/29] simplify structure, add input file pick --- src/gui/app.rs | 42 +++++++++++++++++++++++++++++++------- src/gui/mod.rs | 1 - src/gui/panels/mod.rs | 3 --- src/gui/panels/top_menu.rs | 24 ---------------------- src/main.rs | 2 ++ 5 files changed, 37 insertions(+), 35 deletions(-) delete mode 100644 src/gui/panels/mod.rs delete mode 100644 src/gui/panels/top_menu.rs diff --git a/src/gui/app.rs b/src/gui/app.rs index c8dd7e3b..c3f9f6b2 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,20 +1,48 @@ -use crate::gui::panels::add_top_menu_bar; +use std::path::PathBuf; -pub struct App {} +use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; + +#[derive(Default)] +pub struct App { + picked_image: Option, +} impl App { pub fn new(_cc: &eframe::CreationContext) -> Self { - Self {} + Self::default() } } impl eframe::App for App { - fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - let _ = (ctx, frame); - } + fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { - add_top_menu_bar(ui); + Panel::top("top_menu").show_inside(ui, |ui| { + MenuBar::new().ui(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("❌ Quit").clicked() { + ui.ctx().send_viewport_cmd(ViewportCommand::Close); + } + }); + }); + }); + + CentralPanel::default().show_inside(ui, |ui| { + ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); + ui.label(env!("CARGO_PKG_DESCRIPTION")); + + ui.add_space(4. * ui.spacing().item_spacing.y); + + ui.label("Input image to burn"); + ui.horizontal(|ui| { + if ui.button("Pick file").clicked() { + self.picked_image = rfd::FileDialog::new().pick_file(); + } + if let Some(picked) = &self.picked_image { + ui.label(picked.to_string_lossy()); + } + }); + }); } fn save(&mut self, _storage: &mut dyn eframe::Storage) {} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index eb43d186..dca34060 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,5 +1,4 @@ mod app; -mod panels; use app::App; diff --git a/src/gui/panels/mod.rs b/src/gui/panels/mod.rs deleted file mode 100644 index ec608c0d..00000000 --- a/src/gui/panels/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod top_menu; - -pub use top_menu::add_top_menu_bar; diff --git a/src/gui/panels/top_menu.rs b/src/gui/panels/top_menu.rs deleted file mode 100644 index e98c23ae..00000000 --- a/src/gui/panels/top_menu.rs +++ /dev/null @@ -1,24 +0,0 @@ -use egui::{Panel, ViewportCommand}; - -pub fn add_top_menu_bar(ui: &mut egui::Ui) { - Panel::top("top_menu").show_inside(ui, |ui| { - egui::MenuBar::new().ui(ui, |ui| { - ui.menu_button("File", |ui| { - if ui.button("❌ Quit").clicked() { - ui.ctx().send_viewport_cmd(ViewportCommand::Close); - } - }); - ui.menu_button("Theme", |ui| { - if ui.button("Dark").clicked() { - // TODO: - } - if ui.button("Light").clicked() { - // TODO: - } - if ui.button("System").clicked() { - // TODO: - } - }); - }); - }); -} diff --git a/src/main.rs b/src/main.rs index 0daaff33..912ce89b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ pub struct Args { pub enum Command { Burn(ui::BurnArgs), + #[cfg(feature = "gui")] Gui, /// INTERNAL ONLY! @@ -74,6 +75,7 @@ fn main() { Err(e) => handle_toplevel_error(e), } } + #[cfg(feature = "gui")] Command::Gui => { gui::run_gui().unwrap(); } From c547dd2e450a256b77d3aafcc1b0de334924b906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 29 Apr 2026 22:41:39 +0200 Subject: [PATCH 04/29] start adding hashing, overall app state --- Cargo.lock | 50 ++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/gui/app.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7590647e..7d60fa47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,8 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a" dependencies = [ + "enumn", + "serde", "uuid", ] @@ -110,6 +112,7 @@ dependencies = [ "cfg-if", "getrandom 0.3.4", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -519,6 +522,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-buffer" @@ -1181,6 +1187,7 @@ checksum = "137c0ce4ce4152ff7e223a7ce22ee1057cdff61fce0a45c32459c3ccec64868d" dependencies = [ "bytemuck", "emath", + "serde", ] [[package]] @@ -1199,6 +1206,7 @@ dependencies = [ "glow", "glutin", "glutin-winit", + "home", "image", "js-sys", "log", @@ -1209,6 +1217,8 @@ dependencies = [ "percent-encoding", "profiling", "raw-window-handle", + "ron", + "serde", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", @@ -1232,6 +1242,8 @@ dependencies = [ "log", "nohash-hasher", "profiling", + "ron", + "serde", "smallvec", "unicode-segmentation", ] @@ -1272,6 +1284,7 @@ dependencies = [ "objc2-ui-kit 0.3.2", "profiling", "raw-window-handle", + "serde", "smithay-clipboard", "web-time", "webbrowser", @@ -1308,6 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a05cd8bdf3b598488c627ca97c7fe8909448ffa26278dd3c7e535cdb554d721" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -1343,6 +1357,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "epaint" version = "0.34.1" @@ -1360,6 +1385,7 @@ dependencies = [ "parking_lot", "profiling", "self_cell", + "serde", "skrifa", "smallvec", "vello_cpu", @@ -1488,6 +1514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -3341,6 +3368,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" +dependencies = [ + "bitflags 2.11.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + [[package]] name = "rstest" version = "0.19.0" @@ -3658,6 +3699,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smithay-client-toolkit" @@ -4106,6 +4150,12 @@ dependencies = [ "rustc-hash 2.1.2", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index a2e9d2ba..453d285a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ clap = { version = "4.5.49", features = ["derive", "cargo", "wrap_help"] } crossterm = { version = "0.27.0", features = ["event-stream"] } derive_more = "0.99.20" digest = "0.10.7" -eframe = { version = "0.34", optional = true, default-features = false, features = ["accesskit", "default_fonts", "wayland", "web_screen_reader", "x11", "glow"] } +eframe = { version = "0.34", optional = true, default-features = false, features = ["accesskit", "default_fonts", "wayland", "web_screen_reader", "x11", "glow", "persistence"] } egui = { version = "0.34", optional = true } flate2 = "1.1.4" format-bytes = "0.3.0" diff --git a/src/gui/app.rs b/src/gui/app.rs index c3f9f6b2..bd7bbda7 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,22 +1,42 @@ +use egui::{CentralPanel, Color32, MenuBar, Panel, RichText, ViewportCommand}; use std::path::PathBuf; -use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; +use crate::{ + compression::CompressionFormat, + hash::{HashAlg, parse_hash_input}, +}; #[derive(Default)] +#[cfg_attr(debug_assertions, derive(serde::Deserialize, serde::Serialize))] pub struct App { picked_image: Option, + file_hash_str: String, + file_hash_algorithms_possible: Vec, + file_hash_algorithm_selected: Option, + latest_hashing_error: String, + skip_hashing: bool, } impl App { + #[cfg(not(debug_assertions))] pub fn new(_cc: &eframe::CreationContext) -> Self { Self::default() } + + #[cfg(debug_assertions)] + pub fn new(cc: &eframe::CreationContext) -> Self { + cc.storage + .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) + .unwrap_or_default() + } } impl eframe::App for App { fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + const SECTION_SPACING: f32 = 6.; + Panel::top("top_menu").show_inside(ui, |ui| { MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { @@ -31,19 +51,69 @@ impl eframe::App for App { ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); ui.label(env!("CARGO_PKG_DESCRIPTION")); - ui.add_space(4. * ui.spacing().item_spacing.y); + ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); + + ui.strong("Image"); + if ui.button("💿 Pick file").clicked() { + self.picked_image = rfd::FileDialog::new().pick_file(); + } + if let Some(picked) = &self.picked_image { + ui.label(picked.to_string_lossy()); + if let Some(cf) = CompressionFormat::detect_from_path(picked) { + ui.label(format!("Detected compression format: {}", cf)); + } else { + // ui.label(RichText::new("Couldn't detect compression format for picked image, assuming uncompressed!").color(Color32::YELLOW)); + } + } + + ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.label("Input image to burn"); ui.horizontal(|ui| { - if ui.button("Pick file").clicked() { - self.picked_image = rfd::FileDialog::new().pick_file(); + ui.strong("File hash"); + ui.checkbox(&mut self.skip_hashing, "Skip?"); + }); + + ui.add_enabled_ui(!self.skip_hashing, |ui| { + ui.label("We will guess the hash algorithm from your input."); + if ui.text_edit_singleline(&mut self.file_hash_str).changed() { + match parse_hash_input(&self.file_hash_str) { + Ok((algs, _)) => { + self.file_hash_algorithms_possible = algs; + self.latest_hashing_error.clear(); + } + Err(e) => { + self.file_hash_algorithms_possible = vec![]; + self.file_hash_algorithm_selected = None; + self.latest_hashing_error = e.to_string(); + } + } } - if let Some(picked) = &self.picked_image { - ui.label(picked.to_string_lossy()); + + if self.skip_hashing { + return; + } + + if !self.latest_hashing_error.is_empty() { + ui.label(RichText::new(&self.latest_hashing_error).color(Color32::RED)); } + ui.horizontal(|ui| { + for alg in &self.file_hash_algorithms_possible { + let is_selected = Some(*alg) == self.file_hash_algorithm_selected; + + if ui.selectable_label(is_selected, alg.to_string()).clicked() { + self.file_hash_algorithm_selected = Some(*alg); + } + } + }); }); }); } + #[cfg(not(debug_assertions))] fn save(&mut self, _storage: &mut dyn eframe::Storage) {} + + #[cfg(debug_assertions)] + fn save(&mut self, storage: &mut dyn eframe::Storage) { + eframe::set_value(storage, eframe::APP_KEY, self); + } } From c0b79bae5810f363ccbcbe88f7c97b0a648917d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 30 Apr 2026 11:09:16 +0200 Subject: [PATCH 05/29] create `sections.rs`, create functions per section. add (shitty) target disk selection --- src/gui/app.rs | 113 ++++++++++++++++----------------- src/gui/mod.rs | 1 + src/gui/sections.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 58 deletions(-) create mode 100644 src/gui/sections.rs diff --git a/src/gui/app.rs b/src/gui/app.rs index bd7bbda7..52922df3 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,33 +1,63 @@ -use egui::{CentralPanel, Color32, MenuBar, Panel, RichText, ViewportCommand}; +use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; use std::path::PathBuf; use crate::{ compression::CompressionFormat, - hash::{HashAlg, parse_hash_input}, + device::{Removable, WriteTarget, enumerate_devices}, + gui::sections::{add_begin_writing_ui, add_file_hash_ui, add_image_ui, add_target_disk_ui}, + hash::HashAlg, }; #[derive(Default)] #[cfg_attr(debug_assertions, derive(serde::Deserialize, serde::Serialize))] pub struct App { - picked_image: Option, - file_hash_str: String, - file_hash_algorithms_possible: Vec, - file_hash_algorithm_selected: Option, - latest_hashing_error: String, - skip_hashing: bool, + pub picked_image: Option, + pub file_hash_options: FileHashOptions, + pub detected_compression_format: Option, + #[cfg_attr(debug_assertions, serde(skip))] + pub possible_write_targets: Vec, + #[cfg_attr(debug_assertions, serde(skip))] + pub selected_write_target: Option, + pub show_all_disks: bool, +} + +#[derive(Default)] +#[cfg_attr(debug_assertions, derive(serde::Deserialize, serde::Serialize))] +pub struct FileHashOptions { + pub entered_hash: String, + pub possible_algorithms: Vec, + pub selected_algorithm: Option, + pub last_error: String, + pub skip: bool, + #[cfg_attr(debug_assertions, serde(skip))] + pub verified: bool, } impl App { #[cfg(not(debug_assertions))] pub fn new(_cc: &eframe::CreationContext) -> Self { - Self::default() + let mut s = Self::default(); + s.refresh_devices(); + s } #[cfg(debug_assertions)] pub fn new(cc: &eframe::CreationContext) -> Self { - cc.storage + let mut s: Self = cc + .storage .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) - .unwrap_or_default() + .unwrap_or_default(); + s.refresh_devices(); + s + } + + pub fn refresh_devices(&mut self) { + // TODO: deduplicate this. + // This is code stolen from `ask_outfile.rs`! + self.possible_write_targets = enumerate_devices() + .filter(|d| self.show_all_disks || d.removable == Removable::Yes) + .collect(); + self.possible_write_targets.sort(); } } @@ -53,58 +83,25 @@ impl eframe::App for App { ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.strong("Image"); - if ui.button("💿 Pick file").clicked() { - self.picked_image = rfd::FileDialog::new().pick_file(); - } - if let Some(picked) = &self.picked_image { - ui.label(picked.to_string_lossy()); - if let Some(cf) = CompressionFormat::detect_from_path(picked) { - ui.label(format!("Detected compression format: {}", cf)); - } else { - // ui.label(RichText::new("Couldn't detect compression format for picked image, assuming uncompressed!").color(Color32::YELLOW)); - } - } + add_image_ui(self, ui); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.horizontal(|ui| { - ui.strong("File hash"); - ui.checkbox(&mut self.skip_hashing, "Skip?"); + ui.add_enabled_ui(self.picked_image.is_some(), |ui| { + add_file_hash_ui(&mut self.file_hash_options, ui) }); - ui.add_enabled_ui(!self.skip_hashing, |ui| { - ui.label("We will guess the hash algorithm from your input."); - if ui.text_edit_singleline(&mut self.file_hash_str).changed() { - match parse_hash_input(&self.file_hash_str) { - Ok((algs, _)) => { - self.file_hash_algorithms_possible = algs; - self.latest_hashing_error.clear(); - } - Err(e) => { - self.file_hash_algorithms_possible = vec![]; - self.file_hash_algorithm_selected = None; - self.latest_hashing_error = e.to_string(); - } - } - } - - if self.skip_hashing { - return; - } - - if !self.latest_hashing_error.is_empty() { - ui.label(RichText::new(&self.latest_hashing_error).color(Color32::RED)); - } - ui.horizontal(|ui| { - for alg in &self.file_hash_algorithms_possible { - let is_selected = Some(*alg) == self.file_hash_algorithm_selected; - - if ui.selectable_label(is_selected, alg.to_string()).clicked() { - self.file_hash_algorithm_selected = Some(*alg); - } - } - }); + ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); + + ui.add_enabled_ui( + self.file_hash_options.verified || self.file_hash_options.skip, + |ui| add_target_disk_ui(self, ui), + ); + + ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); + + ui.add_enabled_ui(self.selected_write_target.is_some(), |ui| { + add_begin_writing_ui(self, ui) }); }); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index dca34060..71801e69 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,4 +1,5 @@ mod app; +mod sections; use app::App; diff --git a/src/gui/sections.rs b/src/gui/sections.rs new file mode 100644 index 00000000..23588415 --- /dev/null +++ b/src/gui/sections.rs @@ -0,0 +1,149 @@ +use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; + +use crate::{ + compression::CompressionFormat, + device::{self, Removable, WriteTarget, enumerate_devices}, + gui::app::{App, FileHashOptions}, + hash::parse_hash_input, +}; + +pub fn add_file_hash_ui(hash_options: &mut FileHashOptions, ui: &mut egui::Ui) { + let FileHashOptions { + entered_hash: file_hash_str, + possible_algorithms: file_hash_algorithms_possible, + selected_algorithm: file_hash_algorithm_selected, + last_error: latest_hashing_error, + skip: skip_hashing, + verified: verified_hash, + } = hash_options; + + ui.horizontal(|ui| { + ui.strong("File hash"); + ui.checkbox(skip_hashing, "Skip?"); + }); + + ui.scope_builder( + UiBuilder { + disabled: *skip_hashing, + // invisible: *skip_hashing, + ..Default::default() + }, + |ui| { + ui.label("We will guess the hash algorithm from your input."); + if ui.text_edit_singleline(file_hash_str).changed() { + match parse_hash_input(file_hash_str) { + Ok((algs, _)) => { + if algs.len() == 1 { + *file_hash_algorithm_selected = Some(algs[0]); + } + *file_hash_algorithms_possible = algs; + latest_hashing_error.clear(); + } + Err(e) => { + *file_hash_algorithms_possible = vec![]; + *file_hash_algorithm_selected = None; + *latest_hashing_error = e.to_string(); + } + } + } + + if latest_hashing_error.is_empty() { + ui.horizontal(|ui| { + for alg in file_hash_algorithms_possible { + let is_selected = Some(*alg) == *file_hash_algorithm_selected; + + if ui.selectable_label(is_selected, alg.to_string()).clicked() { + *file_hash_algorithm_selected = Some(*alg); + } + } + }); + + ui.horizontal(|ui| { + ui.add_enabled(false, Checkbox::without_text(verified_hash)); + if ui.button("Verify").clicked() { + // TODO: + *verified_hash = true; + } + }); + } else if *skip_hashing { + ui.label(""); + } else { + ui.label(RichText::new(&*latest_hashing_error).color(Color32::RED)); + } + }, + ); +} + +pub fn add_image_ui(app: &mut App, ui: &mut egui::Ui) { + let App { + detected_compression_format, + picked_image, + .. + } = app; + + ui.strong("Image"); + if ui.button("💿 Pick file").clicked() + && let Some(picked) = rfd::FileDialog::new().pick_file() + { + *detected_compression_format = CompressionFormat::detect_from_path(&picked); + *picked_image = Some(picked); + } + if let Some(picked) = picked_image { + ui.label(picked.to_string_lossy()); + if let Some(cf) = detected_compression_format { + ui.label(format!("Detected compression format: {}", cf)); + } else { + ui.label( + RichText::new( + "Couldn't detect compression format for picked image, assuming uncompressed!", + ) + .color(Color32::YELLOW), + ); + } + } +} + +pub fn add_target_disk_ui(app: &mut App, ui: &mut egui::Ui) { + ui.strong("Target disk"); + if ui.button("Refresh devices").clicked() { + app.refresh_devices(); + } + + // FIXME: + // - stop alloc:ing and doing so much work here.. DON'T CLONE! + // - move the label formatting into a place where it's done ONCE, not on every ui render! + // - deduplicate, label formatting is stolen from `ask_outfile.rs` + ComboBox::from_label(format!("{} available", app.possible_write_targets.len())) + .selected_text( + app.selected_write_target + .as_ref() + .map(|dev| match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }) + .unwrap_or_default(), + ) + .show_ui(ui, |ui| { + for dev in &app.possible_write_targets { + let label = match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }; + ui.selectable_value(&mut app.selected_write_target, Some(dev.clone()), label); + } + }); +} + +pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) {} From 95bb4d07657650078757d5408b380c8bd5d7c4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 30 Apr 2026 12:27:54 +0200 Subject: [PATCH 06/29] start creating "begin write" section --- src/gui/app.rs | 13 +++++++++++++ src/gui/sections.rs | 33 +++++++++++++++++++++++++++++++-- src/ui/mod.rs | 1 + 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 52922df3..338fc824 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -6,6 +6,7 @@ use crate::{ device::{Removable, WriteTarget, enumerate_devices}, gui::sections::{add_begin_writing_ui, add_file_hash_ui, add_image_ui, add_target_disk_ui}, hash::HashAlg, + ui::BeginParams, }; #[derive(Default)] @@ -19,6 +20,8 @@ pub struct App { #[cfg_attr(debug_assertions, serde(skip))] pub selected_write_target: Option, pub show_all_disks: bool, + #[cfg_attr(debug_assertions, serde(skip))] + pub begin_params: Option, } #[derive(Default)] @@ -59,6 +62,16 @@ impl App { .collect(); self.possible_write_targets.sort(); } + + pub fn file_hash_is_valid_or_skipped(&self) -> bool { + self.file_hash_options.verified || self.file_hash_options.skip + } + + pub fn is_ready_for_writing(&self) -> bool { + self.picked_image.is_some() + && self.file_hash_is_valid_or_skipped() + && self.selected_write_target.is_some() + } } impl eframe::App for App { diff --git a/src/gui/sections.rs b/src/gui/sections.rs index 23588415..5d3e9a5d 100644 --- a/src/gui/sections.rs +++ b/src/gui/sections.rs @@ -2,9 +2,10 @@ use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; use crate::{ compression::CompressionFormat, - device::{self, Removable, WriteTarget, enumerate_devices}, + device, gui::app::{App, FileHashOptions}, hash::parse_hash_input, + ui::BeginParams, }; pub fn add_file_hash_ui(hash_options: &mut FileHashOptions, ui: &mut egui::Ui) { @@ -146,4 +147,32 @@ pub fn add_target_disk_ui(app: &mut App, ui: &mut egui::Ui) { }); } -pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) {} +pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) { + ui.add_enabled_ui(app.is_ready_for_writing(), |ui| { + ui.strong("Write"); + if ui.button("Prepare for writing").clicked() { + // FIXME: + // don't unwrap. + // actually don't even have this shitty refresh button, + // should just refresh when any of the underlying values change + app.begin_params = BeginParams::new( + app.picked_image.clone().unwrap(), + app.detected_compression_format.unwrap(), + app.selected_write_target.clone().unwrap(), + ) + .ok(); + } + + if let Some(begin_params) = &app.begin_params { + ui.label(begin_params.to_string()); + ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); + ui.label( + RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") + .color(Color32::YELLOW), + ); + if ui.button("Perform write").clicked() { + // TODO: + } + } + }); +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 189f8b50..d74901c5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,6 +6,7 @@ mod utils; use std::{fs::File, sync::Arc}; pub use self::cli::BurnArgs; +pub use self::start::BeginParams; pub use self::utils::ByteSpeed; use crate::{ logging::LogPaths, From 9ad3e082b5a06ffc723ad1de18f79ee13178d6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 30 Apr 2026 12:29:51 +0200 Subject: [PATCH 07/29] REMOVEME: remove all CI drop/revert this commit later! just for skipping CI when i'm in draft --- .github/workflows/ci-unix.yml | 74 ----------------- .github/workflows/ci.yml | 148 ---------------------------------- .github/workflows/deb.yml | 27 ------- .github/workflows/publish.yml | 61 -------------- 4 files changed, 310 deletions(-) delete mode 100644 .github/workflows/ci-unix.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/deb.yml delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci-unix.yml b/.github/workflows/ci-unix.yml deleted file mode 100644 index ffbbe3f2..00000000 --- a/.github/workflows/ci-unix.yml +++ /dev/null @@ -1,74 +0,0 @@ -on: - workflow_call: - inputs: - build_runner: - required: true - type: string - e2e_test_runner: - required: false - type: string - target_triple: - required: true - type: string - do_e2e_tests: - required: true - type: boolean - use_cachix: - required: true - type: boolean - secrets: - CACHIX_AUTH_TOKEN: - required: false - -env: - nix_target: ".#caligula-${{ inputs.target_triple }}" - artifact: "caligula-${{ inputs.target_triple }}" - -jobs: - build: - name: "Build using Nix" - runs-on: ${{ inputs.build_runner }} - steps: - - uses: actions/checkout@v4 - - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - extra_nix_config: | - extra-platforms = ${{ inputs.target_triple }} - system-features = kvm benchmark big-parallel nixos-test uid-range - - - uses: cachix/cachix-action@v16 - if: ${{ inputs.use_cachix }} - with: - name: astralbijection - authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - - - name: Build package - run: nix build ${{ env.nix_target }} --show-trace --log-lines 10000 --fallback - - - uses: actions/upload-artifact@v4 - with: - name: ${{ env.artifact }} - path: result/bin/caligula - - - name: Run checks - if: inputs.do_e2e_tests - run: nix flake check --keep-going - - e2e-test-fresh: - name: e2e test on fresh ${{ inputs.e2e_test_runner }} - runs-on: ${{ inputs.e2e_test_runner }} - needs: build - if: inputs.do_e2e_tests - - steps: - - uses: actions/download-artifact@v5 - with: - name: ${{ env.artifact }} - - - name: Set permissions on artifact - run: chmod +x caligula - - - name: Ensure the command runs - run: ./caligula --help diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 384a0f97..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: "CI" - -on: - push: - pull_request: - pull_request_target: - -jobs: - lint: - name: Run formatting and linting checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - extra_nix_config: | - extra-platforms = ${{ inputs.target_triple }} - - - name: Prefetch lint script dependencies - run: nix build .#lint-script - - - name: Run lint script - run: nix run .#lint-script - - cargo-machete: - name: Check for unused dependencies - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: bnjbvr/cargo-machete@v0.9.2 - - ci-x86_64-linux: - uses: ./.github/workflows/ci-unix.yml - with: - build_runner: ubuntu-latest - e2e_test_runner: ubuntu-latest - target_triple: x86_64-linux - do_e2e_tests: true - use_cachix: ${{ github.event_name == 'push' }} - secrets: inherit - - ci-aarch64-linux: - uses: ./.github/workflows/ci-unix.yml - with: - build_runner: ubuntu-latest - target_triple: aarch64-linux - do_e2e_tests: false - use_cachix: ${{ github.event_name == 'push' }} - secrets: inherit - - ci-darwin: - name: CI ${{ matrix.target }} - runs-on: ${{ matrix.runner }} - - strategy: - fail-fast: false - matrix: - include: - - name: macOS Intel (x86_64) - runner: macos-15-intel - target: x86_64-darwin - rustTarget: x86_64-apple-darwin - - - name: macOS Apple Silicon (arm64) - runner: macos-latest - target: aarch64-darwin - rustTarget: aarch64-apple-darwin - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.rustTarget }} - - - name: Cache cargo - uses: Swatinem/rust-cache@v2 - - - name: Test (release) - run: cargo test --release - - - name: Build (release) - run: cargo build --release - - - name: Prepare artifact - run: | - cp target/release/caligula caligula - - - name: Test binary - run: | - ./caligula --help - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: caligula-${{ matrix.target }} - path: caligula - - ci-devshell: - strategy: - matrix: - include: - - runner: macos-latest - platform: x86_64-darwin - - runner: ubuntu-latest - platform: x86_64-linux - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 - if: ${{ github.event_name == 'push' }} - with: - name: astralbijection - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - name: Build dev shell - run: nix build .#devShells.${{ matrix.platform }}.default --show-trace --log-lines 10000 --fallback - - make-release-group: - runs-on: ubuntu-latest - needs: - - ci-aarch64-linux - - ci-x86_64-linux - - ci-darwin - steps: - - name: Download all artifacts - uses: actions/download-artifact@v5 - - - name: Assemble release directory - run: | - mkdir release - for i in caligula-*; do - cp "$i/caligula" "release/$i" - done - - tree - - - uses: actions/upload-artifact@v4 - with: - name: release - path: release diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml deleted file mode 100644 index 71dd6e7e..00000000 --- a/.github/workflows/deb.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Build Debian Package - -on: - push: - branches: [ main, feature/161-debian-packaging ] - pull_request: - -jobs: - build-deb: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Install cargo-deb - run: cargo install cargo-deb - - - name: Build Debian package - run: cargo deb - - - name: Upload Debian package artifact - uses: actions/upload-artifact@v4 - with: - name: caligula-deb - path: target/debian/*.deb diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 5713bb26..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: "Publish pipeline" - -on: - push: - workflow_dispatch: - release: - types: [published] - -jobs: - aur-publish: - name: Publish to AUR - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 - with: - name: astralbijection - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - - name: Generate PKGBUILD - run: | - nix build .#caligula-bin-aur - cat result/PKGBUILD - cp result/PKGBUILD . - - - name: Publish AUR package - uses: KSXGitHub/github-actions-deploy-aur@v2 - if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' }} - with: - pkgname: caligula-bin - pkgbuild: ./PKGBUILD - commit_username: ifd3f - commit_email: astrid@astrid.tech - ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} - commit_message: Update AUR package - ssh_keyscan_types: rsa,dsa,ecdsa,ed25519 - - deb-publish: - name: Upload Debian Package - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Install cargo-deb - run: cargo install cargo-deb - - - name: Build Debian package - run: cargo deb - - - name: Upload .deb to GitHub Release - if: ${{ github.event_name == 'release' && github.event.action == 'published' }} - uses: softprops/action-gh-release@v2 - with: - files: target/debian/*.deb From 05c9644fa3f48c8c01b20a552e917fff25ec2367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 30 Apr 2026 12:30:39 +0200 Subject: [PATCH 08/29] Revert "REMOVEME: remove all CI" This reverts commit 15d74f61f79abc28ee764f80fc523447ee4b1c98. --- .github/workflows/ci-unix.yml | 74 +++++++++++++++++ .github/workflows/ci.yml | 148 ++++++++++++++++++++++++++++++++++ .github/workflows/deb.yml | 27 +++++++ .github/workflows/publish.yml | 61 ++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 .github/workflows/ci-unix.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deb.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci-unix.yml b/.github/workflows/ci-unix.yml new file mode 100644 index 00000000..ffbbe3f2 --- /dev/null +++ b/.github/workflows/ci-unix.yml @@ -0,0 +1,74 @@ +on: + workflow_call: + inputs: + build_runner: + required: true + type: string + e2e_test_runner: + required: false + type: string + target_triple: + required: true + type: string + do_e2e_tests: + required: true + type: boolean + use_cachix: + required: true + type: boolean + secrets: + CACHIX_AUTH_TOKEN: + required: false + +env: + nix_target: ".#caligula-${{ inputs.target_triple }}" + artifact: "caligula-${{ inputs.target_triple }}" + +jobs: + build: + name: "Build using Nix" + runs-on: ${{ inputs.build_runner }} + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + extra_nix_config: | + extra-platforms = ${{ inputs.target_triple }} + system-features = kvm benchmark big-parallel nixos-test uid-range + + - uses: cachix/cachix-action@v16 + if: ${{ inputs.use_cachix }} + with: + name: astralbijection + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Build package + run: nix build ${{ env.nix_target }} --show-trace --log-lines 10000 --fallback + + - uses: actions/upload-artifact@v4 + with: + name: ${{ env.artifact }} + path: result/bin/caligula + + - name: Run checks + if: inputs.do_e2e_tests + run: nix flake check --keep-going + + e2e-test-fresh: + name: e2e test on fresh ${{ inputs.e2e_test_runner }} + runs-on: ${{ inputs.e2e_test_runner }} + needs: build + if: inputs.do_e2e_tests + + steps: + - uses: actions/download-artifact@v5 + with: + name: ${{ env.artifact }} + + - name: Set permissions on artifact + run: chmod +x caligula + + - name: Ensure the command runs + run: ./caligula --help diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..384a0f97 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,148 @@ +name: "CI" + +on: + push: + pull_request: + pull_request_target: + +jobs: + lint: + name: Run formatting and linting checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + extra_nix_config: | + extra-platforms = ${{ inputs.target_triple }} + + - name: Prefetch lint script dependencies + run: nix build .#lint-script + + - name: Run lint script + run: nix run .#lint-script + + cargo-machete: + name: Check for unused dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: bnjbvr/cargo-machete@v0.9.2 + + ci-x86_64-linux: + uses: ./.github/workflows/ci-unix.yml + with: + build_runner: ubuntu-latest + e2e_test_runner: ubuntu-latest + target_triple: x86_64-linux + do_e2e_tests: true + use_cachix: ${{ github.event_name == 'push' }} + secrets: inherit + + ci-aarch64-linux: + uses: ./.github/workflows/ci-unix.yml + with: + build_runner: ubuntu-latest + target_triple: aarch64-linux + do_e2e_tests: false + use_cachix: ${{ github.event_name == 'push' }} + secrets: inherit + + ci-darwin: + name: CI ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + + strategy: + fail-fast: false + matrix: + include: + - name: macOS Intel (x86_64) + runner: macos-15-intel + target: x86_64-darwin + rustTarget: x86_64-apple-darwin + + - name: macOS Apple Silicon (arm64) + runner: macos-latest + target: aarch64-darwin + rustTarget: aarch64-apple-darwin + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rustTarget }} + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + + - name: Test (release) + run: cargo test --release + + - name: Build (release) + run: cargo build --release + + - name: Prepare artifact + run: | + cp target/release/caligula caligula + + - name: Test binary + run: | + ./caligula --help + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: caligula-${{ matrix.target }} + path: caligula + + ci-devshell: + strategy: + matrix: + include: + - runner: macos-latest + platform: x86_64-darwin + - runner: ubuntu-latest + platform: x86_64-linux + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v5 + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v16 + if: ${{ github.event_name == 'push' }} + with: + name: astralbijection + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Build dev shell + run: nix build .#devShells.${{ matrix.platform }}.default --show-trace --log-lines 10000 --fallback + + make-release-group: + runs-on: ubuntu-latest + needs: + - ci-aarch64-linux + - ci-x86_64-linux + - ci-darwin + steps: + - name: Download all artifacts + uses: actions/download-artifact@v5 + + - name: Assemble release directory + run: | + mkdir release + for i in caligula-*; do + cp "$i/caligula" "release/$i" + done + + tree + + - uses: actions/upload-artifact@v4 + with: + name: release + path: release diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml new file mode 100644 index 00000000..71dd6e7e --- /dev/null +++ b/.github/workflows/deb.yml @@ -0,0 +1,27 @@ +name: Build Debian Package + +on: + push: + branches: [ main, feature/161-debian-packaging ] + pull_request: + +jobs: + build-deb: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build Debian package + run: cargo deb + + - name: Upload Debian package artifact + uses: actions/upload-artifact@v4 + with: + name: caligula-deb + path: target/debian/*.deb diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..5713bb26 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,61 @@ +name: "Publish pipeline" + +on: + push: + workflow_dispatch: + release: + types: [published] + +jobs: + aur-publish: + name: Publish to AUR + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v16 + with: + name: astralbijection + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Generate PKGBUILD + run: | + nix build .#caligula-bin-aur + cat result/PKGBUILD + cp result/PKGBUILD . + + - name: Publish AUR package + uses: KSXGitHub/github-actions-deploy-aur@v2 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' }} + with: + pkgname: caligula-bin + pkgbuild: ./PKGBUILD + commit_username: ifd3f + commit_email: astrid@astrid.tech + ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} + commit_message: Update AUR package + ssh_keyscan_types: rsa,dsa,ecdsa,ed25519 + + deb-publish: + name: Upload Debian Package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build Debian package + run: cargo deb + + - name: Upload .deb to GitHub Release + if: ${{ github.event_name == 'release' && github.event.action == 'published' }} + uses: softprops/action-gh-release@v2 + with: + files: target/debian/*.deb From b97c443e12533d9ea336970bc11856e46e704c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 30 Apr 2026 14:43:41 +0200 Subject: [PATCH 09/29] WIP: use local flavor of tokio runtime (PROBABLY NOT THE SOLUTION) and try to get writing to work not working yet --- src/gui/app.rs | 82 ++++++++++++++-------- src/gui/mod.rs | 8 ++- src/gui/sections.rs | 166 ++++++++++++++++++++++++++++++++------------ src/main.rs | 18 ++++- src/ui/mod.rs | 16 ++--- 5 files changed, 204 insertions(+), 86 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 338fc824..d11c2a38 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,19 +1,35 @@ use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; -use std::path::PathBuf; +use std::{ + path::PathBuf, + rc::Rc, + sync::{Arc, Mutex}, +}; use crate::{ compression::CompressionFormat, device::{Removable, WriteTarget, enumerate_devices}, gui::sections::{add_begin_writing_ui, add_file_hash_ui, add_image_ui, add_target_disk_ui}, hash::HashAlg, + logging::LogPaths, ui::BeginParams, }; +pub struct App { + pub log_paths: Arc, + pub options: Options, + pub ongoing_write: Rc>>, +} + +pub struct OngoingWrite { + pub write_progress: u64, + pub verify_progress: u64, +} + #[derive(Default)] #[cfg_attr(debug_assertions, derive(serde::Deserialize, serde::Serialize))] -pub struct App { +pub struct Options { pub picked_image: Option, - pub file_hash_options: FileHashOptions, + pub file_hash: FileHashOptions, pub detected_compression_format: Option, #[cfg_attr(debug_assertions, serde(skip))] pub possible_write_targets: Vec, @@ -22,6 +38,8 @@ pub struct App { pub show_all_disks: bool, #[cfg_attr(debug_assertions, serde(skip))] pub begin_params: Option, + #[cfg_attr(debug_assertions, serde(skip))] + pub has_confirmed_writing: bool, } #[derive(Default)] @@ -37,19 +55,19 @@ pub struct FileHashOptions { } impl App { - #[cfg(not(debug_assertions))] - pub fn new(_cc: &eframe::CreationContext) -> Self { - let mut s = Self::default(); - s.refresh_devices(); - s - } - - #[cfg(debug_assertions)] - pub fn new(cc: &eframe::CreationContext) -> Self { - let mut s: Self = cc + pub fn new(cc: &eframe::CreationContext, log_paths: Arc) -> Self { + #[cfg(not(debug_assertions))] + let options = Options::default(); + #[cfg(debug_assertions)] + let options: Options = cc .storage .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) .unwrap_or_default(); + let mut s = Self { + log_paths, + options, + ongoing_write: Rc::new(Mutex::new(None)), + }; s.refresh_devices(); s } @@ -57,20 +75,20 @@ impl App { pub fn refresh_devices(&mut self) { // TODO: deduplicate this. // This is code stolen from `ask_outfile.rs`! - self.possible_write_targets = enumerate_devices() - .filter(|d| self.show_all_disks || d.removable == Removable::Yes) + self.options.possible_write_targets = enumerate_devices() + .filter(|d| self.options.show_all_disks || d.removable == Removable::Yes) .collect(); - self.possible_write_targets.sort(); + self.options.possible_write_targets.sort(); } - pub fn file_hash_is_valid_or_skipped(&self) -> bool { - self.file_hash_options.verified || self.file_hash_options.skip + pub fn file_hash_is_verified_or_skipped(&self) -> bool { + self.options.file_hash.verified || self.options.file_hash.skip } pub fn is_ready_for_writing(&self) -> bool { - self.picked_image.is_some() - && self.file_hash_is_valid_or_skipped() - && self.selected_write_target.is_some() + self.options.picked_image.is_some() + && self.file_hash_is_verified_or_skipped() + && self.options.selected_write_target.is_some() } } @@ -91,29 +109,33 @@ impl eframe::App for App { }); CentralPanel::default().show_inside(ui, |ui| { + if let Some(ongoing_write) = &*self.ongoing_write.lock().unwrap() { + ui.label("writing!!"); + return; + } + ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); ui.label(env!("CARGO_PKG_DESCRIPTION")); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - add_image_ui(self, ui); + add_image_ui(&mut self.options, ui); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.add_enabled_ui(self.picked_image.is_some(), |ui| { - add_file_hash_ui(&mut self.file_hash_options, ui) + ui.add_enabled_ui(self.options.picked_image.is_some(), |ui| { + add_file_hash_ui(&mut self.options.file_hash, ui) }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.add_enabled_ui( - self.file_hash_options.verified || self.file_hash_options.skip, - |ui| add_target_disk_ui(self, ui), - ); + ui.add_enabled_ui(self.file_hash_is_verified_or_skipped(), |ui| { + add_target_disk_ui(self, ui) + }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - ui.add_enabled_ui(self.selected_write_target.is_some(), |ui| { + ui.add_enabled_ui(self.options.selected_write_target.is_some(), |ui| { add_begin_writing_ui(self, ui) }); }); @@ -124,6 +146,6 @@ impl eframe::App for App { #[cfg(debug_assertions)] fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, eframe::APP_KEY, self); + eframe::set_value(storage, eframe::APP_KEY, &self.options); } } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 71801e69..4a3df878 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -2,11 +2,15 @@ mod app; mod sections; use app::App; +use std::sync::Arc; +use tokio::runtime::Handle; -pub fn run_gui() -> eframe::Result { +use crate::logging::LogPaths; + +pub fn main(log_paths: Arc) -> eframe::Result<()> { eframe::run_native( "caligula-gui", Default::default(), - Box::new(|cc| Ok(Box::new(App::new(cc)))), + Box::new(|cc| Ok(Box::new(App::new(cc, log_paths)))), ) } diff --git a/src/gui/sections.rs b/src/gui/sections.rs index 5d3e9a5d..300ae845 100644 --- a/src/gui/sections.rs +++ b/src/gui/sections.rs @@ -1,11 +1,15 @@ use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; +use futures::StreamExt; +use std::time::Instant; +use tokio::task::LocalSet; use crate::{ compression::CompressionFormat, device, - gui::app::{App, FileHashOptions}, + gui::app::{App, FileHashOptions, OngoingWrite, Options}, hash::parse_hash_input, - ui::BeginParams, + herder_facade::make_herder_facade_impl, + ui::{BeginParams, Interactive, UseSudo, WriterState, try_start_burn}, }; pub fn add_file_hash_ui(hash_options: &mut FileHashOptions, ui: &mut egui::Ui) { @@ -75,12 +79,12 @@ pub fn add_file_hash_ui(hash_options: &mut FileHashOptions, ui: &mut egui::Ui) { ); } -pub fn add_image_ui(app: &mut App, ui: &mut egui::Ui) { - let App { +pub fn add_image_ui(options: &mut Options, ui: &mut egui::Ui) { + let Options { detected_compression_format, picked_image, .. - } = app; + } = options; ui.strong("Image"); if ui.button("💿 Pick file").clicked() @@ -114,37 +118,45 @@ pub fn add_target_disk_ui(app: &mut App, ui: &mut egui::Ui) { // - stop alloc:ing and doing so much work here.. DON'T CLONE! // - move the label formatting into a place where it's done ONCE, not on every ui render! // - deduplicate, label formatting is stolen from `ask_outfile.rs` - ComboBox::from_label(format!("{} available", app.possible_write_targets.len())) - .selected_text( - app.selected_write_target - .as_ref() - .map(|dev| match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }) - .unwrap_or_default(), - ) - .show_ui(ui, |ui| { - for dev in &app.possible_write_targets { - let label = match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }; - ui.selectable_value(&mut app.selected_write_target, Some(dev.clone()), label); - } - }); + ComboBox::from_label(format!( + "{} available", + app.options.possible_write_targets.len() + )) + .selected_text( + app.options + .selected_write_target + .as_ref() + .map(|dev| match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }) + .unwrap_or_default(), + ) + .show_ui(ui, |ui| { + for dev in &app.options.possible_write_targets { + let label = match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }; + ui.selectable_value( + &mut app.options.selected_write_target, + Some(dev.clone()), + label, + ); + } + }); } pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) { @@ -155,23 +167,91 @@ pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) { // don't unwrap. // actually don't even have this shitty refresh button, // should just refresh when any of the underlying values change - app.begin_params = BeginParams::new( - app.picked_image.clone().unwrap(), - app.detected_compression_format.unwrap(), - app.selected_write_target.clone().unwrap(), + app.options.begin_params = BeginParams::new( + app.options.picked_image.clone().unwrap(), + app.options.detected_compression_format.unwrap(), + app.options.selected_write_target.clone().unwrap(), ) .ok(); } - if let Some(begin_params) = &app.begin_params { + if let Some(begin_params) = &app.options.begin_params { ui.label(begin_params.to_string()); ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); + + if !app.options.has_confirmed_writing { + if ui.button("Perform write").clicked() { + app.options.has_confirmed_writing = true; + } + return; + } + ui.label( RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") .color(Color32::YELLOW), ); - if ui.button("Perform write").clicked() { - // TODO: + + if ui.button("I know, do it!").clicked() { + // TODO: make sure this really needs to clone + let log_paths = app.log_paths.clone(); + let begin_params = begin_params.clone(); + let cf = app.options.detected_compression_format.unwrap(); // FIXME: + let ongoing_write = app.ongoing_write.clone(); + + tokio::task::spawn_local(async move { + eprintln!("inside task!"); + let mut herder = make_herder_facade_impl(log_paths.main()); + + // FIXME: parameters to `try_start_burn` + let interactive = Interactive::Never; + let mut handle = try_start_burn( + &mut herder, + &begin_params.make_child_config(), + UseSudo::Never, + interactive.is_interactive(), + ) + .await?; + + let input_file_bytes = handle.initial_info.input_file_bytes; + + let mut child_state = + WriterState::initial(Instant::now(), !cf.is_identity(), input_file_bytes); + + *ongoing_write.lock().unwrap() = Some(OngoingWrite { + write_progress: 0, + verify_progress: 0, + }); + + loop { + eprintln!("got event!"); + let x = handle.events.next().await; + child_state = child_state.on_status(Instant::now(), x); + // FIXME: fugly-ass unwrapping + match &child_state { + WriterState::Writing(b) => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .write_progress = (b.approximate_ratio() * 1000.0) as u64 + } + WriterState::Verifying { + total_write_bytes, .. + } => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .verify_progress = total_write_bytes * 1000 / input_file_bytes + } + WriterState::Finished { .. } => break, + } + } + + anyhow::Ok(()) + }); } } }); diff --git a/src/main.rs b/src/main.rs index 912ce89b..2c084a22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use clap::{CommandFactory as _, Parser}; use tracing::debug; -use crate::orchestrator::make_orchestrator_impl; +use crate::logging::LogPaths; mod byteseries; mod compression; @@ -54,7 +54,8 @@ pub struct HerderDaemonArgs { log_file: String, } -fn main() { +#[tokio::main(flavor = "local")] +async fn main() { let args: Args = match std::env::var("_CALIGULA_CONFIGURE_CLAP_FOR_README") { Ok(var) if var == "1" => parse_args_for_readme_generation(), _ => Args::parse(), @@ -77,7 +78,18 @@ fn main() { } #[cfg(feature = "gui")] Command::Gui => { - gui::run_gui().unwrap(); + // FIXME: duplicated setup from `Command::Burn` + let state_dir = util::ensure_state_dir().await.unwrap(); + let log_paths = logging::LogPaths::init(state_dir); + logging::init_logging_parent(&log_paths); + + debug!("Starting primary process"); + + match gui::main(log_paths.into()) { + Ok(_) => (), + // FIXME: shitty to_string on error + Err(e) => handle_toplevel_error(anyhow::anyhow!(e.to_string())), + } } Command::HerderDaemon(args) => { logging::init_logging_child(args.log_file); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d74901c5..2a6f1c2b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -5,15 +5,15 @@ mod utils; use std::{fs::File, sync::Arc}; -pub use self::cli::BurnArgs; -pub use self::start::BeginParams; -pub use self::utils::ByteSpeed; +pub use self::{ + cli::{BurnArgs, Interactive, UseSudo}, + start::{BeginParams, begin_writing, try_start_burn}, + utils::ByteSpeed, + writer_tracking::WriterState, +}; use crate::{ - logging::LogPaths, - orchestrator::Orchestrator, - runtime::RemoteSpawn, - tty::TermiosRestore, - ui::{simple_ui::do_setup_wizard, utils::TUICapture}, + herder_facade::make_herder_facade_impl, logging::LogPaths, tty::TermiosRestore, + ui::simple_ui::do_setup_wizard, }; use tracing::{debug, info}; From 149f79904107f52c9b54b1cedd520ba88ee96888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Mon, 4 May 2026 19:25:29 +0200 Subject: [PATCH 10/29] go back to multithreaded tokio runtime --- Cargo.toml | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 453d285a..c77804b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ sha1 = "0.10.6" sha2 = "0.10.9" shell-words = "1.1.0" thiserror = "1.0.69" -tokio = { version = "1.48.0", features = ["rt", "process", "io-util", "io-std", "macros", "fs"] } +tokio = { version = "1.48.0", features = ["rt-multi-thread", "process", "io-util", "io-std", "macros", "fs"] } tokio-stream = "0.1.18" tracing = { version = "0.1.41", default-features = false, features = [ "std", diff --git a/src/main.rs b/src/main.rs index 2c084a22..8956afb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ pub struct HerderDaemonArgs { log_file: String, } -#[tokio::main(flavor = "local")] +#[tokio::main] async fn main() { let args: Args = match std::env::var("_CALIGULA_CONFIGURE_CLAP_FOR_README") { Ok(var) if var == "1" => parse_args_for_readme_generation(), From 2da6c0317f418a2f13a032db37c85c64283bca6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Tue, 5 May 2026 08:35:17 +0200 Subject: [PATCH 11/29] somewhat working PoC, but insanely hacky. Spawning a thread just for a local tokio runtime there. That'sinsane. --- src/gui/app.rs | 11 +++-- src/gui/sections.rs | 117 ++++++++++++++++++++++++++------------------ 2 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index d11c2a38..4c11af2d 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,7 +1,6 @@ use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; use std::{ path::PathBuf, - rc::Rc, sync::{Arc, Mutex}, }; @@ -17,7 +16,7 @@ use crate::{ pub struct App { pub log_paths: Arc, pub options: Options, - pub ongoing_write: Rc>>, + pub ongoing_write: Arc>>, } pub struct OngoingWrite { @@ -66,7 +65,7 @@ impl App { let mut s = Self { log_paths, options, - ongoing_write: Rc::new(Mutex::new(None)), + ongoing_write: Arc::new(Mutex::new(None)), }; s.refresh_devices(); s @@ -111,6 +110,12 @@ impl eframe::App for App { CentralPanel::default().show_inside(ui, |ui| { if let Some(ongoing_write) = &*self.ongoing_write.lock().unwrap() { ui.label("writing!!"); + ui.label(format!("Write progress: {}%", ongoing_write.write_progress)); + ui.label(format!( + "Verify progress: {}%", + ongoing_write.verify_progress + )); + return; } diff --git a/src/gui/sections.rs b/src/gui/sections.rs index 300ae845..f42cf6c4 100644 --- a/src/gui/sections.rs +++ b/src/gui/sections.rs @@ -1,7 +1,8 @@ use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; use futures::StreamExt; -use std::time::Instant; +use std::{thread, time::Instant}; use tokio::task::LocalSet; +use tracing::info; use crate::{ compression::CompressionFormat, @@ -197,60 +198,82 @@ pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) { let begin_params = begin_params.clone(); let cf = app.options.detected_compression_format.unwrap(); // FIXME: let ongoing_write = app.ongoing_write.clone(); + let egui_ctx = ui.ctx().clone(); - tokio::task::spawn_local(async move { - eprintln!("inside task!"); - let mut herder = make_herder_facade_impl(log_paths.main()); + // FIXME: + // absolutely terrible solution, spawning a thread + // just to create a local tokio runtime. + // this is just a hack... + thread::spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); - // FIXME: parameters to `try_start_burn` - let interactive = Interactive::Never; - let mut handle = try_start_burn( - &mut herder, - &begin_params.make_child_config(), - UseSudo::Never, - interactive.is_interactive(), - ) - .await?; + let local = tokio::task::LocalSet::new(); - let input_file_bytes = handle.initial_info.input_file_bytes; + local + .block_on(&rt, async move { + eprintln!("inside task!"); + let mut herder = make_herder_facade_impl(log_paths.main()); - let mut child_state = - WriterState::initial(Instant::now(), !cf.is_identity(), input_file_bytes); + // FIXME: parameters to `try_start_burn` + let interactive = Interactive::Never; + let mut handle = try_start_burn( + &mut herder, + &begin_params.make_child_config(), + UseSudo::Never, + interactive.is_interactive(), + ) + .await?; - *ongoing_write.lock().unwrap() = Some(OngoingWrite { - write_progress: 0, - verify_progress: 0, - }); + let input_file_bytes = handle.initial_info.input_file_bytes; - loop { - eprintln!("got event!"); - let x = handle.events.next().await; - child_state = child_state.on_status(Instant::now(), x); - // FIXME: fugly-ass unwrapping - match &child_state { - WriterState::Writing(b) => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .write_progress = (b.approximate_ratio() * 1000.0) as u64 - } - WriterState::Verifying { - total_write_bytes, .. - } => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .verify_progress = total_write_bytes * 1000 / input_file_bytes + let mut child_state = WriterState::initial( + Instant::now(), + !cf.is_identity(), + input_file_bytes, + ); + + *ongoing_write.lock().unwrap() = Some(OngoingWrite { + write_progress: 0, + verify_progress: 0, + }); + + loop { + let x = handle.events.next().await; + info!(?x, "got event from burn handle"); + child_state = child_state.on_status(Instant::now(), x); + // FIXME: fugly-ass unwrapping + match &child_state { + WriterState::Writing(b) => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .write_progress = + (b.approximate_ratio() * 1000.0) as u64 + } + WriterState::Verifying { + total_write_bytes, .. + } => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .verify_progress = + total_write_bytes * 1000 / input_file_bytes + } + WriterState::Finished { .. } => break, + } + egui_ctx.request_repaint(); } - WriterState::Finished { .. } => break, - } - } - anyhow::Ok(()) + anyhow::Ok(()) + }) + .unwrap(); }); } } From aac7fffcc3b099db52959788b268cbf99cc18893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 6 May 2026 12:10:30 +0200 Subject: [PATCH 12/29] revert changes to other code --- src/main.rs | 16 +++++++++------- src/ui/mod.rs | 15 +++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8956afb6..f6de0f82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use clap::{CommandFactory as _, Parser}; use tracing::debug; -use crate::logging::LogPaths; +use crate::orchestrator::make_orchestrator_impl; mod byteseries; mod compression; @@ -54,8 +54,7 @@ pub struct HerderDaemonArgs { log_file: String, } -#[tokio::main] -async fn main() { +fn main() { let args: Args = match std::env::var("_CALIGULA_CONFIGURE_CLAP_FOR_README") { Ok(var) if var == "1" => parse_args_for_readme_generation(), _ => Args::parse(), @@ -79,13 +78,16 @@ async fn main() { #[cfg(feature = "gui")] Command::Gui => { // FIXME: duplicated setup from `Command::Burn` - let state_dir = util::ensure_state_dir().await.unwrap(); - let log_paths = logging::LogPaths::init(state_dir); + + let state_dir = util::ensure_state_dir().unwrap(); + let log_paths = logging::LogPaths::init(&state_dir); logging::init_logging_parent(&log_paths); - debug!("Starting primary process"); + let runtime = crate::runtime::AsyncRuntime::start(); + let orc = Arc::new(make_orchestrator_impl(log_paths.main())); - match gui::main(log_paths.into()) { + debug!("Starting primary process"); + match gui::main(runtime, orc, log_paths.into()) { Ok(_) => (), // FIXME: shitty to_string on error Err(e) => handle_toplevel_error(anyhow::anyhow!(e.to_string())), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2a6f1c2b..189f8b50 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -5,15 +5,14 @@ mod utils; use std::{fs::File, sync::Arc}; -pub use self::{ - cli::{BurnArgs, Interactive, UseSudo}, - start::{BeginParams, begin_writing, try_start_burn}, - utils::ByteSpeed, - writer_tracking::WriterState, -}; +pub use self::cli::BurnArgs; +pub use self::utils::ByteSpeed; use crate::{ - herder_facade::make_herder_facade_impl, logging::LogPaths, tty::TermiosRestore, - ui::simple_ui::do_setup_wizard, + logging::LogPaths, + orchestrator::Orchestrator, + runtime::RemoteSpawn, + tty::TermiosRestore, + ui::{simple_ui::do_setup_wizard, utils::TUICapture}, }; use tracing::{debug, info}; From c351ee3d4916818f833cad6020765f325a546522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 6 May 2026 12:31:05 +0200 Subject: [PATCH 13/29] WIP: rework using syncified code. remove `sections.rs`, just have them as methods --- src/gui/app.rs | 273 +++++++++++++++++++++++++++++++++++++++--- src/gui/mod.rs | 12 +- src/gui/sections.rs | 281 -------------------------------------------- 3 files changed, 265 insertions(+), 301 deletions(-) delete mode 100644 src/gui/sections.rs diff --git a/src/gui/app.rs b/src/gui/app.rs index 4c11af2d..73f15a53 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,22 +1,27 @@ -use egui::{CentralPanel, MenuBar, Panel, RichText, ViewportCommand}; +use egui::{ + CentralPanel, Checkbox, Color32, ComboBox, MenuBar, Panel, RichText, UiBuilder, ViewportCommand, +}; use std::{ path::PathBuf, sync::{Arc, Mutex}, + time::Instant, }; use crate::{ compression::CompressionFormat, - device::{Removable, WriteTarget, enumerate_devices}, - gui::sections::{add_begin_writing_ui, add_file_hash_ui, add_image_ui, add_target_disk_ui}, - hash::HashAlg, + device::{self, Removable, WriteTarget, enumerate_devices}, + hash::{HashAlg, parse_hash_input}, logging::LogPaths, - ui::BeginParams, + orchestrator::{Orchestrator, WriteVerifyParams, WriterState}, + runtime::RemoteSpawn, }; -pub struct App { +pub struct App { pub log_paths: Arc, pub options: Options, pub ongoing_write: Arc>>, + pub orc: Arc, + pub runtime: R, } pub struct OngoingWrite { @@ -36,9 +41,9 @@ pub struct Options { pub selected_write_target: Option, pub show_all_disks: bool, #[cfg_attr(debug_assertions, serde(skip))] - pub begin_params: Option, - #[cfg_attr(debug_assertions, serde(skip))] pub has_confirmed_writing: bool, + #[cfg_attr(debug_assertions, serde(skip))] + pub write_verify_params: Option, } #[derive(Default)] @@ -53,21 +58,32 @@ pub struct FileHashOptions { pub verified: bool, } -impl App { - pub fn new(cc: &eframe::CreationContext, log_paths: Arc) -> Self { +impl App { + pub fn new( + cc: &eframe::CreationContext, + runtime: R, + orc: Arc, + log_paths: Arc, + ) -> Self { #[cfg(not(debug_assertions))] let options = Options::default(); + #[cfg(debug_assertions)] let options: Options = cc .storage .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) .unwrap_or_default(); + let mut s = Self { log_paths, options, ongoing_write: Arc::new(Mutex::new(None)), + orc, + runtime, }; + s.refresh_devices(); + s } @@ -89,9 +105,236 @@ impl App { && self.file_hash_is_verified_or_skipped() && self.options.selected_write_target.is_some() } + + pub fn add_file_hash_ui(&mut self, ui: &mut egui::Ui) { + let FileHashOptions { + entered_hash: file_hash_str, + possible_algorithms: file_hash_algorithms_possible, + selected_algorithm: file_hash_algorithm_selected, + last_error: latest_hashing_error, + skip: skip_hashing, + verified: verified_hash, + } = &mut self.options.file_hash; + + ui.horizontal(|ui| { + ui.strong("File hash"); + ui.checkbox(skip_hashing, "Skip?"); + }); + + ui.scope_builder( + UiBuilder { + disabled: *skip_hashing, + // invisible: *skip_hashing, + ..Default::default() + }, + |ui| { + ui.label("We will guess the hash algorithm from your input."); + if ui.text_edit_singleline(file_hash_str).changed() { + match parse_hash_input(file_hash_str) { + Ok((algs, _)) => { + if algs.len() == 1 { + *file_hash_algorithm_selected = Some(algs[0]); + } + *file_hash_algorithms_possible = algs; + latest_hashing_error.clear(); + } + Err(e) => { + *file_hash_algorithms_possible = vec![]; + *file_hash_algorithm_selected = None; + *latest_hashing_error = e.to_string(); + } + } + } + + if latest_hashing_error.is_empty() { + ui.horizontal(|ui| { + for alg in file_hash_algorithms_possible { + let is_selected = Some(*alg) == *file_hash_algorithm_selected; + + if ui.selectable_label(is_selected, alg.to_string()).clicked() { + *file_hash_algorithm_selected = Some(*alg); + } + } + }); + + ui.horizontal(|ui| { + ui.add_enabled(false, Checkbox::without_text(verified_hash)); + if ui.button("Verify").clicked() { + // TODO: + *verified_hash = true; + } + }); + } else if *skip_hashing { + ui.label(""); + } else { + ui.label(RichText::new(&*latest_hashing_error).color(Color32::RED)); + } + }, + ); + } + + pub fn add_image_ui(&mut self, ui: &mut egui::Ui) { + let Options { + detected_compression_format, + picked_image, + .. + } = &mut self.options; + + ui.strong("Image"); + if ui.button("💿 Pick file").clicked() + && let Some(picked) = rfd::FileDialog::new().pick_file() + { + *detected_compression_format = CompressionFormat::detect_from_path(&picked); + *picked_image = Some(picked); + } + if let Some(picked) = picked_image { + ui.label(picked.to_string_lossy()); + if let Some(cf) = detected_compression_format { + ui.label(format!("Detected compression format: {}", cf)); + } else { + ui.label( + RichText::new( + "Couldn't detect compression format for picked image, assuming uncompressed!", + ) + .color(Color32::YELLOW), + ); + } + } + } + + pub fn add_target_disk_ui(&mut self, ui: &mut egui::Ui) { + ui.strong("Target disk"); + if ui.button("Refresh devices").clicked() { + self.refresh_devices(); + } + + // FIXME: + // - stop alloc:ing and doing so much work here.. DON'T CLONE! + // - move the label formatting into a place where it's done ONCE, not on every ui render! + // - deduplicate, label formatting is stolen from `ask_outfile.rs` + ComboBox::from_label(format!( + "{} available", + self.options.possible_write_targets.len() + )) + .selected_text( + self.options + .selected_write_target + .as_ref() + .map(|dev| match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }) + .unwrap_or_default(), + ) + .show_ui(ui, |ui| { + for dev in &self.options.possible_write_targets { + let label = match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }; + ui.selectable_value( + &mut self.options.selected_write_target, + Some(dev.clone()), + label, + ); + } + }); + } + + pub fn add_begin_writing_ui(&mut self, ui: &mut egui::Ui) { + ui.add_enabled_ui(self.is_ready_for_writing(), |ui| { + ui.strong("Write"); + if ui.button("Prepare for writing").clicked() { + // FIXME: + // don't unwrap. + // actually don't even have this shitty refresh button, + // should just refresh when any of the underlying values change + self.options.write_verify_params = WriteVerifyParams::new( + self.options.picked_image.clone().unwrap(), + self.options.detected_compression_format.unwrap(), + self.options.selected_write_target.clone().unwrap(), + ) + .ok(); + } + + if let Some(write_verify_params) = &self.options.write_verify_params { + // TODO: show summary! + // ui.label(write_verify_params.to_string()); + + ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); + + if !self.options.has_confirmed_writing { + if ui.button("Perform write").clicked() { + self.options.has_confirmed_writing = true; + } + return; + } + + ui.label( + RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") + .color(Color32::YELLOW), + ); + + if ui.button("I know, do it!").clicked() { + // TODO: make sure this really needs to clone + let log_paths = self.log_paths.clone(); + let write_verify_params = self.options.write_verify_params.take(); + let cf = self.options.detected_compression_format.unwrap(); // FIXME: + let ongoing_write = self.ongoing_write.clone(); + let egui_ctx = ui.ctx().clone(); + + *ongoing_write.lock().unwrap() = Some(OngoingWrite { + write_progress: 0, + verify_progress: 0, + }); + + loop { + let x = handle.events.next().await; + info!(?x, "got event from burn handle"); + child_state = child_state.on_status(Instant::now(), x); + // FIXME: fugly-ass unwrselfing + match &child_state { + WriterState::Writing(b) => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .write_progress = (b.approximate_ratio() * 1000.0) as u64 + } + WriterState::Verifying { + total_write_bytes, .. + } => { + ongoing_write + .lock() + .unwrap() + .as_mut() + .unwrap() + .verify_progress = total_write_bytes * 1000 / input_file_bytes + } + WriterState::Finished { .. } => break, + } + egui_ctx.request_repaint(); + } + } + } + }); + } } -impl eframe::App for App { +impl eframe::App for App { fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { @@ -124,24 +367,24 @@ impl eframe::App for App { ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - add_image_ui(&mut self.options, ui); + self.add_image_ui(ui); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); ui.add_enabled_ui(self.options.picked_image.is_some(), |ui| { - add_file_hash_ui(&mut self.options.file_hash, ui) + self.add_file_hash_ui(ui) }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); ui.add_enabled_ui(self.file_hash_is_verified_or_skipped(), |ui| { - add_target_disk_ui(self, ui) + self.add_target_disk_ui(ui) }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); ui.add_enabled_ui(self.options.selected_write_target.is_some(), |ui| { - add_begin_writing_ui(self, ui) + self.add_begin_writing_ui(ui) }); }); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 4a3df878..94fbf814 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,16 +1,18 @@ mod app; -mod sections; use app::App; use std::sync::Arc; -use tokio::runtime::Handle; -use crate::logging::LogPaths; +use crate::{logging::LogPaths, orchestrator::Orchestrator, runtime::RemoteSpawn}; -pub fn main(log_paths: Arc) -> eframe::Result<()> { +pub fn main( + runtime: impl RemoteSpawn, + orc: Arc, + log_paths: Arc, +) -> eframe::Result<()> { eframe::run_native( "caligula-gui", Default::default(), - Box::new(|cc| Ok(Box::new(App::new(cc, log_paths)))), + Box::new(|cc| Ok(Box::new(App::new(cc, runtime, orc, log_paths)))), ) } diff --git a/src/gui/sections.rs b/src/gui/sections.rs deleted file mode 100644 index f42cf6c4..00000000 --- a/src/gui/sections.rs +++ /dev/null @@ -1,281 +0,0 @@ -use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; -use futures::StreamExt; -use std::{thread, time::Instant}; -use tokio::task::LocalSet; -use tracing::info; - -use crate::{ - compression::CompressionFormat, - device, - gui::app::{App, FileHashOptions, OngoingWrite, Options}, - hash::parse_hash_input, - herder_facade::make_herder_facade_impl, - ui::{BeginParams, Interactive, UseSudo, WriterState, try_start_burn}, -}; - -pub fn add_file_hash_ui(hash_options: &mut FileHashOptions, ui: &mut egui::Ui) { - let FileHashOptions { - entered_hash: file_hash_str, - possible_algorithms: file_hash_algorithms_possible, - selected_algorithm: file_hash_algorithm_selected, - last_error: latest_hashing_error, - skip: skip_hashing, - verified: verified_hash, - } = hash_options; - - ui.horizontal(|ui| { - ui.strong("File hash"); - ui.checkbox(skip_hashing, "Skip?"); - }); - - ui.scope_builder( - UiBuilder { - disabled: *skip_hashing, - // invisible: *skip_hashing, - ..Default::default() - }, - |ui| { - ui.label("We will guess the hash algorithm from your input."); - if ui.text_edit_singleline(file_hash_str).changed() { - match parse_hash_input(file_hash_str) { - Ok((algs, _)) => { - if algs.len() == 1 { - *file_hash_algorithm_selected = Some(algs[0]); - } - *file_hash_algorithms_possible = algs; - latest_hashing_error.clear(); - } - Err(e) => { - *file_hash_algorithms_possible = vec![]; - *file_hash_algorithm_selected = None; - *latest_hashing_error = e.to_string(); - } - } - } - - if latest_hashing_error.is_empty() { - ui.horizontal(|ui| { - for alg in file_hash_algorithms_possible { - let is_selected = Some(*alg) == *file_hash_algorithm_selected; - - if ui.selectable_label(is_selected, alg.to_string()).clicked() { - *file_hash_algorithm_selected = Some(*alg); - } - } - }); - - ui.horizontal(|ui| { - ui.add_enabled(false, Checkbox::without_text(verified_hash)); - if ui.button("Verify").clicked() { - // TODO: - *verified_hash = true; - } - }); - } else if *skip_hashing { - ui.label(""); - } else { - ui.label(RichText::new(&*latest_hashing_error).color(Color32::RED)); - } - }, - ); -} - -pub fn add_image_ui(options: &mut Options, ui: &mut egui::Ui) { - let Options { - detected_compression_format, - picked_image, - .. - } = options; - - ui.strong("Image"); - if ui.button("💿 Pick file").clicked() - && let Some(picked) = rfd::FileDialog::new().pick_file() - { - *detected_compression_format = CompressionFormat::detect_from_path(&picked); - *picked_image = Some(picked); - } - if let Some(picked) = picked_image { - ui.label(picked.to_string_lossy()); - if let Some(cf) = detected_compression_format { - ui.label(format!("Detected compression format: {}", cf)); - } else { - ui.label( - RichText::new( - "Couldn't detect compression format for picked image, assuming uncompressed!", - ) - .color(Color32::YELLOW), - ); - } - } -} - -pub fn add_target_disk_ui(app: &mut App, ui: &mut egui::Ui) { - ui.strong("Target disk"); - if ui.button("Refresh devices").clicked() { - app.refresh_devices(); - } - - // FIXME: - // - stop alloc:ing and doing so much work here.. DON'T CLONE! - // - move the label formatting into a place where it's done ONCE, not on every ui render! - // - deduplicate, label formatting is stolen from `ask_outfile.rs` - ComboBox::from_label(format!( - "{} available", - app.options.possible_write_targets.len() - )) - .selected_text( - app.options - .selected_write_target - .as_ref() - .map(|dev| match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }) - .unwrap_or_default(), - ) - .show_ui(ui, |ui| { - for dev in &app.options.possible_write_targets { - let label = match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }; - ui.selectable_value( - &mut app.options.selected_write_target, - Some(dev.clone()), - label, - ); - } - }); -} - -pub fn add_begin_writing_ui(app: &mut App, ui: &mut egui::Ui) { - ui.add_enabled_ui(app.is_ready_for_writing(), |ui| { - ui.strong("Write"); - if ui.button("Prepare for writing").clicked() { - // FIXME: - // don't unwrap. - // actually don't even have this shitty refresh button, - // should just refresh when any of the underlying values change - app.options.begin_params = BeginParams::new( - app.options.picked_image.clone().unwrap(), - app.options.detected_compression_format.unwrap(), - app.options.selected_write_target.clone().unwrap(), - ) - .ok(); - } - - if let Some(begin_params) = &app.options.begin_params { - ui.label(begin_params.to_string()); - ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); - - if !app.options.has_confirmed_writing { - if ui.button("Perform write").clicked() { - app.options.has_confirmed_writing = true; - } - return; - } - - ui.label( - RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") - .color(Color32::YELLOW), - ); - - if ui.button("I know, do it!").clicked() { - // TODO: make sure this really needs to clone - let log_paths = app.log_paths.clone(); - let begin_params = begin_params.clone(); - let cf = app.options.detected_compression_format.unwrap(); // FIXME: - let ongoing_write = app.ongoing_write.clone(); - let egui_ctx = ui.ctx().clone(); - - // FIXME: - // absolutely terrible solution, spawning a thread - // just to create a local tokio runtime. - // this is just a hack... - thread::spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - let local = tokio::task::LocalSet::new(); - - local - .block_on(&rt, async move { - eprintln!("inside task!"); - let mut herder = make_herder_facade_impl(log_paths.main()); - - // FIXME: parameters to `try_start_burn` - let interactive = Interactive::Never; - let mut handle = try_start_burn( - &mut herder, - &begin_params.make_child_config(), - UseSudo::Never, - interactive.is_interactive(), - ) - .await?; - - let input_file_bytes = handle.initial_info.input_file_bytes; - - let mut child_state = WriterState::initial( - Instant::now(), - !cf.is_identity(), - input_file_bytes, - ); - - *ongoing_write.lock().unwrap() = Some(OngoingWrite { - write_progress: 0, - verify_progress: 0, - }); - - loop { - let x = handle.events.next().await; - info!(?x, "got event from burn handle"); - child_state = child_state.on_status(Instant::now(), x); - // FIXME: fugly-ass unwrapping - match &child_state { - WriterState::Writing(b) => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .write_progress = - (b.approximate_ratio() * 1000.0) as u64 - } - WriterState::Verifying { - total_write_bytes, .. - } => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .verify_progress = - total_write_bytes * 1000 / input_file_bytes - } - WriterState::Finished { .. } => break, - } - egui_ctx.request_repaint(); - } - - anyhow::Ok(()) - }) - .unwrap(); - }); - } - } - }); -} From c479b7d4f0ff7a95da28a45225ec4ff47475172f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 6 May 2026 12:32:25 +0200 Subject: [PATCH 14/29] rename methods, simplify --- src/gui/app.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 73f15a53..b1af1f3c 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -19,7 +19,7 @@ use crate::{ pub struct App { pub log_paths: Arc, pub options: Options, - pub ongoing_write: Arc>>, + pub ongoing_write: Option, pub orc: Arc, pub runtime: R, } @@ -106,7 +106,7 @@ impl App { && self.options.selected_write_target.is_some() } - pub fn add_file_hash_ui(&mut self, ui: &mut egui::Ui) { + pub fn file_hash_ui(&mut self, ui: &mut egui::Ui) { let FileHashOptions { entered_hash: file_hash_str, possible_algorithms: file_hash_algorithms_possible, @@ -173,7 +173,7 @@ impl App { ); } - pub fn add_image_ui(&mut self, ui: &mut egui::Ui) { + pub fn image_ui(&mut self, ui: &mut egui::Ui) { let Options { detected_compression_format, picked_image, @@ -202,7 +202,7 @@ impl App { } } - pub fn add_target_disk_ui(&mut self, ui: &mut egui::Ui) { + pub fn target_disk_ui(&mut self, ui: &mut egui::Ui) { ui.strong("Target disk"); if ui.button("Refresh devices").clicked() { self.refresh_devices(); @@ -288,14 +288,9 @@ impl App { ); if ui.button("I know, do it!").clicked() { - // TODO: make sure this really needs to clone - let log_paths = self.log_paths.clone(); let write_verify_params = self.options.write_verify_params.take(); - let cf = self.options.detected_compression_format.unwrap(); // FIXME: - let ongoing_write = self.ongoing_write.clone(); - let egui_ctx = ui.ctx().clone(); - *ongoing_write.lock().unwrap() = Some(OngoingWrite { + self.ongoing_write = Some(OngoingWrite { write_progress: 0, verify_progress: 0, }); @@ -367,18 +362,18 @@ impl eframe::App for App { ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); - self.add_image_ui(ui); + self.image_ui(ui); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); ui.add_enabled_ui(self.options.picked_image.is_some(), |ui| { - self.add_file_hash_ui(ui) + self.file_hash_ui(ui) }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); ui.add_enabled_ui(self.file_hash_is_verified_or_skipped(), |ui| { - self.add_target_disk_ui(ui) + self.target_disk_ui(ui) }); ui.add_space(SECTION_SPACING * ui.spacing().item_spacing.y); From 90d673c5dbe6e673857eeeab4613989afdd410b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Wed, 6 May 2026 12:46:34 +0200 Subject: [PATCH 15/29] WIP: using orchestrator in main thread. THIS WONT WORK actually, need to create worker thread --- src/gui/app.rs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index b1af1f3c..0f5a38b5 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -6,13 +6,14 @@ use std::{ sync::{Arc, Mutex}, time::Instant, }; +use tracing::{error, info}; use crate::{ compression::CompressionFormat, device::{self, Removable, WriteTarget, enumerate_devices}, hash::{HashAlg, parse_hash_input}, logging::LogPaths, - orchestrator::{Orchestrator, WriteVerifyParams, WriterState}, + orchestrator::{Orchestrator, OrchestratorExt, WriteVerifyParams, WriterState}, runtime::RemoteSpawn, }; @@ -288,7 +289,16 @@ impl App { ); if ui.button("I know, do it!").clicked() { - let write_verify_params = self.options.write_verify_params.take(); + let child_state = match self + .orc + .start_write_verify_blocking(self.runtime, *write_verify_params) + { + Err(e) => { + error!(?e, "failed to start write/verify process"); + return; + } + Ok(p) => p.state, + }; self.ongoing_write = Some(OngoingWrite { write_progress: 0, @@ -296,32 +306,21 @@ impl App { }); loop { - let x = handle.events.next().await; - info!(?x, "got event from burn handle"); - child_state = child_state.on_status(Instant::now(), x); - // FIXME: fugly-ass unwrselfing - match &child_state { + // FIXME: fugly-ass unwrapping + match *child_state.borrow() { WriterState::Writing(b) => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .write_progress = (b.approximate_ratio() * 1000.0) as u64 + self.ongoing_write.unwrap().write_progress = + (b.approximate_ratio() * 1000.0) as u64 } WriterState::Verifying { total_write_bytes, .. } => { - ongoing_write - .lock() - .unwrap() - .as_mut() - .unwrap() - .verify_progress = total_write_bytes * 1000 / input_file_bytes + self.ongoing_write.unwrap().verify_progress = + total_write_bytes * 1000 / input_file_bytes } WriterState::Finished { .. } => break, } - egui_ctx.request_repaint(); + ui.ctx().request_repaint(); } } } From 32a9573586b83ce43259a16fd5baf14e03cf3a67 Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 18:01:12 -0700 Subject: [PATCH 16/29] rename Orchestrator to Facade --- src/{orchestrator => facade}/analyze_input.rs | 0 src/{orchestrator => facade}/disks.rs | 0 src/{orchestrator => facade}/hash.rs | 3 +- .../legacy_facade}/client.rs | 0 .../legacy_facade}/facade.rs | 12 ++++---- .../legacy_facade}/mod.rs | 8 +++--- src/{orchestrator => facade}/mod.rs | 28 +++++++++---------- src/{orchestrator => facade}/real.rs | 18 ++++++------ src/{orchestrator => facade}/watch.rs | 0 src/{orchestrator => facade}/write_verify.rs | 0 src/herder_api/write_verify.rs | 2 +- src/main.rs | 8 +++--- src/ui/fancy_ui/display.rs | 2 +- src/ui/fancy_ui/mod.rs | 2 +- src/ui/fancy_ui/state.rs | 2 +- src/ui/fancy_ui/widgets.rs | 2 +- src/ui/mod.rs | 6 ++-- src/ui/simple_ui/ask_outfile.rs | 2 +- src/ui/simple_ui/mod.rs | 20 ++++++------- 19 files changed, 57 insertions(+), 58 deletions(-) rename src/{orchestrator => facade}/analyze_input.rs (100%) rename src/{orchestrator => facade}/disks.rs (100%) rename src/{orchestrator => facade}/hash.rs (93%) rename src/{orchestrator/herder_facade => facade/legacy_facade}/client.rs (100%) rename src/{orchestrator/herder_facade => facade/legacy_facade}/facade.rs (94%) rename src/{orchestrator/herder_facade => facade/legacy_facade}/mod.rs (90%) rename src/{orchestrator => facade}/mod.rs (85%) rename src/{orchestrator => facade}/real.rs (88%) rename src/{orchestrator => facade}/watch.rs (100%) rename src/{orchestrator => facade}/write_verify.rs (100%) diff --git a/src/orchestrator/analyze_input.rs b/src/facade/analyze_input.rs similarity index 100% rename from src/orchestrator/analyze_input.rs rename to src/facade/analyze_input.rs diff --git a/src/orchestrator/disks.rs b/src/facade/disks.rs similarity index 100% rename from src/orchestrator/disks.rs rename to src/facade/disks.rs diff --git a/src/orchestrator/hash.rs b/src/facade/hash.rs similarity index 93% rename from src/orchestrator/hash.rs rename to src/facade/hash.rs index 1d4dabf0..569b3920 100644 --- a/src/orchestrator/hash.rs +++ b/src/facade/hash.rs @@ -5,8 +5,7 @@ use std::{path::PathBuf, time::Instant}; use bytes::Bytes; use crate::{ - byteseries::ByteSeries, compression::CompressionFormat, hash::HashAlg, - orchestrator::watch::Watch, + byteseries::ByteSeries, compression::CompressionFormat, facade::watch::Watch, hash::HashAlg, }; /// Parameters for starting a new hashing operation. diff --git a/src/orchestrator/herder_facade/client.rs b/src/facade/legacy_facade/client.rs similarity index 100% rename from src/orchestrator/herder_facade/client.rs rename to src/facade/legacy_facade/client.rs diff --git a/src/orchestrator/herder_facade/facade.rs b/src/facade/legacy_facade/facade.rs similarity index 94% rename from src/orchestrator/herder_facade/facade.rs rename to src/facade/legacy_facade/facade.rs index 7bf0b723..3e2b4411 100644 --- a/src/orchestrator/herder_facade/facade.rs +++ b/src/facade/legacy_facade/facade.rs @@ -7,7 +7,7 @@ use tracing::{debug, trace}; use tracing_unwrap::ResultExt; use super::{ - DaemonError, HerdHandle, HerderFacade, StartWriterError, + DaemonError, HerdHandle, LegacyFacade, StartWriterError, client::{HerderClient, HerderClientFactory, LazyHerderClient, RawHerderClient}, }; use crate::{ @@ -16,11 +16,11 @@ use crate::{ ipc_common::read_msg_async, }; -/// Make the actual prod-used [HerderFacade]. +/// Make the actual prod-used [LegacyFacade]. /// /// Doing it this way with a function is so that we can hide all of those ugly -/// ugly ugly type signatures under a nice `impl HerderFacade + 'static`! -pub fn make_herder_facade_impl(log_path: &str) -> impl HerderFacade + 'static { +/// ugly ugly type signatures under a nice `impl LegacyFacade + 'static`! +pub fn make_legacy_facade_impl(log_path: &str) -> impl LegacyFacade + 'static { let event_demux = Arc::new(std::sync::Mutex::new(EventDemuxMap::new())); /// Simple implementor of [HerderClientFactory]. @@ -61,7 +61,7 @@ pub fn make_herder_facade_impl(log_path: &str) -> impl HerderFacade + 'static { } } -/// Implementation of the actual [HerderFacade] used by Caligula. +/// Implementation of the actual [LegacyFacade] used by Caligula. struct HerderFacadeImpl { event_demux: Arc>>, next_writer_id: u64, @@ -70,7 +70,7 @@ struct HerderFacadeImpl { escalated_daemon: Esc, } -impl HerderFacade for HerderFacadeImpl +impl LegacyFacade for HerderFacadeImpl where Std: HerderClient, Esc: HerderClient, diff --git a/src/orchestrator/herder_facade/mod.rs b/src/facade/legacy_facade/mod.rs similarity index 90% rename from src/orchestrator/herder_facade/mod.rs rename to src/facade/legacy_facade/mod.rs index c55c7b6a..7b2e8b32 100644 --- a/src/orchestrator/herder_facade/mod.rs +++ b/src/facade/legacy_facade/mod.rs @@ -1,13 +1,13 @@ //! WARNING: HERE THERE BE DRAGONS //! -//! The good parts of this submodule will get assimilated into orchestrator once +//! The good parts of this submodule will get assimilated into CaligulaFacade once //! I get back to working on the stdiomux branch. Don't rely on this module -//! whatsoever! Orchestrator is mildly stable though. +//! whatsoever! CaligulaFacade is mildly stable though. mod client; mod facade; -pub use facade::make_herder_facade_impl; +pub use facade::make_legacy_facade_impl; use futures::stream::BoxStream; use crate::herder_api::{HerdAction, HerdEvent, TopLevelHerdEvent}; @@ -20,7 +20,7 @@ use crate::herder_api::{HerdAction, HerdEvent, TopLevelHerdEvent}; /// /// Making it a trait is so that we can easily test the UI as a separate /// component from the backend. -pub trait HerderFacade { +pub trait LegacyFacade { async fn start_herd( &mut self, action: A, diff --git a/src/orchestrator/mod.rs b/src/facade/mod.rs similarity index 85% rename from src/orchestrator/mod.rs rename to src/facade/mod.rs index fbad8ea1..933f9795 100644 --- a/src/orchestrator/mod.rs +++ b/src/facade/mod.rs @@ -1,4 +1,4 @@ -//! Exposes the [`Orchestrator`], which is a facade that orchestrates all +//! Exposes the [`CaligulaFacade`], which is a facade that orchestrates all //! "high-level" work and tracks the state of worker tasks. use std::{path::PathBuf, sync::Arc}; @@ -6,18 +6,18 @@ use std::{path::PathBuf, sync::Arc}; pub use self::{ disks::DiskList, hash::{HashStarted, StartHashParams}, - herder_facade::{DaemonError, StartWriterError}, + legacy_facade::{DaemonError, StartWriterError}, write_verify::{WriteVerifyParams, WriteVerifyStarted, WriterVerifyState}, }; use crate::{ - escalation::EscalationMethod, herder_api::write_verify::*, - orchestrator::analyze_input::InputAnalysis, runtime::RemoteSpawn, + escalation::EscalationMethod, facade::analyze_input::InputAnalysis, + herder_api::write_verify::*, runtime::RemoteSpawn, }; mod analyze_input; mod disks; mod hash; -mod herder_facade; +mod legacy_facade; mod real; pub mod watch; mod write_verify; @@ -35,13 +35,13 @@ mod write_verify; /// /// Note that the interface is fully asynchronous. For synchronous UI /// implementations, you should spawn a worker task as a shim between the -/// [`Orchestrator`] and your synchronous UI threads, probably using channels +/// [`CaligulaFacade`] and your synchronous UI threads, probably using channels /// and such. /// /// The API for this can be considered "mostly" stable. I'll be changing out the /// error types, but in general, the overall shape of this API can be used for /// new UI developments. -pub trait Orchestrator: Sync + Send + 'static { +pub trait CaligulaFacade: Sync + Send + 'static { /// Analyze an input file to guess how we should handle it. /// /// Returns the results of the analysis, or an error if the file could not @@ -98,13 +98,13 @@ pub trait Orchestrator: Sync + Send + 'static { fn is_escalated(&self) -> bool; } -/// Make the actual prod-used orchestrator implementation. -pub fn make_orchestrator_impl(log_path: &str) -> impl Orchestrator { - self::real::OrchestratorImpl::new(herder_facade::make_herder_facade_impl(log_path)) +/// Make the actual prod-used CaligulaFacade implementation. +pub fn make_real_facade(log_path: &str) -> impl CaligulaFacade { + self::real::FacadeImpl::new(legacy_facade::make_legacy_facade_impl(log_path)) } -pub trait OrchestratorExt: Orchestrator { - /// Like [`Orchestrator::start_write_verify()`], but it blocks your thread +pub trait FacadeExt: CaligulaFacade { + /// Like [`CaligulaFacade::start_write_verify()`], but it blocks your thread /// while waiting for it to start. /// /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async @@ -121,7 +121,7 @@ pub trait OrchestratorExt: Orchestrator { .expect("remote task dropped!") } - /// Like [`Orchestrator::escalate()`], but it blocks your thread while + /// Like [`CaligulaFacade::escalate()`], but it blocks your thread while /// waiting for it to start. /// /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async @@ -139,4 +139,4 @@ pub trait OrchestratorExt: Orchestrator { } } -impl OrchestratorExt for O {} +impl FacadeExt for O {} diff --git a/src/orchestrator/real.rs b/src/facade/real.rs similarity index 88% rename from src/orchestrator/real.rs rename to src/facade/real.rs index 33b0de32..9c8b18a4 100644 --- a/src/orchestrator/real.rs +++ b/src/facade/real.rs @@ -2,31 +2,31 @@ use std::{path::PathBuf, time::Instant}; use futures::StreamExt; -use super::herder_facade::{DaemonError, HerderFacade, StartWriterError}; +use super::legacy_facade::{DaemonError, LegacyFacade, StartWriterError}; use crate::{ escalation::EscalationMethod, - herder_api::write_verify::WriteVerifyEvent, - orchestrator::{ - DiskList, Orchestrator, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, + facade::{ + CaligulaFacade, DiskList, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, analyze_input::InputAnalysis, hash::{HashStarted, StartHashParams}, watch::Watch, }, + herder_api::write_verify::WriteVerifyEvent, }; -/// Actual orchestrator implementation used by Caligula. -pub struct OrchestratorImpl { +/// Actual CaligulaFacade implementation used by Caligula. +pub struct FacadeImpl { inner: tokio::sync::Mutex>, } struct Inner { // TODO: get rid of the entire herder facade thing altogether. just assimilate the good parts - // into orchestrator. + // into CaligulaFacade. h: H, escalation: Option>, } -impl OrchestratorImpl { +impl FacadeImpl { pub fn new(h: H) -> Self { Self { inner: Inner { @@ -38,7 +38,7 @@ impl OrchestratorImpl { } } -impl Orchestrator for OrchestratorImpl { +impl CaligulaFacade for FacadeImpl { async fn start_write_verify( &self, params: WriteVerifyParams, diff --git a/src/orchestrator/watch.rs b/src/facade/watch.rs similarity index 100% rename from src/orchestrator/watch.rs rename to src/facade/watch.rs diff --git a/src/orchestrator/write_verify.rs b/src/facade/write_verify.rs similarity index 100% rename from src/orchestrator/write_verify.rs rename to src/facade/write_verify.rs diff --git a/src/herder_api/write_verify.rs b/src/herder_api/write_verify.rs index da6d565d..04855724 100644 --- a/src/herder_api/write_verify.rs +++ b/src/herder_api/write_verify.rs @@ -104,7 +104,7 @@ impl Display for WriteVerifyError { f, "Failed to unmount disk (exit code {exit_code})\n{message}" ), - WriteVerifyError::Panicked => write!(f, "Orchestrator panicked!"), + WriteVerifyError::Panicked => write!(f, "CaligulaFacade panicked!"), } } } diff --git a/src/main.rs b/src/main.rs index 4f6c28ed..44bb2e46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,13 @@ use std::sync::Arc; use clap::{CommandFactory as _, Parser}; use tracing::debug; -use crate::orchestrator::make_orchestrator_impl; +use crate::facade::make_real_facade; mod byteseries; mod compression; mod device; mod escalation; +mod facade; mod hash; mod hashfile; mod herder_api; @@ -16,7 +17,6 @@ mod herder_daemon; mod ipc_common; mod logging; mod native; -mod orchestrator; mod runtime; mod tty; mod ui; @@ -62,10 +62,10 @@ fn main() { logging::init_logging_parent(&log_paths); let runtime = crate::runtime::AsyncRuntime::start(); - let orc = Arc::new(make_orchestrator_impl(log_paths.main())); + let facade = Arc::new(make_real_facade(log_paths.main())); debug!("Starting primary process"); - match ui::main(runtime, orc, log_paths.into(), burn_args) { + match ui::main(runtime, facade, log_paths.into(), burn_args) { Ok(_) => (), Err(e) => handle_toplevel_error(e), } diff --git a/src/ui/fancy_ui/display.rs b/src/ui/fancy_ui/display.rs index 19e20887..a5b7f20a 100644 --- a/src/ui/fancy_ui/display.rs +++ b/src/ui/fancy_ui/display.rs @@ -10,7 +10,7 @@ use super::{ state::State, widgets::{SpeedChart, WriterProgressBar, WritingInfoTable}, }; -use crate::{logging::LogPaths, orchestrator::WriterVerifyState}; +use crate::{facade::WriterVerifyState, logging::LogPaths}; struct ComputedLayout { progress: Rect, diff --git a/src/ui/fancy_ui/mod.rs b/src/ui/fancy_ui/mod.rs index 28149ac2..03034556 100644 --- a/src/ui/fancy_ui/mod.rs +++ b/src/ui/fancy_ui/mod.rs @@ -6,8 +6,8 @@ use tokio::sync::mpsc; use self::state::UIEvent; use crate::{ + facade::{WriteVerifyParams, WriterVerifyState, watch::Watch}, logging::LogPaths, - orchestrator::{WriteVerifyParams, WriterVerifyState, watch::Watch}, runtime::RemoteSpawn, ui::fancy_ui::{display::draw, state::State}, }; diff --git a/src/ui/fancy_ui/state.rs b/src/ui/fancy_ui/state.rs index 123f3172..cfa326cf 100644 --- a/src/ui/fancy_ui/state.rs +++ b/src/ui/fancy_ui/state.rs @@ -4,7 +4,7 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use tracing::info; use super::widgets::{QuitModal, QuitModalResult, SpeedChartState}; -use crate::orchestrator::{WriteVerifyParams, WriterVerifyState}; +use crate::facade::{WriteVerifyParams, WriterVerifyState}; #[derive(Debug, PartialEq, Clone)] pub enum UIEvent { diff --git a/src/ui/fancy_ui/widgets.rs b/src/ui/fancy_ui/widgets.rs index c4d2e8c7..5ef64ada 100644 --- a/src/ui/fancy_ui/widgets.rs +++ b/src/ui/fancy_ui/widgets.rs @@ -13,7 +13,7 @@ use ratatui::{ }, }; -use crate::orchestrator::WriterVerifyState; +use crate::facade::WriterVerifyState; pub struct SpeedChart<'a> { pub state: &'a WriterVerifyState, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 816d694c..72b13490 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -9,8 +9,8 @@ use tracing::{debug, info}; pub use self::{cli::BurnArgs, utils::ByteSpeed}; use crate::{ + facade::CaligulaFacade, logging::LogPaths, - orchestrator::Orchestrator, runtime::RemoteSpawn, tty::TermiosRestore, ui::{simple_ui::do_setup_wizard, utils::TUICapture}, @@ -19,7 +19,7 @@ use crate::{ /// Entrypoint for both TUI-based UIs. pub fn main( runtime: impl RemoteSpawn, - orc: Arc, + facade: Arc, log_paths: Arc, args: BurnArgs, ) -> anyhow::Result<()> { @@ -39,7 +39,7 @@ pub fn main( }; let started = simple_ui::try_start_write_or_escalate( - orc.clone(), + facade.clone(), &runtime, &start_write_verify, args.root, diff --git a/src/ui/simple_ui/ask_outfile.rs b/src/ui/simple_ui/ask_outfile.rs index accab390..b22dc4d5 100644 --- a/src/ui/simple_ui/ask_outfile.rs +++ b/src/ui/simple_ui/ask_outfile.rs @@ -6,7 +6,7 @@ use tracing::debug; use crate::{ compression::{AVAILABLE_FORMATS, CompressionArg, CompressionFormat}, device::{self, Removable, WriteTarget, enumerate_devices}, - orchestrator::WriteVerifyParams, + facade::WriteVerifyParams, ui::cli::BurnArgs, }; diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 90b7b6cb..ea5ee047 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -17,12 +17,12 @@ use self::{ use super::cli::BurnArgs; use crate::{ device::WriteTarget, - herder_api::write_verify::{WriteVerifyError, WriteVerifyEvent}, - logging::LogPaths, - orchestrator::{ - Orchestrator, OrchestratorExt, StartWriterError, WriteVerifyParams, WriteVerifyStarted, + facade::{ + CaligulaFacade, FacadeExt, StartWriterError, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, watch::Watch, }, + herder_api::write_verify::{WriteVerifyError, WriteVerifyEvent}, + logging::LogPaths, runtime::RemoteSpawn, ui::cli::UseSudo, }; @@ -65,7 +65,7 @@ pub struct Params<'a> { /// sudo based on what's provided in the `root` argument. #[tracing::instrument(skip_all, fields(root, interactive))] pub fn try_start_write_or_escalate( - orc: Arc, + facade: Arc, runtime: &impl RemoteSpawn, args: &WriteVerifyParams, root: UseSudo, @@ -73,7 +73,7 @@ pub fn try_start_write_or_escalate( ) -> Result> { tracing::info!("Starting burn without escalation"); - let err = match orc + let err = match facade .clone() .start_write_verify_blocking(runtime, args.clone()) { @@ -101,13 +101,13 @@ pub fn try_start_write_or_escalate( .expect("prompting the user should not fail"); if response { - orc.clone().escalate_blocking(runtime, None)?; - return orc.start_write_verify_blocking(runtime, args.clone()); + facade.clone().escalate_blocking(runtime, None)?; + return facade.start_write_verify_blocking(runtime, args.clone()); } } (UseSudo::Always, _) => { - orc.clone().escalate_blocking(runtime, None)?; - return orc.start_write_verify_blocking(runtime, args.clone()); + facade.clone().escalate_blocking(runtime, None)?; + return facade.start_write_verify_blocking(runtime, args.clone()); } _ => {} } From 737253b49de36feff4e284f9411097d72ca340e6 Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 18:11:20 -0700 Subject: [PATCH 17/29] Split Facade into multiple traits --- src/facade/mod.rs | 97 +++++++++++++--------------------- src/facade/real.rs | 23 +++++--- src/ui/simple_ui/facade_ext.rs | 48 +++++++++++++++++ src/ui/simple_ui/mod.rs | 6 ++- 4 files changed, 104 insertions(+), 70 deletions(-) create mode 100644 src/ui/simple_ui/facade_ext.rs diff --git a/src/facade/mod.rs b/src/facade/mod.rs index 933f9795..e98abad4 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -1,7 +1,7 @@ //! Exposes the [`CaligulaFacade`], which is a facade that orchestrates all //! "high-level" work and tracks the state of worker tasks. -use std::{path::PathBuf, sync::Arc}; +use std::path::PathBuf; pub use self::{ disks::DiskList, @@ -10,8 +10,7 @@ pub use self::{ write_verify::{WriteVerifyParams, WriteVerifyStarted, WriterVerifyState}, }; use crate::{ - escalation::EscalationMethod, facade::analyze_input::InputAnalysis, - herder_api::write_verify::*, runtime::RemoteSpawn, + escalation::EscalationMethod, facade::analyze_input::InputAnalysis, herder_api::write_verify::*, }; mod analyze_input; @@ -41,27 +40,17 @@ mod write_verify; /// The API for this can be considered "mostly" stable. I'll be changing out the /// error types, but in general, the overall shape of this API can be used for /// new UI developments. -pub trait CaligulaFacade: Sync + Send + 'static { - /// Analyze an input file to guess how we should handle it. - /// - /// Returns the results of the analysis, or an error if the file could not - /// be read. This method is fault-tolerant, so the only errors that can - /// cause this operation to fail are I/O errors. - /// - /// The intended workflow is that you call it once, to fill up your UI's - /// wizard with data, and then ask the user for more information if - /// there's anything that's not certain. - #[expect(unused, reason = "Stub interface created for later use.")] - async fn analyze_input(&self, input: PathBuf) -> std::io::Result; +pub trait CaligulaFacade: + Sync + Send + DiskWatcher + Analyzer + Escalator + Orchestrator + 'static +{ +} - /// Get a handle for watching the list of disks available. This may update - /// as disks are added and removed to the system. - /// - /// Although this returns a handle immediately, the initial results may take - /// a while to load. - #[expect(unused, reason = "Stub interface created for later use.")] - fn watch_disks(&self) -> watch::Watch; +impl CaligulaFacade for F where + F: Sync + Send + DiskWatcher + Analyzer + Escalator + Orchestrator + 'static +{ +} +pub trait Orchestrator { /// Read a file and calculate its hash in a background thread. /// /// Returns when the file is opened and the thread is running, with a handle @@ -77,7 +66,33 @@ pub trait CaligulaFacade: Sync + Send + 'static { &self, params: WriteVerifyParams, ) -> Result>; +} + +pub trait DiskWatcher { + /// Get a handle for watching the list of disks available. This may update + /// as disks are added and removed to the system. + /// + /// Although this returns a handle immediately, the initial results may take + /// a while to load. + #[expect(unused, reason = "Stub interface created for later use.")] + fn watch_disks(&self) -> watch::Watch; +} +pub trait Analyzer { + /// Analyze an input file to guess how we should handle it. + /// + /// Returns the results of the analysis, or an error if the file could not + /// be read. This method is fault-tolerant, so the only errors that can + /// cause this operation to fail are I/O errors. + /// + /// The intended workflow is that you call it once, to fill up your UI's + /// wizard with data, and then ask the user for more information if + /// there's anything that's not certain. + #[expect(unused, reason = "Stub interface created for later use.")] + async fn analyze_input(&self, input: PathBuf) -> std::io::Result; +} + +pub trait Escalator { /// Attempt to spawn a child process as root using the provided escalation /// method (or [`None`] to automatically guess which one to use). /// @@ -102,41 +117,3 @@ pub trait CaligulaFacade: Sync + Send + 'static { pub fn make_real_facade(log_path: &str) -> impl CaligulaFacade { self::real::FacadeImpl::new(legacy_facade::make_legacy_facade_impl(log_path)) } - -pub trait FacadeExt: CaligulaFacade { - /// Like [`CaligulaFacade::start_write_verify()`], but it blocks your thread - /// while waiting for it to start. - /// - /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async - /// context! Just use the non-blocking version of the trait! It's mostly - /// only useful for the simple UI wizard, which is inherently blocky. - fn start_write_verify_blocking( - self: Arc, - spawn: impl RemoteSpawn, - params: WriteVerifyParams, - ) -> Result> { - spawn - .spawn(move || async move { self.start_write_verify(params).await }) - .blocking_recv() - .expect("remote task dropped!") - } - - /// Like [`CaligulaFacade::escalate()`], but it blocks your thread while - /// waiting for it to start. - /// - /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async - /// context! Just use the non-blocking version of the trait! It's mostly - /// only useful for the simple UI wizard, which is inherently blocky. - fn escalate_blocking( - self: Arc, - spawn: impl RemoteSpawn, - method: Option, - ) -> Result<(), DaemonError> { - spawn - .spawn(move || async move { self.escalate(method).await }) - .blocking_recv() - .expect("remote task dropped!") - } -} - -impl FacadeExt for O {} diff --git a/src/facade/real.rs b/src/facade/real.rs index 9c8b18a4..14fc2309 100644 --- a/src/facade/real.rs +++ b/src/facade/real.rs @@ -6,7 +6,8 @@ use super::legacy_facade::{DaemonError, LegacyFacade, StartWriterError}; use crate::{ escalation::EscalationMethod, facade::{ - CaligulaFacade, DiskList, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, + Analyzer,DiskList, DiskWatcher, Escalator, Orchestrator, WriteVerifyParams, + WriteVerifyStarted, WriterVerifyState, analyze_input::InputAnalysis, hash::{HashStarted, StartHashParams}, watch::Watch, @@ -38,7 +39,13 @@ impl FacadeImpl { } } -impl CaligulaFacade for FacadeImpl { +impl Orchestrator for FacadeImpl { + async fn start_hash(&self, _params: StartHashParams) -> std::io::Result { + unimplemented!( + "Until this is implemented, for testing purposes, you may replace this with test values." + ) + } + async fn start_write_verify( &self, params: WriteVerifyParams, @@ -73,7 +80,9 @@ impl CaligulaFacade for FacadeImpl { state, }) } +} +impl Escalator for FacadeImpl { async fn escalate(&self, method: Option) -> Result<(), DaemonError> { let mut inner = self.inner.lock().await; inner.escalation = Some(method); @@ -89,19 +98,17 @@ impl CaligulaFacade for FacadeImpl { }; lock.escalation.is_some() } +} - async fn start_hash(&self, _params: StartHashParams) -> std::io::Result { - unimplemented!( - "Until this is implemented, for testing purposes, you may replace this with test values." - ) - } - +impl DiskWatcher for FacadeImpl { fn watch_disks(&self) -> Watch { unimplemented!( "Until this is implemented, for testing purposes, you may replace this with test values." ) } +} +impl Analyzer for FacadeImpl { async fn analyze_input(&self, _input: PathBuf) -> std::io::Result { unimplemented!( "Until this is implemented, for testing purposes, you may replace this with test values." diff --git a/src/ui/simple_ui/facade_ext.rs b/src/ui/simple_ui/facade_ext.rs new file mode 100644 index 00000000..22eb3466 --- /dev/null +++ b/src/ui/simple_ui/facade_ext.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use crate::{ + escalation::EscalationMethod, + facade::{ + CaligulaFacade, DaemonError, StartWriterError, WriteVerifyParams, WriteVerifyStarted, + }, + herder_api::write_verify::WriteVerifyEvent, + runtime::RemoteSpawn, +}; + +pub trait FacadeExt: CaligulaFacade { + /// Like [`CaligulaFacade::start_write_verify()`], but it blocks your thread + /// while waiting for it to start. + /// + /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async + /// context! Just use the non-blocking version of the trait! It's mostly + /// only useful for the simple UI wizard, which is inherently blocky. + fn start_write_verify_blocking( + self: Arc, + spawn: impl RemoteSpawn, + params: WriteVerifyParams, + ) -> Result> { + spawn + .spawn(move || async move { self.start_write_verify(params).await }) + .blocking_recv() + .expect("remote task dropped!") + } + + /// Like [`CaligulaFacade::escalate()`], but it blocks your thread while + /// waiting for it to start. + /// + /// THIS SHOULD ABSOLUTELY NOT UNDER ANY CIRCUMSTANCES be used in an async + /// context! Just use the non-blocking version of the trait! It's mostly + /// only useful for the simple UI wizard, which is inherently blocky. + fn escalate_blocking( + self: Arc, + spawn: impl RemoteSpawn, + method: Option, + ) -> Result<(), DaemonError> { + spawn + .spawn(move || async move { self.escalate(method).await }) + .blocking_recv() + .expect("remote task dropped!") + } +} + +impl FacadeExt for O {} diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index ea5ee047..7893621d 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -13,13 +13,14 @@ use tracing::debug; use self::{ ask_hash::ask_hash, ask_outfile::{ask_compression, ask_outfile, confirm_write}, + facade_ext::FacadeExt as _, }; use super::cli::BurnArgs; use crate::{ device::WriteTarget, facade::{ - CaligulaFacade, FacadeExt, StartWriterError, WriteVerifyParams, WriteVerifyStarted, - WriterVerifyState, watch::Watch, + CaligulaFacade, StartWriterError, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, + watch::Watch, }, herder_api::write_verify::{WriteVerifyError, WriteVerifyEvent}, logging::LogPaths, @@ -29,6 +30,7 @@ use crate::{ mod ask_hash; mod ask_outfile; +mod facade_ext; /// How often we refresh the display const REFRESH_PERIOD: Duration = Duration::from_millis(250); From 108a108a636ed3f3afba34a7f401ca47c6fa7250 Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 18:14:24 -0700 Subject: [PATCH 18/29] Move write_verify and hash into submodule of facade --- src/facade/mod.rs | 9 +++++---- src/facade/{ => workflow}/hash.rs | 0 src/facade/workflow/mod.rs | 2 ++ src/facade/{ => workflow}/write_verify.rs | 0 4 files changed, 7 insertions(+), 4 deletions(-) rename src/facade/{ => workflow}/hash.rs (100%) create mode 100644 src/facade/workflow/mod.rs rename src/facade/{ => workflow}/write_verify.rs (100%) diff --git a/src/facade/mod.rs b/src/facade/mod.rs index e98abad4..aa031ee8 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -5,9 +5,11 @@ use std::path::PathBuf; pub use self::{ disks::DiskList, - hash::{HashStarted, StartHashParams}, legacy_facade::{DaemonError, StartWriterError}, - write_verify::{WriteVerifyParams, WriteVerifyStarted, WriterVerifyState}, + workflow::{ + hash::{HashStarted, StartHashParams}, + write_verify::{WriteVerifyParams, WriteVerifyStarted, WriterVerifyState}, + }, }; use crate::{ escalation::EscalationMethod, facade::analyze_input::InputAnalysis, herder_api::write_verify::*, @@ -15,11 +17,10 @@ use crate::{ mod analyze_input; mod disks; -mod hash; mod legacy_facade; mod real; pub mod watch; -mod write_verify; +pub mod workflow; /// Main facade for UI implementations to interact with the rest of the /// program's logic. diff --git a/src/facade/hash.rs b/src/facade/workflow/hash.rs similarity index 100% rename from src/facade/hash.rs rename to src/facade/workflow/hash.rs diff --git a/src/facade/workflow/mod.rs b/src/facade/workflow/mod.rs new file mode 100644 index 00000000..26445088 --- /dev/null +++ b/src/facade/workflow/mod.rs @@ -0,0 +1,2 @@ +pub mod hash; +pub mod write_verify; diff --git a/src/facade/write_verify.rs b/src/facade/workflow/write_verify.rs similarity index 100% rename from src/facade/write_verify.rs rename to src/facade/workflow/write_verify.rs From c3da6d766234778a26f993ff184a1e6565b708ff Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 18:53:19 -0700 Subject: [PATCH 19/29] now split Orchestrator into workflow-specific traits --- src/facade/legacy_facade/mod.rs | 4 +-- src/facade/mod.rs | 43 +++++++++++++-------------- src/facade/real.rs | 42 +++++++++++++-------------- src/facade/workflow/hash.rs | 19 ++++++++++-- src/facade/workflow/mod.rs | 33 +++++++++++++++++++++ src/facade/workflow/write_verify.rs | 45 ++++++++++++++++++++--------- src/herder_api/write_verify.rs | 35 ++++++---------------- src/ui/mod.rs | 6 ++-- src/ui/simple_ui/facade_ext.rs | 9 ++---- src/ui/simple_ui/mod.rs | 20 ++++++------- 10 files changed, 148 insertions(+), 108 deletions(-) diff --git a/src/facade/legacy_facade/mod.rs b/src/facade/legacy_facade/mod.rs index 7b2e8b32..5e1657be 100644 --- a/src/facade/legacy_facade/mod.rs +++ b/src/facade/legacy_facade/mod.rs @@ -1,7 +1,7 @@ //! WARNING: HERE THERE BE DRAGONS //! -//! The good parts of this submodule will get assimilated into CaligulaFacade once -//! I get back to working on the stdiomux branch. Don't rely on this module +//! The good parts of this submodule will get assimilated into CaligulaFacade +//! once I get back to working on the stdiomux branch. Don't rely on this module //! whatsoever! CaligulaFacade is mildly stable though. mod client; diff --git a/src/facade/mod.rs b/src/facade/mod.rs index aa031ee8..67fbe46c 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -7,12 +7,13 @@ pub use self::{ disks::DiskList, legacy_facade::{DaemonError, StartWriterError}, workflow::{ - hash::{HashStarted, StartHashParams}, - write_verify::{WriteVerifyParams, WriteVerifyStarted, WriterVerifyState}, + Orchestrator, + write_verify::{WriteVerifyParams, WriterVerifyState}, }, }; use crate::{ - escalation::EscalationMethod, facade::analyze_input::InputAnalysis, herder_api::write_verify::*, + escalation::EscalationMethod, + facade::{analyze_input::InputAnalysis, workflow::hash::StartHashParams}, }; mod analyze_input; @@ -42,33 +43,29 @@ pub mod workflow; /// error types, but in general, the overall shape of this API can be used for /// new UI developments. pub trait CaligulaFacade: - Sync + Send + DiskWatcher + Analyzer + Escalator + Orchestrator + 'static + Sync + + Send + + DiskWatcher + + Analyzer + + Escalator + + Orchestrator + + Orchestrator + + 'static { } impl CaligulaFacade for F where - F: Sync + Send + DiskWatcher + Analyzer + Escalator + Orchestrator + 'static + F: Sync + + Send + + DiskWatcher + + Analyzer + + Escalator + + Orchestrator + + Orchestrator + + 'static { } -pub trait Orchestrator { - /// Read a file and calculate its hash in a background thread. - /// - /// Returns when the file is opened and the thread is running, with a handle - /// to watch its progress, or an error if the file could not be opened. - #[expect(unused, reason = "Stub interface created for later use.")] - async fn start_hash(&self, params: StartHashParams) -> std::io::Result; - - /// Start a write + verify workflow in the background. - /// - /// Returns when we get an initial success message from the task group, or - /// there was a failure. - async fn start_write_verify( - &self, - params: WriteVerifyParams, - ) -> Result>; -} - pub trait DiskWatcher { /// Get a handle for watching the list of disks available. This may update /// as disks are added and removed to the system. diff --git a/src/facade/real.rs b/src/facade/real.rs index 14fc2309..61c0be96 100644 --- a/src/facade/real.rs +++ b/src/facade/real.rs @@ -2,17 +2,16 @@ use std::{path::PathBuf, time::Instant}; use futures::StreamExt; -use super::legacy_facade::{DaemonError, LegacyFacade, StartWriterError}; +use super::legacy_facade::{DaemonError, LegacyFacade}; use crate::{ escalation::EscalationMethod, facade::{ - Analyzer,DiskList, DiskWatcher, Escalator, Orchestrator, WriteVerifyParams, - WriteVerifyStarted, WriterVerifyState, + Analyzer, DiskList, DiskWatcher, Escalator, Orchestrator, WriteVerifyParams, + WriterVerifyState, analyze_input::InputAnalysis, - hash::{HashStarted, StartHashParams}, watch::Watch, + workflow::hash::{HashingState, StartHashParams}, }, - herder_api::write_verify::WriteVerifyEvent, }; /// Actual CaligulaFacade implementation used by Caligula. @@ -39,23 +38,19 @@ impl FacadeImpl { } } -impl Orchestrator for FacadeImpl { - async fn start_hash(&self, _params: StartHashParams) -> std::io::Result { - unimplemented!( - "Until this is implemented, for testing purposes, you may replace this with test values." - ) - } - - async fn start_write_verify( - &self, - params: WriteVerifyParams, - ) -> Result> { +impl Orchestrator for FacadeImpl { + async fn start_workflow(&self, params: WriteVerifyParams) -> Watch { tracing::info!("Requesting herder to start"); // request the herder to start the action let mut inner = self.inner.lock().await; let esc = inner.escalation.is_some(); - let handle = inner.h.start_herd(params.make_child_config(), esc).await?; + let Ok(handle) = inner.h.start_herd(params.make_child_config(), esc).await else { + todo!() + /*return Watch { + rx: tokio::sync::watch::channel(WriterVerifyState::error(todo!())).1, + };*/ + }; drop(inner); // create state reduction task @@ -73,12 +68,15 @@ impl Orchestrator for FacadeImpl { }); } }); - let state = super::watch::Watch { rx: rx_state }; + super::watch::Watch { rx: rx_state } + } +} - Ok(WriteVerifyStarted { - start: handle.initial_info, - state, - }) +impl Orchestrator for FacadeImpl { + async fn start_workflow(&self, _workflow: StartHashParams) -> Watch { + unimplemented!( + "Until this is implemented, for testing purposes, you may replace this with test values." + ) } } diff --git a/src/facade/workflow/hash.rs b/src/facade/workflow/hash.rs index 569b3920..e3a15440 100644 --- a/src/facade/workflow/hash.rs +++ b/src/facade/workflow/hash.rs @@ -5,7 +5,13 @@ use std::{path::PathBuf, time::Instant}; use bytes::Bytes; use crate::{ - byteseries::ByteSeries, compression::CompressionFormat, facade::watch::Watch, hash::HashAlg, + byteseries::ByteSeries, + compression::CompressionFormat, + facade::{ + watch::Watch, + workflow::{Workflow, WorkflowState}, + }, + hash::HashAlg, }; /// Parameters for starting a new hashing operation. @@ -21,6 +27,10 @@ pub struct StartHashParams { pub compression: CompressionFormat, } +impl Workflow for StartHashParams { + type State = HashingState; +} + /// Result from hash starting. pub struct HashStarted { /// Handle for watching the state updates of this hasher. @@ -58,8 +68,13 @@ impl HashingState { pub fn file_size_bytes(&self) -> u64 { self.file_size_bytes } +} + +impl WorkflowState for HashingState { + type Error = std::io::Error; + type Success = Bytes; - pub fn result(&self) -> Option<&std::io::Result> { + fn result(&self) -> Option<&Result> { self.result.as_ref() } } diff --git a/src/facade/workflow/mod.rs b/src/facade/workflow/mod.rs index 26445088..b96ef52b 100644 --- a/src/facade/workflow/mod.rs +++ b/src/facade/workflow/mod.rs @@ -1,2 +1,35 @@ +use std::error::Error; + +use crate::facade::watch::Watch; + pub mod hash; pub mod write_verify; + +/// An object that can handle the spawning of a specific workflow. +pub trait Orchestrator { + /// Start the workflow in the background. + /// + /// The workflow may have immediately returned an error. It is up to the + /// caller to check and see if that is the case. + async fn start_workflow(&self, workflow: W) -> Watch; +} + +/// Marker trait representing the parameters of a workflow that can be scheduled +/// to run in the background with an [`Orchestrator`]. +pub trait Workflow { + /// The state associated with this [`Workflow`]. + type State: WorkflowState; +} + +/// A point-in-time representation of the state of an initialized [`Workflow`]. +pub trait WorkflowState: Sync + 'static { + /// Result of this workflow on success. + type Success: Sync + 'static; + + /// Result of this workflow on failure. + type Error: Sync + Error + 'static; + + /// Check this workflow for a result. If it returns [`None`], then this + /// workflow is still running. + fn result(&self) -> Option<&Result>; +} diff --git a/src/facade/workflow/write_verify.rs b/src/facade/workflow/write_verify.rs index 9fe517eb..479c9f77 100644 --- a/src/facade/workflow/write_verify.rs +++ b/src/facade/workflow/write_verify.rs @@ -1,13 +1,13 @@ -use std::{fs::File, path::PathBuf, time::Instant}; +use std::{fs::File, path::PathBuf, sync::Arc, time::Instant}; use bytesize::ByteSize; use tracing::{info, trace}; -use super::watch::Watch; use crate::{ byteseries::{ByteSeries, EstimatedTime}, compression::CompressionFormat, device::WriteTarget, + facade::workflow::WorkflowState, herder_api::write_verify::*, }; @@ -20,12 +20,8 @@ pub struct WriteVerifyParams { pub target: WriteTarget, } -/// Result of starting a write + verify workflow. -#[derive(Debug, Clone)] -pub struct WriteVerifyStarted { - #[expect(dead_code)] - pub start: WriteVerifyStart, - pub state: Watch, +impl super::Workflow for WriteVerifyParams { + type State = WriterVerifyState; } impl WriteVerifyParams { @@ -67,19 +63,40 @@ pub enum WriterVerifyState { }, Finished { finish_time: Instant, - result: Result<(), WriteVerifyError>, + result: Result<(), Arc>, write_hist: ByteSeries, verify_hist: Option, total_write_bytes: u64, }, } +impl WorkflowState for WriterVerifyState { + type Error = Arc; + type Success = (); + + fn result(&self) -> Option<&Result> { + match self { + WriterVerifyState::Finished { result, .. } => Some(result), + _ => None, + } + } +} + impl WriterVerifyState { - #[tracing::instrument] pub fn initial(now: Instant, is_input_compressed: bool, input_file_bytes: u64) -> Self { WriterVerifyState::Writing(Writing::new(now, is_input_compressed, input_file_bytes)) } + pub fn error(now: Instant, error: Arc) -> Self { + WriterVerifyState::Finished { + finish_time: now, + result: Err(error), + write_hist: ByteSeries::new(now), + verify_hist: None, + total_write_bytes: 0, + } + } + #[tracing::instrument(skip_all, fields(msg), level = "debug")] pub fn on_status(mut self, now: Instant, msg: Option) -> Self { match msg { @@ -147,7 +164,7 @@ impl WriterVerifyState { let total_write_bytes = st.write_hist.bytes_encountered(); WriterVerifyState::Finished { finish_time: now, - result: error, + result: error.map_err(Arc::new), write_hist: st.write_hist, verify_hist: None, total_write_bytes, @@ -161,7 +178,7 @@ impl WriterVerifyState { let total_write_bytes = write_hist.bytes_encountered(); WriterVerifyState::Finished { finish_time: now, - result: error, + result: error.map_err(Arc::new), write_hist, verify_hist: Some(verify_hist), total_write_bytes, @@ -182,7 +199,7 @@ impl Default for WriterVerifyState { let now = Instant::now(); Self::Finished { finish_time: now, - result: Err(WriteVerifyError::Panicked), + result: Err(Arc::new(WriteVerifyError::Panicked)), write_hist: ByteSeries::new(now), verify_hist: None, total_write_bytes: 0, @@ -333,7 +350,7 @@ mod tests { .. } => { assert_eq!(finish_time - t0, Duration::from_secs(2)); - assert_eq!(error, Err(WriteVerifyError::UnexpectedTermination)); + assert_eq!(error, Err(WriteVerifyError::UnexpectedTermination.into())); } s => panic!("Unexpected {s:#?}"), } diff --git a/src/herder_api/write_verify.rs b/src/herder_api/write_verify.rs index 04855724..e198a3eb 100644 --- a/src/herder_api/write_verify.rs +++ b/src/herder_api/write_verify.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, path::PathBuf}; +use std::path::PathBuf; use serde::{Deserialize, Serialize}; @@ -65,14 +65,21 @@ pub struct WriteVerifyStart { pub input_file_bytes: u64, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)] pub enum WriteVerifyError { + #[error("Unexpected end of output file. Is your output file too small?")] EndOfOutput, + #[error("Permission denied while opening file")] PermissionDenied, + #[error("Disk verification failed!")] VerificationFailed, + #[error("The child process unexpectedly terminated!")] UnexpectedTermination, + #[error("Unknown error occurred in child process: {0}")] UnknownChildProcError(String), + #[error("Failed to unmount disk (exit code {exit_code})\n{message}")] FailedToUnmount { message: String, exit_code: i32 }, + #[error("Orchestrator panicked!")] Panicked, } @@ -84,27 +91,3 @@ impl From for WriteVerifyError { } } } - -impl Display for WriteVerifyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WriteVerifyError::EndOfOutput => write!( - f, - "Unexpected end of output file. Is your output file too small?" - ), - WriteVerifyError::PermissionDenied => write!(f, "Permission denied while opening file"), - WriteVerifyError::VerificationFailed => write!(f, "Disk verification failed!"), - WriteVerifyError::UnexpectedTermination => { - write!(f, "The child process unexpectedly terminated!") - } - WriteVerifyError::UnknownChildProcError(err) => { - write!(f, "Unknown error occurred in child process: {err}") - } - WriteVerifyError::FailedToUnmount { message, exit_code } => write!( - f, - "Failed to unmount disk (exit code {exit_code})\n{message}" - ), - WriteVerifyError::Panicked => write!(f, "CaligulaFacade panicked!"), - } - } -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 72b13490..ebc58363 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -38,7 +38,7 @@ pub fn main( return Ok(()); }; - let started = simple_ui::try_start_write_or_escalate( + let child_state = simple_ui::try_start_write_or_escalate( facade.clone(), &runtime, &start_write_verify, @@ -55,14 +55,14 @@ pub fn main( fancy_ui::Params { terminal, begin: &start_write_verify, - child_state: started.state, + child_state, terminal_events: crossterm::event::EventStream::new(), log_paths: &log_paths, }, ); } else { simple_ui::run(simple_ui::Params { - child_state: started.state, + child_state, log_paths: &log_paths, }); } diff --git a/src/ui/simple_ui/facade_ext.rs b/src/ui/simple_ui/facade_ext.rs index 22eb3466..b14025a2 100644 --- a/src/ui/simple_ui/facade_ext.rs +++ b/src/ui/simple_ui/facade_ext.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use crate::{ escalation::EscalationMethod, - facade::{ - CaligulaFacade, DaemonError, StartWriterError, WriteVerifyParams, WriteVerifyStarted, - }, - herder_api::write_verify::WriteVerifyEvent, + facade::{CaligulaFacade, DaemonError, WriteVerifyParams, WriterVerifyState, watch::Watch}, runtime::RemoteSpawn, }; @@ -20,9 +17,9 @@ pub trait FacadeExt: CaligulaFacade { self: Arc, spawn: impl RemoteSpawn, params: WriteVerifyParams, - ) -> Result> { + ) -> Watch { spawn - .spawn(move || async move { self.start_write_verify(params).await }) + .spawn(move || async move { self.start_workflow(params).await }) .blocking_recv() .expect("remote task dropped!") } diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 7893621d..30c2dad5 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -19,8 +19,8 @@ use super::cli::BurnArgs; use crate::{ device::WriteTarget, facade::{ - CaligulaFacade, StartWriterError, WriteVerifyParams, WriteVerifyStarted, WriterVerifyState, - watch::Watch, + CaligulaFacade, StartWriterError, WriteVerifyParams, WriterVerifyState, watch::Watch, + workflow::WorkflowState, }, herder_api::write_verify::{WriteVerifyError, WriteVerifyEvent}, logging::LogPaths, @@ -72,20 +72,20 @@ pub fn try_start_write_or_escalate( args: &WriteVerifyParams, root: UseSudo, interactive: bool, -) -> Result> { +) -> Result, StartWriterError> { tracing::info!("Starting burn without escalation"); - let err = match facade + let h = facade .clone() - .start_write_verify_blocking(runtime, args.clone()) - { - Ok(p) => { - return Ok(p); + .start_write_verify_blocking(runtime, args.clone()); + let err = match h.borrow().result() { + Some(Err(e)) => e, + _ => { + return Ok(h); } - Err(e) => e, }; - if let StartWriterError::Failed(WriteVerifyError::PermissionDenied) = &err { + if let StartWriterError::Failed(WriteVerifyError::PermissionDenied) = err.as_ref() { tracing::info!("Unescalated burn failed"); match (root, interactive) { (UseSudo::Ask, true) => { From e766ab7473bcc0d46d1022b546fb882a70b5a8ce Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 19:08:09 -0700 Subject: [PATCH 20/29] split into workflow and worker error wehhh --- src/facade/workflow/write_verify.rs | 62 ++++++++++++++++++----- src/herder_api/write_verify.rs | 10 ++-- src/herder_daemon/writer_process/mod.rs | 12 ++--- src/herder_daemon/writer_process/tests.rs | 10 ++-- src/ui/simple_ui/mod.rs | 8 +-- 5 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/facade/workflow/write_verify.rs b/src/facade/workflow/write_verify.rs index 479c9f77..11ff59b0 100644 --- a/src/facade/workflow/write_verify.rs +++ b/src/facade/workflow/write_verify.rs @@ -7,8 +7,8 @@ use crate::{ byteseries::{ByteSeries, EstimatedTime}, compression::CompressionFormat, device::WriteTarget, - facade::workflow::WorkflowState, - herder_api::write_verify::*, + facade::{DaemonError, workflow::WorkflowState}, + herder_api::{write_verify::*}, }; /// Params for starting a write + verify workflow. @@ -63,16 +63,39 @@ pub enum WriterVerifyState { }, Finished { finish_time: Instant, - result: Result<(), Arc>, + result: Result<(), Arc>, write_hist: ByteSeries, verify_hist: Option, total_write_bytes: u64, }, } +#[derive(Debug, Clone, thiserror::Error)] +pub enum WriteVerifyWorkflowError { + #[error("Unexpected first status: {0:?}")] + UnexpectedFirstStatus(WriteVerifyEvent), + #[error("Daemon management error: {0}")] + Daemon(#[from] Arc), + #[error("Worker error: {0}")] + Worker(#[from] WriteVerifyWorkerError), + #[error("Orchestrator panicked!")] + Panicked, +} + +impl PartialEq for WriteVerifyWorkflowError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::UnexpectedFirstStatus(l0), Self::UnexpectedFirstStatus(r0)) => l0 == r0, + (Self::Daemon(_), Self::Daemon(_)) => true, + (Self::Worker(l0), Self::Worker(r0)) => l0 == r0, + _ => false, + } + } +} + impl WorkflowState for WriterVerifyState { - type Error = Arc; type Success = (); + type Error = Arc; fn result(&self) -> Option<&Result> { match self { @@ -87,10 +110,10 @@ impl WriterVerifyState { WriterVerifyState::Writing(Writing::new(now, is_input_compressed, input_file_bytes)) } - pub fn error(now: Instant, error: Arc) -> Self { + pub fn error(now: Instant, error: WriteVerifyWorkflowError) -> Self { WriterVerifyState::Finished { finish_time: now, - result: Err(error), + result: Err(error.into()), write_hist: ByteSeries::new(now), verify_hist: None, total_write_bytes: 0, @@ -114,7 +137,7 @@ impl WriterVerifyState { } Some(WriteVerifyEvent::Error(reason)) => { info!("Received error notification"); - self.into_finished(now, Err(reason)) + self.into_finished(now, Err(reason.into())) } Some(WriteVerifyEvent::Success) => { info!("Received success notification"); @@ -122,7 +145,10 @@ impl WriterVerifyState { } None => { info!("Messages terminated unexpectedly"); - self.into_finished(now, Err(WriteVerifyError::UnexpectedTermination)) + self.into_finished( + now, + Err(WriteVerifyWorkerError::UnexpectedTermination.into()), + ) } other => panic!( "Received unexpected child status {:#?}\nCurrent state: {:#?}", @@ -158,7 +184,11 @@ impl WriterVerifyState { }; } - fn into_finished(self, now: Instant, error: Result<(), WriteVerifyError>) -> WriterVerifyState { + fn into_finished( + self, + now: Instant, + error: Result<(), WriteVerifyWorkflowError>, + ) -> WriterVerifyState { match self { WriterVerifyState::Writing(st) => { let total_write_bytes = st.write_hist.bytes_encountered(); @@ -199,7 +229,7 @@ impl Default for WriterVerifyState { let now = Instant::now(); Self::Finished { finish_time: now, - result: Err(Arc::new(WriteVerifyError::Panicked)), + result: Err(Arc::new(WriteVerifyWorkflowError::Panicked)), write_hist: ByteSeries::new(now), verify_hist: None, total_write_bytes: 0, @@ -270,7 +300,10 @@ impl Writing { #[cfg(test)] mod tests { - use std::time::{Duration, Instant}; + use std::{ + sync::Arc, + time::{Duration, Instant}, + }; use super::WriterVerifyState; use crate::{byteseries::ByteSeries, herder_api::write_verify::*}; @@ -350,7 +383,12 @@ mod tests { .. } => { assert_eq!(finish_time - t0, Duration::from_secs(2)); - assert_eq!(error, Err(WriteVerifyError::UnexpectedTermination.into())); + assert_eq!( + error, + Err(Arc::new( + WriteVerifyWorkerError::UnexpectedTermination.into() + )) + ); } s => panic!("Unexpected {s:#?}"), } diff --git a/src/herder_api/write_verify.rs b/src/herder_api/write_verify.rs index e198a3eb..5f60ea17 100644 --- a/src/herder_api/write_verify.rs +++ b/src/herder_api/write_verify.rs @@ -36,13 +36,13 @@ pub enum WriteVerifyEvent { duration_millis: u64, }, Success, - Error(WriteVerifyError), + Error(WriteVerifyWorkerError), } super::impl_try_from_top_level_herd_event!(Writer => WriteVerifyEvent); impl super::HerdEvent for WriteVerifyEvent { - type Failure = WriteVerifyError; + type Failure = WriteVerifyWorkerError; type StartInfo = WriteVerifyStart; fn downcast_as_initial_info(self) -> Result { @@ -66,7 +66,7 @@ pub struct WriteVerifyStart { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)] -pub enum WriteVerifyError { +pub enum WriteVerifyWorkerError { #[error("Unexpected end of output file. Is your output file too small?")] EndOfOutput, #[error("Permission denied while opening file")] @@ -79,11 +79,9 @@ pub enum WriteVerifyError { UnknownChildProcError(String), #[error("Failed to unmount disk (exit code {exit_code})\n{message}")] FailedToUnmount { message: String, exit_code: i32 }, - #[error("Orchestrator panicked!")] - Panicked, } -impl From for WriteVerifyError { +impl From for WriteVerifyWorkerError { fn from(value: std::io::Error) -> Self { match value.kind() { std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, diff --git a/src/herder_daemon/writer_process/mod.rs b/src/herder_daemon/writer_process/mod.rs index 9e7f8308..3935c8cf 100644 --- a/src/herder_daemon/writer_process/mod.rs +++ b/src/herder_daemon/writer_process/mod.rs @@ -57,7 +57,7 @@ pub fn spawn_writer( fn run( mut tx: impl FnMut(WriteVerifyEvent), args: &WriteVerifyAction, -) -> Result<(), WriteVerifyError> { +) -> Result<(), WriteVerifyWorkerError> { if cfg!(target_os = "macos") && args.target_type == device::Type::Disk { let mut command = Command::new("diskutil"); command @@ -81,7 +81,7 @@ fn run( let exit_code = exit.into_raw(); if !exit.success() { - return Err(WriteVerifyError::FailedToUnmount { + return Err(WriteVerifyWorkerError::FailedToUnmount { message: format!("stderr: {stderr}\nstdout: {stdout}"), exit_code, }); @@ -195,7 +195,7 @@ struct WriteOp { impl WriteOp { /// Execute the write operation. Returns total number of bytes written. #[inline(always)] - fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result { + fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result { let mut file = FileSourceReader::new(self.cf, self.file_read_buf_size, &mut self.file); let mut disk = CountWrite::new(&mut self.disk); let mut buf = avec_rt![[self.disk_block_size] | 0u8; self.buf_size]; @@ -225,7 +225,7 @@ impl WriteOp { let written_bytes = disk.write(&buf[..])?; if written_bytes == 0 { checkpoint!(); - return Err(WriteVerifyError::EndOfOutput); + return Err(WriteVerifyWorkerError::EndOfOutput); } } checkpoint!(); @@ -277,7 +277,7 @@ struct VerifyOp { impl VerifyOp { #[inline(always)] - fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result<(), WriteVerifyError> { + fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result<(), WriteVerifyWorkerError> { let mut file = FileSourceReader::new(self.cf, self.file_read_buf_size, &mut self.file); let mut disk = CountRead::new(&mut self.disk); @@ -305,7 +305,7 @@ impl VerifyOp { if file_buf[..file_read_bytes] != disk_buf[..file_read_bytes] { trace!(file_read_bytes, "verification failed"); - return Err(WriteVerifyError::VerificationFailed); + return Err(WriteVerifyWorkerError::VerificationFailed); } } checkpoint!(); diff --git a/src/herder_daemon/writer_process/tests.rs b/src/herder_daemon/writer_process/tests.rs index fb882ed5..902cec40 100644 --- a/src/herder_daemon/writer_process/tests.rs +++ b/src/herder_daemon/writer_process/tests.rs @@ -87,7 +87,7 @@ fn write_file_larger_than_disk(#[values(1032, 2000, 6000, 7000)] file_size: usiz }; let result = test.execute(false); - assert_matches!(result.execute_result, Err(WriteVerifyError::EndOfOutput)); + assert_matches!(result.execute_result, Err(WriteVerifyWorkerError::EndOfOutput)); assert_eq!(&result.disk, &result.file[..test.disk_size]); } @@ -195,7 +195,7 @@ fn verify_sad_case_works() { }; let result = test.execute(); - assert_eq!(result.return_val, Err(WriteVerifyError::VerificationFailed)); + assert_eq!(result.return_val, Err(WriteVerifyWorkerError::VerificationFailed)); } #[rstest] @@ -242,7 +242,7 @@ fn verify_misaligned_case_sad_path_works(#[case] file_size: usize, #[case] flip_ }; let result = test.execute(); - assert_eq!(result.return_val, Err(WriteVerifyError::VerificationFailed)); + assert_eq!(result.return_val, Err(WriteVerifyWorkerError::VerificationFailed)); } /// Helpers for these tests. These go in their own little module to enforce @@ -350,7 +350,7 @@ mod helpers { pub file: Vec, pub disk: Vec, pub events: Vec, - pub execute_result: Result, + pub execute_result: Result, } impl WriteTest { @@ -407,7 +407,7 @@ mod helpers { pub _requested_file_reads: Vec, pub _requested_disk_reads: Vec, pub _events: Vec, - pub return_val: Result<(), WriteVerifyError>, + pub return_val: Result<(), WriteVerifyWorkerError>, } impl VerifyTest { diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 30c2dad5..332b6b18 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -20,9 +20,9 @@ use crate::{ device::WriteTarget, facade::{ CaligulaFacade, StartWriterError, WriteVerifyParams, WriterVerifyState, watch::Watch, - workflow::WorkflowState, + workflow::{WorkflowState, write_verify::WriteVerifyWorkflowError}, }, - herder_api::write_verify::{WriteVerifyError, WriteVerifyEvent}, + herder_api::write_verify::{WriteVerifyWorkerError}, logging::LogPaths, runtime::RemoteSpawn, ui::cli::UseSudo, @@ -72,7 +72,7 @@ pub fn try_start_write_or_escalate( args: &WriteVerifyParams, root: UseSudo, interactive: bool, -) -> Result, StartWriterError> { +) -> Result, WriteVerifyWorkflowError> { tracing::info!("Starting burn without escalation"); let h = facade @@ -85,7 +85,7 @@ pub fn try_start_write_or_escalate( } }; - if let StartWriterError::Failed(WriteVerifyError::PermissionDenied) = err.as_ref() { + if let StartWriterError::Failed(WriteVerifyWorkerError::PermissionDenied) = err.as_ref() { tracing::info!("Unescalated burn failed"); match (root, interactive) { (UseSudo::Ask, true) => { From 531c6363684de744c15b28167e9ffa66985647b4 Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 19:33:07 -0700 Subject: [PATCH 21/29] fix all the errors --- src/facade/mod.rs | 4 ++-- src/facade/real.rs | 32 ++++++++++++++++++++----- src/facade/workflow/mod.rs | 29 ++++++++++++++++++++++ src/facade/workflow/write_verify.rs | 7 +++--- src/ui/simple_ui/facade_ext.rs | 18 ++++++++++---- src/ui/simple_ui/mod.rs | 37 ++++++++++++++++++----------- 6 files changed, 96 insertions(+), 31 deletions(-) diff --git a/src/facade/mod.rs b/src/facade/mod.rs index 67fbe46c..73ef7f48 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -7,8 +7,8 @@ pub use self::{ disks::DiskList, legacy_facade::{DaemonError, StartWriterError}, workflow::{ - Orchestrator, - write_verify::{WriteVerifyParams, WriterVerifyState}, + Orchestrator, OrchestratorExt, + write_verify::{WriteVerifyParams, WriteVerifyWorkflowError, WriterVerifyState}, }, }; use crate::{ diff --git a/src/facade/real.rs b/src/facade/real.rs index 61c0be96..ea98f1f6 100644 --- a/src/facade/real.rs +++ b/src/facade/real.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, time::Instant}; +use std::{path::PathBuf, sync::Arc, time::Instant}; use futures::StreamExt; @@ -45,11 +45,31 @@ impl Orchestrator for Facad // request the herder to start the action let mut inner = self.inner.lock().await; let esc = inner.escalation.is_some(); - let Ok(handle) = inner.h.start_herd(params.make_child_config(), esc).await else { - todo!() - /*return Watch { - rx: tokio::sync::watch::channel(WriterVerifyState::error(todo!())).1, - };*/ + let handle: crate::facade::legacy_facade::HerdHandle< + crate::herder_api::write_verify::WriteVerifyEvent, + > = match inner.h.start_herd(params.make_child_config(), esc).await { + Ok(handle) => handle, + Err(e) => { + // oh god what a shitshow + // TODO: refactor the shit out of this thing + return Watch { + rx: tokio::sync::watch::channel(WriterVerifyState::error( + Instant::now(), + match e { + crate::facade::StartWriterError::UnexpectedFirstStatus(s) => { + crate::facade::WriteVerifyWorkflowError::Unexpected(s) + } + crate::facade::StartWriterError::Failed(f) => { + crate::facade::WriteVerifyWorkflowError::Worker(f) + } + crate::facade::StartWriterError::DaemonError(daemon_error) => { + crate::facade::WriteVerifyWorkflowError::Daemon(Arc::new(daemon_error)) + } + }, + )) + .1, + }; + } }; drop(inner); diff --git a/src/facade/workflow/mod.rs b/src/facade/workflow/mod.rs index b96ef52b..dc762815 100644 --- a/src/facade/workflow/mod.rs +++ b/src/facade/workflow/mod.rs @@ -33,3 +33,32 @@ pub trait WorkflowState: Sync + 'static { /// workflow is still running. fn result(&self) -> Option<&Result>; } + +pub trait OrchestratorExt: Orchestrator { + /// Helper method for starting a workflow and checking if it immediately errored + /// before continuing. + async fn start_workflow_checked( + &self, + workflow: W, + ) -> Result, (Watch, ::Error)> + where + ::Error: Clone, + { + let h = self.start_workflow(workflow).await; + let err = match h.borrow().result() { + Some(Err(e)) => Some(e.clone()), + _ => None, + }; + if let Some(err) = err { + return Err((h, err)); + } + Ok(h) + } +} + +impl OrchestratorExt for O +where + W: Workflow, + O: Orchestrator, +{ +} diff --git a/src/facade/workflow/write_verify.rs b/src/facade/workflow/write_verify.rs index 11ff59b0..3ffd3ee5 100644 --- a/src/facade/workflow/write_verify.rs +++ b/src/facade/workflow/write_verify.rs @@ -8,7 +8,7 @@ use crate::{ compression::CompressionFormat, device::WriteTarget, facade::{DaemonError, workflow::WorkflowState}, - herder_api::{write_verify::*}, + herder_api::write_verify::*, }; /// Params for starting a write + verify workflow. @@ -72,8 +72,8 @@ pub enum WriterVerifyState { #[derive(Debug, Clone, thiserror::Error)] pub enum WriteVerifyWorkflowError { - #[error("Unexpected first status: {0:?}")] - UnexpectedFirstStatus(WriteVerifyEvent), + #[error("Unexpected first event: {0:?}")] + Unexpected(WriteVerifyEvent), #[error("Daemon management error: {0}")] Daemon(#[from] Arc), #[error("Worker error: {0}")] @@ -85,7 +85,6 @@ pub enum WriteVerifyWorkflowError { impl PartialEq for WriteVerifyWorkflowError { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::UnexpectedFirstStatus(l0), Self::UnexpectedFirstStatus(r0)) => l0 == r0, (Self::Daemon(_), Self::Daemon(_)) => true, (Self::Worker(l0), Self::Worker(r0)) => l0 == r0, _ => false, diff --git a/src/ui/simple_ui/facade_ext.rs b/src/ui/simple_ui/facade_ext.rs index b14025a2..6dc5856e 100644 --- a/src/ui/simple_ui/facade_ext.rs +++ b/src/ui/simple_ui/facade_ext.rs @@ -2,11 +2,14 @@ use std::sync::Arc; use crate::{ escalation::EscalationMethod, - facade::{CaligulaFacade, DaemonError, WriteVerifyParams, WriterVerifyState, watch::Watch}, + facade::{ + CaligulaFacade, DaemonError, OrchestratorExt, WriteVerifyParams, + WriteVerifyWorkflowError, WriterVerifyState, watch::Watch, + }, runtime::RemoteSpawn, }; -pub trait FacadeExt: CaligulaFacade { +pub trait FacadeExt: CaligulaFacade + OrchestratorExt { /// Like [`CaligulaFacade::start_write_verify()`], but it blocks your thread /// while waiting for it to start. /// @@ -17,9 +20,14 @@ pub trait FacadeExt: CaligulaFacade { self: Arc, spawn: impl RemoteSpawn, params: WriteVerifyParams, - ) -> Watch { + ) -> Result, Arc> { spawn - .spawn(move || async move { self.start_workflow(params).await }) + .spawn(move || async move { + self.as_ref() + .start_workflow_checked(params) + .await + .map_err(|(_, e)| e) + }) .blocking_recv() .expect("remote task dropped!") } @@ -42,4 +50,4 @@ pub trait FacadeExt: CaligulaFacade { } } -impl FacadeExt for O {} +impl> FacadeExt for O {} diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 332b6b18..62055fb2 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -19,10 +19,10 @@ use super::cli::BurnArgs; use crate::{ device::WriteTarget, facade::{ - CaligulaFacade, StartWriterError, WriteVerifyParams, WriterVerifyState, watch::Watch, - workflow::{WorkflowState, write_verify::WriteVerifyWorkflowError}, + CaligulaFacade, DaemonError, WriteVerifyParams, WriterVerifyState, watch::Watch, + workflow::write_verify::WriteVerifyWorkflowError, }, - herder_api::write_verify::{WriteVerifyWorkerError}, + herder_api::write_verify::WriteVerifyWorkerError, logging::LogPaths, runtime::RemoteSpawn, ui::cli::UseSudo, @@ -61,6 +61,14 @@ pub struct Params<'a> { pub log_paths: &'a LogPaths, } +#[derive(Debug, thiserror::Error)] +pub enum WriteOrEscalateError { + #[error("Error spawning writer: {0}")] + Write(#[from] Arc), + #[error("Error escalating: {0}")] + Escalate(#[from] DaemonError), +} + /// Attempt to start burning the disk with the given params. /// /// If received permission denied, figures out if it needs to ask the user to @@ -72,20 +80,21 @@ pub fn try_start_write_or_escalate( args: &WriteVerifyParams, root: UseSudo, interactive: bool, -) -> Result, WriteVerifyWorkflowError> { +) -> Result, WriteOrEscalateError> { tracing::info!("Starting burn without escalation"); - let h = facade + let err = match facade .clone() - .start_write_verify_blocking(runtime, args.clone()); - let err = match h.borrow().result() { - Some(Err(e)) => e, - _ => { - return Ok(h); + .start_write_verify_blocking(runtime, args.clone()) + { + Ok(p) => { + return Ok(p); } + Err(e) => e, }; - if let StartWriterError::Failed(WriteVerifyWorkerError::PermissionDenied) = err.as_ref() { + if let WriteVerifyWorkflowError::Worker(WriteVerifyWorkerError::PermissionDenied) = err.as_ref() + { tracing::info!("Unescalated burn failed"); match (root, interactive) { (UseSudo::Ask, true) => { @@ -104,18 +113,18 @@ pub fn try_start_write_or_escalate( if response { facade.clone().escalate_blocking(runtime, None)?; - return facade.start_write_verify_blocking(runtime, args.clone()); + return Ok(facade.start_write_verify_blocking(runtime, args.clone())?); } } (UseSudo::Always, _) => { facade.clone().escalate_blocking(runtime, None)?; - return facade.start_write_verify_blocking(runtime, args.clone()); + return Ok(facade.start_write_verify_blocking(runtime, args.clone())?); } _ => {} } } - Err(err) + Err(err)? } /// Run the simple TUI. From 60012b09f08902800449f9f1f512bfc9bb9f9ba8 Mon Sep 17 00:00:00 2001 From: Astrid Yu Date: Wed, 6 May 2026 19:51:00 -0700 Subject: [PATCH 22/29] Rename and document stuff --- src/facade/analyze_input.rs | 2 +- src/facade/mod.rs | 41 +++++------ src/facade/real.rs | 27 +++---- src/facade/workflow/hash.rs | 17 +---- src/facade/workflow/mod.rs | 22 +++++- src/facade/workflow/write_verify.rs | 85 +++++++++++------------ src/herder_api/mod.rs | 6 ++ src/herder_api/write_verify.rs | 8 +-- src/herder_daemon/writer_process/mod.rs | 18 +++-- src/herder_daemon/writer_process/tests.rs | 19 +++-- src/runtime.rs | 2 + src/ui/fancy_ui/display.rs | 8 +-- src/ui/fancy_ui/mod.rs | 8 +-- src/ui/fancy_ui/state.rs | 15 ++-- src/ui/fancy_ui/widgets.rs | 20 +++--- src/ui/simple_ui/ask_outfile.rs | 6 +- src/ui/simple_ui/facade_ext.rs | 12 ++-- src/ui/simple_ui/mod.rs | 22 +++--- 18 files changed, 176 insertions(+), 162 deletions(-) diff --git a/src/facade/analyze_input.rs b/src/facade/analyze_input.rs index 475123ec..79024c5c 100644 --- a/src/facade/analyze_input.rs +++ b/src/facade/analyze_input.rs @@ -7,7 +7,7 @@ use bytes::Bytes; use crate::{compression::CompressionFormat, hash::HashAlg, util::candidate::Candidates}; /// Result of analyzing an input file for its properties. -pub struct InputAnalysis { +pub struct FileAnalysis { pub compression: Candidates, pub hash_file: Candidates, } diff --git a/src/facade/mod.rs b/src/facade/mod.rs index 73ef7f48..e7321998 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -8,12 +8,12 @@ pub use self::{ legacy_facade::{DaemonError, StartWriterError}, workflow::{ Orchestrator, OrchestratorExt, - write_verify::{WriteVerifyParams, WriteVerifyWorkflowError, WriterVerifyState}, + write_verify::{WVState, WriteVerifyWorkflow, WriteVerifyWorkflowError}, }, }; use crate::{ escalation::EscalationMethod, - facade::{analyze_input::InputAnalysis, workflow::hash::StartHashParams}, + facade::{analyze_input::FileAnalysis, workflow::hash::HashWorkflow}, }; mod analyze_input; @@ -26,18 +26,8 @@ pub mod workflow; /// Main facade for UI implementations to interact with the rest of the /// program's logic. /// -/// This can be thought of as the glue between the UI and the backend, handling -/// the following: -/// -/// - spawning child processes and escalating them -/// - orchestrating multi-step workflows, such as write + verify -/// - reduction of event streams from the child processes into full states, -/// along with [`Watch`] handles for you to query state updates -/// -/// Note that the interface is fully asynchronous. For synchronous UI -/// implementations, you should spawn a worker task as a shim between the -/// [`CaligulaFacade`] and your synchronous UI threads, probably using channels -/// and such. +/// This trait is split up into several subtraits, each representing a different +/// kind of action the UI can take. /// /// The API for this can be considered "mostly" stable. I'll be changing out the /// error types, but in general, the overall shape of this API can be used for @@ -46,10 +36,10 @@ pub trait CaligulaFacade: Sync + Send + DiskWatcher - + Analyzer + + FileAnalyzer + Escalator - + Orchestrator - + Orchestrator + + Orchestrator + + Orchestrator + 'static { } @@ -58,14 +48,15 @@ impl CaligulaFacade for F where F: Sync + Send + DiskWatcher - + Analyzer + + FileAnalyzer + Escalator - + Orchestrator - + Orchestrator + + Orchestrator + + Orchestrator + 'static { } +/// Represents an interface to the disk querying subsystem. pub trait DiskWatcher { /// Get a handle for watching the list of disks available. This may update /// as disks are added and removed to the system. @@ -76,8 +67,11 @@ pub trait DiskWatcher { fn watch_disks(&self) -> watch::Watch; } -pub trait Analyzer { - /// Analyze an input file to guess how we should handle it. +/// Represents an interface to the file analysis subsystem. +pub trait FileAnalyzer { + /// Analyze a file to guess how we should handle it. + /// + /// This is a fairly fast operation, expected to take 1-3 seconds to run. /// /// Returns the results of the analysis, or an error if the file could not /// be read. This method is fault-tolerant, so the only errors that can @@ -87,9 +81,10 @@ pub trait Analyzer { /// wizard with data, and then ask the user for more information if /// there's anything that's not certain. #[expect(unused, reason = "Stub interface created for later use.")] - async fn analyze_input(&self, input: PathBuf) -> std::io::Result; + async fn analyze_file(&self, input: PathBuf) -> std::io::Result; } +/// Represents an interface to the privilege escalation subsystem. pub trait Escalator { /// Attempt to spawn a child process as root using the provided escalation /// method (or [`None`] to automatically guess which one to use). diff --git a/src/facade/real.rs b/src/facade/real.rs index ea98f1f6..f20cc7f9 100644 --- a/src/facade/real.rs +++ b/src/facade/real.rs @@ -6,11 +6,10 @@ use super::legacy_facade::{DaemonError, LegacyFacade}; use crate::{ escalation::EscalationMethod, facade::{ - Analyzer, DiskList, DiskWatcher, Escalator, Orchestrator, WriteVerifyParams, - WriterVerifyState, - analyze_input::InputAnalysis, + DiskList, DiskWatcher, Escalator, FileAnalyzer, Orchestrator, WVState, WriteVerifyWorkflow, + analyze_input::FileAnalysis, watch::Watch, - workflow::hash::{HashingState, StartHashParams}, + workflow::hash::{HashWorkflow, HashingState}, }, }; @@ -38,8 +37,8 @@ impl FacadeImpl { } } -impl Orchestrator for FacadeImpl { - async fn start_workflow(&self, params: WriteVerifyParams) -> Watch { +impl Orchestrator for FacadeImpl { + async fn start_workflow(&self, params: WriteVerifyWorkflow) -> Watch { tracing::info!("Requesting herder to start"); // request the herder to start the action @@ -53,7 +52,7 @@ impl Orchestrator for Facad // oh god what a shitshow // TODO: refactor the shit out of this thing return Watch { - rx: tokio::sync::watch::channel(WriterVerifyState::error( + rx: tokio::sync::watch::channel(WVState::error( Instant::now(), match e { crate::facade::StartWriterError::UnexpectedFirstStatus(s) => { @@ -63,7 +62,9 @@ impl Orchestrator for Facad crate::facade::WriteVerifyWorkflowError::Worker(f) } crate::facade::StartWriterError::DaemonError(daemon_error) => { - crate::facade::WriteVerifyWorkflowError::Daemon(Arc::new(daemon_error)) + crate::facade::WriteVerifyWorkflowError::Daemon(Arc::new( + daemon_error, + )) } }, )) @@ -74,7 +75,7 @@ impl Orchestrator for Facad drop(inner); // create state reduction task - let (tx_state, rx_state) = tokio::sync::watch::channel(WriterVerifyState::initial( + let (tx_state, rx_state) = tokio::sync::watch::channel(WVState::initial( Instant::now(), !params.compression.is_identity(), handle.initial_info.input_file_bytes, @@ -92,8 +93,8 @@ impl Orchestrator for Facad } } -impl Orchestrator for FacadeImpl { - async fn start_workflow(&self, _workflow: StartHashParams) -> Watch { +impl Orchestrator for FacadeImpl { + async fn start_workflow(&self, _workflow: HashWorkflow) -> Watch { unimplemented!( "Until this is implemented, for testing purposes, you may replace this with test values." ) @@ -126,8 +127,8 @@ impl DiskWatcher for FacadeImpl { } } -impl Analyzer for FacadeImpl { - async fn analyze_input(&self, _input: PathBuf) -> std::io::Result { +impl FileAnalyzer for FacadeImpl { + async fn analyze_file(&self, _input: PathBuf) -> std::io::Result { unimplemented!( "Until this is implemented, for testing purposes, you may replace this with test values." ) diff --git a/src/facade/workflow/hash.rs b/src/facade/workflow/hash.rs index e3a15440..8be33068 100644 --- a/src/facade/workflow/hash.rs +++ b/src/facade/workflow/hash.rs @@ -16,7 +16,7 @@ use crate::{ /// Parameters for starting a new hashing operation. #[derive(Debug, PartialEq, Eq, Clone)] -pub struct StartHashParams { +pub struct HashWorkflow { /// File to use pub file: PathBuf, @@ -27,16 +27,10 @@ pub struct StartHashParams { pub compression: CompressionFormat, } -impl Workflow for StartHashParams { +impl Workflow for HashWorkflow { type State = HashingState; } -/// Result from hash starting. -pub struct HashStarted { - /// Handle for watching the state updates of this hasher. - pub state: Watch, -} - /// Active, point-in-time state of a hashing operation. pub struct HashingState { /// Read speed history. @@ -48,7 +42,7 @@ pub struct HashingState { } impl HashingState { - pub fn new(now: Instant, file_size_bytes: u64) -> Self { + fn new(now: Instant, file_size_bytes: u64) -> Self { Self { read_bytes_history: ByteSeries::new(now), file_size_bytes, @@ -56,11 +50,6 @@ impl HashingState { } } - /// Whether or not this operation is finished. - pub fn is_finished(&self) -> bool { - self.result.is_some() - } - pub fn read_bytes_history(&self) -> &ByteSeries { &self.read_bytes_history } diff --git a/src/facade/workflow/mod.rs b/src/facade/workflow/mod.rs index dc762815..e1c520d9 100644 --- a/src/facade/workflow/mod.rs +++ b/src/facade/workflow/mod.rs @@ -5,7 +5,23 @@ use crate::facade::watch::Watch; pub mod hash; pub mod write_verify; -/// An object that can handle the spawning of a specific workflow. +/// An abstract interface to the workflow spawning subsystem for a specific +/// workflow. +/// +/// [`Workflow`]s represent a long-running, possibly multi-step series of tasks +/// that users would want to schedule, like write + verify, hash calculation, +/// and so on. This trait handles everything related to the workflow and exposes +/// it ti the UI: +/// +/// - spawning child processes and escalating them +/// - orchestration of multiple steps +/// - reduction of event streams from the child processes into full states +/// +/// Note that although some implementations spawn workers effectively +/// immediately, the actual interface is asynchronous. For synchronous UI +/// implementations, you should spawn a worker task as a shim between the +/// [`Orchestrator`] and your synchronous UI threads, probably using channels +/// and such to communicate. pub trait Orchestrator { /// Start the workflow in the background. /// @@ -35,8 +51,8 @@ pub trait WorkflowState: Sync + 'static { } pub trait OrchestratorExt: Orchestrator { - /// Helper method for starting a workflow and checking if it immediately errored - /// before continuing. + /// Helper method for starting a workflow and checking if it immediately + /// errored before continuing. async fn start_workflow_checked( &self, workflow: W, diff --git a/src/facade/workflow/write_verify.rs b/src/facade/workflow/write_verify.rs index 3ffd3ee5..02950f84 100644 --- a/src/facade/workflow/write_verify.rs +++ b/src/facade/workflow/write_verify.rs @@ -13,18 +13,18 @@ use crate::{ /// Params for starting a write + verify workflow. #[derive(Debug, PartialEq, Eq, Clone)] -pub struct WriteVerifyParams { +pub struct WriteVerifyWorkflow { pub input_file: PathBuf, pub input_file_size: ByteSize, pub compression: CompressionFormat, pub target: WriteTarget, } -impl super::Workflow for WriteVerifyParams { - type State = WriterVerifyState; +impl super::Workflow for WriteVerifyWorkflow { + type State = WVState; } -impl WriteVerifyParams { +impl WriteVerifyWorkflow { pub fn new( input_file: PathBuf, compression: CompressionFormat, @@ -51,10 +51,9 @@ impl WriteVerifyParams { } } -/// A state machine for tracking the state of the writer and verifier, based on -/// received messages. +/// A state machine for tracking the state of the write + verify workflow. #[derive(Debug, Clone, PartialEq)] -pub enum WriterVerifyState { +pub enum WVState { Writing(Writing), Verifying { write_hist: ByteSeries, @@ -77,7 +76,7 @@ pub enum WriteVerifyWorkflowError { #[error("Daemon management error: {0}")] Daemon(#[from] Arc), #[error("Worker error: {0}")] - Worker(#[from] WriteVerifyWorkerError), + Worker(#[from] LegacyWriteVerifyError), #[error("Orchestrator panicked!")] Panicked, } @@ -92,25 +91,25 @@ impl PartialEq for WriteVerifyWorkflowError { } } -impl WorkflowState for WriterVerifyState { - type Success = (); +impl WorkflowState for WVState { type Error = Arc; + type Success = (); fn result(&self) -> Option<&Result> { match self { - WriterVerifyState::Finished { result, .. } => Some(result), + WVState::Finished { result, .. } => Some(result), _ => None, } } } -impl WriterVerifyState { +impl WVState { pub fn initial(now: Instant, is_input_compressed: bool, input_file_bytes: u64) -> Self { - WriterVerifyState::Writing(Writing::new(now, is_input_compressed, input_file_bytes)) + WVState::Writing(Writing::new(now, is_input_compressed, input_file_bytes)) } pub fn error(now: Instant, error: WriteVerifyWorkflowError) -> Self { - WriterVerifyState::Finished { + WVState::Finished { finish_time: now, result: Err(error.into()), write_hist: ByteSeries::new(now), @@ -130,7 +129,7 @@ impl WriterVerifyState { Some(WriteVerifyEvent::FinishedWriting { verifying }) => { info!("Received finished writing notification"); match self { - WriterVerifyState::Writing(st) => st.into_finished(now, verifying), + WVState::Writing(st) => st.into_finished(now, verifying), c => c, } } @@ -146,7 +145,7 @@ impl WriterVerifyState { info!("Messages terminated unexpectedly"); self.into_finished( now, - Err(WriteVerifyWorkerError::UnexpectedTermination.into()), + Err(LegacyWriteVerifyError::UnexpectedTermination.into()), ) } other => panic!( @@ -174,24 +173,20 @@ impl WriterVerifyState { fn on_total_bytes(&mut self, now: Instant, src: u64, dest: u64) { match self { - WriterVerifyState::Writing(st) => { + WVState::Writing(st) => { st.read_hist.push(now, src); st.write_hist.push(now, dest); } - WriterVerifyState::Verifying { verify_hist, .. } => verify_hist.push(now, dest), - WriterVerifyState::Finished { .. } => {} + WVState::Verifying { verify_hist, .. } => verify_hist.push(now, dest), + WVState::Finished { .. } => {} }; } - fn into_finished( - self, - now: Instant, - error: Result<(), WriteVerifyWorkflowError>, - ) -> WriterVerifyState { + fn into_finished(self, now: Instant, error: Result<(), WriteVerifyWorkflowError>) -> WVState { match self { - WriterVerifyState::Writing(st) => { + WVState::Writing(st) => { let total_write_bytes = st.write_hist.bytes_encountered(); - WriterVerifyState::Finished { + WVState::Finished { finish_time: now, result: error.map_err(Arc::new), write_hist: st.write_hist, @@ -199,13 +194,13 @@ impl WriterVerifyState { total_write_bytes, } } - WriterVerifyState::Verifying { + WVState::Verifying { write_hist, verify_hist, .. } => { let total_write_bytes = write_hist.bytes_encountered(); - WriterVerifyState::Finished { + WVState::Finished { finish_time: now, result: error.map_err(Arc::new), write_hist, @@ -218,11 +213,11 @@ impl WriterVerifyState { } pub fn is_finished(&self) -> bool { - matches!(self, WriterVerifyState::Finished { .. }) + matches!(self, WVState::Finished { .. }) } } -impl Default for WriterVerifyState { +impl Default for WVState { /// Suitable value to put into the cell when [`std::mem::take()`] is called. fn default() -> Self { let now = Instant::now(); @@ -273,20 +268,20 @@ impl Writing { } } - fn into_finished(self, time: Instant, verifying: bool) -> WriterVerifyState { + fn into_finished(self, time: Instant, verifying: bool) -> WVState { let total_write_bytes = self.write_hist.bytes_encountered(); if verifying { info!(verifying, "Transition to verifying"); - WriterVerifyState::Verifying { + WVState::Verifying { write_hist: self.write_hist, verify_hist: ByteSeries::new(time), total_write_bytes, } } else { info!(verifying, "Transition to finished"); - WriterVerifyState::Finished { + WVState::Finished { finish_time: time, result: Ok(()), write_hist: self.write_hist, @@ -304,13 +299,13 @@ mod tests { time::{Duration, Instant}, }; - use super::WriterVerifyState; + use super::WVState; use crate::{byteseries::ByteSeries, herder_api::write_verify::*}; #[test] fn accept_total_bytes_messages() { let t0 = Instant::now(); - let s = WriterVerifyState::initial(t0, false, 80) + let s = WVState::initial(t0, false, 80) .on_status( t0 + Duration::from_secs(1), Some(WriteVerifyEvent::TotalBytes { src: 20, dest: 10 }), @@ -325,7 +320,7 @@ mod tests { ); let s = match s { - WriterVerifyState::Writing(s) => s, + WVState::Writing(s) => s, s => panic!("unexpected {:#?}", s), }; assert_eq!(s.read_hist.last_datapoint(), (3.0, 60)); @@ -335,13 +330,13 @@ mod tests { #[test] fn writing_value_for_uncompressed_ratio() { let t0 = Instant::now(); - let s = WriterVerifyState::initial(t0, false, 400).on_status( + let s = WVState::initial(t0, false, 400).on_status( t0 + Duration::from_secs(1), Some(WriteVerifyEvent::TotalBytes { src: 15, dest: 40 }), ); let s = match s { - WriterVerifyState::Writing(s) => s, + WVState::Writing(s) => s, s => panic!("unexpected {:#?}", s), }; assert_eq!(s.approximate_ratio(), 0.1); @@ -350,7 +345,7 @@ mod tests { #[test] fn writing_value_for_compressed_ratio() { let t0 = Instant::now(); - let s = WriterVerifyState::initial(t0, true, 80).on_status( + let s = WVState::initial(t0, true, 80).on_status( t0 + Duration::from_secs(1), Some(WriteVerifyEvent::TotalBytes { src: 20, @@ -359,7 +354,7 @@ mod tests { ); let s = match s { - WriterVerifyState::Writing(s) => s, + WVState::Writing(s) => s, s => panic!("unexpected {s:#?}"), }; assert_eq!(s.approximate_ratio(), 0.25); @@ -368,7 +363,7 @@ mod tests { #[test] fn sudden_terminate_in_writing_state_sets_error() { let t0 = Instant::now(); - let s = WriterVerifyState::initial(t0, true, 80) + let s = WVState::initial(t0, true, 80) .on_status( t0 + Duration::from_secs(1), Some(WriteVerifyEvent::TotalBytes { src: 20, dest: 20 }), @@ -376,7 +371,7 @@ mod tests { .on_status(t0 + Duration::from_secs(2), None); match s { - WriterVerifyState::Finished { + WVState::Finished { finish_time, result: error, .. @@ -385,7 +380,7 @@ mod tests { assert_eq!( error, Err(Arc::new( - WriteVerifyWorkerError::UnexpectedTermination.into() + LegacyWriteVerifyError::UnexpectedTermination.into() )) ); } @@ -397,7 +392,7 @@ mod tests { fn terminate_during_finished_is_idempotent() { let t0 = Instant::now(); let finish_time = t0 + Duration::from_secs(10); - let s0 = WriterVerifyState::Finished { + let s0 = WVState::Finished { finish_time, result: Ok(()), write_hist: ByteSeries::new(t0), @@ -415,7 +410,7 @@ mod tests { fn finished_during_finished_is_idempotent() { let t0 = Instant::now(); let finish_time = t0 + Duration::from_secs(10); - let s0 = WriterVerifyState::Finished { + let s0 = WVState::Finished { finish_time, result: Ok(()), write_hist: ByteSeries::new(t0), diff --git a/src/herder_api/mod.rs b/src/herder_api/mod.rs index 24179953..fbe179aa 100644 --- a/src/herder_api/mod.rs +++ b/src/herder_api/mod.rs @@ -1,3 +1,9 @@ +//! Defines traits and modules for IPC between the child process and parent +//! process. +//! +//! The UI may speak some of these types, but it should prefer to use the +//! higher-level interfaces defined in [`crate::facade`]. + pub mod write_verify; use std::fmt::{Debug, Display}; diff --git a/src/herder_api/write_verify.rs b/src/herder_api/write_verify.rs index 5f60ea17..3942afd9 100644 --- a/src/herder_api/write_verify.rs +++ b/src/herder_api/write_verify.rs @@ -36,13 +36,13 @@ pub enum WriteVerifyEvent { duration_millis: u64, }, Success, - Error(WriteVerifyWorkerError), + Error(LegacyWriteVerifyError), } super::impl_try_from_top_level_herd_event!(Writer => WriteVerifyEvent); impl super::HerdEvent for WriteVerifyEvent { - type Failure = WriteVerifyWorkerError; + type Failure = LegacyWriteVerifyError; type StartInfo = WriteVerifyStart; fn downcast_as_initial_info(self) -> Result { @@ -66,7 +66,7 @@ pub struct WriteVerifyStart { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)] -pub enum WriteVerifyWorkerError { +pub enum LegacyWriteVerifyError { #[error("Unexpected end of output file. Is your output file too small?")] EndOfOutput, #[error("Permission denied while opening file")] @@ -81,7 +81,7 @@ pub enum WriteVerifyWorkerError { FailedToUnmount { message: String, exit_code: i32 }, } -impl From for WriteVerifyWorkerError { +impl From for LegacyWriteVerifyError { fn from(value: std::io::Error) -> Self { match value.kind() { std::io::ErrorKind::PermissionDenied => Self::PermissionDenied, diff --git a/src/herder_daemon/writer_process/mod.rs b/src/herder_daemon/writer_process/mod.rs index 3935c8cf..d2b686cd 100644 --- a/src/herder_daemon/writer_process/mod.rs +++ b/src/herder_daemon/writer_process/mod.rs @@ -57,7 +57,7 @@ pub fn spawn_writer( fn run( mut tx: impl FnMut(WriteVerifyEvent), args: &WriteVerifyAction, -) -> Result<(), WriteVerifyWorkerError> { +) -> Result<(), LegacyWriteVerifyError> { if cfg!(target_os = "macos") && args.target_type == device::Type::Disk { let mut command = Command::new("diskutil"); command @@ -81,7 +81,7 @@ fn run( let exit_code = exit.into_raw(); if !exit.success() { - return Err(WriteVerifyWorkerError::FailedToUnmount { + return Err(LegacyWriteVerifyError::FailedToUnmount { message: format!("stderr: {stderr}\nstdout: {stdout}"), exit_code, }); @@ -195,7 +195,10 @@ struct WriteOp { impl WriteOp { /// Execute the write operation. Returns total number of bytes written. #[inline(always)] - fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result { + fn execute( + &mut self, + mut tx: impl FnMut(WriteVerifyEvent), + ) -> Result { let mut file = FileSourceReader::new(self.cf, self.file_read_buf_size, &mut self.file); let mut disk = CountWrite::new(&mut self.disk); let mut buf = avec_rt![[self.disk_block_size] | 0u8; self.buf_size]; @@ -225,7 +228,7 @@ impl WriteOp { let written_bytes = disk.write(&buf[..])?; if written_bytes == 0 { checkpoint!(); - return Err(WriteVerifyWorkerError::EndOfOutput); + return Err(LegacyWriteVerifyError::EndOfOutput); } } checkpoint!(); @@ -277,7 +280,10 @@ struct VerifyOp { impl VerifyOp { #[inline(always)] - fn execute(&mut self, mut tx: impl FnMut(WriteVerifyEvent)) -> Result<(), WriteVerifyWorkerError> { + fn execute( + &mut self, + mut tx: impl FnMut(WriteVerifyEvent), + ) -> Result<(), LegacyWriteVerifyError> { let mut file = FileSourceReader::new(self.cf, self.file_read_buf_size, &mut self.file); let mut disk = CountRead::new(&mut self.disk); @@ -305,7 +311,7 @@ impl VerifyOp { if file_buf[..file_read_bytes] != disk_buf[..file_read_bytes] { trace!(file_read_bytes, "verification failed"); - return Err(WriteVerifyWorkerError::VerificationFailed); + return Err(LegacyWriteVerifyError::VerificationFailed); } } checkpoint!(); diff --git a/src/herder_daemon/writer_process/tests.rs b/src/herder_daemon/writer_process/tests.rs index 902cec40..02f459ac 100644 --- a/src/herder_daemon/writer_process/tests.rs +++ b/src/herder_daemon/writer_process/tests.rs @@ -87,7 +87,10 @@ fn write_file_larger_than_disk(#[values(1032, 2000, 6000, 7000)] file_size: usiz }; let result = test.execute(false); - assert_matches!(result.execute_result, Err(WriteVerifyWorkerError::EndOfOutput)); + assert_matches!( + result.execute_result, + Err(LegacyWriteVerifyError::EndOfOutput) + ); assert_eq!(&result.disk, &result.file[..test.disk_size]); } @@ -195,7 +198,10 @@ fn verify_sad_case_works() { }; let result = test.execute(); - assert_eq!(result.return_val, Err(WriteVerifyWorkerError::VerificationFailed)); + assert_eq!( + result.return_val, + Err(LegacyWriteVerifyError::VerificationFailed) + ); } #[rstest] @@ -242,7 +248,10 @@ fn verify_misaligned_case_sad_path_works(#[case] file_size: usize, #[case] flip_ }; let result = test.execute(); - assert_eq!(result.return_val, Err(WriteVerifyWorkerError::VerificationFailed)); + assert_eq!( + result.return_val, + Err(LegacyWriteVerifyError::VerificationFailed) + ); } /// Helpers for these tests. These go in their own little module to enforce @@ -350,7 +359,7 @@ mod helpers { pub file: Vec, pub disk: Vec, pub events: Vec, - pub execute_result: Result, + pub execute_result: Result, } impl WriteTest { @@ -407,7 +416,7 @@ mod helpers { pub _requested_file_reads: Vec, pub _requested_disk_reads: Vec, pub _events: Vec, - pub return_val: Result<(), WriteVerifyWorkerError>, + pub return_val: Result<(), LegacyWriteVerifyError>, } impl VerifyTest { diff --git a/src/runtime.rs b/src/runtime.rs index 6db3c0ab..a74f41c2 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -6,6 +6,8 @@ use tokio::{ pub type FutureFactory = Box LocalBoxFuture<'static, ()> + Send>; +/// Abstract interface for a single-threaded async runtime running on a +/// different thread. pub trait RemoteSpawn { /// Schedule a future to be spawned on a remote async thread, and return a /// handle for awaiting its completion. diff --git a/src/ui/fancy_ui/display.rs b/src/ui/fancy_ui/display.rs index a5b7f20a..ab6c3682 100644 --- a/src/ui/fancy_ui/display.rs +++ b/src/ui/fancy_ui/display.rs @@ -10,7 +10,7 @@ use super::{ state::State, widgets::{SpeedChart, WriterProgressBar, WritingInfoTable}, }; -use crate::{facade::WriterVerifyState, logging::LogPaths}; +use crate::{facade::WVState, logging::LogPaths}; struct ComputedLayout { progress: Rect, @@ -68,7 +68,7 @@ fn centered_rect(r: Rect, w: u16, h: u16) -> Rect { /// Draw the TUI. pub fn draw( state: &mut State, - child: &WriterVerifyState, + child: &WVState, terminal: &mut Terminal, log_paths: &LogPaths, ) -> std::io::Result<()> { @@ -77,12 +77,12 @@ pub fn draw( let progress_bar = WriterProgressBar::from_writer(child); let final_time = match child { - WriterVerifyState::Finished { finish_time, .. } => *finish_time, + WVState::Finished { finish_time, .. } => *finish_time, _ => Instant::now(), }; let error = match &child { - WriterVerifyState::Finished { result: error, .. } => error.as_ref().err(), + WVState::Finished { result: error, .. } => error.as_ref().err(), _ => None, }; diff --git a/src/ui/fancy_ui/mod.rs b/src/ui/fancy_ui/mod.rs index 03034556..d866a104 100644 --- a/src/ui/fancy_ui/mod.rs +++ b/src/ui/fancy_ui/mod.rs @@ -6,7 +6,7 @@ use tokio::sync::mpsc; use self::state::UIEvent; use crate::{ - facade::{WriteVerifyParams, WriterVerifyState, watch::Watch}, + facade::{WVState, WriteVerifyWorkflow, watch::Watch}, logging::LogPaths, runtime::RemoteSpawn, ui::fancy_ui::{display::draw, state::State}, @@ -25,8 +25,8 @@ where T: Stream> + Send + 'static, { pub terminal: &'a mut Terminal, - pub begin: &'a WriteVerifyParams, - pub child_state: Watch, + pub begin: &'a WriteVerifyWorkflow, + pub child_state: Watch, pub terminal_events: T, pub log_paths: &'a LogPaths, } @@ -88,7 +88,7 @@ fn draw_loop( terminal: &mut Terminal, mut state: State, events: impl IntoIterator, - child: Watch, + child: Watch, log_paths: &LogPaths, ) { for ev in events { diff --git a/src/ui/fancy_ui/state.rs b/src/ui/fancy_ui/state.rs index cfa326cf..38815889 100644 --- a/src/ui/fancy_ui/state.rs +++ b/src/ui/fancy_ui/state.rs @@ -4,7 +4,7 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use tracing::info; use super::widgets::{QuitModal, QuitModalResult, SpeedChartState}; -use crate::facade::{WriteVerifyParams, WriterVerifyState}; +use crate::facade::{WVState, WriteVerifyWorkflow}; #[derive(Debug, PartialEq, Clone)] pub enum UIEvent { @@ -21,7 +21,7 @@ pub struct State { } impl State { - pub fn initial(params: &WriteVerifyParams) -> Self { + pub fn initial(params: &WriteVerifyWorkflow) -> Self { State { input_filename: params.input_file.to_string_lossy().to_string(), target_filename: params.target.devnode.to_string_lossy().to_string(), @@ -34,7 +34,7 @@ impl State { /// /// Returns [`Self`], or [`None`] to signal completion. #[tracing::instrument(skip_all, level = "debug", fields(ev))] - pub fn on_event(self, child: &WriterVerifyState, ev: UIEvent) -> Option { + pub fn on_event(self, child: &WVState, ev: UIEvent) -> Option { match ev { UIEvent::SleepTimeout => Some(self), UIEvent::RecvTermEvent(e) => self.on_term_event(child, e), @@ -44,7 +44,7 @@ impl State { #[tracing::instrument(skip_all, level = "debug", fields(ev))] fn on_term_event( self, - child: &WriterVerifyState, + child: &WVState, ev: Result, ) -> Option { match ev { @@ -62,12 +62,7 @@ impl State { } } - fn handle_key_down( - mut self, - child: &WriterVerifyState, - kc: KeyCode, - km: KeyModifiers, - ) -> Option { + fn handle_key_down(mut self, child: &WVState, kc: KeyCode, km: KeyModifiers) -> Option { if let Some(qm) = &self.quit_modal { return match qm.handle_key_down(kc) { Some(QuitModalResult::Quit) => None, diff --git a/src/ui/fancy_ui/widgets.rs b/src/ui/fancy_ui/widgets.rs index 5ef64ada..0b1bf20b 100644 --- a/src/ui/fancy_ui/widgets.rs +++ b/src/ui/fancy_ui/widgets.rs @@ -13,10 +13,10 @@ use ratatui::{ }, }; -use crate::facade::WriterVerifyState; +use crate::facade::WVState; pub struct SpeedChart<'a> { - pub state: &'a WriterVerifyState, + pub state: &'a WVState, pub final_time: Instant, } @@ -133,9 +133,9 @@ pub struct WriterProgressBar { } impl WriterProgressBar { - pub fn from_writer(state: &WriterVerifyState) -> WriterProgressBar { + pub fn from_writer(state: &WVState) -> WriterProgressBar { match state { - WriterVerifyState::Writing(st) => WriterProgressBar { + WVState::Writing(st) => WriterProgressBar { bytes_written: st.write_hist.bytes_encountered(), label_state: "Burning...", style: Style::default().fg(Color::Yellow), @@ -143,7 +143,7 @@ impl WriterProgressBar { display_total_bytes: st.total_raw_bytes, }, - WriterVerifyState::Verifying { + WVState::Verifying { verify_hist, total_write_bytes, .. @@ -154,7 +154,7 @@ impl WriterProgressBar { Style::default().fg(Color::Blue).bg(Color::Yellow), ), - WriterVerifyState::Finished { + WVState::Finished { write_hist, result, total_write_bytes, @@ -224,7 +224,7 @@ impl WriterProgressBar { pub struct WritingInfoTable<'a> { pub input_filename: &'a str, pub target_filename: &'a str, - pub state: &'a WriterVerifyState, + pub state: &'a WVState, } impl WritingInfoTable<'_> { @@ -241,13 +241,13 @@ impl WritingInfoTable<'_> { ]; match &self.state { - WriterVerifyState::Writing(st) => { + WVState::Writing(st) => { rows.push(Row::new([ Cell::from("ETA Write"), Cell::from(format!("{}", st.eta_write())), ])); } - WriterVerifyState::Verifying { + WVState::Verifying { verify_hist: vdata, total_write_bytes, .. @@ -261,7 +261,7 @@ impl WritingInfoTable<'_> { Cell::from(format!("{}", vdata.estimated_time_left(*total_write_bytes))), ])); } - WriterVerifyState::Finished { + WVState::Finished { verify_hist: vdata, .. } => { if let Some(vdata) = vdata { diff --git a/src/ui/simple_ui/ask_outfile.rs b/src/ui/simple_ui/ask_outfile.rs index b22dc4d5..ce552011 100644 --- a/src/ui/simple_ui/ask_outfile.rs +++ b/src/ui/simple_ui/ask_outfile.rs @@ -6,7 +6,7 @@ use tracing::debug; use crate::{ compression::{AVAILABLE_FORMATS, CompressionArg, CompressionFormat}, device::{self, Removable, WriteTarget, enumerate_devices}, - facade::WriteVerifyParams, + facade::WriteVerifyWorkflow, ui::cli::BurnArgs, }; @@ -80,7 +80,7 @@ pub fn ask_outfile(args: &BurnArgs) -> anyhow::Result { #[tracing::instrument(skip_all)] pub fn confirm_write( args: &BurnArgs, - begin_params: &WriteVerifyParams, + begin_params: &WriteVerifyWorkflow, ) -> Result { if args.force { debug!("Skipping confirm because of --force"); @@ -95,7 +95,7 @@ pub fn confirm_write( } } -fn print_begin_params(params: &WriteVerifyParams) { +fn print_begin_params(params: &WriteVerifyWorkflow) { println!("Input: {}", params.input_file.to_string_lossy()); if params.compression.is_identity() { println!(" Size: {}", params.input_file_size); diff --git a/src/ui/simple_ui/facade_ext.rs b/src/ui/simple_ui/facade_ext.rs index 6dc5856e..2731888f 100644 --- a/src/ui/simple_ui/facade_ext.rs +++ b/src/ui/simple_ui/facade_ext.rs @@ -3,13 +3,13 @@ use std::sync::Arc; use crate::{ escalation::EscalationMethod, facade::{ - CaligulaFacade, DaemonError, OrchestratorExt, WriteVerifyParams, - WriteVerifyWorkflowError, WriterVerifyState, watch::Watch, + CaligulaFacade, DaemonError, OrchestratorExt, WVState, WriteVerifyWorkflow, + WriteVerifyWorkflowError, watch::Watch, }, runtime::RemoteSpawn, }; -pub trait FacadeExt: CaligulaFacade + OrchestratorExt { +pub trait FacadeExt: CaligulaFacade + OrchestratorExt { /// Like [`CaligulaFacade::start_write_verify()`], but it blocks your thread /// while waiting for it to start. /// @@ -19,8 +19,8 @@ pub trait FacadeExt: CaligulaFacade + OrchestratorExt { fn start_write_verify_blocking( self: Arc, spawn: impl RemoteSpawn, - params: WriteVerifyParams, - ) -> Result, Arc> { + params: WriteVerifyWorkflow, + ) -> Result, Arc> { spawn .spawn(move || async move { self.as_ref() @@ -50,4 +50,4 @@ pub trait FacadeExt: CaligulaFacade + OrchestratorExt { } } -impl> FacadeExt for O {} +impl> FacadeExt for O {} diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 62055fb2..0b0e1371 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -19,10 +19,10 @@ use super::cli::BurnArgs; use crate::{ device::WriteTarget, facade::{ - CaligulaFacade, DaemonError, WriteVerifyParams, WriterVerifyState, watch::Watch, + CaligulaFacade, DaemonError, WVState, WriteVerifyWorkflow, watch::Watch, workflow::write_verify::WriteVerifyWorkflowError, }, - herder_api::write_verify::WriteVerifyWorkerError, + herder_api::write_verify::LegacyWriteVerifyError, logging::LogPaths, runtime::RemoteSpawn, ui::cli::UseSudo, @@ -41,14 +41,14 @@ const REFRESH_PERIOD: Duration = Duration::from_millis(250); /// Returns the [BeginParams] if the user confirms, and None if the user /// doesn't. #[tracing::instrument(skip_all)] -pub fn do_setup_wizard(args: &BurnArgs) -> Result, anyhow::Error> { +pub fn do_setup_wizard(args: &BurnArgs) -> Result, anyhow::Error> { let compression = ask_compression(args)?; let _hash_info = ask_hash(args, compression)?; let target = match &args.out { Some(f) => WriteTarget::try_from(f.as_ref())?, None => ask_outfile(args)?, }; - let begin_params = WriteVerifyParams::new(args.image.clone(), compression, target)?; + let begin_params = WriteVerifyWorkflow::new(args.image.clone(), compression, target)?; if !confirm_write(args, &begin_params)? { eprintln!("Aborting."); return Ok(None); @@ -57,7 +57,7 @@ pub fn do_setup_wizard(args: &BurnArgs) -> Result, any } pub struct Params<'a> { - pub child_state: Watch, + pub child_state: Watch, pub log_paths: &'a LogPaths, } @@ -77,10 +77,10 @@ pub enum WriteOrEscalateError { pub fn try_start_write_or_escalate( facade: Arc, runtime: &impl RemoteSpawn, - args: &WriteVerifyParams, + args: &WriteVerifyWorkflow, root: UseSudo, interactive: bool, -) -> Result, WriteOrEscalateError> { +) -> Result, WriteOrEscalateError> { tracing::info!("Starting burn without escalation"); let err = match facade @@ -93,7 +93,7 @@ pub fn try_start_write_or_escalate( Err(e) => e, }; - if let WriteVerifyWorkflowError::Worker(WriteVerifyWorkerError::PermissionDenied) = err.as_ref() + if let WriteVerifyWorkflowError::Worker(LegacyWriteVerifyError::PermissionDenied) = err.as_ref() { tracing::info!("Unescalated burn failed"); match (root, interactive) { @@ -151,10 +151,10 @@ pub fn run<'a>(params: Params<'a>) { let child_state = params.child_state.borrow(); match &*child_state { - WriterVerifyState::Writing(b) => { + WVState::Writing(b) => { write_progress.set_position((b.approximate_ratio() * (length as f64)) as u64) } - WriterVerifyState::Verifying { + WVState::Verifying { verify_hist, total_write_bytes, .. @@ -162,7 +162,7 @@ pub fn run<'a>(params: Params<'a>) { let ratio = verify_hist.bytes_encountered() as f64 / *total_write_bytes as f64; verify_progress.set_position((ratio * (length as f64)) as u64) } - WriterVerifyState::Finished { result, .. } => { + WVState::Finished { result, .. } => { match result { Err(error) => { println!("Error occurred while writing: {error}"); From 9daebf857c81c85fe1250d04e155e541539f86c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 11:52:51 +0200 Subject: [PATCH 23/29] export `ui::simple_ui::facade_ext::FacadeExt` publicly --- src/ui/mod.rs | 2 +- src/ui/simple_ui/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ebc58363..afcbfcce 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,7 +7,7 @@ use std::{fs::File, sync::Arc}; use tracing::{debug, info}; -pub use self::{cli::BurnArgs, utils::ByteSpeed}; +pub use self::{cli::BurnArgs, utils::ByteSpeed, simple_ui::FacadeExt}; use crate::{ facade::CaligulaFacade, logging::LogPaths, diff --git a/src/ui/simple_ui/mod.rs b/src/ui/simple_ui/mod.rs index 0b0e1371..8e811c4b 100644 --- a/src/ui/simple_ui/mod.rs +++ b/src/ui/simple_ui/mod.rs @@ -13,7 +13,6 @@ use tracing::debug; use self::{ ask_hash::ask_hash, ask_outfile::{ask_compression, ask_outfile, confirm_write}, - facade_ext::FacadeExt as _, }; use super::cli::BurnArgs; use crate::{ @@ -28,6 +27,8 @@ use crate::{ ui::cli::UseSudo, }; +pub use facade_ext::FacadeExt; + mod ask_hash; mod ask_outfile; mod facade_ext; From a755a0f502e0023c94623dbb64acc9a15fba0ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 11:53:10 +0200 Subject: [PATCH 24/29] WIP: try to correctly implement worker thread --- src/gui/app.rs | 202 +++++++++++++++++++++++++++---------------------- src/gui/mod.rs | 4 +- 2 files changed, 113 insertions(+), 93 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index c6761b5f..e14f08ac 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -3,30 +3,28 @@ use egui::{ }; use std::{ path::PathBuf, - sync::{Arc, Mutex}, + sync::{ + Arc, Mutex, + mpsc::{self, Receiver, Sender}, + }, + time::Duration, }; use tracing::error; use crate::{ compression::CompressionFormat, device::{self, Removable, WriteTarget, enumerate_devices}, - facade::{CaligulaFacade, WVState, WriteVerifyWorkflow}, + facade::{CaligulaFacade, WVState, WriteVerifyWorkflow, watch::Watch}, hash::{HashAlg, parse_hash_input}, logging::LogPaths, runtime::RemoteSpawn, + ui::FacadeExt, }; -pub struct App { - pub log_paths: Arc, +pub struct App { pub options: Options, - pub ongoing_write: Option, - pub orc: Arc, - pub runtime: R, -} - -pub struct OngoingWrite { - pub write_progress: u64, - pub verify_progress: u64, + pub main_to_worker_tx: Sender, + pub write_verify_state: Arc>>>, } #[derive(Default)] @@ -42,8 +40,6 @@ pub struct Options { pub show_all_disks: bool, #[cfg_attr(debug_assertions, serde(skip))] pub has_confirmed_writing: bool, - #[cfg_attr(debug_assertions, serde(skip))] - pub write_verify_params: Option, } #[derive(Default)] @@ -58,10 +54,58 @@ pub struct FileHashOptions { pub verified: bool, } -impl App { +enum WorkerEvent { + StartWrite(WriteVerifyWorkflow), +} + +impl App { + fn spawn_worker_thread( + orc: Arc, + runtime: impl RemoteSpawn + Send + 'static, + ui_ctx: egui::Context, + main_to_worker_rx: Receiver, + write_verify_state: Arc>>>, + ) { + const REFRESH_PERIOD: Duration = Duration::from_millis(250); + + std::thread::spawn(move || { + loop { + let Ok(WorkerEvent::StartWrite(write_verify_workflow)) = + main_to_worker_rx.try_recv() + else { + std::thread::sleep(REFRESH_PERIOD); + continue; + }; + + let state = match orc + .clone() + .start_write_verify_blocking(&runtime, write_verify_workflow) + { + Err(e) => { + error!(?e, "failed to start write/verify process"); + continue; + } + Ok(state) => state, + }; + + // WARNING: can i really + // clone state? + *write_verify_state.lock().unwrap() = Some(state.clone()); + + loop { + if matches!(&*state.borrow(), WVState::Finished { .. }) { + break; // installation done! + } + ui_ctx.request_repaint(); + std::thread::sleep(REFRESH_PERIOD); + } + } + }); + } + pub fn new( cc: &eframe::CreationContext, - runtime: R, + runtime: impl RemoteSpawn + Send + 'static, orc: Arc, log_paths: Arc, ) -> Self { @@ -74,12 +118,22 @@ impl App { .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) .unwrap_or_default(); - let mut s = Self { - log_paths, - options, - ongoing_write: Arc::new(Mutex::new(None)), + let (main_to_worker_tx, main_to_worker_rx) = mpsc::channel(); + + let write_verify_state = Arc::new(Mutex::new(None)); + + Self::spawn_worker_thread( orc, runtime, + cc.egui_ctx.clone(), + main_to_worker_rx, + write_verify_state.clone(), + ); + + let mut s = Self { + options, + main_to_worker_tx, + write_verify_state, }; s.refresh_devices(); @@ -261,73 +315,39 @@ impl App { // don't unwrap. // actually don't even have this shitty refresh button, // should just refresh when any of the underlying values change - self.options.write_verify_params = WriteVerifyWorkflow::new( - self.options.picked_image.clone().unwrap(), - self.options.detected_compression_format.unwrap(), - self.options.selected_write_target.clone().unwrap(), - ) - .ok(); + // self.options.write_verify_params = WriteVerifyWorkflow::new( + // self.options.picked_image.clone().unwrap(), + // self.options.detected_compression_format.unwrap(), + // self.options.selected_write_target.clone().unwrap(), + // ) + // .ok(); } - if let Some(write_verify_params) = &self.options.write_verify_params { - // TODO: show summary! - // ui.label(write_verify_params.to_string()); - - ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); - - if !self.options.has_confirmed_writing { - if ui.button("Perform write").clicked() { - self.options.has_confirmed_writing = true; - } - return; - } - - ui.label( - RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") - .color(Color32::YELLOW), - ); - - if ui.button("I know, do it!").clicked() { - let child_state = match self - .orc - .start_write_verify_blocking(self.runtime, *write_verify_params) - { - Err(e) => { - error!(?e, "failed to start write/verify process"); - return; - } - Ok(p) => p.state, - }; - - self.ongoing_write = Some(OngoingWrite { - write_progress: 0, - verify_progress: 0, - }); - - loop { - // FIXME: fugly-ass unwrapping - match *child_state.borrow() { - WVState::Writing(b) => { - self.ongoing_write.unwrap().write_progress = - (b.approximate_ratio() * 1000.0) as u64 - } - WVState::Verifying { - total_write_bytes, .. - } => { - self.ongoing_write.unwrap().verify_progress = - total_write_bytes * 1000 / input_file_bytes - } - WVState::Finished { .. } => break, - } - ui.ctx().request_repaint(); - } - } - } + // if let Some(write_verify_params) = &self.options.write_verify_params { + // // TODO: show summary! + // // ui.label(write_verify_params.to_string()); + // + // ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); + // + // if !self.options.has_confirmed_writing { + // if ui.button("Perform write").clicked() { + // self.options.has_confirmed_writing = true; + // } + // return; + // } + // + // ui.label( + // RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") + // .color(Color32::YELLOW), + // ); + // + // if ui.button("I know, do it!").clicked() {} + // } }); } } -impl eframe::App for App { +impl eframe::App for App { fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { @@ -344,16 +364,16 @@ impl eframe::App for App { }); CentralPanel::default().show_inside(ui, |ui| { - if let Some(ongoing_write) = &*self.ongoing_write.lock().unwrap() { - ui.label("writing!!"); - ui.label(format!("Write progress: {}%", ongoing_write.write_progress)); - ui.label(format!( - "Verify progress: {}%", - ongoing_write.verify_progress - )); - - return; - } + // if let Some(ongoing_write) = &*self.ongoing_write.lock().unwrap() { + // ui.label("writing!!"); + // ui.label(format!("Write progress: {}%", ongoing_write.write_progress)); + // ui.label(format!( + // "Verify progress: {}%", + // ongoing_write.verify_progress + // )); + // + // return; + // } ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); ui.label(env!("CARGO_PKG_DESCRIPTION")); diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 091b2355..ca331cf1 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -3,10 +3,10 @@ mod app; use app::App; use std::sync::Arc; -use crate::{logging::LogPaths, facade::CaligulaFacade, runtime::RemoteSpawn}; +use crate::{facade::CaligulaFacade, logging::LogPaths, runtime::RemoteSpawn}; pub fn main( - runtime: impl RemoteSpawn, + runtime: impl RemoteSpawn + Send + 'static, orc: Arc, log_paths: Arc, ) -> eframe::Result<()> { From 8f4bc95e0f33df0fb04fae64508003704965e753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 11:54:30 +0200 Subject: [PATCH 25/29] remove generics, simplifying impl blocks --- src/gui/app.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index e14f08ac..81cd94a4 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -21,7 +21,7 @@ use crate::{ ui::FacadeExt, }; -pub struct App { +pub struct App { pub options: Options, pub main_to_worker_tx: Sender, pub write_verify_state: Arc>>>, @@ -58,9 +58,9 @@ enum WorkerEvent { StartWrite(WriteVerifyWorkflow), } -impl App { +impl App { fn spawn_worker_thread( - orc: Arc, + orc: Arc, runtime: impl RemoteSpawn + Send + 'static, ui_ctx: egui::Context, main_to_worker_rx: Receiver, @@ -106,7 +106,7 @@ impl App { pub fn new( cc: &eframe::CreationContext, runtime: impl RemoteSpawn + Send + 'static, - orc: Arc, + orc: Arc, log_paths: Arc, ) -> Self { #[cfg(not(debug_assertions))] @@ -347,7 +347,7 @@ impl App { } } -impl eframe::App for App { +impl eframe::App for App { fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { From cb020bc8a2cceebe0b3081b118cd9681c27612cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 12:05:27 +0200 Subject: [PATCH 26/29] somewhat working, need to clean this shit code up --- src/gui/app.rs | 74 +++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 49 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 81cd94a4..a8ca2ff3 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -9,7 +9,7 @@ use std::{ }, time::Duration, }; -use tracing::error; +use tracing::{error, info}; use crate::{ compression::CompressionFormat, @@ -56,6 +56,7 @@ pub struct FileHashOptions { enum WorkerEvent { StartWrite(WriteVerifyWorkflow), + Abort, } impl App { @@ -73,6 +74,7 @@ impl App { let Ok(WorkerEvent::StartWrite(write_verify_workflow)) = main_to_worker_rx.try_recv() else { + info!("nothing happening in worker thread, sleeping..."); std::thread::sleep(REFRESH_PERIOD); continue; }; @@ -88,17 +90,14 @@ impl App { Ok(state) => state, }; - // WARNING: can i really - // clone state? - *write_verify_state.lock().unwrap() = Some(state.clone()); + // WARNING: can i really clone state? + write_verify_state.lock().unwrap().replace(state.clone()); - loop { - if matches!(&*state.borrow(), WVState::Finished { .. }) { - break; // installation done! - } + while !matches!(&*state.borrow(), WVState::Finished { .. }) { ui_ctx.request_repaint(); std::thread::sleep(REFRESH_PERIOD); } + ui_ctx.request_repaint(); } }); } @@ -311,38 +310,19 @@ impl App { ui.add_enabled_ui(self.is_ready_for_writing(), |ui| { ui.strong("Write"); if ui.button("Prepare for writing").clicked() { - // FIXME: - // don't unwrap. - // actually don't even have this shitty refresh button, - // should just refresh when any of the underlying values change - // self.options.write_verify_params = WriteVerifyWorkflow::new( - // self.options.picked_image.clone().unwrap(), - // self.options.detected_compression_format.unwrap(), - // self.options.selected_write_target.clone().unwrap(), - // ) - // .ok(); - } + // FIXME: unwrapping! if any are missing we need to show this to user instead + // TODO: remove this button and automaatically populate this ingoing values all are Some + let write_verify_workflow = WriteVerifyWorkflow::new( + self.options.picked_image.clone().unwrap(), + self.options.detected_compression_format.unwrap(), + self.options.selected_write_target.clone().unwrap(), + ) + .unwrap(); - // if let Some(write_verify_params) = &self.options.write_verify_params { - // // TODO: show summary! - // // ui.label(write_verify_params.to_string()); - // - // ui.label(RichText::new("Ready to write!").color(Color32::GREEN)); - // - // if !self.options.has_confirmed_writing { - // if ui.button("Perform write").clicked() { - // self.options.has_confirmed_writing = true; - // } - // return; - // } - // - // ui.label( - // RichText::new("THIS ACTION WILL DESTROY ALL DATA ON THIS DEVICE!!!") - // .color(Color32::YELLOW), - // ); - // - // if ui.button("I know, do it!").clicked() {} - // } + self.main_to_worker_tx + .send(WorkerEvent::StartWrite(write_verify_workflow)) + .unwrap() + } }); } } @@ -364,16 +344,12 @@ impl eframe::App for App { }); CentralPanel::default().show_inside(ui, |ui| { - // if let Some(ongoing_write) = &*self.ongoing_write.lock().unwrap() { - // ui.label("writing!!"); - // ui.label(format!("Write progress: {}%", ongoing_write.write_progress)); - // ui.label(format!( - // "Verify progress: {}%", - // ongoing_write.verify_progress - // )); - // - // return; - // } + if let Some(write_verify_state) = &*self.write_verify_state.lock().unwrap() { + ui.label(format!( + "Current state: {:?}", + write_verify_state.borrow().write_hist() + )); + } ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); ui.label(env!("CARGO_PKG_DESCRIPTION")); From ce7f480f07a3a2499cead94075e3f3fe1b133732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 12:33:05 +0200 Subject: [PATCH 27/29] very shitty but working abort worker threada --- src/gui/app.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index a8ca2ff3..2b6a8891 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,5 +1,6 @@ use egui::{ - CentralPanel, Checkbox, Color32, ComboBox, MenuBar, Panel, RichText, UiBuilder, ViewportCommand, + CentralPanel, Checkbox, Color32, ComboBox, MenuBar, Panel, ProgressBar, RichText, UiBuilder, + ViewportCommand, }; use std::{ path::PathBuf, @@ -70,7 +71,7 @@ impl App { const REFRESH_PERIOD: Duration = Duration::from_millis(250); std::thread::spawn(move || { - loop { + 'outer: loop { let Ok(WorkerEvent::StartWrite(write_verify_workflow)) = main_to_worker_rx.try_recv() else { @@ -90,13 +91,24 @@ impl App { Ok(state) => state, }; - // WARNING: can i really clone state? write_verify_state.lock().unwrap().replace(state.clone()); while !matches!(&*state.borrow(), WVState::Finished { .. }) { ui_ctx.request_repaint(); std::thread::sleep(REFRESH_PERIOD); + + if matches!(main_to_worker_rx.try_recv(), Ok(WorkerEvent::Abort)) { + write_verify_state.lock().unwrap().take(); + ui_ctx.request_repaint(); + continue 'outer; + } } + + while !matches!(main_to_worker_rx.try_recv(), Ok(WorkerEvent::Abort)) { + std::thread::sleep(REFRESH_PERIOD); + } + + write_verify_state.lock().unwrap().take(); ui_ctx.request_repaint(); } }); @@ -345,10 +357,45 @@ impl eframe::App for App { CentralPanel::default().show_inside(ui, |ui| { if let Some(write_verify_state) = &*self.write_verify_state.lock().unwrap() { - ui.label(format!( - "Current state: {:?}", - write_verify_state.borrow().write_hist() - )); + match &*write_verify_state.borrow() { + WVState::Writing(writing) => { + ui.label("Burning"); + ui.add( + ProgressBar::new(writing.approximate_ratio() as f32).show_percentage(), + ); + if ui.button("Abort").clicked() { + self.main_to_worker_tx.send(WorkerEvent::Abort).unwrap(); + } + } + WVState::Verifying { + total_write_bytes, + verify_hist, + .. + } => { + ui.label("Verifying"); + let ratio = + verify_hist.bytes_encountered() as f32 / *total_write_bytes as f32; + ui.add(ProgressBar::new(ratio).show_percentage()); + if ui.button("Abort").clicked() { + self.main_to_worker_tx.send(WorkerEvent::Abort).unwrap(); + } + } + WVState::Finished { + finish_time, + result, + total_write_bytes, + .. + } => { + ui.label(format!( + "Finished in {finish_time:?} with result {result:?}" + )); + ui.label(format!("Total bytes written: {total_write_bytes}")); + if ui.button("Finish").clicked() { + self.main_to_worker_tx.send(WorkerEvent::Abort).unwrap(); + } + } + } + return; } ui.label(RichText::new(env!("CARGO_PKG_NAME")).heading().size(26.)); From 47fa7eba0b83bf3c13fb2d5301877ce456b97be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 12:35:05 +0200 Subject: [PATCH 28/29] add TODO --- src/gui/app.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/app.rs b/src/gui/app.rs index 2b6a8891..5f80f9ee 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -100,6 +100,12 @@ impl App { if matches!(main_to_worker_rx.try_recv(), Ok(WorkerEvent::Abort)) { write_verify_state.lock().unwrap().take(); ui_ctx.request_repaint(); + + // `state` goes out of scope here. + // TODO: + // find out if dropping `state` cancels the + // WriteVerifyWorkflow. + // or do we just lose control of it? continue 'outer; } } From d86aa2c6730d6ce15c3a3bf3cea3623f2dbeebd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C3=85kesson?= Date: Thu, 7 May 2026 12:41:21 +0200 Subject: [PATCH 29/29] move sections into own module (again). add back confirm write button --- src/gui/app.rs | 169 +--------------------------------------- src/gui/mod.rs | 1 + src/gui/sections.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 168 deletions(-) create mode 100644 src/gui/sections.rs diff --git a/src/gui/app.rs b/src/gui/app.rs index 5f80f9ee..e4f0816b 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -55,7 +55,7 @@ pub struct FileHashOptions { pub verified: bool, } -enum WorkerEvent { +pub enum WorkerEvent { StartWrite(WriteVerifyWorkflow), Abort, } @@ -176,173 +176,6 @@ impl App { && self.file_hash_is_verified_or_skipped() && self.options.selected_write_target.is_some() } - - pub fn file_hash_ui(&mut self, ui: &mut egui::Ui) { - let FileHashOptions { - entered_hash: file_hash_str, - possible_algorithms: file_hash_algorithms_possible, - selected_algorithm: file_hash_algorithm_selected, - last_error: latest_hashing_error, - skip: skip_hashing, - verified: verified_hash, - } = &mut self.options.file_hash; - - ui.horizontal(|ui| { - ui.strong("File hash"); - ui.checkbox(skip_hashing, "Skip?"); - }); - - ui.scope_builder( - UiBuilder { - disabled: *skip_hashing, - // invisible: *skip_hashing, - ..Default::default() - }, - |ui| { - ui.label("We will guess the hash algorithm from your input."); - if ui.text_edit_singleline(file_hash_str).changed() { - match parse_hash_input(file_hash_str) { - Ok((algs, _)) => { - if algs.len() == 1 { - *file_hash_algorithm_selected = Some(algs[0]); - } - *file_hash_algorithms_possible = algs; - latest_hashing_error.clear(); - } - Err(e) => { - *file_hash_algorithms_possible = vec![]; - *file_hash_algorithm_selected = None; - *latest_hashing_error = e.to_string(); - } - } - } - - if latest_hashing_error.is_empty() { - ui.horizontal(|ui| { - for alg in file_hash_algorithms_possible { - let is_selected = Some(*alg) == *file_hash_algorithm_selected; - - if ui.selectable_label(is_selected, alg.to_string()).clicked() { - *file_hash_algorithm_selected = Some(*alg); - } - } - }); - - ui.horizontal(|ui| { - ui.add_enabled(false, Checkbox::without_text(verified_hash)); - if ui.button("Verify").clicked() { - // TODO: - *verified_hash = true; - } - }); - } else if *skip_hashing { - ui.label(""); - } else { - ui.label(RichText::new(&*latest_hashing_error).color(Color32::RED)); - } - }, - ); - } - - pub fn image_ui(&mut self, ui: &mut egui::Ui) { - let Options { - detected_compression_format, - picked_image, - .. - } = &mut self.options; - - ui.strong("Image"); - if ui.button("💿 Pick file").clicked() - && let Some(picked) = rfd::FileDialog::new().pick_file() - { - *detected_compression_format = CompressionFormat::detect_from_path(&picked); - *picked_image = Some(picked); - } - if let Some(picked) = picked_image { - ui.label(picked.to_string_lossy()); - if let Some(cf) = detected_compression_format { - ui.label(format!("Detected compression format: {}", cf)); - } else { - ui.label( - RichText::new( - "Couldn't detect compression format for picked image, assuming uncompressed!", - ) - .color(Color32::YELLOW), - ); - } - } - } - - pub fn target_disk_ui(&mut self, ui: &mut egui::Ui) { - ui.strong("Target disk"); - if ui.button("Refresh devices").clicked() { - self.refresh_devices(); - } - - // FIXME: - // - stop alloc:ing and doing so much work here.. DON'T CLONE! - // - move the label formatting into a place where it's done ONCE, not on every ui render! - // - deduplicate, label formatting is stolen from `ask_outfile.rs` - ComboBox::from_label(format!( - "{} available", - self.options.possible_write_targets.len() - )) - .selected_text( - self.options - .selected_write_target - .as_ref() - .map(|dev| match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }) - .unwrap_or_default(), - ) - .show_ui(ui, |ui| { - for dev in &self.options.possible_write_targets { - let label = match dev.target_type { - device::Type::Disk => format!( - "{} | {} - {} ({}, removable: {})", - dev.name, dev.model, dev.size, dev.target_type, dev.removable - ), - _ => format!( - "{} | {} - {} ({})", - dev.name, dev.model, dev.size, dev.target_type - ), - }; - ui.selectable_value( - &mut self.options.selected_write_target, - Some(dev.clone()), - label, - ); - } - }); - } - - pub fn add_begin_writing_ui(&mut self, ui: &mut egui::Ui) { - ui.add_enabled_ui(self.is_ready_for_writing(), |ui| { - ui.strong("Write"); - if ui.button("Prepare for writing").clicked() { - // FIXME: unwrapping! if any are missing we need to show this to user instead - // TODO: remove this button and automaatically populate this ingoing values all are Some - let write_verify_workflow = WriteVerifyWorkflow::new( - self.options.picked_image.clone().unwrap(), - self.options.detected_compression_format.unwrap(), - self.options.selected_write_target.clone().unwrap(), - ) - .unwrap(); - - self.main_to_worker_tx - .send(WorkerEvent::StartWrite(write_verify_workflow)) - .unwrap() - } - }); - } } impl eframe::App for App { diff --git a/src/gui/mod.rs b/src/gui/mod.rs index ca331cf1..0c666507 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,4 +1,5 @@ mod app; +mod sections; use app::App; use std::sync::Arc; diff --git a/src/gui/sections.rs b/src/gui/sections.rs new file mode 100644 index 00000000..b25a3563 --- /dev/null +++ b/src/gui/sections.rs @@ -0,0 +1,183 @@ +use egui::{Checkbox, Color32, ComboBox, RichText, UiBuilder}; + +use crate::{ + compression::CompressionFormat, + device::{self}, + facade::WriteVerifyWorkflow, + gui::app::{App, FileHashOptions, Options, WorkerEvent}, + hash::parse_hash_input, +}; + +impl App { + pub fn file_hash_ui(&mut self, ui: &mut egui::Ui) { + let FileHashOptions { + entered_hash: file_hash_str, + possible_algorithms: file_hash_algorithms_possible, + selected_algorithm: file_hash_algorithm_selected, + last_error: latest_hashing_error, + skip: skip_hashing, + verified: verified_hash, + } = &mut self.options.file_hash; + + ui.horizontal(|ui| { + ui.strong("File hash"); + ui.checkbox(skip_hashing, "Skip?"); + }); + + ui.scope_builder( + UiBuilder { + disabled: *skip_hashing, + // invisible: *skip_hashing, + ..Default::default() + }, + |ui| { + ui.label("We will guess the hash algorithm from your input."); + if ui.text_edit_singleline(file_hash_str).changed() { + match parse_hash_input(file_hash_str) { + Ok((algs, _)) => { + if algs.len() == 1 { + *file_hash_algorithm_selected = Some(algs[0]); + } + *file_hash_algorithms_possible = algs; + latest_hashing_error.clear(); + } + Err(e) => { + *file_hash_algorithms_possible = vec![]; + *file_hash_algorithm_selected = None; + *latest_hashing_error = e.to_string(); + } + } + } + + if latest_hashing_error.is_empty() { + ui.horizontal(|ui| { + for alg in file_hash_algorithms_possible { + let is_selected = Some(*alg) == *file_hash_algorithm_selected; + + if ui.selectable_label(is_selected, alg.to_string()).clicked() { + *file_hash_algorithm_selected = Some(*alg); + } + } + }); + + ui.horizontal(|ui| { + ui.add_enabled(false, Checkbox::without_text(verified_hash)); + if ui.button("Verify").clicked() { + // TODO: + *verified_hash = true; + } + }); + } else if *skip_hashing { + ui.label(""); + } else { + ui.label(RichText::new(&*latest_hashing_error).color(Color32::RED)); + } + }, + ); + } + + pub fn image_ui(&mut self, ui: &mut egui::Ui) { + let Options { + detected_compression_format, + picked_image, + .. + } = &mut self.options; + + ui.strong("Image"); + if ui.button("💿 Pick file").clicked() + && let Some(picked) = rfd::FileDialog::new().pick_file() + { + *detected_compression_format = CompressionFormat::detect_from_path(&picked); + *picked_image = Some(picked); + } + if let Some(picked) = picked_image { + ui.label(picked.to_string_lossy()); + if let Some(cf) = detected_compression_format { + ui.label(format!("Detected compression format: {}", cf)); + } else { + ui.label( + RichText::new( + "Couldn't detect compression format for picked image, assuming uncompressed!", + ) + .color(Color32::YELLOW), + ); + } + } + } + + pub fn target_disk_ui(&mut self, ui: &mut egui::Ui) { + ui.strong("Target disk"); + if ui.button("Refresh devices").clicked() { + self.refresh_devices(); + } + + // FIXME: + // - stop alloc:ing and doing so much work here.. DON'T CLONE! + // - move the label formatting into a place where it's done ONCE, not on every ui render! + // - deduplicate, label formatting is stolen from `ask_outfile.rs` + ComboBox::from_label(format!( + "{} available", + self.options.possible_write_targets.len() + )) + .selected_text( + self.options + .selected_write_target + .as_ref() + .map(|dev| match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }) + .unwrap_or_default(), + ) + .show_ui(ui, |ui| { + for dev in &self.options.possible_write_targets { + let label = match dev.target_type { + device::Type::Disk => format!( + "{} | {} - {} ({}, removable: {})", + dev.name, dev.model, dev.size, dev.target_type, dev.removable + ), + _ => format!( + "{} | {} - {} ({})", + dev.name, dev.model, dev.size, dev.target_type + ), + }; + ui.selectable_value( + &mut self.options.selected_write_target, + Some(dev.clone()), + label, + ); + } + }); + } + + pub fn add_begin_writing_ui(&mut self, ui: &mut egui::Ui) { + ui.add_enabled_ui(self.is_ready_for_writing(), |ui| { + ui.strong("Write"); + if ui.button("Prepare for writing").clicked() { + self.options.has_confirmed_writing = true; + } + if self.options.has_confirmed_writing && ui.button("Write!").clicked() { + // FIXME: unwrapping! if any are missing we need to show this to user instead + // TODO: remove this button and automaatically populate this ingoing values all are Some + let write_verify_workflow = WriteVerifyWorkflow::new( + self.options.picked_image.clone().unwrap(), + self.options.detected_compression_format.unwrap(), + self.options.selected_write_target.clone().unwrap(), + ) + .unwrap(); + + self.main_to_worker_tx + .send(WorkerEvent::StartWrite(write_verify_workflow)) + .unwrap(); + + self.options.has_confirmed_writing = false; + } + }); + } +}