Compare commits

...

19 Commits
main ... devel

Author SHA1 Message Date
~erin 7ccf7f48a9
Test listing artists 2023-03-23 08:31:05 -04:00
~erin 09ccbe9cd4
Basic Search trait for lists 2023-03-22 22:17:34 -04:00
~erin ef19940418
Switch all lists over to StatefulLists 2023-03-22 21:46:38 -04:00
~erin b0b0a4e058
Implement basic state switching, different lists 2023-03-22 21:43:34 -04:00
~erin 373de985f8
List songs & play from 2023-03-22 12:40:11 -04:00
~erin 377ee3b3cd
Clean up Cargo.toml, imports 2023-03-22 08:34:48 -04:00
~erin 98884cac19
Formatting 2023-03-22 08:21:44 -04:00
~erin bfbc791a7b
Read options from configuration file 2023-03-22 00:16:00 -04:00
~erin e7fcad9049
Keybindings for controlling MPD 2023-03-21 23:27:44 -04:00
~erin 701345de29
Subcommands for controlling MPD 2023-03-21 23:24:40 -04:00
~erin 70b77cfb72
Add basic commandline options 2023-03-21 23:13:31 -04:00
~erin 1bfaecb74f
Add Makefile 2023-03-21 22:44:15 -04:00
~erin 075c56423f
Better formatted song info 2023-03-21 22:29:20 -04:00
~erin 8997dd64c1
Basic search bar input 2023-03-21 22:13:11 -04:00
~erin 6fa50943a8
Show song info 2023-03-21 21:53:37 -04:00
~erin abfccf1a17
Quit on error, log 2023-03-21 20:16:24 -04:00
~erin 1d33339439
Connect to MPD, update progress bar 2023-03-21 19:33:19 -04:00
~erin 521ca5de0a
Test stateful list 2023-03-21 19:05:35 -04:00
~erin 8e26850fb8
Basic TUI layout 2023-03-21 18:12:55 -04:00
7 changed files with 1657 additions and 16 deletions

735
Cargo.lock generated Normal file
View File

@ -0,0 +1,735 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
[[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 = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098"
dependencies = [
"bitflags 2.0.2",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "clap_lex"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi",
]
[[package]]
name = "dirs"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
dependencies = [
"libc",
"redox_users",
"windows-sys",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "io-lifetimes"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
name = "mpd"
version = "0.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a20784da57fa01bf7910a5da686d9f39ff37feaa774856b71f050e4331bf82"
dependencies = [
"bufstream",
"rustc-serialize",
"time",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "os_str_bytes"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "paris"
version = "1.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eaf2319cd71dd9ff38c72bebde61b9ea657134abcf26ae4205f54f772a32810"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustix"
version = "0.36.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.4",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.4",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "toml"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tui"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
dependencies = [
"bitflags 1.3.2",
"cassowary",
"crossterm",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[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-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[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.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "winnow"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
dependencies = [
"memchr",
]
[[package]]
name = "xenmotif"
version = "0.1.0"
dependencies = [
"clap",
"crossterm",
"dirs",
"mpd",
"paris",
"serde",
"toml",
"tui",
]

View File

@ -1,9 +1,9 @@
[package]
name = "xenmotif"
description = "A TUI client for mpd"
authors = ["Erin <contact@the-system.eu.org>"]
homepage = "https://xenmotif.the-system.eu.org"
repository = "https://git.lavender.software/erin/xenmotif"
license = "CNPLv7+"
license-file = "LICENSE.md"
keywords = ["audio", "client", "tui"]
categories = ["command-line-utilities"]
@ -17,13 +17,18 @@ notifications = []
dbus = []
images = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tui = "0.19"
crossterm = "0.25"
mpd = "*"
paris = { version = "1.5", features = ["no_logger", "macros"] }
clap = { version = "4.1.11", features = ["derive"] }
toml = "0.7.3"
serde = { version = "1.0.158", features = ["derive"] }
dirs = "5.0"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
#upx --best --lzma target/release/min-sized-rust

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
debug:
@cargo run
release:
@cargo build --release
@upx --best --lzma target/release/xenmotif

View File

@ -6,19 +6,34 @@ A simple `TUI` and commandline client for `mpd`.
- Modern features such as album art, scrobbling, notifications, etc.
- Simple configuration via a `TOML` file, and commandline options
## Configuration
If a configuration file is not found, `xenmotif` will create a new `config.toml` file in the OS config directory.
A custom configuration file can be passed as an argument, and arguments will override options in the config file.
## Keybindings
`p` - Toggle play/pause
`s` - Stop
`Tab` - Swap through search sections
`i` - Insert mode (search)
`<esc>` - Return to normal mode
`q` - Quit
## API (WIP)
### Commandline Options
`--help` - Print help options
`-h/--help` - Print help options
`-p/--port` - Specify MPD port
`--port` - Specify MPD port
`-h/--host` - Specify MPD host
`--host` - Specify MPD host
`-c/--config` - Use a specific configuration file
`-v/--version` - Print version information
`-j/--json` - Output in JSON format
`-V/--version` - Print version information
### Subcommands
`stop` - Stop playing
@ -26,7 +41,3 @@ A simple `TUI` and commandline client for `mpd`.
`play` - Start/resume playing
`pause` - Pause playing
`queue` - Print off queue
`info` - Print current song information

View File

@ -1,3 +1,562 @@
fn main() {
println!("Hello, world!");
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::{
error::Error,
fs, io,
path::PathBuf,
process::exit,
time::{Duration, Instant},
};
use tui::{
backend::{Backend, CrosstermBackend},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Style},
text::{Span, Spans},
widgets::{Block, BorderType, Borders, Gauge, List, ListItem, Paragraph, Wrap},
Frame, Terminal,
};
use mpd::{search::{Query, Term}, Song};
use clap::{Parser, Subcommand};
use paris::{error, info};
use serde::{Deserialize, Serialize};
mod structs;
use structs::{App, InputMode, StatefulList, Blocks, SongList, Search, ArtistList};
#[derive(Deserialize, Serialize)]
struct Config {
host: Option<String>,
port: Option<u16>,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// The port of the MPD server to connect to
#[arg(long)]
port: Option<u16>,
/// The IP of the MPD server to connect to
#[arg(long)]
host: Option<String>,
/// Specify a configuration file
#[arg(short, long)]
config: Option<String>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// Stop playing
Stop,
/// Start/resume playing
Play,
/// Pause playing
Pause,
}
fn main() -> Result<(), Box<dyn Error>> {
// Parse arguments
let args = Args::parse();
let mut default_config = dirs::config_dir().unwrap();
default_config.push("xenmotif");
default_config.push("config.toml");
let config_file = match args.config {
Some(file) => PathBuf::from(file),
None => default_config.clone(),
};
let config_contents = match fs::read_to_string(&config_file) {
Ok(f) => f,
Err(e) => {
error!("Unable to read config file! {}", e);
let config = Config {
host: Some("127.0.0.1".to_string()),
port: Some(6600),
};
let toml = toml::to_string(&config).unwrap();
default_config.pop();
fs::create_dir_all(&default_config).unwrap();
default_config.push("config.toml");
fs::write(&default_config, toml).unwrap();
info!("Created new config file at {}", &default_config.display());
exit(0);
}
};
let config: Config = match toml::from_str(&config_contents) {
Ok(s) => s,
Err(e) => {
error!("Couldn't parse config file! {}", e);
exit(1);
}
};
let port = match args.port {
Some(p) => p,
None => match config.port {
Some(h) => h,
None => 6600,
},
};
let host = match args.host {
Some(h) => h,
None => match config.host {
Some(h) => h,
None => "127.0.0.1".to_string(),
},
};
let mut app = App::new(port, host);
match args.command {
Some(Commands::Stop) => {
app.mpd_client.stop().unwrap();
exit(0);
}
Some(Commands::Play) => {
app.mpd_client.play().unwrap();
exit(0);
}
Some(Commands::Pause) => {
app.mpd_client.pause(true).unwrap();
exit(0);
}
None => {}
}
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// create app and run it
let tick_rate = Duration::from_millis(5);
let res = run_app(&mut terminal, app, tick_rate);
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
error!("{:?}", err)
}
Ok(())
}
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
mut app: App,
tick_rate: Duration,
) -> io::Result<()> {
let mut last_tick = Instant::now();
loop {
terminal.draw(|f| ui(f, &mut app))?;
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_millis(1));
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
match app.input_mode {
InputMode::Normal => {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('i') => app.input_mode = InputMode::Editing,
KeyCode::Char('p') => app.mpd_client.toggle_pause().unwrap(),
KeyCode::Char('s') => app.mpd_client.stop().unwrap(),
KeyCode::Tab => app.switch_block(),
KeyCode::Char('l') => {
match app.selected_block {
Blocks::Songs => {
match app.songs.choose() {
Some(s) => {
match app.mpd_client.clear() {
Ok(..) => {},
Err(e) => error!("Could not clear queue: {}", e),
}
match app.mpd_client.push(s) {
Ok(..) => {},
Err(e) => error!("Cannot play song! {}", e),
}
app.mpd_client.play().unwrap();
},
None => {},
}
},
Blocks::Artists => {
// match app.artists.choose() {
// Some(s) => {
// match app.mpd_client.clear() {
// Ok(..) => {},
// Err(e) => error!("Could not clear queue: {}", e),
// }
// match app.mpd_client.push(s) {
// Ok(..) => {},
// Err(e) => error!("Cannot play song! {}", e),
// }
// app.mpd_client.play().unwrap();
// },
// None => {},
// }
},
Blocks::Albums => {
// match app.albums.choose() {
// Some(s) => {
// match app.mpd_client.clear() {
// Ok(..) => {},
// Err(e) => error!("Could not clear queue: {}", e),
// }
// match app.mpd_client.push(s) {
// Ok(..) => {},
// Err(e) => error!("Cannot play song! {}", e),
// }
// app.mpd_client.play().unwrap();
// },
// None => {},
// }
},
}
},
KeyCode::Char('h') => {
match app.selected_block {
Blocks::Songs => app.songs.unselect(),
Blocks::Artists => app.artists.unselect(),
Blocks::Albums => app.albums.unselect(),
}
},
KeyCode::Char('j') => {
match app.selected_block {
Blocks::Songs => app.songs.next(),
Blocks::Artists => app.artists.next(),
Blocks::Albums => app.albums.next(),
}
},
KeyCode::Char('k') => {
match app.selected_block {
Blocks::Songs => app.songs.previous(),
Blocks::Artists => app.artists.previous(),
Blocks::Albums => app.albums.previous(),
}
},
_ => {},
}
},
InputMode::Editing => {
match key.code {
KeyCode::Esc => {
app.messages.push(app.input.drain(..).collect());
let last_results = match app.messages.last() {
Some(r) => r,
None => "",
};
app.last_songs = app.songs.mpd_search(&mut app.mpd_client, last_results.to_string(), (0,50));
app.last_artists = app.artists.mpd_search(&mut app.mpd_client, last_results.to_string(), (0,50));
app.artists = ArtistList::with_items(app.last_artists.clone());
app.songs = SongList::with_items(app.last_songs.clone());
app.input_mode = InputMode::Normal;
}
KeyCode::Char(c) => {
app.input.push(c);
let results = app.input.clone();
app.last_songs = app.songs.mpd_search(&mut app.mpd_client, results.to_string(), (0,50));
app.last_artists = app.artists.mpd_search(&mut app.mpd_client, results.to_string(), (0,50));
app.artists = ArtistList::with_items(app.last_artists.clone());
app.songs = SongList::with_items(app.last_songs.clone());
}
KeyCode::Backspace => {
app.input.pop();
let results = app.input.clone();
app.last_songs = app.songs.mpd_search(&mut app.mpd_client, results.to_string(), (0,50));
app.last_artists = app.artists.mpd_search(&mut app.mpd_client, results.to_string(), (0,50));
app.artists = ArtistList::with_items(app.last_artists.clone());
app.songs = SongList::with_items(app.last_songs.clone());
}
_ => {}
}
},
}
}
}
if last_tick.elapsed() >= tick_rate {
// app.on_tick();
last_tick = Instant::now();
}
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let mpd_status = match app.mpd_client.status() {
Ok(s) => s,
Err(e) => {
exit(1);
}
};
let current_song = match app.mpd_client.currentsong() {
Ok(s) => s,
Err(e) => {
error!("Couldn't get current song: {}", e);
exit(1);
}
};
// Wrapping block for a group
// Just draw the block and the group on the same area and build the group
// with at least a margin of 1
let size = f.size();
// Surrounding block
let title = Block::default()
.borders(Borders::ALL)
.title("Xenmotif")
.title_alignment(Alignment::Center)
.border_type(BorderType::Rounded);
f.render_widget(title, size);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(68),
Constraint::Percentage(2),
]
.as_ref(),
)
.split(f.size());
// Top two inner blocks
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
.split(chunks[0]);
// Song info & Search bar
let song_search = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
.margin(2)
.split(top_chunks[1]);
// Cover art block
let image = Block::default()
.title(vec![
Span::styled("With", Style::default().fg(Color::Yellow)),
Span::from(" background"),
])
.style(Style::default().bg(Color::Green));
f.render_widget(image, top_chunks[0]);
// Song/now playing
let mut title = String::new();
let song_text = match current_song {
Some(song) => {
let mut info: Vec<Spans> = vec![];
title = match song.title {
Some(s) => s,
None => "<unnamed>".to_string(),
};
let artist = match song.tags.get("AlbumArtist") {
Some(val) => Span::styled(format!("{val}"), Style::default().fg(Color::Green)),
None => Span::styled("<unknown artist>", Style::default().fg(Color::Red)),
};
let album = match song.tags.get("Album") {
Some(val) => Span::styled(format!("{val}"), Style::default().fg(Color::Cyan)),
None => Span::styled("<unknown album>", Style::default().fg(Color::Red)),
};
let date = match song.tags.get("Date") {
Some(val) => Span::styled(format!("{val}"), Style::default().fg(Color::White)),
None => Span::styled("<unknown date>", Style::default().fg(Color::Red)),
};
info.push(Spans::from(vec![
artist,
Span::raw(" - "),
album,
Span::raw(" ("),
date,
Span::raw(")"),
]));
let duration = match song.duration {
Some(s) => {
let minutes = s.num_minutes();
let seconds = s.num_seconds() - minutes * 60;
let s_seconds;
if seconds < 10 {
s_seconds = format!("0{}", seconds);
} else {
s_seconds = format!("{}", seconds);
}
format!("{}:{}", minutes, s_seconds)
}
None => "none".to_string(),
};
let elapsed = match mpd_status.time {
Some(t) => {
let minutes = t.0.num_minutes();
let seconds = t.0.num_seconds() - minutes * 60;
let s_seconds;
if seconds < 10 {
s_seconds = format!("0{}", seconds);
} else {
s_seconds = format!("{}", seconds);
}
format!("{}:{}", minutes, s_seconds)
}
None => "none".to_string(),
};
info.push(Spans::from(Span::styled(
format!("({}/{})", elapsed, duration),
Style::default(),
)));
info
}
None => vec![Spans::from(Span::styled(
"Not Playing",
Style::default().fg(Color::Red),
))],
};
let song = Paragraph::new(song_text)
.block(
Block::default()
.title(vec![
Span::styled("──┤ ", Style::default().fg(Color::White)),
Span::raw(title),
Span::styled(" ├──", Style::default().fg(Color::White)),
])
.title_alignment(Alignment::Center),
)
.style(Style::default())
.alignment(Alignment::Center)
.wrap(Wrap { trim: true });
f.render_widget(song, song_search[0]);
// Search
let input = Paragraph::new(app.input.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Search"));
f.render_widget(input, song_search[1]);
match app.input_mode {
InputMode::Normal => {}
InputMode::Editing => f.set_cursor(
song_search[1].x + app.input.len() as u16 + 1,
song_search[1].y + 1,
),
}
// Sort & View
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(1)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(30),
Constraint::Percentage(30),
]
.as_ref(),
)
.split(chunks[1]);
// Artists
let artists: Vec<ListItem> = app
.artists
.items
.iter()
.map(|i| {
let lines = vec![Spans::from(i.0.clone())];
ListItem::new(lines)
})
.collect();
let artists_block = List::new(artists)
.block(Block::default().title("Artists").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Artists => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_stateful_widget(artists_block, bottom_chunks[0], &mut app.artists.state);
// Albums
let albums: Vec<ListItem> = app
.albums
.items
.iter()
.map(|i| {
let lines = vec![Spans::from(i.0.clone())];
ListItem::new(lines)
})
.collect();
let albums_block = List::new(albums)
.block(Block::default().title("Albums").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Albums => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_stateful_widget(albums_block, bottom_chunks[1], &mut app.albums.state);
// Songs
let songs: Vec<ListItem> = app
.songs
.items
.iter()
.map(|i| {
let lines = vec![Spans::from(i.0.clone())];
ListItem::new(lines)
})
.collect();
let songs_block = List::new(songs)
.block(Block::default().title("Songs").borders(Borders::ALL)
.border_style(match app.selected_block {
Blocks::Songs => Style::default().fg(Color::Yellow),
_ => Style::default(),
}))
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">");
f.render_stateful_widget(songs_block, bottom_chunks[2], &mut app.songs.state);
// Timeline
let percent = match mpd_status.time {
Some(t) => {
let played = t.0.num_seconds() as f64;
let total = t.1.num_seconds() as f64;
let result = played / total;
result * 100.0
}
None => 0.0,
};
let timeline = Gauge::default()
.block(Block::default().borders(Borders::NONE))
.gauge_style(Style::default().fg(Color::Red))
.percent(percent as u16);
f.render_widget(timeline, chunks[2]);
}

0
src/search.rs Normal file
View File

326
src/structs.rs Normal file
View File

@ -0,0 +1,326 @@
use mpd::Client;
use tui::widgets::ListState;
use paris::error;
use std::process::exit;
use mpd::{song::Song, Query, Term};
pub trait Search {
type Item;
fn mpd_search(&self, client: &mut Client, query: String, range: (u32, u32)) -> Vec<Self::Item>;
fn fuzzy_search(&self, client: &Client, query: String, range: (u32, u32)) -> Vec<Self::Item>;
}
pub trait StatefulList {
type Item;
type List;
type Selector;
fn with_items(items: Vec<Self::Item>) -> Self::List;
fn next(&mut self);
fn previous(&mut self);
fn choose(&mut self) -> Option<Self::Selector>;
fn unselect(&mut self);
}
pub struct SongList {
pub state: ListState,
pub items: Vec<(String, usize, Option<Song>)>,
}
impl Search for SongList {
type Item = (String, usize, Option<Song>);
fn mpd_search(&self, client: &mut Client, query: String, range: (u32, u32)) -> Vec<Self::Item> {
match client.search(&Query::new().and(Term::File, query), range) {
Ok(s) => {
let mut results: Vec<(String, usize, Option<Song>)> = vec![];
let mut x = 0;
for i in s {
let name = match i.title.clone() {
Some(n) => n,
None => "<unnamed>".to_string(),
};
results.push((name, x, Some(i.clone())));
x += 1;
}
results
},
Err(e) => vec![("<no results>".to_string(), 0, None)],
}
}
fn fuzzy_search(&self, client: &Client, query: String, range: (u32, u32)) -> Vec<Self::Item> {
error!("Unimplemented!");
exit(1);
}
}
impl StatefulList for SongList {
type Item = (String, usize, Option<Song>);
type List = SongList;
type Selector = Song;
fn with_items(items: Vec<(String, usize, Option<Song>)>) -> SongList {
SongList {
state: ListState::default(),
items,
}
}
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<Song> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
};
let songs = self.items[selected].2.clone();
return songs;
}
}
pub struct ArtistList {
pub state: ListState,
pub items: Vec<(String, usize)>,
}
impl Search for ArtistList {
type Item = (String, usize);
fn mpd_search(&self, client: &mut Client, query: String, range: (u32, u32)) -> Vec<Self::Item> {
match client.list(&Term::Tag(std::borrow::Cow::Borrowed("AlbumArtist")), &Query::new().and(Term::Any, query)) {
Ok(s) => {
let mut results: Vec<(String, usize)> = vec![];
let mut x = 0;
for i in s {
error!("{}", i);
results.push((i, x));
x += 1;
}
results
},
Err(e) => vec![("<no results>".to_string(), 0)],
}
}
fn fuzzy_search(&self, client: &Client, query: String, range: (u32, u32)) -> Vec<Self::Item> {
error!("Unimplemented!");
exit(1);
}
}
impl StatefulList for ArtistList {
type Item = (String, usize);
type List = ArtistList;
type Selector = String;
fn with_items(items: Vec<(String, usize)>) -> ArtistList {
ArtistList {
state: ListState::default(),
items,
}
}
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<String> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
};
let artist = self.items[selected].1.clone();
return Some(artist.to_string());
}
}
pub struct AlbumList {
pub state: ListState,
pub items: Vec<(String, usize)>,
}
impl StatefulList for AlbumList {
type Item = (String, usize);
type List = AlbumList;
type Selector = String;
fn with_items(items: Vec<(String, usize)>) -> AlbumList {
AlbumList {
state: ListState::default(),
items,
}
}
fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
fn unselect(&mut self) {
self.state.select(None);
}
fn choose(&mut self) -> Option<String> {
let selected = match self.state.selected() {
Some(i) => i,
None => 0,
};
let album = self.items[selected].0.clone();
return Some(album);
}
}
/// This struct holds the current state of the app. In particular, it has the `items` field which is a wrapper
/// around `ListState`. Keeping track of the items state let us render the associated widget with its state
/// and have access to features such as natural scrolling.
///
/// Check the event handling at the bottom to see how to change the state on incoming events.
/// Check the drawing logic for items on how to specify the highlighting style for selected items.
pub struct App {
pub songs: SongList,
pub last_songs: Vec<(String, usize, Option<Song>)>,
pub artists: ArtistList,
pub last_artists: Vec<(String, usize)>,
pub albums: AlbumList,
pub last_albums: Vec<(String, usize)>,
pub mpd_client: Client,
pub input: String,
pub input_mode: InputMode,
pub selected_block: Blocks,
pub messages: Vec<String>,
}
impl App {
pub fn switch_block(&mut self) {
let current_block = &self.selected_block;
self.selected_block = match current_block {
Blocks::Artists => Blocks::Albums,
Blocks::Albums => Blocks::Songs,
Blocks::Songs => Blocks::Artists,
};
}
pub fn new(port: u16, host: String) -> App {
let client = match Client::connect(format!("{}:{}", host, port)) {
Ok(conn) => conn,
Err(e) => {
error!("Could not connect to MPD daemon: {}", e);
exit(1);
}
};
App {
songs: SongList::with_items(vec![]),
last_songs: vec![],
artists: ArtistList::with_items(vec![]),
last_artists: vec![],
albums: AlbumList::with_items(vec![]),
last_albums: vec![],
mpd_client: client,
input: String::new(),
input_mode: InputMode::Normal,
selected_block: Blocks::Songs,
messages: Vec::new(),
}
}
}
pub enum InputMode {
Normal,
Editing,
}
pub enum Blocks {
Artists,
Albums,
Songs,
}