Initial commit
* Working UI for minimal MPD client * Header, Now Playing and Playlist panels * ? toggles keyboard shortcut help
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /target | ||||
							
								
								
									
										643
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										643
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,643 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "allocator-api2" | ||||
| version = "0.2.21" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | ||||
|  | ||||
| [[package]] | ||||
| name = "anyhow" | ||||
| version = "1.0.98" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" | ||||
|  | ||||
| [[package]] | ||||
| name = "autocfg" | ||||
| version = "1.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "2.9.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" | ||||
|  | ||||
| [[package]] | ||||
| name = "bufstream" | ||||
| version = "0.1.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" | ||||
|  | ||||
| [[package]] | ||||
| name = "cassowary" | ||||
| version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" | ||||
|  | ||||
| [[package]] | ||||
| name = "castaway" | ||||
| version = "0.2.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" | ||||
| dependencies = [ | ||||
|  "rustversion", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" | ||||
|  | ||||
| [[package]] | ||||
| name = "compact_str" | ||||
| version = "0.7.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" | ||||
| dependencies = [ | ||||
|  "castaway", | ||||
|  "cfg-if", | ||||
|  "itoa", | ||||
|  "ryu", | ||||
|  "static_assertions", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossterm" | ||||
| version = "0.27.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "crossterm_winapi", | ||||
|  "libc", | ||||
|  "mio", | ||||
|  "parking_lot", | ||||
|  "signal-hook", | ||||
|  "signal-hook-mio", | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossterm_winapi" | ||||
| version = "0.9.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" | ||||
| dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "deranged" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" | ||||
| dependencies = [ | ||||
|  "powerfmt", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.15.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" | ||||
|  | ||||
| [[package]] | ||||
| name = "equivalent" | ||||
| version = "1.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||||
|  | ||||
| [[package]] | ||||
| name = "foldhash" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" | ||||
|  | ||||
| [[package]] | ||||
| name = "hashbrown" | ||||
| version = "0.15.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" | ||||
| dependencies = [ | ||||
|  "allocator-api2", | ||||
|  "equivalent", | ||||
|  "foldhash", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "heck" | ||||
| version = "0.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "itoa" | ||||
| version = "1.0.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" | ||||
|  | ||||
| [[package]] | ||||
| name = "libc" | ||||
| version = "0.2.174" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" | ||||
|  | ||||
| [[package]] | ||||
| name = "lock_api" | ||||
| version = "0.4.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "scopeguard", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "log" | ||||
| version = "0.4.27" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" | ||||
|  | ||||
| [[package]] | ||||
| name = "lru" | ||||
| version = "0.12.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" | ||||
| dependencies = [ | ||||
|  "hashbrown", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "mio" | ||||
| version = "0.8.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "log", | ||||
|  "wasi", | ||||
|  "windows-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "mpd" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2e12cace5746cb0aa78faa6cd2caec9f9c01882fc0e6b54d34685a2d3303ea34" | ||||
| dependencies = [ | ||||
|  "bufstream", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "num-conv" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" | ||||
|  | ||||
| [[package]] | ||||
| name = "num_threads" | ||||
| version = "0.1.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "parking_lot" | ||||
| version = "0.12.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" | ||||
| dependencies = [ | ||||
|  "lock_api", | ||||
|  "parking_lot_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "parking_lot_core" | ||||
| version = "0.9.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "libc", | ||||
|  "redox_syscall", | ||||
|  "smallvec", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "paste" | ||||
| version = "1.0.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" | ||||
|  | ||||
| [[package]] | ||||
| name = "powerfmt" | ||||
| version = "0.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | ||||
|  | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.95" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" | ||||
| dependencies = [ | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.40" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ratatui" | ||||
| version = "0.27.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "cassowary", | ||||
|  "compact_str", | ||||
|  "crossterm", | ||||
|  "itertools", | ||||
|  "lru", | ||||
|  "paste", | ||||
|  "stability", | ||||
|  "strum", | ||||
|  "strum_macros", | ||||
|  "time", | ||||
|  "unicode-segmentation", | ||||
|  "unicode-truncate", | ||||
|  "unicode-width", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "redox_syscall" | ||||
| version = "0.5.16" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rsmpd" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "crossterm", | ||||
|  "mpd", | ||||
|  "ratatui", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustversion" | ||||
| version = "1.0.21" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" | ||||
|  | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.20" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" | ||||
|  | ||||
| [[package]] | ||||
| name = "scopeguard" | ||||
| version = "1.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | ||||
|  | ||||
| [[package]] | ||||
| name = "serde" | ||||
| version = "1.0.219" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" | ||||
| dependencies = [ | ||||
|  "serde_derive", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_derive" | ||||
| version = "1.0.219" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "signal-hook" | ||||
| version = "0.3.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "signal-hook-registry", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "signal-hook-mio" | ||||
| version = "0.2.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "mio", | ||||
|  "signal-hook", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "signal-hook-registry" | ||||
| version = "1.4.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "smallvec" | ||||
| version = "1.15.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" | ||||
|  | ||||
| [[package]] | ||||
| name = "stability" | ||||
| version = "0.2.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" | ||||
| dependencies = [ | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "static_assertions" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" | ||||
|  | ||||
| [[package]] | ||||
| name = "strum" | ||||
| version = "0.26.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" | ||||
| dependencies = [ | ||||
|  "strum_macros", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "strum_macros" | ||||
| version = "0.26.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" | ||||
| dependencies = [ | ||||
|  "heck", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "rustversion", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.104" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "time" | ||||
| version = "0.3.41" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" | ||||
| dependencies = [ | ||||
|  "deranged", | ||||
|  "libc", | ||||
|  "num-conv", | ||||
|  "num_threads", | ||||
|  "powerfmt", | ||||
|  "serde", | ||||
|  "time-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "time-core" | ||||
| version = "0.1.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-ident" | ||||
| version = "1.0.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-segmentation" | ||||
| version = "1.12.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-truncate" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" | ||||
| dependencies = [ | ||||
|  "itertools", | ||||
|  "unicode-segmentation", | ||||
|  "unicode-width", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-width" | ||||
| version = "0.1.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" | ||||
|  | ||||
| [[package]] | ||||
| name = "wasi" | ||||
| version = "0.11.1+wasi-snapshot-preview1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi" | ||||
| version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | ||||
| dependencies = [ | ||||
|  "winapi-i686-pc-windows-gnu", | ||||
|  "winapi-x86_64-pc-windows-gnu", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi-i686-pc-windows-gnu" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi-x86_64-pc-windows-gnu" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||
|  | ||||
| [[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-targets" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm 0.48.5", | ||||
|  "windows_aarch64_msvc 0.48.5", | ||||
|  "windows_i686_gnu 0.48.5", | ||||
|  "windows_i686_msvc 0.48.5", | ||||
|  "windows_x86_64_gnu 0.48.5", | ||||
|  "windows_x86_64_gnullvm 0.48.5", | ||||
|  "windows_x86_64_msvc 0.48.5", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-targets" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm 0.52.6", | ||||
|  "windows_aarch64_msvc 0.52.6", | ||||
|  "windows_i686_gnu 0.52.6", | ||||
|  "windows_i686_gnullvm", | ||||
|  "windows_i686_msvc 0.52.6", | ||||
|  "windows_x86_64_gnu 0.52.6", | ||||
|  "windows_x86_64_gnullvm 0.52.6", | ||||
|  "windows_x86_64_msvc 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||||
							
								
								
									
										10
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| [package] | ||||
| name = "rsmpd" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| mpd = "*" | ||||
| crossterm = "0.27.0" | ||||
| ratatui = { version = "0.27.0", features = ["all-widgets"] } | ||||
| anyhow = "*" | ||||
							
								
								
									
										6
									
								
								scripts/vscode
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								scripts/vscode
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| /opt/vscode/VSCode-linux-x64/code \ | ||||
|     --enable-features=UseOzonePlatform,WaylandWindowDecorations \ | ||||
|     --ozone-platform-hint=auto \ | ||||
|     --unity-launch %F . & disown %1 | ||||
							
								
								
									
										359
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,359 @@ | ||||
| // src/main.rs | ||||
|  | ||||
| use crossterm::{ | ||||
|     event::{self, Event, KeyCode}, | ||||
|     execute, | ||||
|     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, | ||||
| }; | ||||
| use mpd::{Client, Song, State}; | ||||
| use ratatui::{ | ||||
|     backend::Backend, | ||||
|     layout::{Constraint, Direction, Layout, Margin, Alignment}, | ||||
|     style::{Color, Modifier, Style}, | ||||
|     widgets::{Block, Borders, List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState}, | ||||
|     Frame, Terminal, | ||||
| }; | ||||
| use ratatui::backend::CrosstermBackend; | ||||
| use std::{io, time::Duration}; | ||||
|  | ||||
| /// Application state | ||||
| struct App { | ||||
|     client: Client, | ||||
|     current_song: Option<Song>, | ||||
|     playlist: Vec<Song>, | ||||
|     status: mpd::Status, | ||||
|     selected_index: usize,  // Currently selected song in the playlist | ||||
|     scroll_offset: usize,   // Offset for playlist scrolling | ||||
|     show_help: bool,       // Whether to show the help panel | ||||
| } | ||||
|  | ||||
| impl App { | ||||
|     /// Create a new App instance, connecting to MPD | ||||
|     fn new() -> Result<Self, anyhow::Error> { | ||||
|         let mut client = Client::connect("127.0.0.1:6600")?; | ||||
|         let status = client.status()?; | ||||
|         let playlist = client.queue()?; | ||||
|         let current_song = client.currentsong()?; | ||||
|  | ||||
|         Ok(Self { | ||||
|             client, | ||||
|             current_song, | ||||
|             playlist, | ||||
|             status, | ||||
|             selected_index: 0, | ||||
|             scroll_offset: 0, | ||||
|             show_help: false, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Refresh the application state from MPD | ||||
|     fn refresh(&mut self, playlist_height: usize) -> Result<(), anyhow::Error> { | ||||
|         self.status = self.client.status()?; | ||||
|         self.current_song = self.client.currentsong()?; | ||||
|          | ||||
|         // Get the initial queue | ||||
|         if let Ok(queue) = self.client.queue() { | ||||
|             // Process the queue with a temporary vector to avoid mutations while iterating | ||||
|             let mut processed: Vec<Song> = Vec::with_capacity(queue.len()); | ||||
|              | ||||
|             // Only add songs we haven't seen before | ||||
|             for song in queue { | ||||
|                 let duplicate = processed.iter().any(|s: &Song| { | ||||
|                     // Two songs are the same if they have the same file path | ||||
|                     song.file == s.file | ||||
|                 }); | ||||
|                  | ||||
|                 if !duplicate { | ||||
|                     processed.push(song); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             self.playlist = processed; | ||||
|         } else { | ||||
|             self.playlist.clear(); | ||||
|         } | ||||
|          | ||||
|         // Update scroll position | ||||
|         self.update_scroll(playlist_height); | ||||
|          | ||||
|         Ok(()) | ||||
|     } | ||||
|      | ||||
|     /// Move selection up, adjusting scroll if necessary | ||||
|     fn move_up(&mut self) { | ||||
|         if self.selected_index > 0 { | ||||
|             self.selected_index -= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Move selection down, adjusting scroll if necessary | ||||
|     fn move_down(&mut self) { | ||||
|         if self.selected_index + 1 < self.playlist.len() { | ||||
|             self.selected_index += 1; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     /// Update scroll offset to ensure selected item is visible | ||||
|     fn update_scroll(&mut self, height: usize) { | ||||
|         if self.selected_index >= self.scroll_offset + height { | ||||
|             self.scroll_offset = self.selected_index.saturating_sub(height) + 1; | ||||
|         } else if self.selected_index < self.scroll_offset { | ||||
|             self.scroll_offset = self.selected_index; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() -> anyhow::Result<()> { | ||||
|     // Setup terminal | ||||
|     enable_raw_mode()?; | ||||
|     let mut stdout = io::stdout(); | ||||
|     execute!(stdout, EnterAlternateScreen)?; | ||||
|     let backend = CrosstermBackend::new(stdout); | ||||
|     let mut terminal = Terminal::new(backend)?; | ||||
|  | ||||
|     // Create app and get initial height | ||||
|     let mut app = App::new()?; | ||||
|     let height = terminal.size()?.height.saturating_sub(12) as usize; | ||||
|     app.refresh(height)?; | ||||
|      | ||||
|     // Run the main loop | ||||
|     run_app(&mut terminal, &mut app)?; | ||||
|  | ||||
|     // Restore terminal | ||||
|     disable_raw_mode()?; | ||||
|     execute!(terminal.backend_mut(), LeaveAlternateScreen,)?; | ||||
|     terminal.show_cursor()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> anyhow::Result<()> { | ||||
|     loop { | ||||
|         // Get the available height for the playlist | ||||
|         let height = terminal.size()?.height.saturating_sub(12) as usize; | ||||
|         app.refresh(height)?; | ||||
|          | ||||
|         terminal.draw(|f| ui(f, app))?; | ||||
|  | ||||
|         if event::poll(Duration::from_millis(250))? { | ||||
|             if let Event::Key(key) = event::read()? { | ||||
|                 match key.code { | ||||
|                     KeyCode::Char('q') => return Ok(()), | ||||
|                     KeyCode::Char('p') => { | ||||
|                         if app.status.state == State::Play { | ||||
|                             app.client.pause(true)?; | ||||
|                         } else { | ||||
|                             app.client.play()?; | ||||
|                         } | ||||
|                     } | ||||
|                     KeyCode::Char('s') => app.client.stop()?, | ||||
|                     KeyCode::Char('c') => { | ||||
|                         app.client.clear()?; | ||||
|                         app.client.update()?; | ||||
|                         app.client.play()?; | ||||
|                     } | ||||
|                     // Navigation | ||||
|                     KeyCode::Up => app.move_up(), | ||||
|                     KeyCode::Down => app.move_down(), | ||||
|                     // Play selected song | ||||
|                     KeyCode::Enter => { | ||||
|                         if !app.playlist.is_empty() { | ||||
|                             // Play the selected song by its position in the playlist | ||||
|                             app.client.switch(app.selected_index as u32)?; | ||||
|                             app.client.play()?; | ||||
|                         } | ||||
|                     } | ||||
|                     // Volume control | ||||
|                     KeyCode::Char('+') | KeyCode::Char('=') => { | ||||
|                         let new_vol = (app.status.volume + 5).min(100); | ||||
|                         app.client.volume(new_vol)?; | ||||
|                     } | ||||
|                     KeyCode::Char('-') => { | ||||
|                         let new_vol = app.status.volume.saturating_sub(5); | ||||
|                         app.client.volume(new_vol)?; | ||||
|                     } | ||||
|                     KeyCode::Char('?') => { | ||||
|                         app.show_help = !app.show_help; | ||||
|                     } | ||||
|                     _ => () | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Helper function to create a centered rect using up certain percentage of the available rect | ||||
| fn centered_rect(percent_x: u16, percent_y: u16, r: ratatui::layout::Rect) -> ratatui::layout::Rect { | ||||
|     // Calculate the width and height of the popup | ||||
|     let popup_layout = Layout::default() | ||||
|         .direction(Direction::Vertical) | ||||
|         .constraints([ | ||||
|             Constraint::Percentage((100 - percent_y) / 2), | ||||
|             Constraint::Percentage(percent_y), | ||||
|             Constraint::Percentage((100 - percent_y) / 2), | ||||
|         ].as_ref()) | ||||
|         .split(r); | ||||
|  | ||||
|     Layout::default() | ||||
|         .direction(Direction::Horizontal) | ||||
|         .constraints([ | ||||
|             Constraint::Percentage((100 - percent_x) / 2), | ||||
|             Constraint::Percentage(percent_x), | ||||
|             Constraint::Percentage((100 - percent_x) / 2), | ||||
|         ].as_ref()) | ||||
|         .split(popup_layout[1])[1] | ||||
| } | ||||
|  | ||||
| pub(crate) fn ui(f: &mut Frame, app: &App) { | ||||
|     let chunks = Layout::default() | ||||
|         .direction(Direction::Vertical) | ||||
|         .constraints([ | ||||
|             Constraint::Length(3),  // Header | ||||
|             Constraint::Length(10), // Status | ||||
|             Constraint::Min(0)      // Playlist | ||||
|         ].as_ref()) | ||||
|         .split(f.size()); | ||||
|  | ||||
|     // --- Header Widget --- | ||||
|     let header = Paragraph::new("Sonnet MPD") | ||||
|         .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)) | ||||
|         .alignment(ratatui::layout::Alignment::Center) | ||||
|         .block(Block::default() | ||||
|             .borders(Borders::ALL) | ||||
|             .border_style(Style::default().fg(Color::Cyan))); | ||||
|      | ||||
|     f.render_widget(header, chunks[0]); | ||||
|  | ||||
|     // --- Status and Current Song Widget --- | ||||
|     let status_text = format!( | ||||
|         "State: {:?}\nVolume: {}%\nRepeat: {:?}, Random: {:?}", | ||||
|         app.status.state, app.status.volume, app.status.repeat, app.status.random | ||||
|     ); | ||||
|  | ||||
|     let song_title = if let Some(song) = &app.current_song { | ||||
|         song.title.as_deref().unwrap_or("Unknown Title") | ||||
|     } else { | ||||
|         "None" | ||||
|     }; | ||||
|  | ||||
|     let status_block = Block::default().title("Status").borders(Borders::ALL); | ||||
|     let status_paragraph = Paragraph::new(status_text).block(status_block); | ||||
|     f.render_widget(status_paragraph, chunks[1]); | ||||
|  | ||||
|     let current_song_block = Block::default() | ||||
|         .title("Now Playing") | ||||
|         .borders(Borders::ALL); | ||||
|     let song_paragraph = Paragraph::new(song_title) | ||||
|         .style(Style::default().fg(Color::Yellow)) | ||||
|         .block(current_song_block); | ||||
|      | ||||
|     // We can render one widget on top of another by rendering it on the same area | ||||
|     f.render_widget(song_paragraph, chunks[1]); | ||||
|  | ||||
|  | ||||
|     // --- Playlist Widget --- | ||||
|     // Get visible portion of the playlist | ||||
|     let visible_height = chunks[2].height.saturating_sub(2) as usize; | ||||
|  | ||||
|     let playlist_items: Vec<ListItem> = app | ||||
|         .playlist | ||||
|         .iter() | ||||
|         .enumerate() | ||||
|         .skip(app.scroll_offset) | ||||
|         .take(visible_height) | ||||
|         .map(|(i, song)| { | ||||
|             let title = song.title.as_deref().unwrap_or("Unknown"); | ||||
|             let artist = song.tags.iter() | ||||
|                 .find(|(key, _)| key == "Artist") | ||||
|                 .map(|(_, val)| val.as_str()) | ||||
|                 .unwrap_or("Unknown"); | ||||
|             let album = song.tags.iter() | ||||
|                 .find(|(key, _)| key == "Album") | ||||
|                 .map(|(_, val)| val.as_str()) | ||||
|                 .unwrap_or("Unknown"); | ||||
|              | ||||
|             let line = format!("{}. {} - {} ({})", i + 1, title, artist, album); | ||||
|             let mut list_item = ListItem::new(line); | ||||
|  | ||||
|             // Style for the selected item | ||||
|             if i == app.selected_index { | ||||
|                 list_item = list_item.style(Style::default().add_modifier(Modifier::REVERSED)); | ||||
|             } | ||||
|  | ||||
|             // Additional highlight for the currently playing song | ||||
|             if let Some(current) = &app.current_song { | ||||
|                 if current.file == song.file { | ||||
|                     list_item = list_item.style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)); | ||||
|                 } | ||||
|             } | ||||
|             list_item | ||||
|         }) | ||||
|         .collect(); | ||||
|  | ||||
|     let playlist_list = List::new(playlist_items) | ||||
|         .block(Block::default().title(format!("Playlist ({} songs)", app.playlist.len())).borders(Borders::ALL)) | ||||
|         .highlight_style(Style::default().add_modifier(Modifier::BOLD)) | ||||
|         .highlight_symbol(">> "); | ||||
|  | ||||
|     f.render_widget(playlist_list.clone(), chunks[2]); | ||||
|  | ||||
|     // Add a scrollbar if there are more items than visible space | ||||
|     if app.playlist.len() > visible_height { | ||||
|         let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) | ||||
|             .track_style(Style::default().fg(Color::DarkGray)) | ||||
|             .thumb_style(Style::default().fg(Color::White)); | ||||
|              | ||||
|         let scrollbar_area = chunks[2].inner(Margin {  | ||||
|             vertical: 1,  | ||||
|             horizontal: 0  | ||||
|         }); | ||||
|          | ||||
|         // Calculate maximum scroll position (total items - visible items) | ||||
|         let max_scroll = app.playlist.len().saturating_sub(visible_height); | ||||
|          | ||||
|         // Normalize scroll position to be between 0 and max_scroll | ||||
|         let scroll_pos = app.scroll_offset.min(max_scroll); | ||||
|          | ||||
|         let mut scrollbar_state = ScrollbarState::default() | ||||
|             .content_length(max_scroll + 1) // +1 because we need to include the last position | ||||
|             .position(scroll_pos); | ||||
|          | ||||
|         f.render_stateful_widget( | ||||
|             scrollbar, | ||||
|             scrollbar_area, | ||||
|             &mut scrollbar_state | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // Show help panel if enabled | ||||
|     if app.show_help { | ||||
|         let help_text = vec![ | ||||
|             "Keyboard Shortcuts", | ||||
|             "─────────────────", | ||||
|             "", | ||||
|             "q    Quit", | ||||
|             "p    Play/Pause", | ||||
|             "s    Stop", | ||||
|             "c    Clear playlist", | ||||
|             "↑    Move selection up", | ||||
|             "↓    Move selection down", | ||||
|             "⏎    Play selected song", | ||||
|             "+/=  Volume up", | ||||
|             "-    Volume down", | ||||
|             "?    Toggle this help panel", | ||||
|         ].join("\n"); | ||||
|  | ||||
|         let area = centered_rect(50, 60, f.size()); | ||||
|          | ||||
|         let help_paragraph = Paragraph::new(help_text) | ||||
|             .block(Block::default() | ||||
|                 .title("Help") | ||||
|                 .borders(Borders::ALL) | ||||
|                 .border_style(Style::default().fg(Color::Cyan))) | ||||
|             .style(Style::default().fg(Color::White)) | ||||
|             .alignment(Alignment::Left); | ||||
|              | ||||
|         f.render_widget(ratatui::widgets::Clear, area); // Clear the background | ||||
|         f.render_widget(help_paragraph, area); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user