Initial commit
This commit is contained in:
commit
1b757150ea
8 changed files with 1182 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/.idea
|
531
Cargo.lock
generated
Normal file
531
Cargo.lock
generated
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.74"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[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.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flume"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"nanorand",
|
||||||
|
"spin",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
|
||||||
|
|
||||||
|
[[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 = "js-sys"
|
||||||
|
version = "0.3.70"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.158"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libpulse-binding"
|
||||||
|
version = "2.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed3557a2dfc380c8f061189a01c6ae7348354e0c9886038dc6c171219c08eaff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"libpulse-sys",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libpulse-sys"
|
||||||
|
version = "1.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc19e110fbf42c17260d30f6d3dc545f58491c7830d38ecb9aaca96e26067a9b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"pkg-config",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nanorand"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulseaudio-volume-interface"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"flume",
|
||||||
|
"im",
|
||||||
|
"libpulse-binding",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
|
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 = "rustc-demangle"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[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 = "spin"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.40.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.77",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.77",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "pulseaudio-volume-interface"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
flume = "0.11.0"
|
||||||
|
im = "15.1.0"
|
||||||
|
tokio = { version = "1.38.0", default-features = false, features = ["sync"] }
|
||||||
|
libpulse-binding = "2.28.1"
|
||||||
|
log = "0.4.21"
|
||||||
|
arc-swap = "1.7.1"
|
55
LICENSE.md
Normal file
55
LICENSE.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Blue Oak Model License
|
||||||
|
|
||||||
|
Version 1.0.0
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This license gives everyone as much permission to work with
|
||||||
|
this software as possible, while protecting contributors
|
||||||
|
from liability.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
In order to receive this license, you must agree to its
|
||||||
|
rules. The rules of this license are both obligations
|
||||||
|
under that agreement and conditions to your license.
|
||||||
|
You must not do anything with this software that triggers
|
||||||
|
a rule that you cannot or will not follow.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
Each contributor licenses you to do everything with this
|
||||||
|
software that would otherwise infringe that contributor's
|
||||||
|
copyright in it.
|
||||||
|
|
||||||
|
## Notices
|
||||||
|
|
||||||
|
You must ensure that everyone who gets a copy of
|
||||||
|
any part of this software from you, with or without
|
||||||
|
changes, also gets the text of this license or a link to
|
||||||
|
<https://blueoakcouncil.org/license/1.0.0>.
|
||||||
|
|
||||||
|
## Excuse
|
||||||
|
|
||||||
|
If anyone notifies you in writing that you have not
|
||||||
|
complied with [Notices](#notices), you can keep your
|
||||||
|
license by taking all practical steps to comply within 30
|
||||||
|
days after the notice. If you do not do so, your license
|
||||||
|
ends immediately.
|
||||||
|
|
||||||
|
## Patent
|
||||||
|
|
||||||
|
Each contributor licenses you to do everything with this
|
||||||
|
software that would otherwise infringe any patent claims
|
||||||
|
they can license or become able to license.
|
||||||
|
|
||||||
|
## Reliability
|
||||||
|
|
||||||
|
No contributor can revoke this license.
|
||||||
|
|
||||||
|
## No Liability
|
||||||
|
|
||||||
|
***As far as the law allows, this software comes as is,
|
||||||
|
without any warranty or condition, and no contributor
|
||||||
|
will be liable to anyone for any damages related to this
|
||||||
|
software or this license, under any kind of legal claim.***
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# pulseaudio-volume-interface
|
||||||
|
> Rust library for observing and modifying PulseAudio source, sink, and sink input volumes. Also allows to set the default source and sink.
|
||||||
|
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
max_width = 160
|
208
src/lib.rs
Normal file
208
src/lib.rs
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
mod worker;
|
||||||
|
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
use crate::worker::*;
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use im::HashMap;
|
||||||
|
use libpulse_binding::context::introspect::{SinkInfo, SinkInputInfo, SourceInfo};
|
||||||
|
use libpulse_binding::volume::{ChannelVolumes, Volume};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use libpulse_binding::mainloop::api::Mainloop as _;
|
||||||
|
|
||||||
|
pub type EntityId = NonZeroU32;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
|
pub enum EntityKind {
|
||||||
|
Source,
|
||||||
|
Sink,
|
||||||
|
SinkInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum EntityMetadata {
|
||||||
|
Source {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
},
|
||||||
|
Sink {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
},
|
||||||
|
SinkInput {
|
||||||
|
description: String,
|
||||||
|
binary_name: Option<String>,
|
||||||
|
application_name: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct EntityState {
|
||||||
|
id: EntityId,
|
||||||
|
is_muted: bool,
|
||||||
|
channel_volumes: ChannelVolumes,
|
||||||
|
metadata: EntityMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityState {
|
||||||
|
pub fn id(&self) -> &EntityId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_muted(&self) -> bool {
|
||||||
|
self.is_muted
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_volumes(&self) -> Vec<f32> {
|
||||||
|
self.channel_volumes.get().iter().map(|v| v.0 as f32 / Volume::NORMAL.0 as f32).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn metadata(&self) -> &EntityMetadata {
|
||||||
|
&self.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> EntityKind {
|
||||||
|
match &self.metadata {
|
||||||
|
EntityMetadata::Source { .. } => EntityKind::Source,
|
||||||
|
EntityMetadata::Sink { .. } => EntityKind::Sink,
|
||||||
|
EntityMetadata::SinkInput { .. } => EntityKind::SinkInput,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SourceInfo<'_>> for EntityState {
|
||||||
|
fn from(value: &SourceInfo) -> Self {
|
||||||
|
EntityState {
|
||||||
|
id: NonZeroU32::new(value.index).unwrap(),
|
||||||
|
is_muted: value.mute,
|
||||||
|
channel_volumes: value.volume,
|
||||||
|
metadata: EntityMetadata::Source {
|
||||||
|
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||||
|
description: value.description.clone().unwrap_or_default().into_owned(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SinkInfo<'_>> for EntityState {
|
||||||
|
fn from(value: &SinkInfo) -> Self {
|
||||||
|
EntityState {
|
||||||
|
id: NonZeroU32::new(value.index).unwrap(),
|
||||||
|
is_muted: value.mute,
|
||||||
|
channel_volumes: value.volume,
|
||||||
|
metadata: EntityMetadata::Sink {
|
||||||
|
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||||
|
description: value.description.clone().unwrap_or_default().into_owned(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SinkInputInfo<'_>> for EntityState {
|
||||||
|
fn from(value: &SinkInputInfo) -> Self {
|
||||||
|
EntityState {
|
||||||
|
id: NonZeroU32::new(value.index).unwrap(),
|
||||||
|
is_muted: value.mute,
|
||||||
|
channel_volumes: value.volume,
|
||||||
|
metadata: EntityMetadata::SinkInput {
|
||||||
|
description: value.name.clone().unwrap_or_default().into_owned(),
|
||||||
|
application_name: value
|
||||||
|
.proplist
|
||||||
|
.get("application.name")
|
||||||
|
.map(|v| String::from_utf8_lossy(v).trim_end_matches(char::from(0)).to_owned()),
|
||||||
|
binary_name: value
|
||||||
|
.proplist
|
||||||
|
.get("application.process.binary")
|
||||||
|
.map(|v| String::from_utf8_lossy(v).trim_end_matches(char::from(0)).to_owned()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct State {
|
||||||
|
timestamp: Instant,
|
||||||
|
default_sink_id: Option<EntityId>,
|
||||||
|
default_source_id: Option<EntityId>,
|
||||||
|
entities_by_id: HashMap<EntityId, Arc<EntityState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn timestamp(&self) -> &Instant {
|
||||||
|
&self.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_sink_id(&self) -> &Option<EntityId> { &self.default_sink_id }
|
||||||
|
|
||||||
|
pub fn default_source_id(&self) -> &Option<EntityId> { &self.default_source_id }
|
||||||
|
|
||||||
|
pub fn entities_by_id(&self) -> &HashMap<EntityId, Arc<EntityState>> {
|
||||||
|
&self.entities_by_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PulseAudioVolumeInterface {
|
||||||
|
#[allow(unused)]
|
||||||
|
worker: Arc<PaWorker>,
|
||||||
|
current_state: Arc<ArcSwap<State>>,
|
||||||
|
state_tx: broadcast::Sender<Arc<State>>,
|
||||||
|
commands_tx: flume::Sender<ThreadMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PulseAudioVolumeInterface {
|
||||||
|
pub fn new(client_name: String) -> PulseAudioVolumeInterface {
|
||||||
|
let (commands_tx, commands_rx) = flume::unbounded();
|
||||||
|
let state_tx = broadcast::Sender::new(5);
|
||||||
|
let current_state = Arc::new(ArcSwap::new(Arc::new(State {
|
||||||
|
timestamp: Instant::now(),
|
||||||
|
default_sink_id: None,
|
||||||
|
default_source_id: None,
|
||||||
|
entities_by_id: HashMap::new(),
|
||||||
|
})));
|
||||||
|
|
||||||
|
PaThread::spawn(client_name, commands_tx.clone(), commands_rx, state_tx.clone(), Arc::clone(¤t_state));
|
||||||
|
|
||||||
|
let worker = PaWorker {
|
||||||
|
commands_tx: commands_tx.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
PulseAudioVolumeInterface {
|
||||||
|
worker: Arc::new(worker),
|
||||||
|
current_state,
|
||||||
|
commands_tx,
|
||||||
|
state_tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_to_state(&self) -> (Arc<State>, Receiver<Arc<State>>) {
|
||||||
|
let rx = self.state_tx.subscribe();
|
||||||
|
let state = self.current_state();
|
||||||
|
(state, rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_state(&self) -> Arc<State> {
|
||||||
|
Arc::clone(&self.current_state.load())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_is_muted(&self, id: EntityId, value: bool) {
|
||||||
|
self.commands_tx.send(ThreadMessage::SetIsMuted { id, value }).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_channel_volumes(&self, id: EntityId, channel_volumes: impl Into<Box<[f32]>>) {
|
||||||
|
self.commands_tx
|
||||||
|
.send(ThreadMessage::SetChannelVolumes {
|
||||||
|
id,
|
||||||
|
channel_volumes: channel_volumes.into(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_default_entity(&self, id: EntityId) {
|
||||||
|
self.commands_tx.send(ThreadMessage::SetDefaultEntity { id }).unwrap()
|
||||||
|
}
|
||||||
|
}
|
370
src/worker.rs
Normal file
370
src/worker.rs
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
use crate::{EntityId, EntityKind, EntityMetadata, EntityState, State};
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use libpulse_binding::callbacks::ListResult;
|
||||||
|
use libpulse_binding::context::introspect::{Introspector, ServerInfo};
|
||||||
|
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet};
|
||||||
|
use libpulse_binding::context::{subscribe, Context, FlagSet, State as ConnectionState};
|
||||||
|
use libpulse_binding::def::Retval;
|
||||||
|
use libpulse_binding::mainloop::api::Mainloop as _;
|
||||||
|
use libpulse_binding::mainloop::threaded::Mainloop;
|
||||||
|
use libpulse_binding::volume::Volume;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum ThreadMessage {
|
||||||
|
// emitted by the library user
|
||||||
|
SetIsMuted {
|
||||||
|
id: EntityId,
|
||||||
|
value: bool,
|
||||||
|
},
|
||||||
|
SetChannelVolumes {
|
||||||
|
id: EntityId,
|
||||||
|
channel_volumes: Box<[f32]>,
|
||||||
|
},
|
||||||
|
SetDefaultEntity {
|
||||||
|
id: EntityId,
|
||||||
|
},
|
||||||
|
Terminate,
|
||||||
|
|
||||||
|
// emitted by libpulse
|
||||||
|
LoadServerInfo,
|
||||||
|
LoadSinkInput {
|
||||||
|
entity_id: EntityId,
|
||||||
|
},
|
||||||
|
LoadSink {
|
||||||
|
entity_id: EntityId,
|
||||||
|
},
|
||||||
|
LoadSource {
|
||||||
|
entity_id: EntityId,
|
||||||
|
},
|
||||||
|
|
||||||
|
// emitted by handlers for the messages above
|
||||||
|
HandleServerInfo {
|
||||||
|
default_source_name: Option<Box<str>>,
|
||||||
|
default_sink_name: Option<Box<str>>,
|
||||||
|
},
|
||||||
|
UpsertEntity {
|
||||||
|
entity_state: Arc<EntityState>,
|
||||||
|
},
|
||||||
|
RemoveEntity {
|
||||||
|
entity_id: EntityId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct PaThread {
|
||||||
|
mainloop: Mainloop,
|
||||||
|
context: Context,
|
||||||
|
introspector: Introspector,
|
||||||
|
commands_tx: flume::Sender<ThreadMessage>,
|
||||||
|
commands_rx: flume::Receiver<ThreadMessage>,
|
||||||
|
state_tx: broadcast::Sender<Arc<State>>,
|
||||||
|
current_state: Arc<ArcSwap<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaThread {
|
||||||
|
pub(super) fn spawn(
|
||||||
|
client_name: String,
|
||||||
|
commands_tx: flume::Sender<ThreadMessage>,
|
||||||
|
commands_rx: flume::Receiver<ThreadMessage>,
|
||||||
|
state_tx: broadcast::Sender<Arc<State>>,
|
||||||
|
current_state: Arc<ArcSwap<State>>,
|
||||||
|
) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut mainloop = Mainloop::new().unwrap();
|
||||||
|
let context = Context::new(&mainloop, &client_name).unwrap();
|
||||||
|
let introspector = context.introspect();
|
||||||
|
|
||||||
|
log::debug!("Starting the mainloop thread…");
|
||||||
|
mainloop.start().expect("starting the mainloop never fails");
|
||||||
|
|
||||||
|
let mut t = PaThread {
|
||||||
|
mainloop,
|
||||||
|
context,
|
||||||
|
introspector,
|
||||||
|
commands_tx,
|
||||||
|
commands_rx,
|
||||||
|
state_tx,
|
||||||
|
current_state,
|
||||||
|
};
|
||||||
|
|
||||||
|
t.init();
|
||||||
|
t.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
log::debug!("Initializing…");
|
||||||
|
self.mainloop.lock();
|
||||||
|
self.context.connect(None, FlagSet::NOFLAGS, None).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let (context_state_change_tx, context_state_change_rx) = flume::bounded(1);
|
||||||
|
self.context.set_state_callback(Some(Box::new(move || {
|
||||||
|
context_state_change_tx.send(()).unwrap();
|
||||||
|
})));
|
||||||
|
|
||||||
|
self.mainloop.unlock();
|
||||||
|
loop {
|
||||||
|
context_state_change_rx.recv().unwrap();
|
||||||
|
|
||||||
|
self.mainloop.lock();
|
||||||
|
if self.context.get_state() == ConnectionState::Ready {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.mainloop.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mainloop is still locked
|
||||||
|
|
||||||
|
{
|
||||||
|
let commands_tx = self.commands_tx.clone();
|
||||||
|
self.introspector.get_sink_input_info_list(move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_sink_input_info_list failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(sink_input) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(sink_input.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let commands_tx = self.commands_tx.clone();
|
||||||
|
self.introspector.get_sink_info_list(move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_sink_info_list failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(sink) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(sink.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let commands_tx = self.commands_tx.clone();
|
||||||
|
self.introspector.get_source_info_list(move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_source_info_list failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(source) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(source.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let commands_tx = self.commands_tx.clone();
|
||||||
|
self.context.set_subscribe_callback(Some(Box::new(move |facility, operation, entity_id| {
|
||||||
|
let entity_id = NonZeroU32::new(entity_id).unwrap();
|
||||||
|
let facility = facility.unwrap();
|
||||||
|
|
||||||
|
if facility == Facility::Server {
|
||||||
|
commands_tx.send(ThreadMessage::LoadServerInfo).unwrap();
|
||||||
|
} else {
|
||||||
|
match operation.unwrap() {
|
||||||
|
subscribe::Operation::Removed => {
|
||||||
|
commands_tx.send(ThreadMessage::RemoveEntity { entity_id }).unwrap();
|
||||||
|
}
|
||||||
|
subscribe::Operation::New | subscribe::Operation::Changed => {
|
||||||
|
match facility {
|
||||||
|
Facility::SinkInput => commands_tx.send(ThreadMessage::LoadSinkInput { entity_id }).unwrap(),
|
||||||
|
Facility::Sink => commands_tx.send(ThreadMessage::LoadSink { entity_id }).unwrap(),
|
||||||
|
Facility::Source => commands_tx.send(ThreadMessage::LoadSource { entity_id }).unwrap(),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.subscribe(InterestMaskSet::SERVER | InterestMaskSet::SINK | InterestMaskSet::SOURCE | InterestMaskSet::SINK_INPUT, |success| {
|
||||||
|
if !success {
|
||||||
|
panic!("Context.subscribe failed")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.mainloop.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(mut self) {
|
||||||
|
log::debug!("Waiting for commands…");
|
||||||
|
|
||||||
|
'outer: loop {
|
||||||
|
while let Ok(command) = self.commands_rx.recv() {
|
||||||
|
self.mainloop.lock();
|
||||||
|
let commands_tx = self.commands_tx.clone();
|
||||||
|
|
||||||
|
match command {
|
||||||
|
ThreadMessage::Terminate => {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
ThreadMessage::SetIsMuted { id, value } => {
|
||||||
|
if let Some(state) = self.current_state.load().entities_by_id.get(&id) {
|
||||||
|
match state.kind() {
|
||||||
|
EntityKind::Sink => self.introspector.set_sink_mute_by_index(id.into(), value, None),
|
||||||
|
EntityKind::Source => self.introspector.set_source_mute_by_index(id.into(), value, None),
|
||||||
|
EntityKind::SinkInput => self.introspector.set_sink_input_mute(id.into(), value, None),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThreadMessage::SetChannelVolumes { id, channel_volumes } => {
|
||||||
|
if let Some(state) = self.current_state.load().entities_by_id.get(&id) {
|
||||||
|
let mut value = state.channel_volumes;
|
||||||
|
for (i, v) in channel_volumes.iter().enumerate() {
|
||||||
|
value.set(i as u8, Volume((Volume::NORMAL.0 as f32 * v).floor() as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
match state.kind() {
|
||||||
|
EntityKind::Sink => self.introspector.set_sink_volume_by_index(id.into(), &value, None),
|
||||||
|
EntityKind::Source => self.introspector.set_source_volume_by_index(id.into(), &value, None),
|
||||||
|
EntityKind::SinkInput => self.introspector.set_sink_input_volume(id.into(), &value, None),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ThreadMessage::SetDefaultEntity { id } => {
|
||||||
|
if let Some(state) = self.current_state.load().entities_by_id.get(&id) {
|
||||||
|
match &state.metadata {
|
||||||
|
EntityMetadata::Sink { name, .. } => {
|
||||||
|
self.context.set_default_sink(name, |_| {});
|
||||||
|
}
|
||||||
|
EntityMetadata::Source { name, .. } => {
|
||||||
|
self.context.set_default_source(name, |_| {});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::error!("Setting default entity failed. Only sinks and sources can be defaults.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
log::error!("Setting default entity failed. Unknown entity id: {id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadMessage::LoadServerInfo => {
|
||||||
|
self.introspector.get_server_info(move |server_info| {
|
||||||
|
commands_tx.send(ThreadMessage::HandleServerInfo {
|
||||||
|
default_sink_name: server_info.default_sink_name.as_ref().map(|n| n.to_string().into_boxed_str()),
|
||||||
|
default_source_name: server_info.default_source_name.as_ref().map(|n| n.to_string().into_boxed_str()),
|
||||||
|
}).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ThreadMessage::LoadSinkInput { entity_id } => {
|
||||||
|
self.introspector.get_sink_input_info(entity_id.into(), move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_sink_input_info failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(sink_input) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(sink_input.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ThreadMessage::LoadSink { entity_id } => {
|
||||||
|
self.introspector
|
||||||
|
.get_sink_info_by_index(entity_id.into(), move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_sink_info_by_index failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(sink) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(sink.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ThreadMessage::LoadSource { entity_id } => {
|
||||||
|
self.introspector
|
||||||
|
.get_source_info_by_index(entity_id.into(), move |list_result| match list_result {
|
||||||
|
ListResult::Error => panic!("Introspector.get_source_info_by_index failed"),
|
||||||
|
ListResult::End => {}
|
||||||
|
ListResult::Item(source) => commands_tx
|
||||||
|
.send(ThreadMessage::UpsertEntity {
|
||||||
|
entity_state: Arc::new(source.into()),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadMessage::HandleServerInfo { default_sink_name, default_source_name} => {
|
||||||
|
let current_state = self.current_state.load();
|
||||||
|
self.set_state(Arc::new(State {
|
||||||
|
timestamp: Instant::now(),
|
||||||
|
default_source_id: default_source_name
|
||||||
|
.map(|search_name| {
|
||||||
|
current_state
|
||||||
|
.entities_by_id
|
||||||
|
.iter()
|
||||||
|
.find(|(_, e)| match &e.metadata {
|
||||||
|
EntityMetadata::Source { name, .. } => *name == *search_name,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.map(|(id, _)| (*id).into())
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
default_sink_id: default_sink_name
|
||||||
|
.map(|search_name| {
|
||||||
|
current_state
|
||||||
|
.entities_by_id
|
||||||
|
.iter()
|
||||||
|
.find(|(_, e)| match &e.metadata {
|
||||||
|
EntityMetadata::Sink { name, .. } => *name == *search_name,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.map(|(id, _)| (*id).into())
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
entities_by_id: current_state.entities_by_id.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
ThreadMessage::UpsertEntity { entity_state } => {
|
||||||
|
let current_state = self.current_state.load();
|
||||||
|
self.set_state(Arc::new(State {
|
||||||
|
timestamp: Instant::now(),
|
||||||
|
entities_by_id: current_state.entities_by_id.update(entity_state.id, entity_state),
|
||||||
|
..**current_state
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
ThreadMessage::RemoveEntity { entity_id } => {
|
||||||
|
let current_state = self.current_state.load();
|
||||||
|
self.set_state(Arc::new(State {
|
||||||
|
timestamp: Instant::now(),
|
||||||
|
entities_by_id: current_state.entities_by_id.without(&entity_id),
|
||||||
|
..**current_state
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mainloop.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mainloop.quit(Retval(0));
|
||||||
|
self.mainloop.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&self, value: Arc<State>) {
|
||||||
|
self.current_state.store(Arc::clone(&value));
|
||||||
|
|
||||||
|
// If there are no subscribers, that’s ok.
|
||||||
|
_ = self.state_tx.send(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct PaWorker {
|
||||||
|
pub(super) commands_tx: flume::Sender<ThreadMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for PaWorker {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.commands_tx.send(ThreadMessage::Terminate).ok();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue