feat: add volume save/restore on failure/unload

On the first failed unlock attempt, save the current system volume and
mute state then set volume to maximum unmuted; restore on g_module_unload.
A lock file under XDG_RUNTIME_DIR prevents double-acquisition when
multiple gtklock windows are active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Asger Geel Weirsøe
2026-05-06 09:50:49 +02:00
parent 7913ced403
commit 74e0f544a0
7 changed files with 471 additions and 21 deletions

323
Cargo.lock generated
View File

@@ -19,12 +19,10 @@ dependencies = [
"ahfail-ui", "ahfail-ui",
"gdk", "gdk",
"gdk-pixbuf", "gdk-pixbuf",
"gio",
"glib", "glib",
"gstreamer", "gstreamer",
"gstreamer-player", "gstreamer-player",
"gtk", "gtk",
"libc",
"pkg-config", "pkg-config",
"rand", "rand",
] ]
@@ -44,7 +42,9 @@ dependencies = [
"gstreamer", "gstreamer",
"gstreamer-player", "gstreamer-player",
"gtk", "gtk",
"libc",
"rand", "rand",
"tempfile",
"ureq", "ureq",
] ]
@@ -61,7 +61,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd"
dependencies = [ dependencies = [
"atk-sys", "atk-sys",
"bitflags", "bitflags 1.3.2",
"glib", "glib",
"libc", "libc",
] ]
@@ -96,13 +96,19 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.15.12" version = "0.15.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib",
"libc", "libc",
@@ -172,6 +178,22 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]] [[package]]
name = "field-offset" name = "field-offset"
version = "0.3.6" version = "0.3.6"
@@ -198,6 +220,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@@ -264,7 +292,7 @@ version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cairo-rs", "cairo-rs",
"gdk-pixbuf", "gdk-pixbuf",
"gdk-sys", "gdk-sys",
@@ -280,7 +308,7 @@ version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gio", "gio",
"glib", "glib",
@@ -328,13 +356,26 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]] [[package]]
name = "gio" name = "gio"
version = "0.15.12" version = "0.15.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
@@ -364,7 +405,7 @@ version = "0.15.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor", "futures-executor",
@@ -420,7 +461,7 @@ version = "0.18.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66363bacf5e4f6eb281564adc2902e44c52ae5c45082423e7439e9012b75456" checksum = "d66363bacf5e4f6eb281564adc2902e44c52ae5c45082423e7439e9012b75456"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cfg-if", "cfg-if",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -444,7 +485,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224f35f36582407caf58ded74854526beeecc23d0cf64b8d1c3e00584ed6863f" checksum = "224f35f36582407caf58ded74854526beeecc23d0cf64b8d1c3e00584ed6863f"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cfg-if", "cfg-if",
"glib", "glib",
"gstreamer", "gstreamer",
@@ -471,7 +512,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f14ee02352ba73cadebe640bfb33f12fe8d03cbcad816a102d55a0251fb99bb" checksum = "5f14ee02352ba73cadebe640bfb33f12fe8d03cbcad816a102d55a0251fb99bb"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"glib", "glib",
"gstreamer", "gstreamer",
"gstreamer-player-sys", "gstreamer-player-sys",
@@ -512,7 +553,7 @@ version = "0.18.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9418adfc72dafa1ad9eb106527ce4804887d101027c4528ec28c7d29cc899519" checksum = "9418adfc72dafa1ad9eb106527ce4804887d101027c4528ec28c7d29cc899519"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cfg-if", "cfg-if",
"futures-channel", "futures-channel",
"glib", "glib",
@@ -544,7 +585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0"
dependencies = [ dependencies = [
"atk", "atk",
"bitflags", "bitflags 1.3.2",
"cairo-rs", "cairo-rs",
"field-offset", "field-offset",
"futures-channel", "futures-channel",
@@ -592,6 +633,15 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.16.1"
@@ -691,6 +741,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.1.0" version = "1.1.0"
@@ -719,15 +775,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.16.1",
"serde",
"serde_core",
] ]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.2"
@@ -820,7 +896,7 @@ version = "0.15.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"glib", "glib",
"libc", "libc",
"once_cell", "once_cell",
@@ -893,6 +969,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn 2.0.111",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@@ -945,6 +1031,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@@ -972,7 +1064,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.16",
] ]
[[package]] [[package]]
@@ -983,7 +1075,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"getrandom", "getrandom 0.2.16",
"libc", "libc",
"untrusted", "untrusted",
"windows-sys", "windows-sys",
@@ -998,6 +1090,19 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustix"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.40" version = "0.23.40"
@@ -1068,6 +1173,19 @@ dependencies = [
"syn 2.0.111", "syn 2.0.111",
] ]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.9" version = "0.6.9"
@@ -1165,6 +1283,19 @@ version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
version = "3.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@@ -1246,6 +1377,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@@ -1304,6 +1441,58 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags 2.11.1",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.11" version = "0.26.11"
@@ -1435,6 +1624,100 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck 0.5.0",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck 0.5.0",
"indexmap",
"prettyplease",
"syn 2.0.111",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn 2.0.111",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags 2.11.1",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.3" version = "0.6.3"
@@ -1543,3 +1826,9 @@ dependencies = [
"quote", "quote",
"syn 2.0.111", "syn 2.0.111",
] ]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@@ -56,6 +56,13 @@ impl WindowHandler {
} }
println!("[ahfail] Error label changed to: '{}'", text_str); println!("[ahfail] Error label changed to: '{}'", text_str);
MODULE_STATE.with(|s| {
let mut st = s.borrow_mut();
if st.volume_state.is_none() {
st.volume_state = ahfail_ui::volume::save_and_set_max();
}
});
MODULE_STATE.with(|state| { MODULE_STATE.with(|state| {
let state = state.borrow(); let state = state.borrow();
if let (Some(animation), Some(audio_uri)) = (&state.animation, &state.audio_uri) { if let (Some(animation), Some(audio_uri)) = (&state.animation, &state.audio_uri) {

View File

@@ -136,8 +136,12 @@ pub unsafe extern "C" fn on_idle_show(_gtklock: *mut GtkLock) {}
pub unsafe extern "C" fn g_module_unload(_module: *mut c_void) { pub unsafe extern "C" fn g_module_unload(_module: *mut c_void) {
MODULE_STATE.with(|state| { MODULE_STATE.with(|state| {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
if let Some(vs) = state.volume_state.take() {
ahfail_ui::volume::restore(vs);
}
state.animation = None; state.animation = None;
state.audio_uri = None; state.audio_uri = None;
state.config.deadzone = None; state.config.deadzone = None;
state.volume_state = None;
}); });
} }

View File

@@ -7,6 +7,7 @@ pub struct ModuleState {
pub animation: Option<gdk_pixbuf::PixbufAnimation>, pub animation: Option<gdk_pixbuf::PixbufAnimation>,
pub audio_uri: Option<String>, pub audio_uri: Option<String>,
pub config: ModuleConfig, pub config: ModuleConfig,
pub volume_state: Option<ahfail_ui::volume::VolumeState>,
} }
pub struct WindowData { pub struct WindowData {
@@ -22,5 +23,6 @@ thread_local! {
animation: None, animation: None,
audio_uri: None, audio_uri: None,
config: ModuleConfig { deadzone: None }, config: ModuleConfig { deadzone: None },
volume_state: None,
}) }; }) };
} }

View File

@@ -17,3 +17,7 @@ gio = { version = "0.15", package = "gio" }
gdk-pixbuf = "0.15" gdk-pixbuf = "0.15"
rand = "0.8" rand = "0.8"
ureq = "2" ureq = "2"
libc = "0.2"
[dev-dependencies]
tempfile = "3"

View File

@@ -1,7 +1,139 @@
pub struct VolumeState; use std::path::PathBuf;
use std::process::Command;
use std::fs;
use std::io::Write;
pub fn save_and_set_max() -> Option<VolumeState> { pub struct VolumeState {
None pub volume: u32,
pub muted: bool,
pub lock_path: PathBuf,
} }
pub fn restore(_state: VolumeState) {} /// Atomically creates lock file. Returns true if this process is first (primary).
pub fn try_acquire_lock(path: &std::path::Path) -> bool {
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map(|mut f| { let _ = f.write_all(b"1"); true })
.unwrap_or(false)
}
/// Returns lock file path: $XDG_RUNTIME_DIR/ahfail.lock or /tmp/ahfail-{uid}.lock
pub fn lock_path() -> PathBuf {
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
PathBuf::from(dir).join("ahfail.lock")
} else {
PathBuf::from(format!("/tmp/ahfail-{}.lock", get_uid()))
}
}
fn get_uid() -> u32 {
unsafe { libc::getuid() }
}
/// If primary: save current system volume/mute state, set volume to 100% unmuted.
/// Returns Some(VolumeState) if this process is the primary (should restore on exit).
pub fn save_and_set_max() -> Option<VolumeState> {
let path = lock_path();
if !try_acquire_lock(&path) { return None; }
let (volume, muted) = get_current_volume();
set_volume_max();
Some(VolumeState { volume, muted, lock_path: path })
}
/// Restores volume from saved state and removes lock file.
pub fn restore(state: VolumeState) {
restore_volume(state.volume, state.muted);
let _ = fs::remove_file(&state.lock_path);
}
#[cfg(target_os = "linux")]
fn get_current_volume() -> (u32, bool) {
let vol = Command::new("pactl")
.args(["get-sink-volume", "@DEFAULT_SINK@"])
.output()
.ok()
.and_then(|o| parse_pactl_volume(&String::from_utf8_lossy(&o.stdout)))
.unwrap_or(100);
let muted = Command::new("pactl")
.args(["get-sink-mute", "@DEFAULT_SINK@"])
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).contains("yes"))
.unwrap_or(false);
(vol, muted)
}
#[cfg(target_os = "linux")]
fn parse_pactl_volume(output: &str) -> Option<u32> {
output.split('%').next()
.and_then(|s| s.split_whitespace().last())
.and_then(|s| s.parse().ok())
}
#[cfg(target_os = "linux")]
fn set_volume_max() {
let _ = Command::new("pactl").args(["set-sink-mute", "@DEFAULT_SINK@", "0"]).status();
let _ = Command::new("pactl").args(["set-sink-volume", "@DEFAULT_SINK@", "65536"]).status();
}
#[cfg(target_os = "linux")]
fn restore_volume(volume: u32, muted: bool) {
let v = ((volume as u64 * 65536) / 100) as u32;
let _ = Command::new("pactl")
.args(["set-sink-volume", "@DEFAULT_SINK@", &v.to_string()])
.status();
let mute_arg = if muted { "1" } else { "0" };
let _ = Command::new("pactl").args(["set-sink-mute", "@DEFAULT_SINK@", mute_arg]).status();
}
#[cfg(target_os = "macos")]
fn get_current_volume() -> (u32, bool) {
let output = Command::new("osascript")
.args(["-e", "output volume of (get volume settings)"])
.output()
.ok();
let vol = output.as_ref()
.and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse().ok())
.unwrap_or(100);
let muted = Command::new("osascript")
.args(["-e", "output muted of (get volume settings)"])
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).trim() == "true")
.unwrap_or(false);
(vol, muted)
}
#[cfg(target_os = "macos")]
fn set_volume_max() {
let _ = Command::new("osascript")
.args(["-e", "set volume output volume 100 without output muted"])
.status();
}
#[cfg(target_os = "macos")]
fn restore_volume(volume: u32, muted: bool) {
let script = if muted {
format!("set volume output volume {} with output muted", volume)
} else {
format!("set volume output volume {} without output muted", volume)
};
let _ = Command::new("osascript").args(["-e", &script]).status();
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn get_current_volume() -> (u32, bool) {
(100, false)
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn set_volume_max() {}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn restore_volume(_volume: u32, _muted: bool) {}

View File

@@ -0,0 +1,12 @@
#[test]
fn lock_file_created_and_prevents_second_acquisition() {
let dir = tempfile::tempdir().unwrap();
let lock_path = dir.path().join("ahfail.lock");
let acquired = ahfail_ui::volume::try_acquire_lock(&lock_path);
assert!(acquired);
assert!(lock_path.exists());
let acquired2 = ahfail_ui::volume::try_acquire_lock(&lock_path);
assert!(!acquired2);
}