Compare commits

..

No commits in common. "ebcc7aa841c66220db1c4548d221d989e13d71cb" and "bffa521f479e8bf149f4069b79756d71a8a8d80a" have entirely different histories.

13 changed files with 132 additions and 269 deletions

172
Cargo.lock generated
View file

@ -57,9 +57,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bitflags"
version = "2.13.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
dependencies = [
"serde_core",
]
@ -77,7 +77,7 @@ dependencies = [
[[package]]
name = "breadgreet"
version = "0.2.0"
version = "0.1.1"
dependencies = [
"bread-theme",
"breadlock-ui",
@ -95,7 +95,7 @@ dependencies = [
[[package]]
name = "breadlock"
version = "0.2.0"
version = "0.1.1"
dependencies = [
"breadlock-ui",
"chrono",
@ -108,12 +108,11 @@ dependencies = [
"tracing",
"tracing-subscriber",
"wayland-client",
"zeroize",
]
[[package]]
name = "breadlock-ui"
version = "0.2.0"
version = "0.1.1"
dependencies = [
"bread-theme",
"chrono",
@ -131,18 +130,18 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytemuck"
version = "1.25.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.11.0"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5"
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
dependencies = [
"proc-macro2",
"quote",
@ -151,9 +150,9 @@ dependencies = [
[[package]]
name = "bytes"
version = "1.12.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]]
name = "cairo-rs"
@ -205,9 +204,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.3.0"
version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [
"find-msvc-tools",
"shlex",
@ -291,9 +290,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "cursor-icon"
@ -402,9 +401,9 @@ dependencies = [
[[package]]
name = "font-types"
version = "0.12.1"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad67eced03f5504d9cbd3a879b5958b5c54d4e5fd794361c6eb21b05fb703411"
checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
dependencies = [
"bytemuck",
]
@ -443,9 +442,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
@ -458,9 +457,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
@ -468,15 +467,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
@ -485,15 +484,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
@ -502,21 +501,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.33"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
@ -940,9 +939,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.8.3"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "memmap2"
@ -974,9 +973,9 @@ dependencies = [
[[package]]
name = "mio"
version = "1.2.2"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [
"libc",
"wasi",
@ -1102,7 +1101,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit 0.25.13+spec-1.1.0",
"toml_edit 0.25.12+spec-1.1.0",
]
[[package]]
@ -1146,13 +1145,12 @@ checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
[[package]]
name = "read-fonts"
version = "0.41.0"
version = "0.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81"
dependencies = [
"bytemuck",
"font-types",
"once_cell",
]
[[package]]
@ -1168,9 +1166,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.16"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@ -1253,9 +1251,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.23"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rustybuzz"
@ -1282,9 +1280,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "self_cell"
version = "1.3.0"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
[[package]]
name = "semver"
@ -1380,15 +1378,15 @@ dependencies = [
[[package]]
name = "simd-adler32"
version = "0.3.10"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "skrifa"
version = "0.44.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564"
dependencies = [
"bytemuck",
"read-fonts",
@ -1453,9 +1451,9 @@ checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
[[package]]
name = "socket2"
version = "0.6.5"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
@ -1463,9 +1461,9 @@ dependencies = [
[[package]]
name = "spin"
version = "0.9.9"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
@ -1478,9 +1476,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "swash"
version = "0.2.10"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
checksum = "0811b01ca2c4e8718760713911feaf4675c24f94e50530a015ec646cfb622f7c"
dependencies = [
"skrifa",
"yazi",
@ -1489,9 +1487,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.119"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
@ -1516,7 +1514,7 @@ dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml 1.1.3+spec-1.1.0",
"toml 1.1.2+spec-1.1.0",
"version-compare",
]
@ -1568,9 +1566,9 @@ dependencies = [
[[package]]
name = "thread_local"
version = "1.1.10"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
@ -1603,9 +1601,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.12.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
@ -1618,9 +1616,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.53.0"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
@ -1634,9 +1632,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.7.1"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
@ -1657,9 +1655,9 @@ dependencies = [
[[package]]
name = "toml"
version = "1.1.3+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"indexmap",
"serde_core",
@ -1667,7 +1665,7 @@ dependencies = [
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.4",
"winnow 1.0.3",
]
[[package]]
@ -1704,14 +1702,14 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.25.13+spec-1.1.0"
version = "0.25.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
dependencies = [
"indexmap",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.4",
"winnow 1.0.3",
]
[[package]]
@ -1720,7 +1718,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow 1.0.4",
"winnow 1.0.3",
]
[[package]]
@ -1731,9 +1729,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "tracing"
@ -2198,9 +2196,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "1.0.4"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
dependencies = [
"memchr",
]
@ -2249,14 +2247,8 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
[[package]]
name = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zmij"
version = "1.0.23"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View file

@ -7,7 +7,7 @@ Two binaries, one workspace:
- **`breadlock`** — locks the *already running* Hyprland session via `ext-session-lock-v1`. Drop-in for `hyprlock`.
- **`breadgreet`** — a graphical greeter that speaks `greetd`'s own IPC protocol (the same architecture as `gtkgreet`/`regreet`). `greetd` keeps owning PAM auth, VT switching, and session launching; `breadgreet` only draws the login UI and relays the conversation. This is a deliberate choice over reimplementing a display manager from scratch — `greetd` is already installed and battle-tested.
Both use [`bread-theme`](https://git.breadway.dev/Breadway/bread-ecosystem) for palette loading, matching the rest of the bread* ecosystem (breadbar, breadbox, bos-settings).
Both use [`bread-theme`](https://github.com/Breadway/bread-ecosystem) for palette loading, matching the rest of the bread* ecosystem (breadbar, breadbox, bos-settings).
## Architecture
@ -54,7 +54,7 @@ sudo pacman -S gtk4 wayland libxkbcommon pam rust cargo
## Packaging
`packaging/arch/PKGBUILD` builds and installs both binaries plus `/etc/pam.d/breadlock`, published to the `[breadway]` pacman repo by `.forgejo/workflows/package.yml`. breadlock is pacman-only — it is not in bread-ecosystem's registry and has no `bakery.toml`; a PAM/greeter component gets installed through the package manager, not the bakery curl-script channel.
`packaging/arch/PKGBUILD` builds and installs both binaries plus `/etc/pam.d/breadlock`. `bakery.toml` is the bread-ecosystem package index entry.
**Not included, by design**: this repo does not touch `/etc/greetd/config.toml`, install a lock keybind, or wire up `hypridle`. Once packaged, wiring BOS to actually use these binaries means:

15
bakery.toml Normal file
View file

@ -0,0 +1,15 @@
name = "breadlock"
description = "Session locker and greetd greeter for Hyprland / Wayland"
binaries = ["breadlock", "breadgreet"]
system_deps = ["pam", "wayland", "libxkbcommon", "gtk4"]
optional_system_deps = ["cage", "hyprland"]
bread_deps = []
[config]
dir = "~/.config/breadlock"
example = "breadlock.example.toml"
[install]
post_install = [
"echo 'breadlock installed. /etc/pam.d/breadlock is installed by the package; wiring greetd (cage -s -- breadgreet) and a lock keybind/hypridle is a separate manual step.'",
]

View file

@ -1,9 +1,9 @@
[package]
name = "breadgreet"
version = "0.2.0"
version = "0.1.1"
edition = "2021"
license = "MIT"
authors = ["Breadway <plasticbread849@gmail.com>"]
authors = ["Breadway <rileyhorsham@gmail.com>"]
description = "Graphical greetd greeter for Hyprland / Wayland"
[[bin]]

View file

@ -1,9 +1,9 @@
[package]
name = "breadlock-ui"
version = "0.2.0"
version = "0.1.1"
edition = "2021"
license = "MIT"
authors = ["Breadway <plasticbread849@gmail.com>"]
authors = ["Breadway <rileyhorsham@gmail.com>"]
[dependencies]
bread-theme.workspace = true

View file

@ -1,9 +1,9 @@
[package]
name = "breadlock"
version = "0.2.0"
version = "0.1.1"
edition = "2021"
license = "MIT"
authors = ["Breadway <plasticbread849@gmail.com>"]
authors = ["Breadway <rileyhorsham@gmail.com>"]
description = "Session locker for Hyprland / Wayland (ext-session-lock-v1)"
[[bin]]
@ -24,7 +24,6 @@ wayland-client = "0.31"
tiny-skia = "0.12"
chrono = "0.4"
pam-client2 = { version = "0.5", default-features = false }
zeroize = { version = "1", features = ["std"] }
serde.workspace = true
toml.workspace = true
tracing.workspace = true

View file

@ -35,14 +35,8 @@ pub fn register<Data: 'static>(
/// Spawns a PAM check for `username`/`password` on its own thread; the
/// outcome arrives later as an event on the loop registered via
/// [`register`]. `password` is moved in and dropped as soon as the PAM
/// conversation consumes it — it is never logged. It's a `Zeroizing<String>`
/// so the buffer is wiped the moment it goes out of scope at the end of this
/// closure, rather than just deallocated with the bytes intact.
pub fn spawn_check(
username: String,
password: zeroize::Zeroizing<String>,
result_tx: Sender<AuthResult>,
) {
/// conversation consumes it — it is never logged.
pub fn spawn_check(username: String, password: String, result_tx: Sender<AuthResult>) {
std::thread::spawn(move || {
let result = pam::check(&username, &password);
let _ = result_tx.send(result);

View file

@ -4,7 +4,6 @@
use pam_client2::conv_mock::Conversation;
use pam_client2::{Context, Flag};
use zeroize::Zeroize;
/// The PAM service name — matches `/etc/pam.d/breadlock`
/// (packaging/pam.d/breadlock), which is what actually determines the auth
@ -25,24 +24,12 @@ pub enum AuthError {
/// `acct_mgmt` (no `open_session` — the graphical session is already open;
/// this only re-proves who's sitting at the keyboard).
pub fn check(username: &str, password: &str) -> Result<(), AuthError> {
// `Conversation::with_credentials` copies `password` into its own
// `String` field (it has to — PAM's conversation callback is invoked
// later, synchronously, by libpam via FFI). That struct has no Drop/
// zeroize of its own, so we reach back in and zero it explicitly below
// before `ctx` (and the conversation it owns) is dropped.
let conv = Conversation::with_credentials(username, password);
let mut ctx =
Context::new(SERVICE, Some(username), conv).map_err(|_| AuthError::ContextInit)?;
let result = ctx
.authenticate(Flag::NONE)
.map_err(|_| AuthError::Authenticate)
.and_then(|()| {
ctx.acct_mgmt(Flag::NONE)
.map_err(|_| AuthError::AccountInvalid)
});
ctx.conversation_mut().password.zeroize();
result
ctx.authenticate(Flag::NONE)
.map_err(|_| AuthError::Authenticate)?;
ctx.acct_mgmt(Flag::NONE)
.map_err(|_| AuthError::AccountInvalid)?;
Ok(())
}

View file

@ -4,7 +4,6 @@ use smithay_client_toolkit::seat::keyboard::{
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
use wayland_client::protocol::{wl_keyboard, wl_seat, wl_surface};
use wayland_client::{Connection, QueueHandle};
use zeroize::Zeroize;
use crate::auth;
use crate::state::{AppState, AuthState};
@ -24,27 +23,7 @@ impl SeatHandler for AppState {
capability: Capability,
) {
if capability == Capability::Keyboard && self.keyboard.is_none() {
// Plain `get_keyboard` never populates SCTK's internal repeat
// timer, so `KeyboardHandler::repeat_key` below only ever fires
// for compositors that implement server-side key repeat
// (wl_keyboard >= v10's "repeated" pseudo key-state) themselves —
// Hyprland does not reliably do this. `get_keyboard_with_repeat`
// registers SCTK's own client-side repeat timer driven by the
// compositor's `repeat_info` (delay/rate); if a compositor *does*
// do server-side repeat it advertises `rate = 0`, which this
// timer already treats as disabled, so the two mechanisms can't
// double-fire.
let repeat_qh = qh.clone();
let loop_handle = self.loop_handle.clone();
match self.seat_state.get_keyboard_with_repeat(
qh,
&seat,
None,
loop_handle,
Box::new(move |state: &mut AppState, _keyboard, event| {
state.handle_key(&repeat_qh, event);
}),
) {
match self.seat_state.get_keyboard(qh, &seat, None) {
Ok(keyboard) => self.keyboard = Some(keyboard),
Err(err) => tracing::error!(%err, "failed to bind keyboard"),
}
@ -148,26 +127,11 @@ impl AppState {
match event.keysym {
Keysym::Return | Keysym::KP_Enter => self.submit(),
Keysym::BackSpace => {
if let Some((idx, _)) = self.password.char_indices().last() {
// Plain `String::pop()` shrinks the logical length but
// leaves the removed character's bytes sitting in the
// buffer's spare capacity. Zero them explicitly before
// truncating.
//
// SAFETY: `idx` comes from `char_indices()`, so it is a
// valid char boundary; the retained prefix `[..idx]`
// is untouched and still valid UTF-8, and we truncate to
// exactly that boundary immediately after zeroing the
// (now-discarded) tail.
unsafe {
self.password.as_mut_vec()[idx..].zeroize();
}
self.password.truncate(idx);
}
self.password.pop();
self.clear_failed_state();
}
Keysym::Escape => {
self.password.zeroize();
self.password.clear();
self.clear_failed_state();
}
_ => {
@ -187,7 +151,7 @@ impl AppState {
}
fn clear_failed_state(&mut self) {
if matches!(self.auth_state, AuthState::Failed | AuthState::ConfigError) {
if self.auth_state == AuthState::Failed {
self.auth_state = AuthState::Idle;
}
}
@ -197,15 +161,7 @@ impl AppState {
return;
}
self.auth_state = AuthState::Checking;
// Hand ownership of the buffer to the auth thread; re-reserve
// capacity up front so the next password typed doesn't reallocate
// (see the `password` field doc in state.rs). The taken buffer is
// zeroized automatically when it's dropped at the end of the PAM
// check (`auth::spawn_check`/`pam::check`).
let password = std::mem::replace(
&mut self.password,
zeroize::Zeroizing::new(String::with_capacity(128)),
);
let password = std::mem::take(&mut self.password);
auth::spawn_check(self.username.clone(), password, self.auth_tx.clone());
}
}

View file

@ -73,7 +73,6 @@ impl OutputHandler for AppState {
let lock_surface = session_lock.create_lock_surface(surface, &output, qh);
self.surfaces.push(LockSurface {
surface: lock_surface,
output,
width: 0,
height: 0,
});
@ -87,16 +86,11 @@ impl OutputHandler for AppState {
) {
}
/// A monitor disappeared (unplug, or Hyprland dropping/recreating it on
/// a mode change). Drop the lock surface tied to it — otherwise
/// `surfaces` only ever grows across hotplug cycles and `redraw_all`
/// keeps trying to commit to a surface whose output is gone.
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
_output: wl_output::WlOutput,
) {
self.surfaces.retain(|s| s.output != output);
}
}

View file

@ -57,24 +57,8 @@ fn main() {
state.exit = true;
}
Err(err) => {
match err {
// A broken PAM setup (missing/invalid /etc/pam.d/breadlock,
// context init failure) is a config problem, not a typo —
// rendering it identically to "wrong password" would lock
// the user out with zero indication of what's actually
// wrong. Log loudly and show a distinct on-screen message.
auth::AuthError::ContextInit => {
tracing::error!(
%err,
"PAM context initialization failed — check /etc/pam.d/breadlock exists and is valid; authentication cannot succeed until this is fixed"
);
state.auth_state = AuthState::ConfigError;
}
auth::AuthError::Authenticate | auth::AuthError::AccountInvalid => {
tracing::warn!(%err, "authentication failed");
state.auth_state = AuthState::Failed;
}
}
tracing::warn!(%err, "authentication failed");
state.auth_state = AuthState::Failed;
state.schedule_clear_failed(auth_result_qh.clone());
}
}
@ -104,10 +88,7 @@ fn main() {
background,
text_renderer: breadlock_ui::painter::TextRenderer::new(),
username,
// Pre-reserve capacity so ordinary typing doesn't reallocate — a
// reallocation leaves the old (unzeroized) backing buffer, with the
// password bytes still in it, on the heap.
password: zeroize::Zeroizing::new(String::with_capacity(128)),
password: String::new(),
auth_state: AuthState::Idle,
auth_tx,
exit: false,
@ -126,7 +107,6 @@ fn main() {
let lock_surface = session_lock.create_lock_surface(surface, &output, &qh);
app_state.surfaces.push(LockSurface {
surface: lock_surface,
output,
width: 0,
height: 0,
});
@ -149,48 +129,15 @@ fn main() {
)
.expect("failed to register the clock-tick timer");
// A dispatch error here is the one path that can end this process while
// the session lock is still up: `SessionLockInner::drop` deliberately
// does *not* send `unlock`, only `destroy` (see the crate's own doc
// comment — "choosing not to unlock here results in us failing secure"),
// so an abrupt exit stays fail-secure at the protocol level; the failure
// mode is a frozen/unusable lock screen (Hyprland's "lock client
// crashed" state), not an unlocked one. We do NOT call `.unlock()` from
// here — doing so on an error path would make an unattended failure
// capable of unlocking the session, i.e. turn a fail-secure bug into a
// fail-open one. Instead: tolerate a burst of transient errors (a single
// `dispatch()` hiccup shouldn't be fatal) and only give up, loudly, after
// several consecutive failures.
const MAX_CONSECUTIVE_DISPATCH_ERRORS: u32 = 5;
let mut consecutive_errors = 0u32;
while !app_state.exit {
match event_loop.dispatch(Duration::from_millis(250), &mut app_state) {
Ok(()) => consecutive_errors = 0,
Err(err) => {
consecutive_errors += 1;
tracing::error!(
%err,
consecutive_errors,
"event loop dispatch failed — session remains locked (fail-secure); \
if this persists the lock screen may become unresponsive and require \
a VT switch or `loginctl` to recover"
);
if consecutive_errors >= MAX_CONSECUTIVE_DISPATCH_ERRORS {
tracing::error!(
"giving up after {consecutive_errors} consecutive dispatch failures; \
exiting WITHOUT unlocking this is intentional (fail-secure), but \
the screen will likely be stuck and need a VT switch to recover"
);
break;
}
}
if let Err(err) = event_loop.dispatch(Duration::from_millis(250), &mut app_state) {
tracing::error!(%err, "event loop dispatch failed");
break;
}
}
// Make sure the compositor actually receives the unlock/destroy
// requests queued above (from a successful auth) before the process
// exits. This is a no-op if we got here via the dispatch-error path
// above, since nothing queued an unlock in that case.
// requests queued above before the process exits.
let _ = app_state.conn.roundtrip();
}

View file

@ -9,7 +9,7 @@ use smithay_client_toolkit::seat::SeatState;
use smithay_client_toolkit::session_lock::{SessionLock, SessionLockState, SessionLockSurface};
use smithay_client_toolkit::shm::{Shm, ShmHandler};
use std::time::Duration;
use wayland_client::protocol::{wl_keyboard, wl_output, wl_shm};
use wayland_client::protocol::{wl_keyboard, wl_shm};
use wayland_client::{Connection, QueueHandle};
use crate::auth::AuthResult;
@ -18,12 +18,9 @@ use crate::config::Config;
use crate::render;
/// Per-output lock surface plus the size the compositor last `configure`d it
/// to (0x0 until the first configure arrives). `output` is kept so
/// `output_destroyed` can find and drop the surface belonging to an unplugged
/// monitor — without it, hotplug/unplug cycles only ever grow `surfaces`.
/// to (0x0 until the first configure arrives).
pub struct LockSurface {
pub surface: SessionLockSurface,
pub output: wl_output::WlOutput,
pub width: u32,
pub height: u32,
}
@ -34,16 +31,7 @@ pub enum AuthState {
/// A PAM check is running on its own thread; input is ignored until it
/// resolves so a second Enter can't race the first attempt.
Checking,
/// The password (or account state) was rejected by PAM — an ordinary
/// wrong-password/locked-account outcome the user can retry.
Failed,
/// PAM itself failed to initialize (e.g. `/etc/pam.d/breadlock` is
/// missing or unreadable) — this is a config/deployment problem, not
/// something the user's password can fix. Rendered with a distinct
/// message so a broken install doesn't look like an endless string of
/// typos with no way to discover the real cause. See `main.rs`'s
/// auth-result callback, which is the only place this is set.
ConfigError,
}
pub struct AppState {
@ -65,13 +53,7 @@ pub struct AppState {
pub text_renderer: breadlock_ui::painter::TextRenderer,
pub username: String,
/// Wrapped in `Zeroizing` so the buffer is wiped on every drop/replace
/// (e.g. when `submit()` swaps in a fresh one) rather than just
/// deallocated with the password bytes left sitting in freed heap
/// memory. Individual edits (backspace, clear) still need their own
/// explicit zeroing — see `input/keyboard.rs` — since `Zeroizing` only
/// hooks `Drop`, not in-place mutation.
pub password: zeroize::Zeroizing<String>,
pub password: String,
pub auth_state: AuthState,
pub auth_tx: Sender<AuthResult>,
@ -99,9 +81,6 @@ impl AppState {
let status_text = match self.auth_state {
AuthState::Checking => Some("Checking…".to_string()),
AuthState::Failed => Some("Wrong password".to_string()),
AuthState::ConfigError => {
Some("PAM config error — check logs (breadlock service not set up correctly)".to_string())
}
AuthState::Idle => None,
};
@ -113,7 +92,7 @@ impl AppState {
font_family: &self.config.appearance.font.family,
clock_text: &clock_text,
password_len: self.password.len(),
failed: matches!(self.auth_state, AuthState::Failed | AuthState::ConfigError),
failed: self.auth_state == AuthState::Failed,
status_text: status_text.as_deref(),
};
@ -172,7 +151,7 @@ impl AppState {
let _ =
self.loop_handle
.insert_source(Timer::from_duration(timeout), move |_, _, state| {
if matches!(state.auth_state, AuthState::Failed | AuthState::ConfigError) {
if state.auth_state == AuthState::Failed {
state.auth_state = AuthState::Idle;
state.redraw_all(&qh);
}

View file

@ -1,11 +1,11 @@
# Maintainer: Breadway <plasticbread849@gmail.com>
# Maintainer: Breadway <rileyhorsham@gmail.com>
pkgname=breadlock
pkgver=0.1.0
pkgrel=1
pkgdesc="Session locker and greetd greeter for Hyprland / Wayland"
arch=('x86_64')
url="https://git.breadway.dev/Breadway/breadlock"
url="https://github.com/Breadway/breadlock"
license=('MIT')
# Some Rust deps build vendored C/asm into static archives; makepkg's default
# -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read, causing