From f76fc0e6991fb04e1dbfee60d1042490ef5ff3fb Mon Sep 17 00:00:00 2001 From: r0chd Date: Mon, 7 Sep 2026 22:15:38 +0200 Subject: [PATCH] monolithic repository --- .tangled/workflows/check.yaml | 7 +- .tangled/workflows/clippy.yaml | 7 +- .tangled/workflows/fmt.yaml | 3 + .tangled/workflows/test.yaml | 9 +- Cargo.lock | 395 +++-- Cargo.toml | 5 +- daemon/src/components.rs | 4 - default.nix | 18 +- docs/INSTALL.md | 4 +- flake.nix | 75 +- hyprwire-protocols-sessiond/Cargo.toml | 6 +- hyprwire-protocols-sessiond/build.rs | 18 +- .../protocols/session_backlight_v1.xml | 81 + hyprwire-protocols-sessiond/src/lib.rs | 15 +- libsession/Cargo.toml | 5 +- libsession/libsession.pc.in | 10 + nix/internal/finix-checks.nix | 2 +- nix/internal/finix-vm.nix | 35 +- nix/internal/nixos-checks.nix | 2 +- nix/internal/nixos-vm.nix | 10 + nix/internal/shell.nix | 14 +- nix/sessiond-backlight/finix.nix | 48 + nix/sessiond-backlight/package.nix | 31 + nix/sessiond-hooks/finix.nix | 73 + nix/sessiond-hooks/package.nix | 31 + nix/sessiond-uaccess/finix.nix | 89 + nix/sessiond-uaccess/package.nix | 45 + nix/{ => sessiond}/common.nix | 7 +- nix/{ => sessiond}/finix.nix | 0 nix/{ => sessiond}/nixos.nix | 4 +- nix/{ => sessiond}/package.nix | 68 +- pam/Cargo.toml | 5 +- {ctl => sessionctl}/Cargo.toml | 7 +- {ctl => sessionctl}/build.rs | 0 {ctl => sessionctl}/src/main.rs | 0 sessiond-backlight/.gitignore | 1 + sessiond-backlight/Cargo.lock | 676 ++++++++ sessiond-backlight/Cargo.toml | 23 + sessiond-backlight/src/main.rs | 441 +++++ sessiond-hooks/.gitignore | 1 + sessiond-hooks/Cargo.lock | 776 +++++++++ sessiond-hooks/Cargo.toml | 26 + sessiond-hooks/LICENSE | 13 + sessiond-hooks/README.md | 98 ++ sessiond-hooks/src/config.rs | 92 + sessiond-hooks/src/logging.rs | 111 ++ sessiond-hooks/src/main.rs | 614 +++++++ sessiond-uaccess/.gitignore | 1 + sessiond-uaccess/Cargo.lock | 1503 +++++++++++++++++ sessiond-uaccess/Cargo.toml | 26 + sessiond-uaccess/LICENSE | 14 + sessiond-uaccess/README.md | 98 ++ sessiond-uaccess/lua/sessiond-uaccess.lua | 31 + sessiond-uaccess/rules/70-uaccess.lua | 90 + sessiond-uaccess/src/config.rs | 184 ++ sessiond-uaccess/src/device.rs | 10 + sessiond-uaccess/src/ecs.rs | 59 + sessiond-uaccess/src/logging.rs | 136 ++ sessiond-uaccess/src/main.rs | 499 ++++++ sessiond-uaccess/src/session.rs | 71 + {daemon => sessiond}/Cargo.toml | 9 +- {daemon => sessiond}/src/cgroup.rs | 3 +- {daemon => sessiond}/src/config.rs | 5 +- {daemon => sessiond}/src/consolekit.rs | 0 {daemon => sessiond}/src/inhibit.rs | 22 +- {daemon => sessiond}/src/main.rs | 5 +- {daemon => sessiond}/src/reconcile.rs | 25 +- {daemon => sessiond}/src/session.rs | 38 +- {daemon => sessiond}/src/state.rs | 9 +- {daemon => sessiond}/src/terminal.rs | 0 {daemon => sessiond}/src/user.rs | 73 +- {daemon => sessiond}/src/vt.rs | 9 +- 72 files changed, 6513 insertions(+), 412 deletions(-) delete mode 100644 daemon/src/components.rs create mode 100644 hyprwire-protocols-sessiond/protocols/session_backlight_v1.xml create mode 100644 libsession/libsession.pc.in create mode 100644 nix/sessiond-backlight/finix.nix create mode 100644 nix/sessiond-backlight/package.nix create mode 100644 nix/sessiond-hooks/finix.nix create mode 100644 nix/sessiond-hooks/package.nix create mode 100644 nix/sessiond-uaccess/finix.nix create mode 100644 nix/sessiond-uaccess/package.nix rename nix/{ => sessiond}/common.nix (90%) rename nix/{ => sessiond}/finix.nix (100%) rename nix/{ => sessiond}/nixos.nix (86%) rename nix/{ => sessiond}/package.nix (64%) rename {ctl => sessionctl}/Cargo.toml (87%) rename {ctl => sessionctl}/build.rs (100%) rename {ctl => sessionctl}/src/main.rs (100%) create mode 100644 sessiond-backlight/.gitignore create mode 100644 sessiond-backlight/Cargo.lock create mode 100644 sessiond-backlight/Cargo.toml create mode 100644 sessiond-backlight/src/main.rs create mode 100644 sessiond-hooks/.gitignore create mode 100644 sessiond-hooks/Cargo.lock create mode 100644 sessiond-hooks/Cargo.toml create mode 100644 sessiond-hooks/LICENSE create mode 100644 sessiond-hooks/README.md create mode 100644 sessiond-hooks/src/config.rs create mode 100644 sessiond-hooks/src/logging.rs create mode 100644 sessiond-hooks/src/main.rs create mode 100644 sessiond-uaccess/.gitignore create mode 100644 sessiond-uaccess/Cargo.lock create mode 100644 sessiond-uaccess/Cargo.toml create mode 100644 sessiond-uaccess/LICENSE create mode 100644 sessiond-uaccess/README.md create mode 100644 sessiond-uaccess/lua/sessiond-uaccess.lua create mode 100644 sessiond-uaccess/rules/70-uaccess.lua create mode 100644 sessiond-uaccess/src/config.rs create mode 100644 sessiond-uaccess/src/device.rs create mode 100644 sessiond-uaccess/src/ecs.rs create mode 100644 sessiond-uaccess/src/logging.rs create mode 100644 sessiond-uaccess/src/main.rs create mode 100644 sessiond-uaccess/src/session.rs rename {daemon => sessiond}/Cargo.toml (90%) rename {daemon => sessiond}/src/cgroup.rs (99%) rename {daemon => sessiond}/src/config.rs (97%) rename {daemon => sessiond}/src/consolekit.rs (100%) rename {daemon => sessiond}/src/inhibit.rs (95%) rename {daemon => sessiond}/src/main.rs (98%) rename {daemon => sessiond}/src/reconcile.rs (77%) rename {daemon => sessiond}/src/session.rs (97%) rename {daemon => sessiond}/src/state.rs (97%) rename {daemon => sessiond}/src/terminal.rs (100%) rename {daemon => sessiond}/src/user.rs (87%) rename {daemon => sessiond}/src/vt.rs (71%) diff --git a/.tangled/workflows/check.yaml b/.tangled/workflows/check.yaml index 649cef5..d31496a 100644 --- a/.tangled/workflows/check.yaml +++ b/.tangled/workflows/check.yaml @@ -12,6 +12,8 @@ dependencies: - gcc - linux-pam - pkg-config + - udev + - acl steps: - name: cargo check @@ -33,6 +35,9 @@ steps: Libs: -L\${libdir} -lsession Cflags: -I\${includedir} EOF - export PKG_CONFIG_PATH="$(pwd)/target/pkgconfig" + export PKG_CONFIG_PATH="$(pwd)/target/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" cargo check --workspace --all-features + cargo check --manifest-path sessiond-backlight/Cargo.toml + cargo check --manifest-path sessiond-hooks/Cargo.toml + cargo check --manifest-path sessiond-uaccess/Cargo.toml diff --git a/.tangled/workflows/clippy.yaml b/.tangled/workflows/clippy.yaml index 89d7dbb..4865152 100644 --- a/.tangled/workflows/clippy.yaml +++ b/.tangled/workflows/clippy.yaml @@ -12,6 +12,8 @@ dependencies: - gcc - linux-pam - pkg-config + - udev + - acl steps: - name: cargo clippy @@ -34,6 +36,9 @@ steps: Libs: -L\${libdir} -lsession Cflags: -I\${includedir} EOF - export PKG_CONFIG_PATH="$(pwd)/target/pkgconfig" + export PKG_CONFIG_PATH="$(pwd)/target/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" cargo clippy --workspace --all-features -- -D warnings + cargo clippy --manifest-path sessiond-backlight/Cargo.toml -- -D warnings + cargo clippy --manifest-path sessiond-hooks/Cargo.toml -- -D warnings + cargo clippy --manifest-path sessiond-uaccess/Cargo.toml -- -D warnings diff --git a/.tangled/workflows/fmt.yaml b/.tangled/workflows/fmt.yaml index 2284832..6f49699 100644 --- a/.tangled/workflows/fmt.yaml +++ b/.tangled/workflows/fmt.yaml @@ -25,3 +25,6 @@ steps: rustup default stable rustup component add rustfmt cargo fmt --check + cargo fmt --manifest-path sessiond-backlight/Cargo.toml --check + cargo fmt --manifest-path sessiond-hooks/Cargo.toml --check + cargo fmt --manifest-path sessiond-uaccess/Cargo.toml --check diff --git a/.tangled/workflows/test.yaml b/.tangled/workflows/test.yaml index 730858e..8b95f41 100644 --- a/.tangled/workflows/test.yaml +++ b/.tangled/workflows/test.yaml @@ -13,14 +13,19 @@ dependencies: - linux-pam - pkg-config - nix + - udev + - acl steps: - name: cargo test command: | rustup toolchain install stable rustup default stable - cargo build --package daemon - cargo test --package daemon + cargo build --package sessiond + cargo test --package sessiond + cargo test --manifest-path sessiond-backlight/Cargo.toml + cargo test --manifest-path sessiond-hooks/Cargo.toml + cargo test --manifest-path sessiond-uaccess/Cargo.toml - name: finix vm tests command: | diff --git a/Cargo.lock b/Cargo.lock index 3c47d0c..cda1a83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arrayvec" @@ -166,7 +166,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -198,13 +198,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] @@ -224,9 +224,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bevy_ecs" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4654b9b3535fa7813d2878a50e1b5ad80a361dc1c913b96ded57667f65d70a01" +checksum = "a350b2e4c2e47f7903d688affb8a723b31bca63af9789f1da88703f1ba04e354" dependencies = [ "arrayvec", "bevy_ecs_macros", @@ -250,46 +250,46 @@ dependencies = [ [[package]] name = "bevy_ecs_macro_logic" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee0b98a74141ce8dce4654c60020ebbaefab32646f42af6bbae55f282ae65ff7" +checksum = "76a10fef45a3e7dad717ba03e8b5f7ecdf3b3fd1cd1c5e425793525938e90d5f" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "bevy_ecs_macros" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc958a194d226d618404e0fea99a3c8b4146207a00a3ba07fa712bf71607240" +checksum = "3916d712264b6f3ec9c964ba10719450ea371e537e202a2ffcfa9f6a5e68169d" dependencies = [ "bevy_ecs_macro_logic", "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "bevy_macro_utils" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746a19912c6dc1bbe79188778573e8a253d5832c696b2fcb95578c17b29ff7ba" +checksum = "7164fe229422295bba15f1885048f34f4660db069aacb895208f996b0c7784fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "toml_edit", ] [[package]] name = "bevy_platform" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3120670bb2308980f723477c9d82476fcda31f08be9b256c3f0acdee82a65094" +checksum = "fd610a417e7b4a3b9602cf11264d16cbbff8eabe689c5fed8be4e84c6a2680a3" dependencies = [ "foldhash", "futures-channel", @@ -303,15 +303,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b511e89f7078fd21fdea77061a0ca3e737297c7011bb10c073e0aecb17c9fea6" +checksum = "1974de4b140f102fded747229ff4a61921acec07f02f0d1dcdadfe9ce177d0ff" [[package]] name = "bevy_tasks" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa34e6b9b804851ec08a41c0346c859d82e2fa98c906d74b965ee61bbbb688e4" +checksum = "1f787d4ba7237b64b30bd2d690a8cbae1ba775e0eec326711c446654cb720e7c" dependencies = [ "async-task", "atomic-waker", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aceea6c7ffdd568f866d5ca4dfe50c33440c54f7bc230c13a9ba7ab0c91aed1" +checksum = "de51fa7ea2e95d4c2d1278c5629285d59eec951cc60c992964c03f30910ef5a1" dependencies = [ "bevy_platform", "disqualified", @@ -348,9 +348,9 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blocking" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" dependencies = [ "async-channel", "async-task", @@ -393,15 +393,15 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "clap" -version = "4.6.2" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -409,9 +409,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -440,14 +440,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] @@ -530,44 +530,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ctl" -version = "0.2.1" -dependencies = [ - "anyhow", - "clap", - "clap_complete", - "clap_complete_nushell", - "comfy-table", - "hyprwire", - "hyprwire-protocols-sessiond", - "hyprwire-scanner", - "owo-colors", - "serde_json", - "termtree", -] - -[[package]] -name = "daemon" -version = "0.2.1" -dependencies = [ - "anyhow", - "bevy_ecs", - "calloop", - "clap", - "env_logger", - "hyprwire", - "hyprwire-protocols-sessiond", - "hyprwire-scanner", - "log", - "rustix", - "serde", - "syslog", - "toml", - "uzers", - "zbus", -] - [[package]] name = "defmt" version = "1.1.1" @@ -587,7 +549,7 @@ dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -624,7 +586,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -667,7 +629,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -711,11 +673,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -732,9 +693,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fixedbitset" @@ -750,24 +711,24 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-lite" @@ -784,15 +745,15 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-task", @@ -877,9 +838,9 @@ dependencies = [ [[package]] name = "hyprwire" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b50723c9685352232486a20c8435544833926a2119a142868cf478f2642f266" +checksum = "19ee2af8d569f2a6d71130b9643dc534b7255b57f7953260d044d88d9735e707" dependencies = [ "hyprwire-core", "hyprwire-scanner", @@ -890,38 +851,37 @@ dependencies = [ [[package]] name = "hyprwire-core" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbed3858de0e63414aa82ba0049672b7f3edf23b40e9fa497929d8f4c749d62b" +checksum = "1a76bc1faef6e638086f116cdfc19e590232d7565f569e01fd9eb217ac86f35f" [[package]] name = "hyprwire-protocols-sessiond" -version = "0.1.1" +version = "0.2.0" dependencies = [ - "bevy_ecs", "hyprwire", "hyprwire-scanner", ] [[package]] name = "hyprwire-scanner" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726d74a6c38d8ee1ca2fee6f113b18daf93e92548ea0576a890eb700b2c84698" +checksum = "2b50d1dc744b592f4354885b2c445f7ba4f61e3472d02e23d7b81d73f39dbf15" dependencies = [ "hyprwire-core", "prettyplease", "proc-macro2", "quick-xml", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -941,11 +901,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt", + "jiff-core", "jiff-static", "log", "portable-atomic", @@ -953,37 +914,48 @@ dependencies = [ "serde_core", ] +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + [[package]] name = "jiff-static" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libsession" -version = "0.2.1" +version = "0.0.0" dependencies = [ "hyprwire", "hyprwire-protocols-sessiond", @@ -1027,9 +999,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "memchr" @@ -1109,7 +1081,7 @@ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "pam" -version = "0.2.1" +version = "0.0.0" dependencies = [ "hyprwire", "hyprwire-protocols-sessiond", @@ -1194,15 +1166,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -1220,7 +1192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.119", ] [[package]] @@ -1234,9 +1206,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1252,9 +1224,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1288,9 +1260,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1345,9 +1317,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1355,29 +1327,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -1388,13 +1360,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] @@ -1406,6 +1378,44 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sessionctl" +version = "0.0.0" +dependencies = [ + "anyhow", + "clap", + "clap_complete", + "clap_complete_nushell", + "comfy-table", + "hyprwire", + "hyprwire-protocols-sessiond", + "hyprwire-scanner", + "owo-colors", + "serde_json", + "termtree", +] + +[[package]] +name = "sessiond" +version = "0.2.3" +dependencies = [ + "anyhow", + "bevy_ecs", + "calloop", + "clap", + "env_logger", + "hyprwire", + "hyprwire-protocols-sessiond", + "hyprwire-scanner", + "log", + "rustix", + "serde", + "syslog", + "toml", + "uzers", + "zbus", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -1433,9 +1443,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" [[package]] name = "spin" @@ -1469,6 +1479,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syslog" version = "7.0.0" @@ -1502,29 +1523,29 @@ checksum = "d4d1330fe7f7f872cd05165130b10602d667b205fd85be09be2814b115d4ced9" [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "libc", @@ -1554,9 +1575,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" dependencies = [ "indexmap", "serde_core", @@ -1590,9 +1611,9 @@ dependencies = [ [[package]] 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" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] @@ -1623,7 +1644,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1678,9 +1699,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "js-sys", "serde_core", @@ -1705,7 +1726,7 @@ checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1716,9 +1737,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -1729,9 +1750,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1739,22 +1760,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -1819,9 +1840,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.17.0" +version = "5.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b97f866896a4be7aefd2b5a8e01bb6773d19a775d54ab28b4d094b9a4480e" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" dependencies = [ "async-broadcast", "async-executor", @@ -1854,14 +1875,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.17.0" +version = "5.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e05ad887425eecf5e8384dc2406a4a9313eb73468712fc1cdea362eb4fe0469" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 3.0.5", "zbus_names", "zvariant", "zvariant_utils", @@ -1869,15 +1890,24 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.3" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039ca249fee9559680f3a9f05b55e0761fee51af4f6c1e7d8c1f31e549721d2" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", "winnow", "zvariant", ] +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zmij" version = "1.0.23" @@ -1886,40 +1916,41 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zvariant" -version = "5.13.0" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cf057bb00bf5c9ad77abb6147b0ca4818236a1858416e9d988e40d6322fefa7" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" dependencies = [ "endi", "enumflags2", "serde", "winnow", + "zcheapstr", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.13.0" +version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8118ca6bda77bfc0ab51d660db0c955f2505eef854c9a449435bccb616933b31" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 3.0.5", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.5.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 3.0.5", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 62e0112..12a4061 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ - "ctl", - "daemon", + "sessionctl", + "sessiond", "libsession", "libsession-rs", "libsession-sys", @@ -27,7 +27,6 @@ categories = [] edition = "2024" license = "GPL-3.0-only" repository = "https://tangled.org/r0chd.pl/sessiond" -version = "0.2.1" description = "session management daemon" readme = "README.md" diff --git a/daemon/src/components.rs b/daemon/src/components.rs deleted file mode 100644 index 6e94cbc..0000000 --- a/daemon/src/components.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(bevy_ecs::component::Component)] -pub struct SessionInhibitor( - pub crate::session_inhibit_v1::server::session_inhibitor::SessionInhibitor, -); diff --git a/default.nix b/default.nix index 0b655d1..3cd1de8 100644 --- a/default.nix +++ b/default.nix @@ -22,10 +22,19 @@ let }; in rec { - default = pkgs.callPackage ./nix/package.nix { + default = pkgs.callPackage ./nix/sessiond/package.nix { inherit rustPlatform; }; sessiond = default; + sessiond-backlight = pkgs.callPackage ./nix/sessiond-backlight/package.nix { + inherit rustPlatform; + }; + sessiond-hooks = pkgs.callPackage ./nix/sessiond-hooks/package.nix { + inherit rustPlatform; + }; + sessiond-uaccess = pkgs.callPackage ./nix/sessiond-uaccess/package.nix { + inherit rustPlatform; + }; }; in { @@ -37,13 +46,16 @@ in }; nixosModules = rec { - default = import ./nix/nixos.nix; + default = import ./nix/sessiond/nixos.nix; sessiond = default; }; finixModules = rec { - default = import ./nix/finix.nix; + default = import ./nix/sessiond/finix.nix; sessiond = default; + sessiond-backlight = import ./nix/sessiond-backlight/finix.nix; + sessiond-hooks = import ./nix/sessiond-hooks/finix.nix; + sessiond-uaccess = import ./nix/sessiond-uaccess/finix.nix; }; formatter = import ./nix/internal/formatter.nix { inherit pkgs rustToolchain; }; diff --git a/docs/INSTALL.md b/docs/INSTALL.md index fcca8fc..fb9eabf 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -102,8 +102,8 @@ cargo build --workspace --release --features consolekit Build outputs can be found in `target/release`: -- `daemon`: the session daemon -- `ctl`: the control client +- `sessiond`: the session daemon +- `sessionctl`: the control client - `libpam.so`: the PAM module ## Other distributions diff --git a/flake.nix b/flake.nix index eacd8bd..28f8947 100644 --- a/flake.nix +++ b/flake.nix @@ -65,34 +65,48 @@ program = "${script}"; }; - packages = forAllSystems (pkgs: { - sessiond = - let - rustToolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ - "rust-analyzer" - "rust-src" - "llvm-tools" - ]; - }; - rustPlatform = pkgs.makeRustPlatform { - cargo = rustToolchain; - rustc = rustToolchain; - }; - in - pkgs.callPackage ./nix/package.nix { + packages = forAllSystems ( + pkgs: + let + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ + "rust-analyzer" + "rust-src" + "llvm-tools" + ]; + }; + rustPlatform = pkgs.makeRustPlatform { + cargo = rustToolchain; + rustc = rustToolchain; + }; + in + { + sessiond = pkgs.callPackage ./nix/sessiond/package.nix { inherit rustPlatform; }; - default = self.packages.${pkgs.stdenv.hostPlatform.system}.sessiond; - }); + sessiond-backlight = pkgs.callPackage ./nix/sessiond-backlight/package.nix { + inherit rustPlatform; + }; + sessiond-hooks = pkgs.callPackage ./nix/sessiond-hooks/package.nix { + inherit rustPlatform; + }; + sessiond-uaccess = pkgs.callPackage ./nix/sessiond-uaccess/package.nix { + inherit rustPlatform; + }; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.sessiond; + } + ); nixosModules = { - sessiond = import ./nix/nixos.nix; + sessiond = import ./nix/sessiond/nixos.nix; default = self.nixosModules.sessiond; }; finixModules = { - sessiond = import ./nix/finix.nix; + sessiond = import ./nix/sessiond/finix.nix; + sessiond-backlight = import ./nix/sessiond-backlight/finix.nix; + sessiond-hooks = import ./nix/sessiond-hooks/finix.nix; + sessiond-uaccess = import ./nix/sessiond-uaccess/finix.nix; default = self.finixModules.sessiond; }; @@ -117,10 +131,15 @@ nixos-vm = nixpkgs.lib.nixosSystem { inherit system; specialArgs = { - inherit (self.packages.${system}) sessiond; + inherit (self.packages.${system}) + sessiond + sessiond-backlight + sessiond-hooks + sessiond-uaccess + ; }; modules = [ - ./nix/nixos.nix + ./nix/sessiond/nixos.nix ./nix/internal/nixos-vm.nix ]; }; @@ -135,11 +154,19 @@ finix-vm = finix.lib.finixSystem { inherit (pkgs) lib; specialArgs = { - inherit (self.packages.${system}) sessiond; + inherit (self.packages.${system}) + sessiond + sessiond-backlight + sessiond-hooks + sessiond-uaccess + ; }; modules = [ { nixpkgs = { inherit pkgs; }; } - ./nix/finix.nix + ./nix/sessiond/finix.nix + ./nix/sessiond-backlight/finix.nix + ./nix/sessiond-hooks/finix.nix + ./nix/sessiond-uaccess/finix.nix ./nix/internal/finix-vm.nix "${finix.outPath}/modules/virtualisation/qemu.nix" ] diff --git a/hyprwire-protocols-sessiond/Cargo.toml b/hyprwire-protocols-sessiond/Cargo.toml index 34348bf..83b8b8d 100644 --- a/hyprwire-protocols-sessiond/Cargo.toml +++ b/hyprwire-protocols-sessiond/Cargo.toml @@ -5,16 +5,12 @@ categories.workspace = true edition.workspace = true license = "MIT" repository.workspace = true -version = "0.1.1" +version = "0.2.0" description = "Common Hyprwire protocols for sessiond" readme = "README.md" [dependencies] hyprwire = "0.5.1" -bevy_ecs = { version = "0.19.0", default-features = false, optional = true } - -[features] -bevy_ecs = ["dep:bevy_ecs"] [build-dependencies] hyprwire-scanner = "0.5.1" diff --git a/hyprwire-protocols-sessiond/build.rs b/hyprwire-protocols-sessiond/build.rs index 996f170..3f44bf1 100644 --- a/hyprwire-protocols-sessiond/build.rs +++ b/hyprwire-protocols-sessiond/build.rs @@ -3,15 +3,11 @@ use std::io; fn main() -> io::Result<()> { let scanner = hyprwire_scanner::configure() .with_targets(hyprwire_scanner::Targets::CLIENT | hyprwire_scanner::Targets::SERVER); - scanner - .type_attribute( - ".", - "#[cfg_attr(feature = \"bevy_ecs\", derive(bevy_ecs::component::Component))]", - ) - .compile(&[ - "protocols/session_core_v1.xml", - "protocols/session_inhibit_v1.xml", - "protocols/session_management_v1.xml", - "protocols/session_management_v2.xml", - ]) + scanner.compile(&[ + "protocols/session_core_v1.xml", + "protocols/session_inhibit_v1.xml", + "protocols/session_management_v1.xml", + "protocols/session_management_v2.xml", + "protocols/session_backlight_v1.xml", + ]) } diff --git a/hyprwire-protocols-sessiond/protocols/session_backlight_v1.xml b/hyprwire-protocols-sessiond/protocols/session_backlight_v1.xml new file mode 100644 index 0000000..5a1033b --- /dev/null +++ b/hyprwire-protocols-sessiond/protocols/session_backlight_v1.xml @@ -0,0 +1,81 @@ + + + + Copyright (c) 2026 Oskar Rochowiak + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + + + + Interface for creating objects for backlight and LED devices. A client may + only create or modify an object while it owns an active local session. + + + + + + + + + Creates a `session_backlight_device` object for the device identified by + subsystem and name. The subsystem must be `backlight` or `leds`. + + + + + + + + + + + + + Interface for changing the brightness of one device selected by the + subsystem and name passed to `get_device`. A client may only perform an + operation while it owns an active local session. The result of each + operation is reported with done or error. + + + + + + + + + + + + Sets brightness for this device in /sys/class/backlight or + /sys/class/leds. + + + + + + + + + + + + + + diff --git a/hyprwire-protocols-sessiond/src/lib.rs b/hyprwire-protocols-sessiond/src/lib.rs index 95a2d98..5f01206 100644 --- a/hyprwire-protocols-sessiond/src/lib.rs +++ b/hyprwire-protocols-sessiond/src/lib.rs @@ -31,18 +31,15 @@ define_protocol_modules!(server => { v1 => session_management_v1 => "session_management_v1", v2 => session_management_v2 => "session_management_v2", }, + session_backlight => { v1 => session_backlight_v1 => "session_backlight_v1" }, }); define_protocol_modules!(client => { session_core => { v1 => session_core_v1 => "session_core_v1" }, session_inhibit => { v1 => session_inhibit_v1 => "session_inhibit_v1" }, - session_management => { v1 => session_management_v1 => "session_management_v1" }, + session_management => { + v1 => session_management_v1 => "session_management_v1", + v2 => session_management_v2 => "session_management_v2", + }, + session_backlight => { v1 => session_backlight_v1 => "session_backlight_v1" }, }); - -#[cfg(feature = "bevy_ecs")] -impl bevy_ecs::component::Component - for server::session_inhibit::v1::session_inhibit_v1::session_inhibitor::SessionInhibitor -{ - const STORAGE_TYPE: bevy_ecs::component::StorageType = bevy_ecs::component::StorageType::Table; - type Mutability = bevy_ecs::component::Mutable; -} diff --git a/libsession/Cargo.toml b/libsession/Cargo.toml index f42326e..68aa08b 100644 --- a/libsession/Cargo.toml +++ b/libsession/Cargo.toml @@ -5,7 +5,8 @@ categories.workspace = true edition.workspace = true license = "MIT" repository.workspace = true -version.workspace = true +version = "0.0.0" +publish = false description.workspace = true readme.workspace = true @@ -16,7 +17,7 @@ crate-type = ["cdylib", "staticlib"] [dependencies] hyprwire = { version = "0.5.1", optional = true } zbus = { version = "5.16.0", optional = true } -hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.1.1", optional = true } +hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0", optional = true } [build-dependencies] diff --git a/libsession/libsession.pc.in b/libsession/libsession.pc.in new file mode 100644 index 0000000..6a8ad70 --- /dev/null +++ b/libsession/libsession.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +includedir=@INCLUDE@ +libdir=@LIBDIR@ + +Name: libsession +URL: https://tangled.org/r0chd.pl/sessiond +Description: Session management client library +Version: @LIBSESSION_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lsession diff --git a/nix/internal/finix-checks.nix b/nix/internal/finix-checks.nix index 0dbccd3..f57cf09 100644 --- a/nix/internal/finix-checks.nix +++ b/nix/internal/finix-checks.nix @@ -14,7 +14,7 @@ let testArgs = { inherit sessiondTest; - sessiondModule = import (self + "/nix/finix.nix"); + sessiondModule = import (self + "/nix/sessiond/finix.nix"); sessiondPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.sessiond; }; diff --git a/nix/internal/finix-vm.nix b/nix/internal/finix-vm.nix index a3ef6a0..f48f735 100644 --- a/nix/internal/finix-vm.nix +++ b/nix/internal/finix-vm.nix @@ -3,6 +3,9 @@ pkgs, config, sessiond, + sessiond-hooks, + sessiond-uaccess, + sessiond-backlight, ... }: let @@ -55,11 +58,33 @@ in debug = true; settings = { users.user_3.linger = true; - power = { - suspend = [ "${pkgs.zzz}/bin/zzz" ]; - hibernate = [ "${pkgs.zzz}/bin/ZZZ" ]; - reboot = [ "${pkgs.finit}/bin/reboot" ]; - poweroff = [ "${pkgs.finit}/bin/poweroff" ]; + }; + }; + sessiond-uaccess = { + enable = true; + package = sessiond-uaccess; + logLevel = "debug"; + }; + sessiond-backlight = { + enable = true; + package = sessiond-backlight; + logLevel = "debug"; + }; + sessiond-hooks = { + package = sessiond-hooks; + logLevel = "debug"; + hooks.dinit.settings = { + on_start = { + exec = lib.getExe' pkgs.dinit "dinit"; + args = [ + "--user" + "--services-dir" + "~/.config/dinit.d" + ]; + }; + restart = { + on = "Failure"; + delay = 5; }; }; }; diff --git a/nix/internal/nixos-checks.nix b/nix/internal/nixos-checks.nix index aa0ff10..4558f40 100644 --- a/nix/internal/nixos-checks.nix +++ b/nix/internal/nixos-checks.nix @@ -10,7 +10,7 @@ let testArgs = { inherit sessiondTest; - sessiondModule = import (self + "/nix/nixos.nix"); + sessiondModule = import (self + "/nix/sessiond/nixos.nix"); sessiondPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.sessiond; }; diff --git a/nix/internal/nixos-vm.nix b/nix/internal/nixos-vm.nix index 0f3bca5..bd982c6 100644 --- a/nix/internal/nixos-vm.nix +++ b/nix/internal/nixos-vm.nix @@ -3,6 +3,9 @@ pkgs, modulesPath, sessiond, + sessiond-backlight, + sessiond-hooks, + sessiond-uaccess, ... }: let @@ -18,6 +21,13 @@ in package = sessiond; }; + environment.systemPackages = [ + sessiond + sessiond-backlight + sessiond-hooks + sessiond-uaccess + ]; + boot.kernelParams = [ "console=ttyS0" ]; diff --git a/nix/internal/shell.nix b/nix/internal/shell.nix index 02584cd..fd47b50 100644 --- a/nix/internal/shell.nix +++ b/nix/internal/shell.nix @@ -6,11 +6,6 @@ pkgs.mkShell { name = "sessiond-devshell"; packages = builtins.attrValues { - sessiond-testctl = pkgs.writeShellScriptBin "sessiond-testctl" '' - printf 'sessiond-testctl args:' - printf ' <%s>' "$@" - printf '\n' - ''; inherit rustToolchain ; @@ -20,6 +15,8 @@ pkgs.mkShell { npins linux-pam pkg-config + udev + acl ; }; @@ -27,11 +24,4 @@ pkgs.mkShell { buildInputs = [ sessiond ]; RUST_SRC_PATH = "${rustToolchain}/lib/rustlib/src/rust/library"; - - shellHook = '' - if [[ $- == *i* ]] && [ -z "''${IN_NIX_SHELL_NU:-}" ]; then - export IN_NIX_SHELL_NU=1 - exec ${pkgs.nushell}/bin/nu - fi - ''; } diff --git a/nix/sessiond-backlight/finix.nix b/nix/sessiond-backlight/finix.nix new file mode 100644 index 0000000..c2c4ca2 --- /dev/null +++ b/nix/sessiond-backlight/finix.nix @@ -0,0 +1,48 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.services.sessiond-backlight; +in +{ + options.services.sessiond-backlight = { + enable = lib.mkEnableOption "sessiond-backlight"; + package = lib.mkPackageOption pkgs "sessiond-backlight" { }; + debug = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable debug logging. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.dbus.packages = [ cfg.package ]; + + finit.services.sessiond-backlight = { + description = ""; + conditions = [ + "service/sessiond/ready" + ]; + command = lib.escapeShellArgs [ + (lib.getExe cfg.package) + "--log-target" + "syslog" + ]; + environment = + if cfg.debug then + { + LOG_LEVEL = "debug"; + } + else + { + LOG_LEVEL = lib.mkDefault "info"; + }; + }; + }; +} diff --git a/nix/sessiond-backlight/package.nix b/nix/sessiond-backlight/package.nix new file mode 100644 index 0000000..5cdb600 --- /dev/null +++ b/nix/sessiond-backlight/package.nix @@ -0,0 +1,31 @@ +{ + lib, + rustPlatform, +}: +let + cargoToml = fromTOML (builtins.readFile ../../sessiond-backlight/Cargo.toml); +in +rustPlatform.buildRustPackage { + pname = cargoToml.package.name; + inherit (cargoToml.package) version; + + cargoLock.lockFile = ../../sessiond-backlight/Cargo.lock; + + buildAndTestSubdir = "sessiond-backlight"; + + postUnpack = "cp source/sessiond-backlight/Cargo.lock source/Cargo.lock"; + + src = lib.cleanSourceWith { + src = ../..; + filter = path: type: lib.cleanSourceFilter path type || lib.hasSuffix "/Cargo.lock" (toString path); + }; + + meta = { + description = cargoToml.package.description; + homepage = cargoToml.package.repository; + license = lib.licenses.gpl3Only; + maintainers = builtins.attrValues { inherit (lib.maintainers) r0chd; }; + platforms = lib.platforms.linux; + mainProgram = "sessiond-backlight"; + }; +} diff --git a/nix/sessiond-hooks/finix.nix b/nix/sessiond-hooks/finix.nix new file mode 100644 index 0000000..2f18ee4 --- /dev/null +++ b/nix/sessiond-hooks/finix.nix @@ -0,0 +1,73 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.services.sessiond-hooks; + tomlFormat = pkgs.formats.toml { }; +in +{ + options.services.sessiond-hooks = { + package = lib.mkPackageOption pkgs "sessiond-hooks" { }; + debug = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable debug logging. + ''; + }; + hooks = lib.mkOption { + type = types.attrsOf ( + types.submodule { + options = { + enable = lib.mkOption { + type = types.bool; + default = true; + }; + settings = lib.mkOption { + type = tomlFormat.type; + description = "Hook settings serialized to TOML."; + }; + }; + } + ); + default = { }; + description = '' + Per-user session hooks launched. Each named entry spawns an + independent `sessiond-hooks` process. ''; + }; + }; + + config.finit.services = lib.mapAttrs' ( + name: hook: + let + configFile = tomlFormat.generate "sessiond-hooks-${name}.toml" hook.settings; + in + lib.nameValuePair "sessiond-hooks-${name}" { + inherit (hook) enable; + description = "sessiond per-user hook: ${name}"; + conditions = [ + "service/sessiond/ready" + ]; + command = lib.escapeShellArgs [ + (lib.getExe cfg.package) + "--log-target" + "syslog" + "--config" + "${configFile}" + ]; + environment = + if cfg.debug then + { + LOG_LEVEL = "debug"; + } + else + { + LOG_LEVEL = lib.mkDefault "info"; + }; + } + ) cfg.hooks; +} diff --git a/nix/sessiond-hooks/package.nix b/nix/sessiond-hooks/package.nix new file mode 100644 index 0000000..586dc30 --- /dev/null +++ b/nix/sessiond-hooks/package.nix @@ -0,0 +1,31 @@ +{ + lib, + rustPlatform, +}: +let + cargoToml = fromTOML (builtins.readFile ../../sessiond-hooks/Cargo.toml); +in +rustPlatform.buildRustPackage { + pname = cargoToml.package.name; + inherit (cargoToml.package) version; + + cargoLock.lockFile = ../../sessiond-hooks/Cargo.lock; + + buildAndTestSubdir = "sessiond-hooks"; + + postUnpack = "cp source/sessiond-hooks/Cargo.lock source/Cargo.lock"; + + src = lib.cleanSourceWith { + src = ../..; + filter = path: type: lib.cleanSourceFilter path type || lib.hasSuffix "/Cargo.lock" (toString path); + }; + + meta = { + description = cargoToml.package.description; + homepage = cargoToml.package.repository; + license = lib.licenses.gpl3Only; + maintainers = builtins.attrValues { inherit (lib.maintainers) r0chd; }; + platforms = lib.platforms.linux; + mainProgram = "sessiond-hooks"; + }; +} diff --git a/nix/sessiond-uaccess/finix.nix b/nix/sessiond-uaccess/finix.nix new file mode 100644 index 0000000..bb40cf9 --- /dev/null +++ b/nix/sessiond-uaccess/finix.nix @@ -0,0 +1,89 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.services.sessiond-uaccess; + extraRuleNames = builtins.attrNames cfg.extraRules; + validRuleName = name: builtins.match "[0-9A-Za-z._+-]+[.]lua" name != null; + extraRulesPackage = pkgs.linkFarm "sessiond-uaccess-extra-rules" ( + lib.mapAttrsToList (name: text: { + inherit name; + path = pkgs.writeText name text; + }) cfg.extraRules + ); +in +{ + options.services.sessiond-uaccess = { + enable = lib.mkEnableOption "sessiond-uaccess"; + package = lib.mkPackageOption pkgs "sessiond-uaccess" { }; + debug = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable debug logging. + ''; + }; + extraRules = lib.mkOption { + type = types.attrsOf types.lines; + default = { }; + example = { + "90-local.lua" = '' + device { + access = "rw", + match = function(dev) + return dev.subsystem == "backlight" + end, + } + ''; + }; + description = '' + Additional Lua rule files loaded after the packaged sessiond-uaccess + rules. Attribute names are used as filenames and should end in + {file}`.lua`; numeric prefixes can be used to express ordering. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = builtins.all validRuleName extraRuleNames; + message = "services.sessiond-uaccess.extraRules attribute names must be simple Lua filenames ending in .lua."; + } + ]; + + finit.services.sessiond-uaccess = { + description = "Grant device access to active local sessions"; + conditions = [ + "service/sessiond/ready" + "service/syslogd/ready" + ]; + command = lib.escapeShellArgs ( + [ + (lib.getExe cfg.package) + "--log-target" + "syslog" + "--rules-dirs" + "${cfg.package}/share/sessiond-uaccess/rules" + ] + ++ lib.optionals (cfg.extraRules != { }) [ + "--rules-dirs" + "${extraRulesPackage}" + ] + ); + environment = + if cfg.debug then + { + LOG_LEVEL = "debug"; + } + else + { + LOG_LEVEL = lib.mkDefault "info"; + }; + }; + }; +} diff --git a/nix/sessiond-uaccess/package.nix b/nix/sessiond-uaccess/package.nix new file mode 100644 index 0000000..746b572 --- /dev/null +++ b/nix/sessiond-uaccess/package.nix @@ -0,0 +1,45 @@ +{ + lib, + rustPlatform, + pkg-config, + udev, + acl, +}: +let + cargoToml = fromTOML (builtins.readFile ../../sessiond-uaccess/Cargo.toml); +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = cargoToml.package.name; + inherit (cargoToml.package) version; + + cargoLock.lockFile = ../../sessiond-uaccess/Cargo.lock; + + buildAndTestSubdir = "sessiond-uaccess"; + + postUnpack = "cp source/sessiond-uaccess/Cargo.lock source/Cargo.lock"; + + src = lib.cleanSourceWith { + src = ../..; + filter = path: type: lib.cleanSourceFilter path type || lib.hasSuffix "/Cargo.lock" (toString path); + }; + + nativeBuildInputs = [ pkg-config ]; + buildInputs = [ + acl + udev + ]; + + postInstall = '' + install -d $out/share/sessiond-uaccess + cp -r ${finalAttrs.src}/sessiond-uaccess/rules $out/share/sessiond-uaccess/ + ''; + + meta = { + description = cargoToml.package.description; + homepage = cargoToml.package.repository; + license = lib.licenses.gpl3Only; + maintainers = builtins.attrValues { inherit (lib.maintainers) r0chd; }; + platforms = lib.platforms.linux; + mainProgram = "sessiond-uaccess"; + }; +}) diff --git a/nix/common.nix b/nix/sessiond/common.nix similarity index 90% rename from nix/common.nix rename to nix/sessiond/common.nix index 3b40f28..dcf5480 100644 --- a/nix/common.nix +++ b/nix/sessiond/common.nix @@ -5,11 +5,12 @@ }: let format = pkgs.formats.toml { }; + inherit (lib) types; in { options.services.sessiond = { enable = lib.mkOption { - type = lib.types.bool; + type = types.bool; default = false; description = '' Whether to enable [sessiond](${pkgs.sessiond.meta.homepage}) as a system service. @@ -17,7 +18,7 @@ in }; package = lib.mkOption { - type = lib.types.package; + type = types.package; default = pkgs.sessiond; defaultText = lib.literalExpression "pkgs.sessiond"; description = '' @@ -26,7 +27,7 @@ in }; debug = lib.mkOption { - type = lib.types.bool; + type = types.bool; default = false; description = '' Whether to enable debug logging. diff --git a/nix/finix.nix b/nix/sessiond/finix.nix similarity index 100% rename from nix/finix.nix rename to nix/sessiond/finix.nix diff --git a/nix/nixos.nix b/nix/sessiond/nixos.nix similarity index 86% rename from nix/nixos.nix rename to nix/sessiond/nixos.nix index ee27b80..da20a7e 100644 --- a/nix/nixos.nix +++ b/nix/sessiond/nixos.nix @@ -7,7 +7,7 @@ let cfg = config.services.sessiond; settingsFormat = pkgs.formats.toml { }; - daemonConfig = settingsFormat.generate "sessiond.toml" cfg.settings; + sessiondConfig = settingsFormat.generate "sessiond.toml" cfg.settings; in { imports = [ ./common.nix ]; @@ -21,7 +21,7 @@ in wantedBy = [ "default.target" ]; serviceConfig = { - ExecStart = "${cfg.package}/bin/sessiond --config ${daemonConfig}"; + ExecStart = "${cfg.package}/bin/sessiond --config ${sessiondConfig}"; Environment = if cfg.debug then "LOG_LEVEL=debug" else "LOG_LEVEL=info"; Type = "notify"; Delegate = true; diff --git a/nix/package.nix b/nix/sessiond/package.nix similarity index 64% rename from nix/package.nix rename to nix/sessiond/package.nix index d9de760..6261a3e 100644 --- a/nix/package.nix +++ b/nix/sessiond/package.nix @@ -13,21 +13,11 @@ installShellFiles, }: let - cargoToml = fromTOML (builtins.readFile ../Cargo.toml); - - # libsession-sys/libsession-rs link against libsession, which - # only exists once this derivation's own postInstall runs - cargoWorkspaceFlags = [ - "--workspace" - "--exclude" - "libsession-sys" - "--exclude" - "libsession-rs" - ]; + cargoToml = fromTOML (builtins.readFile ../../sessiond/Cargo.toml); in -rustPlatform.buildRustPackage { +rustPlatform.buildRustPackage (finalAttrs: { pname = "sessiond"; - inherit (cargoToml.workspace.package) version; + inherit (cargoToml.package) version; outputs = [ "out" @@ -36,23 +26,23 @@ rustPlatform.buildRustPackage { ]; cargoLock = { - lockFile = ../Cargo.lock; + lockFile = ../../Cargo.lock; outputHashes = { "pamsm-0.5.5" = "sha256-wmSKEVSgd36kwa9ky4H9+ubnqUUwyUPw/OjK8v3wGbk="; }; }; src = lib.cleanSourceWith { - src = ../.; + src = ../..; filter = path: type: let - relPath = lib.removePrefix (toString ../. + "/") (toString path); + relPath = lib.removePrefix (toString ../.. + "/") (toString path); in lib.any (p: lib.hasPrefix p relPath) [ - "ctl" + "sessionctl" "pam" - "daemon" + "sessiond" "libsession" "libsession-sys" "libsession-rs" @@ -73,37 +63,39 @@ rustPlatform.buildRustPackage { linux-pam ]; - cargoBuildFlags = cargoWorkspaceFlags; - cargoTestFlags = cargoWorkspaceFlags; + cargoBuildFlags = [ + "-p" + "sessiond" + "-p" + "sessionctl" + "-p" + "pam" + "-p" + "libsession" + "-p" + "hyprwire-protocols-sessiond" + ]; buildNoDefaultFeatures = true; + doCheck = false; buildFeatures = lib.optionals withConsoleKit [ - "daemon/consolekit" + "sessiond/consolekit" ] ++ map (backend: "libsession/${backend}") libsessionBackends; postInstall = '' mkdir -p $out/lib/security mv $out/lib/libpam.so $out/lib/security/pam_sessiond.so - mv $out/bin/daemon $out/bin/sessiond - mv $out/bin/ctl $out/bin/sessionctl - install -Dm644 libsession/include/libsession.h $out/include/libsession.h mkdir -p $out/lib/pkgconfig - cat > $out/lib/pkgconfig/libsession.pc < Result<(), BrightnessError> { + let mut components = path::Path::new(value).components(); + if value.is_empty() + || !matches!(components.next(), Some(path::Component::Normal(_))) + || components.next().is_some() + { + return Err(BrightnessError::InvalidArgs(format!( + "invalid brightness {kind} {value}" + ))); + } + + Ok(()) + } + + fn open( + session_backlight_device: session_backlight_device::SessionBacklightDevice, + subsystem: String, + name: String, + ) -> Result { + if !matches!(subsystem.as_str(), "backlight" | "leds") { + return Err(BrightnessError::InvalidArgs(format!( + "unsupported brightness subsystem {subsystem}" + ))); + } + Self::validate_component("device name", &name)?; + + let path = path::Path::new("/sys/class").join(&subsystem).join(&name); + + let max_brightness = fs::read_to_string(path.join("max_brightness")) + .map_err(|err| { + BrightnessError::Failed(format!( + "failed to read {subsystem}/{name} max_brightness: {err}" + )) + })? + .trim() + .parse::() + .map_err(|err| { + BrightnessError::Failed(format!( + "failed to parse {subsystem}/{name} max_brightness: {err}" + )) + })?; + + let brightness = fs::OpenOptions::new() + .write(true) + .open(path.join("brightness")) + .map_err(|err| { + BrightnessError::Failed(format!( + "failed to open {subsystem}/{name} brightness: {err}" + )) + })?; + + log::debug!("opened {subsystem}/{name} with max_brightness={max_brightness}"); + + Ok(Self { + subsystem, + name, + brightness, + max_brightness, + object: session_backlight_device, + }) + } + + fn set(&mut self, brightness: u32) -> anyhow::Result<()> { + if brightness > self.max_brightness { + anyhow::bail!( + "brightness {brightness} exceeds max_brightness {}", + self.max_brightness + ); + } + + self.brightness.seek(io::SeekFrom::Start(0))?; + self.brightness + .write_all(brightness.to_string().as_bytes())?; + self.brightness.write_all(b"\n")?; + self.brightness.flush()?; + + Ok(()) + } +} + +struct Session { + id: u32, + object: session_core_session::SessionCoreSession, + local: bool, + uid: u32, + active: session_core_v1::ActiveState, + done: bool, +} + +impl Session { + const fn new(id: u32, object: session_core_session::SessionCoreSession) -> Self { + Self { + id, + object, + local: false, + uid: 0, + done: false, + active: session_core_v1::ActiveState::Opening, + } + } +} + +#[derive(Clone, Copy)] +struct SessionAccess { + id: u32, + local: bool, + uid: u32, + active: session_core_v1::ActiveState, + done: bool, +} + +impl From<&Session> for SessionAccess { + fn from(session: &Session) -> Self { + Self { + id: session.id, + local: session.local, + uid: session.uid, + active: session.active, + done: session.done, + } + } +} + +struct SessionAccessState { + sessions: Vec, +} + +impl SessionAccessState { + const fn new() -> Self { + Self { + sessions: Vec::new(), + } + } + + fn update(&mut self, session: &Session) { + let access = SessionAccess::from(session); + + if let Some(existing) = self + .sessions + .iter_mut() + .find(|existing| existing.id == session.id) + { + *existing = access; + } else { + self.sessions.push(access); + } + } + + fn remove(&mut self, session_id: u32) { + self.sessions.retain(|session| session.id != session_id); + } + + fn has_active_local_session(&self, uid: u32) -> bool { + self.sessions.iter().any(|session| { + session.done + && session.local + && session.uid == uid + && matches!(session.active, session_core_v1::ActiveState::Active) + }) + } +} + +struct SessiondBacklight { + session_core_manager: Option, + sessions: Vec, + access: SessionAccessState, + devices: Vec, +} + +impl hyprwire::Dispatch for SessiondBacklight { + fn event( + &mut self, + object: &session_core_manager::SessionCoreManager, + event: ::Event<'_>, + ) { + if let session_core_manager::Event::SessionAdded { session_id } = event { + self.sessions.push(Session::new( + session_id, + object.send_get_session::(session_id).unwrap(), + )); + } + } +} + +impl hyprwire::Dispatch for SessiondBacklight { + fn event( + &mut self, + object: &session_core_session::SessionCoreSession, + event: ::Event<'_>, + ) { + let Some(session) = self + .sessions + .iter_mut() + .find(|session| session.object == *object) + else { + return; + }; + + match event { + session_core_session::Event::Active { state } => session.active = state, + session_core_session::Event::Uid { uid } => session.uid = uid, + session_core_session::Event::IsLocal { is_local } => session.local = is_local != 0, + session_core_session::Event::Done => session.done = true, + session_core_session::Event::Destroyed => { + let session_id = session.id; + self.sessions.retain(|session| session.id != session_id); + self.access.remove(session_id); + return; + } + _ => {} + } + + self.access.update(session); + } +} + +impl hyprwire::Handler for SessiondBacklight {} + +impl hyprwire::Dispatch for SessiondBacklight { + fn event( + &mut self, + object: &session_backlight_manager::SessionBacklightManager, + event: ::Event<'_>, + ) { + match event { + session_backlight_manager::Event::Destroy => {} + session_backlight_manager::Event::GetDevice { + session_backlight_device, + subsystem, + name, + } => match Device::open(session_backlight_device, subsystem, name) { + Ok(device) => self.devices.push(device), + Err(error) => { + let (code, message) = match error { + BrightnessError::InvalidArgs(message) => { + (session_backlight_v1::ErrorCode::InvalidArgs, message) + } + BrightnessError::Failed(message) => { + (session_backlight_v1::ErrorCode::Io, message) + } + }; + object.send_error(code, message); + } + }, + _ => {} + } + } +} + +impl hyprwire::Dispatch for SessiondBacklight { + fn event( + &mut self, + object: &session_backlight_device::SessionBacklightDevice, + event: ::Event<'_>, + ) { + match event { + session_backlight_device::Event::SetBrightness { brightness } => { + let client = object.client(); + let creds = client.creds(); + + let Some(device) = self + .devices + .iter_mut() + .find(|device| device.object == *object) + else { + return; + }; + + log::debug!( + "SetBrightness called: uid={}, pid={}, subsystem={}, name={}, brightness={brightness}", + creds.uid.as_raw(), + creds.pid.as_raw_pid(), + device.subsystem, + device.name, + ); + + if !self.access.has_active_local_session(creds.uid.as_raw()) && !creds.uid.is_root() + { + object.send_error( + session_backlight_v1::ErrorCode::PermissionDenied, + format!( + "caller uid {} has no active local session", + creds.uid.as_raw() + ), + ); + return; + } + + match device.set(brightness) { + Ok(()) => object.send_done(), + Err(message) => { + object.send_error(session_backlight_v1::ErrorCode::Io, message.to_string()); + } + } + } + session_backlight_device::Event::Destroy => { + self.devices.retain(|device| device.object != *object); + } + _ => {} + } + } +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let log_level = match env::var("LOG_LEVEL") { + Ok(log) if log == "trace" => log::LevelFilter::Trace, + Ok(log) if log == "debug" => log::LevelFilter::Debug, + Ok(log) if log == "warn" => log::LevelFilter::Warn, + Ok(log) if log == "error" => log::LevelFilter::Error, + Ok(log) if log == "off" => log::LevelFilter::Off, + _ => log::LevelFilter::Info, + }; + + match cli.log_target { + LogTarget::Stderr => { + env_logger::Builder::new() + .filter(Some("daemon"), log_level) + .init(); + } + LogTarget::Syslog => { + syslog::init( + syslog::Facility::LOG_DAEMON, + log_level, + Some("sessiond-backlight"), + )?; + } + } + + let mut event_loop = calloop::EventLoop::try_new()?; + + let mut sessiond_backlight = SessiondBacklight { + session_core_manager: None, + sessions: Vec::new(), + access: SessionAccessState::new(), + devices: Vec::new(), + }; + + let socket_path = path::Path::new("/run/sessiond-backlight.sock"); + let mut server = server::Server::bind(&socket_path)?; + fs::set_permissions(socket_path, fs::Permissions::from_mode(0o666))?; + + server.add_implementation::( + &mut sessiond_backlight, + 1, + ); + + let mut client = client::Client::connect("/run/sessiond/sessiond.sock")?; + let eq = client.new_event_queue(); + + client.add_implementation::(); + + eq.wait_for_handshake(&mut sessiond_backlight)?; + + sessiond_backlight.session_core_manager = + Some(client.bind::( + &eq, + &mut sessiond_backlight, + 1, + )?); + + let fd_wrapper = unsafe { generic::FdWrapper::new(client.extract_loop_fd().as_raw_fd()) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + event_loop + .handle() + .insert_source(source, move |_, _, state| { + _ = eq.dispatch_events(state, false); + Ok(calloop::PostAction::Continue) + })?; + + let fd_wrapper = unsafe { generic::FdWrapper::new(server.extract_loop_fd().as_raw_fd()) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + event_loop + .handle() + .insert_source(source, move |_, _, state| { + _ = server.dispatch_events(state, false); + Ok(calloop::PostAction::Continue) + })?; + + event_loop.run(None, &mut sessiond_backlight, |_| {})?; + + Ok(()) +} diff --git a/sessiond-hooks/.gitignore b/sessiond-hooks/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/sessiond-hooks/.gitignore @@ -0,0 +1 @@ +/target diff --git a/sessiond-hooks/Cargo.lock b/sessiond-hooks/Cargo.lock new file mode 100644 index 0000000..5a2432f --- /dev/null +++ b/sessiond-hooks/Cargo.lock @@ -0,0 +1,776 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.1", + "polling", + "rustix", + "slab", + "tracing", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + +[[package]] +name = "hyprwire" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ee2af8d569f2a6d71130b9643dc534b7255b57f7953260d044d88d9735e707" +dependencies = [ + "hyprwire-core", + "hyprwire-scanner", + "polling", + "rustix", +] + +[[package]] +name = "hyprwire-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a76bc1faef6e638086f116cdfc19e590232d7565f569e01fd9eb217ac86f35f" + +[[package]] +name = "hyprwire-protocols-sessiond" +version = "0.2.0" +dependencies = [ + "hyprwire", + "hyprwire-scanner", +] + +[[package]] +name = "hyprwire-scanner" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b50d1dc744b592f4354885b2c445f7ba4f61e3472d02e23d7b81d73f39dbf15" +dependencies = [ + "hyprwire-core", + "prettyplease", + "proc-macro2", + "quick-xml", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[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.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sessiond-hooks" +version = "0.0.0" +dependencies = [ + "anyhow", + "calloop", + "clap", + "env_logger", + "hyprwire", + "hyprwire-protocols-sessiond", + "log", + "rustix", + "serde", + "syslog", + "toml", + "uzers", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syslog" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019f1500a13379b7d051455df397c75770de6311a7a188a699499502704d9f10" +dependencies = [ + "hostname", + "libc", + "log", + "time", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "toml" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uzers" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8275fb1afee25b4111d2dc8b5c505dbbc4afd0b990cb96deb2d88bff8be18d" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" diff --git a/sessiond-hooks/Cargo.toml b/sessiond-hooks/Cargo.toml new file mode 100644 index 0000000..8b32c1f --- /dev/null +++ b/sessiond-hooks/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sessiond-hooks" +keywords = [] +categories = [] +edition = "2024" +license = "GPL-3.0-only" +repository = "https://tangled.org/r0chd.pl/sessiond" +version = "0.0.0" +publish = false +description = "Launch and supervise per-user session hooks with sessiond" +readme = "README.md" + +[dependencies] +anyhow = "1.0.103" +calloop = "0.14.4" +clap = { version = "4.5.51", features = ["derive"] } +env_logger = "0.11.8" +hyprwire = "0.5.1" +hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0" } +log = { version = "0.4.28", features = ["kv"] } +rustix = "1.1.4" +syslog = "7.0.0" +uzers = "0.12.2" +serde = { version = "1.0.228", features = ["derive", "rc"] } +toml = "1.1.2" +[workspace] diff --git a/sessiond-hooks/LICENSE b/sessiond-hooks/LICENSE new file mode 100644 index 0000000..368976c --- /dev/null +++ b/sessiond-hooks/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2026 Oskar Rochowiak + +This program is free software: you can redistribute it and/or modify +it under the terms of version 3 of the GNU General Public License as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/sessiond-hooks/README.md b/sessiond-hooks/README.md new file mode 100644 index 0000000..73cd5bb --- /dev/null +++ b/sessiond-hooks/README.md @@ -0,0 +1,98 @@ +# sessiond-hooks + +`sessiond-hooks` is a `sessiond` client that launches and supervises a per-user +session hook (`dinit --user`, `runsvdir`, …) started for every unique UID. + +## Installation + +`sessiond-hooks` requires `sessiond` to be installed and enabled first. + +On finix with flakes: + +```nix +{ + inputs.sessiond-hooks.url = "git+https://tangled.org/r0chd.pl/sessiond-hooks"; +} +``` + +```nix +{ inputs, lib, pkgs, ... }: +{ + imports = [ + inputs.sessiond-hooks.finixModules.default + ]; + + services.sessiond-hooks = { + package = inputs.sessiond-hooks.packages.${pkgs.system}.default; + hooks.dinit.settings = { + on_start = { + exec = lib.getExe' pkgs.dinit "dinit"; + args = [ "--user" "--services-dir" "~/.config/dinit.d" ]; + }; + restart = { + on = "Failure"; + delay = 5; + }; + }; + }; +} +``` + +With npins: + +```sh +npins add git --name sessiond-hooks https://tangled.org/r0chd.pl/sessiond-hooks --branch master +``` + +```nix +{ lib, pkgs, ... }: +let + sources = import ./npins; + sessiond-hooks = import sources.sessiond-hooks.outPath { }; +in +{ + imports = [ + sessiond-hooks.finixModules.default + ]; + + services.sessiond-hooks = { + package = sessiond-hooks.packages.sessiond-hooks; + hooks.dinit.settings = { + on_start = { + exec = lib.getExe' pkgs.dinit "dinit"; + args = [ "--user" "--services-dir" "~/.config/dinit.d" ]; + }; + restart = { + on = "Failure"; + delay = 5; + }; + }; + }; +} +``` + +To build it manually: + +```sh +cargo build --release +``` + +The binary is written to `target/release/sessiond-hooks`. + +## Usage + +```sh +sessiond-hooks [--config PATH] [--log-target stderr|syslog] +``` + +Example configuration: + +```toml +[on_start] +exec = "/usr/bin/dinit" +args = ["--user", "--services-dir", "~/.config/dinit.d"] + +[restart] +on = "Failure" +delay = 5 +``` diff --git a/sessiond-hooks/src/config.rs b/sessiond-hooks/src/config.rs new file mode 100644 index 0000000..26c2881 --- /dev/null +++ b/sessiond-hooks/src/config.rs @@ -0,0 +1,92 @@ +use anyhow::Context as _; +use serde::Deserialize; +use std::{fs, io, path}; + +#[derive(Clone, Copy, Default, Deserialize)] +pub enum RestartPolicy { + #[default] + Failure, + Never, + Always, +} + +#[derive(Deserialize)] +pub struct Restart { + pub on: RestartPolicy, + pub delay: u64, +} + +impl Default for Restart { + fn default() -> Self { + Self { + on: RestartPolicy::default(), + delay: 5, + } + } +} + +#[derive(Deserialize)] +pub struct Config { + pub on_start: Command, + pub restart: Restart, +} + +#[derive(Clone, Deserialize)] +pub struct Command { + pub exec: String, + pub args: Vec, +} + +impl Command { + pub fn expand_home(&self, home: &str) -> Self { + fn expand(value: &str, home: &str) -> String { + if value == "~" { + return home.to_owned(); + } + + match value.strip_prefix("~/") { + Some(relative) => format!("{}/{relative}", home.trim_end_matches('/')), + None => value.to_owned(), + } + } + + Self { + exec: expand(&self.exec, home), + args: self.args.iter().map(|arg| expand(arg, home)).collect(), + } + } +} + +impl Config { + pub fn load(path: Option) -> anyhow::Result { + let explicit_path = path.is_some(); + let path = path.unwrap_or_else(|| path::PathBuf::from("/etc/sessiond-hooks.toml")); + + let config = match fs::read_to_string(&path) { + Ok(config) => config, + Err(err) if !explicit_path && err.kind() == io::ErrorKind::NotFound => { + log::info!(path = path.to_string_lossy().as_ref(); "config file not found, using defaults"); + anyhow::bail!(""); + } + Err(err) => { + log::error!( + path = path.to_string_lossy().as_ref(), + error = err.to_string().as_str(); + "failed to read config file" + ); + return Err(err) + .with_context(|| format!("failed to read config file {}", path.display())); + } + }; + + toml::from_str::(&config) + .inspect_err(|err| { + log::error!( + path = path.to_string_lossy().as_ref(), + error = err.to_string().as_str(); + "failed to parse config file" + ); + }) + .with_context(|| format!("failed to parse config file {}", path.display())) + } +} diff --git a/sessiond-hooks/src/logging.rs b/sessiond-hooks/src/logging.rs new file mode 100644 index 0000000..5561b43 --- /dev/null +++ b/sessiond-hooks/src/logging.rs @@ -0,0 +1,111 @@ +use log::kv; +use std::fmt::Write as _; +use std::io::Write as _; +use std::sync; + +type SyslogLogger = syslog::Logger; + +/// A syslog logger which preserves the key-values supplied to `log!` macros. +/// +/// `syslog::BasicLogger` currently formats only `Record::args()`, silently +/// dropping `Record::key_values()`. Keep the traditional RFC 3164 transport +/// and put the fields in the message body so existing syslog deployments keep +/// working. +pub struct KvLogger { + logger: sync::Arc>, +} + +impl KvLogger { + fn new(logger: SyslogLogger) -> Self { + Self { + logger: sync::Arc::new(sync::Mutex::new(logger)), + } + } +} + +impl log::Log for KvLogger { + fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { + metadata.level() <= log::max_level() && metadata.level() <= log::STATIC_MAX_LEVEL + } + + fn log(&self, record: &log::Record<'_>) { + if !self.enabled(record.metadata()) { + return; + } + + let message = format_message(record); + let Ok(mut logger) = self.logger.lock() else { + return; + }; + + let result = match record.level() { + log::Level::Error => logger.err(message), + log::Level::Warn => logger.warning(message), + log::Level::Info => logger.info(message), + log::Level::Debug | log::Level::Trace => logger.debug(message), + }; + let _ = result; + } + + fn flush(&self) { + if let Ok(mut logger) = self.logger.lock() { + let _ = logger.backend.flush(); + } + } +} + +struct FieldVisitor { + fields: String, +} + +impl<'kvs> kv::VisitSource<'kvs> for FieldVisitor { + fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> { + if !self.fields.is_empty() { + self.fields.push(' '); + } + let _ = write!(self.fields, "{}={}", key.as_str(), value); + Ok(()) + } +} + +pub(crate) fn format_message(record: &log::Record<'_>) -> String { + let mut message = record.args().to_string(); + let mut visitor = FieldVisitor { + fields: String::new(), + }; + + if let Err(error) = record.key_values().visit(&mut visitor) { + if !visitor.fields.is_empty() { + message.push(' '); + } + let _ = write!(message, "kv_error={error}"); + } + + if !visitor.fields.is_empty() { + message.push(' '); + message.push_str(&visitor.fields); + } + message +} + +pub fn init_syslog(facility: syslog::Facility, log_level: log::LevelFilter) -> syslog::Result<()> { + let formatter = syslog::Formatter3164 { + facility, + hostname: None, + process: "sessiond-hooks".to_owned(), + pid: std::process::id(), + }; + + let logger = if let Ok(logger) = syslog::unix(formatter.clone()) { + logger + } else if let Ok(logger) = syslog::tcp(formatter.clone(), ("127.0.0.1", 601)) { + logger + } else { + syslog::udp(formatter, ("0.0.0.0", 0), ("127.0.0.1", 514))? + }; + + log::set_boxed_logger(Box::new(KvLogger::new(logger))) + .map_err(|error| syslog::Error::Initialization(Box::new(error)))?; + log::set_max_level(log_level); + Ok(()) +} diff --git a/sessiond-hooks/src/main.rs b/sessiond-hooks/src/main.rs new file mode 100644 index 0000000..4bea48b --- /dev/null +++ b/sessiond-hooks/src/main.rs @@ -0,0 +1,614 @@ +mod config; +mod logging; + +use anyhow::Context; +use calloop::{generic, timer}; +use clap::{Parser, ValueEnum}; +use hyprwire::client; +use hyprwire_protocols_sessiond::client::session_core::v1::session_core_v1; +use hyprwire_protocols_sessiond::client::session_core::v1::session_core_v1::{ + session_core_manager, session_core_user, +}; +use hyprwire_protocols_sessiond::client::session_management::v2::session_management_v2; +use hyprwire_protocols_sessiond::client::session_management::v2::session_management_v2::{ + session_management_session, session_manager, +}; +use std::collections::BTreeMap; +use std::os::fd::AsRawFd; +use std::os::unix::process::CommandExt; +use std::{env, ops, path, process, time}; +use uzers::os::unix::UserExt; + +#[derive(Parser)] +#[command(version, about = "Launch and supervise a per-user session hook")] +struct Cli { + #[arg( + long, + value_name = "PATH", + help = "Path to the TOML configuration file" + )] + config: Option, + #[arg(long, value_enum, default_value_t = LogTarget::Stderr)] + log_target: LogTarget, +} + +#[derive(Clone, Copy, ValueEnum)] +enum LogTarget { + Stderr, + Syslog, +} + +struct PendingManagedSession { + id: u32, + uid: u32, + object: session_management_session::SessionManagementSession, + envs: BTreeMap, +} + +struct ManagedSession { + id: u32, + uid: u32, + _object: session_management_session::SessionManagementSession, + envs: BTreeMap, + hook: Option, +} + +struct User { + uid: u32, + state: session_core_v1::UserState, + state_generation: u64, + object: session_core_user::SessionCoreUser, +} + +impl ManagedSession { + fn run_hook( + &mut self, + config: &config::Config, + loop_handle: calloop::LoopHandle, + ) { + let Some(user) = uzers::get_user_by_uid(self.uid) else { + log::error!(uid = self.uid; "cannot start hook for unknown uid"); + return; + }; + let command = config + .on_start + .expand_home(user.home_dir().to_string_lossy().as_ref()); + let mut cmd = process::Command::new("/bin/sh"); + cmd.arg("-c") + .arg( + r#" + [ -r /etc/profile ] && . /etc/profile + + exec env "$@" + "#, + ) + .arg("sessiond-hook"); + for (key, value) in self.envs.iter() { + cmd.arg(format!("{key}={value}")); + } + cmd.arg(&command.exec); + for arg in &command.args { + cmd.arg(arg); + } + cmd.uid(self.uid).gid(user.primary_group_id()); + for (key, value) in self.envs.iter() { + cmd.env(key, value); + } + + log::info!( + exec:? = command.exec, + args:? = command.args, + session_id = self.id, + uid = self.uid, + env_count = self.envs.len(); + "spawning hook" + ); + let mut child = match cmd.spawn() { + Ok(child) => child, + Err(err) => { + log::error!( + exec:? = command.exec, + uid = self.uid, + error:% = err; + "failed to spawn hook" + ); + return; + } + }; + log::info!(exec:? = command.exec, pid = child.id(), + session_id = self.id, uid = self.uid; "spawned hook"); + + let Some(pid) = rustix::process::Pid::from_raw(child.id() as i32) else { + log::error!(pid = child.id(); "cannot supervise hook with invalid pid"); + let _ = child.kill(); + let _ = child.wait(); + return; + }; + let pidfd = match rustix::process::pidfd_open(pid, rustix::process::PidfdFlags::empty()) { + Ok(pidfd) => pidfd, + Err(err) => { + log::error!( + pid = child.id(), + error:% = err; + "cannot open pidfd for hook" + ); + let _ = child.kill(); + let _ = child.wait(); + return; + } + }; + + self.hook = Some(child); + + let fd_wrapper = unsafe { generic::FdWrapper::new(pidfd) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + + let session_id = self.id; + if let Err(err) = loop_handle.insert_source(source, move |_, _, state| { + let Some(session) = state + .managed_sessions + .iter_mut() + .find(|session| session.id == session_id) + else { + log::debug!( + session_id; + "discarding child-exit event for removed session" + ); + return Ok(calloop::PostAction::Remove); + }; + + if let Some(child) = &mut session.hook + && let Some(status) = child.try_wait()? + { + match (state.config.restart.on, status.success()) { + (config::RestartPolicy::Never, _) | (config::RestartPolicy::Failure, true) => {} + (config::RestartPolicy::Always, _) + | (config::RestartPolicy::Failure, false) => { + let restart_delay = state.config.restart.delay; + let restart = + timer::Timer::from_duration(time::Duration::from_secs(restart_delay)); + if let Err(error) = + state + .loop_handle + .insert_source(restart, move |_, _, state| { + if let Some(session) = state + .managed_sessions + .iter_mut() + .find(|session| session.id == session_id) + { + log::info!(session_id = session.id, + uid = session.uid, + delay_seconds = restart_delay; + "restarting hook" + ); + session.run_hook(&state.config, state.loop_handle.clone()); + } + + timer::TimeoutAction::Drop + }) + { + log::error!( + session_id, + error:% = error; + "failed to schedule restart" + ); + } + } + } + log::info!( + session_id, + status:% = status; + "hook exited" + ); + + session.hook = None; + + return Ok(calloop::PostAction::Remove); + } + + Ok(calloop::PostAction::Continue) + }) { + log::error!( + session_id, + error:% = err; + "failed to supervise hook" + ); + if let Some(child) = &mut self.hook { + let _ = child.kill(); + let _ = child.wait(); + } + self.hook = None; + } + } +} + +fn normal_uid_range(login_defs: &str) -> anyhow::Result> { + let mut uid_min = None; + let mut uid_max = None; + + for line in login_defs.lines() { + let mut fields = line.split_whitespace(); + let Some(name) = fields.next() else { + continue; + }; + let target = match name { + "UID_MIN" => &mut uid_min, + "UID_MAX" => &mut uid_max, + _ => continue, + }; + let value = fields + .next() + .with_context(|| format!("{name} has no value in /etc/login.defs"))?; + *target = Some( + value + .parse::() + .with_context(|| format!("invalid {name} value in /etc/login.defs"))?, + ); + } + + let uid_min = uid_min.context("UID_MIN is missing from /etc/login.defs")?; + let uid_max = uid_max.context("UID_MAX is missing from /etc/login.defs")?; + anyhow::ensure!( + uid_min <= uid_max, + "UID_MIN exceeds UID_MAX in /etc/login.defs" + ); + + Ok(uid_min..=uid_max) +} + +fn is_hook_user(uid: u32, normal_uids: &ops::RangeInclusive) -> bool { + uid == 0 || normal_uids.contains(&uid) +} + +struct SessiondHooks { + users: Vec, + pending_managed_sessions: Vec, + managed_sessions: Vec, + config: config::Config, + normal_uids: ops::RangeInclusive, + session_manager: Option, + loop_handle: calloop::LoopHandle<'static, Self>, +} + +impl SessiondHooks { + fn create_managed_session(&mut self, uid: u32) { + if self + .managed_sessions + .iter() + .any(|session| session.uid == uid) + || self + .pending_managed_sessions + .iter() + .any(|session| session.uid == uid) + { + log::info!(uid; "uid already has a managed hook session"); + return; + } + + let Some(session_manager) = self.session_manager.as_ref() else { + log::error!(uid; "cannot create managed session: manager is unavailable"); + return; + }; + + let Some(session_object) = session_manager.send_create_session::(uid) else { + log::error!(uid; "failed to create managed session: protocol returned no object"); + return; + }; + + let pending_session = PendingManagedSession { + id: 0, + uid, + object: session_object, + envs: BTreeMap::new(), + }; + + pending_session + .object + .send_class(session_management_v2::SessionClass::Manager); + pending_session.object.send_done(); + + self.pending_managed_sessions.push(pending_session); + } + + fn finish_teardown(&mut self, uid: u32) { + if let Some(index) = self + .managed_sessions + .iter() + .position(|session| session.uid == uid) + { + let mut session = self.managed_sessions.swap_remove(index); + if let Some(mut child) = session.hook.take() { + let _ = child.kill(); + let _ = child.wait(); + } + } else if let Some(index) = self + .pending_managed_sessions + .iter() + .position(|session| session.uid == uid) + { + drop(self.pending_managed_sessions.swap_remove(index)); + } + } +} + +impl hyprwire::Dispatch for SessiondHooks { + fn event( + &mut self, + object: &session_core_manager::SessionCoreManager, + event: ::Event<'_>, + ) { + match event { + session_core_manager::Event::UserAdded { uid } => { + if !is_hook_user(uid, &self.normal_uids) { + log::debug!(uid; "ignoring non-login user"); + return; + } + + if self.users.iter().any(|user| user.uid == uid) { + log::info!(uid; "uid is already being observed"); + return; + } + + let Some(user_object) = object.send_get_user::(uid) else { + log::error!(uid; "failed to create user observer: protocol returned no object"); + return; + }; + + self.users.push(User { + uid, + state: session_core_v1::UserState::Online, + state_generation: 0, + object: user_object, + }); + + self.create_managed_session(uid); + } + session_core_manager::Event::Done => {} + session_core_manager::Event::Error { code, message } => { + log::error!(code:? = code, message:% = message; "session_core_manager error") + } + _ => {} + } + } +} + +impl hyprwire::Dispatch for SessiondHooks { + fn event( + &mut self, + object: &session_core_user::SessionCoreUser, + event: ::Event<'_>, + ) { + if let session_core_user::Event::State { state } = event { + let Some(user) = self.users.iter_mut().find(|user| user.object == *object) else { + return; + }; + + user.state = state; + user.state_generation = user.state_generation.wrapping_add(1); + let uid = user.uid; + let state_generation = user.state_generation; + + if state == session_core_v1::UserState::Offline { + if let Err(error) = self.loop_handle.insert_source( + timer::Timer::from_duration(time::Duration::from_secs(5)), + { + let object = object.clone(); + move |_, _, state| { + let Some(user) = state.users.iter().find(|user| user.object == object) + else { + return timer::TimeoutAction::Drop; + }; + + if user.state != session_core_v1::UserState::Offline + || user.state_generation != state_generation + { + return timer::TimeoutAction::Drop; + } + + state.finish_teardown(uid); + timer::TimeoutAction::Drop + } + }, + ) { + log::error!(uid, error:% = error; "failed to schedule offline teardown"); + } + } else if matches!( + state, + session_core_v1::UserState::Lingering + | session_core_v1::UserState::Online + | session_core_v1::UserState::Active + ) { + self.create_managed_session(uid); + } + } + } +} + +impl hyprwire::Dispatch for SessiondHooks { + fn event( + &mut self, + object: &session_management_session::SessionManagementSession, + event: ::Event< + '_, + >, + ) { + match event { + session_management_session::Event::Env { name, value } => { + let Some(session) = self + .pending_managed_sessions + .iter_mut() + .find(|session| session.object == *object) + else { + return; + }; + + log::debug!(name:? = name, session_id = session.id, uid = session.uid; + "received environment variable"); + session.envs.insert(name, value); + } + session_management_session::Event::SessionId { session_id } => { + let Some(session) = self + .pending_managed_sessions + .iter_mut() + .find(|session| session.object == *object) + else { + return; + }; + + session.id = session_id; + log::info!(session_id, uid = session.uid; "assigned management session object"); + } + session_management_session::Event::Done => { + let Some(session) = self + .pending_managed_sessions + .iter() + .position(|session| session.object == *object) + else { + return; + }; + let session = self.pending_managed_sessions.swap_remove(session); + + let Some(user) = uzers::get_user_by_uid(session.uid) else { + log::error!(uid = session.uid; "cannot start hook for unknown uid"); + return; + }; + + let mut managed_session = ManagedSession { + id: session.id, + uid: session.uid, + _object: session.object, + envs: session.envs, + hook: None, + }; + managed_session.envs.extend(BTreeMap::from([ + ( + "HOME".to_string(), + user.home_dir().to_string_lossy().into_owned(), + ), + ( + "USER".to_string(), + user.name().to_string_lossy().into_owned(), + ), + ( + "LOGNAME".to_string(), + user.name().to_string_lossy().into_owned(), + ), + ( + "SHELL".to_string(), + user.shell().to_string_lossy().into_owned(), + ), + ])); + managed_session.run_hook(&self.config, self.loop_handle.clone()); + + self.managed_sessions.push(managed_session); + } + session_management_session::Event::Error { code, message } => { + if let Some(index) = self + .pending_managed_sessions + .iter() + .position(|session| session.object == *object) + { + let session = self.pending_managed_sessions.swap_remove(index); + log::error!( + uid = session.uid, + session_id = session.id, + code:? = code, + message:% = message; + "managed session creation failed" + ); + } + } + _ => {} + } + } +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let config = match config::Config::load(cli.config) { + Ok(config) => config, + Err(err) => { + anyhow::bail!(err); + } + }; + + let normal_uids = normal_uid_range( + &std::fs::read_to_string("/etc/login.defs").context("reading /etc/login.defs")?, + )?; + let log_level = match env::var("LOG_LEVEL") { + Ok(log) if log == "trace" => log::LevelFilter::Trace, + Ok(log) if log == "debug" => log::LevelFilter::Debug, + Ok(log) if log == "warn" => log::LevelFilter::Warn, + Ok(log) if log == "error" => log::LevelFilter::Error, + Ok(log) if log == "off" => log::LevelFilter::Off, + _ => log::LevelFilter::Info, + }; + + match cli.log_target { + LogTarget::Stderr => { + env_logger::Builder::new() + .filter(Some("sessiond_hooks"), log_level) + .init(); + } + LogTarget::Syslog => { + logging::init_syslog(syslog::Facility::LOG_DAEMON, log_level)?; + } + } + + let mut event_loop = calloop::EventLoop::try_new()?; + let mut socket = client::Client::connect("/run/sessiond/sessiond.sock")?; + let eq = socket.new_event_queue(); + + let mut sessiond_hooks = SessiondHooks { + loop_handle: event_loop.handle(), + users: Vec::new(), + managed_sessions: Vec::new(), + pending_managed_sessions: Vec::new(), + normal_uids, + session_manager: None, + config, + }; + + socket.add_implementation::(); + socket.add_implementation::(); + + eq.wait_for_handshake(&mut sessiond_hooks)?; + + sessiond_hooks.session_manager = + Some(socket.bind::(&eq, &mut sessiond_hooks, 1)?); + let _session_core_manager = + socket.bind::(&eq, &mut sessiond_hooks, 2)?; + + eq.roundtrip(&mut sessiond_hooks)?; + + let fd_wrapper = unsafe { generic::FdWrapper::new(socket.extract_loop_fd().as_raw_fd()) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + event_loop + .handle() + .insert_source(source, move |_, _, state| { + eq.dispatch_events(state, false) + .map_err(std::io::Error::other)?; + Ok(calloop::PostAction::Continue) + })?; + + event_loop.run(None, &mut sessiond_hooks, |_| {})?; + + Ok(()) +} + +hyprwire::delegate_noop!(SessiondHooks: session_manager::SessionManager); diff --git a/sessiond-uaccess/.gitignore b/sessiond-uaccess/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/sessiond-uaccess/.gitignore @@ -0,0 +1 @@ +/target diff --git a/sessiond-uaccess/Cargo.lock b/sessiond-uaccess/Cargo.lock new file mode 100644 index 0000000..db5fd92 --- /dev/null +++ b/sessiond-uaccess/Cargo.lock @@ -0,0 +1,1503 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "acl-sys" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc079f9bdd3124fd18df23c67f7e0f79d24751ae151dcffd095fcade07a3eb2" +dependencies = [ + "libc", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bevy_ecs" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a350b2e4c2e47f7903d688affb8a723b31bca63af9789f1da88703f1ba04e354" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_tasks", + "bevy_utils", + "bitflags 2.13.1", + "bumpalo", + "concurrent-queue", + "derive_more", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "slotmap", + "smallvec", + "thiserror", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macro_logic" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a10fef45a3e7dad717ba03e8b5f7ecdf3b3fd1cd1c5e425793525938e90d5f" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3916d712264b6f3ec9c964ba10719450ea371e537e202a2ffcfa9f6a5e68169d" +dependencies = [ + "bevy_ecs_macro_logic", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7164fe229422295bba15f1885048f34f4660db069aacb895208f996b0c7784fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "toml_edit", +] + +[[package]] +name = "bevy_platform" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd610a417e7b4a3b9602cf11264d16cbbff8eabe689c5fed8be4e84c6a2680a3" +dependencies = [ + "foldhash", + "futures-channel", + "hashbrown 0.16.1", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "windows-sys 0.61.2", +] + +[[package]] +name = "bevy_ptr" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1974de4b140f102fded747229ff4a61921acec07f02f0d1dcdadfe9ce177d0ff" + +[[package]] +name = "bevy_tasks" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f787d4ba7237b64b30bd2d690a8cbae1ba775e0eec326711c446654cb720e7c" +dependencies = [ + "async-task", + "atomic-waker", + "bevy_platform", + "crossbeam-queue", + "derive_more", + "futures-lite", + "heapless", + "web-task", +] + +[[package]] +name = "bevy_utils" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de51fa7ea2e95d4c2d1278c5629285d59eec951cc60c992964c03f30910ef5a1" +dependencies = [ + "bevy_platform", + "disqualified", + "indexmap", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.1", + "nix", + "polling", + "rustix", + "slab", + "tracing", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "equivalent", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + +[[package]] +name = "hyprwire" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ee2af8d569f2a6d71130b9643dc534b7255b57f7953260d044d88d9735e707" +dependencies = [ + "hyprwire-core", + "hyprwire-scanner", + "polling", + "rustix", +] + +[[package]] +name = "hyprwire-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a76bc1faef6e638086f116cdfc19e590232d7565f569e01fd9eb217ac86f35f" + +[[package]] +name = "hyprwire-protocols-sessiond" +version = "0.2.0" +dependencies = [ + "hyprwire", + "hyprwire-scanner", +] + +[[package]] +name = "hyprwire-scanner" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b50d1dc744b592f4354885b2c445f7ba4f61e3472d02e23d7b81d73f39dbf15" +dependencies = [ + "hyprwire-core", + "prettyplease", + "proc-macro2", + "quick-xml", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lua-src" +version = "550.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c110c2fa33f34e0de05448e1f3eb2e0631e7a69e2d8ae1586cffc9fc9f9949" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.7.2+b925b3e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920cf654b23d217c550ceea57c32cd2a413ea27b6d47ed77b5ee0cf655adefa6" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mlua" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad72ffa037cf5970c9860674f32f703fda25d86cf217475fe7a79c5f9961bcaa" +dependencies = [ + "bstr", + "either", + "libc", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92136787b906d4e55cfe96cd6c62e010bb1a56889d0d6cf83eb016dbad07576b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "posix-acl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9928b761309e4a4ca4f2d90eb03029142e3f7164107e18db4d46516515b87441" +dependencies = [ + "acl-sys", + "libc", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[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.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "sessiond-uaccess" +version = "0.0.0" +dependencies = [ + "anyhow", + "bevy_ecs", + "calloop", + "clap", + "env_logger", + "hyprwire", + "hyprwire-protocols-sessiond", + "log", + "mlua", + "posix-acl", + "syslog", + "udev", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syslog" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019f1500a13379b7d051455df397c75770de6311a7a188a699499502704d9f10" +dependencies = [ + "hostname", + "libc", + "log", + "time", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "udev" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" +dependencies = [ + "io-lifetimes", + "libc", + "libudev-sys", + "pkg-config", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-task" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdc136a53ccd64a1211f107ccc34404769fbcc0f165f1afa065f5d88ab93538" +dependencies = [ + "async-task", + "cfg-if", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] diff --git a/sessiond-uaccess/Cargo.toml b/sessiond-uaccess/Cargo.toml new file mode 100644 index 0000000..a66b38a --- /dev/null +++ b/sessiond-uaccess/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sessiond-uaccess" +keywords = [] +categories = [] +edition = "2024" +license = "GPL-3.0-only" +repository = "https://tangled.org/r0chd.pl/sessiond" +version = "0.0.0" +publish = false +description = "Dynamic device access manager" +readme = "README.md" + +[dependencies] +anyhow = "1.0.103" +calloop = { version = "0.14.4", features = ["signals"] } +env_logger = { version = "0.11.11", features = ["kv"] } +hyprwire = "0.5.1" +hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0" } +udev = "0.9.3" +mlua = { version = "0.12", features = ["lua54", "vendored"] } +clap = { version = "4.6.2", features = ["derive"] } +bevy_ecs = { version = "0.19.0", default-features = false } +log = { version = "0.4.33", features = ["kv"] } +posix-acl = "1.2.0" +syslog = "7.0.0" +[workspace] diff --git a/sessiond-uaccess/LICENSE b/sessiond-uaccess/LICENSE new file mode 100644 index 0000000..f20c1b4 --- /dev/null +++ b/sessiond-uaccess/LICENSE @@ -0,0 +1,14 @@ +Copyright (C) 2026 Oskar Rochowiak + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/sessiond-uaccess/README.md b/sessiond-uaccess/README.md new file mode 100644 index 0000000..afaded3 --- /dev/null +++ b/sessiond-uaccess/README.md @@ -0,0 +1,98 @@ +# sessiond-uaccess + +`sessiond-uaccess` is a `sessiond` client that grants device-node access to +active local sessions by writing POSIX ACL entries. + +## Installation + +`sessiond-uaccess` requires `sessiond` to be installed and enabled first. + +On finix with flakes: + +```nix +{ + inputs.sessiond-uaccess.url = "git+https://tangled.org/r0chd.pl/sessiond-uaccess"; +} +``` + +```nix +{ inputs, pkgs, ... }: +{ + imports = [ + inputs.sessiond-uaccess.finixModules.default + ]; + + services.sessiond-uaccess = { + enable = true; + package = inputs.sessiond-uaccess.packages.${pkgs.system}.default; + }; +} +``` + +With npins: + +```sh +npins add git --name sessiond-uaccess https://tangled.org/r0chd.pl/sessiond-uaccess --branch master +``` + +```nix +{ pkgs, ... }: +let + sources = import ./npins; + sessiond-uaccess = import sources.sessiond-uaccess.outPath { }; +in +{ + imports = [ + sessiond-uaccess.finixModules.default + ]; + + services.sessiond-uaccess = { + enable = true; + package = sessiond-uaccess.packages.sessiond-uaccess; + }; +} +``` + +To build it manually: + +```sh +cargo build --release +``` + +The binary is written to `target/release/sessiond-uaccess`. + +## Usage + +```sh +sessiond-uaccess --rules-dirs PATH [--rules-dirs PATH...] +``` + +`sessiond-uaccess` connects to `/run/sessiond/sessiond.sock`, loads every `*.lua` file +from each existing rules directory, scans existing udev devices, and then keeps +listening for new udev devices and session changes. + +### mdevd + +`sessiond-uaccess` can be used with `mdevd` and `libudev-zero`. Start `mdevd` +with `-O 4` so it rebroadcasts processed uevents to the netlink group used by +the libudev monitor: + +```sh +mdevd -O 4 +``` + +## Rules + +Rules are Lua files that call `device` with an access mode and match function: + +```lua +device { + access = "rw", + match = function(dev) + return dev.subsystem == "sound" + end, +} +``` + +The default rules in `rules/70-uaccess.lua` cover sound devices, cameras, DRI +devices, render and accelerator nodes, AMD KFD, KVM, rfkill, and optical drives. diff --git a/sessiond-uaccess/lua/sessiond-uaccess.lua b/sessiond-uaccess/lua/sessiond-uaccess.lua new file mode 100644 index 0000000..b3942d0 --- /dev/null +++ b/sessiond-uaccess/lua/sessiond-uaccess.lua @@ -0,0 +1,31 @@ +---@meta sessiond-uaccess +---@class Device +---@field subsystem string? +---@field sysname string +---@field syspath string +---@field devnode string? +local Device = {} + +---@param name string +---@return string? +function Device:attr(name) end + +---@param name string +---@return string? +function Device:property(name) end + +---@param subsystem? string +---@return Device? +function Device:parent(subsystem) end + +---@return Device[] +function Device:parents() end + +---@class Rule +---@field access '"r"'|'"w"'|'"rw"' +---@field match fun(dev: Device): boolean +---@param rule Rule +function device(rule) end + +---@type integer +api_version = 1 diff --git a/sessiond-uaccess/rules/70-uaccess.lua b/sessiond-uaccess/rules/70-uaccess.lua new file mode 100644 index 0000000..80b22ff --- /dev/null +++ b/sessiond-uaccess/rules/70-uaccess.lua @@ -0,0 +1,90 @@ +-- Sound devices +device { + access = "rw", + match = function(dev) + return dev.subsystem == "sound" + end, +} +-- Webcams, frame grabber, TV cards +device { + access = "rw", + match = function(dev) + return dev.subsystem == "video4linux" + or dev.subsystem == "dvb" + or dev.subsystem == "media" + or dev.sysname == "udmabuf" + end, +} + +-- DRI video devices +device { + access = "rw", + match = function(dev) + return dev.subsystem == "drm" + and dev.sysname:match("^card.*$") ~= nil + end, +} + +-- DRI render nodes, DRI accel nodes, AMD KFD nodes, KVM +device { + access = "rw", + match = function(dev) + return ( + dev.subsystem == "drm" + and dev.sysname:match("^renderD.*$") ~= nil + ) or ( + dev.subsystem == "accel" + and dev.sysname:match("^accel.*$") ~= nil + ) or ( + dev.subsystem == "kfd" + and dev.sysname == "kfd" + ) or ( + dev.subsystem == "misc" + and dev.sysname == "kvm" + ) + end, +} + +-- rfkill / radio killswitches +device { + access = "rw", + match = function(dev) + return dev.sysname == "rfkill" + and dev.subsystem == "misc" + end, +} + +-- Optical drives +device { + access = "rw", + match = function(dev) + if dev.subsystem ~= "scsi_generic" then + return false + end + + local scsi = dev:parent("scsi") + if not scsi then + return false + end + + local ty = scsi:attr("type") + return ty == "4" or ty == "5" + end, +} + +-- FIDO security keys +device { + access = "rw", + match = function(dev) + if dev.subsystem ~= "hidraw" then + return false + end + for _, parent in ipairs(dev:parents()) do + local product = parent:property("PRODUCT") + if product ~= nil and product:match("^1050/") ~= nil then + return true + end + end + return false + end, +} diff --git a/sessiond-uaccess/src/config.rs b/sessiond-uaccess/src/config.rs new file mode 100644 index 0000000..55130cc --- /dev/null +++ b/sessiond-uaccess/src/config.rs @@ -0,0 +1,184 @@ +use std::{cell, fs, path, rc}; + +pub struct Config { + pub lua: mlua::Lua, + pub rules: rc::Rc>>, +} + +impl Config { + pub fn load(rule_dirs: &[path::PathBuf]) -> anyhow::Result { + let lua = mlua::Lua::new_with( + mlua::StdLib::TABLE | mlua::StdLib::STRING | mlua::StdLib::MATH | mlua::StdLib::UTF8, + mlua::LuaOptions::default(), + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + lua.set_memory_limit(512 * 1024) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + let globals = lua.globals(); + globals + .set("api_version", 1) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + let rules = rc::Rc::new(cell::RefCell::new(Vec::new())); + + let device_fn = lua + .create_function({ + let rules = rc::Rc::clone(&rules); + move |lua, rule: mlua::Table| { + let access = match rule.get::("access")?.as_str() { + "r" => AccessMode::Read, + "w" => AccessMode::Write, + "rw" => AccessMode::ReadWrite, + other => { + return Err(mlua::Error::RuntimeError(format!( + "invalid access mode: {other}" + ))); + } + }; + + let match_fn = + lua.create_registry_value(rule.get::("match")?)?; + + rules.borrow_mut().push(LuaRule { access, match_fn }); + + Ok(()) + } + }) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + lua.globals() + .set("device", device_fn) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + for dir in rule_dirs.iter().filter(|dir| dir.exists()) { + let mut rule_paths = fs::read_dir(dir)? + .map(|rule| rule.map(|rule| rule.path())) + .collect::, _>>()?; + rule_paths.retain(|path| path.extension().is_some_and(|extension| extension == "lua")); + rule_paths.sort(); + + for path in rule_paths { + let source = fs::read_to_string(&path)?; + + lua.load(&source) + .set_name(path.to_string_lossy().as_ref()) + .exec() + .map_err(|e| anyhow::anyhow!("{e}"))?; + + log::debug!(path:% = path.display(); "loaded Lua rule file"); + } + } + + Ok(Self { lua, rules }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AccessMode { + Read, + Write, + ReadWrite, +} + +impl AccessMode { + pub const fn as_str(self) -> &'static str { + match self { + Self::Read => "r", + Self::Write => "w", + Self::ReadWrite => "rw", + } + } +} + +pub struct LuaRule { + pub access: AccessMode, + pub match_fn: mlua::RegistryKey, +} + +#[derive(Clone)] +pub struct LuaDevice { + pub device: udev::Device, +} + +impl LuaDevice { + pub const fn new(device: udev::Device) -> Self { + Self { device } + } +} + +impl mlua::UserData for LuaDevice { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("subsystem", |_, this| { + Ok(this + .device + .subsystem() + .map(|subsystem| subsystem.to_string_lossy().into_owned())) + }); + + fields.add_field_method_get("sysname", |_, this| { + Ok(this.device.sysname().to_string_lossy().into_owned()) + }); + + fields.add_field_method_get("syspath", |_, this| { + Ok(this.device.syspath().to_string_lossy().into_owned()) + }); + + fields.add_field_method_get("devnode", |_, this| { + Ok(this + .device + .devnode() + .map(|devnode| devnode.to_string_lossy().into_owned())) + }); + } + + fn add_methods>(methods: &mut M) { + methods.add_method("attr", |_, this, name: String| { + Ok(this + .device + .attribute_value(&name) + .map(|value| value.to_string_lossy().into_owned())) + }); + + methods.add_method("property", |_, this, name: String| { + Ok(this + .device + .property_value(&name) + .map(|value| value.to_string_lossy().into_owned())) + }); + + methods.add_method("parent", |lua, this, subsystem: Option| { + let mut parent = this.device.parent(); + + while let Some(device) = parent { + let matches = subsystem.as_deref().is_none_or(|subsystem| { + device + .subsystem() + .is_some_and(|device_subsystem| device_subsystem == subsystem) + }); + + if matches { + return lua.create_userdata(Self::new(device)).map(Some); + } + + parent = device.parent(); + } + + Ok(None::) + }); + + methods.add_method("parents", |lua, this, ()| { + let parents = lua.create_table()?; + let mut parent = this.device.parent(); + let mut index = 1; + + while let Some(device) = parent { + parents.set(index, Self::new(device.clone()))?; + index += 1; + parent = device.parent(); + } + + Ok(parents) + }); + } +} diff --git a/sessiond-uaccess/src/device.rs b/sessiond-uaccess/src/device.rs new file mode 100644 index 0000000..8234df8 --- /dev/null +++ b/sessiond-uaccess/src/device.rs @@ -0,0 +1,10 @@ +use crate::ecs; +use bevy_ecs::prelude::*; +use std::path; + +pub fn entity_by_syspath(world: &mut World, syspath: &path::Path) -> Option { + let mut devices = world.query_filtered::<(Entity, &ecs::Syspath), With>(); + devices + .iter(world) + .find_map(|(entity, device_syspath)| (device_syspath.0 == syspath).then_some(entity)) +} diff --git a/sessiond-uaccess/src/ecs.rs b/sessiond-uaccess/src/ecs.rs new file mode 100644 index 0000000..fe5342d --- /dev/null +++ b/sessiond-uaccess/src/ecs.rs @@ -0,0 +1,59 @@ +use crate::session_core_v1::session_core_session; +use crate::{config, session_core_v1}; +use bevy_ecs::prelude::*; +use std::{path, sync}; + +pub fn insert_session( + world: &mut World, + object: session_core_session::SessionCoreSession, +) -> Entity { + world + .spawn((Session, SessionObject(object), Done(false))) + .id() +} + +#[derive(Component)] +pub struct Syspath(pub sync::Arc); + +#[derive(Component)] +pub struct Devnode(pub path::PathBuf); + +#[derive(Component)] +pub struct Session; + +#[derive(Component)] +pub struct SessionObject(pub session_core_session::SessionCoreSession); + +#[derive(Component)] +pub struct DeviceEntity(pub Entity); + +#[derive(Component)] +pub struct Uid(pub u32); + +#[derive(Component)] +pub struct AccessMode(pub config::AccessMode); + +#[derive(Component)] +pub struct ActiveState(pub session_core_v1::ActiveState); + +#[derive(Component)] +pub struct IsLocal(pub bool); + +#[derive(Component)] +pub struct Done(pub bool); + +#[derive(Component)] +pub struct Device; + +#[derive(Component)] +pub struct Grant; + +#[derive(Message)] +pub struct DeviceRemoved { + pub syspath: sync::Arc, +} + +#[derive(Message)] +pub struct DeviceAdded { + pub syspath: sync::Arc, +} diff --git a/sessiond-uaccess/src/logging.rs b/sessiond-uaccess/src/logging.rs new file mode 100644 index 0000000..9981053 --- /dev/null +++ b/sessiond-uaccess/src/logging.rs @@ -0,0 +1,136 @@ +use log::kv; +use std::fmt::Write as _; +use std::io::Write as _; +use std::sync; + +type SyslogLogger = syslog::Logger; + +/// A syslog logger which preserves the key-values supplied to `log!` macros. +/// +/// `syslog::BasicLogger` currently formats only `Record::args()`, silently +/// dropping `Record::key_values()`. Keep the traditional RFC 3164 transport +/// and put the fields in the message body so existing syslog deployments keep +/// working. +pub struct KvLogger { + logger: sync::Arc>, +} + +impl KvLogger { + fn new(logger: SyslogLogger) -> Self { + Self { + logger: sync::Arc::new(sync::Mutex::new(logger)), + } + } +} + +impl log::Log for KvLogger { + fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { + metadata.level() <= log::max_level() && metadata.level() <= log::STATIC_MAX_LEVEL + } + + fn log(&self, record: &log::Record<'_>) { + if !self.enabled(record.metadata()) { + return; + } + + let message = format_message(record); + let Ok(mut logger) = self.logger.lock() else { + return; + }; + + let result = match record.level() { + log::Level::Error => logger.err(message), + log::Level::Warn => logger.warning(message), + log::Level::Info => logger.info(message), + log::Level::Debug | log::Level::Trace => logger.debug(message), + }; + let _ = result; + } + + fn flush(&self) { + if let Ok(mut logger) = self.logger.lock() { + let _ = logger.backend.flush(); + } + } +} + +struct FieldVisitor { + fields: String, +} + +impl<'kvs> kv::VisitSource<'kvs> for FieldVisitor { + fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> { + if !self.fields.is_empty() { + self.fields.push(' '); + } + let _ = write!(self.fields, "{}={}", key.as_str(), value); + Ok(()) + } +} + +fn format_message(record: &log::Record<'_>) -> String { + let mut message = record.args().to_string(); + let mut visitor = FieldVisitor { + fields: String::new(), + }; + + if let Err(error) = record.key_values().visit(&mut visitor) { + if !visitor.fields.is_empty() { + message.push(' '); + } + let _ = write!(message, "kv_error={error}"); + } + + if !visitor.fields.is_empty() { + message.push(' '); + message.push_str(&visitor.fields); + } + message +} + +pub fn init_syslog(facility: syslog::Facility, log_level: log::LevelFilter) -> syslog::Result<()> { + let formatter = syslog::Formatter3164 { + facility, + hostname: None, + process: "sessiond-uaccess".to_owned(), + pid: std::process::id(), + }; + + let logger = if let Ok(logger) = syslog::unix(formatter.clone()) { + logger + } else if let Ok(logger) = syslog::tcp(formatter.clone(), ("127.0.0.1", 601)) { + logger + } else { + syslog::udp(formatter, ("0.0.0.0", 0), ("127.0.0.1", 514))? + }; + + log::set_boxed_logger(Box::new(KvLogger::new(logger))) + .map_err(|error| syslog::Error::Initialization(Box::new(error)))?; + log::set_max_level(log_level); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::format_message; + + #[test] + fn includes_key_values_after_message() { + let record = log::Record::builder() + .args(format_args!("session added")) + .key_values(&[("session_id", 42)]) + .build(); + + assert_eq!(format_message(&record), "session added session_id=42"); + } + + #[test] + fn supports_values_with_spaces() { + let record = log::Record::builder() + .args(format_args!("assigned")) + .key_values(&[("seat", "seat 1")]) + .build(); + + assert_eq!(format_message(&record), "assigned seat=seat 1"); + } +} diff --git a/sessiond-uaccess/src/main.rs b/sessiond-uaccess/src/main.rs new file mode 100644 index 0000000..3859ccd --- /dev/null +++ b/sessiond-uaccess/src/main.rs @@ -0,0 +1,499 @@ +mod config; +mod ecs; +mod logging; +mod session; + +use anyhow::Context; +use bevy_ecs::message; +use bevy_ecs::prelude::*; +use calloop::{generic, signals}; +use clap::{Parser, ValueEnum}; +use hyprwire::client; +use hyprwire_protocols_sessiond::client::session_core::v1::session_core_v1; +use posix_acl as acl; +use session_core_v1::session_core_manager; +use std::os::fd::AsRawFd; +use std::{env, path, sync, time}; + +struct SessiondUaccess { + reconcile: Schedule, + world: World, +} + +fn revoke_stale_grants( + mut commands: Commands, + grants: Query<(Entity, &ecs::DeviceEntity, &ecs::AccessMode, &ecs::Uid), With>, + devices: Query<(&ecs::Syspath, &ecs::Devnode), With>, + sessions: Query<(&ecs::Uid, &ecs::Done, &ecs::IsLocal, &ecs::ActiveState), With>, +) { + for (grant_entity, device_entity, access_mode, grant_uid) in &grants { + if sessions.iter().any(|(uid, done, is_local, active)| { + uid.0 == grant_uid.0 + && done.0 + && is_local.0 + && active.0 == session_core_v1::ActiveState::Active + }) { + continue; + } + + let Ok((syspath, devnode)) = devices.get(device_entity.0) else { + commands.entity(grant_entity).despawn(); + continue; + }; + + let mut acl = match acl::PosixACL::read_acl(&devnode.0) { + Ok(acl) => acl, + Err(e) => { + log::error!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = grant_uid.0, + access = access_mode.0.as_str(), + error = e.to_string().as_str(); + "failed to read device acl" + ); + continue; + } + }; + acl.remove(acl::Qualifier::User(grant_uid.0)); + if let Err(e) = acl.write_acl(&devnode.0) { + log::error!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = grant_uid.0, + access = access_mode.0.as_str(), + error = e.to_string().as_str(); + "failed to write device acl" + ); + continue; + } + + commands.entity(grant_entity).despawn(); + + log::info!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = grant_uid.0, + access = access_mode.0.as_str(); + "removed device grant" + ); + } +} + +fn apply_missing_grants( + mut commands: Commands, + devices: Query<(Entity, &ecs::Syspath, &ecs::Devnode, &ecs::AccessMode), With>, + sessions: Query<(&ecs::Uid, &ecs::Done, &ecs::IsLocal, &ecs::ActiveState), With>, + grants: Query<(&ecs::DeviceEntity, &ecs::Uid), With>, +) { + for (uid, done, is_local, active) in &sessions { + if !done.0 || !is_local.0 || active.0 != session_core_v1::ActiveState::Active { + continue; + } + + for (device_entity, syspath, devnode, access_mode) in &devices { + let grant_exists = grants.iter().any(|(grant_device_entity, grant_uid)| { + grant_device_entity.0 == device_entity && grant_uid.0 == uid.0 + }); + if grant_exists { + continue; + } + + let mut acl = match acl::PosixACL::read_acl(&devnode.0) { + Ok(acl) => acl, + Err(e) => { + log::error!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = uid.0, + access = access_mode.0.as_str(), + error = e.to_string().as_str(); + "failed to read device acl" + ); + continue; + } + }; + + let acl_rule = match access_mode.0 { + config::AccessMode::Read => acl::ACL_READ, + config::AccessMode::Write => acl::ACL_WRITE, + config::AccessMode::ReadWrite => acl::ACL_READ | acl::ACL_WRITE, + }; + acl.set(acl::Qualifier::User(uid.0), acl_rule); + if let Err(e) = acl.write_acl(&devnode.0) { + log::error!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = uid.0, + access = access_mode.0.as_str(), + error = e.to_string().as_str(); + "failed to write device acl" + ); + continue; + } + + commands.spawn(( + ecs::Grant, + ecs::DeviceEntity(device_entity), + ecs::AccessMode(access_mode.0), + ecs::Uid(uid.0), + )); + + log::info!( + syspath:% = syspath.0.display(), + devnode:% = devnode.0.display(), + uid = uid.0, + access = access_mode.0.as_str(); + "inserted device grant" + ); + } + } +} + +fn process_device_removals( + mut message: MessageReader, + devices: Query<(Entity, &ecs::Syspath), With>, + grants: Query<(Entity, &ecs::DeviceEntity), With>, + mut commands: Commands, +) { + for removed in message.read() { + let Some((device_entity, _)) = devices + .iter() + .find(|(_, syspath)| syspath.0.as_ref() == removed.syspath.as_ref()) + else { + continue; + }; + + for (grant_entity, grant_device) in &grants { + if grant_device.0 == device_entity { + commands.entity(grant_entity).despawn(); + } + } + + commands.entity(device_entity).despawn(); + } +} + +#[allow(clippy::needless_pass_by_value)] +fn reconcile_devices( + mut message: MessageReader, + mut commands: Commands, + devices: Query< + ( + Entity, + &ecs::Syspath, + Option<&ecs::Devnode>, + Option<&ecs::AccessMode>, + ), + With, + >, + grants: Query<(Entity, &ecs::DeviceEntity), With>, + config: NonSend, +) { + for syspath in message.read().map(|added| sync::Arc::clone(&added.syspath)) { + let device = match udev::Device::from_syspath(&syspath) { + Ok(device) => config::LuaDevice::new(device), + Err(error) => { + log::debug!( + syspath:% = syspath.display(), + error = error.to_string().as_str(); + "failed to resolve device during reconciliation" + ); + continue; + } + }; + + let started = time::Instant::now(); + let Some(devnode) = device.device.devnode() else { + log::debug!(syspath:% = syspath.display(); "skipping device without devnode"); + continue; + }; + let subsystem = device + .device + .subsystem() + .map(|subsystem| subsystem.to_string_lossy().into_owned()); + let sysname = device.device.sysname().to_string_lossy().into_owned(); + + for rule in config.rules.borrow().iter() { + let result = { + let match_fn = config + .lua + .registry_value::(&rule.match_fn) + .map_err(|error| anyhow::anyhow!("{error}")); + match_fn.and_then(|match_fn| { + match_fn + .call::(device.clone()) + .map(|matched| (matched, rule.access)) + .map_err(|error| anyhow::anyhow!("{error}")) + }) + }; + + let (matched, access) = match result { + Ok(result) => result, + Err(error) => { + log::error!( + syspath:% = syspath.display(), + error = error.to_string().as_str(); + "failed to evaluate device match rule" + ); + continue; + } + }; + + if matched { + log::debug!( + syspath:% = syspath.display(), + devnode:% = devnode.display(), + subsystem = subsystem.as_deref(), + sysname = sysname.as_str(); + "device matched rule" + ); + + if let Some((device_entity, _, existing_devnode, existing_access_mode)) = devices + .iter() + .find(|(_, device_syspath, _, _)| device_syspath.0 == syspath) + { + let device_changed = existing_devnode + .is_none_or(|existing_devnode| existing_devnode.0 != devnode) + || existing_access_mode.is_none_or(|access_mode| access_mode.0 != access); + + if device_changed { + for (grant_entity, grant_device) in &grants { + if grant_device.0 == device_entity { + commands.entity(grant_entity).despawn(); + } + } + commands.entity(device_entity).despawn(); + commands.spawn(( + ecs::Device, + ecs::Syspath(sync::Arc::clone(&syspath)), + ecs::Devnode(devnode.to_owned()), + ecs::AccessMode(access), + )); + } + } else { + commands.spawn(( + ecs::Device, + ecs::Syspath(sync::Arc::clone(&syspath)), + ecs::Devnode(devnode.to_owned()), + ecs::AccessMode(access), + )); + log::info!( + syspath:% = syspath.display(), + devnode:% = devnode.display(); + "inserted managed device" + ); + } + + break; + } + } + + log::debug!( + syspath:% = syspath.display(), + devnode:% = devnode.display(), + duration:? = started.elapsed(); + "reconciled device" + ); + } +} + +#[derive(Parser)] +struct Args { + #[arg(long)] + rules_dirs: Vec, + #[arg(long, value_enum, default_value_t = LogTarget::Stderr)] + log_target: LogTarget, +} + +#[derive(Clone, Copy, Debug, ValueEnum)] +enum LogTarget { + Stderr, + Syslog, +} + +fn main() -> anyhow::Result<()> { + let startup_started = time::Instant::now(); + let args = Args::parse(); + + let log_level = match env::var("LOG_LEVEL") { + Ok(log) if log == "trace" => log::LevelFilter::Trace, + Ok(log) if log == "debug" => log::LevelFilter::Debug, + Ok(log) if log == "warn" => log::LevelFilter::Warn, + Ok(log) if log == "error" => log::LevelFilter::Error, + Ok(log) if log == "off" => log::LevelFilter::Off, + _ => log::LevelFilter::Info, + }; + + match args.log_target { + LogTarget::Stderr => { + env_logger::Builder::new() + .filter(Some("sessiond_uaccess"), log_level) + .init(); + } + LogTarget::Syslog => { + logging::init_syslog(syslog::Facility::LOG_DAEMON, log_level)?; + } + } + + log::info!(target:? = args.log_target, level:? = log_level; "logging initialized"); + + let mut schedule = Schedule::default(); + schedule.add_systems( + ( + message::message_update_system, + reconcile_devices, + process_device_removals, + revoke_stale_grants, + apply_missing_grants, + ) + .chain(), + ); + + let mut world = World::new(); + message::MessageRegistry::register_message::(&mut world); + message::MessageRegistry::register_message::(&mut world); + world.insert_non_send( + config::Config::load(&args.rules_dirs).context("failed to load Lua rules")?, + ); + + let mut sessiond_uaccess = SessiondUaccess { + world, + reconcile: schedule, + }; + + log::info!( + rules_dirs = args.rules_dirs.len(), + rules = sessiond_uaccess.world.non_send::().rules.borrow().len(); + "loaded access rules" + ); + + let mut event_loop = calloop::EventLoop::try_new()?; + + let loop_signal = event_loop.get_signal(); + + let signals = signals::Signals::new(&[signals::Signal::SIGTERM, signals::Signal::SIGINT])?; + event_loop + .handle() + .insert_source(signals, move |event, (), state: &mut SessiondUaccess| { + log::info!(signal:? = event.signal(); "terminating"); + + let sessions: Vec<_> = { + let mut sessions = state + .world + .query_filtered::<(Entity, &ecs::Uid), With>(); + sessions + .iter(&state.world) + .map(|(entity, uid)| (entity, uid.0)) + .collect() + }; + + for (session_entity, _) in sessions { + state.world.despawn(session_entity); + } + + loop_signal.stop(); + loop_signal.wakeup(); + })?; + + let mut socket = client::Client::connect("/run/sessiond/sessiond.sock") + .context("failed to connect to /run/sessiond/sessiond.sock")?; + let eq = socket.new_event_queue(); + + socket.add_implementation::(); + + eq.wait_for_handshake(&mut sessiond_uaccess) + .context("sessiond protocol handshake failed")?; + + let _session_core_manager = socket + .bind::(&eq, &mut sessiond_uaccess, 1) + .context("failed to bind sessiond manager")?; + + log::info!(socket = "/run/sessiond/sessiond.sock"; "connected to sessiond"); + + eq.roundtrip(&mut sessiond_uaccess)?; + + let fd_wrapper = unsafe { generic::FdWrapper::new(socket.extract_loop_fd().as_raw_fd()) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + event_loop + .handle() + .insert_source(source, move |_, _, state| { + if let Err(error) = eq.dispatch_events(state, false) { + let error = error.to_string(); + log::error!(error = error.as_str(); "failed to dispatch sessiond events"); + } + Ok(calloop::PostAction::Continue) + })?; + + let monitor = udev::MonitorBuilder::new()?.listen()?; + + let mut enumerator = udev::Enumerator::new()?; + for device in enumerator.scan_devices()? { + log::trace!( + syspath = device.syspath().to_string_lossy().as_ref(); + "reconciling existing device" + ); + sessiond_uaccess.world.write_message(ecs::DeviceAdded { + syspath: device.syspath().into(), + }); + } + + event_loop + .handle() + .insert_source( + generic::Generic::new(monitor, calloop::Interest::READ, calloop::Mode::Level), + |_, monitor, state: &mut SessiondUaccess| { + for event in monitor.iter() { + match event.event_type() { + udev::EventType::Add | udev::EventType::Change => { + state.world.write_message(ecs::DeviceAdded { + syspath: event.device().syspath().into(), + }); + } + udev::EventType::Remove => { + state.world.write_message(ecs::DeviceRemoved { + syspath: event.device().syspath().into(), + }); + } + _ => {} + } + } + + Ok(calloop::PostAction::Continue) + }, + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + let wakeup_started = time::Instant::now(); + event_loop.get_signal().wakeup(); + log::debug!( + duration:? = startup_started.elapsed(); + "queued initial device reconciliation wakeup" + ); + + let mut first_schedule = true; + event_loop.run(None, &mut sessiond_uaccess, move |state| { + let reconciliation_started = time::Instant::now(); + state.reconcile.run(&mut state.world); + let reconciliation_duration = reconciliation_started.elapsed(); + + if first_schedule { + first_schedule = false; + log::debug!( + startup_duration:? = startup_started.elapsed(), + wakeup_duration:? = wakeup_started.elapsed(), + reconciliation_duration:? = reconciliation_duration; + "completed startup reconciliation" + ); + } + })?; + + Ok(()) +} diff --git a/sessiond-uaccess/src/session.rs b/sessiond-uaccess/src/session.rs new file mode 100644 index 0000000..f8bd441 --- /dev/null +++ b/sessiond-uaccess/src/session.rs @@ -0,0 +1,71 @@ +use crate::ecs; +use crate::session_core_v1::{session_core_manager, session_core_session}; +use bevy_ecs::prelude::*; + +impl hyprwire::Dispatch for crate::SessiondUaccess { + fn event( + &mut self, + object: &session_core_manager::SessionCoreManager, + event: ::Event<'_>, + ) { + if let session_core_manager::Event::SessionAdded { session_id } = event + && let Some(session) = object.send_get_session::(session_id) + { + ecs::insert_session(&mut self.world, session); + log::debug!(session_id; "session added"); + } + } +} + +impl hyprwire::Dispatch for crate::SessiondUaccess { + fn event( + &mut self, + object: &session_core_session::SessionCoreSession, + event: ::Event<'_>, + ) { + let Some(entity) = entity_by_object(&mut self.world, object) else { + return; + }; + + match event { + session_core_session::Event::Uid { uid } => { + self.world.entity_mut(entity).insert(ecs::Uid(uid)); + log::debug!(uid; "session UID changed"); + } + session_core_session::Event::Active { state } => { + self.world + .entity_mut(entity) + .insert(ecs::ActiveState(state)); + log::debug!(state:? = state; "session active state changed"); + } + session_core_session::Event::IsLocal { is_local } => { + self.world + .entity_mut(entity) + .insert(ecs::IsLocal(is_local != 0)); + log::debug!(is_local = is_local != 0; "session locality changed"); + } + session_core_session::Event::Done => { + self.world.entity_mut(entity).insert(ecs::Done(true)); + log::debug!("session completed"); + } + session_core_session::Event::Destroyed => { + let uid = self.world.get::(entity).map(|uid| uid.0); + + self.world.entity_mut(entity).despawn(); + + log::info!(uid = uid; "session removed"); + } + _ => {} + } + } +} + +pub fn entity_by_object( + world: &mut World, + object: &session_core_session::SessionCoreSession, +) -> Option { + let mut sessions = world.query_filtered::<(Entity, &ecs::SessionObject), With>(); + sessions + .iter(world) + .find_map(|(entity, session_object)| (&session_object.0 == object).then_some(entity)) +} diff --git a/daemon/Cargo.toml b/sessiond/Cargo.toml similarity index 90% rename from daemon/Cargo.toml rename to sessiond/Cargo.toml index 608d5bd..84944a9 100644 --- a/daemon/Cargo.toml +++ b/sessiond/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "daemon" +name = "sessiond" keywords.workspace = true categories.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -version.workspace = true +version = "0.2.3" +publish = false description.workspace = true readme.workspace = true @@ -26,9 +27,7 @@ toml = "1.1.2" uzers = "0.12.2" syslog = "7.0.0" hyprwire = { version = "0.5.1", features = ["log"] } -hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.1.1", features = [ - "bevy_ecs", -] } +hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0" } [build-dependencies] hyprwire-scanner = "0.5.1" diff --git a/daemon/src/cgroup.rs b/sessiond/src/cgroup.rs similarity index 99% rename from daemon/src/cgroup.rs rename to sessiond/src/cgroup.rs index cc4dbad..a332467 100644 --- a/daemon/src/cgroup.rs +++ b/sessiond/src/cgroup.rs @@ -265,7 +265,7 @@ pub fn handle_cgroup_empty( user_registry: Res, cgroup_manager: Res, cgroups: Query<&CgroupPath, With>, - config: Res, + config: Res, shutdown_state: Res, loop_signal: Res, #[cfg(feature = "consolekit")] console_kit: Res, @@ -307,7 +307,6 @@ pub fn handle_cgroup_empty( && session_registry.entities_by_uid(uid.0).is_empty() && let Err(e) = user::remove_runtime_dir( &config - .0 .users .get(&sync::Arc::from(user.name().to_string_lossy())) .unwrap_or(&default_config) diff --git a/daemon/src/config.rs b/sessiond/src/config.rs similarity index 97% rename from daemon/src/config.rs rename to sessiond/src/config.rs index e6321bc..5c61eeb 100644 --- a/daemon/src/config.rs +++ b/sessiond/src/config.rs @@ -82,7 +82,7 @@ fn default_runtime_dir() -> String { "/run/user/{uid}".to_string() } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Resource)] #[serde(rename_all = "kebab-case")] #[derive(Default)] pub struct Config { @@ -123,6 +123,3 @@ impl Config { .with_context(|| format!("failed to parse config file {}", path.display())) } } - -#[derive(Resource)] -pub struct DaemonConfig(pub Config); diff --git a/daemon/src/consolekit.rs b/sessiond/src/consolekit.rs similarity index 100% rename from daemon/src/consolekit.rs rename to sessiond/src/consolekit.rs diff --git a/daemon/src/inhibit.rs b/sessiond/src/inhibit.rs similarity index 95% rename from daemon/src/inhibit.rs rename to sessiond/src/inhibit.rs index 9e5bf0c..73a28f2 100644 --- a/daemon/src/inhibit.rs +++ b/sessiond/src/inhibit.rs @@ -6,6 +6,12 @@ use hyprwire_protocols_sessiond::server::session_inhibit::v1::session_inhibit_v1 use hyprwire_protocols_sessiond::server::session_inhibit::v1::session_inhibit_v1::session_inhibitor; use std::fs; +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +pub struct Mode(pub session_inhibit_v1::Mode); + +#[derive(Component, Debug, Clone, PartialEq, Eq)] +pub struct SessionInhibitor(pub session_inhibitor::SessionInhibitor); + #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct Inhibitor; @@ -96,8 +102,8 @@ pub struct InhibitorBundle { pub username: Username, pub pid: Pid, pub comm: Comm, - pub mode: session_inhibit_v1::Mode, - pub object: session_inhibitor::SessionInhibitor, + pub mode: Mode, + pub object: SessionInhibitor, pub objects: Objects, } @@ -189,8 +195,8 @@ impl hyprwire::Dispatch for crat username: Username(username), pid: Pid(pid), comm: Comm(comm), - mode, - object: protocol_object, + mode: Mode(mode), + object: SessionInhibitor(protocol_object), objects: Objects::default(), }); @@ -239,12 +245,12 @@ impl hyprwire::Dispatch for crate::Sessiond let mut inhibitors = self.world.query_filtered::<( Entity, &InhibitorId, - &session_inhibitor::SessionInhibitor, + &SessionInhibitor, &Objects, ), With>(); inhibitors .iter(&self.world) - .find(|(_, _, candidate, _)| **candidate == *object) + .find(|(_, _, candidate, _)| candidate.0 == *object) .map(|(entity, id, _, objects)| (entity, *id, objects.0.clone())) }; @@ -289,7 +295,7 @@ pub fn handle_add_object( &Username, &Pid, &Comm, - &session_inhibit_v1::Mode, + &Mode, &mut Objects, ), With, @@ -313,7 +319,7 @@ pub fn handle_add_object( request.object.send_comm(&comm.0); request.object.send_what(what.0); request.object.send_why(&why.0); - request.object.send_mode(core_mode(*mode)); + request.object.send_mode(core_mode(mode.0)); request.object.send_active(1); request.object.send_done(); objects.0.push(request.object.clone()); diff --git a/daemon/src/main.rs b/sessiond/src/main.rs similarity index 98% rename from daemon/src/main.rs rename to sessiond/src/main.rs index f66773c..ab63a8b 100644 --- a/daemon/src/main.rs +++ b/sessiond/src/main.rs @@ -94,7 +94,6 @@ fn handle_shutdown( fn run(runtime_dir: &path::Path, config: config::Config) -> anyhow::Result<()> { let mut event_loop = calloop::EventLoop::try_new()?; - #[cfg(feature = "consolekit")] let signals = signals::Signals::new(&[ signals::Signal::SIGUSR1, signals::Signal::SIGUSR2, @@ -120,7 +119,7 @@ fn run(runtime_dir: &path::Path, config: config::Config) -> anyhow::Result<()> { world.init_resource::(); world.init_resource::(); world.insert_resource(cgroup::CgroupManager::new()?); - world.insert_resource(config::DaemonConfig(config)); + world.insert_resource(config); world.insert_resource(LoopSignal(event_loop.get_signal())); let state_path = runtime_dir.join("state.toml"); world.insert_resource(state::StateWriter(state_path)); @@ -291,7 +290,7 @@ fn main() -> anyhow::Result<()> { match args.log_target { LogTarget::Stderr => { env_logger::Builder::new() - .filter(Some("daemon"), log_level) + .filter(Some("sessiond"), log_level) .init(); } LogTarget::Syslog => { diff --git a/daemon/src/reconcile.rs b/sessiond/src/reconcile.rs similarity index 77% rename from daemon/src/reconcile.rs rename to sessiond/src/reconcile.rs index ff9404a..ca199d1 100644 --- a/daemon/src/reconcile.rs +++ b/sessiond/src/reconcile.rs @@ -3,7 +3,6 @@ use crate::consolekit; use crate::{session, user}; use bevy_ecs::prelude::*; use bevy_ecs::schedule::ScheduleLabel; -use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1; use std::mem; #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] @@ -38,14 +37,11 @@ pub fn sync_consolekit_sessions( &session::Uid, &session::Timestamp, Option<&session::IsLocal>, - &session_core_v1::ActiveState, + &session::ActiveState, ), ( With, - Or<( - Added, - Changed, - )>, + Or<(Added, Changed)>, ), >, ) { @@ -55,7 +51,7 @@ pub fn sync_consolekit_sessions( uid: uid.0, timestamp: timestamp.0, is_local: is_local.is_some(), - active: *active_state, + active: active_state.0, }); } } @@ -63,27 +59,24 @@ pub fn sync_consolekit_sessions( pub fn notify_session_active_states( mut batch: ResMut, sessions: Query< - (Entity, &session_core_v1::ActiveState, &session::Objects), - ( - With, - Changed, - ), + (Entity, &session::ActiveState, &session::Objects), + (With, Changed), >, users: Query< - (Entity, &session_core_v1::UserState, &user::Objects), - (With, Changed), + (Entity, &user::UserState, &user::Objects), + (With, Changed), >, ) { for (entity, state, objects) in &sessions { for object in &objects.0 { - object.send_active(*state); + object.send_active(state.0); } batch.session(entity); } for (entity, state, objects) in &users { for object in &objects.0 { - object.send_state(*state); + object.send_state(state.0); } batch.user(entity); } diff --git a/daemon/src/session.rs b/sessiond/src/session.rs similarity index 97% rename from daemon/src/session.rs rename to sessiond/src/session.rs index 5477bc6..512d7b5 100644 --- a/daemon/src/session.rs +++ b/sessiond/src/session.rs @@ -1,5 +1,4 @@ use crate::{cgroup, config, inhibit, user, vt}; -use bevy_ecs::entity; use bevy_ecs::prelude::*; use hyprwire::server; use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1; @@ -13,6 +12,21 @@ use hyprwire_protocols_sessiond::server::session_management::v2::session_managem use std::collections::{BTreeMap, HashMap}; use std::{num, sync, time}; +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +pub struct ActiveState(pub session_core_v1::ActiveState); + +impl ActiveState { + pub const ONLINE: Self = Self(session_core_v1::ActiveState::Online); + pub const ACTIVE: Self = Self(session_core_v1::ActiveState::Active); + pub const CLOSING: Self = Self(session_core_v1::ActiveState::Closing); +} + +impl From for ActiveState { + fn from(state: session_core_v1::ActiveState) -> Self { + Self(state) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum SessionManagementTarget { V1(session_manager_v1::SessionManager), @@ -142,7 +156,7 @@ pub struct SessionBundle { pub timestamp: Timestamp, pub leader_id: LeaderId, pub env: SessionEnv, - pub active_state: session_core_v1::ActiveState, + pub active_state: ActiveState, pub objects: Objects, } @@ -262,7 +276,7 @@ pub fn spawn( env: BTreeMap, sync::Arc>, cgroup: cgroup::CgroupBundle, session_registry: &mut ResMut, -) -> entity::Entity { +) -> Entity { let mut cmd = commands.spawn(SessionBundle { marker: Session, id: session_id, @@ -272,7 +286,7 @@ pub fn spawn( timestamp: Timestamp(time::SystemTime::now()), leader_id: LeaderId(leader_id), env: SessionEnv(env), - active_state, + active_state: active_state.into(), objects: Objects::default(), }); cmd.insert(cgroup); @@ -303,7 +317,7 @@ pub fn spawn( #[derive(Message)] pub struct SessionControlMessage { - pub entity: entity::Entity, + pub entity: Entity, pub object: Option, pub action: SessionControlAction, } @@ -323,7 +337,7 @@ pub fn handle_session_control( ( &SessionId, &Uid, - &mut session_core_v1::ActiveState, + &mut ActiveState, Option<&Vt>, &mut Objects, ), @@ -350,7 +364,7 @@ pub fn handle_session_control( continue; } log::info!(session_id = session_id.as_raw(); "terminate requested"); - *active_state = session_core_v1::ActiveState::Closing; + *active_state = ActiveState::CLOSING; log::info!(session_id = session_id.0, vt = vt.map_or(0, |vt| vt.0.get()); "session state changed"); let Ok((cgroup_session_id, cgroup_path)) = cgroups.get(action.entity) else { continue; @@ -377,7 +391,7 @@ pub fn handle_session_control( continue; } log::info!(session_id = session_id.as_raw(); "kill requested"); - *active_state = session_core_v1::ActiveState::Closing; + *active_state = ActiveState::CLOSING; log::info!(session_id = session_id.0, vt = vt.map_or(0, |vt| vt.0.get()); "session state changed"); let Ok((cgroup_session_id, cgroup_path)) = cgroups.get(action.entity) else { continue; @@ -520,7 +534,7 @@ pub fn handle_add_object( &Username, Option<&Vt>, &SessionClass, - &session_core_v1::ActiveState, + &ActiveState, &LeaderId, &Timestamp, Option<&IsLocal>, @@ -562,7 +576,7 @@ pub fn handle_add_object( request.object.send_username(&username.0); request.object.send_class((*class).into()); request.object.send_vt(vt.map_or(0, |vt| vt.0.get())); - request.object.send_active(*state); + request.object.send_active(state.0); request.object.send_leader(leader_id.0.cast_unsigned()); let timestamp_secs = timestamp .0 @@ -734,7 +748,7 @@ pub fn handle_session_management_action( info_managers: Res, mut session_registry: ResMut, sessions: Query<(&SessionEnv, &Uid), With>, - config: Res, + config: Res, ) { for action in actions.read() { if let SessionManagementAction::ManagerDestroy { management_object } = &action.action { @@ -855,7 +869,7 @@ pub fn handle_session_management_action( } }; - let user_config = config.0.users.get(&username).cloned().unwrap_or_default(); + let user_config = config.users.get(&username).cloned().unwrap_or_default(); user::create_runtime_dir( pending_session.uid, &user_config.runtime_dir(pending_session.uid), diff --git a/daemon/src/state.rs b/sessiond/src/state.rs similarity index 97% rename from daemon/src/state.rs rename to sessiond/src/state.rs index 103cbb5..37b132b 100644 --- a/daemon/src/state.rs +++ b/sessiond/src/state.rs @@ -79,7 +79,7 @@ pub fn write_state( Option<&session::RemoteHost>, &session::Timestamp, &session::SessionEnv, - &session_core_v1::ActiveState, + &session::ActiveState, ), (With, With), >, @@ -97,7 +97,7 @@ pub fn write_state( Changed, Changed, Changed, - Changed, + Changed, )>, ), >, @@ -126,7 +126,7 @@ pub fn write_state( session::SessionClass::Manager => "manager", session::SessionClass::ManagerEarly => "manager-early", }; - let active = match active_state { + let active = match active_state.0 { session_core_v1::ActiveState::Active => "active", session_core_v1::ActiveState::Closing => "closing", session_core_v1::ActiveState::Opening => "opening", @@ -203,7 +203,7 @@ pub fn load_state( state_writer: Res, mut next_session_id: ResMut, cgroup_manager: Res, - config: Res, + config: Res, mut session_registry: ResMut, ) { if state_writer.0.exists() { @@ -229,7 +229,6 @@ pub fn load_state( }; let runtime_dir = config - .0 .users .get(&saved_session.username) .cloned() diff --git a/daemon/src/terminal.rs b/sessiond/src/terminal.rs similarity index 100% rename from daemon/src/terminal.rs rename to sessiond/src/terminal.rs diff --git a/daemon/src/user.rs b/sessiond/src/user.rs similarity index 87% rename from daemon/src/user.rs rename to sessiond/src/user.rs index 3aef1e9..31f1f8a 100644 --- a/daemon/src/user.rs +++ b/sessiond/src/user.rs @@ -5,6 +5,23 @@ use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1::sess use std::collections::HashMap; use std::{fs, io, sync}; +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +pub struct UserState(pub session_core_v1::UserState); + +impl UserState { + pub const ACTIVE: Self = Self(session_core_v1::UserState::Active); + pub const CLOSING: Self = Self(session_core_v1::UserState::Closing); + pub const LINGERING: Self = Self(session_core_v1::UserState::Lingering); + pub const OFFLINE: Self = Self(session_core_v1::UserState::Offline); + pub const ONLINE: Self = Self(session_core_v1::UserState::Online); +} + +impl From for UserState { + fn from(state: session_core_v1::UserState) -> Self { + Self(state) + } +} + #[derive(Resource, Default)] pub struct UserRegistry { pub by_uid: HashMap, @@ -16,7 +33,7 @@ pub struct UserBundle { pub marker: User, pub uid: session::Uid, pub username: session::Username, - pub state: session_core_v1::UserState, + pub state: UserState, pub objects: Objects, } @@ -34,7 +51,7 @@ pub fn spawn( uid: u32, username: sync::Arc, lingering: bool, - state: session_core_v1::UserState, + state: UserState, user_registry: &mut ResMut, info_managers: &session::SessionInfoManagers, ) -> Entity { @@ -81,11 +98,7 @@ pub fn remove_runtime_dir(path: &str) -> io::Result<()> { pub fn ensure_session_users( mut commands: Commands, sessions: Query< - ( - &session::Uid, - &session::Username, - &session_core_v1::ActiveState, - ), + (&session::Uid, &session::Username, &session::ActiveState), (With, Added), >, mut user_registry: ResMut, @@ -96,10 +109,10 @@ pub fn ensure_session_users( continue; } - let state = match session_state { - session_core_v1::ActiveState::Active => session_core_v1::UserState::Active, - session_core_v1::ActiveState::Closing => session_core_v1::UserState::Closing, - _ => session_core_v1::UserState::Online, + let state = match session_state.0 { + session_core_v1::ActiveState::Active => UserState::ACTIVE, + session_core_v1::ActiveState::Closing => UserState::CLOSING, + _ => UserState::ONLINE, }; spawn( @@ -115,20 +128,9 @@ pub fn ensure_session_users( } pub fn reconcile_user_states( - users: Query< - ( - &session::Uid, - Option<&Lingering>, - &mut session_core_v1::UserState, - ), - With, - >, + users: Query<(&session::Uid, Option<&Lingering>, &mut UserState), With>, sessions: Query< - ( - &session::Uid, - &session::SessionClass, - &session_core_v1::ActiveState, - ), + (&session::Uid, &session::SessionClass, &session::ActiveState), With, >, ) { @@ -145,20 +147,20 @@ pub fn reconcile_user_states( .map(|(_, _, state)| state) .collect(); - let desired = if states.contains(&&session_core_v1::ActiveState::Active) { - session_core_v1::UserState::Active + let desired = if states.contains(&&session::ActiveState::ACTIVE) { + UserState::ACTIVE } else if !states.is_empty() && states .iter() - .all(|state| **state == session_core_v1::ActiveState::Closing) + .all(|state| **state == session::ActiveState::CLOSING) { - session_core_v1::UserState::Closing + UserState::CLOSING } else if !states.is_empty() { - session_core_v1::UserState::Online + UserState::ONLINE } else if lingering.is_some() { - session_core_v1::UserState::Lingering + UserState::LINGERING } else { - session_core_v1::UserState::Offline + UserState::OFFLINE }; if *user_state != desired { @@ -182,7 +184,7 @@ pub fn handle_add_object( &session::Uid, &session::Username, Option<&Lingering>, - &session_core_v1::UserState, + &UserState, &mut Objects, ), With, @@ -206,7 +208,7 @@ pub fn handle_add_object( } else { request.object.send_lingering(0); } - request.object.send_state(*state); + request.object.send_state(state.0); request.object.send_can_modify(u32::from( request.creds.uid.is_root() || uid.0 == request.creds.uid.as_raw(), @@ -351,13 +353,12 @@ impl hyprwire::Dispatch for crate::Sessiond pub fn initialize_lingering_users( mut commands: Commands, - config: Res, + config: Res, cgroup_manager: Res, mut user_registry: ResMut, info_managers: Res, ) { let lingering_users = config - .0 .users .iter() .filter(|(_, config)| config.linger) @@ -374,7 +375,7 @@ pub fn initialize_lingering_users( uid, user.name().to_string_lossy().into(), true, - session_core_v1::UserState::Lingering, + UserState::LINGERING, &mut user_registry, &info_managers, ); diff --git a/daemon/src/vt.rs b/sessiond/src/vt.rs similarity index 71% rename from daemon/src/vt.rs rename to sessiond/src/vt.rs index 3864ebb..c893901 100644 --- a/daemon/src/vt.rs +++ b/sessiond/src/vt.rs @@ -1,6 +1,5 @@ use crate::{session, terminal}; use bevy_ecs::prelude::*; -use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1; use std::num; pub fn current_vt() -> anyhow::Result { @@ -14,7 +13,7 @@ pub fn refresh_current_vt( ( &session::Vt, Option<&session::IsLocal>, - &mut session_core_v1::ActiveState, + &mut session::ActiveState, ), With, >, @@ -24,13 +23,13 @@ pub fn refresh_current_vt( }; for (vt, is_local, mut active_state) in &mut sessions { - if *active_state == session_core_v1::ActiveState::Closing { + if *active_state == session::ActiveState::CLOSING { continue; } let desired = match (is_local, vt.0 == current_vt) { - (Some(_), true) => session_core_v1::ActiveState::Active, - (Some(_), false) => session_core_v1::ActiveState::Online, + (Some(_), true) => session::ActiveState::ACTIVE, + (Some(_), false) => session::ActiveState::ONLINE, _ => continue, }; -- 2.51.2