From 62018c7844777f3e02bb1d32ec353a5e5e20e472 Mon Sep 17 00:00:00 2001 From: Abs Date: Thu, 22 Jan 2026 21:37:38 +0300 Subject: [PATCH] Linutil New design (#1176) * New theme & design * Fix shellcheck assignment spacing * Fixed system_info file * Fixed Cargo fmt * add ctt logo * update version and fmt * update docs with xtask --------- Co-authored-by: Chris Titus --- Cargo.lock | 984 ++++++++++++++++++++++++++- Cargo.toml | 2 +- core/tabs/system-setup/tab_data.toml | 1 - docs/userguide.md | 34 +- start.sh | 14 + tui/Cargo.toml | 6 +- tui/assets/ctt.ico | Bin 0 -> 198433 bytes tui/assets/ctt_logo.png | Bin 0 -> 7291 bytes tui/src/confirmation.rs | 7 +- tui/src/filter.rs | 33 +- tui/src/floating_text.rs | 7 +- tui/src/logo.rs | 261 +++++++ tui/src/main.rs | 5 +- tui/src/running_command.rs | 53 +- tui/src/state.rs | 213 ++++-- tui/src/system_info.rs | 204 ++++++ tui/src/theme.rs | 34 +- 17 files changed, 1707 insertions(+), 151 deletions(-) create mode 100644 tui/assets/ctt.ico create mode 100644 tui/assets/ctt_logo.png create mode 100644 tui/src/logo.rs create mode 100644 tui/src/system_info.rs diff --git a/Cargo.lock b/Cargo.lock index 02b91df..87b47d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +17,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -73,18 +97,103 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -97,6 +206,39 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "cassowary" version = "0.3.0" @@ -119,6 +261,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -174,6 +318,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.4" @@ -194,6 +344,49 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crossterm" version = "0.28.1" @@ -219,6 +412,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "darling" version = "0.20.11" @@ -287,6 +486,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -303,6 +522,50 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "filedescriptor" version = "0.8.3" @@ -310,7 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] @@ -320,6 +583,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -343,6 +616,39 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -366,12 +672,58 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "icy_sixel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a9c4770bc47b0a933256a496cfb8b6531f753ea9bccb19c6dff0ff7273fc" + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.11", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + [[package]] name = "include_dir" version = "0.7.4" @@ -423,6 +775,17 @@ dependencies = [ "syn", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ioctl-rs" version = "0.1.6" @@ -447,27 +810,62 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "linutil_core" -version = "25.12.18" +version = "26.1.22" dependencies = [ "ego-tree", "include_dir", @@ -479,15 +877,17 @@ dependencies = [ [[package]] name = "linutil_tui" -version = "25.12.18" +version = "26.1.22" dependencies = [ "clap", + "image", "linutil_core", "nix 0.29.0", "oneshot", "portable-pty", - "rand", + "rand 0.8.5", "ratatui", + "ratatui-image", "time", "tree-sitter-bash", "tree-sitter-highlight", @@ -523,6 +923,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" version = "0.12.5" @@ -532,6 +941,16 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.6" @@ -547,6 +966,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -559,6 +988,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.25.1" @@ -585,12 +1030,77 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -600,6 +1110,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "once_cell_polyfill" version = "1.70.2" @@ -612,6 +1128,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking_lot" version = "0.12.5" @@ -641,12 +1163,31 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-pty" version = "0.8.1" @@ -692,6 +1233,49 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.42" @@ -701,6 +1285,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -708,8 +1298,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -719,7 +1319,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -728,7 +1338,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -743,7 +1362,7 @@ dependencies = [ "crossterm", "indoc", "instability", - "itertools", + "itertools 0.13.0", "lru", "paste", "strum", @@ -752,6 +1371,92 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "ratatui-image" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dbebe428e366c230b2251c7091f2a56ec2bf818d96cdc807f6474428ee09040" +dependencies = [ + "base64-simd", + "icy_sixel", + "image", + "rand 0.8.5", + "ratatui", + "rustix 0.38.44", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.2", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -790,6 +1495,12 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + [[package]] name = "rustix" version = "0.38.44" @@ -967,12 +1678,33 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1045,7 +1777,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1059,6 +1800,31 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "time" version = "0.3.44" @@ -1158,7 +1924,7 @@ dependencies = [ "lazy_static", "regex", "streaming-iterator", - "thiserror", + "thiserror 1.0.69", "tree-sitter", ] @@ -1195,7 +1961,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools", + "itertools 0.13.0", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -1218,6 +1984,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "vt100-ctt" version = "0.16.0" @@ -1259,6 +2042,66 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "which" version = "7.0.3" @@ -1293,12 +2136,76 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1405,13 +2312,25 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + [[package]] name = "xtask" -version = "25.12.18" +version = "26.1.22" dependencies = [ "linutil_core", ] +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "zerocopy" version = "0.8.31" @@ -1431,3 +2350,42 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2" +dependencies = [ + "zune-core 0.5.1", +] diff --git a/Cargo.toml b/Cargo.toml index a0d8508..6815842 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ default-members = ["tui", "core"] resolver = "2" [workspace.package] license = "MIT" -version = "25.12.18" +version = "26.1.22" edition = "2021" [profile.release] diff --git a/core/tabs/system-setup/tab_data.toml b/core/tabs/system-setup/tab_data.toml index b96fa7f..b36eb85 100644 --- a/core/tabs/system-setup/tab_data.toml +++ b/core/tabs/system-setup/tab_data.toml @@ -238,4 +238,3 @@ description = "This Script will set the default TTY font to Terminus size 32 Bol script = "terminus-tty.sh" task_list = "I PFM" - diff --git a/docs/userguide.md b/docs/userguide.md index 76d8ecb..3f67b18 100644 --- a/docs/userguide.md +++ b/docs/userguide.md @@ -107,30 +107,29 @@ https://github.com/ChrisTitusTech/dwm-titus - **Loot Filter**: This is a loot filter for Diablo II Resurrected. It's designed to be a simple, clean, and easy to read loot filter that highlights the most important items. Works on battle.net and single player. No frills, no config, just highlights high runes and other valuable items. For more information visit: https://github.com/ChrisTitusTech/d2r-loot-filter -### Fallout 76 +### Emulators -- **Fallout 76 Custom INI**: This script will add a Custom Fallout 76 INI file that improves performance and stability. It also includes some quality of life improvements. - -### Console Emulators - -- **RetroArch**: Frontend for emulators, game engines and media players. NO Games or Bios' are included -- **Mesen 2**: NES and SNES Emulator. NO Games are included -- **snes9x**: Super Nintendo Emulator. NO Games are included +- **Blastem**: A Genesis emulator. NO Games are included - **bsnes**: Super Nintendo Emulator. NO Games are included +- **Dolphin Emulator**: GameCube and Wii Emulator. NO Games are included +- **Flycast**: A Dreamcase emulator. NO Games are included +- **Gopher 64**: N64 Emulator. NO Games are included +- **Kronos**: A Sega Saturn emulator. NO Games are included +- **MelonDS**: Nintendo DS and DSi Emulator. NO Games are included - **mGBA**: Nintendo Game Boy Advance Emulator. NO Games are included - **Mupen 64 Plus**: N64 Emulator. NO Games are included -- **Dolphin Emulator**: GameCube and Wii Emulator. NO Games are included -- **MelonDS**: Nintendo DS and DSi Emulator. NO Games are included -- **Ryujinx**: Nintendo Switch emulator. NO Games are included -- **PCSX**: A Playstation emulator. NO Games or Bios' are included - **PCSX2**: A Playstation 2 emulator. NO Games or Bios' are included - **Play!**: An Experimental Playstation 2 emulator. NO Games or Bios' are included +- **RetroArch Game Emulator**: Frontend for emulators, game engines and media players. NO Games or Bios' are included - **RPCS3**: A Playstation 3 emulator. NO Games or Bios' are included +- **Ryujinx**: Nintendo Switch emulator. NO Games are included +- **snes9x**: Super Nintendo Emulator. NO Games are included - **XEMU**: An Original Xbox emulator. NO Games or Bios' are included - **Xenia**: An Experimental Xbox 360 emulator. NO Games or Bios' are included -- **Blastem**: A Genesis emulator. NO Games are included -- **Kronos**: A Sega Saturn emulator. NO Games are included -- **Flycast**: A Dreamcase emulator. NO Games are included + +### Fallout 76 + +- **Fallout 76 INI and Mods**: This script will add a Custom Fallout 76 INI file that improves performance and stability. It also includes some quality of life improvements. ## Security @@ -165,11 +164,6 @@ https://github.com/ChrisTitusTech/dwm-titus - **Install Desktop Environment**: This script allows you to install various desktop environments (GNOME, KDE, XFCE, Cinnamon, MATE, Budgie, LXQt, LXDE) and window managers (i3, Sway, DWM, Awesome, BSPWM, Openbox, Fluxbox) using your distro's package manager. The script provides an interactive menu to select your preferred desktop environment or window manager, and handles the installation process automatically. - **Uninstall Desktop Environment**: This script allows you to uninstall desktop environments and window managers that were previously installed. It provides an interactive menu to select which desktop environment or window manager to remove, and handles the uninstallation process automatically. -### Desktop Environment Setup - -- **Install Desktop Environment**: This script allows you to install various desktop environments (GNOME, KDE, XFCE, Cinnamon, MATE, Budgie, LXQt, LXDE) and window managers (i3, Sway, DWM, Awesome, BSPWM, Openbox, Fluxbox) using your distro's package manager. The script provides an interactive menu to select your preferred desktop environment or window manager, and handles the installation process automatically. -- **Uninstall Desktop Environment**: This script allows you to uninstall desktop environments and window managers that were previously installed. It provides an interactive menu to select which desktop environment or window manager to remove, and handles the uninstallation process automatically. - ### Fedora - **Btrfs Assistant, Snapper Config, grub-btrfs**: Installs Btrfs Assistant, Snapper, dnf snapper plugin and takes the first root(/) and /home snapshots. Enables snapper-timeline and snapper-cleanup services. Installs Grub-Btrfs. Notice: To perform a system recovery via grub-btrfs, perform a restore operation with Btrfs Assistant GUI after booting into the snapshot. Notice: If you used the default Fedora disk partitioning during OS installation, the /boot configured as an separate EXT4 partition. Therefore, it cannot be included in root snapshots. Backup separately. diff --git a/start.sh b/start.sh index 71e8758..9c55111 100755 --- a/start.sh +++ b/start.sh @@ -18,6 +18,20 @@ check() { unset message } +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +if [ -f "$SCRIPT_DIR/Cargo.toml" ] && command -v cargo >/dev/null 2>&1; then + TARGET_DIR=${CARGO_TARGET_DIR:-"$HOME/.cache/linutil/target"} + mkdir -p "$TARGET_DIR" + check $? "Preparing build cache" + + cd "$SCRIPT_DIR" + check $? "Entering linutil directory" + + CARGO_TARGET_DIR="$TARGET_DIR" cargo run -p linutil_tui --bin linutil -- "$@" + check $? "Running linutil from source" + exit 0 +fi + findArch() { case "$(uname -m)" in x86_64|amd64) arch="x86_64" ;; diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 726a486..125dfb2 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" license.workspace = true repository = "https://github.com/ChrisTitusTech/linutil/tree/main/tui" version.workspace = true -include = ["src/*.rs", "Cargo.toml", "cool_tips.txt", "../man/linutil.1"] +include = ["src/*.rs", "Cargo.toml", "cool_tips.txt", "assets/ctt_logo.png", "../man/linutil.1"] [[bin]] name = "linutil" @@ -19,12 +19,14 @@ tips = ["rand"] [dependencies] clap = { version = "4.5.20", features = ["derive"] } -linutil_core = { version = "25.12.18", path = "../core" } +image = { version = "0.25.6", default-features = false, features = ["ico", "png"] } +linutil_core = { version = "26.1.22", path = "../core" } nix = { version = "0.29.0", features = [ "user" ] } oneshot = { version = "0.1.8", features = ["std"], default-features = false } portable-pty = "0.8.1" rand = { version = "0.8.5", optional = true } ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false } +ratatui-image = { version = "9.0.0", default-features = false, features = ["crossterm", "image-defaults"] } time = { version = "0.3.36", features = ["formatting", "local-offset", "macros"], default-features = false } tree-sitter-bash = "0.23.3" tree-sitter-highlight = "0.24.4" diff --git a/tui/assets/ctt.ico b/tui/assets/ctt.ico new file mode 100644 index 0000000000000000000000000000000000000000..3354f18bce64c93b4dd721fec5271cc6173b08d8 GIT binary patch literal 198433 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�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 Eg1%{gcmMzZ literal 0 HcmV?d00001 diff --git a/tui/assets/ctt_logo.png b/tui/assets/ctt_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e60ce6869468bba67226f14383d02cd4f179ebf9 GIT binary patch literal 7291 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 V+LUq{g8cvh002ovPDHLkV1oCG6%YUb literal 0 HcmV?d00001 diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index ea465fc..615eaf6 100644 --- a/tui/src/confirmation.rs +++ b/tui/src/confirmation.rs @@ -60,8 +60,9 @@ impl ConfirmPrompt { impl FloatContent for ConfirmPrompt { fn draw(&mut self, frame: &mut Frame, area: Rect, theme: &theme::Theme) { let block = Block::bordered() - .border_set(border::ROUNDED) - .title(" Confirm selections ") + .border_set(border::PLAIN) + .border_style(Style::default().fg(theme.focused_color())) + .title(" CONFIRM SELECTIONS ") .title_bottom(Line::from(vec![ Span::raw(" ["), Span::styled("y", Style::default().fg(theme.success_color())), @@ -70,7 +71,7 @@ impl FloatContent for ConfirmPrompt { Span::raw("] to abort "), ])) .title_alignment(Alignment::Center) - .title_style(Style::default().bold()) + .title_style(Style::default().fg(theme.tab_color()).bold()) .style(Style::default()); let inner_area = block.inner(area); diff --git a/tui/src/filter.rs b/tui/src/filter.rs index 8f0d015..2e0921a 100644 --- a/tui/src/filter.rs +++ b/tui/src/filter.rs @@ -4,7 +4,7 @@ use ratatui::{ crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, prelude::*, symbols::border, - widgets::{Block, Paragraph}, + widgets::{Block, Padding, Paragraph}, }; use unicode_width::UnicodeWidthChar; @@ -116,10 +116,16 @@ impl Filter { pub fn draw_searchbar(&self, frame: &mut Frame, area: Rect, theme: &Theme) { //Set the search bar text (If empty use the placeholder) let display_text = if !self.in_search_mode && self.search_input.is_empty() { - Span::raw("Press / to search") + Span::styled( + "Type to search (/)", + Style::default().fg(theme.unfocused_color()).dim(), + ) } else { let input_text = self.search_input.iter().collect::(); - Span::styled(input_text, Style::default().fg(theme.focused_color())) + Span::styled( + input_text, + Style::default().fg(theme.focused_color()).bold(), + ) }; let search_color = if self.in_search_mode { @@ -132,8 +138,11 @@ impl Filter { let search_bar = Paragraph::new(display_text) .block( Block::bordered() - .border_set(border::ROUNDED) - .title(" Search "), + .border_set(border::PLAIN) + .border_style(Style::default().fg(search_color)) + .title(" SEARCH ") + .title_style(Style::default().fg(theme.tab_color()).bold()) + .padding(Padding::horizontal(1)), ) .style(Style::default().fg(search_color)); @@ -153,18 +162,20 @@ impl Filter { .iter() .map(|c| c.width().unwrap_or(1) as u16) .sum(); - let x = area.x + cursor_position + 1; - let y = area.y + 1; - frame.set_cursor_position(Position::new(x, y)); + let inner_x = area.x + 2; + let inner_y = area.y + 1; + let inner_width = area.width.saturating_sub(4); + let x = inner_x + cursor_position; + frame.set_cursor_position(Position::new(x, inner_y)); if let Some(preview) = &self.completion { - let preview_x = area.x + search_input_size + 1; + let preview_x = inner_x + search_input_size; let preview_span = Span::styled(preview, Style::default().fg(theme.search_preview_color())); let preview_area = Rect::new( preview_x, - y, - (preview.len() as u16).min(area.width - search_input_size - 1), // Ensure the completion preview stays within the search bar bounds + inner_y, + (preview.len() as u16).min(inner_width.saturating_sub(search_input_size)), // Ensure the completion preview stays within the search bar bounds 1, ); frame.render_widget(Paragraph::new(preview_span), preview_area); diff --git a/tui/src/floating_text.rs b/tui/src/floating_text.rs index dcc4022..f857b9f 100644 --- a/tui/src/floating_text.rs +++ b/tui/src/floating_text.rs @@ -172,13 +172,14 @@ impl<'a> FloatingText<'a> { } impl FloatContent for FloatingText<'_> { - fn draw(&mut self, frame: &mut Frame, area: Rect, _theme: &Theme) { + fn draw(&mut self, frame: &mut Frame, area: Rect, theme: &Theme) { let block = Block::default() .borders(Borders::ALL) - .border_set(border::ROUNDED) + .border_set(border::PLAIN) + .border_style(Style::default().fg(theme.focused_color())) .title(self.mode_title.as_str()) .title_alignment(Alignment::Center) - .title_style(Style::default().reversed()) + .title_style(Style::default().fg(theme.tab_color()).bold()) .style(Style::default()); let inner_area = block.inner(area); diff --git a/tui/src/logo.rs b/tui/src/logo.rs new file mode 100644 index 0000000..030059b --- /dev/null +++ b/tui/src/logo.rs @@ -0,0 +1,261 @@ +use crate::theme::Theme; +use image::{imageops::FilterType, RgbaImage}; +use ratatui::{prelude::*, widgets::Paragraph}; +use ratatui_image::{ + picker::{Picker, ProtocolType}, + protocol::StatefulProtocol, + Resize, StatefulImage, +}; + +const LOGO_TEXT_GAP: u16 = 0; +const LOGO_ALPHA_CUTOFF: u8 = 10; +const LOGO_SCALE_NUM: u32 = 70; +const LOGO_SCALE_DEN: u32 = 100; + +enum Renderer { + Protocol { + protocol: Box, + resize: Resize, + }, + Blocks, +} + +pub struct Logo { + renderer: Renderer, + rgba: RgbaImage, + font_size: (u16, u16), + image_size: (u32, u32), + cached_size: (u16, u16), + cached_lines: Vec>, + last_area_size: (u16, u16), +} + +impl Logo { + pub fn load() -> Option { + let dyn_image = image::load_from_memory(include_bytes!("../assets/ctt_logo.png")).ok()?; + let rgba = dyn_image.to_rgba8(); + let image_size = rgba.dimensions(); + + let mut picker = Picker::from_query_stdio().unwrap_or_else(|_| Picker::halfblocks()); + picker.set_background_color([0, 0, 0, 0]); + let font_size = picker.font_size(); + + let renderer = if picker.protocol_type() == ProtocolType::Halfblocks { + Renderer::Blocks + } else { + let protocol = + Box::new(picker.new_resize_protocol(image::DynamicImage::ImageRgba8(rgba.clone()))); + Renderer::Protocol { + protocol, + resize: Resize::Scale(Some(FilterType::Triangle)), + } + }; + + Some(Self { + renderer, + rgba, + font_size, + image_size, + cached_size: (0, 0), + cached_lines: Vec::new(), + last_area_size: (0, 0), + }) + } + + pub fn area_height_for_width(&self, width: u16, max_height: u16) -> u16 { + if width == 0 || max_height == 0 { + return 0; + } + + let scaled_width = self.scaled_width(width); + let max_image_height = max_height.saturating_sub(LOGO_TEXT_GAP + 1); + if max_image_height == 0 { + return max_height.min(1); + } + + let mut image_height = self.rows_for_width(scaled_width); + if image_height > max_image_height { + image_height = max_image_height; + } + + image_height + LOGO_TEXT_GAP + 1 + } + + pub fn draw(&mut self, frame: &mut Frame, area: Rect, theme: &Theme) { + if area.height == 0 || area.width == 0 { + return; + } + + self.refresh_protocol_if_needed(area); + + let max_image_height = area.height.saturating_sub(LOGO_TEXT_GAP + 1); + let (draw_width, draw_height) = self.draw_size(area.width, max_image_height); + + if draw_width > 0 && draw_height > 0 { + let centered_x = area.x + area.width.saturating_sub(draw_width) / 2; + let max_x = area.x + area.width.saturating_sub(draw_width); + let image_x = centered_x.min(max_x); + let image_area = Rect::new(image_x, area.y, draw_width, draw_height); + let mut use_blocks = matches!(self.renderer, Renderer::Blocks); + + if !use_blocks { + if let Renderer::Protocol { protocol, resize } = &mut self.renderer { + let widget = + StatefulImage::::default().resize(resize.clone()); + frame.render_stateful_widget(widget, image_area, protocol.as_mut()); + if let Some(result) = protocol.last_encoding_result() { + if result.is_err() { + use_blocks = true; + } + } + } + } + + if use_blocks { + self.renderer = Renderer::Blocks; + self.render_blocks(frame, image_area); + } + } + + let text_y = if draw_height > 0 { + area.y + draw_height + LOGO_TEXT_GAP + } else { + area.y + }; + if text_y < area.y + area.height { + let text_area = Rect::new(area.x, text_y, area.width, 1); + let label = Line::styled( + format!("Linutil V{}", env!("CARGO_PKG_VERSION")), + Style::default().fg(theme.tab_color()).bold(), + ); + let text = Paragraph::new(label).alignment(Alignment::Center); + frame.render_widget(text, text_area); + } + } + + fn draw_size(&self, width: u16, max_height: u16) -> (u16, u16) { + if width == 0 || max_height == 0 { + return (0, 0); + } + + let scaled_width = self.scaled_width(width); + let mut draw_width = scaled_width; + let mut draw_height = self.rows_for_width(draw_width); + if draw_height > max_height { + draw_height = max_height; + draw_width = self.width_for_height(draw_height).min(scaled_width); + } + + (draw_width, draw_height) + } + + fn rows_for_width(&self, width: u16) -> u16 { + if width == 0 || self.image_size.0 == 0 || self.font_size.0 == 0 || self.font_size.1 == 0 { + return 0; + } + + let pixel_width = u64::from(width) * u64::from(self.font_size.0); + let scaled_pixel_height = + u64::from(self.image_size.1) * pixel_width / u64::from(self.image_size.0); + let row_height = u64::from(self.font_size.1); + let rows = scaled_pixel_height.div_ceil(row_height); + + rows.min(u64::from(u16::MAX)) as u16 + } + + fn width_for_height(&self, height: u16) -> u16 { + if height == 0 || self.image_size.1 == 0 || self.font_size.0 == 0 || self.font_size.1 == 0 { + return 0; + } + + let pixel_height = u64::from(height) * u64::from(self.font_size.1); + let scaled_pixel_width = + u64::from(self.image_size.0) * pixel_height / u64::from(self.image_size.1); + let col_width = u64::from(self.font_size.0); + let cols = scaled_pixel_width.div_ceil(col_width); + + cols.min(u64::from(u16::MAX)) as u16 + } + + fn render_blocks(&mut self, frame: &mut Frame, area: Rect) { + if area.width == 0 || area.height == 0 { + return; + } + + let size = (area.width, area.height); + if self.cached_size != size { + self.cached_size = size; + self.cached_lines.clear(); + + let resized = image::imageops::resize( + &self.rgba, + area.width as u32, + area.height as u32, + FilterType::Triangle, + ); + + for y in 0..area.height { + let mut spans = Vec::with_capacity(area.width as usize); + for x in 0..area.width { + let pixel = resized.get_pixel(x as u32, y as u32).0; + if pixel[3] < LOGO_ALPHA_CUTOFF { + spans.push(Span::raw(" ")); + } else { + spans.push(Span::styled( + "#", + Style::default().fg(Color::Rgb(pixel[0], pixel[1], pixel[2])), + )); + } + } + self.cached_lines.push(Line::from(spans)); + } + } + + frame.render_widget(Paragraph::new(Text::from(self.cached_lines.clone())), area); + } + + fn refresh_protocol_if_needed(&mut self, area: Rect) { + let area_size = (area.width, area.height); + if self.last_area_size == area_size { + return; + } + self.last_area_size = area_size; + + if !matches!(self.renderer, Renderer::Protocol { .. }) { + return; + } + + let mut picker = match Picker::from_query_stdio() { + Ok(picker) => picker, + Err(_) => return, + }; + picker.set_background_color([0, 0, 0, 0]); + let new_font_size = picker.font_size(); + let protocol_type = picker.protocol_type(); + + if protocol_type == ProtocolType::Halfblocks { + self.renderer = Renderer::Blocks; + self.font_size = new_font_size; + self.cached_size = (0, 0); + return; + } + + if self.font_size != new_font_size { + self.font_size = new_font_size; + self.renderer = Renderer::Protocol { + protocol: Box::new( + picker.new_resize_protocol(image::DynamicImage::ImageRgba8(self.rgba.clone())), + ), + resize: Resize::Scale(Some(FilterType::Triangle)), + }; + } + } + + fn scaled_width(&self, width: u16) -> u16 { + if width == 0 { + return 0; + } + let scaled = (u32::from(width) * LOGO_SCALE_NUM + (LOGO_SCALE_DEN / 2)) / LOGO_SCALE_DEN; + scaled.clamp(1, u16::MAX as u32) as u16 + } +} diff --git a/tui/src/main.rs b/tui/src/main.rs index 729b9c3..e312c39 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -4,9 +4,11 @@ mod filter; mod float; mod floating_text; mod hint; +mod logo; mod root; mod running_command; mod state; +mod system_info; mod theme; #[cfg(feature = "tips")] @@ -34,13 +36,14 @@ use std::{ fn main() -> Result<()> { let args = Args::parse(); - let mut state = AppState::new(args.clone()); stdout().execute(EnterAlternateScreen)?; if args.mouse { stdout().execute(EnableMouseCapture)?; } + let mut state = AppState::new(args.clone()); + enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index 531181e..23d64da 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -48,32 +48,57 @@ impl FloatContent for RunningCommand { let block = if !self.is_finished() { // Display a block indicating the command is running Block::bordered() - .border_set(border::ROUNDED) - .title_top(Line::from("Running the command....").centered()) - .title_style(Style::default().reversed()) - .title_bottom(Line::from("Press Ctrl-C to KILL the command")) + .border_set(border::PLAIN) + .border_style(Style::default().fg(theme.focused_color())) + .title_top( + Line::styled( + " RUNNING COMMAND ", + Style::default().fg(theme.focused_color()).bold(), + ) + .centered(), + ) + .title_bottom( + Line::styled( + " Press Ctrl-C to kill the command ", + Style::default().fg(theme.unfocused_color()), + ) + .centered(), + ) } else { // Display a block with the command's exit status - let title_line = if self.get_exit_status().success() { - Line::styled( - "SUCCESS! Press to close this window", - Style::default().fg(theme.success_color()).reversed(), + let (title_line, border_color) = if self.get_exit_status().success() { + ( + Line::styled( + " SUCCESS - Press to close ", + Style::default().fg(theme.success_color()).bold(), + ), + theme.success_color(), ) } else { - Line::styled( - "FAILED! Press to close this window", - Style::default().fg(theme.fail_color()).reversed(), + ( + Line::styled( + " FAILED - Press to close ", + Style::default().fg(theme.fail_color()).bold(), + ), + theme.fail_color(), ) }; let log_path = if let Some(log_path) = &self.log_path { - Line::from(format!(" Log saved: {log_path} ")) + Line::styled( + format!(" Log saved: {log_path} "), + Style::default().fg(theme.unfocused_color()), + ) } else { - Line::from(" Press 'l' to save command log ") + Line::styled( + " Press 'l' to save command log ", + Style::default().fg(theme.unfocused_color()), + ) }; Block::bordered() - .border_set(border::ROUNDED) + .border_set(border::PLAIN) + .border_style(Style::default().fg(border_color)) .title_top(title_line.centered()) .title_bottom(log_path.centered()) }; diff --git a/tui/src/state.rs b/tui/src/state.rs index f388209..7f503a8 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -4,9 +4,11 @@ use crate::{ float::{Float, FloatContent}, floating_text::FloatingText, hint::{create_shortcut_list, Shortcut}, + logo::Logo, root::check_root_status, running_command::RunningCommand, shortcuts, + system_info::SystemInfo, theme::Theme, Args, }; @@ -16,7 +18,7 @@ use ratatui::{ layout::Flex, prelude::*, symbols::border, - widgets::{Block, List, ListState, Paragraph}, + widgets::{Block, List, ListState, Padding, Paragraph}, }; use std::rc::Rc; @@ -24,7 +26,9 @@ const MIN_WIDTH: u16 = 100; const MIN_HEIGHT: u16 = 25; const FLOAT_SIZE: u16 = 95; const CONFIRM_PROMPT_FLOAT_SIZE: u16 = 40; -const TITLE: &str = concat!(" Linux Toolbox - ", env!("CARGO_PKG_VERSION"), " "); +const LEFT_EXTRA_WIDTH: u16 = 4; +const TITLE: &str = concat!(" LINUTIL v", env!("CARGO_PKG_VERSION"), " "); +const LIST_HIGHLIGHT_SYMBOL: &str = "> "; const ACTIONS_GUIDE: &str = "List of important tasks performed by commands' names: D - disk modifications (ex. partitioning) (privileged) @@ -67,6 +71,8 @@ pub struct AppState { size_bypass: bool, skip_confirmation: bool, mouse_enabled: bool, + system_info: Option, + logo: Option, } pub enum Focus { @@ -114,7 +120,7 @@ impl AppState { .iter() .map(|tab| tab.name.len() + args.theme.tab_icon().len()) .max() - .unwrap_or(22) as u16; // 22 is the length of "Linutil by Chris Titus" title + .unwrap_or(22) as u16; let mut state = Self { areas: None, @@ -134,6 +140,8 @@ impl AppState { size_bypass: args.size_bypass, skip_confirmation: args.skip_confirmation, mouse_enabled: args.mouse, + system_info: SystemInfo::gather(), + logo: Logo::load(), }; #[cfg(unix)] @@ -294,29 +302,14 @@ impl AppState { return frame.render_widget(warning, centered_layout[1]); } - let label_block = Block::bordered().border_set(border::Set { - top_left: " ", - top_right: " ", - bottom_left: " ", - bottom_right: " ", - vertical_left: " ", - vertical_right: " ", - horizontal_top: "*", - horizontal_bottom: "*", - }); - - let label = Paragraph::new(Line::from(vec![ - Span::styled("Linutil ", Style::default().bold()), - Span::styled("by Chris Titus", Style::default().italic()), - ])) - .block(label_block) - .centered(); - let (keybind_scope, shortcuts) = self.get_keybinds(); let keybinds_block = Block::bordered() .title(format!(" {keybind_scope} ")) - .border_set(border::ROUNDED); + .border_set(border::PLAIN) + .border_style(Style::default().fg(self.theme.unfocused_color())) + .title_style(Style::default().fg(self.theme.tab_color()).bold()) + .padding(Padding::horizontal(1)); let keybind_render_width = keybinds_block.inner(area).width; let keybinds = create_shortcut_list(shortcuts, keybind_render_width); @@ -329,14 +322,49 @@ impl AppState { .split(area); let horizontal = Layout::horizontal([ - Constraint::Min(self.longest_tab_display_len + 5), + Constraint::Min(self.longest_tab_display_len + 7 + LEFT_EXTRA_WIDTH), Constraint::Percentage(100), ]) .split(vertical[0]); - let left_chunks = - Layout::vertical([Constraint::Length(3), Constraint::Min(1)]).split(horizontal[0]); - frame.render_widget(label, left_chunks[0]); + let info_height = self + .system_info + .as_ref() + .map(|info| info.entries_len() as u16 + 2) + .unwrap_or(0); + let show_info = info_height > 0 && horizontal[0].height > info_height.saturating_add(3); + + let reserved_info_height = if show_info { info_height } else { 0 }; + let max_logo_area_height = horizontal[0] + .height + .saturating_sub(reserved_info_height) + .saturating_sub(1); + let logo_height = if let Some(logo) = &self.logo { + logo.area_height_for_width(horizontal[0].width, max_logo_area_height) + } else { + max_logo_area_height.min(1) + }; + let left_chunks = if show_info { + Layout::vertical([ + Constraint::Length(logo_height), + Constraint::Min(1), + Constraint::Length(info_height), + ]) + .split(horizontal[0]) + } else { + Layout::vertical([Constraint::Length(logo_height), Constraint::Min(1)]) + .split(horizontal[0]) + }; + if let Some(logo) = &mut self.logo { + logo.draw(frame, left_chunks[0], &self.theme); + } else { + let label = Paragraph::new(Line::styled( + format!("Linutil V{}", env!("CARGO_PKG_VERSION")), + Style::default().fg(self.theme.tab_color()).bold(), + )) + .alignment(Alignment::Center); + frame.render_widget(label, left_chunks[0]); + } self.areas = Some(Areas { tab_list: left_chunks[1], @@ -349,36 +377,85 @@ impl AppState { .map(|tab| tab.name.as_str()) .collect::>(); - let (tab_hl_style, highlight_symbol) = if let Focus::TabList = self.focus { - ( - Style::default().reversed().fg(self.theme.tab_color()), - self.theme.tab_icon(), - ) - } else if let Focus::Search = self.focus { - (Style::reset(), " ") + let tab_focus = matches!(self.focus, Focus::TabList); + let tab_hl_style = if tab_focus { + Style::default() + .bg(self.theme.focused_color()) + .fg(Color::Black) + .bold() } else { - ( - Style::new().fg(self.theme.tab_color()), - self.theme.tab_icon(), - ) + Style::default().fg(self.theme.unfocused_color()) + }; + let highlight_symbol = self.theme.tab_icon(); + let tab_border_style = if tab_focus { + Style::default().fg(self.theme.focused_color()) + } else { + Style::default().fg(self.theme.unfocused_color()) }; let tab_list = List::new(tabs) - .block(Block::bordered().border_set(border::ROUNDED)) + .block( + Block::bordered() + .border_set(border::PLAIN) + .border_style(tab_border_style) + .padding(Padding::horizontal(1)), + ) + .style(Style::default().fg(self.theme.tab_color())) .highlight_style(tab_hl_style) .highlight_symbol(highlight_symbol); frame.render_stateful_widget(tab_list, left_chunks[1], &mut self.current_tab); + if show_info && left_chunks.len() > 2 { + self.draw_system_info(frame, left_chunks[2]); + } let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(1)]).split(horizontal[1]); self.filter.draw_searchbar(frame, chunks[0], &self.theme); + let title = if self.multi_select { + &format!("{TITLE}[Multi-Select] ") + } else { + TITLE + }; + + #[cfg(feature = "tips")] + let bottom_title = Line::from(format!(" {} ", self.tip)) + .bold() + .fg(self.theme.unfocused_color()) + .centered(); + #[cfg(not(feature = "tips"))] + let bottom_title = ""; + + let task_list_title = Line::from(" FLAGS ").right_aligned(); + let list_focus = matches!(self.focus, Focus::List); + let list_dim_style = if list_focus { + Style::default() + } else { + Style::default().dim() + }; + let list_border_style = if list_focus { + Style::default().fg(self.theme.focused_color()) + } else { + Style::default().fg(self.theme.unfocused_color()) + }; + let list_block = Block::bordered() + .border_set(border::PLAIN) + .border_style(list_border_style) + .title(title) + .title(task_list_title) + .title_bottom(bottom_title) + .padding(Padding::horizontal(1)); + let list_inner_width = list_block.inner(chunks[1]).width as usize; + let list_content_width = list_inner_width.saturating_sub(LIST_HIGHLIGHT_SYMBOL.len()); + let mut items: Vec = Vec::with_capacity(self.filter.item_list().len()); if !self.at_root() { items.push( - Line::from(format!("{} ..", self.theme.dir_icon())).style(self.theme.dir_color()), + Line::from(format!("{} ..", self.theme.dir_icon())) + .style(self.theme.dir_color()) + .patch_style(list_dim_style), ); } @@ -403,54 +480,43 @@ impl AppState { self.theme.dir_color(), ) .patch_style(style) + .patch_style(list_dim_style) } else { let left_content = format!("{} {} {}", self.theme.cmd_icon(), node.name, indicator); let right_content = format!("{} ", node.task_list); let center_space = " ".repeat( - chunks[1].width as usize - left_content.len() - right_content.len(), + list_content_width.saturating_sub(left_content.len() + right_content.len()), ); Line::styled( format!("{left_content}{center_space}{right_content}"), self.theme.cmd_color(), ) .patch_style(style) + .patch_style(list_dim_style) } }, )); - let style = if let Focus::List = self.focus { - Style::default().reversed() + let list_highlight_style = if list_focus { + Style::default() + .bg(self.theme.focused_color()) + .fg(Color::Black) + .bold() } else { - Style::new() + list_dim_style }; - - let title = if self.multi_select { - &format!("{TITLE}[Multi-Select] ") + let list_highlight_symbol = if list_focus { + LIST_HIGHLIGHT_SYMBOL } else { - TITLE + " " }; - #[cfg(feature = "tips")] - let bottom_title = Line::from(format!(" {} ", self.tip)) - .bold() - .blue() - .centered(); - #[cfg(not(feature = "tips"))] - let bottom_title = ""; - - let task_list_title = Line::from(" Important Actions ").right_aligned(); - // Create the list widget with items let list = List::new(items) - .highlight_style(style) - .block( - Block::bordered() - .border_set(border::ROUNDED) - .title(title) - .title(task_list_title) - .title_bottom(bottom_title), - ) + .highlight_style(list_highlight_style) + .highlight_symbol(list_highlight_symbol) + .block(list_block) .scroll_padding(1); frame.render_stateful_widget(list, chunks[1], &mut self.selection); @@ -901,4 +967,21 @@ impl AppState { } self.refresh_tab(); } + + fn draw_system_info(&self, frame: &mut Frame, area: Rect) { + let Some(info) = &self.system_info else { + return; + }; + + let block = Block::bordered() + .border_set(border::PLAIN) + .border_style(Style::default().fg(self.theme.unfocused_color())) + .title(" SYSTEM ") + .title_style(Style::default().fg(self.theme.tab_color()).bold()) + .padding(Padding::horizontal(1)); + + let inner_width = block.inner(area).width as usize; + let text = Text::from(info.render_lines(&self.theme, inner_width)); + frame.render_widget(Paragraph::new(text).block(block), area); + } } diff --git a/tui/src/system_info.rs b/tui/src/system_info.rs new file mode 100644 index 0000000..a49d401 --- /dev/null +++ b/tui/src/system_info.rs @@ -0,0 +1,204 @@ +use crate::theme::Theme; +use ratatui::{ + style::{Style, Stylize}, + text::{Line, Span}, +}; +use std::process::Command; + +const FASTFETCH_ARGS: [&str; 6] = [ + "--logo", + "none", + "--separator", + " : ", + "--structure", + "cpu:disk:memory:gpu", +]; + +pub struct SystemInfo { + entries: Vec, +} + +struct InfoEntry { + label: &'static str, + value: String, +} + +impl SystemInfo { + pub fn gather() -> Option { + fastfetch_entries().map(|entries| Self { entries }) + } + + pub fn entries_len(&self) -> usize { + self.entries.len() + } + + pub fn render_lines(&self, theme: &Theme, max_width: usize) -> Vec> { + self.entries + .iter() + .map(|entry| { + let prefix = format!("{:>4}: ", entry.label); + let value_width = max_width.saturating_sub(prefix.len()); + let value = truncate_value(&entry.value, value_width); + Line::from(vec![ + Span::styled( + format!("{:>4}", entry.label), + Style::default().fg(theme.tab_color()).bold(), + ), + Span::styled(": ", Style::default().fg(theme.unfocused_color())), + Span::styled(value, Style::default().fg(theme.cmd_color())), + ]) + }) + .collect() + } +} + +fn fastfetch_entries() -> Option> { + let output = Command::new("fastfetch") + .args(FASTFETCH_ARGS) + .env("NO_COLOR", "1") + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let cleaned = strip_ansi_codes(&stdout); + + let mut cpu = None; + let mut disk = None; + let mut ram = None; + let mut gpu = None; + + for line in cleaned.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + + let mut parts = line.splitn(2, ':'); + let label = parts.next().unwrap_or("").trim(); + let value = parts.next().unwrap_or("").trim(); + if value.is_empty() { + continue; + } + + let label_lower = label.to_ascii_lowercase(); + if cpu.is_none() && label_lower.starts_with("cpu") { + cpu = Some(normalize_value("cpu", value)); + } else if disk.is_none() && label_lower.starts_with("disk") { + disk = Some(normalize_value("disk", value)); + } else if ram.is_none() + && (label_lower.starts_with("memory") || label_lower.starts_with("ram")) + { + ram = Some(normalize_value("ram", value)); + } else if gpu.is_none() && label_lower.starts_with("gpu") { + gpu = Some(normalize_value("gpu", value)); + } + } + + if cpu.is_none() && disk.is_none() && ram.is_none() && gpu.is_none() { + return None; + } + + Some(vec![ + InfoEntry { + label: "CPU", + value: cpu.unwrap_or_else(|| "n/a".to_string()), + }, + InfoEntry { + label: "DISK", + value: disk.unwrap_or_else(|| "n/a".to_string()), + }, + InfoEntry { + label: "RAM", + value: ram.unwrap_or_else(|| "n/a".to_string()), + }, + InfoEntry { + label: "GPU", + value: gpu.unwrap_or_else(|| "n/a".to_string()), + }, + ]) +} + +fn strip_ansi_codes(input: &str) -> String { + let mut output = String::with_capacity(input.len()); + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '\x1b' && matches!(chars.peek(), Some('[')) { + chars.next(); + for next in chars.by_ref() { + if next == 'm' { + break; + } + } + continue; + } + output.push(c); + } + + output +} + +fn normalize_value(label: &str, value: &str) -> String { + match label { + "cpu" => strip_device_details(value), + "gpu" => strip_device_details(value), + "disk" => extract_total(value), + "ram" => extract_total(value), + _ => value.trim().to_string(), + } +} + +fn strip_device_details(value: &str) -> String { + let mut trimmed = value.trim().to_string(); + if let Some(idx) = trimmed.find(" @") { + trimmed.truncate(idx); + trimmed = trimmed.trim().to_string(); + } + if let Some(idx) = trimmed.find(" (") { + trimmed.truncate(idx); + trimmed = trimmed.trim().to_string(); + } + if let Some(idx) = trimmed.find(" [") { + trimmed.truncate(idx); + trimmed = trimmed.trim().to_string(); + } + trimmed +} + +fn extract_total(value: &str) -> String { + let mut parts = value.splitn(2, '/'); + let _used = parts.next(); + let Some(total_part) = parts.next() else { + return value.trim().to_string(); + }; + let mut tokens = total_part.split_whitespace(); + let Some(amount) = tokens.next() else { + return total_part.trim().to_string(); + }; + let Some(unit) = tokens.next() else { + return amount.to_string(); + }; + format!("{amount} {unit}") +} + +fn truncate_value(value: &str, max_width: usize) -> String { + if max_width == 0 { + return String::new(); + } + + let value_chars: Vec = value.chars().collect(); + if value_chars.len() <= max_width { + return value.to_string(); + } + + if max_width <= 3 { + return value_chars.iter().take(max_width).collect(); + } + + let slice: String = value_chars.iter().take(max_width - 3).collect(); + format!("{slice}...") +} diff --git a/tui/src/theme.rs b/tui/src/theme.rs index f09c6b5..e3802c0 100644 --- a/tui/src/theme.rs +++ b/tui/src/theme.rs @@ -16,14 +16,14 @@ pub enum Theme { impl Theme { pub const fn dir_color(&self) -> Color { match self { - Theme::Default => Color::Blue, - Theme::Compatible => Color::Blue, + Theme::Default => Color::LightCyan, + Theme::Compatible => Color::Cyan, } } pub const fn cmd_color(&self) -> Color { match self { - Theme::Default => Color::Rgb(204, 224, 208), + Theme::Default => Color::White, Theme::Compatible => Color::LightGreen, } } @@ -37,49 +37,49 @@ impl Theme { pub const fn tab_color(&self) -> Color { match self { - Theme::Default => Color::Rgb(255, 255, 85), - Theme::Compatible => Color::Yellow, + Theme::Default => Color::Yellow, + Theme::Compatible => Color::LightYellow, } } pub const fn dir_icon(&self) -> &'static str { match self { - Theme::Default => "  ", - Theme::Compatible => "[DIR]", + Theme::Default => "[D]", + Theme::Compatible => "[D]", } } pub const fn cmd_icon(&self) -> &'static str { match self { - Theme::Default => "  ", - Theme::Compatible => "[CMD]", + Theme::Default => "[*]", + Theme::Compatible => "[*]", } } pub const fn tab_icon(&self) -> &'static str { match self { - Theme::Default => " ", - Theme::Compatible => ">> ", + Theme::Default => ">> ", + Theme::Compatible => "> ", } } pub const fn multi_select_icon(&self) -> &'static str { match self { - Theme::Default => "", + Theme::Default => "*", Theme::Compatible => "*", } } pub const fn success_color(&self) -> Color { match self { - Theme::Default => Color::Rgb(5, 255, 55), + Theme::Default => Color::LightGreen, Theme::Compatible => Color::Green, } } pub const fn fail_color(&self) -> Color { match self { - Theme::Default => Color::Rgb(199, 55, 44), + Theme::Default => Color::LightRed, Theme::Compatible => Color::Red, } } @@ -87,21 +87,21 @@ impl Theme { pub const fn focused_color(&self) -> Color { match self { Theme::Default => Color::LightBlue, - Theme::Compatible => Color::LightBlue, + Theme::Compatible => Color::LightCyan, } } pub const fn search_preview_color(&self) -> Color { match self { Theme::Default => Color::DarkGray, - Theme::Compatible => Color::DarkGray, + Theme::Compatible => Color::Gray, } } pub const fn unfocused_color(&self) -> Color { match self { Theme::Default => Color::Gray, - Theme::Compatible => Color::Gray, + Theme::Compatible => Color::DarkGray, } } }