diff --git a/Cargo.lock b/Cargo.lock index 1d0fd7b..528bca8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,7 +229,7 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2 0.5.3", - "time", + "time 0.3.20", "url", ] @@ -337,6 +337,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -349,6 +364,108 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.37.23", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "autocfg" version = "1.1.0" @@ -397,6 +514,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite", + "log", +] + [[package]] name = "brotli" version = "3.3.4" @@ -418,6 +550,18 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.4.0" @@ -458,6 +602,30 @@ dependencies = [ "path-slash", ] +[[package]] +name = "chrono" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "windows-targets 0.48.5", +] + +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -471,10 +639,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time", + "time 0.3.20", "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.6" @@ -535,6 +709,7 @@ dependencies = [ "anyhow", "futures", "lazy_static", + "mpris-dbus", "serde", "serde_json", "static-files", @@ -551,7 +726,18 @@ dependencies = [ "tracing-subscriber", "win-gsmtc", "win-wrapper", - "windows", + "windows 0.51.1", +] + +[[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]] @@ -586,12 +772,75 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enumflags2" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "equivalent" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[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 = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[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.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "flate2" version = "1.0.25" @@ -665,6 +914,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[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-macro" version = "0.3.28" @@ -724,7 +988,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -779,6 +1043,18 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.9" @@ -808,6 +1084,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows 0.48.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.3.0" @@ -838,6 +1137,26 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +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.2", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.6" @@ -853,6 +1172,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -871,6 +1199,18 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "local-channel" version = "0.1.3" @@ -923,6 +1263,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -956,10 +1305,38 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] +[[package]] +name = "mpris-dbus" +version = "0.1.0" +dependencies = [ + "chrono", + "derive_more", + "futures", + "serde", + "tap", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", + "zbus", +] + +[[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", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -970,13 +1347,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -995,12 +1381,28 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[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 = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1019,7 +1421,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -1089,12 +1491,38 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[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 = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[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", +] + [[package]] name = "proc-macro2" version = "1.0.63" @@ -1152,6 +1580,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.7.3" @@ -1193,6 +1630,33 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.13" @@ -1242,6 +1706,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -1283,6 +1758,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1338,6 +1823,12 @@ dependencies = [ "path-slash", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.109" @@ -1366,6 +1857,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand 2.0.0", + "redox_syscall 0.3.5", + "rustix 0.38.11", + "windows-sys 0.48.0", +] + [[package]] name = "thiserror" version = "1.0.48" @@ -1396,6 +1900,17 @@ dependencies = [ "once_cell", ] +[[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 = "time" version = "0.3.20" @@ -1454,6 +1969,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.3", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -1571,7 +2087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time", + "time 0.3.20", "tracing-subscriber", ] @@ -1631,6 +2147,16 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicase" version = "2.6.0" @@ -1693,12 +2219,78 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[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 = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + [[package]] name = "win-gsmtc" version = "0.1.0" @@ -1708,14 +2300,14 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows", + "windows 0.51.1", ] [[package]] name = "win-wrapper" version = "0.1.0" dependencies = [ - "windows", + "windows 0.51.1", ] [[package]] @@ -1740,6 +2332,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.51.1" @@ -1900,6 +2501,77 @@ dependencies = [ "memchr", ] +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "zbus" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +dependencies = [ + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "byteorder", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" @@ -1929,3 +2601,41 @@ dependencies = [ "libc", "pkg-config", ] + +[[package]] +name = "zvariant" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +dependencies = [ + "proc-macro-crate", + "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 fae8c28..888e9a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/Nerixyz/current-song2" categories = ["multimedia"] # TODO: find something more fitting [workspace] -members = [".", "lib/win-gsmtc", "lib/win-wrapper"] +members = [".", "lib/win-gsmtc", "lib/win-wrapper", "lib/mpris-dbus"] [features] # When enabled, the overlay will be bundled into the executable. @@ -58,5 +58,8 @@ win-gsmtc = { path = "lib/win-gsmtc" } win-wrapper = { path = "lib/win-wrapper" } windows = "0.51" +[target.'cfg(unix)'.dependencies] +mpris-dbus = { path = "lib/mpris-dbus" } + [build-dependencies] actix-web-static-files = "4.0" diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c044a47..1325990 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -37,6 +37,9 @@ markdown_extensions: - attr_list - footnotes - md_in_html + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.highlight: anchor_linenums: true - pymdownx.snippets diff --git a/docs/src/Configuration.md b/docs/src/Configuration.md index e963f22..ec78538 100644 --- a/docs/src/Configuration.md +++ b/docs/src/Configuration.md @@ -9,6 +9,7 @@ The configuration uses the [toml](https://toml.io) format. The default configura ```toml no_autostart = true +# GSMTC is Windows only [modules.gsmtc] enabled = true @@ -16,6 +17,11 @@ enabled = true mode = "Exclude" items = ["chrome.exe", "msedge.exe", "firefox.exe"] +# DBus is Unix only +[modules.dbus] +enabled = true +destinations = ["org.mpris.MediaPlayer2.spotify"] + [modules.file] enabled = false @@ -35,7 +41,7 @@ The server executable always searches for the configuration file next to itself: ╰─ config.toml ``` -## `no_autostart` +## `no_autostart` :fontawesome-brands-windows: This flag controls if the application will try to add itself to autostart. @@ -43,7 +49,7 @@ This flag controls if the application will try to add itself to autostart. - If it's `false` (default), then it **will** check the autostart and possibly add itself there. You can still disable the entry on the _Task Manager_'s _Autostart_ tab since this is independent of the actual registry entry. -## GSMTC (Global System Media Transport Controls) +## GSMTC (Global System Media Transport Controls) :fontawesome-brands-windows: GSMTC uses Windows' own media tracking to provide metadata. However, not every application emits metadata to this system or only limited metadata (specifically browsers; that's why they're excluded by default). @@ -89,6 +95,27 @@ three modes: `Disabled`,`Include`, and `Exclude`: Controls whether the module should be enabled or not. +## D-Bus :fontawesome-brands-linux: + +The D-Bus module and collect metadata from any player implementing the [Media Player Remote Interfacing Specification (MPRIS)](https://specifications.freedesktop.org/mpris-spec/latest/). Most players support MPRIS (see [_Supported Clients_](https://wiki.archlinux.org/title/MPRIS#Supported_clients)). To collect metadata from a specific client, add its destination to [`destinations`](#destinations). + +The following [metadata](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata) is collected (when available): + +- [`mpris:length`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#mpris:length) +- [`xesam:title`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:title) +- [`xesam:album`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:album) +- [`xesam:trackNumber`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:tracknumber) +- [`xesam:artist`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#xesam:artist) (joined by `, `) +- [`mpris:artUrl`](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/#mpris:arturl) + +### `destinations` + +A list of destinations to listen to. Often this is the player name in lower-case prefixed with `org.mpris.MediaPlayer2.`. Each destination becomes a source formatted as `dbus::{destination}`. + +### `is_enabled` + +Controls whether the module should be enabled or not. + ## Server ### `custom_theme_path` diff --git a/lib/mpris-dbus/Cargo.toml b/lib/mpris-dbus/Cargo.toml new file mode 100644 index 0000000..3227245 --- /dev/null +++ b/lib/mpris-dbus/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mpris-dbus" +version = "0.1.0" +edition = "2021" +description = "A wrapper around the org.mpris.MediaPlayer2.Player DBus interface" +license = "MIT OR Apache-2.0" +repository = "https://github.com/Nerixyz/current-song2" +keywords = ["unix", "spotify", "dbus"] +categories = ["multimedia"] +readme = "README.md" + +[dependencies] +chrono = "0.4" +derive_more = "0.99.17" +futures = "0.3" +serde = { version = "1", features = ["derive"] } +tap = "1" +thiserror = "1" +tokio = { version = "1.32", features = ["sync", "rt", "macros"] } +tracing = "0.1" +zbus = { version = "3.14", default-features = false, features = ["tokio"] } + +[dev-dependencies] +tokio = { version = "1.32", features = ["sync", "macros", "rt-multi-thread"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/lib/mpris-dbus/examples/listen.rs b/lib/mpris-dbus/examples/listen.rs new file mode 100644 index 0000000..44cb8a4 --- /dev/null +++ b/lib/mpris-dbus/examples/listen.rs @@ -0,0 +1,16 @@ +use mpris_dbus::player; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + let mut listener = player::listen("org.mpris.MediaPlayer2.spotify") + .await + .unwrap(); + println!("Waiting for events..."); + while let Some(state) = listener.recv().await { + println!("{state:#?}"); + } +} diff --git a/lib/mpris-dbus/src/interface.rs b/lib/mpris-dbus/src/interface.rs new file mode 100644 index 0000000..f63437f --- /dev/null +++ b/lib/mpris-dbus/src/interface.rs @@ -0,0 +1,128 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use zbus::{dbus_proxy, fdo, zvariant}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, zvariant::Type, Deserialize, Serialize)] +#[zvariant(signature = "s")] +pub enum PlaybackStatus { + Playing, + Paused, + #[default] + Stopped, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, zvariant::Type, Deserialize, Serialize)] +#[zvariant(signature = "s")] +pub enum LoopStatus { + #[default] + None, + Track, + Playlist, +} + +#[dbus_proxy( + interface = "org.mpris.MediaPlayer2.Player", + default_path = "/org/mpris/MediaPlayer2", + gen_blocking = false +)] +trait SpotifyPlayer { + // Methods + fn next(&self) -> fdo::Result<()>; + fn previous(&self) -> fdo::Result<()>; + fn pause(&self) -> fdo::Result<()>; + fn play_pause(&self) -> fdo::Result<()>; + fn stop(&self) -> fdo::Result<()>; + fn play(&self) -> fdo::Result<()>; + fn seek(&self, offset: i64) -> fdo::Result<()>; + fn set_position(&self, track_id: zvariant::ObjectPath<'_>, position: i64) -> fdo::Result<()>; + fn open_uri(&self, uri: zvariant::Str<'_>) -> fdo::Result<()>; + + // Signals + #[dbus_proxy(signal)] + fn seeked(&self, position: i64) -> fdo::Result<()>; + + // Properties + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn playback_status(&self) -> fdo::Result; + + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn loop_status(&self) -> fdo::Result; + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn set_loop_status(&self, status: LoopStatus) -> fdo::Result<()>; + + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn rate(&self) -> fdo::Result; + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn set_rate(&self, rate: f64) -> fdo::Result<()>; + + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn shuffle(&self) -> fdo::Result; + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn set_shuffle(&self, enabled: bool) -> fdo::Result<()>; + + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn volume(&self) -> fdo::Result; + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn set_volume(&self, volume: f64) -> fdo::Result<()>; + + #[dbus_proxy(property(emits_changed_signal = "true"))] + fn metadata(&self) -> fdo::Result, zvariant::Value>>; + + #[dbus_proxy(property(emits_changed_signal = "false"))] + fn position(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn minimum_rate(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn maximum_rate(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_go_next(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_go_previous(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_play(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_pause(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_seek(&self) -> fdo::Result; + #[dbus_proxy(property)] + fn can_control(&self) -> fdo::Result; +} + +macro_rules! string_enum_conversions { + ($en:ty; $($val:ident => $str:literal),+) => { + impl<'a> From<$en> for zvariant::Value<'a> { + fn from(value: $en) -> Self { + zvariant::Value::Str(zvariant::Str::from_static(match value { + $(<$en>::$val => $str),+ + })) + } + } + + impl TryFrom for $en { + type Error = zvariant::Error; + + fn try_from(value: zvariant::OwnedValue) -> Result { + let Ok(s) = <&str>::try_from(&value) else { + return Err(Self::Error::IncorrectType); + }; + match s { + $($str => Ok(Self::$val)),+, + _ => Err(Self::Error::IncorrectType), + } + } + } + }; +} + +string_enum_conversions! { LoopStatus; + None => "None", + Track => "Track", + Playlist => "Playlist" +} + +string_enum_conversions! { PlaybackStatus; + Playing => "Playing", + Paused => "Paused", + Stopped => "Stopped" +} diff --git a/lib/mpris-dbus/src/lib.rs b/lib/mpris-dbus/src/lib.rs new file mode 100644 index 0000000..fbbb9dc --- /dev/null +++ b/lib/mpris-dbus/src/lib.rs @@ -0,0 +1,4 @@ +#![cfg(unix)] + +pub mod interface; +pub mod player; diff --git a/lib/mpris-dbus/src/player/listener.rs b/lib/mpris-dbus/src/player/listener.rs new file mode 100644 index 0000000..90efab5 --- /dev/null +++ b/lib/mpris-dbus/src/player/listener.rs @@ -0,0 +1,160 @@ +use std::collections::HashMap; + +use crate::{interface::*, player::State}; +use chrono::Utc; +use futures::StreamExt; +use tap::TapFallible; +use tokio::sync::mpsc; +use tracing::{info, warn}; +use zbus::{zvariant, Connection}; + +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +pub enum Error { + #[error("Failed to get DBus connection to the user message bus")] + GetConnection(zbus::Error), + #[error("Failed to setup the proxy")] + SetupProxy(zbus::Error), +} + +macro_rules! send_or_break { + ($tx:ident, $it:expr) => { + if let Err(_) = $tx.send($it).await { + break; + } + }; +} + +pub async fn listen(dest: D) -> Result, Error> +where + D: TryInto>, + D::Error: Into, +{ + let connection = Connection::session().await.map_err(Error::GetConnection)?; + let proxy = SpotifyPlayerProxy::builder(&connection) + .destination(dest) + .map_err(Error::SetupProxy)? + .build() + .await + .map_err(Error::SetupProxy)?; + + let (tx, rx) = mpsc::channel(8); + + tokio::spawn(async move { + let mut status_changed = proxy.receive_playback_status_changed().await; + let mut meta_changed = proxy.receive_metadata_changed().await; + let mut rate_changed = proxy.receive_rate_changed().await; + let mut seeked = proxy.receive_seeked().await.expect("TODO"); + let mut owner_changed = proxy.receive_owner_changed().await.expect("TODO"); + + let mut state = State::default(); + + loop { + tokio::select! { biased; + Some(status) = status_changed.next() => { + match status.get().await { + Ok(s) => { + state.status = s; + send_or_break!(tx, state.clone()) + }, + Err(e) => warn!(error = %e, "Failed to get status"), + }; + }, + Some(_) = meta_changed.next() => { + update_meta(&proxy, &mut state).await; + send_or_break!(tx, state.clone()); + }, + Some(rate) = rate_changed.next() => { + match rate.get().await { + Ok(r) => { + state.playback_rate = r; + send_or_break!(tx, state.clone()) + }, + Err(e) => warn!(error = %e, "Failed to get rate"), + }; + }, + Some(_) = seeked.next() => { + if update_position(&proxy, &mut state).await { + send_or_break!(tx, state.clone()); + } + }, + Some(owner) = owner_changed.next() => { + if owner.filter(|x| x.is_empty()).is_none() { + state.status = PlaybackStatus::Stopped; + send_or_break!(tx, state.clone()); + } + }, + else => break + } + } + info!("loop ended"); + }); + + Ok(rx) +} + +async fn update_meta(proxy: &SpotifyPlayerProxy<'_>, state: &mut State) { + let Ok(meta) = proxy + .metadata() + .await + .tap_err(|e| warn!(error = %e, "Failed to get metadata")) + else { + return; + }; + + get_meta(&meta, "mpris:length", &mut state.timeline.duration, Some); + get_meta(&meta, "xesam:title", &mut state.title, Some); + get_meta(&meta, "xesam:album", &mut state.album, Some); + get_meta(&meta, "xesam:trackNumber", &mut state.track_number, Some); + get_meta( + &meta, + "xesam:artist", + &mut state.artist, + |artist: zvariant::Array| { + artist + .iter() + .filter_map(|v| zvariant::Str::try_from(v).ok()) + .fold(String::new(), |acc, v| { + if acc.is_empty() { + v.to_string() + } else { + format!("{acc}, {v}") + } + }) + }, + ); + get_meta(&meta, "mpris:artUrl", &mut state.cover_art, Some); + + update_position(proxy, state).await; +} + +async fn update_position(proxy: &SpotifyPlayerProxy<'_>, state: &mut State) -> bool { + match proxy.position().await { + Ok(p) => { + state.timeline.position = p; + state.timeline.ts = Utc::now(); + true + } + Err(e) => { + warn!(error = %e, "Failed to get rate"); + false + } + } +} + +fn get_meta<'a, T, U>( + meta: &'a HashMap, zvariant::Value<'a>>, + key: &'static str, + target: &mut U, + map: impl FnOnce(T) -> U, +) where + T: TryFrom<&'a zvariant::Value<'a>>, + U: Default, +{ + let value = meta + .get(&zvariant::Str::from_static(key)) + .and_then(|v| T::try_from(v).ok()); + match value { + Some(v) => *target = map(v), + None => *target = U::default(), + } +} diff --git a/lib/mpris-dbus/src/player/mod.rs b/lib/mpris-dbus/src/player/mod.rs new file mode 100644 index 0000000..2d882ec --- /dev/null +++ b/lib/mpris-dbus/src/player/mod.rs @@ -0,0 +1,27 @@ +use chrono::{DateTime, Utc}; + +use crate::interface::PlaybackStatus; + +mod listener; + +pub use listener::{listen, Error as ListenError}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct State { + pub title: Option, + pub artist: String, + pub track_number: Option, + pub album: Option, + pub cover_art: Option, + + pub status: PlaybackStatus, + pub playback_rate: f64, + pub timeline: Timeline, +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Timeline { + pub ts: DateTime, + pub duration: Option, + pub position: i64, +} diff --git a/src/config.rs b/src/config.rs index 7f9244d..d32e7a2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::cfg_windows; +use crate::{cfg_unix, cfg_windows}; use serde::{Deserialize, Serialize}; use std::{collections::HashSet, fs, path::PathBuf}; use tracing::{event, Level}; @@ -7,6 +7,7 @@ use tracing::{event, Level}; #[serde(default)] pub struct Config { #[serde(default = "bool_true")] + #[cfg(windows)] pub no_autostart: bool, pub modules: ModuleConfig, pub server: ServerConfig, @@ -65,6 +66,10 @@ pub struct ModuleConfig { #[cfg(windows)] #[cfg_attr(windows, serde(default))] pub gsmtc: GsmtcConfig, + + #[cfg(unix)] + #[cfg_attr(unix, serde(default))] + pub dbus: DbusConfig, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -142,6 +147,25 @@ cfg_windows! { } } +cfg_unix! { + #[derive(Deserialize, Serialize, Debug, Clone)] + #[serde(default)] + pub struct DbusConfig { + #[serde(default = "bool_true")] + pub enabled: bool, + pub destinations: Vec, + } + + impl Default for DbusConfig { + fn default() -> Self { + Self { + enabled: true, + destinations: vec!["org.mpris.MediaPlayer2.spotify".to_owned()], + } + } + } +} + lazy_static::lazy_static! { static ref CONFIG_PATH: PathBuf = PathBuf::from("config.toml"); } diff --git a/src/macros/cfg.rs b/src/macros/cfg.rs index 2c2de55..e617dfb 100644 --- a/src/macros/cfg.rs +++ b/src/macros/cfg.rs @@ -4,3 +4,10 @@ macro_rules! cfg_windows { $( #[cfg(windows)] $item )* } } + +#[macro_export] +macro_rules! cfg_unix { + ($($item:item)*) => { + $( #[cfg(unix)] $item )* + } +} diff --git a/src/main.rs b/src/main.rs index 7e0029a..c1a1fea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,15 @@ async fn init_windows_actors(manager: Addr, image_store: Arc) { + if CONFIG.modules.dbus.enabled { + workers::dbus::start_spawning(manager, &CONFIG.modules.dbus.destinations) + .await + .unwrap(); + } +} + #[actix_web::main] async fn async_main() -> std::io::Result<()> { let (event_rx, manager) = init_channels(); @@ -64,6 +73,9 @@ async fn async_main() -> std::io::Result<()> { #[cfg(windows)] init_windows_actors(manager.clone(), image_store.clone()).await; + #[cfg(unix)] + init_unix_actors(manager.clone()).await; + let image_store: web::Data<_> = image_store.into(); let manager = web::Data::new(manager); let event_rx = web::Data::new(event_rx); diff --git a/src/workers/dbus.rs b/src/workers/dbus.rs new file mode 100644 index 0000000..5620748 --- /dev/null +++ b/src/workers/dbus.rs @@ -0,0 +1,111 @@ +use crate::{ + actors::manager::{CreateModule, Manager, RemoveModule, UpdateModule}, + model::{AlbumInfo, ImageInfo, ModuleState, PlayInfo, TimelineInfo}, +}; +use actix::Addr; +use anyhow::Result as AnyResult; +use mpris_dbus::{interface::PlaybackStatus, player}; +use tap::TapFallible; +use tokio::sync::mpsc; +use tracing::{span, warn, Instrument, Level}; + +#[derive(Debug)] +struct DBusWorker { + manager: Addr, + module_id: usize, + paused: bool, + source: String, +} + +pub async fn start_spawning(manager: Addr, sources: &[String]) -> AnyResult<()> { + for name in sources { + let source = name.clone(); + let manager = manager.clone(); + let Ok(module_id) = manager + .send(CreateModule { priority: 0 }) + .await + .tap_err(|e| warn!(error = %e, "Failed to create module")) + else { + continue; + }; + tokio::spawn( + async move { + let worker = DBusWorker { + manager, + module_id, + paused: false, + source, + }; + let Ok(rx) = player::listen(worker.source.clone()) + .await + .tap_err(|e| warn!(error = %e, "Failed to listen")) + else { + return; + }; + worker.feed_manager(rx).await; + } + .instrument(span!(Level::INFO, "DBusWorker", source = name)), + ); + } + Ok(()) +} + +impl DBusWorker { + async fn feed_manager(mut self, mut rx: mpsc::Receiver) { + while let Some(evt) = rx.recv().await { + self.send_update(evt).await; + } + self.manager + .send(RemoveModule { id: self.module_id }) + .await + .ok(); + } + + async fn send_update(&mut self, state: player::State) { + let paused = state.status != PlaybackStatus::Playing; + if paused && self.paused { + return; + } + let state = convert_model(state, &self.source); + self.paused = paused; + let span = span!(Level::TRACE, "Update Module", id = self.module_id, state = ?state, paused = self.paused); + self.manager + .send(UpdateModule { + id: self.module_id, + state, + }) + .instrument(span) + .await + .ok(); + } +} + +fn convert_model(from: player::State, source: &str) -> ModuleState { + if from.status != PlaybackStatus::Playing { + return ModuleState::Paused; + } + return ModuleState::Playing(PlayInfo { + title: from.title.unwrap_or_default(), + artist: from.artist, + track_number: from.track_number.map(|n| n as u32), + image: from.cover_art.map(ImageInfo::External), + timeline: Some(TimelineInfo { + ts: from + .timeline + .ts + .timestamp_millis() + .try_into() + .unwrap_or_default(), + duration_ms: from.timeline.duration.unwrap_or(0) / 1000, + progress_ms: (from.timeline.position / 1000) + .try_into() + .unwrap_or_default(), + rate: from.playback_rate as f32, + }), + album: from.album.map(|title| AlbumInfo { + title, + track_count: 0, + }), + source: format!("dbus::{source}"), + }); +} diff --git a/src/workers/mod.rs b/src/workers/mod.rs index 9f61e93..8e3d31a 100644 --- a/src/workers/mod.rs +++ b/src/workers/mod.rs @@ -2,3 +2,6 @@ pub mod file_output; #[cfg(windows)] pub mod gsmtc; + +#[cfg(unix)] +pub mod dbus;