commit
This commit is contained in:
parent
97cdbdea1c
commit
846a061063
9 changed files with 432 additions and 263 deletions
305
Cargo.lock
generated
305
Cargo.lock
generated
|
@ -76,7 +76,7 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -86,15 +86,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
|
@ -134,26 +128,6 @@ version = "0.21.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.66.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -166,6 +140,15 @@ version = "2.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
|
@ -193,25 +176,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.15.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -231,17 +195,6 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.13"
|
||||
|
@ -327,21 +280,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
|
@ -437,7 +375,7 @@ dependencies = [
|
|||
"log",
|
||||
"loupedeck_serial",
|
||||
"once_cell",
|
||||
"pipewire",
|
||||
"pa-volume-interface",
|
||||
"regex",
|
||||
"resvg",
|
||||
"rgb",
|
||||
|
@ -467,7 +405,7 @@ version = "0.99.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case 0.4.0",
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
|
@ -567,7 +505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -701,12 +639,6 @@ version = "0.28.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
|
@ -782,6 +714,20 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "im"
|
||||
version = "15.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.12.0"
|
||||
|
@ -834,7 +780,7 @@ checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -873,62 +819,18 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cc",
|
||||
"convert_case 0.6.0",
|
||||
"cookie-factory",
|
||||
"libc",
|
||||
"libspa-sys",
|
||||
"nix",
|
||||
"nom",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev"
|
||||
version = "0.3.0"
|
||||
|
@ -1018,21 +920,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -1061,18 +948,6 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1115,6 +990,15 @@ version = "3.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "pa-volume-interface"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flume",
|
||||
"im",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
|
@ -1138,12 +1022,6 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
|
@ -1156,40 +1034,6 @@ version = "0.2.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"pipewire-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.28"
|
||||
|
@ -1233,6 +1077,21 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rangemap"
|
||||
version = "1.4.0"
|
||||
|
@ -1355,7 +1214,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1531,12 +1390,6 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
|
@ -1558,6 +1411,16 @@ version = "0.3.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
|
@ -1648,25 +1511,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
|
@ -1887,6 +1731,12 @@ version = "0.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unescaper"
|
||||
version = "0.1.3"
|
||||
|
@ -2023,12 +1873,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -2151,15 +1995,6 @@ 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"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"deckster",
|
||||
"loupedeck_serial"
|
||||
"loupedeck_serial",
|
||||
"pa-volume-interface"
|
||||
]
|
||||
|
||||
resolver = "2"
|
||||
|
|
|
@ -17,6 +17,7 @@ flume = "0.11.0"
|
|||
humantime-serde = "1.1.1"
|
||||
log = "0.4.20"
|
||||
loupedeck_serial = { path = "../loupedeck_serial" }
|
||||
pa-volume-interface = { path = "../pa-volume-interface" }
|
||||
regex = "1.10.2"
|
||||
resvg = "0.37.0"
|
||||
rgb = "0.8.37"
|
||||
|
@ -29,4 +30,3 @@ tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-mul
|
|||
toml = "0.8.8"
|
||||
walkdir = "2.4.0"
|
||||
once_cell = "1.19.0"
|
||||
pipewire = "0.7.2"
|
|
@ -1,11 +1,9 @@
|
|||
[knobs.right-top]
|
||||
icon = "@ph/microphone-light[scale=0.9]"
|
||||
indicators.circle.color = "#ffffff"
|
||||
indicators.circle.width = 2
|
||||
indicators.circle.radius = 20
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.direction = "input"
|
||||
mode.audio_volume.regex = "Microphone"
|
||||
mode.audio_volume.regex = "^(SC425 USB Microphone Analog Stereo)$"
|
||||
mode.audio_volume.disable_press_to_unmute = true
|
||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
||||
mode.audio_volume.style.muted.label = "Muted"
|
||||
|
@ -13,7 +11,7 @@ mode.audio_volume.style.inactive.icon = "@ph/microphone-slash-light[alpha=0.9|co
|
|||
|
||||
[knobs.right-middle]
|
||||
icon = "@apps/discord[scale=0.25]"
|
||||
indicators.bar.color = "#ffffff"
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.regex = "Discord"
|
||||
mode.audio_volume.style.active.label = "{percentage}%"
|
||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use thiserror::Error;
|
||||
|
||||
use loupedeck_serial::characteristics::LoupedeckButton;
|
||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckKnob};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
|
||||
/// One-based coordinates of a specific virtual (not physical) key.
|
||||
|
@ -67,6 +67,19 @@ pub enum KnobPosition {
|
|||
RightBottom,
|
||||
}
|
||||
|
||||
impl From<LoupedeckKnob> for KnobPosition {
|
||||
fn from(value: LoupedeckKnob) -> Self {
|
||||
match value {
|
||||
LoupedeckKnob::LeftTop => KnobPosition::LeftTop,
|
||||
LoupedeckKnob::LeftMiddle => KnobPosition::LeftMiddle,
|
||||
LoupedeckKnob::LeftBottom => KnobPosition::LeftBottom,
|
||||
LoupedeckKnob::RightTop => KnobPosition::RightTop,
|
||||
LoupedeckKnob::RightMiddle => KnobPosition::RightMiddle,
|
||||
LoupedeckKnob::RightBottom => KnobPosition::RightBottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||
pub struct KnobPath {
|
||||
pub page_id: String,
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use tokio::select;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use pa_volume_interface::{PaEntityState, PaVolumeInterface};
|
||||
|
||||
use crate::model::knob_page::StyleByStateMap;
|
||||
use crate::model::position::KnobPath;
|
||||
use crate::modes::knob::{KnobEvent, RotationDirection};
|
||||
|
@ -59,23 +64,57 @@ pub enum State {
|
|||
Muted,
|
||||
}
|
||||
|
||||
static PA_VOLUME_INTERFACE: Lazy<PaVolumeInterface> = Lazy::new(|| {
|
||||
let interface = PaVolumeInterface::spawn_thread(Duration::from_millis(500));
|
||||
interface.query_state();
|
||||
interface
|
||||
});
|
||||
|
||||
pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, commands: flume::Sender<IoWorkerCommand>) {
|
||||
let mut volume_update_timestamp = Instant::now();
|
||||
let mut value: f32 = 0.0;
|
||||
let mut entity_state: Option<Arc<PaEntityState>> = None;
|
||||
|
||||
while let Ok((event_path, event)) = events.recv().await {
|
||||
if event_path != path {
|
||||
continue;
|
||||
}
|
||||
let pa_volume_interface = &PA_VOLUME_INTERFACE;
|
||||
let mut volume_states = pa_volume_interface.subscribe_to_state();
|
||||
|
||||
if let KnobEvent::Rotate { direction } = event {
|
||||
let factor = match direction {
|
||||
RotationDirection::Clockwise => 1.0,
|
||||
RotationDirection::Counterclockwise => -1.0,
|
||||
};
|
||||
loop {
|
||||
select! {
|
||||
Ok(volume_state) = volume_states.recv() => {
|
||||
entity_state = volume_state.entities_by_id().values().find(|entity| config.regex.is_match(entity.name())).map(Arc::clone);
|
||||
|
||||
value = (value + (factor * config.delta)).clamp(0.0, 1.0);
|
||||
if volume_state.timestamp() > &volume_update_timestamp {
|
||||
value = entity_state.as_ref().map(|s| *s.channel_volumes().first().unwrap()).unwrap_or(0.0);
|
||||
commands.send(IoWorkerCommand::SetKnobValue { path: path.clone(), value }).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
commands.send(IoWorkerCommand::SetKnobValue { path: path.clone(), value }).unwrap();
|
||||
Ok((event_path, event)) = events.recv() => {
|
||||
if event_path != path {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(entity_state) = &entity_state {
|
||||
match event {
|
||||
KnobEvent::Rotate { direction } => {
|
||||
let factor = match direction {
|
||||
RotationDirection::Clockwise => 1.0,
|
||||
RotationDirection::Counterclockwise => -1.0,
|
||||
};
|
||||
|
||||
volume_update_timestamp = Instant::now();
|
||||
value = (value + (factor * config.delta)).clamp(0.0, 1.0);
|
||||
pa_volume_interface.set_channel_volumes(*entity_state.id(), vec![value; entity_state.channel_volumes().len()]);
|
||||
|
||||
commands.send(IoWorkerCommand::SetKnobValue { path: path.clone(), value }).unwrap();
|
||||
}
|
||||
KnobEvent::Press => {
|
||||
pa_volume_interface.set_is_muted(*entity_state.id(), !entity_state.is_muted())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,14 +266,7 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
|||
}
|
||||
}
|
||||
LoupedeckEvent::KnobRotate { knob, direction } => {
|
||||
let position = match knob {
|
||||
LoupedeckKnob::LeftTop => KnobPosition::LeftTop,
|
||||
LoupedeckKnob::LeftMiddle => KnobPosition::LeftMiddle,
|
||||
LoupedeckKnob::LeftBottom => KnobPosition::LeftBottom,
|
||||
LoupedeckKnob::RightTop => KnobPosition::RightTop,
|
||||
LoupedeckKnob::RightMiddle => KnobPosition::RightMiddle,
|
||||
LoupedeckKnob::RightBottom => KnobPosition::RightBottom,
|
||||
};
|
||||
let position: KnobPosition = knob.into();
|
||||
|
||||
send_knob_event(
|
||||
KnobPath {
|
||||
|
@ -288,6 +281,17 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
|||
},
|
||||
)
|
||||
}
|
||||
LoupedeckEvent::KnobDown { knob } => {
|
||||
let position: KnobPosition = knob.into();
|
||||
|
||||
send_knob_event(
|
||||
KnobPath {
|
||||
page_id: state.active_knob_page_id.clone(),
|
||||
position,
|
||||
},
|
||||
KnobEvent::Press,
|
||||
)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
9
pa-volume-interface/Cargo.toml
Normal file
9
pa-volume-interface/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "pa-volume-interface"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
flume = "0.11.0"
|
||||
im = "15.1.0"
|
||||
tokio = { version = "1.35.1", default-features = false, features = ["sync"] }
|
270
pa-volume-interface/src/lib.rs
Normal file
270
pa-volume-interface/src/lib.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use im::HashMap;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub type PaEntityId = usize;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum PaEntityKind {
|
||||
Source,
|
||||
Sink,
|
||||
Application,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaEntityState {
|
||||
id: PaEntityId,
|
||||
kind: PaEntityKind,
|
||||
name: String,
|
||||
channel_volumes: Box<[f32]>,
|
||||
is_muted: bool,
|
||||
}
|
||||
|
||||
impl PaEntityState {
|
||||
pub fn id(&self) -> &PaEntityId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &PaEntityKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn channel_volumes(&self) -> &[f32] {
|
||||
&self.channel_volumes
|
||||
}
|
||||
|
||||
pub fn is_muted(&self) -> bool {
|
||||
self.is_muted
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaVolumeState {
|
||||
timestamp: Instant,
|
||||
entities_by_id: HashMap<PaEntityId, Arc<PaEntityState>>,
|
||||
default_sink_id: PaEntityId,
|
||||
default_source_id: PaEntityId,
|
||||
}
|
||||
|
||||
impl PaVolumeState {
|
||||
pub fn timestamp(&self) -> &Instant {
|
||||
&self.timestamp
|
||||
}
|
||||
|
||||
pub fn entities_by_id(&self) -> &HashMap<PaEntityId, Arc<PaEntityState>> {
|
||||
&self.entities_by_id
|
||||
}
|
||||
|
||||
pub fn default_sink_id(&self) -> &PaEntityId {
|
||||
&self.default_sink_id
|
||||
}
|
||||
|
||||
pub fn default_source_id(&self) -> &PaEntityId {
|
||||
&self.default_source_id
|
||||
}
|
||||
}
|
||||
|
||||
enum PaCommand {
|
||||
QueryState,
|
||||
SetIsMuted { id: PaEntityId, value: bool },
|
||||
SetChannelVolumes { id: PaEntityId, channel_volumes: Box<[f32]> },
|
||||
Terminate,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PaThread {
|
||||
commands_tx: flume::Sender<PaCommand>,
|
||||
}
|
||||
|
||||
fn query_and_publish_state(state_tx: &broadcast::Sender<PaVolumeState>) {
|
||||
let output = Command::new("pulsemixer").arg("-l").output().unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
panic!("`pulsemixer -l` failed");
|
||||
}
|
||||
|
||||
let raw = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let mut default_sink_id = 0;
|
||||
let mut default_source_id = 0;
|
||||
|
||||
let entities_by_id: HashMap<_, _> = raw
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let (prefix, rest) = line.split_once(':').unwrap();
|
||||
let kind = match prefix {
|
||||
"Sink" => PaEntityKind::Sink,
|
||||
"Source" => PaEntityKind::Source,
|
||||
"Sink input" => PaEntityKind::Application,
|
||||
x => panic!("Unknown entity kind: {}", x),
|
||||
};
|
||||
|
||||
let segments: Vec<&str> = rest.split(", ").collect();
|
||||
|
||||
let id = segments[0]
|
||||
.trim_start()
|
||||
.strip_prefix("ID: ")
|
||||
.unwrap()
|
||||
.split('-')
|
||||
.last()
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
|
||||
let name = segments[1].strip_prefix("Name: ").unwrap();
|
||||
let is_muted = segments[2].strip_prefix("Mute: ").unwrap().parse::<u8>().unwrap() == 1;
|
||||
|
||||
let channel_count = segments[3].strip_prefix("Channels: ").unwrap().parse::<usize>().unwrap();
|
||||
|
||||
let channel_volumes: Box<[f32]> = segments[4..(4 + channel_count)]
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let s = s.strip_prefix("Volumes: [").unwrap_or(s);
|
||||
s.strip_suffix(']').unwrap_or(s)
|
||||
})
|
||||
.map(|s| &s[1..(s.len() - 2)])
|
||||
.map(|s| s.parse::<f32>().unwrap() / 100.0)
|
||||
.collect();
|
||||
|
||||
if segments.last().unwrap() == &"Default" {
|
||||
match kind {
|
||||
PaEntityKind::Source => {
|
||||
default_source_id = id;
|
||||
}
|
||||
PaEntityKind::Sink => {
|
||||
default_sink_id = id;
|
||||
}
|
||||
PaEntityKind::Application => {
|
||||
panic!("Sink sources cannot be a default");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
id,
|
||||
Arc::new(PaEntityState {
|
||||
id,
|
||||
kind,
|
||||
name: name.to_owned(),
|
||||
is_muted,
|
||||
channel_volumes,
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
state_tx
|
||||
.send(PaVolumeState {
|
||||
timestamp: Instant::now(),
|
||||
entities_by_id,
|
||||
default_sink_id,
|
||||
default_source_id,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
impl PaThread {
|
||||
fn spawn(
|
||||
max_time_between_queries: Duration,
|
||||
commands_tx: flume::Sender<PaCommand>,
|
||||
commands_rx: flume::Receiver<PaCommand>,
|
||||
state_tx: broadcast::Sender<PaVolumeState>,
|
||||
) -> PaThread {
|
||||
thread::spawn(move || loop {
|
||||
let command = commands_rx.recv_timeout(max_time_between_queries).unwrap_or(PaCommand::QueryState);
|
||||
|
||||
match command {
|
||||
PaCommand::SetIsMuted { id, value } => {
|
||||
let action = if value { "--mute" } else { "--unmute" };
|
||||
|
||||
let status = Command::new("pulsemixer").args(["--id", &id.to_string(), action]).status().unwrap();
|
||||
|
||||
if !status.success() {
|
||||
panic!("(Un-)muting failed with status code {:?}", status.code());
|
||||
}
|
||||
}
|
||||
PaCommand::SetChannelVolumes { id, channel_volumes } => {
|
||||
let volumes = channel_volumes
|
||||
.iter()
|
||||
.map(|v| (v * 100.0).round() as u32)
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(":");
|
||||
|
||||
let status = Command::new("pulsemixer")
|
||||
.args(["--id", &id.to_string(), "--set-volume-all", &volumes])
|
||||
.status()
|
||||
.unwrap();
|
||||
|
||||
if !status.success() {
|
||||
panic!("Setting the channel values failed with status code {:?}", status.code());
|
||||
}
|
||||
}
|
||||
PaCommand::Terminate => break,
|
||||
PaCommand::QueryState => {}
|
||||
}
|
||||
|
||||
query_and_publish_state(&state_tx)
|
||||
});
|
||||
|
||||
PaThread { commands_tx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PaThread {
|
||||
fn drop(&mut self) {
|
||||
self.commands_tx.send(PaCommand::Terminate).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaVolumeInterface {
|
||||
#[allow(unused)]
|
||||
thread: Arc<PaThread>,
|
||||
state_tx: broadcast::Sender<PaVolumeState>,
|
||||
commands_tx: flume::Sender<PaCommand>,
|
||||
}
|
||||
|
||||
impl PaVolumeInterface {
|
||||
pub fn spawn_thread(max_time_between_queries: Duration) -> PaVolumeInterface {
|
||||
let (commands_tx, commands_rx) = flume::unbounded();
|
||||
let state_tx = broadcast::Sender::new(5);
|
||||
|
||||
let thread = PaThread::spawn(max_time_between_queries, commands_tx.clone(), commands_rx, state_tx.clone());
|
||||
|
||||
PaVolumeInterface {
|
||||
thread: Arc::new(thread),
|
||||
commands_tx,
|
||||
state_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_to_state(&self) -> broadcast::Receiver<PaVolumeState> {
|
||||
self.state_tx.subscribe()
|
||||
}
|
||||
|
||||
pub fn query_state(&self) {
|
||||
self.commands_tx.send(PaCommand::QueryState).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_is_muted(&self, id: PaEntityId, value: bool) {
|
||||
self.commands_tx.send(PaCommand::SetIsMuted { id, value }).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_channel_volumes(&self, id: PaEntityId, channel_volumes: impl Into<Box<[f32]>>) {
|
||||
self.commands_tx
|
||||
.send(PaCommand::SetChannelVolumes {
|
||||
id,
|
||||
channel_volumes: channel_volumes.into(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue