mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-07-27 14:14:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1616d80ee9 | ||
|
|
d97d4c60d2 | ||
|
|
0c3d7cbd35 | ||
|
|
6d7abdbf53 | ||
|
|
41bb035b2c | ||
|
|
3873bf4270 | ||
|
|
2b60b47cce | ||
|
|
65ae8589db | ||
|
|
a33d023950 |
Generated
+112
-61
@@ -294,7 +294,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -303,6 +303,43 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audio-core"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93ebbf82d06013f4c41fe71303feb980cddd78496d904d06be627972de51a24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audioadapter"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91f87b70b051c5866680ad79f6743a42ccab264c009d1a71f4d33a3872ae60c8"
|
||||||
|
dependencies = [
|
||||||
|
"audio-core",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audioadapter-buffers"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9097d67933fb083d382ce980430afdb758aada60846010aee6be068c06cef0ca"
|
||||||
|
dependencies = [
|
||||||
|
"audioadapter",
|
||||||
|
"audioadapter-sample",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audioadapter-sample"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34ab94f2bc04a14e1f49ee5f222f66460e8a1b51627bdfedf34eed394d747938"
|
||||||
|
dependencies = [
|
||||||
|
"audio-core",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@@ -512,9 +549,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
|
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -575,9 +612,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.3"
|
version = "4.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776"
|
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -596,14 +633,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.3"
|
version = "4.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077"
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1087,9 +1124,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
@@ -1370,13 +1407,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-macros"
|
name = "foreign-types-macros"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1533,9 +1570,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
@@ -2128,9 +2165,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.186"
|
version = "0.2.189"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
@@ -2435,16 +2472,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-bigint"
|
|
||||||
version = "0.4.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
|
||||||
dependencies = [
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2465,17 +2492,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-rational"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
|
||||||
dependencies = [
|
|
||||||
"num-bigint",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2915,9 +2931,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opusic-sys"
|
name = "opusic-sys"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2804e694ef0de3b4cbb254de565053b7cb48d3398df7fd60c6c62bed40c5372a"
|
checksum = "6363e8eba20c5db07002d1d5f8aef11a1801e3ab11edf4c3333f9ad37c7ebf8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cmake",
|
"cmake",
|
||||||
]
|
]
|
||||||
@@ -3200,7 +3216,7 @@ checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwsp-cli"
|
name = "pwsp-cli"
|
||||||
version = "1.12.5"
|
version = "1.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -3211,7 +3227,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwsp-daemon"
|
name = "pwsp-daemon"
|
||||||
version = "1.12.5"
|
version = "1.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -3223,9 +3239,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwsp-gui"
|
name = "pwsp-gui"
|
||||||
version = "1.12.5"
|
version = "1.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"dirs",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
"egui_dnd",
|
"egui_dnd",
|
||||||
@@ -3247,7 +3264,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwsp-lib"
|
name = "pwsp-lib"
|
||||||
version = "1.12.5"
|
version = "1.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3278,9 +3295,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.39.4"
|
version = "0.41.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
|
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -3496,11 +3513,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rodio"
|
name = "rodio"
|
||||||
version = "0.22.2"
|
version = "0.22.2"
|
||||||
source = "git+https://github.com/arabianq/rodio.git?rev=4746803c2e5f67004264ac946708d47a9faf510c#4746803c2e5f67004264ac946708d47a9faf510c"
|
source = "git+https://github.com/arabianq/rodio.git?rev=610955cf524467690cc9abe1a6277e8bffcadb71#610955cf524467690cc9abe1a6277e8bffcadb71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cpal",
|
"cpal",
|
||||||
"dasp_sample",
|
"dasp_sample",
|
||||||
"num-rational",
|
"rubato",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"symphonia-adapter-libopus",
|
"symphonia-adapter-libopus",
|
||||||
"thiserror 2.0.19",
|
"thiserror 2.0.19",
|
||||||
@@ -3512,6 +3529,20 @@ version = "0.20.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rubato"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4be9c88e3d722d3d36939e41941f6b6f52810c1235bf19998a7d4535b402892"
|
||||||
|
dependencies = [
|
||||||
|
"audioadapter",
|
||||||
|
"audioadapter-buffers",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"visibility",
|
||||||
|
"windowfunctions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-i18n"
|
name = "rust-i18n"
|
||||||
version = "4.2.1"
|
version = "4.2.1"
|
||||||
@@ -3608,9 +3639,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.15.0"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3732,7 +3763,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3756,7 +3787,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4183,9 +4214,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.2"
|
version = "3.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -4307,7 +4338,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4708,6 +4739,17 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "visibility"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -4799,9 +4841,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-backend"
|
name = "wayland-backend"
|
||||||
version = "0.3.15"
|
version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
@@ -4813,9 +4855,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-client"
|
name = "wayland-client"
|
||||||
version = "0.31.14"
|
version = "0.31.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
@@ -4911,9 +4953,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-scanner"
|
name = "wayland-scanner"
|
||||||
version = "0.31.10"
|
version = "0.31.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
@@ -5096,6 +5138,15 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windowfunctions"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90628d739333b7c5d2ee0b70210b97b8cddc38440c682c96fd9e2c24c2db5f3a"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ members = [
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.12.5"
|
version = "1.14.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["arabian"]
|
authors = ["arabian"]
|
||||||
homepage = "https://pwsp.arabianq.ru"
|
homepage = "https://pwsp.arabianq.ru"
|
||||||
@@ -52,7 +52,7 @@ rustix = { version = "1.1.4", features = ["process"] }
|
|||||||
rust-i18n = "4.0.0"
|
rust-i18n = "4.0.0"
|
||||||
sys-locale = "0.3.2"
|
sys-locale = "0.3.2"
|
||||||
|
|
||||||
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "4746803c2e5f67004264ac946708d47a9faf510c", default-features = false, features = [
|
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "610955cf524467690cc9abe1a6277e8bffcadb71", default-features = false, features = [
|
||||||
"symphonia-all",
|
"symphonia-all",
|
||||||
"symphonia-libopus",
|
"symphonia-libopus",
|
||||||
"playback",
|
"playback",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pkgbase = pwsp-bin
|
pkgbase = pwsp-bin
|
||||||
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
|
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
|
||||||
pkgver = 1.12.5
|
pkgver = 1.14.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/arabianq/pipewire-soundpad
|
url = https://github.com/arabianq/pipewire-soundpad
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -10,11 +10,11 @@ pkgbase = pwsp-bin
|
|||||||
depends = alsa-lib
|
depends = alsa-lib
|
||||||
provides = pwsp
|
provides = pwsp
|
||||||
conflicts = pwsp
|
conflicts = pwsp
|
||||||
source = pipewire-soundpad-1.12.5.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.5.tar.gz
|
source = pipewire-soundpad-1.14.0.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.14.0.tar.gz
|
||||||
sha256sums = SKIP
|
sha256sums = SKIP
|
||||||
source_x86_64 = pwsp-1.12.5-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.5/pwsp-v1.12.5-linux-x64.zip
|
source_x86_64 = pwsp-1.14.0-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.14.0/pwsp-v1.14.0-linux-x64.zip
|
||||||
sha256sums_x86_64 = SKIP
|
sha256sums_x86_64 = SKIP
|
||||||
source_aarch64 = pwsp-1.12.5-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.5/pwsp-v1.12.5-linux-arm64.zip
|
source_aarch64 = pwsp-1.14.0-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.14.0/pwsp-v1.14.0-linux-arm64.zip
|
||||||
sha256sums_aarch64 = SKIP
|
sha256sums_aarch64 = SKIP
|
||||||
|
|
||||||
pkgname = pwsp-bin
|
pkgname = pwsp-bin
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
||||||
pkgname=pwsp-bin
|
pkgname=pwsp-bin
|
||||||
_pkgname=pipewire-soundpad
|
_pkgname=pipewire-soundpad
|
||||||
pkgver=1.12.5
|
pkgver=1.14.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
|
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pkgbase = pwsp
|
pkgbase = pwsp
|
||||||
pkgdesc = Lets you play audio files through your microphone
|
pkgdesc = Lets you play audio files through your microphone
|
||||||
pkgver = 1.12.5
|
pkgver = 1.14.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/arabianq/pipewire-soundpad
|
url = https://github.com/arabianq/pipewire-soundpad
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -12,7 +12,7 @@ pkgbase = pwsp
|
|||||||
makedepends = cmake
|
makedepends = cmake
|
||||||
makedepends = pipewire
|
makedepends = pipewire
|
||||||
makedepends = alsa-lib
|
makedepends = alsa-lib
|
||||||
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.5.tar.gz
|
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.14.0.tar.gz
|
||||||
sha256sums = SKIP
|
sha256sums = SKIP
|
||||||
|
|
||||||
pkgname = pwsp
|
pkgname = pwsp
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
||||||
pkgsubn=pwsp
|
pkgsubn=pwsp
|
||||||
pkgname=pwsp
|
pkgname=pwsp
|
||||||
pkgver=1.12.5
|
pkgver=1.14.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lets you play audio files through your microphone"
|
pkgdesc="Lets you play audio files through your microphone"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
+166
-101
File diff suppressed because one or more lines are too long
@@ -25,7 +25,7 @@
|
|||||||
<name>arabian</name>
|
<name>arabian</name>
|
||||||
</developer>
|
</developer>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="1.12.5" date="2026-07-20" />
|
<release version="1.14.0" date="2026-07-24" />
|
||||||
</releases>
|
</releases>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
</component>
|
</component>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
|
|
||||||
Name: pwsp
|
Name: pwsp
|
||||||
Version: 1.12.5
|
Version: 1.14.0
|
||||||
Release: %autorelease
|
Release: %autorelease
|
||||||
Summary: Lets you play audio files through your microphone
|
Summary: Lets you play audio files through your microphone
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ enum Actions {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Daemon configuration
|
||||||
|
SaveDaemonConfig,
|
||||||
/// Play a sound by hotkey slot name
|
/// Play a sound by hotkey slot name
|
||||||
PlayHotkey { slot: String },
|
PlayHotkey { slot: String },
|
||||||
/// Remove the hotkey slot
|
/// Remove the hotkey slot
|
||||||
@@ -86,6 +88,8 @@ enum GetCommands {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Volume multiplier for all tracks
|
||||||
|
VolumeMultiplier,
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position {
|
Position {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
@@ -106,6 +110,8 @@ enum GetCommands {
|
|||||||
Inputs,
|
Inputs,
|
||||||
/// Version of the daemon
|
/// Version of the daemon
|
||||||
DaemonVersion,
|
DaemonVersion,
|
||||||
|
/// Daemon configuration
|
||||||
|
DaemonConfig,
|
||||||
/// Full player state
|
/// Full player state
|
||||||
FullState,
|
FullState,
|
||||||
/// All hotkey slots
|
/// All hotkey slots
|
||||||
@@ -120,6 +126,8 @@ enum SetCommands {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Volume multiplier for all tracks
|
||||||
|
VolumeMultiplier { volume: f32 },
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position {
|
Position {
|
||||||
position: f32,
|
position: f32,
|
||||||
@@ -165,6 +173,7 @@ async fn main() -> Result<()> {
|
|||||||
concurrent,
|
concurrent,
|
||||||
} => Request::play(&file_path.to_string_lossy(), concurrent),
|
} => Request::play(&file_path.to_string_lossy(), concurrent),
|
||||||
Actions::ToggleLoop { id } => Request::toggle_loop(id),
|
Actions::ToggleLoop { id } => Request::toggle_loop(id),
|
||||||
|
Actions::SaveDaemonConfig => Request::save_daemon_config(),
|
||||||
Actions::PlayHotkey { slot } => Request::play_hotkey(&slot),
|
Actions::PlayHotkey { slot } => Request::play_hotkey(&slot),
|
||||||
Actions::ClearHotkey { slot } => Request::clear_hotkey(&slot),
|
Actions::ClearHotkey { slot } => Request::clear_hotkey(&slot),
|
||||||
Actions::ClearHotkeyKey { slot } => Request::clear_hotkey_key(&slot),
|
Actions::ClearHotkeyKey { slot } => Request::clear_hotkey_key(&slot),
|
||||||
@@ -172,6 +181,7 @@ async fn main() -> Result<()> {
|
|||||||
Commands::Get { parameter } => match parameter {
|
Commands::Get { parameter } => match parameter {
|
||||||
GetCommands::IsPaused => Request::get_is_paused(),
|
GetCommands::IsPaused => Request::get_is_paused(),
|
||||||
GetCommands::Volume { id } => Request::get_volume(id),
|
GetCommands::Volume { id } => Request::get_volume(id),
|
||||||
|
GetCommands::VolumeMultiplier => Request::get_volume_multiplier(),
|
||||||
GetCommands::Position { id } => Request::get_position(id),
|
GetCommands::Position { id } => Request::get_position(id),
|
||||||
GetCommands::Duration { id } => Request::get_duration(id),
|
GetCommands::Duration { id } => Request::get_duration(id),
|
||||||
GetCommands::State => Request::get_state(),
|
GetCommands::State => Request::get_state(),
|
||||||
@@ -179,11 +189,13 @@ async fn main() -> Result<()> {
|
|||||||
GetCommands::Input => Request::get_input(),
|
GetCommands::Input => Request::get_input(),
|
||||||
GetCommands::Inputs => Request::get_inputs(),
|
GetCommands::Inputs => Request::get_inputs(),
|
||||||
GetCommands::DaemonVersion => Request::get_daemon_version(),
|
GetCommands::DaemonVersion => Request::get_daemon_version(),
|
||||||
|
GetCommands::DaemonConfig => Request::get_daemon_config(),
|
||||||
GetCommands::FullState => Request::get_full_state(),
|
GetCommands::FullState => Request::get_full_state(),
|
||||||
GetCommands::Hotkeys => Request::get_hotkeys(),
|
GetCommands::Hotkeys => Request::get_hotkeys(),
|
||||||
},
|
},
|
||||||
Commands::Set { parameter } => match parameter {
|
Commands::Set { parameter } => match parameter {
|
||||||
SetCommands::Volume { volume, id } => Request::set_volume(volume, id),
|
SetCommands::Volume { volume, id } => Request::set_volume(volume, id),
|
||||||
|
SetCommands::VolumeMultiplier { volume } => Request::set_volume_multiplier(volume),
|
||||||
SetCommands::Position { position, id } => Request::seek(position, id),
|
SetCommands::Position { position, id } => Request::seek(position, id),
|
||||||
SetCommands::Input { name } => Request::set_input(&name),
|
SetCommands::Input { name } => Request::set_input(&name),
|
||||||
SetCommands::Loop { enabled, id } => Request::set_loop(&enabled, id),
|
SetCommands::Loop { enabled, id } => Request::set_loop(&enabled, id),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ anyhow.workspace = true
|
|||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
dirs.workspace = true
|
||||||
|
|
||||||
egui.workspace = true
|
egui.workspace = true
|
||||||
eframe.workspace = true
|
eframe.workspace = true
|
||||||
|
|||||||
@@ -217,6 +217,28 @@ kz = "Әрқашан дыбыс деңгейін есте сақтау"
|
|||||||
he = "זכור תמיד עוצמת קול"
|
he = "זכור תמיד עוצמת קול"
|
||||||
pt-BR = "Lembrar volume"
|
pt-BR = "Lembrar volume"
|
||||||
|
|
||||||
|
[gui.settings.remember_volume_multiplier]
|
||||||
|
en = "Always remember volume multiplier"
|
||||||
|
ru = "Всегда запоминать множитель громкости"
|
||||||
|
es = "Recordar siempre el multiplicador de volumen"
|
||||||
|
fr = "Toujours se souvenir du multiplicateur de volume"
|
||||||
|
zh = "始终记住音量乘数"
|
||||||
|
ar = "تذكر ضرب الصوت دائمًا"
|
||||||
|
kz = "Әрқашан дыбыс деңгейін есте сақтау"
|
||||||
|
he = "זכור תמיד עוצמת קול"
|
||||||
|
pt-BR = "Lembrar volume"
|
||||||
|
|
||||||
|
[gui.settings.volume_multiplier]
|
||||||
|
en = "Volume multiplier"
|
||||||
|
ru = "Множитель громкости"
|
||||||
|
es = "Multiplicador de volumen"
|
||||||
|
fr = "Multiplicateur de volume"
|
||||||
|
zh = "音量乘数"
|
||||||
|
ar = "ضرب الصوت"
|
||||||
|
kz = "Дыбыс деңгейі"
|
||||||
|
he = "עוצמת קול"
|
||||||
|
pt-BR = "Volume"
|
||||||
|
|
||||||
[gui.settings.remember_mic]
|
[gui.settings.remember_mic]
|
||||||
en = "Always remember microphone"
|
en = "Always remember microphone"
|
||||||
ru = "Всегда запоминать микрофон"
|
ru = "Всегда запоминать микрофон"
|
||||||
@@ -331,6 +353,17 @@ kz = "Ыстық пернелерді іздеу..."
|
|||||||
he = "חפש מקשי קיצור..."
|
he = "חפש מקשי קיצור..."
|
||||||
pt-BR = "Buscar atalhos..."
|
pt-BR = "Buscar atalhos..."
|
||||||
|
|
||||||
|
[gui.search_truncated_warning]
|
||||||
|
en = "... and %{count} more results"
|
||||||
|
ru = "... и ещё %{count} результатов"
|
||||||
|
es = "... y %{count} resultados más"
|
||||||
|
fr = "... et %{count} résultats de plus"
|
||||||
|
zh = "... 还有 %{count} 个结果"
|
||||||
|
ar = "... و %{count} نتائج أخرى"
|
||||||
|
kz = "... және тағы %{count} нәтиже"
|
||||||
|
he = "... ועוד %{count} תוצאות"
|
||||||
|
pt-BR = "... e mais %{count} resultados"
|
||||||
|
|
||||||
[gui.hotkeys.add_command_select]
|
[gui.hotkeys.add_command_select]
|
||||||
en = "Add Command"
|
en = "Add Command"
|
||||||
ru = "Добавить команду"
|
ru = "Добавить команду"
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(&mut self, ctx: &Context) {
|
pub fn handle_input(&mut self, ctx: &Context) {
|
||||||
let _modifiers = self.modifiers(ctx);
|
let modifiers = self.modifiers(ctx);
|
||||||
let search_focused = {
|
let search_focused = {
|
||||||
if let Some(focused_id) = self.get_focused(ctx)
|
if let Some(focused_id) = self.get_focused(ctx)
|
||||||
&& let Some(search_id) = self.app_state.search_field_id
|
&& let Some(search_id) = self.app_state.search_field_id
|
||||||
@@ -185,14 +185,15 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Focus search field
|
// Focus search field
|
||||||
if self.key_pressed(ctx, Key::Slash) {
|
if modifiers.ctrl && self.key_pressed(ctx, Key::F) {
|
||||||
if search_focused {
|
self.app_state.force_focus_search = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfocus search field
|
||||||
|
if search_focused && self.key_pressed(ctx, Key::Escape) {
|
||||||
ctx.memory_mut(|m| {
|
ctx.memory_mut(|m| {
|
||||||
m.request_focus(Id::NULL);
|
m.request_focus(Id::NULL);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
self.app_state.force_focus_search = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for hotkey chord triggers
|
// Check for hotkey chord triggers
|
||||||
|
|||||||
+162
-26
@@ -9,22 +9,24 @@ use itertools::Itertools;
|
|||||||
use pwsp_lib::{
|
use pwsp_lib::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::PlayerState,
|
audio_player::PlayerState,
|
||||||
config::GuiConfig,
|
config::{GuiConfig, HotkeyConfig},
|
||||||
config::HotkeyConfig,
|
|
||||||
gui::{AppState, AudioPlayerState},
|
gui::{AppState, AudioPlayerState},
|
||||||
socket::Request,
|
socket::Request,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::gui::{
|
||||||
daemon::get_daemon_config,
|
get_daemon_config, get_gui_config, make_request_async, make_request_sync,
|
||||||
gui::{get_gui_config, make_request_async, make_request_sync, start_app_state_thread},
|
start_app_state_thread, update_daemon_config,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
fs,
|
fs,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
use system_fonts::{FontStyle, FoundFontSource, find_for_locale};
|
use system_fonts::{FontStyle, FoundFontSource, find_for_locale};
|
||||||
|
|
||||||
@@ -32,6 +34,16 @@ const SUPPORTED_EXTENSIONS: [&str; 13] = [
|
|||||||
"mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", "avi", "opus",
|
"mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", "avi", "opus",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
fn get_cache_file_for(path: &Path) -> PathBuf {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
path.hash(&mut hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
dirs::cache_dir()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/tmp"))
|
||||||
|
.join("pwsp")
|
||||||
|
.join(format!("{:x}.json", hash))
|
||||||
|
}
|
||||||
|
|
||||||
struct SoundpadGui {
|
struct SoundpadGui {
|
||||||
pub app_state: AppState,
|
pub app_state: AppState,
|
||||||
pub config: GuiConfig,
|
pub config: GuiConfig,
|
||||||
@@ -107,20 +119,120 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_dir(&mut self, path: &PathBuf) {
|
pub fn open_dir(&mut self, path: &PathBuf, ctx: Option<Context>, force_rescan: bool) {
|
||||||
self.app_state.current_dir = Some(path.clone());
|
self.app_state.current_dir = Some(path.clone());
|
||||||
match path.read_dir() {
|
|
||||||
|
if !self.app_state.dir_cache.contains_key(path) || force_rescan {
|
||||||
|
match fs::read_dir(path) {
|
||||||
Ok(read_dir) => {
|
Ok(read_dir) => {
|
||||||
self.app_state.listed_files = read_dir
|
let files = read_dir
|
||||||
.filter_map(|res| res.ok())
|
.filter_map(|res| res.ok())
|
||||||
.map(|entry| entry.path())
|
.map(|entry| entry.path())
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
self.app_state.listed_files = files.iter().cloned().collect();
|
||||||
|
self.app_state.dir_cache.insert(path.clone(), files);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to read directory {:?}: {}", path, e);
|
eprintln!("Failed to read directory {:?}: {}", path, e);
|
||||||
self.app_state.listed_files.clear();
|
self.app_state.listed_files.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.app_state.listed_files = self
|
||||||
|
.app_state
|
||||||
|
.dir_cache
|
||||||
|
.get(path)
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_scanning = self.app_state.scanning_dirs.lock().unwrap().contains(path);
|
||||||
|
let is_cached = self.app_state.recursive_files_cache.contains_key(path);
|
||||||
|
let has_scanned_this_session = self.app_state.scanned_this_session.contains(path);
|
||||||
|
|
||||||
|
let needs_scan = force_rescan || !has_scanned_this_session || !is_cached;
|
||||||
|
|
||||||
|
if !is_scanning && needs_scan {
|
||||||
|
self.app_state
|
||||||
|
.scanning_dirs
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(path.clone());
|
||||||
|
self.app_state.scanned_this_session.insert(path.clone());
|
||||||
|
|
||||||
|
let finished_scans = self.app_state.finished_scans.clone();
|
||||||
|
let scanning_dirs = self.app_state.scanning_dirs.clone();
|
||||||
|
let path_clone = path.clone();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let cache_file = get_cache_file_for(&path_clone);
|
||||||
|
|
||||||
|
// 1. Try to load from disk cache if we don't have it in memory yet
|
||||||
|
if !is_cached
|
||||||
|
&& let Ok(data) = fs::read_to_string(&cache_file)
|
||||||
|
&& let Ok((all_files, dir_updates)) = serde_json::from_str::<(
|
||||||
|
Vec<PathBuf>,
|
||||||
|
HashMap<PathBuf, Vec<PathBuf>>,
|
||||||
|
)>(&data)
|
||||||
|
{
|
||||||
|
finished_scans.lock().unwrap().push((
|
||||||
|
path_clone.clone(),
|
||||||
|
all_files,
|
||||||
|
dir_updates,
|
||||||
|
));
|
||||||
|
if let Some(ctx) = ctx.as_ref() {
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Scan recursively
|
||||||
|
let mut all_files = Vec::new();
|
||||||
|
let mut dir_updates = HashMap::new();
|
||||||
|
let mut dirs_to_visit = vec![path_clone.clone()];
|
||||||
|
|
||||||
|
while let Some(dir) = dirs_to_visit.pop() {
|
||||||
|
if let Ok(entries) = fs::read_dir(&dir) {
|
||||||
|
let mut children = Vec::new();
|
||||||
|
for entry in entries.filter_map(|e| e.ok()) {
|
||||||
|
let p = entry.path();
|
||||||
|
if p.is_dir() {
|
||||||
|
dirs_to_visit.push(p.clone());
|
||||||
|
children.push(p);
|
||||||
|
} else if crate::gui::SUPPORTED_EXTENSIONS.contains(
|
||||||
|
&p.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
) {
|
||||||
|
all_files.push(p.clone());
|
||||||
|
children.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir_updates.insert(dir, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Save to disk cache
|
||||||
|
if let Some(parent) = cache_file.parent() {
|
||||||
|
fs::create_dir_all(parent).ok();
|
||||||
|
}
|
||||||
|
if let Ok(json) = serde_json::to_string(&(all_files.clone(), dir_updates.clone())) {
|
||||||
|
fs::write(&cache_file, json).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Send to UI
|
||||||
|
finished_scans
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push((path_clone.clone(), all_files, dir_updates));
|
||||||
|
scanning_dirs.lock().unwrap().remove(&path_clone);
|
||||||
|
if let Some(ctx) = ctx {
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_file(&mut self, path: &Path, concurrent: bool) {
|
pub fn play_file(&mut self, path: &Path, concurrent: bool) {
|
||||||
@@ -130,10 +242,11 @@ impl SoundpadGui {
|
|||||||
pub fn set_input(&mut self, name: String) {
|
pub fn set_input(&mut self, name: String) {
|
||||||
make_request_async(Request::set_input(&name));
|
make_request_async(Request::set_input(&name));
|
||||||
|
|
||||||
if self.config.save_input {
|
if self.config.save_input
|
||||||
let mut daemon_config = get_daemon_config();
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
daemon_config.default_input_name = Some(name);
|
daemon_config.default_input_name = Some(name);
|
||||||
daemon_config.save_to_file().ok();
|
update_daemon_config(&daemon_config).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +270,14 @@ impl SoundpadGui {
|
|||||||
make_request_async(Request::play_hotkey(slot));
|
make_request_async(Request::play_hotkey(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_filtered_files(&self) -> Vec<PathBuf> {
|
pub fn get_filtered_files(
|
||||||
|
&self,
|
||||||
|
matching_dirs: &HashSet<PathBuf>,
|
||||||
|
matching_files: &HashSet<PathBuf>,
|
||||||
|
) -> Vec<PathBuf> {
|
||||||
|
let search_query = self.app_state.search_query.to_lowercase();
|
||||||
|
let search_query = search_query.trim();
|
||||||
|
|
||||||
let mut files: Vec<PathBuf> = self.app_state.listed_files.iter().cloned().collect();
|
let mut files: Vec<PathBuf> = self.app_state.listed_files.iter().cloned().collect();
|
||||||
let sort_order = self
|
let sort_order = self
|
||||||
.app_state
|
.app_state
|
||||||
@@ -178,29 +298,42 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let search_query = self.app_state.search_query.to_lowercase();
|
let is_cached = self
|
||||||
let search_query = search_query.trim();
|
.app_state
|
||||||
|
.current_dir
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|d| self.app_state.recursive_files_cache.contains_key(d));
|
||||||
|
|
||||||
|
if !search_query.is_empty() && is_cached {
|
||||||
|
files
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| {
|
||||||
|
if p.is_dir() {
|
||||||
|
matching_dirs.contains(p)
|
||||||
|
} else {
|
||||||
|
matching_files.contains(p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
files
|
files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|entry_path| {
|
.filter(|entry_path| {
|
||||||
if entry_path.is_dir() {
|
if !entry_path.is_dir()
|
||||||
return true;
|
&& !SUPPORTED_EXTENSIONS.contains(
|
||||||
}
|
|
||||||
|
|
||||||
if !SUPPORTED_EXTENSIONS.contains(
|
|
||||||
&entry_path
|
&entry_path
|
||||||
.extension()
|
.extension()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !search_query.is_empty() {
|
if !search_query.is_empty() {
|
||||||
let file_name = entry_path.file_name().unwrap_or_default().to_string_lossy();
|
let file_name =
|
||||||
|
entry_path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
if !file_name.to_lowercase().contains(search_query) {
|
if !file_name.to_lowercase().contains(search_query) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -210,6 +343,7 @@ impl SoundpadGui {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_font(font_name: &str, font_bytes: &[u8], fonts: &mut FontDefinitions) -> Result<()> {
|
fn add_font(font_name: &str, font_bytes: &[u8], fonts: &mut FontDefinitions) -> Result<()> {
|
||||||
@@ -325,14 +459,16 @@ mod tests {
|
|||||||
// On the real OS filesystem, these paths don't exist, so they are treated as files.
|
// On the real OS filesystem, these paths don't exist, so they are treated as files.
|
||||||
// Unsupported extensions (like .txt) will be filtered out.
|
// Unsupported extensions (like .txt) will be filtered out.
|
||||||
// So we expect only file_b and file_c, sorted alphabetically.
|
// So we expect only file_b and file_c, sorted alphabetically.
|
||||||
let filtered = gui.get_filtered_files();
|
let empty_dirs = HashSet::new();
|
||||||
|
let empty_files = HashSet::new();
|
||||||
|
let filtered = gui.get_filtered_files(&empty_dirs, &empty_files);
|
||||||
assert_eq!(filtered.len(), 2);
|
assert_eq!(filtered.len(), 2);
|
||||||
assert_eq!(filtered[0], file_b);
|
assert_eq!(filtered[0], file_b);
|
||||||
assert_eq!(filtered[1], file_c);
|
assert_eq!(filtered[1], file_c);
|
||||||
|
|
||||||
// Test search query
|
// Test search query
|
||||||
gui.app_state.search_query = "c_fi".to_string();
|
gui.app_state.search_query = "c_fi".to_string();
|
||||||
let filtered_search = gui.get_filtered_files();
|
let filtered_search = gui.get_filtered_files(&empty_dirs, &empty_files);
|
||||||
assert_eq!(filtered_search.len(), 1);
|
assert_eq!(filtered_search.len(), 1);
|
||||||
assert_eq!(filtered_search[0], file_c);
|
assert_eq!(filtered_search[0], file_c);
|
||||||
|
|
||||||
@@ -345,7 +481,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
gui.app_state.search_query = String::new();
|
gui.app_state.search_query = String::new();
|
||||||
let filtered_desc = gui.get_filtered_files();
|
let filtered_desc = gui.get_filtered_files(&empty_dirs, &empty_files);
|
||||||
assert_eq!(filtered_desc.len(), 2);
|
assert_eq!(filtered_desc.len(), 2);
|
||||||
assert_eq!(filtered_desc[0], file_c);
|
assert_eq!(filtered_desc[0], file_c);
|
||||||
assert_eq!(filtered_desc[1], file_b);
|
assert_eq!(filtered_desc[1], file_b);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use eframe::{App, Frame as EFrame};
|
|||||||
use egui::{CentralPanel, Context, ThemePreference};
|
use egui::{CentralPanel, Context, ThemePreference};
|
||||||
use pwsp_lib::{
|
use pwsp_lib::{
|
||||||
types::{config::PreferredTheme, socket::Request},
|
types::{config::PreferredTheme, socket::Request},
|
||||||
utils::{daemon::get_daemon_config, gui::make_request_async},
|
utils::gui::{get_daemon_config, make_request_async, update_daemon_config},
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -85,10 +85,29 @@ impl App for SoundpadGui {
|
|||||||
self.app_state.ignore_volume_update_until =
|
self.app_state.ignore_volume_update_until =
|
||||||
Some(Instant::now() + Duration::from_millis(300));
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
|
||||||
if self.config.save_volume {
|
if self.config.save_volume
|
||||||
let mut daemon_config = get_daemon_config();
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
daemon_config.default_volume = Some(self.app_state.volume_slider_value);
|
daemon_config.default_volume = Some(self.app_state.volume_slider_value);
|
||||||
daemon_config.save_to_file().ok();
|
update_daemon_config(&daemon_config).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.app_state.volume_multiplier_dragged {
|
||||||
|
make_request_async(Request::set_volume_multiplier(
|
||||||
|
self.app_state.volume_multiplier_slider_value,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.app_state.volume_multiplier_dragged = false;
|
||||||
|
self.app_state.ignore_volume_multiplier_update_until =
|
||||||
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
|
||||||
|
if self.config.save_volume_multiplier
|
||||||
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
|
daemon_config.default_volume_multiplier =
|
||||||
|
Some(self.app_state.volume_multiplier_slider_value);
|
||||||
|
update_daemon_config(&daemon_config).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+183
-17
@@ -10,7 +10,7 @@ use pwsp_lib::types::{
|
|||||||
gui::{AppState, AudioPlayerState},
|
gui::{AppState, AudioPlayerState},
|
||||||
};
|
};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::{cmp::Ordering, path::Path, path::PathBuf};
|
use std::{cmp::Ordering, collections::HashSet, path::Path, path::PathBuf};
|
||||||
|
|
||||||
pub(crate) enum FileAction {
|
pub(crate) enum FileAction {
|
||||||
Play(PathBuf, bool),
|
Play(PathBuf, bool),
|
||||||
@@ -195,7 +195,7 @@ impl SoundpadGui {
|
|||||||
.sort_order = order;
|
.sort_order = order;
|
||||||
self.config.save_to_file().ok();
|
self.config.save_to_file().ok();
|
||||||
self.app_state.dir_cache.remove(path);
|
self.app_state.dir_cache.remove(path);
|
||||||
self.open_dir(path);
|
self.open_dir(path, Some(ui.ctx().clone()), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -203,7 +203,7 @@ impl SoundpadGui {
|
|||||||
self.app_state.dirs = dirs;
|
self.app_state.dirs = dirs;
|
||||||
|
|
||||||
if let Some(path) = dir_to_open {
|
if let Some(path) = dir_to_open {
|
||||||
self.open_dir(&path);
|
self.open_dir(&path, Some(ui.ctx().clone()), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -227,12 +227,41 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
fn draw_files_search_field(&mut self, ui: &mut Ui) {
|
fn draw_files_search_field(&mut self, ui: &mut Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
let is_scanning = self
|
||||||
|
.app_state
|
||||||
|
.current_dir
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|d| self.app_state.scanning_dirs.lock().unwrap().contains(d));
|
||||||
|
let spinner_width = if is_scanning { 20.0 } else { 0.0 };
|
||||||
|
|
||||||
|
let refresh_button = Button::new(ICON_REFRESH).frame(false);
|
||||||
|
let refresh_width = 24.0;
|
||||||
|
|
||||||
let search_field_response = ui.add_sized(
|
let search_field_response = ui.add_sized(
|
||||||
[ui.available_width(), 22.0],
|
[
|
||||||
|
f32::max(
|
||||||
|
0.0,
|
||||||
|
ui.available_width() - spinner_width - refresh_width - 8.0,
|
||||||
|
),
|
||||||
|
22.0,
|
||||||
|
],
|
||||||
TextEdit::singleline(&mut self.app_state.search_query)
|
TextEdit::singleline(&mut self.app_state.search_query)
|
||||||
.hint_text(t!("gui.search_placeholder")),
|
.hint_text(t!("gui.search_placeholder")),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if is_scanning {
|
||||||
|
ui.spinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
let refresh_response = ui
|
||||||
|
.add_sized([refresh_width, 22.0], refresh_button)
|
||||||
|
.on_hover_text("Refresh");
|
||||||
|
if refresh_response.clicked()
|
||||||
|
&& let Some(dir) = self.app_state.current_dir.clone()
|
||||||
|
{
|
||||||
|
self.open_dir(&dir, Some(ui.ctx().clone()), true);
|
||||||
|
}
|
||||||
|
|
||||||
if self.app_state.force_focus_search {
|
if self.app_state.force_focus_search {
|
||||||
search_field_response.request_focus();
|
search_field_response.request_focus();
|
||||||
self.app_state.force_focus_search = false;
|
self.app_state.force_focus_search = false;
|
||||||
@@ -247,9 +276,54 @@ impl SoundpadGui {
|
|||||||
ui.set_min_width(area_size.x);
|
ui.set_min_width(area_size.x);
|
||||||
ui.set_min_height(area_size.y);
|
ui.set_min_height(area_size.y);
|
||||||
|
|
||||||
|
let finished = {
|
||||||
|
let mut scans = self.app_state.finished_scans.lock().unwrap();
|
||||||
|
std::mem::take(&mut *scans)
|
||||||
|
};
|
||||||
|
for (top_dir, all_files, dir_updates) in finished {
|
||||||
|
self.app_state
|
||||||
|
.recursive_files_cache
|
||||||
|
.insert(top_dir, all_files);
|
||||||
|
self.app_state.dir_cache.extend(dir_updates);
|
||||||
|
}
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
let mut actions = Vec::new();
|
let mut actions = Vec::new();
|
||||||
let files = self.get_filtered_files();
|
|
||||||
|
let search_query = self.app_state.search_query.to_lowercase();
|
||||||
|
let search_query = search_query.trim();
|
||||||
|
|
||||||
|
let mut matching_dirs = std::collections::HashSet::new();
|
||||||
|
let mut matching_files = std::collections::HashSet::new();
|
||||||
|
|
||||||
|
if !search_query.is_empty()
|
||||||
|
&& let Some(current_dir) = &self.app_state.current_dir
|
||||||
|
&& let Some(all_files) = self.app_state.recursive_files_cache.get(current_dir)
|
||||||
|
{
|
||||||
|
for file in all_files {
|
||||||
|
if let Ok(rel_path) = file.strip_prefix(current_dir) {
|
||||||
|
let rel_path_str =
|
||||||
|
rel_path.to_string_lossy().to_lowercase().replace("\\", "/");
|
||||||
|
if rel_path_str.contains(search_query) {
|
||||||
|
matching_files.insert(file.clone());
|
||||||
|
let mut p = file.parent();
|
||||||
|
while let Some(parent) = p {
|
||||||
|
if parent == current_dir {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
matching_dirs.insert(parent.to_path_buf());
|
||||||
|
p = parent.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_results = 300;
|
||||||
|
let is_truncated = !search_query.is_empty() && matching_files.len() > max_results;
|
||||||
|
|
||||||
|
let files = self.get_filtered_files(&matching_dirs, &matching_files);
|
||||||
|
let mut rendered_count = 0;
|
||||||
for entry_path in files {
|
for entry_path in files {
|
||||||
Self::draw_tree_node(
|
Self::draw_tree_node(
|
||||||
ui,
|
ui,
|
||||||
@@ -258,6 +332,22 @@ impl SoundpadGui {
|
|||||||
&mut self.app_state,
|
&mut self.app_state,
|
||||||
&self.audio_player_state,
|
&self.audio_player_state,
|
||||||
&mut actions,
|
&mut actions,
|
||||||
|
&matching_dirs,
|
||||||
|
&matching_files,
|
||||||
|
search_query,
|
||||||
|
&mut rendered_count,
|
||||||
|
max_results,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_truncated {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
let text = t!(
|
||||||
|
"gui.search_truncated_warning",
|
||||||
|
count = matching_files.len() - max_results
|
||||||
|
);
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(text).color(egui::Color32::from_rgb(200, 150, 50)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +376,7 @@ impl SoundpadGui {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_tree_node_dir(
|
fn draw_tree_node_dir(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
@@ -293,15 +384,39 @@ impl SoundpadGui {
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
audio_player_state: &AudioPlayerState,
|
audio_player_state: &AudioPlayerState,
|
||||||
actions: &mut Vec<FileAction>,
|
actions: &mut Vec<FileAction>,
|
||||||
|
matching_dirs: &std::collections::HashSet<PathBuf>,
|
||||||
|
matching_files: &std::collections::HashSet<PathBuf>,
|
||||||
|
search_query: &str,
|
||||||
|
rendered_count: &mut usize,
|
||||||
|
max_results: usize,
|
||||||
) {
|
) {
|
||||||
|
if *rendered_count >= max_results && !search_query.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let dir_name = path
|
let dir_name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
CollapsingHeader::new(dir_name)
|
|
||||||
.id_salt(&path)
|
let id_salt = if search_query.is_empty() {
|
||||||
.show(ui, |ui| {
|
format!("normal_{:?}", path)
|
||||||
|
} else {
|
||||||
|
format!("search_{:?}", path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut header = CollapsingHeader::new(dir_name).id_salt(id_salt);
|
||||||
|
|
||||||
|
let is_cached = app_state
|
||||||
|
.current_dir
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|d| app_state.recursive_files_cache.contains_key(d));
|
||||||
|
if !search_query.is_empty() && is_cached {
|
||||||
|
header = header.default_open(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.show(ui, |ui| {
|
||||||
let children = if let Some(cached) = app_state.dir_cache.get(&path) {
|
let children = if let Some(cached) = app_state.dir_cache.get(&path) {
|
||||||
cached.clone()
|
cached.clone()
|
||||||
} else {
|
} else {
|
||||||
@@ -339,21 +454,40 @@ impl SoundpadGui {
|
|||||||
read
|
read
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_query = app_state.search_query.to_lowercase();
|
|
||||||
let search_query = search_query.trim();
|
|
||||||
|
|
||||||
for child in children {
|
for child in children {
|
||||||
if !child.is_dir() && !search_query.is_empty() {
|
if *rendered_count >= max_results && !search_query.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !search_query.is_empty() && is_cached {
|
||||||
|
if child.is_dir() && !matching_dirs.contains(&child) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !child.is_dir() && !matching_files.contains(&child) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if !search_query.is_empty() && !child.is_dir() {
|
||||||
let file_name = child
|
let file_name = child
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_lowercase();
|
||||||
if !file_name.to_lowercase().contains(search_query) {
|
if !file_name.contains(search_query) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::draw_tree_node(ui, child, config, app_state, audio_player_state, actions);
|
Self::draw_tree_node(
|
||||||
|
ui,
|
||||||
|
child,
|
||||||
|
config,
|
||||||
|
app_state,
|
||||||
|
audio_player_state,
|
||||||
|
actions,
|
||||||
|
matching_dirs,
|
||||||
|
matching_files,
|
||||||
|
search_query,
|
||||||
|
rendered_count,
|
||||||
|
max_results,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -364,7 +498,10 @@ impl SoundpadGui {
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
audio_player_state: &AudioPlayerState,
|
audio_player_state: &AudioPlayerState,
|
||||||
actions: &mut Vec<FileAction>,
|
actions: &mut Vec<FileAction>,
|
||||||
|
rendered_count: &mut usize,
|
||||||
) {
|
) {
|
||||||
|
*rendered_count += 1;
|
||||||
|
|
||||||
let file_name = path
|
let file_name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -500,6 +637,7 @@ impl SoundpadGui {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_tree_node(
|
fn draw_tree_node(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
@@ -507,11 +645,39 @@ impl SoundpadGui {
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
audio_player_state: &AudioPlayerState,
|
audio_player_state: &AudioPlayerState,
|
||||||
actions: &mut Vec<FileAction>,
|
actions: &mut Vec<FileAction>,
|
||||||
|
matching_dirs: &HashSet<PathBuf>,
|
||||||
|
matching_files: &HashSet<PathBuf>,
|
||||||
|
search_query: &str,
|
||||||
|
rendered_count: &mut usize,
|
||||||
|
max_results: usize,
|
||||||
) {
|
) {
|
||||||
|
if *rendered_count >= max_results && !search_query.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
Self::draw_tree_node_dir(ui, path, config, app_state, audio_player_state, actions);
|
Self::draw_tree_node_dir(
|
||||||
|
ui,
|
||||||
|
path,
|
||||||
|
config,
|
||||||
|
app_state,
|
||||||
|
audio_player_state,
|
||||||
|
actions,
|
||||||
|
matching_dirs,
|
||||||
|
matching_files,
|
||||||
|
search_query,
|
||||||
|
rendered_count,
|
||||||
|
max_results,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Self::draw_tree_node_file(ui, path, app_state, audio_player_state, actions);
|
Self::draw_tree_node_file(
|
||||||
|
ui,
|
||||||
|
path,
|
||||||
|
app_state,
|
||||||
|
audio_player_state,
|
||||||
|
actions,
|
||||||
|
rendered_count,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::gui::SoundpadGui;
|
use crate::gui::SoundpadGui;
|
||||||
use egui::{Align, Button, Color32, ComboBox, Layout, RichText, Ui};
|
use egui::{Align, Button, Color32, ComboBox, Layout, RichText, Slider, Ui};
|
||||||
use egui_material_icons::icons::ICON_ARROW_BACK;
|
use egui_material_icons::icons::ICON_ARROW_BACK;
|
||||||
use pwsp_lib::types::config::PreferredTheme;
|
use pwsp_lib::types::config::PreferredTheme;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
@@ -34,6 +34,10 @@ impl SoundpadGui {
|
|||||||
&mut self.config.save_volume,
|
&mut self.config.save_volume,
|
||||||
t!("gui.settings.remember_volume"),
|
t!("gui.settings.remember_volume"),
|
||||||
);
|
);
|
||||||
|
let save_volume_multiplier_response = ui.checkbox(
|
||||||
|
&mut self.config.save_volume_multiplier,
|
||||||
|
t!("gui.settings.remember_volume_multiplier"),
|
||||||
|
);
|
||||||
let save_input_response =
|
let save_input_response =
|
||||||
ui.checkbox(&mut self.config.save_input, t!("gui.settings.remember_mic"));
|
ui.checkbox(&mut self.config.save_input, t!("gui.settings.remember_mic"));
|
||||||
let save_scale_response = ui.checkbox(
|
let save_scale_response = ui.checkbox(
|
||||||
@@ -46,6 +50,7 @@ impl SoundpadGui {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if save_volume_response.changed()
|
if save_volume_response.changed()
|
||||||
|
|| save_volume_multiplier_response.changed()
|
||||||
|| save_input_response.changed()
|
|| save_input_response.changed()
|
||||||
|| save_scale_response.changed()
|
|| save_scale_response.changed()
|
||||||
|| pause_on_exit_response.changed()
|
|| pause_on_exit_response.changed()
|
||||||
@@ -88,6 +93,39 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// ----------- Sliders ------------
|
||||||
|
// Volume multiplier
|
||||||
|
let should_update_multiplier = !self.app_state.volume_multiplier_dragged
|
||||||
|
&& self
|
||||||
|
.app_state
|
||||||
|
.ignore_volume_multiplier_update_until
|
||||||
|
.map(|t| std::time::Instant::now() > t)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if should_update_multiplier {
|
||||||
|
self.app_state.volume_multiplier_slider_value =
|
||||||
|
self.audio_player_state.volume_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let slider = Slider::new(
|
||||||
|
&mut self.app_state.volume_multiplier_slider_value,
|
||||||
|
0.01..=3.0,
|
||||||
|
);
|
||||||
|
let response = ui.add(slider);
|
||||||
|
ui.label(t!("gui.settings.volume_multiplier"));
|
||||||
|
|
||||||
|
if response.changed() {
|
||||||
|
// This condition is required to avoid spamming requests while dragging, but to allow changing the value via TextEdit
|
||||||
|
if !response.dragged() || (response.dragged() && response.drag_stopped()) {
|
||||||
|
self.app_state.volume_multiplier_dragged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// --------------------------------
|
||||||
|
|
||||||
ui.with_layout(Layout::bottom_up(Align::Min), |ui| {
|
ui.with_layout(Layout::bottom_up(Align::Min), |ui| {
|
||||||
ui.label(t!(
|
ui.label(t!(
|
||||||
"gui.settings.version",
|
"gui.settings.version",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::pipewire::DeviceType,
|
types::pipewire::DeviceType,
|
||||||
utils::{
|
utils::{
|
||||||
daemon::get_daemon_config,
|
daemon::with_daemon_config,
|
||||||
pipewire::{PwTerminator, create_link, get_device, link_player_to_virtual_mic},
|
pipewire::{PwTerminator, create_link, get_device, link_player_to_virtual_mic},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -40,6 +40,7 @@ pub struct FullState {
|
|||||||
pub state: PlayerState,
|
pub state: PlayerState,
|
||||||
pub tracks: Vec<TrackInfo>,
|
pub tracks: Vec<TrackInfo>,
|
||||||
pub volume: f32,
|
pub volume: f32,
|
||||||
|
pub volume_multiplier: f32,
|
||||||
pub current_input: String,
|
pub current_input: String,
|
||||||
pub all_inputs: HashMap<String, String>,
|
pub all_inputs: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
@@ -63,12 +64,19 @@ pub struct AudioPlayer {
|
|||||||
pub input_device_name: Option<String>,
|
pub input_device_name: Option<String>,
|
||||||
|
|
||||||
pub volume: f32, // Master volume
|
pub volume: f32, // Master volume
|
||||||
|
pub volume_multiplier: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioPlayer {
|
impl AudioPlayer {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
let daemon_config = get_daemon_config();
|
let (default_input_name, default_volume, default_volume_multiplier) =
|
||||||
let default_volume = daemon_config.default_volume.unwrap_or(1.0);
|
with_daemon_config(|c| {
|
||||||
|
(
|
||||||
|
c.default_input_name.clone(),
|
||||||
|
c.default_volume.unwrap_or(1.0),
|
||||||
|
c.default_volume_multiplier.unwrap_or(1.0),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut audio_player = AudioPlayer {
|
let mut audio_player = AudioPlayer {
|
||||||
stream_handle: None,
|
stream_handle: None,
|
||||||
@@ -77,9 +85,10 @@ impl AudioPlayer {
|
|||||||
|
|
||||||
input_link_sender: None,
|
input_link_sender: None,
|
||||||
player_link_sender: None,
|
player_link_sender: None,
|
||||||
input_device_name: daemon_config.default_input_name.clone(),
|
input_device_name: default_input_name,
|
||||||
|
|
||||||
volume: default_volume,
|
volume: default_volume,
|
||||||
|
volume_multiplier: default_volume_multiplier,
|
||||||
};
|
};
|
||||||
|
|
||||||
if audio_player.input_device_name.is_some() {
|
if audio_player.input_device_name.is_some() {
|
||||||
@@ -262,12 +271,16 @@ impl AudioPlayer {
|
|||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Some(sound) = self.tracks.get_mut(&id) {
|
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||||
sound.volume = volume;
|
sound.volume = volume;
|
||||||
sound.sink.set_volume(self.volume * volume);
|
sound
|
||||||
|
.sink
|
||||||
|
.set_volume(self.volume * sound.volume * self.volume_multiplier);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.volume = volume;
|
self.volume = volume;
|
||||||
for sound in self.tracks.values_mut() {
|
for sound in self.tracks.values_mut() {
|
||||||
sound.sink.set_volume(self.volume * sound.volume);
|
sound
|
||||||
|
.sink
|
||||||
|
.set_volume(self.volume * sound.volume * self.volume_multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,7 +360,7 @@ impl AudioPlayer {
|
|||||||
.ok_or_else(|| anyhow::anyhow!("stream_handle is unexpectedly missing"))?
|
.ok_or_else(|| anyhow::anyhow!("stream_handle is unexpectedly missing"))?
|
||||||
.mixer();
|
.mixer();
|
||||||
let sink = Player::connect_new(mixer);
|
let sink = Player::connect_new(mixer);
|
||||||
sink.set_volume(self.volume); // Default volume is 1.0 * master
|
sink.set_volume(self.volume * self.volume_multiplier); // Default volume is 1.0 * master
|
||||||
sink.append(source);
|
sink.append(source);
|
||||||
sink.play();
|
sink.play();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::{FullState, PlayerState},
|
audio_player::{FullState, PlayerState},
|
||||||
config::HotkeyConfig,
|
config::{DaemonConfig, HotkeyConfig},
|
||||||
socket::{Request, Response},
|
socket::{Request, Response},
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
commands::parse_command,
|
commands::parse_command,
|
||||||
daemon::get_audio_player,
|
daemon::{get_audio_player, with_daemon_config},
|
||||||
pipewire::{get_all_devices, get_device},
|
pipewire::{get_all_devices, get_device},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -46,11 +46,17 @@ pub struct GetVolumeCommand {
|
|||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GetVolumeMultiplierCommand {}
|
||||||
|
|
||||||
pub struct SetVolumeCommand {
|
pub struct SetVolumeCommand {
|
||||||
pub volume: Option<f32>,
|
pub volume: Option<f32>,
|
||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SetVolumeMultiplierCommand {
|
||||||
|
pub volume_multiplier: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GetPositionCommand {
|
pub struct GetPositionCommand {
|
||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
@@ -127,6 +133,14 @@ pub struct ClearHotkeyKeyCommand {
|
|||||||
pub slot: Option<String>,
|
pub slot: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GetDaemonConfigCommand {}
|
||||||
|
|
||||||
|
pub struct SaveDaemonConfigCommand {}
|
||||||
|
|
||||||
|
pub struct UpdateDaemonConfigCommand {
|
||||||
|
pub new_config: DaemonConfig,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for PingCommand {
|
impl Executable for PingCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -262,6 +276,18 @@ impl Executable for GetVolumeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for GetVolumeMultiplierCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
let audio_player = match get_audio_player().await {
|
||||||
|
Ok(player) => player.lock().await,
|
||||||
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::new(true, audio_player.volume_multiplier.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for SetVolumeCommand {
|
impl Executable for SetVolumeCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -278,6 +304,26 @@ impl Executable for SetVolumeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for SetVolumeMultiplierCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
if let Some(volume_multiplier) = self.volume_multiplier {
|
||||||
|
let mut audio_player = match get_audio_player().await {
|
||||||
|
Ok(player) => player.lock().await,
|
||||||
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
||||||
|
};
|
||||||
|
audio_player.volume_multiplier = volume_multiplier;
|
||||||
|
audio_player.set_volume(volume_multiplier, None); // Reset current volume for all tracks to apply multiplier
|
||||||
|
Response::new(
|
||||||
|
true,
|
||||||
|
format!("Audio volume multiplier was set to {}", volume_multiplier),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Response::new(false, "Invalid volume multiplier value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for GetPositionCommand {
|
impl Executable for GetPositionCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -508,6 +554,7 @@ impl Executable for GetFullStateCommand {
|
|||||||
state: audio_player.get_state(),
|
state: audio_player.get_state(),
|
||||||
tracks: audio_player.get_tracks(),
|
tracks: audio_player.get_tracks(),
|
||||||
volume: audio_player.volume,
|
volume: audio_player.volume,
|
||||||
|
volume_multiplier: audio_player.volume_multiplier,
|
||||||
current_input: current_input_nick,
|
current_input: current_input_nick,
|
||||||
all_inputs,
|
all_inputs,
|
||||||
};
|
};
|
||||||
@@ -723,3 +770,36 @@ impl Executable for ClearHotkeyKeyCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for GetDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
let serialized = with_daemon_config(|c| serde_json::to_string(&c));
|
||||||
|
|
||||||
|
match serialized {
|
||||||
|
Ok(s) => Response::new(true, s),
|
||||||
|
Err(err) => Response::new(false, format!("Failed to serialize daemon config: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for SaveDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
match with_daemon_config(|c| c.save_to_file()) {
|
||||||
|
Ok(_) => Response::new(true, "Daemon config saved successfully"),
|
||||||
|
Err(err) => Response::new(false, format!("Failed to save daemon config: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for UpdateDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
with_daemon_config(|c| {
|
||||||
|
c.clone_from(&self.new_config);
|
||||||
|
});
|
||||||
|
|
||||||
|
Response::new(true, "Daemon config updated successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use std::{
|
|||||||
pub struct DaemonConfig {
|
pub struct DaemonConfig {
|
||||||
pub default_input_name: Option<String>,
|
pub default_input_name: Option<String>,
|
||||||
pub default_volume: Option<f32>,
|
pub default_volume: Option<f32>,
|
||||||
|
pub default_volume_multiplier: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonConfig {
|
impl DaemonConfig {
|
||||||
@@ -73,6 +74,7 @@ pub struct GuiConfig {
|
|||||||
pub left_panel_width: f32,
|
pub left_panel_width: f32,
|
||||||
|
|
||||||
pub save_volume: bool,
|
pub save_volume: bool,
|
||||||
|
pub save_volume_multiplier: bool,
|
||||||
pub save_input: bool,
|
pub save_input: bool,
|
||||||
pub save_scale_factor: bool,
|
pub save_scale_factor: bool,
|
||||||
pub pause_on_exit: bool,
|
pub pause_on_exit: bool,
|
||||||
@@ -117,6 +119,7 @@ impl Default for GuiConfig {
|
|||||||
left_panel_width: 280.0,
|
left_panel_width: 280.0,
|
||||||
|
|
||||||
save_volume: false,
|
save_volume: false,
|
||||||
|
save_volume_multiplier: false,
|
||||||
save_input: false,
|
save_input: false,
|
||||||
save_scale_factor: false,
|
save_scale_factor: false,
|
||||||
pause_on_exit: false,
|
pause_on_exit: false,
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ use egui::Id;
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub type ScanResult = (PathBuf, Vec<PathBuf>, HashMap<PathBuf, Vec<PathBuf>>);
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct TrackUiState {
|
pub struct TrackUiState {
|
||||||
pub position_slider_value: f32,
|
pub position_slider_value: f32,
|
||||||
@@ -31,13 +34,16 @@ pub struct AppState {
|
|||||||
|
|
||||||
pub show_settings: bool,
|
pub show_settings: bool,
|
||||||
pub volume_dragged: bool,
|
pub volume_dragged: bool,
|
||||||
|
pub volume_multiplier_dragged: bool,
|
||||||
pub force_focus_search: bool,
|
pub force_focus_search: bool,
|
||||||
|
|
||||||
pub volume_slider_value: f32,
|
pub volume_slider_value: f32,
|
||||||
|
pub volume_multiplier_slider_value: f32,
|
||||||
|
|
||||||
pub search_field_id: Option<Id>,
|
pub search_field_id: Option<Id>,
|
||||||
|
|
||||||
pub ignore_volume_update_until: Option<Instant>,
|
pub ignore_volume_update_until: Option<Instant>,
|
||||||
|
pub ignore_volume_multiplier_update_until: Option<Instant>,
|
||||||
|
|
||||||
pub current_dir: Option<PathBuf>,
|
pub current_dir: Option<PathBuf>,
|
||||||
pub dirs: Vec<PathBuf>,
|
pub dirs: Vec<PathBuf>,
|
||||||
@@ -47,6 +53,11 @@ pub struct AppState {
|
|||||||
pub listed_dirs: HashSet<PathBuf>,
|
pub listed_dirs: HashSet<PathBuf>,
|
||||||
pub dir_cache: HashMap<PathBuf, Vec<PathBuf>>,
|
pub dir_cache: HashMap<PathBuf, Vec<PathBuf>>,
|
||||||
|
|
||||||
|
pub scanning_dirs: Arc<Mutex<HashSet<PathBuf>>>,
|
||||||
|
pub scanned_this_session: HashSet<PathBuf>,
|
||||||
|
pub finished_scans: Arc<Mutex<Vec<ScanResult>>>,
|
||||||
|
pub recursive_files_cache: HashMap<PathBuf, Vec<PathBuf>>,
|
||||||
|
|
||||||
pub show_hotkeys: bool,
|
pub show_hotkeys: bool,
|
||||||
pub hotkey_capture_active: bool,
|
pub hotkey_capture_active: bool,
|
||||||
|
|
||||||
@@ -65,6 +76,7 @@ pub struct AudioPlayerState {
|
|||||||
pub tracks: Vec<TrackInfo>,
|
pub tracks: Vec<TrackInfo>,
|
||||||
|
|
||||||
pub volume: f32, // Master volume
|
pub volume: f32, // Master volume
|
||||||
|
pub volume_multiplier: f32,
|
||||||
|
|
||||||
pub current_input: String,
|
pub current_input: String,
|
||||||
pub all_inputs: HashMap<String, String>,
|
pub all_inputs: HashMap<String, String>,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::config::DaemonConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -94,6 +95,10 @@ impl Request {
|
|||||||
Request::new("get_volume", args)
|
Request::new("get_volume", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_volume_multiplier() -> Self {
|
||||||
|
Request::new("get_volume_multiplier", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_position(id: Option<u32>) -> Self {
|
pub fn get_position(id: Option<u32>) -> Self {
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
let id_str;
|
let id_str;
|
||||||
@@ -138,6 +143,13 @@ impl Request {
|
|||||||
Request::new("set_volume".to_string(), args)
|
Request::new("set_volume".to_string(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_volume_multiplier(volume: f32) -> Self {
|
||||||
|
Request::new(
|
||||||
|
"set_volume_multiplier",
|
||||||
|
vec![("volume_multiplier", &volume.to_string())],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seek(position: f32, id: Option<u32>) -> Self {
|
pub fn seek(position: f32, id: Option<u32>) -> Self {
|
||||||
let mut args = vec![("position".to_string(), position.to_string())];
|
let mut args = vec![("position".to_string(), position.to_string())];
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
@@ -172,6 +184,24 @@ impl Request {
|
|||||||
Request::new("get_daemon_version", vec![])
|
Request::new("get_daemon_version", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_daemon_config() -> Self {
|
||||||
|
Request::new("get_daemon_config", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_daemon_config() -> Self {
|
||||||
|
Request::new("save_daemon_config", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_daemon_config(new_config: &DaemonConfig) -> Self {
|
||||||
|
Request::new(
|
||||||
|
"update_daemon_config",
|
||||||
|
vec![(
|
||||||
|
"new_config",
|
||||||
|
&serde_json::to_string(&new_config).unwrap_or_default(),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_full_state() -> Self {
|
pub fn get_full_state() -> Self {
|
||||||
Request::new("get_full_state", vec![])
|
Request::new("get_full_state", vec![])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::types::{commands::*, socket::Request};
|
use crate::types::{commands::*, config::DaemonConfig, socket::Request};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
||||||
"get_state" => Some(Box::new(GetStateCommand {})),
|
"get_state" => Some(Box::new(GetStateCommand {})),
|
||||||
"get_volume" => Some(Box::new(GetVolumeCommand { id })),
|
"get_volume" => Some(Box::new(GetVolumeCommand { id })),
|
||||||
|
"get_volume_multiplier" => Some(Box::new(GetVolumeMultiplierCommand {})),
|
||||||
"set_volume" => {
|
"set_volume" => {
|
||||||
let volume = request
|
let volume = request
|
||||||
.args
|
.args
|
||||||
@@ -24,6 +25,15 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(SetVolumeCommand { volume, id }))
|
Some(Box::new(SetVolumeCommand { volume, id }))
|
||||||
}
|
}
|
||||||
|
"set_volume_multiplier" => {
|
||||||
|
let volume_multiplier = request
|
||||||
|
.args
|
||||||
|
.get("volume_multiplier")
|
||||||
|
.unwrap_or(&String::new())
|
||||||
|
.parse::<f32>()
|
||||||
|
.ok();
|
||||||
|
Some(Box::new(SetVolumeMultiplierCommand { volume_multiplier }))
|
||||||
|
}
|
||||||
"get_position" => Some(Box::new(GetPositionCommand { id })),
|
"get_position" => Some(Box::new(GetPositionCommand { id })),
|
||||||
"seek" => {
|
"seek" => {
|
||||||
let position = request
|
let position = request
|
||||||
@@ -119,6 +129,17 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
key_chord,
|
key_chord,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
"get_daemon_config" => Some(Box::new(GetDaemonConfigCommand {})),
|
||||||
|
"save_daemon_config" => Some(Box::new(SaveDaemonConfigCommand {})),
|
||||||
|
"update_daemon_config" => {
|
||||||
|
let new_config = request
|
||||||
|
.args
|
||||||
|
.get("new_config")
|
||||||
|
.and_then(|nc| serde_json::from_str::<DaemonConfig>(nc).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Some(Box::new(UpdateDaemonConfigCommand { new_config }))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,36 +7,52 @@ use crate::types::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, error::Error, fs};
|
use std::{
|
||||||
|
env,
|
||||||
|
error::Error,
|
||||||
|
fs,
|
||||||
|
sync::{Arc, Mutex, OnceLock},
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::UnixStream,
|
net::UnixStream,
|
||||||
sync::{Mutex, OnceCell},
|
sync::{Mutex as AsyncMutex, OnceCell},
|
||||||
time::{Duration, sleep},
|
time::{Duration, sleep},
|
||||||
};
|
};
|
||||||
|
|
||||||
static AUDIO_PLAYER: OnceCell<Mutex<AudioPlayer>> = OnceCell::const_new();
|
static AUDIO_PLAYER: OnceCell<AsyncMutex<AudioPlayer>> = OnceCell::const_new();
|
||||||
|
static DAEMON_CONFIG: OnceLock<Arc<Mutex<DaemonConfig>>> = OnceLock::new();
|
||||||
|
|
||||||
pub async fn get_audio_player() -> Result<&'static Mutex<AudioPlayer>, String> {
|
pub async fn get_audio_player() -> Result<&'static AsyncMutex<AudioPlayer>, String> {
|
||||||
AUDIO_PLAYER
|
AUDIO_PLAYER
|
||||||
.get_or_try_init(|| async {
|
.get_or_try_init(|| async {
|
||||||
println!("Initializing audio player");
|
println!("Initializing audio player");
|
||||||
match AudioPlayer::new().await {
|
match AudioPlayer::new().await {
|
||||||
Ok(player) => Ok(Mutex::new(player)),
|
Ok(player) => Ok(AsyncMutex::new(player)),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_daemon_config() -> DaemonConfig {
|
pub fn get_daemon_config() -> &'static Arc<Mutex<DaemonConfig>> {
|
||||||
DaemonConfig::load_from_file().unwrap_or_else(|_| {
|
DAEMON_CONFIG.get_or_init(|| {
|
||||||
|
Arc::new(Mutex::new(DaemonConfig::load_from_file().unwrap_or_else(
|
||||||
|
|_| {
|
||||||
let config = DaemonConfig::default();
|
let config = DaemonConfig::default();
|
||||||
config.save_to_file().ok();
|
config.save_to_file().ok();
|
||||||
config
|
config
|
||||||
|
},
|
||||||
|
)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_daemon_config<R>(f: impl FnOnce(&mut DaemonConfig) -> R) -> R {
|
||||||
|
let config = get_daemon_config().clone();
|
||||||
|
let mut guard = config.lock().unwrap();
|
||||||
|
f(&mut guard)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_current_uid() -> u32 {
|
fn get_current_uid() -> u32 {
|
||||||
rustix::process::geteuid().as_raw()
|
rustix::process::geteuid().as_raw()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::FullState,
|
audio_player::FullState,
|
||||||
config::{GuiConfig, HotkeyConfig},
|
config::{DaemonConfig, GuiConfig, HotkeyConfig},
|
||||||
gui::AudioPlayerState,
|
gui::AudioPlayerState,
|
||||||
socket::{Request, Response},
|
socket::{Request, Response},
|
||||||
},
|
},
|
||||||
@@ -24,6 +24,19 @@ pub fn get_gui_config() -> GuiConfig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_daemon_config(new_config: &DaemonConfig) -> Result<()> {
|
||||||
|
make_request_sync(Request::update_daemon_config(new_config))?;
|
||||||
|
make_request_async(Request::save_daemon_config());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_daemon_config() -> Result<DaemonConfig> {
|
||||||
|
let response = make_request_sync(Request::get_daemon_config())?;
|
||||||
|
let config: DaemonConfig = serde_json::from_str(&response.message)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_request_sync(request: Request) -> Result<Response> {
|
pub fn make_request_sync(request: Request) -> Result<Response> {
|
||||||
tokio::task::block_in_place(|| {
|
tokio::task::block_in_place(|| {
|
||||||
tokio::runtime::Handle::current()
|
tokio::runtime::Handle::current()
|
||||||
@@ -103,6 +116,7 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
|||||||
};
|
};
|
||||||
guard.tracks = full_state.tracks;
|
guard.tracks = full_state.tracks;
|
||||||
guard.volume = full_state.volume;
|
guard.volume = full_state.volume;
|
||||||
|
guard.volume_multiplier = full_state.volume_multiplier;
|
||||||
guard.current_input = full_state
|
guard.current_input = full_state
|
||||||
.current_input
|
.current_input
|
||||||
.split(" - ")
|
.split(" - ")
|
||||||
|
|||||||
Reference in New Issue
Block a user