can't be bothered writing a commit message

This commit is contained in:
Breadway 2026-08-16 14:44:07 +08:00
parent d49df0b5fa
commit 46ecea18ac
8 changed files with 362 additions and 155 deletions

259
Cargo.lock generated
View file

@ -10,18 +10,18 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -40,13 +40,13 @@ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
@ -77,8 +77,8 @@ dependencies = [
[[package]] [[package]]
name = "bread-theme" name = "bread-theme"
version = "0.7.2" version = "0.7.4"
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.2#30517f161724132cdeb658c04cf5e490be07ee73" source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.4#fcba3760387e2523edb71350f8efea3bc851b21e"
dependencies = [ dependencies = [
"dirs", "dirs",
"gtk4", "gtk4",
@ -108,7 +108,7 @@ dependencies = [
"gtk4", "gtk4",
"relm4", "relm4",
"serde", "serde",
"thiserror 2.0.18", "thiserror 2.0.20",
"tokio", "tokio",
"toml 0.8.23", "toml 0.8.23",
"tracing", "tracing",
@ -126,7 +126,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"smithay-client-toolkit", "smithay-client-toolkit",
"thiserror 2.0.18", "thiserror 2.0.20",
"tiny-skia", "tiny-skia",
"toml 0.8.23", "toml 0.8.23",
"tracing", "tracing",
@ -155,22 +155,22 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.25.1" version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
[[package]] [[package]]
name = "bytemuck_derive" name = "bytemuck_derive"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
@ -229,9 +229,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.3.0" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -370,11 +370,11 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.4.3",
] ]
[[package]] [[package]]
@ -398,9 +398,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -426,9 +426,9 @@ dependencies = [
[[package]] [[package]]
name = "font-types" name = "font-types"
version = "0.12.1" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad67eced03f5504d9cbd3a879b5958b5c54d4e5fd794361c6eb21b05fb703411" checksum = "75382bc7392ef10aad10935f92fc3db36d2d4dad0e5d96d8d65e04f89a07ec39"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
] ]
@ -467,9 +467,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -482,9 +482,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -492,15 +492,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -509,38 +509,38 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -623,15 +623,14 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasip2",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -695,7 +694,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -814,7 +813,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -896,9 +895,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.103" version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@ -913,9 +912,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 = "libm" name = "libm"
@ -925,9 +924,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.18" version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1089,9 +1088,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]] [[package]]
name = "png" name = "png"
@ -1131,42 +1130,42 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[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",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.46" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "5.3.0" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]] [[package]]
name = "rangemap" name = "rangemap"
version = "1.7.1" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d"
[[package]] [[package]]
name = "read-fonts" name = "read-fonts"
@ -1192,9 +1191,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.16" version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1238,7 +1237,7 @@ checksum = "36c9dbf50a60c82375e66b61d522c936b187a11b25c0a42e91c516326ad24a4f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -1318,9 +1317,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_derive", "serde_derive",
@ -1328,29 +1327,29 @@ dependencies = [
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.228" version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.228" version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.150" version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1455,7 +1454,7 @@ dependencies = [
"memmap2", "memmap2",
"pkg-config", "pkg-config",
"rustix", "rustix",
"thiserror 2.0.18", "thiserror 2.0.20",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-csd-frame", "wayland-csd-frame",
@ -1522,6 +1521,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "sys-locale" name = "sys-locale"
version = "0.3.2" version = "0.3.2"
@ -1540,7 +1550,7 @@ dependencies = [
"cfg-expr", "cfg-expr",
"heck", "heck",
"pkg-config", "pkg-config",
"toml 1.1.3+spec-1.1.0", "toml 1.1.4+spec-1.1.0",
"version-compare", "version-compare",
] ]
@ -1561,11 +1571,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.18" version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [ dependencies = [
"thiserror-impl 2.0.18", "thiserror-impl 2.0.20",
] ]
[[package]] [[package]]
@ -1576,18 +1586,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.18" version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
@ -1642,9 +1652,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.0" version = "1.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -1658,13 +1668,13 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.3",
] ]
[[package]] [[package]]
@ -1681,9 +1691,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "1.1.3+spec-1.1.0" version = "1.1.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",
@ -1740,9 +1750,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.1.2+spec-1.1.0" version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [ dependencies = [
"winnow 1.0.4", "winnow 1.0.4",
] ]
@ -1779,7 +1789,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -1905,20 +1915,11 @@ 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.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.126" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -1929,9 +1930,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.126" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1939,31 +1940,31 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.126" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.126" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.15" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
@ -1974,9 +1975,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", "bitflags",
"rustix", "rustix",
@ -2059,9 +2060,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",
@ -2098,7 +2099,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -2109,7 +2110,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.119",
] ]
[[package]] [[package]]
@ -2229,17 +2230,11 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
[[package]] [[package]]
name = "xkbcommon" name = "xkbcommon"

View file

@ -3,7 +3,7 @@ members = ["breadlock-ui", "breadlock", "breadgreet"]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2" } bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.4" }
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2" } bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.2" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"

View file

@ -146,6 +146,7 @@ impl SimpleComponent for App {
spawn_greetd_actor(cmd_rx, sender.clone()); spawn_greetd_actor(cmd_rx, sender.clone());
theme::apply(); theme::apply();
bread_theme::gtk::bind_window_auto(&root);
spawn_clock_ticker(sender.clone()); spawn_clock_ticker(sender.clone());
let model = App { let model = App {

View file

@ -1,4 +1,4 @@
pub use bread_theme::{ink_on, load_palette, Palette}; pub use bread_theme::{ink_on, load_palette, load_palette_for, Palette};
/// Parse a `#rrggbb` hex colour. Falls back to opaque black on malformed input /// Parse a `#rrggbb` hex colour. Falls back to opaque black on malformed input
/// (palette slots are always produced by [`bread_theme`], which guarantees /// (palette slots are always produced by [`bread_theme`], which guarantees

View file

@ -140,8 +140,9 @@ impl KeyboardHandler for AppState {
impl AppState { impl AppState {
fn handle_key(&mut self, qh: &QueueHandle<Self>, event: KeyEvent) { fn handle_key(&mut self, qh: &QueueHandle<Self>, event: KeyEvent) {
// Ignore all input while a PAM check is in flight so a fast second // Ignore all input while a PAM check is in flight so a fast second
// Enter can't race the first attempt. // Enter can't race the first attempt, and while the unlock fade is
if self.auth_state == AuthState::Checking { // playing (auth already succeeded; surfaces stay up until it ends).
if self.auth_state == AuthState::Checking || self.unlocking.is_some() {
return; return;
} }

View file

@ -156,12 +156,13 @@ fn run_lock() {
let auth_tx = auth::register(&loop_handle, move |state: &mut AppState, result| { let auth_tx = auth::register(&loop_handle, move |state: &mut AppState, result| {
match result { match result {
Ok(()) => { Ok(()) => {
tracing::info!("authenticated, unlocking"); // Keep the lock surfaces up and fade the overlay out.
if let Some(lock) = state.session_lock.take() { // Compositor unlock() runs only after UNLOCK_MS — dying
lock.unlock(); // mid-fade is fail-secure (session stays locked).
bread_events::emit_unlocked(); tracing::info!("authenticated, fading out");
if state.unlocking.is_none() {
state.unlocking = Some(std::time::Instant::now());
} }
state.exit = true;
} }
Err(err) => { Err(err) => {
match err { match err {
@ -217,6 +218,9 @@ fn run_lock() {
password: zeroize::Zeroizing::new(String::with_capacity(128)), password: zeroize::Zeroizing::new(String::with_capacity(128)),
auth_state: AuthState::Idle, auth_state: AuthState::Idle,
auth_tx, auth_tx,
appear_started: None,
unlocking: None,
anim_timer_armed: false,
exit: false, exit: false,
}; };
@ -272,7 +276,13 @@ fn run_lock() {
let mut consecutive_errors = 0u32; let mut consecutive_errors = 0u32;
while !app_state.exit { while !app_state.exit {
match event_loop.dispatch(Duration::from_millis(250), &mut app_state) { match event_loop.dispatch(Duration::from_millis(250), &mut app_state) {
Ok(()) => consecutive_errors = 0, Ok(()) => {
consecutive_errors = 0;
// Backup if the 16ms anim timer failed to register: the
// 250ms dispatch timeout (or the 1s clock tick) still
// completes a finished unlock fade.
app_state.complete_unlock_if_ready();
}
Err(err) => { Err(err) => {
consecutive_errors += 1; consecutive_errors += 1;
tracing::error!( tracing::error!(

View file

@ -9,8 +9,20 @@
use crate::background::Background; use crate::background::Background;
use breadlock_ui::painter::{rounded_rect, tokens, TextRenderer}; use breadlock_ui::painter::{rounded_rect, tokens, TextRenderer};
use breadlock_ui::theme::tiny_skia_color; use breadlock_ui::theme::tiny_skia_color;
use std::time::Instant;
use tiny_skia::{Color, Paint, Pixmap}; use tiny_skia::{Color, Paint, Pixmap};
/// Lock-appear duration: overlay fades in and eases up from below rest.
pub const APPEAR_MS: u64 = 450;
/// Unlock-fade duration: overlay fades out with a slight upward drift.
pub const UNLOCK_MS: u64 = 400;
/// Redraw cadence while an animation is in flight (~60 Hz).
pub const ANIM_FRAME_MS: u64 = 16;
const APPEAR_SLIDE_PX: f32 = 28.0;
const UNLOCK_DRIFT_PX: f32 = 20.0;
const DIM_ALPHA: f32 = 0.28;
pub struct FrameInputs<'a> { pub struct FrameInputs<'a> {
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
@ -23,6 +35,40 @@ pub struct FrameInputs<'a> {
/// shake in v1 — just a color/status-text indicator. /// shake in v1 — just a color/status-text indicator.
pub failed: bool, pub failed: bool,
pub status_text: Option<&'a str>, pub status_text: Option<&'a str>,
/// Raw 0..1 lock-appear progress (pre-ease). 1 is rest pose.
pub appear_t: f32,
/// Raw 0..1 unlock-fade progress (pre-ease). 0 when not unlocking.
pub unlock_t: f32,
}
/// Ease-out cubic. `t` is clamped to 0..1.
pub fn ease_out_cubic(t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
1.0 - inv * inv * inv
}
/// Linear 0..1 progress since `started` over `duration_ms`.
pub fn unit_progress(started: Instant, duration_ms: u64) -> f32 {
let dur = duration_ms as f32 / 1000.0;
if dur <= 0.0 {
return 1.0;
}
(started.elapsed().as_secs_f32() / dur).clamp(0.0, 1.0)
}
/// Overlay alpha and y-offset (positive is down) from raw 0..1 progress.
pub fn overlay_motion(appear_t: f32, unlock_t: f32) -> (f32, f32) {
let appear = ease_out_cubic(appear_t);
let unlock = ease_out_cubic(unlock_t);
let alpha = (appear * (1.0 - unlock)).clamp(0.0, 1.0);
let y = APPEAR_SLIDE_PX * (1.0 - appear) - UNLOCK_DRIFT_PX * unlock;
(alpha, y)
}
fn faded(mut color: Color, alpha: f32) -> Color {
color.apply_opacity(alpha);
color
} }
/// Composes one frame. Returns `None` only if `width`/`height` are degenerate /// Composes one frame. Returns `None` only if `width`/`height` are degenerate
@ -31,11 +77,28 @@ pub fn compose(text: &mut TextRenderer, inputs: &FrameInputs) -> Option<Pixmap>
let mut pixmap = Pixmap::new(inputs.width, inputs.height)?; let mut pixmap = Pixmap::new(inputs.width, inputs.height)?;
inputs.background.paint(&mut pixmap); inputs.background.paint(&mut pixmap);
let (alpha, y_off) = overlay_motion(inputs.appear_t, inputs.unlock_t);
if alpha <= 0.0 {
return Some(pixmap);
}
let (w, h) = (inputs.width as f32, inputs.height as f32); let (w, h) = (inputs.width as f32, inputs.height as f32);
let surface_color = tiny_skia_color(&inputs.palette.color0); let surface_color = faded(tiny_skia_color(&inputs.palette.color0), alpha);
let accent_color = tiny_skia_color(&inputs.palette.color4); let accent_color = faded(tiny_skia_color(&inputs.palette.color4), alpha);
let on_surface = tiny_skia_color(breadlock_ui::theme::ink_on(&inputs.palette.color0)); let on_surface = faded(
let red_color = tiny_skia_color(&inputs.palette.color1); tiny_skia_color(breadlock_ui::theme::ink_on(&inputs.palette.color0)),
alpha,
);
let red_color = faded(tiny_skia_color(&inputs.palette.color1), alpha);
// Translucent veil over the (static) wallpaper — fades with the chrome.
if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, h) {
let mut paint = Paint::default();
paint.set_color(
Color::from_rgba(0.0, 0.0, 0.0, DIM_ALPHA * alpha).unwrap_or(Color::TRANSPARENT),
);
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
}
// Clock, large, centered in the upper third. // Clock, large, centered in the upper third.
let clock_size = 64.0; let clock_size = 64.0;
@ -45,16 +108,16 @@ pub fn compose(text: &mut TextRenderer, inputs: &FrameInputs) -> Option<Pixmap>
inputs.clock_text, inputs.clock_text,
inputs.font_family, inputs.font_family,
clock_size, clock_size,
Color::WHITE, faded(Color::WHITE, alpha),
(w - clock_w) / 2.0, (w - clock_w) / 2.0,
h * 0.28, h * 0.28 + y_off,
); );
// Password pill, centered; turns red while showing a failed attempt. // Password pill, centered; turns red while showing a failed attempt.
let pill_w = 280.0_f32.min(w - tokens::SPACE_XL as f32 * 2.0); let pill_w = 280.0_f32.min(w - tokens::SPACE_XL as f32 * 2.0);
let pill_h = 48.0; let pill_h = 48.0;
let pill_x = (w - pill_w) / 2.0; let pill_x = (w - pill_w) / 2.0;
let pill_y = h * 0.5; let pill_y = h * 0.5 + y_off;
if let Some(path) = rounded_rect( if let Some(path) = rounded_rect(
pill_x, pill_x,
@ -97,7 +160,7 @@ pub fn compose(text: &mut TextRenderer, inputs: &FrameInputs) -> Option<Pixmap>
{ {
let mut paint = Paint::default(); let mut paint = Paint::default();
paint.set_color(if inputs.failed { paint.set_color(if inputs.failed {
Color::WHITE faded(Color::WHITE, alpha)
} else { } else {
accent_color accent_color
}); });
@ -172,8 +235,38 @@ mod tests {
password_len: 0, password_len: 0,
failed: false, failed: false,
status_text: None, status_text: None,
appear_t: 1.0,
unlock_t: 0.0,
}; };
let pixmap = compose(&mut text, &inputs).unwrap(); let pixmap = compose(&mut text, &inputs).unwrap();
assert_eq!((pixmap.width(), pixmap.height()), (400, 300)); assert_eq!((pixmap.width(), pixmap.height()), (400, 300));
} }
#[test]
fn ease_out_cubic_bounds_and_shape() {
assert_eq!(ease_out_cubic(0.0), 0.0);
assert_eq!(ease_out_cubic(1.0), 1.0);
assert_eq!(ease_out_cubic(-1.0), 0.0);
assert_eq!(ease_out_cubic(2.0), 1.0);
// Ease-out sits above the linear diagonal in the middle of the curve.
assert!(ease_out_cubic(0.5) > 0.5);
}
#[test]
fn overlay_motion_appear_starts_below_and_fades_in() {
let (a0, y0) = overlay_motion(0.0, 0.0);
assert_eq!(a0, 0.0);
assert!(y0 > 0.0, "clock/pill should start below rest, got y={y0}");
let (a1, y1) = overlay_motion(1.0, 0.0);
assert_eq!(a1, 1.0);
assert_eq!(y1, 0.0);
}
#[test]
fn overlay_motion_unlock_fades_out_and_drifts_up() {
let (a, y) = overlay_motion(1.0, 1.0);
assert_eq!(a, 0.0);
assert!(y < 0.0, "unlock should drift up from rest, got y={y}");
}
} }

View file

@ -8,7 +8,7 @@ use smithay_client_toolkit::registry_handlers;
use smithay_client_toolkit::seat::SeatState; use smithay_client_toolkit::seat::SeatState;
use smithay_client_toolkit::session_lock::{SessionLock, SessionLockState, SessionLockSurface}; use smithay_client_toolkit::session_lock::{SessionLock, SessionLockState, SessionLockSurface};
use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::shm::{Shm, ShmHandler};
use std::time::Duration; use std::time::{Duration, Instant};
use wayland_client::protocol::{wl_keyboard, wl_output, wl_shm}; use wayland_client::protocol::{wl_keyboard, wl_output, wl_shm};
use wayland_client::{Connection, QueueHandle}; use wayland_client::{Connection, QueueHandle};
@ -75,6 +75,17 @@ pub struct AppState {
pub auth_state: AuthState, pub auth_state: AuthState,
pub auth_tx: Sender<AuthResult>, pub auth_tx: Sender<AuthResult>,
/// First-frame timestamp for the lock-appear animation. `None` until
/// the first non-degenerate redraw so the fade starts when the surface
/// is actually visible, not when the process starts.
pub appear_started: Option<Instant>,
/// Set on PAM success. While `Some`, lock surfaces stay up and the
/// overlay fades out; compositor `unlock()` happens only after the
/// fade completes. Dying mid-fade leaves the session locked (fail-secure).
pub unlocking: Option<Instant>,
/// True while a ~16ms animation timer is registered on the event loop.
pub anim_timer_armed: bool,
pub exit: bool, pub exit: bool,
} }
@ -93,28 +104,45 @@ impl AppState {
return; return;
} }
if self.appear_started.is_none() {
self.appear_started = Some(Instant::now());
}
let clock_text = chrono::Local::now() let clock_text = chrono::Local::now()
.format(&self.config.appearance.clock.format) .format(&self.config.appearance.clock.format)
.to_string(); .to_string();
let status_text = match self.auth_state { let status_text = match self.auth_state {
AuthState::Checking => Some("Checking…".to_string()), AuthState::Checking => Some("Checking…".to_string()),
AuthState::Failed => Some("Wrong password".to_string()), AuthState::Failed => Some("Wrong password".to_string()),
AuthState::ConfigError => { AuthState::ConfigError => Some(
Some("PAM config error — check logs (breadlock service not set up correctly)".to_string()) "PAM config error — check logs (breadlock service not set up correctly)"
} .to_string(),
),
AuthState::Idle => None, AuthState::Idle => None,
}; };
let appear_t = self
.appear_started
.map(|t| render::unit_progress(t, render::APPEAR_MS))
.unwrap_or(0.0);
let unlock_t = self
.unlocking
.map(|t| render::unit_progress(t, render::UNLOCK_MS))
.unwrap_or(0.0);
let output_palette = self.palette_for_surface(surface);
let inputs = render::FrameInputs { let inputs = render::FrameInputs {
width, width,
height, height,
background: &self.background, background: &self.background,
palette: &self.palette, palette: &output_palette,
font_family: &self.config.appearance.font.family, font_family: &self.config.appearance.font.family,
clock_text: &clock_text, clock_text: &clock_text,
password_len: self.password.len(), password_len: self.password.len(),
failed: matches!(self.auth_state, AuthState::Failed | AuthState::ConfigError), failed: matches!(self.auth_state, AuthState::Failed | AuthState::ConfigError),
status_text: status_text.as_deref(), status_text: status_text.as_deref(),
appear_t,
unlock_t,
}; };
let Some(pixmap) = render::compose(&mut self.text_renderer, &inputs) else { let Some(pixmap) = render::compose(&mut self.text_renderer, &inputs) else {
@ -149,6 +177,18 @@ impl AppState {
.damage_buffer(0, 0, width as i32, height as i32); .damage_buffer(0, 0, width as i32, height as i32);
surface.wl_surface().commit(); surface.wl_surface().commit();
buffer.destroy(); buffer.destroy();
self.arm_anim_if_needed(qh);
}
fn palette_for_surface(&self, surface: &SessionLockSurface) -> breadlock_ui::theme::Palette {
self.surfaces
.iter()
.find(|s| s.surface.wl_surface() == surface.wl_surface())
.and_then(|s| self.output_state.info(&s.output))
.and_then(|info| info.name)
.map(|name| breadlock_ui::theme::load_palette_for(&name))
.unwrap_or_else(|| self.palette.clone())
} }
/// Redraws every currently-configured surface — used for the clock tick /// Redraws every currently-configured surface — used for the clock tick
@ -162,6 +202,73 @@ impl AppState {
for (surface, width, height) in surfaces { for (surface, width, height) in surfaces {
self.redraw_surface(qh, &surface, width, height); self.redraw_surface(qh, &surface, width, height);
} }
self.complete_unlock_if_ready();
}
fn appear_in_progress(&self) -> bool {
self.appear_started
.map(|t| t.elapsed() < Duration::from_millis(render::APPEAR_MS))
.unwrap_or(true)
}
fn unlock_in_progress(&self) -> bool {
self.unlocking
.map(|t| t.elapsed() < Duration::from_millis(render::UNLOCK_MS))
.unwrap_or(false)
}
fn anim_in_progress(&self) -> bool {
self.unlocking.is_some() || self.appear_in_progress()
}
/// Keep requesting frames while appear or unlock-fade is running.
fn arm_anim_if_needed(&mut self, qh: &QueueHandle<Self>) {
if self.anim_timer_armed || !self.anim_in_progress() {
return;
}
self.anim_timer_armed = true;
let qh = qh.clone();
if self
.loop_handle
.insert_source(
Timer::from_duration(Duration::from_millis(render::ANIM_FRAME_MS)),
move |_, _, state| state.tick_animation(&qh),
)
.is_err()
{
tracing::error!("failed to arm lock animation timer");
self.anim_timer_armed = false;
}
}
fn tick_animation(&mut self, qh: &QueueHandle<Self>) -> TimeoutAction {
self.redraw_all(qh);
if self.unlocking.is_some() && !self.unlock_in_progress() {
self.anim_timer_armed = false;
TimeoutAction::Drop
} else if self.anim_in_progress() {
TimeoutAction::ToDuration(Duration::from_millis(render::ANIM_FRAME_MS))
} else {
self.anim_timer_armed = false;
TimeoutAction::Drop
}
}
/// After the unlock fade reaches t==1, send compositor `unlock` and
/// exit. Not called until then — dying mid-fade stays locked.
pub fn complete_unlock_if_ready(&mut self) {
let Some(started) = self.unlocking else {
return;
};
if started.elapsed() < Duration::from_millis(render::UNLOCK_MS) {
return;
}
if let Some(lock) = self.session_lock.take() {
tracing::info!("unlock fade complete");
lock.unlock();
crate::bread_events::emit_unlocked();
}
self.exit = true;
} }
/// After a failed attempt, clears the "wrong password" state (and /// After a failed attempt, clears the "wrong password" state (and