diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 679 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | alsa/Cargo.toml | 18 | ||||
| -rw-r--r-- | alsa/src/communication.rs | 68 | ||||
| -rw-r--r-- | alsa/src/communication/pipe_chan.rs | 172 | ||||
| -rw-r--r-- | alsa/src/config.rs | 104 | ||||
| -rw-r--r-- | alsa/src/lib.rs | 37 | ||||
| -rw-r--r-- | alsa/src/runnable.rs | 397 | ||||
| -rw-r--r-- | formatable-float/Cargo.toml | 9 | ||||
| -rw-r--r-- | formatable-float/src/lib.rs | 154 | ||||
| -rw-r--r-- | pulse/Cargo.toml | 1 | ||||
| -rw-r--r-- | pulse/src/config.rs | 175 | ||||
| -rw-r--r-- | pulse/src/runnable/mod.rs | 1 | ||||
| -rw-r--r-- | swaystatus/Cargo.toml | 2 | ||||
| -rw-r--r-- | swaystatus/src/commandline/mod.rs | 14 | ||||
| -rw-r--r-- | testconfig | 24 |
17 files changed, 1466 insertions, 393 deletions
@@ -1,3 +1,4 @@ /target *~ *.swp +*/target @@ -4,53 +4,45 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.15" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] -name = "arrayref" -version = "0.3.6" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] -name = "arrayvec" -version = "0.5.2" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "blake2b_simd" -version = "0.5.11" +name = "bitflags" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block" @@ -59,10 +51,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] name = "cc" -version = "1.0.67" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -72,62 +73,67 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", - "time", - "winapi", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] name = "clap" -version = "3.0.0-beta.5" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "bitflags", + "bitflags 1.3.2", + "clap_lex", "indexmap", - "lazy_static", - "os_str_bytes", + "once_cell", "terminal_size", "textwrap", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "clap_lex" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" -dependencies = [ - "autocfg", - "cfg-if", - "lazy_static", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "dirs" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -136,33 +142,40 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.13" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0465971a8cc1fa2455c8465aaa377131e1f1cf4983280f474a13e68793aa770c" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ "serde", ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "formatable-float" +version = "0.1.0" +dependencies = [ + "erased-serde", + "serde", ] [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -177,31 +190,80 @@ dependencies = [ [[package]] name = "gettext-sys" -version = "0.21.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885d118016f633f99f741afe6c1433c040813a3cbc755cbfdf85f963e02fad80" +checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" dependencies = [ "cc", - "tempfile", + "temp-dir", ] [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[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 = "indexmap" -version = "1.6.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -209,21 +271,38 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] name = "locale_config" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -237,6 +316,12 @@ dependencies = [ ] [[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -247,25 +332,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -300,141 +375,83 @@ dependencies = [ ] [[package]] -name = "os_str_bytes" -version = "4.2.0" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" -dependencies = [ - "memchr", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "ppv-lite86" -version = "0.2.10" +name = "os_str_bytes" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" -dependencies = [ - "getrandom 0.2.2", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] name = "redox_syscall" -version = "0.2.6" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.3.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", + "getrandom", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.4.5" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", + "regex-automata", "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-automata" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ - "winapi", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "rust-argon2" -version = "0.8.3" +name = "regex-syntax" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc_version" @@ -446,25 +463,39 @@ dependencies = [ ] [[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] name = "semver" -version = "1.0.4" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.125" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -473,9 +504,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.8" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -483,9 +514,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -508,6 +539,18 @@ dependencies = [ ] [[package]] +name = "swaystatus-alsa" +version = "0.1.0" +dependencies = [ + "erased-serde", + "errno", + "formatable-float", + "libc", + "serde", + "swaystatus-plugin", +] + +[[package]] name = "swaystatus-clock" version = "0.1.0" dependencies = [ @@ -530,90 +573,141 @@ name = "swaystatus-pulse" version = "0.1.0" dependencies = [ "erased-serde", + "formatable-float", "serde", "swaystatus-plugin", ] [[package]] name = "syn" -version = "1.0.69" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] -name = "tempfile" -version = "3.2.0" +name = "temp-dir" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if", - "libc", - "rand", - "redox_syscall 0.2.6", - "remove_dir_all", - "winapi", -] +checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" [[package]] name = "terminal_size" -version = "0.1.16" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "textwrap" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" dependencies = [ "terminal_size", ] [[package]] -name = "time" -version = "0.1.43" +name = "thiserror" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ - "libc", - "winapi", + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] -name = "unicode-xid" -version = "0.2.1" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "winapi" @@ -636,3 +730,144 @@ 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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" @@ -3,6 +3,9 @@ members = [ "swaystatus", "swaystatus-plugin", + "formatable-float", + "alsa", "clock", "pulse" ] +resolver = "2" diff --git a/alsa/Cargo.toml b/alsa/Cargo.toml new file mode 100644 index 0000000..d70a07e --- /dev/null +++ b/alsa/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "swaystatus-alsa" +version = "0.1.0" +authors = ["Andreas Grois <andi@grois.info>"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'} +formatable-float = { path = '../formatable-float', version = '*'} +serde = { version = "1.0", features = ["derive"] } +erased-serde = "0.3" +libc = "0.2.152" +errno = "0.3.8" + +[lib] +crate-type = ["cdylib"] diff --git a/alsa/src/communication.rs b/alsa/src/communication.rs new file mode 100644 index 0000000..3caf4cf --- /dev/null +++ b/alsa/src/communication.rs @@ -0,0 +1,68 @@ +use swaystatus_plugin::*; + +use self::pipe_chan::{Sender, Receiver, SendError, ReceiveError, create_pipe_chan}; + +pub(crate) mod pipe_chan; + +#[repr(C)] +pub enum MessagesFromMain { + Quit, + Refresh +} + +pub(crate) struct MessagesFromMainReceiver{ + receiver : Receiver +} + +pub(crate) struct MessagesFromMainSender{ + sender: Sender +} + +pub(crate) fn make_sender_for_main() -> Result<(MessagesFromMainSender, MessagesFromMainReceiver),()>{ + create_pipe_chan().map(|(sender,receiver)| (MessagesFromMainSender{sender}, MessagesFromMainReceiver{receiver})) +} + +impl MessagesFromMainSender { + pub(crate) fn send(&self, message : MessagesFromMain) -> Result<(), SendError>{ + self.sender.send_byte(match message { + MessagesFromMain::Quit => 0, + MessagesFromMain::Refresh => 1, + }) + } +} + +impl MessagesFromMainReceiver { + pub(crate) fn receive(&self) -> Result<Option<MessagesFromMain>, ReceiveError> { + self.receiver.read_byte().map(|o| o.map(|b| match b { + 0 => MessagesFromMain::Quit, + 1 => MessagesFromMain::Refresh, + _ => unreachable!() + })) + } + pub(crate) fn file_handle(&self) -> &pipe_chan::FileHandle{ + self.receiver.file_handle() + } +} + +pub struct SenderForMain { + sender : MessagesFromMainSender, +} + +impl<'p> SenderForMain { + pub fn new(sender : MessagesFromMainSender) -> Self { + Self { sender } + } + + fn send(&self, message : MessagesFromMain) -> Result<(), PluginCommunicationError> { + self.sender.send(message).map_err(|_| PluginCommunicationError) + } +} + +impl MsgMainToModule for SenderForMain { + fn send_quit(&self) -> Result<(),PluginCommunicationError> { + self.send(MessagesFromMain::Quit) + } + fn send_refresh(&self) -> Result<(),PluginCommunicationError> { + self.send(MessagesFromMain::Refresh) + } +} diff --git a/alsa/src/communication/pipe_chan.rs b/alsa/src/communication/pipe_chan.rs new file mode 100644 index 0000000..5f0f9ec --- /dev/null +++ b/alsa/src/communication/pipe_chan.rs @@ -0,0 +1,172 @@ +use std::{ffi::{c_int, c_void}, fmt::Display, error::Error}; +use libc::{read, close, pipe2, O_NONBLOCK, EINTR, EAGAIN, EWOULDBLOCK, EPIPE }; +use errno::{errno, set_errno, Errno}; //Why isn't this in libc?!? + +/// Sends byte data to the corresponding receiver. +pub(crate) struct Sender { + handle : FileHandle, +} +/// Receives byte data from the corresponding sender. +pub(crate) struct Receiver { + handle : FileHandle, +} + +impl Receiver { + pub(crate) fn read_byte(&self) -> Result<Option<u8>, ReceiveError> { + set_errno(Errno(0)); + let mut buf : u8 = 0; + let status = unsafe {read(self.handle.get_raw(),&mut buf as *mut u8 as *mut c_void, 1)}; + let e = errno(); + if status > 0 { + Ok(Some(buf)) + } else if status == 0 && e == Errno(0) { + Err(ReceiveError::SenderHasHungUp) + } else if e.0 == EINTR { + self.read_byte() //got interrupted by a signal, try again. + } else if e.0 == EAGAIN || e.0 == EWOULDBLOCK { + Ok(None) //nothing to receive + } else { + Err(ReceiveError::UnknownError) + } + } + pub(crate) fn file_handle(&self) -> &FileHandle { + &self.handle + } +} + +impl Sender { + pub(crate) fn send_byte(&self, byte : u8) -> Result<(), SendError> { + set_errno(Errno(0)); + let status = unsafe {libc::write(self.handle.get_raw(), &byte as *const u8 as *const c_void, 1)}; + let e = errno(); + if status > 0 { + Ok(()) + } else if e.0 == EINTR { + self.send_byte(byte) //interrupted, retry + } else if e.0 == EPIPE { + Err(SendError::ReceiverHasHungUp) + } else if e.0 == EAGAIN { + Err(SendError::ChannelFullWouldBlock) + } else { + Err(SendError::UnknownError) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SendError{ + ReceiverHasHungUp, + ChannelFullWouldBlock, + UnknownError +} + +impl Display for SendError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SendError::ReceiverHasHungUp => write!(f, "Write failed, Receiver has closed their end of the pipe."), + SendError::ChannelFullWouldBlock => write!(f, "Write failed, the pipe is clogged."), + SendError::UnknownError => write!(f, "Write failed for unknown reasons. Probably a bug."), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ReceiveError { + SenderHasHungUp, + UnknownError, +} + +impl Display for ReceiveError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReceiveError::SenderHasHungUp => write!(f, "Read failed, Sender has closed their end of the pipe."), + ReceiveError::UnknownError => write!(f, "Read failed for unknown reasons. Probably a bug."), + } + + } +} + +impl Error for ReceiveError {} + +pub(crate) fn create_pipe_chan() -> Result<(Sender, Receiver),()> { + let mut handles : [c_int;2] = [0;2]; + let result = unsafe { pipe2(handles.as_mut_ptr(), O_NONBLOCK) }; + if result == -1 { + Err(()) + } else { + Ok(( + Sender{handle : FileHandle{raw : handles[1]}}, + Receiver{handle : FileHandle{ raw : handles[0]}} + )) + } +} + +pub(crate) struct FileHandle { + raw : c_int, +} + +impl FileHandle { + pub(crate) fn get_raw(&self) -> c_int { + self.raw + } +} + +impl Drop for FileHandle { + fn drop(&mut self) { + unsafe { + close(self.raw); + } + } +} + +#[cfg(test)] +mod pipe_chan_test { + use libc::fcntl; + + use super::*; + + #[test] + fn simple_send_read(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + send.send_byte(27).unwrap(); + assert_eq!(recv.read_byte(), Ok(Some(5))); + assert_eq!(recv.read_byte(), Ok(Some(27))); + assert_eq!(recv.read_byte(), Ok(None)); + } + + #[test] + fn simple_drop_sender(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + send.send_byte(27).unwrap(); + drop(send); + assert_eq!(recv.read_byte(), Ok(Some(5))); + assert_eq!(recv.read_byte(), Ok(Some(27))); + assert_eq!(recv.read_byte(), Err(ReceiveError::SenderHasHungUp)); + } + + #[test] + fn simple_drop_receiver(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + drop(recv); + assert_eq!(send.send_byte(27), Err(SendError::ReceiverHasHungUp)); + } + + #[test] + fn overfill_sender(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + + let capacity = unsafe {fcntl(send.handle.get_raw(), libc::F_GETPIPE_SZ)}; + for _ in 0..capacity { + assert!(send.send_byte(3).is_ok()); + } + assert_eq!(send.send_byte(3), Err(SendError::ChannelFullWouldBlock)); + + } +}
\ No newline at end of file diff --git a/alsa/src/config.rs b/alsa/src/config.rs new file mode 100644 index 0000000..93bcab9 --- /dev/null +++ b/alsa/src/config.rs @@ -0,0 +1,104 @@ +use std::ffi::CString; + +use formatable_float::{FormatableFloatValue, KeyBackingTypeMetadata, FormattingError}; +use serde::{Serialize, Deserialize}; +use swaystatus_plugin::*; + +use crate::{runnable::AlsaVolumeRunnable, communication::{SenderForMain, make_sender_for_main}}; + +#[derive(Serialize, Deserialize)] +pub struct AlsaVolumeConfig{ + pub(crate) device : CString, + pub(crate) element : CString, + pub(crate) abstraction : SElemAbstraction, + sorting: FieldSorting, + mute: FormatableMute, + volume: FormatableFloatValue<VolumeKeyVolume>, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub(crate) enum SElemAbstraction{ + None, + Basic, +} + + +#[derive(Serialize, Deserialize)] +enum FieldSorting { + MuteVolume, + VolumeMute, +} + +impl AlsaVolumeConfig { + pub(crate) fn format_volume(&self, volume : f32, mute : bool) -> Result<String,FormattingError> { + let formatted_mute = self.mute.format_mute(mute).unwrap_or(String::new()); + let join_strings = |v : String,m : String| match self.sorting { + FieldSorting::MuteVolume => m + &v, + FieldSorting::VolumeMute => v + &m, + }; + match self.volume.format_float(volume) + { + Ok(v) => Ok(join_strings(v.unwrap_or_default(), formatted_mute)), + Err(FormattingError::EmptyMap { numeric_fallback }) => Err(FormattingError::EmptyMap { numeric_fallback: join_strings(numeric_fallback, formatted_mute) }), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "Format")] +enum FormatableMute { + Off, + Symbol { + #[serde(rename = "Label")] + label : String, + #[serde(rename = "MuteSymbol")] + mute_symbol : String, + #[serde(rename = "UnmuteSymbol")] + unmute_symbol : String + } +} + +impl FormatableMute { + fn format_mute(&self, mute : bool) -> Option<String> { + match self { + FormatableMute::Off => { None } + FormatableMute::Symbol{ label, mute_symbol, unmute_symbol} => { Some(format!("{}{}", label, { if mute { mute_symbol } else { unmute_symbol }}))} + } + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +struct VolumeKeyVolume; +impl KeyBackingTypeMetadata for VolumeKeyVolume{ + type BackingType = u8; + const MIN : Self::BackingType = 0; + const MAX : Self::BackingType = 100; + const FLOAT_MIN : f32 = 0.0; + const FLOAT_MAX : f32 = 1.0; +} + + +impl SwayStatusModuleInstance for AlsaVolumeConfig { + fn make_runnable<'p>(&'p self, to_main : Box<dyn MsgModuleToMain + 'p>) -> (Box<dyn SwayStatusModuleRunnable + 'p>, Box<dyn MsgMainToModule + 'p>) { + if let Ok((s,r)) = make_sender_for_main() { + (Box::new(AlsaVolumeRunnable::new(to_main, r, self)), Box::new(SenderForMain::new(s))) + } else { + to_main.send_update(Err(PluginError::ShowInsteadOfText("Pipe creation failed. Call your plumber.".to_owned()))) + .expect("Tried to send an error to main, but main is not listening any more."); + panic!("Pipe creation failed. Call your plumber.") + } + } +} + +impl Default for AlsaVolumeConfig { + fn default() -> Self { + Self { + device: CString::new("default").unwrap(), + element: CString::new("Master").unwrap(), + abstraction : SElemAbstraction::None, + volume: FormatableFloatValue::Numeric { label: " ".into(), digits: 0 }, + mute: FormatableMute::Symbol { label : String::new(), mute_symbol : String::from("🔇"), unmute_symbol : String::from("🔊") }, + sorting: FieldSorting::MuteVolume, + } + } +}
\ No newline at end of file diff --git a/alsa/src/lib.rs b/alsa/src/lib.rs new file mode 100644 index 0000000..8b10036 --- /dev/null +++ b/alsa/src/lib.rs @@ -0,0 +1,37 @@ +use swaystatus_plugin::*; + +mod config; +mod communication; +mod runnable; + +use config::AlsaVolumeConfig; + +pub struct AlsaVolumePlugin; +impl SwayStatusModule for AlsaVolumePlugin { + fn get_name(&self) -> &str { + "AlsaVolume" + } + fn deserialize_config<'de, 'p>(&'p self, deserializer : &mut (dyn erased_serde::Deserializer + 'de)) -> Result<Box<dyn SwayStatusModuleInstance + 'p>,erased_serde::Error> { + erased_serde::deserialize::<AlsaVolumeConfig>(deserializer) + .map(|c| Box::new(c) as Box<dyn SwayStatusModuleInstance>) + } + fn get_default_config<'p>(&'p self) -> Box<dyn SwayStatusModuleInstance + 'p> { + Box::new(config::AlsaVolumeConfig::default()) + } + fn print_help(&self) { + println!( +r#"Swaystatus Alsa Volume plugin. + +This is a volume display for ALSA. Currently quite limited, but hey, you're free to extend it. You must set the device and element name in config. +Blanace is not supported at the moment, just volume of a single element."# + ); + } +} + +impl AlsaVolumePlugin { + fn new() -> Self { + Self + } +} + +declare_swaystatus_module!(AlsaVolumePlugin, AlsaVolumePlugin::new); diff --git a/alsa/src/runnable.rs b/alsa/src/runnable.rs new file mode 100644 index 0000000..4835436 --- /dev/null +++ b/alsa/src/runnable.rs @@ -0,0 +1,397 @@ +use std::{cell::RefCell, fmt::Display, error::Error, ffi::CStr}; + +use formatable_float::FormattingError; +use libc::{c_int, c_char, c_uint, c_void, c_long, c_ushort, c_short}; +use swaystatus_plugin::*; + +use crate::{communication::MessagesFromMainReceiver, config::SElemAbstraction}; + +use super::config::AlsaVolumeConfig; + +pub struct AlsaVolumeRunnable<'r>{ + to_main : Box<dyn MsgModuleToMain + 'r>, + from_main : MessagesFromMainReceiver, + config : &'r AlsaVolumeConfig, +} + +impl<'r> AlsaVolumeRunnable<'r> { + pub fn new(to_main : Box<dyn MsgModuleToMain + 'r>, from_main : MessagesFromMainReceiver, config : &'r AlsaVolumeConfig) -> Self { + Self { to_main, from_main, config } + } + fn send_error_to_main<E>(&self, err : E) where E : std::error::Error { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText(String::from("Error")))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more."); + self.to_main.send_update(Err(PluginError::PrintToStdErr(err.to_string()))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more."); + } + fn run_internal(&self) -> Result<(), AlsaVolumeError>{ + //Using C callbacks in Rust is a minefield. + //However, we can take the easy way out here, namely we only care about a single element, so we can just make a single data field ;-) + //It still needs to be in a Cell though. + let elem_scratch_space = RefCell::new(None); + let mixer_scratch_space = MixerScratchSpace{ + elem_name: &self.config.element, + elem_scratch: &elem_scratch_space, + }; + let mixer = open_mixer(0)?; + register_selem(mixer.handle, &self.config.device, self.config.abstraction)?; + unsafe { snd_mixer_set_callback(mixer.handle, Some(Self::mixer_callback)) }; + unsafe { snd_mixer_set_callback_private(mixer.handle, &mixer_scratch_space as *const MixerScratchSpace as *const c_void)}; + load_mixer(mixer.handle)?; + + let mut should_update_main_even_if_unchanged = true; + loop { + + let descriptor_count = unsafe{snd_mixer_poll_descriptors_count(mixer.handle)}; + if descriptor_count < 0 { + return Err(AlsaVolumeError::FailedToGetPollDescriptors); + } + let mut descriptors : Vec<libc::pollfd> = std::iter::once(libc::pollfd{ + fd: self.from_main.file_handle().get_raw(), + events: libc::POLLIN, + revents: 0 + }) + .chain(std::iter::repeat( + libc::pollfd{ + fd: 0, + events: 0, + revents: 0, + } + )) + .take(descriptor_count as usize + 1) + .collect(); + let descriptor_count = if descriptor_count > 0 { unsafe {snd_mixer_poll_descriptors(mixer.handle, &mut descriptors[1] , descriptor_count as c_uint)} } else { 0 }; + if descriptor_count < 0 { + return Err(AlsaVolumeError::FailedToGetPollDescriptors); + } + let n = unsafe {libc::poll(descriptors.as_mut_ptr(), descriptors.len() as u64, -1)}; + if n < 0 && n != libc::EINTR { + return Err(AlsaVolumeError::UnexpectedPollError); + } + //first check if there's any data on our pipe from main. + loop { + match self.from_main.receive(){ + Ok(Some(message)) => match message{ + crate::communication::MessagesFromMain::Quit => { return Ok(())}, + crate::communication::MessagesFromMain::Refresh => { should_update_main_even_if_unchanged = true; }, + }, + Ok(None) => break, //main has nothing more to say. + Err(e) => match e { + crate::communication::pipe_chan::ReceiveError::SenderHasHungUp => { return Err(AlsaVolumeError::MainHungUpWithoutQuit) }, + crate::communication::pipe_chan::ReceiveError::UnknownError => { return Err(AlsaVolumeError::ErrorInPluginCommunication) }, + }, + } + } + let old_values = mixer_scratch_space.elem_scratch.borrow().clone(); + let anything_new_from_alsa = n > (if descriptors[0].revents != 0 { 1 } else { 0 }); + if anything_new_from_alsa { + let mut revents = 0; + let worked = unsafe {snd_mixer_poll_descriptors_revents(mixer.handle, &descriptors[1], descriptor_count as c_uint,&mut revents) }; + if worked < 0{ + return Err(AlsaVolumeError::UnexpectedPollError); + } + if (revents as c_short) & (libc::POLLERR | libc::POLLNVAL) != 0 { + return Err(AlsaVolumeError::DeviceRemoved); + } + if (revents as c_short) & libc::POLLIN != 0 { + let handling_worked = unsafe {snd_mixer_handle_events(mixer.handle)}; + if handling_worked < 0 { + return Err(AlsaVolumeError::EventHandlingError); + } + } + } + let new_values = mixer_scratch_space.elem_scratch.borrow().clone(); + if new_values != old_values || should_update_main_even_if_unchanged { + self.send_updated_values_to_main(new_values).expect("Tried to update main thread, but it seems to be gone?"); + } + should_update_main_even_if_unchanged = false; + } + } + + fn send_updated_values_to_main(&self, volume : Option<ElemVolumeInfo>) -> Result<(),PluginCommunicationError> { + match volume{ + Some(volume) => { + let formatted_volume = self.config.format_volume(volume.volume, volume.mute); + match formatted_volume { + Ok(msg) => { self.to_main.send_update(Ok(msg)) } + Err(e) => { + let full_message = e.to_string(); + match e { + FormattingError::EmptyMap{ numeric_fallback } => { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText(numeric_fallback)))?; + self.to_main.send_update(Err(PluginError::PrintToStdErr(full_message))) + } + } + } + } + }, + None => { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText("Unknown".into()))) + }, + } + } + + extern "C" fn mixer_callback(mixer : SndMixerHandle, flags : c_uint, element : SndMixerElemHandle) -> c_int { + if flags & (1<<2) != 0 { //SND_CTL_EVENT_MASK_ADD + //check if the newly added element is the one we are looking for. + let scratch : &MixerScratchSpace = unsafe{&*(snd_mixer_get_callback_private(mixer) as *const MixerScratchSpace)}; + let elem_name = unsafe { CStr::from_ptr(snd_mixer_selem_get_name(element)) }; + if elem_name == scratch.elem_name { + unsafe {snd_mixer_elem_set_callback(element, Some(Self::element_callback))}; + unsafe {snd_mixer_elem_set_callback_private(element,scratch.elem_scratch as *const ElemScratchSpace as *const c_void)}; + 0 + } else { + 0 + } + } else { + 0 + } + } + + extern "C" fn element_callback(element : SndMixerElemHandle, flags : c_uint) -> c_int { + //okay, we hit the right element, sooo + if flags == (!0) { //SND_CTL_EVENT_MASK_REMOVE + 0 + } else { //could check further to exclude more spurious wake-ups, but for now... + //we don't do any magic here. Just sum up all channel's values and call it a day. + let (count, volume_sum) = ALL_CHANNELS.iter() + .filter_map(|channel| get_volume_for_channel(element, *channel)) + .fold((0,0), |(c, ov), v| (c+1, ov + v)); + let average = if count == 0 { None } else { Some(volume_sum / count)}; + let range = get_volume_range(element); + let normalized = average.zip(range).map(|(average, range)| ((average - range.0)*100000) / (range.1 - range.0)); + let scratch = unsafe{&*(snd_mixer_elem_get_callback_private(element) as *const ElemScratchSpace)}; + let has_mute = unsafe{ snd_mixer_selem_has_playback_switch(element) != 0}; + let is_mute = if has_mute { + !ALL_CHANNELS.iter().any(|channel| get_switch_for_channel(element, *channel)) + } else { false }; + *scratch.borrow_mut() = normalized.map(|volume| ElemVolumeInfo{volume : volume as f32 / 100000.0, mute : is_mute}); + 0 + } + } +} + +#[derive(Clone, Debug, PartialEq)] +struct ElemVolumeInfo{ + volume : f32, + mute : bool +} + +type ElemScratchSpace = RefCell<Option<ElemVolumeInfo>>; + +struct MixerScratchSpace<'a>{ + elem_name : &'a CStr, + elem_scratch : &'a ElemScratchSpace, +} + +impl<'r> SwayStatusModuleRunnable for AlsaVolumeRunnable<'r> { + fn run(&self) { + match self.run_internal(){ + Ok(()) => {}, + Err(e) => self.send_error_to_main(e), + } + } +} + +#[derive(Clone, Copy, Debug)] +enum AlsaVolumeError{ + FailedToOpenMixer, + FailedToLoadElements, + FailedToGetPollDescriptors, + UnexpectedPollError, + ErrorInPluginCommunication, + MainHungUpWithoutQuit, + DeviceRemoved, + EventHandlingError, +} + +impl Display for AlsaVolumeError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AlsaVolumeError::FailedToOpenMixer => write!(f, "Failed to open Mixer"), + AlsaVolumeError::FailedToLoadElements => write!(f, "Failed to load Mixer elements"), + AlsaVolumeError::FailedToGetPollDescriptors => write!(f, "Failed to get poll descriptors"), + AlsaVolumeError::UnexpectedPollError => write!(f, "Polling for updates failed for an unhandled reason. Debug."), + AlsaVolumeError::ErrorInPluginCommunication => write!(f, "Something went wrong with the pipe from main thread. Debug."), + AlsaVolumeError::MainHungUpWithoutQuit => write!(f, "Main thread ended communication without saying goodbye. Debug."), + AlsaVolumeError::DeviceRemoved => write!(f, "Device removed. Unsupported for now."), + AlsaVolumeError::EventHandlingError => write!(f, "Failure while handling mixer events. Debug."), + } + } +} +impl Error for AlsaVolumeError{} + +struct MixerHandleScopeGuard{ + handle : SndMixerHandle +} +impl Drop for MixerHandleScopeGuard{ + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe {snd_mixer_close(self.handle)}; + } + } +} + +fn open_mixer(mode : c_int) -> Result<MixerHandleScopeGuard,AlsaVolumeError>{ + let mut handle : SndMixerHandle = std::ptr::null(); + let error_code = unsafe {snd_mixer_open(&mut handle, mode)}; + if error_code == 0 { + Ok(MixerHandleScopeGuard { handle }) + } else { + Err(AlsaVolumeError::FailedToOpenMixer) + } +} + +fn load_mixer(mixer : SndMixerHandle) -> Result<(), AlsaVolumeError>{ + let error_code = unsafe { snd_mixer_load(mixer)}; + if error_code == 0 { + Ok(()) + } else { + Err(AlsaVolumeError::FailedToLoadElements) + } +} + +fn register_selem(mixer : SndMixerHandle, device : &CStr, abstraction : SElemAbstraction) -> Result<(), AlsaVolumeError>{ + let options = SndMixerSelemRegopt{ + ver: 1, + abstraction: match abstraction { + SElemAbstraction::None => SndMixerSelemRegoptAbstract::None, + SElemAbstraction::Basic => SndMixerSelemRegoptAbstract::Basic, + }, + device: device.as_ptr(), + playback_pcm: std::ptr::null(), + capture_pcm: std::ptr::null(), + }; + let error_code = unsafe {snd_mixer_selem_register(mixer, &options, std::ptr::null_mut())}; + if error_code == 0 { + Ok(()) + } else { + Err(AlsaVolumeError::FailedToOpenMixer) + } +} + +fn get_volume_for_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> Option<c_long>{ + if unsafe {snd_mixer_selem_has_playback_channel(element, channel) > 0} { + let mut value : c_long = 0; + if unsafe { snd_mixer_selem_get_playback_volume(element, channel, &mut value) == 0}{ + Some(value) + } else { + None + } + } else { + None + } +} + +fn get_switch_for_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> bool { + let mut switch = 0; + let worked = unsafe { snd_mixer_selem_get_playback_switch(element, channel, &mut switch)}; + worked == 0 && switch != 0 +} + +fn get_volume_range(element : SndMixerElemHandle) -> Option<(c_long, c_long)>{ + let mut min = 0; + let mut max = 0; + if unsafe { snd_mixer_selem_get_playback_volume_range(element, &mut min, &mut max) == 0 } { + Some((min, max)) + } else { + None + } +} + +#[repr(C)] struct SndMixerT { _private: [u8; 0]} +#[repr(C)] struct SndPcmT { _private: [u8; 0]} +#[repr(C)] struct SndMixerClassT { _private: [u8; 0]} +#[repr(C)] struct SndMixerElemT { _private: [u8; 0]} + +type SndMixerHandle = *const SndMixerT; +type SndMixerElemHandle = *const SndMixerElemT; + +#[repr(C)] enum SndMixerSelemRegoptAbstract { + None, + Basic, +} +#[repr(C)] struct SndMixerSelemRegopt { + ver : c_int, + abstraction : SndMixerSelemRegoptAbstract, + device : *const c_char, + playback_pcm : *const SndPcmT, + capture_pcm : *const SndPcmT, +} + +#[derive(Clone,Copy)] +#[repr(C)] enum SndMixerSelemChannelIdT { + FrontLeft, + FrontRight, + RearLeft, + RearRight, + FrontCenter, + Woofer, + SideLeft, + SideRight, + RearCenter, +} + +static ALL_CHANNELS : [SndMixerSelemChannelIdT;9] = [ + SndMixerSelemChannelIdT::FrontLeft, + SndMixerSelemChannelIdT::FrontRight, + SndMixerSelemChannelIdT::RearLeft, + SndMixerSelemChannelIdT::RearRight, + SndMixerSelemChannelIdT::FrontCenter, + SndMixerSelemChannelIdT::Woofer, + SndMixerSelemChannelIdT::SideLeft, + SndMixerSelemChannelIdT::SideRight, + SndMixerSelemChannelIdT::RearCenter, +]; + + +type SndMixerCallbackT = extern "C" fn(SndMixerHandle, c_uint, SndMixerElemHandle) -> c_int; +type SndMixerElemCallbackT = extern "C" fn(SndMixerElemHandle, c_uint) -> c_int; +#[link(name = "asound")] +extern "C" { + //int snd_mixer_open ( snd_mixer_t ** mixerp, int mode ) + fn snd_mixer_open(mixer : *mut SndMixerHandle, mode : c_int) -> c_int; + //int snd_mixer_close ( snd_mixer_t * mixer ) + fn snd_mixer_close(mixer : SndMixerHandle) -> c_int; + //int snd_mixer_selem_register(snd_mixer_t *mixer, struct snd_mixer_selem_regopt *options, snd_mixer_class_t **classp); + fn snd_mixer_selem_register(mixer : SndMixerHandle, options: *const SndMixerSelemRegopt, class: *mut *const SndMixerClassT) -> c_int; + //void snd_mixer_set_callback ( snd_mixer_t * obj, snd_mixer_callback_t val ) + fn snd_mixer_set_callback(mixer : SndMixerHandle, callback : Option<SndMixerCallbackT>); + //void snd_mixer_set_callback_private ( snd_mixer_t * mixer, void * val ) + fn snd_mixer_set_callback_private(mixer : SndMixerHandle, value : *const c_void); //the *const is a lie, but one that we need for Stacked Borrows sanity. + //void* snd_mixer_get_callback_private ( const snd_mixer_t * mixer ) + fn snd_mixer_get_callback_private(mixer : SndMixerHandle) -> *const c_void; //the *const is a lie, but one that we need for Stacked Borrows sanity. + + //int snd_mixer_load ( snd_mixer_t * mixer ) + fn snd_mixer_load(mixer : SndMixerHandle) -> c_int; + + //const char* snd_mixer_selem_get_name ( snd_mixer_elem_t * elem ) + fn snd_mixer_selem_get_name(element: SndMixerElemHandle) -> *const c_char; + //void snd_mixer_elem_set_callback ( snd_mixer_elem_t * mixer, snd_mixer_elem_callback_t val ) + fn snd_mixer_elem_set_callback(element : SndMixerElemHandle, callback : Option<SndMixerElemCallbackT>); + //void snd_mixer_elem_set_callback_private ( snd_mixer_elem_t * mixer, void * val ) + fn snd_mixer_elem_set_callback_private(element : SndMixerElemHandle, value : *const c_void); + //void* snd_mixer_elem_get_callback_private ( const snd_mixer_elem_t * mixer ) + fn snd_mixer_elem_get_callback_private(element : SndMixerElemHandle) -> *const c_void; + + //int snd_mixer_selem_has_playback_channel ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel ) + fn snd_mixer_selem_has_playback_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> c_int; + //int snd_mixer_selem_get_playback_volume ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel, long * value ) + fn snd_mixer_selem_get_playback_volume(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT, value : *mut c_long) -> c_int; + //int snd_mixer_selem_get_playback_volume_range ( snd_mixer_elem_t * elem, long * min, long * max ) + fn snd_mixer_selem_get_playback_volume_range(element : SndMixerElemHandle, min: *mut c_long, max : *mut c_long) -> c_int; + + //int snd_mixer_selem_has_playback_switch ( snd_mixer_elem_t * elem ) + fn snd_mixer_selem_has_playback_switch(element : SndMixerElemHandle) -> c_int; + //int snd_mixer_selem_get_playback_switch ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel, int * value ) + fn snd_mixer_selem_get_playback_switch(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT, value : *mut c_int) -> c_int; + + //int snd_mixer_poll_descriptors_count ( snd_mixer_t * mixer ) + fn snd_mixer_poll_descriptors_count(mixer : SndMixerHandle) -> c_int; + //int snd_mixer_poll_descriptors ( snd_mixer_t * mixer, struct pollfd * pfds, unsigned int space ) + fn snd_mixer_poll_descriptors(mixer : SndMixerHandle, fds : *mut libc::pollfd, space : c_uint) -> c_int; + + //int snd_mixer_poll_descriptors_revents ( snd_mixer_t * mixer, struct pollfd * pfds, unsigned int nfds, unsigned short * revents ) + fn snd_mixer_poll_descriptors_revents(mixer : SndMixerHandle, fds : *const libc::pollfd, nfds : c_uint, revents : *mut c_ushort) -> c_int; + + //int snd_mixer_handle_events ( snd_mixer_t * mixer ) + fn snd_mixer_handle_events(mixer : SndMixerHandle) -> c_int; +}
\ No newline at end of file diff --git a/formatable-float/Cargo.toml b/formatable-float/Cargo.toml new file mode 100644 index 0000000..323bc07 --- /dev/null +++ b/formatable-float/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "formatable-float" +version = "0.1.0" +authors = ["Andreas Grois <andi@grois.info>"] +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +erased-serde = "0.3" diff --git a/formatable-float/src/lib.rs b/formatable-float/src/lib.rs new file mode 100644 index 0000000..7b1db21 --- /dev/null +++ b/formatable-float/src/lib.rs @@ -0,0 +1,154 @@ +use serde::{Serialize,Deserialize,Serializer,Deserializer}; +use serde::de::Error as DeError; +use serde::de::Unexpected as DeUnexpect; +use std::collections::BTreeMap; +use std::ops::{Add, Sub}; +use std::str::FromStr; +use std::num::{ParseIntError,IntErrorKind}; + +#[derive(Serialize, Deserialize)] +#[serde(tag = "Format")] +pub enum FormatableFloatValue<KeyTypeMetadata : KeyBackingTypeMetadata> { + Off, + Numeric { + #[serde(rename = "Label")] + label : String, + #[serde(rename = "DecimalDigits")] + digits : u8 + }, + Binned { + #[serde(rename = "Label")] + label: String, + #[serde(rename = "PercentToSymbolMap")] + bin_symbol_map : BTreeMap<FormatableFloatKey<KeyTypeMetadata>,String> + } +} + +#[derive(Debug)] +pub enum FormattingError { + EmptyMap { + numeric_fallback : String + } +} +impl std::fmt::Display for FormattingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FormattingError::EmptyMap{numeric_fallback} => { write!(f, "Formatting failed. Empty PercentToSymbolMap. Numeric value: {}", numeric_fallback) } + } + } +} +impl std::error::Error for FormattingError {} + +impl<KeyTypeMetadata : KeyBackingTypeMetadata> FormatableFloatValue<KeyTypeMetadata> { + pub fn format_float(&self, float : f32) -> Result<Option<String>, FormattingError> { + match self { + FormatableFloatValue::Numeric{ label, digits } => { Ok(Some(Self::format_float_numeric(float, label, *digits))) } + FormatableFloatValue::Binned{ label, bin_symbol_map } => { Some(Self::format_float_binned(float, label, bin_symbol_map)).transpose()} + FormatableFloatValue::Off => {Ok(None)} + } + } + pub fn format_float_binned(float : f32, label : &str, bin_symbol_map : &BTreeMap<FormatableFloatKey<KeyTypeMetadata>, String>) -> Result<String,FormattingError> { + let value_to_match = FormatableFloatKey::<KeyTypeMetadata>::match_float(float); + //first try to find the next lower value. + if let Some((_,msg)) = bin_symbol_map.range(..=value_to_match).next_back() { + Ok(format!("{}{}",label,msg)) + } + else if let Some((_,msg)) = bin_symbol_map.iter().next() { + Ok(format!("{}{}",label,msg)) + } + else { + Err(FormattingError::EmptyMap{numeric_fallback : Self::format_float_numeric(float, label, 0) }) + } + } + pub fn format_float_numeric(float : f32, label : &str, digits : u8) -> String { + let percentage = 100.0*float; + format!("{}{:.*}%", label, digits as usize, percentage) + } +} + +///Helper trait for conversion from float to integer backing type for binning keys. +///Needed because Rust seems not to offer a trait that indicates "can be rounded from float" +///in the standard library. There are thir-party crates that do this, but using a full crate +///for a few lines of code sounds a bit excessive... +pub trait BackingTypeFromFloat { + fn round_from_float(float : f32) -> Self; +} + +///Metadata description for Keys. Basically a workaround for Rust's lack of +///constant generics. Having Ord as supertrait is because of the BTreeMap's trait +///bounds. +pub trait KeyBackingTypeMetadata : Ord { + type BackingType + : Ord + + Add<Output = Self::BackingType> + + Sub<Output = Self::BackingType> + + Into<f32> + + BackingTypeFromFloat + + ToString //TOML needs map keys to be strings... + + FromStr<Err = ParseIntError> + + std::fmt::Display; + const MIN : Self::BackingType; + const MAX : Self::BackingType; + const FLOAT_MIN : f32; + const FLOAT_MAX : f32; +} + +#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)] +pub struct FormatableFloatKey<BackingType : KeyBackingTypeMetadata>(pub BackingType::BackingType); + +impl<BackingType : KeyBackingTypeMetadata> FormatableFloatKey<BackingType> { + fn match_float(float : f32) -> Self { + let x = (float - BackingType::FLOAT_MIN) / (BackingType::FLOAT_MAX - BackingType::FLOAT_MIN); + let cx = x.clamp(0.0,1.0); + let interval = BackingType::MAX.into() - BackingType::MIN.into(); + let offset = interval * cx; + let result = BackingType::BackingType::round_from_float(offset) + BackingType::MIN; + Self(result) + } + +} +/// Custom serializer, as TOML only supports string map keys. +impl<Metadata : KeyBackingTypeMetadata> Serialize for FormatableFloatKey<Metadata> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where S : Serializer + { + let string = self.0.to_string(); + serializer.serialize_str(&string) + } +} +impl<'de, Metadata> Deserialize<'de> for FormatableFloatKey<Metadata> + where Metadata : KeyBackingTypeMetadata, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where D: Deserializer<'de> + { + let a = String::deserialize(deserializer)?; + match a.parse() { + Ok(x) => { + if x >= Metadata::MIN && x <= Metadata::MAX { + Ok(Self(x)) + } + else { + Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) + } + } + Err(e) => { + match e.kind() { + IntErrorKind::Empty => { Err(DeError::missing_field("Bin Map Key")) } + IntErrorKind::InvalidDigit => { Err(DeError::invalid_type(DeUnexpect::Str(&a), &"an integer value")) } + IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) } + IntErrorKind::Zero => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("a nonzero integer between {} and {}", Metadata::MIN, Metadata::MAX)))} + _ => { Err(DeError::custom("Value could not be parsed")) } + } + } + } + } +} +macro_rules! impl_key_backing_type_from_float_for { + ($( $t:ty ), *) => { + $( impl BackingTypeFromFloat for $t { + fn round_from_float(float : f32) -> $t { float.round() as $t } + } )* + } +} +impl_key_backing_type_from_float_for!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize, i128, u128); diff --git a/pulse/Cargo.toml b/pulse/Cargo.toml index c6cff4b..c3b4c12 100644 --- a/pulse/Cargo.toml +++ b/pulse/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'} serde = { version = "1.0", features = ["derive"] } erased-serde = "0.3" +formatable-float = { path = '../formatable-float', version = '*'} [lib] crate-type = ["cdylib"] diff --git a/pulse/src/config.rs b/pulse/src/config.rs index 0be7367..fb123e0 100644 --- a/pulse/src/config.rs +++ b/pulse/src/config.rs @@ -1,12 +1,8 @@ -use serde::{Serialize,Deserialize,Serializer,Deserializer}; -use serde::de::Error as DeError; -use serde::de::Unexpected as DeUnexpect; use std::collections::BTreeMap; -use swaystatus_plugin::*; -use std::ops::{Add, Sub}; -use std::str::FromStr; -use std::num::{ParseIntError,IntErrorKind}; +use serde::{Serialize,Deserialize}; +use swaystatus_plugin::*; +use formatable_float::{FormatableFloatValue, FormattingError, KeyBackingTypeMetadata, FormatableFloatKey}; #[derive(Serialize, Deserialize)] #[serde(tag = "Sink")] @@ -19,24 +15,6 @@ pub(crate) enum Sink { } #[derive(Serialize, Deserialize)] -#[serde(tag = "Format")] -enum FormatableVolume<KeyTypeMetadata : VolumeKeyBackingTypeMetadata> { - Off, - Numeric { - #[serde(rename = "Label")] - label : String, - #[serde(rename = "DecimalDigits")] - digits : u8 - }, - Binned { - #[serde(rename = "Label")] - label: String, - #[serde(rename = "PercentToSymbolMap")] - bin_symbol_map : BTreeMap<VolumeKey<KeyTypeMetadata>,String> - } -} - -#[derive(Serialize, Deserialize)] enum FieldSorting { MuteVolumeBalance, MuteBalanceVolume, @@ -65,8 +43,8 @@ enum FormatableMute { pub struct PulseVolumeConfig { sorting : FieldSorting, pub(crate) sink : Sink, - volume : FormatableVolume<VolumeKeyVolume>, - balance : FormatableVolume<VolumeKeyBalance>, + volume : FormatableFloatValue<VolumeKeyVolume>, + balance : FormatableFloatValue<VolumeKeyBalance>, mute : FormatableMute, } @@ -109,14 +87,14 @@ impl Default for PulseVolumeConfig { fn default() -> Self { PulseVolumeConfig { sink : Sink::Default, - volume : FormatableVolume::Numeric { label : String::from(""), digits : 0 }, - balance : FormatableVolume::Binned { + volume : FormatableFloatValue::Numeric { label : String::from(""), digits : 0 }, + balance : FormatableFloatValue::Binned { label : String::from(" "), bin_symbol_map : { let mut a = BTreeMap::new(); - a.insert(VolumeKey(-100),String::from("|..")); - a.insert(VolumeKey(-10), String::from(".|.")); - a.insert(VolumeKey(10), String::from("..|")); + a.insert(FormatableFloatKey(-100),String::from("|..")); + a.insert(FormatableFloatKey(-10), String::from(".|.")); + a.insert(FormatableFloatKey(10), String::from("..|")); a } }, @@ -133,48 +111,6 @@ impl SwayStatusModuleInstance for PulseVolumeConfig { } } -#[derive(Debug)] -pub(crate) enum FormattingError { - EmptyMap { - numeric_fallback : String - } -} -impl std::fmt::Display for FormattingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FormattingError::EmptyMap{numeric_fallback} => { write!(f, "Formatting failed. Empty PercentToSymbolMap. Numeric value: {}", numeric_fallback) } - } - } -} -impl std::error::Error for FormattingError {} - -impl<KeyTypeMetadata : VolumeKeyBackingTypeMetadata> FormatableVolume<KeyTypeMetadata> { - fn format_float(&self, float : f32) -> Result<Option<String>, FormattingError> { - match self { - FormatableVolume::Numeric{ label, digits } => { Ok(Some(Self::format_float_numeric(float, label, *digits))) } - FormatableVolume::Binned{ label, bin_symbol_map } => { Some(Self::format_float_binned(float, label, bin_symbol_map)).transpose()} - FormatableVolume::Off => {Ok(None)} - } - } - fn format_float_binned(float : f32, label : &str, bin_symbol_map : &BTreeMap<VolumeKey<KeyTypeMetadata>, String>) -> Result<String,FormattingError> { - let value_to_match = VolumeKey::<KeyTypeMetadata>::match_float(float); - //first try to find the next lower value. - if let Some((_,msg)) = bin_symbol_map.range(..=value_to_match).next_back() { - Ok(format!("{}{}",label,msg)) - } - else if let Some((_,msg)) = bin_symbol_map.iter().next() { - Ok(format!("{}{}",label,msg)) - } - else { - Err(FormattingError::EmptyMap{numeric_fallback : Self::format_float_numeric(float, label, 0) }) - } - } - fn format_float_numeric(float : f32, label : &str, digits : u8) -> String { - let percentage = 100.0*float; - format!("{}{:.*}%", label, digits as usize, percentage) - } -} - impl FormatableMute { fn format_mute(&self, mute : bool) -> Option<String> { match self { @@ -183,45 +119,12 @@ impl FormatableMute { } } } -///Helper trait for conversion from float to integer backing type for volume binning keys. -///Needed because Rust seems not to offer a trait that indicates "can be rounded from float" -///in the standard library. There are thir-party crates that do this, but using a full crate -///for a few lines of code sounds a bit excessive... -trait VolumeKeyBackingTypeFromFloat { - fn round_from_float(float : f32) -> Self; -} -macro_rules! impl_volume_key_backing_type_from_float_for { - ($( $t:ty ), *) => { - $( impl VolumeKeyBackingTypeFromFloat for $t { - fn round_from_float(float : f32) -> $t { float.round() as $t } - } )* - } -} - -///Metadata description for VolumeKeys. Basically a workaround for Rust's lack of -///constant generics. Having Ord as supertrait is because of the BTreeMap's trait -///bounds. -trait VolumeKeyBackingTypeMetadata : Ord { - type BackingType - : Ord - + Add<Output = Self::BackingType> - + Sub<Output = Self::BackingType> - + Into<f32> - + VolumeKeyBackingTypeFromFloat - + ToString //TOML needs map keys to be strings... - + FromStr<Err = ParseIntError> - + std::fmt::Display; - const MIN : Self::BackingType; - const MAX : Self::BackingType; - const FLOAT_MIN : f32; - const FLOAT_MAX : f32; -} #[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] struct VolumeKeyVolume; #[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] struct VolumeKeyBalance; -impl VolumeKeyBackingTypeMetadata for VolumeKeyVolume{ +impl KeyBackingTypeMetadata for VolumeKeyVolume{ type BackingType = u8; const MIN : Self::BackingType = 0; const MAX : Self::BackingType = 100; @@ -229,64 +132,10 @@ impl VolumeKeyBackingTypeMetadata for VolumeKeyVolume{ const FLOAT_MAX : f32 = 1.0; } -impl VolumeKeyBackingTypeMetadata for VolumeKeyBalance{ +impl KeyBackingTypeMetadata for VolumeKeyBalance{ type BackingType = i8; const MIN : Self::BackingType = -100; const MAX : Self::BackingType = 100; const FLOAT_MIN : f32 = -1.0; const FLOAT_MAX : f32 = 1.0; } - -impl_volume_key_backing_type_from_float_for!(i8, u8); - -#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)] -struct VolumeKey<BackingType : VolumeKeyBackingTypeMetadata>(BackingType::BackingType); - -impl<BackingType : VolumeKeyBackingTypeMetadata> VolumeKey<BackingType> { - fn match_float(float : f32) -> Self { - let x = (float - BackingType::FLOAT_MIN) / (BackingType::FLOAT_MAX - BackingType::FLOAT_MIN); - let cx = x.clamp(0.0,1.0); - let interval = BackingType::MAX.into() - BackingType::MIN.into(); - let offset = interval * cx; - let result = BackingType::BackingType::round_from_float(offset) + BackingType::MIN; - Self(result) - } - -} -/// Custom serializer, as TOML only supports string map keys. -impl<Metadata : VolumeKeyBackingTypeMetadata> Serialize for VolumeKey<Metadata> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where S : Serializer - { - let string = self.0.to_string(); - serializer.serialize_str(&string) - } -} -impl<'de, Metadata> Deserialize<'de> for VolumeKey<Metadata> - where Metadata : VolumeKeyBackingTypeMetadata, -{ - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where D: Deserializer<'de> - { - let a = String::deserialize(deserializer)?; - match a.parse() { - Ok(x) => { - if x >= Metadata::MIN && x <= Metadata::MAX { - Ok(Self(x)) - } - else { - Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) - } - } - Err(e) => { - match e.kind() { - IntErrorKind::Empty => { Err(DeError::missing_field("Bin Map Key")) } - IntErrorKind::InvalidDigit => { Err(DeError::invalid_type(DeUnexpect::Str(&a), &"an integer value")) } - IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) } - IntErrorKind::Zero => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("a nonzero integer between {} and {}", Metadata::MIN, Metadata::MAX)))} - _ => { Err(DeError::custom("Value could not be parsed")) } - } - } - } - } -} diff --git a/pulse/src/runnable/mod.rs b/pulse/src/runnable/mod.rs index b207874..4a89c5c 100644 --- a/pulse/src/runnable/mod.rs +++ b/pulse/src/runnable/mod.rs @@ -3,6 +3,7 @@ use crate::config::*; use swaystatus_plugin::*; use crate::communication::*; use std::convert::TryFrom; +use formatable_float::FormattingError; pub mod pulse; use pulse::{Pulse,MainLoopCreationError, PulseContext, PaContextState, SinkHandle, PulseOperation}; diff --git a/swaystatus/Cargo.toml b/swaystatus/Cargo.toml index d32b6ae..7faab6f 100644 --- a/swaystatus/Cargo.toml +++ b/swaystatus/Cargo.toml @@ -15,7 +15,7 @@ erased-serde = "0.3" toml = "0.5" libloading = "0.7" signal-hook = { version = "0.3", default-features = false, features = ["iterator"]} -clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "cargo", "wrap_help"] } +clap = { version = "3.2.23", default-features = false, features = ["std", "cargo", "wrap_help"] } dirs = "3.0" swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'} diff --git a/swaystatus/src/commandline/mod.rs b/swaystatus/src/commandline/mod.rs index d615355..229ed98 100644 --- a/swaystatus/src/commandline/mod.rs +++ b/swaystatus/src/commandline/mod.rs @@ -32,20 +32,20 @@ pub fn parse_commandline() -> CommandlineParameters { Arg::new("help") .short('h') .long("help") - .about(&*gettext("Prints help information.")) + .help(&*gettext("Prints help information.")) .global(true)) .arg( Arg::new("version") .short('v') .long("version") - .about(&*gettext("Prints version information.")) + .help(&*gettext("Prints version information.")) .global(true)) .arg( Arg::new("config") .short('c') .long("config") .value_name(&*gettext("FILE")) - .about(&*gettext("Path to the configuration file")) + .help(&*gettext("Path to the configuration file")) .display_order(0) .takes_value(true)) .arg( @@ -53,14 +53,14 @@ pub fn parse_commandline() -> CommandlineParameters { .short('p') .long("plugins") .value_name(&*gettext("FOLDER")) - .about(&*gettext("Directory from which the plugins should be loaded")) + .help(&*gettext("Directory from which the plugins should be loaded")) .display_order(0) .takes_value(true)) .arg( Arg::new("sampleconfig") .long("print-sample-config") .short('s') - .about(&*gettext("Prints a sample config file. Beware that the contents of the sample file depend on the loaded plugins, so don't forget to supply the plugins parameter as needed.")) + .help(&*gettext("Prints a sample config file. Beware that the contents of the sample file depend on the loaded plugins, so don't forget to supply the plugins parameter as needed.")) .display_order(2) .takes_value(false) .conflicts_with_all(&["pluginhelp","pluginlist"])) @@ -69,7 +69,7 @@ pub fn parse_commandline() -> CommandlineParameters { .long("plugin-help") .short('g') .value_name(&*gettext("PLUGINS")) - .about(&*gettext("Prints plugin help messages. Either for a given list of plugins, or if no list given, for all loadable plugins.")) + .help(&*gettext("Prints plugin help messages. Either for a given list of plugins, or if no list given, for all loadable plugins.")) .min_values(0) .setting(ArgSettings::MultipleValues) .conflicts_with("pluginlist")) @@ -77,7 +77,7 @@ pub fn parse_commandline() -> CommandlineParameters { Arg::new("pluginlist") .long("list-plugins") .short('l') - .about(&*gettext("Prints a list of plugin names in the plugin folder.")) + .help(&*gettext("Prints a list of plugin names in the plugin folder.")) .display_order(1) .takes_value(false)) .after_help(&*gettext!("If no config path is given, the code looks for the \"swaystatus/config\" file in your XDG config folder (typically \"$HOME/.config/\"). If that lookup fails, loading of \"/etc/swaystatus/config\" is attempted. Similarly, if no plugin folder is given, first the existence of a folder named \"$HOME/.local/lib/swaystatus\" is checked. If this folder does not exist, a default path set at compile time is used, which in your case is \"{}\"." , get_hardcoded_default_library_path())) @@ -47,3 +47,27 @@ UnmuteSymbol = "🔊" [Element.General] BeforeText = "" AfterText = "" + +[[Element]] +Plugin = "AlsaVolume" + +[Element.Config] +device = [100, 101, 102, 97, 117, 108, 116] +element = [77, 97, 115, 116, 101, 114] +abstraction = "None" +sorting = "MuteVolume" + +[Element.Config.mute] +Format = "Symbol" +Label = "" +MuteSymbol = "🔇" +UnmuteSymbol = "🔊" + +[Element.Config.volume] +Format = "Numeric" +Label = " " +DecimalDigits = 0 + +[Element.General] +BeforeText = "" +AfterText = ""
\ No newline at end of file |
