diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 9f0cf9e4..1c93052f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -17,5 +17,3 @@ cargo clippy $clippy_args -- --deny warnings CARGO_BUILD_RUSTDOCFLAGS="--deny warnings" cargo doc --document-private-items --no-deps set +x - -[ $dirty_workspace -eq 0 ] && git add . \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45fea4b8..a7d53020 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,7 +65,7 @@ jobs: run: | # TODO: rustup target add aarch64-unknown-linux-gnu # Linting tools - cargo install cargo-shear + cargo install cargo-shear --locked cargo fetch - name: Build @@ -120,11 +120,12 @@ jobs: - name: Upload x86 id: upload-x86 if: ${{ steps.build.outcome == 'success' }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ${{ steps.create-artifacts.outputs.ARTIFACT_X86 }} path: ${{ steps.create-artifacts.outputs.ARTIFACT_X86 }} if-no-files-found: error + archive: false - name: Save cached cargo dependencies if: steps.deps-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bec7ae3..5953c83e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: env: MESSAGE_FILE: ./message.md steps: + - uses: actions/checkout@v6 + - name: Check if in PR id: pr-number env: @@ -46,8 +48,7 @@ jobs: if [ "${{ needs.build.outputs.build }}" == "success" ]; then write "### :package: Artifacts:" write "1. [Download the tarball](${{ needs.build.outputs.artifact-url }})" - write "2. \`unzip linux-enable-ir-emitter*.tar.gz.zip\`" - write "3. [Execute the install instructions](https://github.com/EmixamPP/linux-enable-ir-emitter?tab=readme-ov-file#installation)" + write "2. [Execute the install instructions](https://github.com/EmixamPP/linux-enable-ir-emitter?tab=readme-ov-file#installation)" fi write "" write "## :test_tube: Tests results: $(outcome_emoji ${{ needs.build.outputs.tests }})" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4312aa51..4583be73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v6 - name: Download artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 - name: Create tag uses: rickstaa/action-create-tag@v1 @@ -25,7 +25,7 @@ jobs: tag: ${{ needs.build.outputs.version }} - name: Create release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: linux-enable-ir-emitter-*.tar.gz tag_name: ${{ needs.build.outputs.version }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0500a043..dc998cae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ When building a package for distribution, please ensure the following: ``` The resulting binary will be located at `target/release/linux-enable-ir-emitter`. You can also use `cargo install --path <...>` to your convenience. 4. The v7 is incompatible with the v6. If applicable, please make sure to use the provided [migration script]() on the saved configuration. - > [!Important] + > [!IMPORTANT] > This script is not yet available. It will be provided when the v7 will be officially released (currently in beta). ## Contributing Code @@ -24,19 +24,21 @@ This project is using the usual Rust conventions. Here are some additional expla cargo build ``` The resulting binary will be located at `target/debug/linux-enable-ir-emitter` - > [!NOTE] > With a debug build, any camera can be used, even not infrared ones. This is useful for development and testing. - -2. Add the pre-commit hooks to make sure the linting checks and tests are passing before each commit: +2. Run with: + ``` + cargo run -- + ``` +3. Add the pre-commit hooks to make sure the linting checks and tests are passing before each commit: ``` git config core.hooksPath .githooks ``` Additionally, it will automatically format/fix the issues for you when possible. -3. If you modified the UI intentionally, you will need to re-generate the UI test snapshots: +4. If you modified the UI intentionally, you will need to re-generate the UI test snapshots: ``` - cargo install cargo-insta + cargo install cargo-insta --locked cargo insta review ``` -4. Please make sure to write tests for your code changes where applicable. -5. Commit, push and please describe enough what you did in your PR description. Thank you very much! +5. Please make sure to write tests for your code changes where applicable. +6. Commit, push and please describe enough what you did in your PR description. Thank you very much! diff --git a/Cargo.lock b/Cargo.lock index 2d27a7b2..7ef12612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -51,7 +51,7 @@ dependencies = [ "ratatui-core", "simdutf8", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.21", ] [[package]] @@ -64,9 +64,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -79,15 +79,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -114,9 +114,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] name = "arbitrary" @@ -132,14 +141,14 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as-slice" @@ -161,9 +170,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "av-scenechange" @@ -180,7 +189,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.21", "v_frame", "y4m", ] @@ -201,9 +210,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" dependencies = [ "arrayvec", ] @@ -232,8 +241,8 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", - "syn 2.0.114", + "shlex 1.3.0", + "syn 2.0.119", "which", ] @@ -266,17 +275,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bitstream-io" -version = "4.9.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" dependencies = [ - "core2", + "no_std_io2", ] [[package]] @@ -290,21 +299,27 @@ dependencies = [ [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "by_address" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder-lite" @@ -323,14 +338,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.52" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -344,21 +359,21 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", @@ -367,9 +382,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -377,9 +392,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -389,21 +404,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "color_quant" @@ -413,15 +428,15 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -433,14 +448,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -452,15 +466,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -472,18 +477,24 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -491,18 +502,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crossterm" @@ -510,14 +521,14 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "crossterm_winapi", "derive_more", "document-features", "futures-core", "mio", "parking_lot", - "rustix 1.1.3", + "rustix 1.1.5", "signal-hook", "signal-hook-mio", "winapi", @@ -560,9 +571,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -570,26 +581,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] @@ -600,12 +611,9 @@ checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "derive_more" @@ -626,7 +634,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -671,9 +679,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encode_unicode" @@ -698,7 +706,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -719,23 +727,25 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] [[package]] name = "exr" -version = "1.74.0" +version = "1.74.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", "rayon-core", "smallvec", "zune-inflate", @@ -753,29 +763,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fax" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" -dependencies = [ - "fax_derive", -] - -[[package]] -name = "fax_derive" -version = "0.2.0" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" [[package]] name = "fdeflate" @@ -799,15 +795,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "finl_unicode" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" +checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2" [[package]] name = "fixedbitset" @@ -817,12 +813,13 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", + "zlib-rs", ] [[package]] @@ -839,9 +836,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -854,9 +851,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -864,15 +861,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -881,38 +878,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -922,7 +919,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -938,32 +934,32 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi 5.3.0", + "wasip2", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasip2", + "r-efi 6.0.0", ] [[package]] name = "gif" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ "color_quant", "weezl", @@ -971,9 +967,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "half" @@ -997,6 +993,17 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.5.0" @@ -1026,9 +1033,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "image" -version = "0.25.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", @@ -1044,8 +1051,8 @@ dependencies = [ "rayon", "rgb", "tiff", - "zune-core 0.5.0", - "zune-jpeg 0.5.8", + "zune-core", + "zune-jpeg", ] [[package]] @@ -1060,18 +1067,18 @@ dependencies = [ [[package]] name = "imgref" -version = "1.12.0" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -1085,9 +1092,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.2" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c91d64f9ad425e80200a50a0e8b8a641680b44e33ce832efe5b8bc65161b07" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" dependencies = [ "console", "once_cell", @@ -1097,15 +1104,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "4c3b5acc1e2fd9375041a388da33d1eb8aed5f7a8c0dd3543e3ea2805adfbe20" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] @@ -1116,7 +1123,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -1125,15 +1132,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1145,39 +1143,40 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "kasuari" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", "portable-atomic", - "thiserror 2.0.17", + "thiserror 2.0.21", ] [[package]] @@ -1206,15 +1205,15 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libfuzzer-sys" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ "arbitrary", "cc", @@ -1230,28 +1229,33 @@ dependencies = [ "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.12" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ - "bitflags 2.10.0", "libc", ] [[package]] name = "line-clipping" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", ] [[package]] name = "linux-enable-ir-emitter" -version = "7.0.0-beta2" +version = "7.0.0" dependencies = [ "ansi-to-tui", "ansipix", @@ -1264,10 +1268,10 @@ dependencies = [ "image", "insta", "log", - "rand 0.9.2", + "rand 0.9.5", "rand_chacha", "ratatui", - "rustix 1.1.3", + "rustix 1.1.5", "serde", "serial_test", "shellexpand", @@ -1285,9 +1289,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" @@ -1306,9 +1310,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "loop9" @@ -1321,11 +1325,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.3" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5" dependencies = [ - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -1350,9 +1354,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmem" @@ -1385,11 +1389,21 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" -version = "1.1.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -1399,9 +1413,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.11" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ "num-traits", "pxfm", @@ -1419,13 +1433,22 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", "memoffset", ] +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + [[package]] name = "nom" version = "7.1.3" @@ -1453,19 +1476,29 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -1475,14 +1508,14 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -1518,9 +1551,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1543,6 +1576,39 @@ dependencies = [ "num-traits", ] +[[package]] +name = "palette" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64" +dependencies = [ + "approx", + "libm", + "palette_derive", + "palette_math", +] + +[[package]] +name = "palette_derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "palette_math" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12" +dependencies = [ + "libm", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1586,9 +1652,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pest" -version = "2.8.5" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8" dependencies = [ "memchr", "ucd-trie", @@ -1596,9 +1662,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf" dependencies = [ "pest", "pest_generator", @@ -1606,25 +1672,24 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb" dependencies = [ "pest", - "sha2", ] [[package]] @@ -1654,7 +1719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.8", ] [[package]] @@ -1667,7 +1732,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -1681,34 +1746,28 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "png" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", ] [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "powerfmt" @@ -1732,46 +1791,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] -name = "pxfm" -version = "0.1.27" +name = "pulp" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" dependencies = [ - "num-traits", + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", ] +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + [[package]] name = "qoi" version = "0.4.1" @@ -1789,9 +1868,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1802,23 +1881,29 @@ 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" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1828,7 +1913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1839,42 +1924,46 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "ratatui" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", + "ratatui-termina", "ratatui-termwiz", "ratatui-widgets", + "serde", ] [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "compact_str", - "hashbrown", - "indoc", - "itertools 0.14.0", + "critical-section", + "hashbrown 0.17.1", + "itertools", "kasuari", "lru", + "palette", + "serde", "strum", - "thiserror 2.0.17", + "thiserror 2.0.21", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -1882,9 +1971,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm", @@ -1894,19 +1983,30 @@ dependencies = [ [[package]] name = "ratatui-macros" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] -name = "ratatui-termwiz" +name = "ratatui-termina" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" +dependencies = [ + "instability", + "ratatui-core", + "termina", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ "ratatui-core", "termwiz", @@ -1914,17 +2014,18 @@ dependencies = [ [[package]] name = "ratatui-widgets" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.10.0", - "hashbrown", + "bitflags 2.13.2", + "hashbrown 0.17.1", "indoc", "instability", - "itertools 0.14.0", + "itertools", "line-clipping", "ratatui-core", + "serde", "strum", "time", "unicode-segmentation", @@ -1947,7 +2048,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools 0.14.0", + "itertools", "libc", "libfuzzer-sys", "log", @@ -1958,19 +2059,19 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.2", + "rand 0.9.5", "rand_chacha", "simd_helpers", - "thiserror 2.0.17", + "thiserror 2.0.21", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" dependencies = [ "avif-serialize", "imgref", @@ -1981,11 +2082,20 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.2", +] + [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2001,31 +2111,36 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", ] [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.16", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.21", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2035,9 +2150,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2046,15 +2161,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rgb" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" [[package]] name = "rustc-hash" @@ -2077,7 +2192,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2086,37 +2201,28 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.2", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - -[[package]] -name = "scc" -version = "2.4.0" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "scopeguard" @@ -2124,23 +2230,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2148,57 +2248,56 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "serial_test" -version = "3.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" dependencies = [ "futures-executor", "futures-util", "log", "once_cell", "parking_lot", - "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -2214,9 +2313,9 @@ dependencies = [ [[package]] name = "shellexpand" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ "dirs", ] @@ -2227,6 +2326,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -2260,9 +2365,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_helpers" @@ -2298,21 +2403,21 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "stable_deref_trait" @@ -2334,23 +2439,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] @@ -2366,9 +2471,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -2377,14 +2493,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", - "rustix 1.1.3", + "rustix 1.1.5", "windows-sys 0.61.2", ] @@ -2397,6 +2513,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.2", + "parking_lot", + "rustix 1.1.5", + "signal-hook", + "windows-sys 0.61.2", +] + [[package]] name = "terminfo" version = "0.9.0" @@ -2426,7 +2555,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.10.0", + "bitflags 2.13.2", "fancy-regex", "filedescriptor", "finl_unicode", @@ -2471,11 +2600,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.21", ] [[package]] @@ -2486,42 +2615,41 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "tiff" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" dependencies = [ "fax", "flate2", "half", "quick-error", "weezl", - "zune-jpeg 0.4.21", + "zune-jpeg", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -2533,15 +2661,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2549,9 +2677,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "pin-project-lite", "tokio-macros", @@ -2559,20 +2687,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", ] [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -2580,7 +2708,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2594,24 +2722,24 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -2621,23 +2749,23 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools", "unicode-segmentation", "unicode-width", ] @@ -2656,12 +2784,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "atomic", - "getrandom 0.3.4", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -2720,18 +2848,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -2742,9 +2870,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2752,22 +2880,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -2983,15 +3111,21 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "y4m" @@ -3001,35 +3135,35 @@ checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.119", ] [[package]] -name = "zune-core" -version = "0.4.12" +name = "zlib-rs" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" [[package]] name = "zune-core" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" [[package]] name = "zune-inflate" @@ -3042,18 +3176,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" -dependencies = [ - "zune-core 0.4.12", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.8" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core 0.5.0", + "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 606fa500..e4e0f372 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linux-enable-ir-emitter" -version = "7.0.0-beta2" +version = "7.0.0" edition = "2024" authors = ["Maxime Dirksen (EmixamPP)"] license = "MIT" diff --git a/README.md b/README.md index fb22d497..94182fc3 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,15 @@ auth optional pam_exec.so /usr/local/bin/linux-enable-ir-emitter run --config /h ``` > [!TIP] -> The installation paths may vary depending on your installation method. You can determine the correct binary absolute paths by running `which linux-enable-ir-emitter` and use that path instead. For the configuration path, it will be written when you can execute `linux-enable-ir-emitter --config`. +> The installation path may vary depending on your installation method. You can determine the correct binary absolute path by running `which linux-enable-ir-emitter` and use it instead. For the configuration path, it will be written when you execute `linux-enable-ir-emitter --config`. ### Integration with other program You will need to execute the `linux-enable-ir-emitter run` command before the program that uses the infrared camera. Alternatively, if you can and/or want to integrate better with the program that uses the camera, you can pass an opened file descriptor for the camera to the command: `linux-enable-ir-emitter run --device --fd `. -> [!Important] -> You will need to pass the config path as argument to `linux-enable-ir-emitter run --config ` **when executed as root** if `linux-enable-ir-emitter configure` was executed as a normal user. +> [!IMPORTANT] +> You will need to pass the config path as argument to `linux-enable-ir-emitter run --config ` **when executed as root**, if `linux-enable-ir-emitter configure` was executed as a normal user. ## How do I enable my infrared emitter? 0. For a better experience, use a large terminal window. diff --git a/src/configure.rs b/src/configure.rs index 4348e444..44f97e4f 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -4,10 +4,12 @@ mod ui; pub async fn configure() -> anyhow::Result<()> { let res = app::run(&mut ratatui::init()).await; ratatui::restore(); + + // Print any successful message to the user once the TUI is closed if let Ok(msg) = &res && !msg.is_empty() { println!("{}", msg); } - res.map(|_| ()) + res.map(|_| ()) // Delete the success message } diff --git a/src/configure/app.rs b/src/configure/app.rs index 5be1b8d2..b33de575 100644 --- a/src/configure/app.rs +++ b/src/configure/app.rs @@ -1,6 +1,7 @@ mod helper; pub mod ir_enabler; pub mod tool_menu; +pub mod tweaker; pub async fn run(terminal: &mut ratatui::DefaultTerminal) -> anyhow::Result<&'static str> { tool_menu::App::new().run(terminal).await diff --git a/src/configure/app/ir_enabler.rs b/src/configure/app/ir_enabler.rs index 7a67ae17..0ea992a4 100644 --- a/src/configure/app/ir_enabler.rs +++ b/src/configure/app/ir_enabler.rs @@ -1,6 +1,6 @@ use super::helper::*; use crate::configure::ui::ir_enabler::{IrEnablerCtx, View, ui}; -use crate::configure::ui::keys::*; +use crate::configure::ui::{DeviceSettingsCtx, SearchSettingsCtx}; use crate::video::ir::analyzer::{ self, IsIrWorking as AnalyzerResponse, Message as AnalyzerRequest, StreamAnalyzer, }; @@ -24,6 +24,14 @@ use tokio::{ task, }; +const KEY_YES: KeyCode = KeyCode::Char('y'); +const KEY_NO: KeyCode = KeyCode::Char('n'); +const KEY_EXIT: KeyCode = KeyCode::Esc; +const KEY_NAVIGATE_UP: KeyCode = KeyCode::Up; +const KEY_NAVIGATE_DOWN: KeyCode = KeyCode::Down; +const KEY_CONTINUE: KeyCode = KeyCode::Enter; +const KEY_DELETE: KeyCode = KeyCode::Backspace; + #[derive(Debug)] pub struct Config { /// Path to the video device. @@ -334,7 +342,7 @@ impl App { async fn handle_term_event(&mut self, event: Event) -> Result<()> { match event { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { - self.handle_key_press(key_event.code.into()).await?; + self.handle_key_press(key_event.code).await?; } _ => {} }; @@ -422,13 +430,14 @@ impl App { } /// Handles a key event based on the current application state. - async fn handle_key_press(&mut self, key: Key) -> Result<()> { + async fn handle_key_press(&mut self, key: KeyCode) -> Result<()> { match self.state() { State::Menu => match key { KEY_EXIT => self.set_state(State::Failure), - KEY_NAVIGATE => self.next_setting(), + KEY_NAVIGATE_UP => self.prev_setting(), + KEY_NAVIGATE_DOWN => self.next_setting(), KEY_DELETE => self.edit_setting(None), - Key(KeyCode::Char(c)) => self.edit_setting(Some(c)), + KeyCode::Char(c) => self.edit_setting(Some(c)), KEY_CONTINUE => self.set_state(State::ConfirmStart), _ => {} }, @@ -443,11 +452,7 @@ impl App { KEY_EXIT => self.set_state(State::ConfirmAbort), key => self.confirm_working(key).await?, }, - State::ConfirmWorking => { - if key == KEY_EXIT { - self.set_state(State::ConfirmAbort) - } - } + State::ConfirmWorking if key == KEY_EXIT => self.set_state(State::ConfirmAbort), _ => {} } Ok(()) @@ -458,7 +463,7 @@ impl App { /// In both of the two case, also changes the state to [`State::Running`]. /// /// Otherwise, does nothing. - async fn confirm_working(&mut self, k: Key) -> Result<()> { + async fn confirm_working(&mut self, k: KeyCode) -> Result<()> { let mut response = IREnablerResponse::No; if k == KEY_YES { response = IREnablerResponse::Yes; @@ -477,7 +482,7 @@ impl App { /// and sends [`IREnablerResponse::Abort`] to the configurator task. /// /// If the key is [`KEY_NO`], change the state back to [`State::Running`]. - async fn abort_or_continue(&mut self, k: Key) -> Result<()> { + async fn abort_or_continue(&mut self, k: KeyCode) -> Result<()> { match k { KEY_NO | KEY_EXIT => self.set_state(self.prev_state()), KEY_YES => { @@ -498,7 +503,7 @@ impl App { /// If the key is [`KEY_NO`], change the state back to the previous state. /// /// Returns directly an error if the video stream is already started. - fn start_or_back(&mut self, k: Key) -> Result<()> { + fn start_or_back(&mut self, k: KeyCode) -> Result<()> { // check that the path exists if !self.is_device_valid() { self.set_state(State::Menu); @@ -534,15 +539,22 @@ impl App { self.device_settings_list_state.select(None); self.search_settings_list_state.select_first(); } - } else if let Some(i) = self.search_settings_list_state.selected() { - if i < 3 { - self.search_settings_list_state.select_next(); + } else { + self.search_settings_list_state.select_next(); + } + } + + /// Moves the selection to the previous setting in the settings lists. + fn prev_setting(&mut self) { + if let Some(i) = self.search_settings_list_state.selected() { + if i > 0 { + self.search_settings_list_state.select_previous(); } else { self.search_settings_list_state.select(None); - self.device_settings_list_state.select_first(); + self.device_settings_list_state.select_last(); } } else { - self.device_settings_list_state.select_first(); + self.device_settings_list_state.select_previous(); } } @@ -594,6 +606,18 @@ impl IrEnablerCtx for App { fn show_menu_start_prompt(&self) -> bool { self.state() == State::ConfirmStart } + fn controls_list_state(&mut self) -> &mut ListState { + &mut self.controls_list_state + } + fn controls(&self) -> &[XuControl] { + &self.controls + } + fn image(&self) -> Option<&Image> { + self.image.as_ref() + } +} + +impl DeviceSettingsCtx for App { fn device_settings_list_state(&mut self) -> &mut ListState { &mut self.device_settings_list_state } @@ -615,6 +639,9 @@ impl IrEnablerCtx for App { fn fps(&self) -> Option { self.config.fps } +} + +impl SearchSettingsCtx for App { fn search_settings_list_state(&mut self) -> &mut ListState { &mut self.search_settings_list_state } @@ -633,15 +660,6 @@ impl IrEnablerCtx for App { fn inc_step(&self) -> u8 { self.config.inc_step } - fn controls_list_state(&mut self) -> &mut ListState { - &mut self.controls_list_state - } - fn controls(&self) -> &[XuControl] { - &self.controls - } - fn image(&self) -> Option<&Image> { - self.image.as_ref() - } } #[cfg(test)] @@ -655,16 +673,16 @@ mod tests { App::new() } - fn make_key_event(keycode: Key) -> KeyEvent { + fn make_key_event(keycode: KeyCode) -> KeyEvent { KeyEvent { - code: keycode.into(), + code: keycode, modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: crossterm::event::KeyEventState::NONE, } } - fn make_term_key_event(keycode: Key) -> Event { + fn make_term_key_event(keycode: KeyCode) -> Event { Event::Key(make_key_event(keycode)) } @@ -713,7 +731,7 @@ mod tests { } #[test] - fn test_next_setting_device_to_search_and_back() { + fn test_next_setting_device_to_search() { let mut app = make_app(); // Move through device settings (0..4) for i in 0..4 { @@ -724,14 +742,47 @@ mod tests { app.next_setting(); assert!(app.device_settings_list_state.selected().is_none()); assert_eq!(app.search_settings_list_state.selected(), Some(0)); - // Move through search settings (0..2) + // Move through search settings (0..3) for i in 0..3 { app.next_setting(); assert_eq!(app.search_settings_list_state.selected(), Some(i + 1)); } - // After 2, should wrap to device settings first + // After 3 we should stay at the last search setting app.next_setting(); + assert_eq!(app.search_settings_list_state.selected(), Some(4)); + } + + #[test] + fn test_prev_setting_device_to_search() { + let mut app = make_app(); + // Start at the end of search settings + app.device_settings_list_state.select(None); + app.search_settings_list_state.select(Some(4)); + + // Move through search settings (4..0) + for i in (1..=4).rev() { + assert_eq!(app.search_settings_list_state.selected(), Some(i)); + app.prev_setting(); + } + + // At index 0 of search settings, prev_setting should move to device settings last + assert_eq!(app.search_settings_list_state.selected(), Some(0)); + app.prev_setting(); assert!(app.search_settings_list_state.selected().is_none()); + + // NOTE: select_last() sets to usize::MAX until screen is rendered to know that it is actually 4 + assert_eq!(app.device_settings_list_state.selected(), Some(usize::MAX)); + // so let's cheat + app.device_settings_list_state.select(Some(4)); + + // Move through device settings (4..0) + for i in (1..=4).rev() { + app.prev_setting(); + assert_eq!(app.device_settings_list_state.selected(), Some(i - 1)); + } + + // At the first device setting, we should stay there + app.prev_setting(); assert_eq!(app.device_settings_list_state.selected(), Some(0)); } @@ -880,7 +931,7 @@ mod tests { let mut app = make_app(); app.set_state(State::Menu); app.device_settings_list_state.select(Some(0)); - let key_event = make_term_key_event(KEY_NAVIGATE); + let key_event = make_term_key_event(KEY_NAVIGATE_DOWN); let res = app.handle_term_event(key_event).await; assert!(res.is_ok(), "{:?}", res.err()); assert_eq!(app.device_settings_list_state.selected(), Some(1)); diff --git a/src/configure/app/tool_menu.rs b/src/configure/app/tool_menu.rs index e304b1ff..5fd4fa1f 100644 --- a/src/configure/app/tool_menu.rs +++ b/src/configure/app/tool_menu.rs @@ -1,9 +1,9 @@ use crate::configure::app::ir_enabler::App as IREnablerApp; -use crate::configure::ui::keys::{KEY_CONTINUE, KEY_EXIT, KEY_NAVIGATE}; +use crate::configure::app::tweaker::App as TweakerApp; use crate::configure::ui::tool_menu::ui; use anyhow::Result; -use crossterm::event; +use crossterm::event::{self, KeyCode}; use crossterm::event::{Event, KeyEventKind}; /// Application state for the tool menu. @@ -31,10 +31,10 @@ impl App { terminal.draw(|f| ui(f, self))?; match event::read()? { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { - match key_event.code.into() { - KEY_NAVIGATE => self.next_tool(), - KEY_CONTINUE => return self.start_tool(terminal).await, - KEY_EXIT => return Ok(""), + match key_event.code { + KeyCode::Tab => self.next_tool(), + KeyCode::Enter => return self.start_tool(terminal).await, + KeyCode::Esc => return Ok(""), _ => {} } } @@ -57,7 +57,7 @@ impl App { ) -> Result<&'static str> { match self.state { State::IREnablerSelected => IREnablerApp::new().run(terminal).await, - State::UVCTweakerSelected => anyhow::bail!("UVC Tweaker is not yet implemented"), + State::UVCTweakerSelected => TweakerApp::new().run(terminal).await, } } } diff --git a/src/configure/app/tweaker.rs b/src/configure/app/tweaker.rs new file mode 100644 index 00000000..6ee47ddb --- /dev/null +++ b/src/configure/app/tweaker.rs @@ -0,0 +1,451 @@ +use super::helper::*; +use crate::configure::ui::DeviceSettingsCtx; +use crate::configure::ui::tweaker::{TweakerCtx, View, ui}; +use crate::video::stream::{Image, Stream, grey_devices}; +use crate::video::uvc::{Device, XuControl}; + +use std::cell::OnceCell; +use std::path::PathBuf; + +use anyhow::{Context, Result, anyhow, bail}; +use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind}; +use futures::{FutureExt, StreamExt}; +use ratatui::{DefaultTerminal, widgets::ListState}; +use tokio::{ + select, + sync::mpsc, + sync::mpsc::{Receiver, Sender}, + task, +}; + +const KEY_YES: KeyCode = KeyCode::Char('y'); +const KEY_NO: KeyCode = KeyCode::Char('n'); +const KEY_EXIT: KeyCode = KeyCode::Esc; +const KEY_NAVIGATE: KeyCode = KeyCode::Tab; +const KEY_NAVIGATE_UP: KeyCode = KeyCode::Up; +const KEY_NAVIGATE_DOWN: KeyCode = KeyCode::Down; +const KEY_NAVIGATE_LEFT: KeyCode = KeyCode::Left; +const KEY_NAVIGATE_RIGHT: KeyCode = KeyCode::Right; +const KEY_CONTINUE: KeyCode = KeyCode::Enter; +const KEY_DELETE: KeyCode = KeyCode::Backspace; +// TODO add key to delete from the config + +#[derive(Debug)] +pub struct Config { + /// Path to the video device. + pub device: PathBuf, + /// Height of the video stream. + pub height: Option, + /// Width of the video stream. + pub width: Option, + /// Number of emitters to configure. + pub emitters: usize, + /// Frames per second of the video stream. + pub fps: Option, +} + +impl Default for Config { + fn default() -> Self { + Self { + device: PathBuf::from("/dev/video"), + height: None, + width: None, + emitters: 1, + fps: None, + } + } +} + +/// Application states. +#[derive(Debug)] +pub enum State { + /// Configuration settings menu + Menu, + /// Configuration is waiting + Waiting, + /// Actively editing a specific byte of a XuControl + Editing(usize, usize), + /// Confirm to save or not the configuration before exiting + SaveBeforeExit, + /// Exit + Exit, +} + +/// "Enabler" application. +pub struct App { + /// Receiver for captured images from the video stream task. + image_rx: Receiver, + /// Sender used by the video stream task for captured images. + image_tx: Option>, + /// Current application state. + /// Should be accessed through [`Self::state()`] + _state: State, + /// List of all the video devices that support grey scale pixel format. + grey_devices: Vec, + /// List of all the device controls that can be modified. + controls: Vec, + /// State of the list of device controls. + controls_list_state: ListState, + /// Settings for the whole application. + config: Config, + /// State of the list of device settings. + device_settings_list_state: ListState, + /// The last captured image from the video stream. + image: Option, + /// Device open to apply controls. Should be accessed through [`Self::get_device()`]. + _device: OnceCell, + /// The optional error that occurred during last control configuration. + error: Option, +} + +impl App { + /// Creates a new "Tweaker" application. + pub fn new() -> Self { + let mut device_settings_list_state = ListState::default(); + device_settings_list_state.select_first(); // already select the first item + + let (image_tx, image_rx) = mpsc::channel(1); // do not increase, we drop images to avoid "video lag" + let mut controls_list_state = ListState::default(); + controls_list_state.select_first(); + Self { + _state: State::Menu, + config: Config::default(), + device_settings_list_state, + image: None, + image_tx: Some(image_tx), + image_rx, + error: None, + controls: Vec::new(), + controls_list_state, + grey_devices: Vec::new(), + _device: OnceCell::new(), + } + } + + /// Initializes the application by detecting grey scale video devices. + /// Returns an error if no grey scale video device is found. + fn init(&mut self) -> Result<()> { + let grey_devices = grey_devices(); + log::debug!("Grey scale video devices: {:?}.", grey_devices); + if grey_devices.is_empty() { + bail!( + "no V4L grey scale video device found (the system probably does not support your infrared camera)" + ); + } else { + self.config.device = grey_devices[0].clone(); + self.grey_devices = grey_devices; + } + Ok(()) + } + + /// Returns the current application state. + pub fn state(&self) -> &State { + &self._state + } + + /// Sets the current application state and updates the previous state. + fn set_state(&mut self, state: State) { + self._state = state; + } + + /// Gets the video device specified in the config. + /// + /// If this is the first time, open it and load the list of controls. + /// + /// # Note + /// This operation is blocking. + fn get_device(&mut self) -> Result<&Device> { + match self._device.get() { + Some(device) => Ok(device), + None => { + let device = Device::open(&self.config.device) + .with_context(|| format!("failed to open device {:?}", self.config.device))?; + self.controls = device.controls(); + self._device.set(device).unwrap(); // safe because we checked that it was None + Ok(self._device.get().unwrap()) + } + } + } + + /// Applies a specific control to the active device. + fn apply_control(&mut self, control_index: usize) -> Result<()> { + let mut control = self.controls[control_index].clone(); + self.get_device()? + .apply_control(&mut control) + .context("failed to apply control") + } + + /// Spawns a new asynchronous task that continuously captures video frames from the stream device + /// and sends them through [`Self::image_tx`]. + /// + /// Returns an error if the video stream is already started. + fn spawn_video_stream_task(&mut self) -> Result<()> { + let device = self.config.device.clone(); + let width = self.config.width; + let height = self.config.height; + let fps = self.config.fps; + + // we take the sender, so if the task exit, the receiver will be notified + let image_tx = self + .image_tx + .take() + .context("video stream task already started")?; + + task::spawn_blocking(move || { + let mut stream = match Stream::open(device, width, height, fps) { + Ok(s) => s, + Err(err) => { + log::error!("Error opening video stream: {err:?}"); + return; + } + }; + log::debug!("{stream}"); + + loop { + match stream.capture() { + Ok(img) => { + if image_tx.blocking_send(img).is_err() { + log::debug!("Exiting video stream task."); + return; + } + } + Err(err) => { + log::error!("Error while capturing image: {err:?}"); + return; + } + } + } + }); + + Ok(()) + } + + /// Runs the application. + pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<&'static str> { + self.init()?; + let mut term_event_stream = EventStream::new(); + + // Draw the UI then wait an event in loop + loop { + terminal.draw(|f| ui(f, self))?; + select! { + Some(event) = term_event_stream.next().fuse() => self.handle_term_event(event?).await?, + image = self.image_rx.recv() => self.handle_image(image)?, + } + + if matches!(self.state(), State::Exit) { + return Ok("Tweaker exited successfully."); + } + } + } + + /// Handles terminal events. + async fn handle_term_event(&mut self, event: Event) -> Result<()> { + match event { + Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + self.handle_key_press(key_event.code).await?; + } + _ => {} + }; + Ok(()) + } + + /// Handles a new captured image. + /// + /// Returns an error if the image is None. + fn handle_image(&mut self, image: Option) -> Result<()> { + if let Some(image) = image { + self.image = Some(image); + Ok(()) + } else { + Err(anyhow!("the video stream has been closed unexpectedly")) + } + } + + /// Handles a key event based on the current application state. + async fn handle_key_press(&mut self, key: KeyCode) -> Result<()> { + if self.error.is_some() { + self.error = None; + return Ok(()); + } + match self.state() { + State::Menu => match key { + KEY_EXIT => self.set_state(State::Exit), + KEY_NAVIGATE | KEY_NAVIGATE_DOWN => self.next_setting(), + KEY_NAVIGATE_UP => self.prev_setting(), + KEY_DELETE => self.edit_setting(None), + KeyCode::Char(c) => self.edit_setting(Some(c)), + KEY_CONTINUE => self.start_or_back()?, + _ => {} + }, + State::Waiting => { + if key == KEY_EXIT { + self.set_state(State::SaveBeforeExit) + } else if key == KEY_CONTINUE { + if let Some(selected) = self.controls_list_state.selected() + && selected < self.controls.len() + { + self.set_state(State::Editing(selected, 0)); + } + } else if key == KEY_NAVIGATE || key == KEY_NAVIGATE_DOWN { + self.controls_list_state.select_next(); + } else if key == KEY_NAVIGATE_UP { + self.controls_list_state.select_previous(); + } + } + State::Editing(c_ptr, b_ptr) => { + let c_idx = *c_ptr; + let mut b_idx = *b_ptr; + let cur_len = self.controls[c_idx].cur().len(); + + if key == KEY_EXIT || key == KEY_CONTINUE { + self.set_state(State::Waiting); + } else if key == KEY_NAVIGATE_LEFT { + if b_idx > 0 { + b_idx -= 1; + self.set_state(State::Editing(c_idx, b_idx)); + } + } else if key == KEY_NAVIGATE_RIGHT { + if b_idx + 1 < cur_len { + b_idx += 1; + self.set_state(State::Editing(c_idx, b_idx)); + } + } else if key == KEY_NAVIGATE_UP || key == KEY_NAVIGATE_DOWN { + let control = &mut self.controls[c_idx]; + let current_val = control.cur()[b_idx]; + + let max_val = control.max().map_or(255, |m| *m.get(b_idx).unwrap_or(&255)); + + let new_val = match key { + KEY_NAVIGATE_UP => current_val.saturating_add(1).min(max_val), + _ => current_val.saturating_sub(1), + }; + + control.cur_mut()[b_idx] = new_val; + if let Err(e) = self.apply_control(c_idx) { + self.error = Some(e.to_string()); + self.controls[c_idx].cur_mut()[b_idx] = current_val; + } + } + } + State::SaveBeforeExit => self.save_before_exit(key).await?, + _ => {} + } + Ok(()) + } + + /// Set the state to exit, if: + /// the key is [`KEY_YES`], save the current configuration, + /// or the key is [`KEY_NO`], discard the current configuration + /// + /// For any other key, set the state back to [`State::Waiting`]. + async fn save_before_exit(&mut self, k: KeyCode) -> Result<()> { + // TODO + match k { + KEY_NO => (), + KEY_YES => (), + _ => { + self.set_state(State::Waiting); + return Ok(()); + } + } + self.set_state(State::Exit); + Ok(()) + } + + /// If the configuration is valid, tries to spawn the video stream task + /// and change the state to [`State::Waiting`]. If it fails, returns the error. + /// + /// Returns directly an error if the video stream is already started. + fn start_or_back(&mut self) -> Result<()> { + // check that the path exists + if !self.is_device_valid() { + return Ok(()); + } + self.spawn_video_stream_task()?; + self.get_device()?.controls(); + self.set_state(State::Waiting); + Ok(()) + } + + /// Check that the current device is grey scale. + fn is_device_valid(&self) -> bool { + self.grey_devices.contains(&self.config.device) + } + + /// Moves the selection to the next setting in the settings lists. + fn next_setting(&mut self) { + self.device_settings_list_state.select_next(); + } + + /// Moves the selection to the previous setting in the settings lists. + fn prev_setting(&mut self) { + self.device_settings_list_state.select_previous(); + } + + /// Edits the currently selected setting in the settings lists by adding or removing a character. + /// + /// If `ch` is `Some(c)`, adds the character `c` (depending on the setting type). + /// If `ch` is `None`, removes the last character. + fn edit_setting(&mut self, ch: Option) { + match self.device_settings_list_state.selected() { + Some(0) => add_or_remove_char_from_path(&mut self.config.device, ch, 10), + Some(1) => add_or_remove_char_from_numeric(&mut self.config.emitters, ch), + Some(2) => add_or_remove_char_from_opt_numeric(&mut self.config.height, ch), + Some(3) => add_or_remove_char_from_opt_numeric(&mut self.config.width, ch), + Some(4) => add_or_remove_char_from_opt_numeric(&mut self.config.fps, ch), + _ => {} + } + } +} + +impl TweakerCtx for App { + fn view(&self) -> View<'_> { + match self.state() { + State::Menu => View::Menu, + State::Waiting => View::Main, + State::SaveBeforeExit => View::Main, + State::Exit => View::Menu, + State::Editing(index, byte_index) => View::Edition(&self.controls[*index], *byte_index), + } + } + fn controls_list_state(&mut self) -> &mut ListState { + &mut self.controls_list_state + } + fn controls(&self) -> &[XuControl] { + &self.controls + } + fn image(&self) -> Option<&Image> { + self.image.as_ref() + } + fn show_save_exit_prompt(&self) -> bool { + matches!(self.state(), State::SaveBeforeExit) + } + fn error_message(&self) -> Option<&String> { + self.error.as_ref() + } +} + +impl DeviceSettingsCtx for App { + fn device_settings_list_state(&mut self) -> &mut ListState { + &mut self.device_settings_list_state + } + fn device_valid(&self) -> (String, bool) { + ( + self.config.device.to_string_lossy().to_string(), + self.is_device_valid(), + ) + } + fn height(&self) -> Option { + self.config.height + } + fn width(&self) -> Option { + self.config.width + } + fn emitters(&self) -> usize { + self.config.emitters + } + fn fps(&self) -> Option { + self.config.fps + } +} diff --git a/src/configure/snapshots/linux_enable_ir_emitter__configure__ui__tests__render_keys.snap b/src/configure/snapshots/linux_enable_ir_emitter__configure__ui__tests__render_keys.snap index 8e9e3e15..2b435bc0 100644 --- a/src/configure/snapshots/linux_enable_ir_emitter__configure__ui__tests__render_keys.snap +++ b/src/configure/snapshots/linux_enable_ir_emitter__configure__ui__tests__render_keys.snap @@ -3,7 +3,7 @@ source: src/configure/ui.rs expression: terminal.backend() --- "Quit " -"Navigate " +"Navigate <↑↓> " "Continue " "Yes " "No " diff --git a/src/configure/ui.rs b/src/configure/ui.rs index 68174193..f14ec199 100644 --- a/src/configure/ui.rs +++ b/src/configure/ui.rs @@ -3,55 +3,61 @@ use helper::*; pub mod ir_enabler; mod shared; use shared::*; +pub use shared::{DeviceSettingsCtx, SearchSettingsCtx}; pub mod tool_menu; +pub mod tweaker; -pub mod keys { +mod keys { use crossterm::event::KeyCode; - use derive_more::{From, Into}; use ratatui::{ style::Stylize, style::{Color, Style}, text::{Line, Span}, }; - #[derive(Debug, Clone, Copy, PartialEq, Eq, From, Into)] - pub(crate) struct Key(pub KeyCode); + #[derive(Debug, Clone, Copy)] + enum KeyRepr { + Code(KeyCode), + Str(&'static str), + } + + #[derive(Debug, Clone, Copy)] + pub struct Key { + repr: KeyRepr, + name: &'static str, + color: Color, + } + + impl Key { + const fn new(code: KeyCode, name: &'static str, color: Color) -> Self { + Self { + repr: KeyRepr::Code(code), + name, + color, + } + } + + const fn custom(repr: &'static str, name: &'static str, color: Color) -> Self { + Self { + repr: KeyRepr::Str(repr), + name, + color, + } + } + } pub fn keys_to_line(keys: &[Key]) -> Line<'static> { let mut spans = Vec::with_capacity(keys.len() * 3); for (i, key) in keys.iter().enumerate() { - match key.0 { - KeyCode::Esc => { - spans.push("Quit <".bold()); - spans.push(Span::styled("Esc", Style::default().fg(Color::Red))); - spans.push(">".bold()); - } - KeyCode::Tab => { - spans.push("Navigate <".bold()); - spans.push(Span::styled("Tab", Style::default().fg(Color::Yellow))); - spans.push(">".bold()); - } - KeyCode::Enter => { - spans.push("Continue <".bold()); - spans.push(Span::styled("Enter", Style::default().fg(Color::Green))); - spans.push(">".bold()); - } - KeyCode::Char('y') => { - spans.push("Yes <".bold()); - spans.push(Span::styled("y", Style::default().fg(Color::Green))); - spans.push(">".bold()); - } - KeyCode::Char('n') => { - spans.push("No <".bold()); - spans.push(Span::styled("n", Style::default().fg(Color::Red))); - spans.push(">".bold()); - } - _ => { - spans.push("? <".bold()); - spans.push(Span::raw(format!("{:?}", key.0))); - spans.push(">".bold()); - } - } + spans.push(format!("{} <", key.name).bold()); + spans.push(Span::styled( + match key.repr { + KeyRepr::Code(code) => code.to_string(), + KeyRepr::Str(s) => s.to_string(), + }, + Style::default().fg(key.color), + )); + spans.push(">".bold()); if i != keys.len() - 1 { spans.push(Span::raw(" ")); @@ -60,12 +66,14 @@ pub mod keys { Line::from(spans) } - pub const KEY_EXIT: Key = Key(KeyCode::Esc); - pub const KEY_NAVIGATE: Key = Key(KeyCode::Tab); - pub const KEY_CONTINUE: Key = Key(KeyCode::Enter); - pub const KEY_YES: Key = Key(KeyCode::Char('y')); - pub const KEY_NO: Key = Key(KeyCode::Char('n')); - pub const KEY_DELETE: Key = Key(KeyCode::Backspace); + pub const KEY_EXIT: Key = Key::new(KeyCode::Esc, "Quit", Color::Red); + pub const KEYS_NAVIGATE: Key = Key::custom("↑↓", "Navigate", Color::Yellow); + pub const KEYS_EDITING_NAVIGATE: Key = Key::custom("←↑↓→", "Navigate/Modify", Color::Yellow); + pub const KEY_NAVIGATE: Key = Key::new(KeyCode::Tab, "Navigate", Color::Yellow); + pub const KEY_CONTINUE: Key = Key::new(KeyCode::Enter, "Continue", Color::Green); + pub const KEY_YES: Key = Key::new(KeyCode::Char('y'), "Yes", Color::Green); + pub const KEY_NO: Key = Key::new(KeyCode::Char('n'), "No", Color::Red); + pub const KEY_EDIT: Key = Key::new(KeyCode::Enter, "Edit", Color::Green); } #[cfg(test)] @@ -77,7 +85,7 @@ mod tests { #[test] fn test_render_keys() { assert_ui_snapshot!(|frame| { - let keys = [KEY_EXIT, KEY_NAVIGATE, KEY_CONTINUE, KEY_YES, KEY_NO]; + let keys = [KEY_EXIT, KEYS_NAVIGATE, KEY_CONTINUE, KEY_YES, KEY_NO]; let chunks = Layout::vertical(vec![Constraint::Length(1); keys.len()]).split(frame.area()); diff --git a/src/configure/ui/ir_enabler.rs b/src/configure/ui/ir_enabler.rs index 7f7231ef..c0c54b1a 100644 --- a/src/configure/ui/ir_enabler.rs +++ b/src/configure/ui/ir_enabler.rs @@ -1,12 +1,13 @@ use super::{ - keys::{KEY_CONTINUE, KEY_EXIT, KEY_NAVIGATE, KEY_NO, KEY_YES, keys_to_line}, - popup_area, render_main_window, render_video_preview, + DeviceSettingsCtx, SearchSettingsCtx, + keys::{KEY_CONTINUE, KEY_EXIT, KEY_NO, KEY_YES, KEYS_NAVIGATE, keys_to_line}, + popup_area, render_full_menu, render_main_window, render_video_preview, }; use crate::video::stream::Image; use ratatui::{ Frame, - layout::{Constraint, Flex, Layout, Rect}, + layout::{Constraint, Layout, Rect}, style::{Color, Style, Stylize}, text::{Line, Text, ToLine}, widgets::{Block, BorderType, Clear, HighlightSpacing, List, ListItem, ListState, Paragraph}, @@ -25,20 +26,6 @@ pub trait IrEnablerCtx { fn show_main_abort_prompt(&self) -> bool; fn show_menu_start_prompt(&self) -> bool; - fn device_settings_list_state(&mut self) -> &mut ListState; - fn device_valid(&self) -> (String, bool); - fn height(&self) -> Option; - fn width(&self) -> Option; - fn emitters(&self) -> usize; - fn fps(&self) -> Option; - - fn search_settings_list_state(&mut self) -> &mut ListState; - fn limit(&self) -> Option; - fn manual(&self) -> bool; - fn analyzer_img_count(&self) -> u64; - fn ref_intensity_var_coef(&self) -> u64; - fn inc_step(&self) -> u8; - fn controls_list_state(&mut self) -> &mut ListState; fn controls(&self) -> &[crate::video::uvc::XuControl]; fn image(&self) -> Option<&Image>; @@ -86,115 +73,6 @@ fn render_confirm_abort_popup(frame: &mut Frame, area: Rect) { frame.render_widget(command_paragraph, command_area); } -/// Renders the main menu with device and search settings. -fn render_menu(frame: &mut Frame, area: Rect, app: &mut A) -where - A: IrEnablerCtx, -{ - let [top_info_area, device_area, search_area, bot_info_area] = Layout::vertical([ - Constraint::Length(1), - Constraint::Length(7), - Constraint::Length(7), - Constraint::Length(1), - ]) - .flex(Flex::Center) - .areas(area); - - let top_info_line = - Line::from("The tool will iterate through the UVC camera controls and modify them.") - .blue() - .centered(); - frame.render_widget(top_info_line, top_info_area); - - let bot_info_line = Line::from(vec![ - "For more explanation, visit ".into(), - "https://github.com/EmixamPP/linux-enable-ir-emitter".underlined(), - ]) - .blue() - .centered(); - frame.render_widget(bot_info_line, bot_info_area); - - let device_block = - Block::bordered().title(Line::from(" Device settings ".bold()).left_aligned()); - let device_settings_list = List::new(vec![ - ListItem::new(Line::from(vec!["Path: ".into(), { - let (device, valid_device) = app.device_valid(); - if valid_device { - device.green() - } else { - format!("{device} (not a grey camera device)").red() - } - }])), - ListItem::new(Line::from(vec![ - "Number of emitters: ".into(), - app.emitters().to_string().green(), - ])), - ListItem::new(Line::from(vec![ - "Resolution height: ".into(), - app.height() - .map_or("auto".to_string(), |h| h.to_string()) - .green(), - ])), - ListItem::new(Line::from(vec![ - "Resolution width: ".into(), - app.width() - .map_or("auto".to_string(), |w| w.to_string()) - .green(), - ])), - ListItem::new(Line::from(vec![ - "FPS: ".into(), - app.fps() - .map_or("auto".to_string(), |f| f.to_string()) - .green(), - ])), - ]) - .highlight_style(Style::default().fg(Color::Yellow)) - .highlight_symbol(">") - .highlight_spacing(HighlightSpacing::Always) - .block(device_block); - frame.render_stateful_widget( - device_settings_list, - device_area, - app.device_settings_list_state(), - ); - - let search_block = Block::bordered() - .title(Line::from(vec![" Search settings".bold(), " (advanced) ".dim()]).left_aligned()); - let search_settings_list = List::new(vec![ - ListItem::new(Line::from(vec![ - "Manual validation: ".into(), - app.manual().to_string().green(), - ])), - ListItem::new(Line::from(vec![ - "Images to analyze in auto validation: ".into(), - app.analyzer_img_count().to_string().green(), - ])), - ListItem::new(Line::from(vec![ - "Light difference significance factor: ".into(), - app.ref_intensity_var_coef().to_string().green(), - ])), - ListItem::new(Line::from(vec![ - "Rejection threshold per control: ".into(), - app.limit() - .map_or("none".to_string(), |w| w.to_string()) - .green(), - ])), - ListItem::new(Line::from(vec![ - "Increment step: ".into(), - app.inc_step().to_string().green(), - ])), - ]) - .highlight_style(Style::default().fg(Color::Yellow)) - .highlight_symbol(">") - .highlight_spacing(HighlightSpacing::Always) - .block(search_block); - frame.render_stateful_widget( - search_settings_list, - search_area, - app.search_settings_list_state(), - ); -} - /// Renders the main application interface showing the camera preview and the list of controls, /// as well as extra information for the current control. /// @@ -259,12 +137,17 @@ where /// Renders the application UI based on the current application state. pub fn ui(frame: &mut Frame, app: &mut A) where - A: IrEnablerCtx, + A: IrEnablerCtx + DeviceSettingsCtx + SearchSettingsCtx, { match app.view() { View::Menu => { - let main_area = render_main_window(frame, &[KEY_NAVIGATE, KEY_CONTINUE, KEY_EXIT]); - render_menu(frame, main_area, app); + let main_area = render_main_window(frame, &[KEYS_NAVIGATE, KEY_CONTINUE, KEY_EXIT]); + render_full_menu( + frame, + main_area, + app, + "The tool will iterate through the UVC camera controls and modify them.", + ); if app.show_menu_start_prompt() { render_confirm_start_popup(frame, main_area); } @@ -320,6 +203,18 @@ mod tests { fn show_menu_start_prompt(&self) -> bool { self.show_menu_start_prompt } + fn controls_list_state(&mut self) -> &mut ListState { + &mut self.controls_list_state + } + fn controls(&self) -> &[crate::video::uvc::XuControl] { + &self.controls + } + fn image(&self) -> Option<&Image> { + self.image.as_ref() + } + } + + impl DeviceSettingsCtx for App { fn device_settings_list_state(&mut self) -> &mut ListState { &mut self.device_settings_list_state } @@ -338,6 +233,9 @@ mod tests { fn fps(&self) -> Option { self.fps } + } + + impl SearchSettingsCtx for App { fn search_settings_list_state(&mut self) -> &mut ListState { &mut self.search_settings_list_state } @@ -356,15 +254,6 @@ mod tests { fn inc_step(&self) -> u8 { self.inc_step } - fn controls_list_state(&mut self) -> &mut ListState { - &mut self.controls_list_state - } - fn controls(&self) -> &[crate::video::uvc::XuControl] { - &self.controls - } - fn image(&self) -> Option<&Image> { - self.image.as_ref() - } } #[test] diff --git a/src/configure/ui/shared.rs b/src/configure/ui/shared.rs index 0c237557..45370d1f 100644 --- a/src/configure/ui/shared.rs +++ b/src/configure/ui/shared.rs @@ -4,10 +4,10 @@ use crate::video::stream::Image; use ansi_to_tui::IntoText as _; use ratatui::{ Frame, - layout::{Constraint, Layout, Rect}, - style::Stylize, + layout::{Constraint, Flex, Layout, Rect}, + style::{Color, Style, Stylize}, text::Line, - widgets::{Block, BorderType, Borders, Paragraph}, + widgets::{Block, BorderType, Borders, HighlightSpacing, List, ListItem, ListState, Paragraph}, }; pub fn render_main_window(frame: &mut Frame, commands: &[keys::Key]) -> Rect { @@ -44,6 +44,157 @@ pub fn render_video_preview(frame: &mut Frame, area: Rect, img: Option<&Image>) frame.render_widget(video_block, area); } +pub trait DeviceSettingsCtx { + fn device_settings_list_state(&mut self) -> &mut ListState; + fn device_valid(&self) -> (String, bool); + fn height(&self) -> Option; + fn width(&self) -> Option; + fn emitters(&self) -> usize; + fn fps(&self) -> Option; +} + +pub trait SearchSettingsCtx { + fn search_settings_list_state(&mut self) -> &mut ListState; + fn limit(&self) -> Option; + fn manual(&self) -> bool; + fn analyzer_img_count(&self) -> u64; + fn ref_intensity_var_coef(&self) -> u64; + fn inc_step(&self) -> u8; +} + +/// Renders the device settings section of the menu. +fn render_device_settings(frame: &mut Frame, area: Rect, app: &mut A) +where + A: DeviceSettingsCtx, +{ + let device_block = + Block::bordered().title(Line::from(" Device settings ".bold()).left_aligned()); + let device_settings_list = List::new(vec![ + ListItem::new(Line::from(vec!["Path: ".into(), { + let (device, valid_device) = app.device_valid(); + if valid_device { + device.green() + } else { + format!("{device} (not a grey camera device)").red() + } + }])), + ListItem::new(Line::from(vec![ + "Number of emitters: ".into(), + app.emitters().to_string().green(), + ])), + ListItem::new(Line::from(vec![ + "Resolution height: ".into(), + app.height() + .map_or("auto".to_string(), |h| h.to_string()) + .green(), + ])), + ListItem::new(Line::from(vec![ + "Resolution width: ".into(), + app.width() + .map_or("auto".to_string(), |w| w.to_string()) + .green(), + ])), + ListItem::new(Line::from(vec![ + "FPS: ".into(), + app.fps() + .map_or("auto".to_string(), |f| f.to_string()) + .green(), + ])), + ]) + .highlight_style(Style::default().fg(Color::Yellow)) + .highlight_symbol(">") + .highlight_spacing(HighlightSpacing::Always) + .block(device_block); + frame.render_stateful_widget(device_settings_list, area, app.device_settings_list_state()); +} + +/// Renders the search settings section of the menu. +fn render_search_settings(frame: &mut Frame, area: Rect, app: &mut A) +where + A: SearchSettingsCtx, +{ + let search_block = Block::bordered() + .title(Line::from(vec![" Search settings".bold(), " (advanced) ".dim()]).left_aligned()); + let search_settings_list = List::new(vec![ + ListItem::new(Line::from(vec![ + "Manual validation: ".into(), + app.manual().to_string().green(), + ])), + ListItem::new(Line::from(vec![ + "Images to analyze in auto validation: ".into(), + app.analyzer_img_count().to_string().green(), + ])), + ListItem::new(Line::from(vec![ + "Light difference significance factor: ".into(), + app.ref_intensity_var_coef().to_string().green(), + ])), + ListItem::new(Line::from(vec![ + "Rejection threshold per control: ".into(), + app.limit() + .map_or("none".to_string(), |w| w.to_string()) + .green(), + ])), + ListItem::new(Line::from(vec![ + "Increment step: ".into(), + app.inc_step().to_string().green(), + ])), + ]) + .highlight_style(Style::default().fg(Color::Yellow)) + .highlight_symbol(">") + .highlight_spacing(HighlightSpacing::Always) + .block(search_block); + frame.render_stateful_widget(search_settings_list, area, app.search_settings_list_state()); +} + +/// Renders the info lines of the menu. +fn render_info(frame: &mut Frame, top_info_area: Rect, top_info: &str, bot_info_area: Rect) { + let top_info_line = Line::from(top_info).blue().centered(); + frame.render_widget(top_info_line, top_info_area); + + let bot_info_line = Line::from(vec![ + "For more explanation, visit ".into(), + "https://github.com/EmixamPP/linux-enable-ir-emitter".underlined(), + ]) + .blue() + .centered(); + frame.render_widget(bot_info_line, bot_info_area); +} + +/// Renders the main menu with device and search settings. +pub fn render_full_menu(frame: &mut Frame, area: Rect, app: &mut A, top_info: &str) +where + A: DeviceSettingsCtx + SearchSettingsCtx, +{ + let [top_info_area, device_area, search_area, bot_info_area] = Layout::vertical([ + Constraint::Length(1), + Constraint::Length(7), + Constraint::Length(7), + Constraint::Length(1), + ]) + .flex(Flex::Center) + .areas(area); + + render_info(frame, top_info_area, top_info, bot_info_area); + render_device_settings(frame, device_area, app); + render_search_settings(frame, search_area, app); +} + +/// Renders the main menu with only the device settings. +pub fn render_device_menu(frame: &mut Frame, area: Rect, app: &mut A, top_info: &str) +where + A: DeviceSettingsCtx, +{ + let [top_info_area, device_area, bot_info_area] = Layout::vertical([ + Constraint::Length(1), + Constraint::Length(7), + Constraint::Length(1), + ]) + .flex(Flex::Center) + .areas(area); + + render_info(frame, top_info_area, top_info, bot_info_area); + render_device_settings(frame, device_area, app); +} #[cfg(test)] mod tests { use super::*; @@ -54,7 +205,7 @@ mod tests { assert_ui_snapshot!(|frame| { let _ = render_main_window( frame, - &[keys::KEY_NAVIGATE, keys::KEY_CONTINUE, keys::KEY_EXIT], + &[keys::KEYS_NAVIGATE, keys::KEY_CONTINUE, keys::KEY_EXIT], ); }); } diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_empty.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_empty.snap index ed5fa54e..7765e8b1 100644 --- a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_empty.snap +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_empty.snap @@ -30,5 +30,5 @@ expression: terminal.backend() "┃ ┃" "┃ ┃" "┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" -"┃ Navigate Continue Quit ┃" +"┃ Navigate <↑↓> Continue Quit ┃" "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_strart.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_strart.snap index 2f8226f7..80a3a1ac 100644 --- a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_strart.snap +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_strart.snap @@ -30,5 +30,5 @@ expression: terminal.backend() "┃ ┃" "┃ ┃" "┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" -"┃ Navigate Continue Quit ┃" +"┃ Navigate <↑↓> Continue Quit ┃" "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_valid_values.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_valid_values.snap index 8557adc4..5eb916f0 100644 --- a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_valid_values.snap +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__ir_enabler__tests__menu_valid_values.snap @@ -30,5 +30,5 @@ expression: terminal.backend() "┃ ┃" "┃ ┃" "┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" -"┃ Navigate Continue Quit ┃" +"┃ Navigate <↑↓> Continue Quit ┃" "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__shared__tests__render_main_window.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__shared__tests__render_main_window.snap index 58918af7..f7ab0fb1 100644 --- a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__shared__tests__render_main_window.snap +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__shared__tests__render_main_window.snap @@ -30,5 +30,5 @@ expression: terminal.backend() "┃ ┃" "┃ ┃" "┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" -"┃ Navigate Continue Quit ┃" +"┃ Navigate <↑↓> Continue Quit ┃" "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_empty.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_empty.snap new file mode 100644 index 00000000..b4571088 --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_empty.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃┌ UVC Controls ─────────────────────────────────┐┌ Camera Preview ───────────────────────────────┐┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃└───────────────────────────────────────────────┘└───────────────────────────────────────────────┘┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Edit Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_abort_prompt.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_abort_prompt.snap new file mode 100644 index 00000000..e6286bf5 --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_abort_prompt.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃┌ UVC Controls ─────────────────────────────────┐┌ Camera Preview ───────────────────────────────┐┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ╔════════════════════════════════════════════════╗ │┃" +"┃│ ║ Do you want to save this configuration? ║ │┃" +"┃│ ║ Yes No ║ │┃" +"┃│ ╚════════════════════════════════════════════════╝ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃└───────────────────────────────────────────────┘└───────────────────────────────────────────────┘┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Edit Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_controls.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_controls.snap new file mode 100644 index 00000000..b18c023d --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_controls.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃┌ UVC Controls ─────────────────────────────────┐┌ Camera Preview ───────────────────────────────┐┃" +"┃│unit: 1 selector: 1 ││ │┃" +"┃│ initial: [1] ││ │┃" +"┃│ current: [1] ││ │┃" +"┃│ maximum: [1] ││ │┃" +"┃│unit: 2 selector: 2 ││ │┃" +"┃│ initial: [2, 2] ││ │┃" +"┃│ current: [2, 2] ││ │┃" +"┃│ maximum: [2, 2] ││ │┃" +"┃│unit: 3 selector: 3 ││ │┃" +"┃│ initial: [3, 3, 3] ││ │┃" +"┃│ current: [3, 3, 3] ││ │┃" +"┃│ maximum: [3, 3, 3] ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃└───────────────────────────────────────────────┘└───────────────────────────────────────────────┘┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Edit Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_image.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_image.snap new file mode 100644 index 00000000..d2c0a7f4 --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_image.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃┌ UVC Controls ─────────────────────────────────┐┌ Camera Preview ───────────────────────────────┐┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ ▄ ▄▄ ▄▄ │┃" +"┃│ ││ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄ │┃" +"┃│ ││ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄ │┃" +"┃│ ││ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄ │┃" +"┃│ ││ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄ │┃" +"┃│ ││ ▄ ▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄ ▄ │┃" +"┃│ ││ ▄▄ ▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄ │┃" +"┃│ ││ ▄▄▄▄▄▄ ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀ ▄▄▄▄▄▀ │┃" +"┃│ ││ ▀▄▄▄▄▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▄▄▄▀ │┃" +"┃│ ││ ▀▀▄▄ ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀ │┃" +"┃│ ││ ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀ │┃" +"┃│ ││ ▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ │┃" +"┃│ ││ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ │┃" +"┃│ ││ ▀▄▄▄▀▄▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀▀▄▀▄▄▀ │┃" +"┃│ ││ ▀▄▄ ▀▄▀▀▀▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀▀▀ ▄▀▄▄▀ │┃" +"┃│ ││ ▀▄▄ ▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▄▀ ▄▀ │┃" +"┃│ ││ ▀▀▄ ▀ ▄▀ │┃" +"┃│ ││ ▀▄ ▀ │┃" +"┃│ ││ ▀ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃└───────────────────────────────────────────────┘└───────────────────────────────────────────────┘┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Edit Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_question.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_question.snap new file mode 100644 index 00000000..73bb8e7d --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__main_with_question.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃┌ Modifiable UVC Controls ──────────────────────┐┌ Camera Preview ───────────────────────────────┐┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃│ ││ │┃" +"┃└───────────────────────────────────────────────┘└───────────────────────────────────────────────┘┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_empty.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_empty.snap new file mode 100644 index 00000000..1c992244 --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_empty.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ The tool allows you to modify the UVC camera controls. ┃" +"┃┌ Device settings ───────────────────────────────────────────────────────────────────────────────┐┃" +"┃│ Path: (not a grey camera device) │┃" +"┃│ Number of emitters: 0 │┃" +"┃│ Resolution height: auto │┃" +"┃│ Resolution width: auto │┃" +"┃│ FPS: auto │┃" +"┃└────────────────────────────────────────────────────────────────────────────────────────────────┘┃" +"┃ For more explanation, visit https://github.com/EmixamPP/linux-enable-ir-emitter ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Continue Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_valid_values.snap b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_valid_values.snap new file mode 100644 index 00000000..3dbd6eaf --- /dev/null +++ b/src/configure/ui/snapshots/linux_enable_ir_emitter__configure__ui__tweaker__tests__menu_valid_values.snap @@ -0,0 +1,34 @@ +--- +source: src/configure/ui/tweaker.rs +expression: terminal.backend() +--- +"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ linux-enable-ir-emitter ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ The tool allows you to modify the UVC camera controls. ┃" +"┃┌ Device settings ───────────────────────────────────────────────────────────────────────────────┐┃" +"┃│ Path: /dev/video2 │┃" +"┃│ Number of emitters: 1 │┃" +"┃│ Resolution height: 720 │┃" +"┃│ Resolution width: 1280 │┃" +"┃│ FPS: 30 │┃" +"┃└────────────────────────────────────────────────────────────────────────────────────────────────┘┃" +"┃ For more explanation, visit https://github.com/EmixamPP/linux-enable-ir-emitter ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃ ┃" +"┃──────────────────────────────────────────────────────────────────────────────────────────────────┃" +"┃ Navigate Continue Quit ┃" +"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" diff --git a/src/configure/ui/tweaker.rs b/src/configure/ui/tweaker.rs new file mode 100644 index 00000000..648d9d83 --- /dev/null +++ b/src/configure/ui/tweaker.rs @@ -0,0 +1,359 @@ +use super::{ + keys::{ + KEY_CONTINUE, KEY_EDIT, KEY_EXIT, KEY_NAVIGATE, KEY_NO, KEY_YES, KEYS_EDITING_NAVIGATE, + keys_to_line, + }, + popup_area, render_device_menu, render_main_window, render_video_preview, +}; +use crate::video::uvc::XuControl; +use crate::{configure::ui::DeviceSettingsCtx, video::stream::Image}; + +use ratatui::{ + Frame, + layout::{Constraint, Layout, Rect}, + style::{Color, Style, Stylize}, + text::{Line, Span}, + widgets::{Block, BorderType, Clear, HighlightSpacing, List, ListItem, ListState, Paragraph}, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub enum View<'a> { + /// Device menu view + #[default] + Menu, + /// Main view with configuration mode waiting + Main, + /// Main view, editing a specific byte of a control + Edition(&'a XuControl, usize), +} + +pub trait TweakerCtx { + fn view(&self) -> View<'_>; + fn show_save_exit_prompt(&self) -> bool; + + fn controls_list_state(&mut self) -> &mut ListState; + fn controls(&self) -> &[crate::video::uvc::XuControl]; + + fn image(&self) -> Option<&Image>; + fn error_message(&self) -> Option<&String>; +} + +/// Renders a confirmation popup to exit the process without saving. +fn render_save_exit_popup(frame: &mut Frame, area: Rect) { + let block = Block::bordered().border_type(BorderType::Double); + let area = popup_area(area, 4, 50); + frame.render_widget(Clear, area); + frame.render_widget(block, area); + + let [info_area, command_area] = + Layout::vertical([Constraint::Length(1), Constraint::Length(1)]) + .margin(1) + .areas(area); + + let paragraph = Paragraph::new("Do you want to save this configuration?").centered(); + frame.render_widget(paragraph, info_area); + + let command_line = keys_to_line(&[KEY_YES, KEY_NO]); + let command_paragraph = Paragraph::new(command_line).centered(); + frame.render_widget(command_paragraph, command_area); +} + +/// Renders the main application interface showing the camera preview and the list of controls, +/// as well as extra information for the current control. +/// +/// Returns the area used for the list of controls. +fn render_main(frame: &mut Frame, area: Rect, app: &mut A) +where + A: TweakerCtx, +{ + let [list_area, video_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(area); + + let list_block = Block::bordered().title(" UVC Controls ".bold()); + let selected_index = app.controls_list_state().selected(); + let current_view = app.view(); + + let items: Vec = app + .controls() + .iter() + .enumerate() + .map(|(i, ctrl)| { + let is_selected = selected_index == Some(i); + let is_editing = matches!( + current_view, + View::Edition(edited_ctrl, _) + if edited_ctrl.unit() == ctrl.unit() + && edited_ctrl.selector() == ctrl.selector() + ); + + let mut lines = vec![ + Line::from(format!( + "unit: {} selector: {}", + ctrl.unit(), + ctrl.selector() + )) + .style(if is_editing { + Style::default().fg(Color::Yellow) + } else if is_selected { + Style::default().fg(Color::Cyan) + } else { + Style::default() + }), + ]; + + let (initial_line, current_line, maximum_line_opt) = match current_view { + View::Edition(c, byte_index) + if c.unit() == ctrl.unit() && c.selector() == ctrl.selector() => + { + let mut cur_spans = vec![Span::raw("[")]; + for (j, v) in ctrl.cur().iter().enumerate() { + if j == byte_index { + cur_spans.push(Span::styled( + v.to_string(), + Style::default() + .add_modifier(ratatui::style::Modifier::REVERSED) + .fg(Color::Yellow), + )); + } else { + cur_spans.push(Span::raw(v.to_string())); + } + if j < ctrl.cur().len() - 1 { + cur_spans.push(Span::raw(", ")); + } + } + cur_spans.push(Span::raw("]")); + + ( + Line::from(format!(" initial: {:?}", ctrl.init())), + Line::from(cur_spans), + ctrl.max() + .map(|m| Line::from(format!(" maximum: {:?}", m))), + ) + } + _ => ( + Line::from(format!(" initial: {:?}", ctrl.init())), + Line::from(format!(" current: {:?}", ctrl.cur())), + ctrl.max() + .map(|m| Line::from(format!(" maximum: {:?}", m))), + ), + }; + + lines.push(initial_line); + lines.push(current_line); + if let Some(max_line) = maximum_line_opt { + lines.push(max_line); + } + ListItem::new(lines) + }) + .collect(); + let controls_list = List::new(items) + .highlight_style(Style::default().fg(Color::Yellow)) + .highlight_spacing(HighlightSpacing::Always) + .scroll_padding(1) + .block(list_block); + frame.render_stateful_widget(controls_list, list_area, app.controls_list_state()); + + render_video_preview(frame, video_area, app.image()); +} + +/// Renders the application UI based on the current application state. +pub fn ui(frame: &mut Frame, app: &mut A) +where + A: TweakerCtx + DeviceSettingsCtx, +{ + match app.view() { + View::Menu => { + let main_area = render_main_window(frame, &[KEY_NAVIGATE, KEY_CONTINUE, KEY_EXIT]); + render_device_menu( + frame, + main_area, + app, + "The tool allows you to modify the UVC camera controls.", + ); + } + View::Main | View::Edition(_, _) => { + let keys = match app.view() { + View::Main => vec![KEY_NAVIGATE, KEY_EDIT, KEY_EXIT], + View::Edition(_, _) => vec![KEYS_EDITING_NAVIGATE, KEY_EXIT], + _ => vec![KEY_EXIT], + }; + let main_area = render_main_window(frame, &keys); + render_main(frame, main_area, app); + + if let Some(err) = app.error_message() { + let error_block = Block::bordered() + .title(Line::from(" Error ").bold()) + .border_type(BorderType::Double) + .border_style(Style::default().fg(Color::Red)); + let area = popup_area(main_area, 3, 50); + let p = + Paragraph::new(Line::from(err.as_str()).style(Style::default().fg(Color::Red))) + .block(error_block) + .centered(); + frame.render_widget(Clear, area); + frame.render_widget(p, area); + } else if app.show_save_exit_prompt() { + render_save_exit_popup(frame, main_area); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::assert_ui_snapshot; + + #[derive(Default)] + struct App<'a> { + view: View<'a>, + show_save_exit_prompt: bool, + device_settings_list_state: ListState, + controls_list_state: ListState, + device_valid: (String, bool), + height: Option, + width: Option, + emitters: usize, + fps: Option, + controls: Vec, + image: Option, + } + + impl<'a> TweakerCtx for App<'a> { + fn view(&self) -> View<'_> { + self.view + } + fn show_save_exit_prompt(&self) -> bool { + self.show_save_exit_prompt + } + fn controls_list_state(&mut self) -> &mut ListState { + &mut self.controls_list_state + } + fn controls(&self) -> &[crate::video::uvc::XuControl] { + &self.controls + } + fn image(&self) -> Option<&Image> { + self.image.as_ref() + } + fn error_message(&self) -> Option<&String> { + None // TODO test + } + } + + impl<'a> DeviceSettingsCtx for App<'a> { + fn device_settings_list_state(&mut self) -> &mut ListState { + &mut self.device_settings_list_state + } + fn device_valid(&self) -> (String, bool) { + self.device_valid.clone() + } + fn height(&self) -> Option { + self.height + } + fn width(&self) -> Option { + self.width + } + fn emitters(&self) -> usize { + self.emitters + } + fn fps(&self) -> Option { + self.fps + } + } + + #[test] + fn test_menu_empty() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + ui(frame, &mut app); + }); + } + + #[test] + fn test_menu_valid_values() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + app.device_valid = ("/dev/video2".to_string(), true); + app.height = Some(720); + app.width = Some(1280); + app.emitters = 1; + app.fps = Some(30); + ui(frame, &mut app); + }); + } + + #[test] + fn test_main_empty() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + app.view = View::Main; + ui(frame, &mut app); + }); + } + + #[test] + fn test_main_with_abort_prompt() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + app.view = View::Main; + app.show_save_exit_prompt = true; + ui(frame, &mut app); + }); + } + + #[test] + fn test_main_with_controls() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + app.view = View::Main; + app.controls = vec![ + crate::video::uvc::XuControl::new( + 1, + 1, + vec![1], + Some(vec![1]), + Some(vec![1]), + Some(vec![1]), + Some(vec![1]), + true, + ) + .unwrap(), + crate::video::uvc::XuControl::new( + 2, + 2, + vec![2, 2], + Some(vec![2, 2]), + Some(vec![2, 2]), + Some(vec![2, 2]), + Some(vec![2, 2]), + true, + ) + .unwrap(), + crate::video::uvc::XuControl::new( + 3, + 3, + vec![3, 3, 3], + Some(vec![3, 3, 3]), + Some(vec![3, 3, 3]), + Some(vec![3, 3, 3]), + Some(vec![3, 3, 3]), + true, + ) + .unwrap(), + ]; + + ui(frame, &mut app); + }); + } + + #[test] + fn test_main_with_image() { + assert_ui_snapshot!(|frame| { + let mut app = App::default(); + app.view = View::Main; + let img = image::open("tests/data/ferris.png").unwrap(); + app.image = Some(img); + ui(frame, &mut app); + }); + } +} diff --git a/src/logger.rs b/src/logger.rs index af738837..e693d624 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; #[cfg(not(test))] static _LOG: &str = env!("LOG"); #[cfg(test)] -pub const _LOG: &str = "target/tmp/linux-enable-ir-emitter.log"; +pub const _LOG: &str = "target/test/linux-enable-ir-emitter.log"; fn log_file_path() -> Result { Ok(shellexpand::env(_LOG)