diff --git a/Cargo.lock b/Cargo.lock index 6886cbe..f2ba9b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,175 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.4.1", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io 2.6.0", + "async-lock 3.4.2", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "atomic" version = "0.6.1" @@ -166,6 +335,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -184,6 +366,12 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -304,6 +492,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -457,6 +654,12 @@ dependencies = [ "syn 2.0.117", ] +[[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.29.0" @@ -470,7 +673,7 @@ dependencies = [ "futures-core", "mio", "parking_lot", - "rustix", + "rustix 1.1.4", "signal-hook", "signal-hook-mio", "winapi", @@ -545,26 +748,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "dbus" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "dbus-crossroads" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bff0bd181fba667660276c6b7ebdc50cff37ce593e7adf9e734f89c8f444e8" -dependencies = [ - "dbus", -] - [[package]] name = "deltae" version = "0.3.2" @@ -580,6 +763,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -722,6 +916,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -747,6 +962,44 @@ dependencies = [ "num-traits", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "extended" version = "0.1.0" @@ -769,6 +1022,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -872,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] -name = "furumi_cli" +name = "furumi_tui" version = "0.1.0" dependencies = [ "anyhow", @@ -918,6 +1180,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.4.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -1054,6 +1344,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1151,7 +1453,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.4", "tokio", "tower-service", "tracing", @@ -1334,6 +1636,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1504,15 +1826,6 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" -[[package]] -name = "libdbus-sys" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" -dependencies = [ - "pkg-config", -] - [[package]] name = "libm" version = "0.2.16" @@ -1537,6 +1850,18 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1591,7 +1916,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ - "nix", + "nix 0.29.0", "winapi", ] @@ -1640,6 +1965,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -1716,6 +2050,18 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + [[package]] name = "nix" version = "0.29.0" @@ -1726,7 +2072,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset", + "memoffset 0.9.1", ] [[package]] @@ -1820,7 +2166,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -1971,6 +2317,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "palette" version = "0.7.6" @@ -1995,6 +2351,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2131,6 +2493,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.4.1", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2150,6 +2523,42 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -2190,13 +2599,23 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2233,7 +2652,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -2271,7 +2690,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -2303,6 +2722,8 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -2312,10 +2733,20 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -2331,6 +2762,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "rand_core" @@ -2568,6 +3002,33 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -2577,7 +3038,7 @@ dependencies = [ "bitflags 2.13.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -2764,6 +3225,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "serde_spanned" version = "1.1.1" @@ -2773,6 +3245,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2870,6 +3353,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.6.4" @@ -2890,12 +3383,13 @@ dependencies = [ "block", "cocoa", "core-graphics", - "dbus", - "dbus-crossroads", "dispatch", "objc", + "pollster", "thiserror 1.0.69", "windows 0.44.0", + "zbus", + "zvariant", ] [[package]] @@ -3175,10 +3669,10 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand", + "fastrand 2.4.1", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -3221,7 +3715,7 @@ dependencies = [ "libc", "log", "memmem", - "nix", + "nix 0.29.0", "num-derive", "num-traits", "ordered-float", @@ -3350,7 +3844,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "socket2", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -3398,12 +3892,18 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.3", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -3413,6 +3913,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.25.12+spec-1.1.0" @@ -3420,9 +3931,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -3431,7 +3942,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] [[package]] @@ -3564,6 +4075,17 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3662,6 +4184,12 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -4064,6 +4592,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4073,6 +4610,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -4106,6 +4652,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4154,6 +4715,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4172,6 +4739,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4190,6 +4763,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4220,6 +4799,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4238,6 +4823,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4256,6 +4847,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4274,6 +4871,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4286,6 +4889,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "1.0.3" @@ -4395,6 +5007,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "yoke" version = "0.8.3" @@ -4418,6 +5040,72 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand 0.8.6", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.52" @@ -4518,3 +5206,41 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml index 05eae58..01d78f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "furumi_cli" +name = "furumi_tui" version = "0.1.0" edition = "2024" +[[bin]] +name = "furumi" +path = "src/main.rs" + [dependencies] anyhow = "1.0.102" crokey = "1.4.0" @@ -16,7 +20,7 @@ reqwest = { version = "0.13.4", default-features = false, features = ["json", "r rodio = { version = "0.22.2", default-features = false, features = ["playback", "mp3", "flac", "vorbis", "wav", "symphonia-aac", "symphonia-isomp4", "symphonia-alac"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.150" -souvlaki = "0.8.3" +souvlaki = { version = "0.8.3", default-features = false, features = ["use_zbus"] } stream-download = { version = "0.24.1", default-features = false, features = ["reqwest-rustls", "temp-storage"] } thiserror = "2.0.18" tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "sync", "time"] } diff --git a/src/api/client.rs b/src/api/client.rs index e0bca07..55f1d49 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -24,7 +24,7 @@ pub enum ApiError { pub fn http_client() -> reqwest::Client { reqwest::Client::builder() .user_agent(format!( - "furumi-cli/{} ({})", + "furumi-tui/{} ({})", env!("CARGO_PKG_VERSION"), std::env::consts::OS )) @@ -35,7 +35,7 @@ pub fn http_client() -> reqwest::Client { } pub fn device_name() -> String { - format!("furumi-cli ({})", std::env::consts::OS) + format!("furumi-tui ({})", std::env::consts::OS) } #[derive(Serialize)] @@ -395,8 +395,22 @@ impl ApiClient { } request }) - .await?; - parse_response(response).await + .await; + let response = match response { + Ok(response) => response, + Err(err) => { + tracing::warn!(%err, %method, path, "api request failed"); + return Err(err); + } + }; + let status = response.status(); + let result = parse_response(response).await; + if let Err(err) = &result { + tracing::warn!(%err, %status, %method, path, "api response error"); + } else { + tracing::debug!(%status, %method, path, "api ok"); + } + result } /// Send a request with a fresh bearer token; on 401, refresh once and diff --git a/src/app/login.rs b/src/app/login.rs index 1ac977b..43014d8 100644 --- a/src/app/login.rs +++ b/src/app/login.rs @@ -180,11 +180,15 @@ pub fn spawn_sso_exchange(form: &mut LoginForm, runtime: &Runtime, code: String) fn login_event(result: Result) -> AppEvent { match result { Ok(session) => { + tracing::info!(user = %session.user.name, server = %session.server_base_url, "signed in"); if let Err(err) = auth::save_session(&session) { tracing::warn!(%err, "failed to persist credentials"); } AppEvent::LoginSucceeded(Box::new(session)) } - Err(err) => AppEvent::LoginFailed(err.to_string()), + Err(err) => { + tracing::warn!(%err, "login failed"); + AppEvent::LoginFailed(err.to_string()) + } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index cab3c4f..e267564 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -702,6 +702,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent global.artists.extend(page.items); } AppEvent::ArtistsLoaded(Err(message)) => { + tracing::warn!(%message, "artists page load failed"); state.global.loading = false; state.global.error = Some(message.clone()); state.status_message = Some(message); @@ -709,14 +710,20 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent AppEvent::ArtistViewLoaded { id, result } => { let entry = match result { Ok(detail) => state::Loadable::Ready(detail), - Err(message) => state::Loadable::Failed(message), + Err(message) => { + tracing::warn!(artist = id, %message, "artist view load failed"); + state::Loadable::Failed(message) + } }; state.artist_views.insert(id, entry); } AppEvent::ReleaseViewLoaded { id, result } => { let entry = match result { Ok(detail) => state::Loadable::Ready(detail), - Err(message) => state::Loadable::Failed(message), + Err(message) => { + tracing::warn!(release = id, %message, "release view load failed"); + state::Loadable::Failed(message) + } }; state.release_views.insert(id, entry); } @@ -770,6 +777,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent push_state_now(state, runtime); } AppEvent::Player(player::PlayerEvent::Failed(message)) => { + tracing::error!(%message, "playback failed"); state.player.playing = false; state.player.paused = false; state.status_message = Some(message); diff --git a/src/app/state.rs b/src/app/state.rs index 9bcc600..dfd23ef 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -167,6 +167,36 @@ pub struct PlaylistsTab { pub opened: Option, } +/// Severity steps for the Logs tab filter, cycled with the view-toggle key. +pub const LOG_LEVELS: [tracing::Level; 5] = [ + tracing::Level::ERROR, + tracing::Level::WARN, + tracing::Level::INFO, + tracing::Level::DEBUG, + tracing::Level::TRACE, +]; + +/// The Logs tab: a live view over the in-memory ring buffer. +#[derive(Debug)] +pub struct LogsTab { + /// Index into LOG_LEVELS; entries more verbose than this are hidden. + pub level_index: usize, + /// Stick to the newest entries as they arrive. + pub follow: bool, + /// When not following: how many (filtered) entries back from the end. + pub scroll_from_end: usize, +} + +impl Default for LogsTab { + fn default() -> Self { + Self { + level_index: 2, + follow: true, + scroll_from_end: 0, + } + } +} + /// Command line (`:`), vim-style. Lives on the Main screen status bar. #[derive(Debug, Default)] pub struct Cmdline { @@ -271,10 +301,17 @@ pub enum Tab { Playlists, Queue, Devices, + Logs, } impl Tab { - pub const ALL: [Tab; 4] = [Tab::Global, Tab::Playlists, Tab::Queue, Tab::Devices]; + pub const ALL: [Tab; 5] = [ + Tab::Global, + Tab::Playlists, + Tab::Queue, + Tab::Devices, + Tab::Logs, + ]; pub fn title(self) -> &'static str { match self { @@ -282,6 +319,7 @@ impl Tab { Tab::Playlists => "Playlists", Tab::Queue => "Queue", Tab::Devices => "Devices", + Tab::Logs => "Logs", } } @@ -307,6 +345,7 @@ impl Tab { Tab::Playlists => KeyContext::Playlists, Tab::Queue => KeyContext::Queue, Tab::Devices => KeyContext::Devices, + Tab::Logs => KeyContext::Logs, } } } @@ -399,6 +438,7 @@ pub struct AppState { /// Liked track ids, for the ♥ markers everywhere tracks are shown. pub likes: std::collections::HashSet, pub likes_loaded: bool, + pub logs: LogsTab, pub cmdline: Cmdline, pub search: SearchState, /// Shared image cache keyed by `art::cache_key(url, w, h)`; reused by diff --git a/src/app/update.rs b/src/app/update.rs index b1abe28..fb72f27 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -142,8 +142,16 @@ pub fn update(state: &mut AppState, action: Action) -> Option { None } Action::ToggleViewMode => { - if state.active_tab == Tab::Global { - state.global.view = state.global.view.toggle(); + match state.active_tab { + Tab::Global => state.global.view = state.global.view.toggle(), + // On the Logs tab the same key cycles the severity filter. + Tab::Logs => { + state.logs.level_index = + (state.logs.level_index + 1) % super::state::LOG_LEVELS.len(); + state.logs.scroll_from_end = 0; + state.logs.follow = true; + } + _ => {} } None } @@ -204,7 +212,7 @@ pub fn selected_track(state: &AppState) -> Option { .queue .get(state.player.queue_pos) .cloned(), - Tab::Devices => None, + Tab::Devices | Tab::Logs => None, } } @@ -397,6 +405,9 @@ fn viewport_lines() -> isize { /// lines. fn page_step(state: &AppState) -> isize { let lines = viewport_lines(); + if state.active_tab != Tab::Global { + return lines; + } let tile_rows = (lines / TILE_HEIGHT as isize).max(1); match state.global.stack.last() { None => match state.global.view { @@ -419,6 +430,20 @@ fn page_step(state: &AppState) -> isize { } fn move_selection(state: &mut AppState, dx: isize, dy: isize) { + if state.active_tab == Tab::Logs { + let total = crate::config::logging::buffer().map_or(0, |b| b.len()); + let logs = &mut state.logs; + if dy < 0 { + logs.follow = false; + logs.scroll_from_end = (logs.scroll_from_end + dy.unsigned_abs()).min(total); + } else if dy > 0 { + logs.scroll_from_end = logs.scroll_from_end.saturating_sub(dy as usize); + if logs.scroll_from_end == 0 { + logs.follow = true; + } + } + return; + } if state.active_tab == Tab::Playlists { let len = playlists_view_len(state); if len == 0 { @@ -565,6 +590,16 @@ fn current_view_len(state: &AppState) -> usize { } fn jump_selection(state: &mut AppState, first: bool) { + if state.active_tab == Tab::Logs { + if first { + state.logs.follow = false; + state.logs.scroll_from_end = crate::config::logging::buffer().map_or(0, |b| b.len()); + } else { + state.logs.follow = true; + state.logs.scroll_from_end = 0; + } + return; + } if state.active_tab != Tab::Global && state.active_tab != Tab::Playlists { return not_yet(state, "Navigation in this view"); } @@ -737,6 +772,10 @@ fn reset_tab(state: &mut AppState, tab: Tab) { state.global.stack.clear(); } Tab::Playlists => state.playlists.opened = None, + Tab::Logs => { + state.logs.follow = true; + state.logs.scroll_from_end = 0; + } Tab::Queue | Tab::Devices => {} } } @@ -797,7 +836,7 @@ mod tests { fn tab_cycling_wraps() { let mut state = AppState::default(); update(&mut state, Action::PrevTab); - assert_eq!(state.active_tab, Tab::Devices); + assert_eq!(state.active_tab, Tab::Logs); update(&mut state, Action::NextTab); assert_eq!(state.active_tab, Tab::Global); } diff --git a/src/config/default_keymap.toml b/src/config/default_keymap.toml index d0727bb..9ec770e 100644 --- a/src/config/default_keymap.toml +++ b/src/config/default_keymap.toml @@ -1,4 +1,4 @@ -# Default keybindings for furumi-cli. +# Default keybindings for furumi. # # To customize, copy entries into /furumi/keymap.toml # (~/.config/furumi/keymap.toml on Linux/macOS). A user binding replaces the @@ -47,6 +47,10 @@ command = { GoToTab = 2 } key_sequence = "4" command = { GoToTab = 3 } +[[keymaps]] +key_sequence = "5" +command = { GoToTab = 4 } + [[keymaps]] key_sequence = "a" command = "QueueAddNext" diff --git a/src/config/keymap.rs b/src/config/keymap.rs index b2c76ef..8594781 100644 --- a/src/config/keymap.rs +++ b/src/config/keymap.rs @@ -21,6 +21,7 @@ pub enum KeyContext { Playlists, Queue, Devices, + Logs, } impl KeyContext { @@ -32,6 +33,7 @@ impl KeyContext { KeyContext::Playlists => "playlists", KeyContext::Queue => "queue", KeyContext::Devices => "devices", + KeyContext::Logs => "logs", } } } diff --git a/src/config/logging.rs b/src/config/logging.rs index ab2230f..ae6ea21 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -129,11 +129,17 @@ fn hms_now() -> String { pub fn init() -> Result<()> { let buffer = Arc::new(LogBuffer::default()); let _ = BUFFER.set(Arc::clone(&buffer)); - let memory_layer = MemoryLayer { buffer }.with_filter( - tracing_subscriber::filter::Targets::new() - .with_default(LevelFilter::INFO) - .with_target("furumi_cli", LevelFilter::TRACE), - ); + + fn memory_layer(buffer: Arc) -> impl tracing_subscriber::Layer + where + S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, + { + MemoryLayer { buffer }.with_filter( + tracing_subscriber::filter::Targets::new() + .with_default(LevelFilter::INFO) + .with_target(env!("CARGO_CRATE_NAME"), LevelFilter::TRACE), + ) + } match open_log_file() { Ok(file) => { @@ -146,13 +152,13 @@ pub fn init() -> Result<()> { .with_filter(filter); tracing_subscriber::registry() .with(file_layer) - .with(memory_layer) + .with(memory_layer(buffer)) .init(); - tracing::info!(version = env!("CARGO_PKG_VERSION"), "furumi-cli starting"); + tracing::info!(version = env!("CARGO_PKG_VERSION"), "furumi starting"); Ok(()) } Err(err) => { - tracing_subscriber::registry().with(memory_layer).init(); + tracing_subscriber::registry().with(memory_layer(buffer)).init(); tracing::warn!(%err, "log file unavailable, in-app logs only"); Err(err) } diff --git a/src/media.rs b/src/media.rs index 10d651a..0b0fd1e 100644 --- a/src/media.rs +++ b/src/media.rs @@ -88,7 +88,7 @@ pub fn run_on_main_thread( fn create_controls() -> Option { let config = PlatformConfig { display_name: "Furumi", - dbus_name: "cy.hexor.furumi_cli", + dbus_name: "cy.hexor.furumi", hwnd: None, }; if cfg!(windows) { diff --git a/src/ui/logs.rs b/src/ui/logs.rs new file mode 100644 index 0000000..9088f40 --- /dev/null +++ b/src/ui/logs.rs @@ -0,0 +1,96 @@ +use ratatui::Frame; +use ratatui::layout::{Alignment, Rect}; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line, Span}; +use ratatui::widgets::{Block, Paragraph}; + +use super::theme; +use crate::app::state::{AppState, LOG_LEVELS}; +use crate::config::logging; + +pub fn draw(frame: &mut Frame, area: Rect, state: &AppState) { + let logs = &state.logs; + let level = LOG_LEVELS[logs.level_index]; + let mode = if logs.follow { "follow" } else { "scroll" }; + let block = Block::bordered() + .title(format!(" Logs — {level}+ · {mode} ")) + .title_style(theme::header()) + .border_style(theme::dim()); + let inner = block.inner(area); + frame.render_widget(block, area); + + let Some(buffer) = logging::buffer() else { + return centered(frame, inner, "in-app log buffer unavailable"); + }; + + let visible = usize::from(inner.height.max(1)); + let skip = if logs.follow { 0 } else { logs.scroll_from_end }; + let (entries, matched) = buffer.window(level, skip, visible); + if entries.is_empty() { + return centered(frame, inner, "no log entries at this level yet"); + } + + for (row_index, entry) in entries.iter().enumerate() { + let row = Rect { + x: inner.x, + y: inner.y + row_index as u16, + width: inner.width, + height: 1, + }; + let line = Line::from(vec![ + Span::styled(format!("{} ", entry.time), theme::dim()), + level_span(entry.level), + Span::styled(format!(" {}: ", short_target(&entry.target)), theme::dim()), + Span::raw(entry.message.clone()), + ]); + frame.render_widget(Paragraph::new(line), row); + } + + // Footer hint with position info while scrolled back. + if !logs.follow && inner.height > 1 { + let footer = Rect { + y: inner.y + inner.height - 1, + height: 1, + ..inner + }; + frame.render_widget( + Paragraph::new(Line::styled( + format!(" ↑{skip} of {matched} · shift-g: follow · v: level "), + theme::tab_active(), + )) + .alignment(Alignment::Right), + footer, + ); + } +} + +fn centered(frame: &mut Frame, area: Rect, text: &str) { + if area.height == 0 { + return; + } + let middle = Rect { y: area.y + area.height / 2, height: 1, ..area }; + frame.render_widget( + Paragraph::new(Line::styled(text.to_string(), theme::dim())) + .alignment(Alignment::Center), + middle, + ); +} + +fn level_span(level: tracing::Level) -> Span<'static> { + match level { + tracing::Level::ERROR => { + Span::styled("ERROR", Style::new().fg(Color::Red).add_modifier(Modifier::BOLD)) + } + tracing::Level::WARN => Span::styled("WARN ", Style::new().fg(Color::Yellow)), + tracing::Level::INFO => Span::styled("INFO ", theme::accent()), + tracing::Level::DEBUG => Span::styled("DEBUG", theme::dim()), + tracing::Level::TRACE => Span::styled("TRACE", theme::dim()), + } +} + +/// `furumi_tui::app::update` → `app::update` — the crate prefix is noise. +fn short_target(target: &str) -> &str { + target + .split_once("::") + .map_or(target, |(_, rest)| rest) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ffa9d91..b879a9e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,7 @@ pub mod art; mod global; mod login; +mod logs; mod playlists; pub mod theme; @@ -30,6 +31,7 @@ pub fn draw(frame: &mut Frame, state: &AppState, keymap: &Keymap) { Tab::Playlists => playlists::draw(frame, main_area, state), Tab::Queue => draw_queue(frame, main_area, state), Tab::Devices => draw_main(frame, main_area, state), + Tab::Logs => logs::draw(frame, main_area, state), } draw_status(frame, status_area, state);