From 96bfc1fd5a68ce5be478a1774233524c9ba6a974 Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 7 May 2026 12:54:44 +0200 Subject: [PATCH] add systemd_service rust plugin (step 1a, no qml wiring yet) --- plugin/Cargo.lock | 544 ++++++++++++++++++++++++++++++++++ plugin/Cargo.toml | 2 + plugin/build.rs | 1 + plugin/src/lib.rs | 1 + plugin/src/systemd_service.rs | 274 +++++++++++++++++ 5 files changed, 822 insertions(+) create mode 100644 plugin/src/systemd_service.rs diff --git a/plugin/Cargo.lock b/plugin/Cargo.lock index ffbddba..d5a71f1 100644 --- a/plugin/Cargo.lock +++ b/plugin/Cargo.lock @@ -23,6 +23,64 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cc" version = "1.2.61" @@ -97,6 +155,15 @@ dependencies = [ "unicode-width 0.2.2", ] +[[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 = "convert_case" version = "0.6.0" @@ -106,6 +173,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "ctor" version = "0.13.1" @@ -282,12 +355,76 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[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 = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -300,6 +437,49 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -329,6 +509,12 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "indexmap" version = "2.14.0" @@ -364,6 +550,18 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -406,6 +604,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44cd706ff0d503ee32b2071166510ca27e281228de10cd3aa8d35ff94560f81" +[[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.29" @@ -427,6 +631,26 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + [[package]] name = "nova-plugin" version = "0.1.0" @@ -440,8 +664,10 @@ dependencies = [ "libc", "serde", "serde_json", + "tokio", "tracing", "tracing-subscriber", + "zbus", ] [[package]] @@ -465,12 +691,37 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -536,6 +787,19 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -597,6 +861,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -612,12 +887,38 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -641,6 +942,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -699,6 +1013,64 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tokio" +version = "1.52.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.44" @@ -760,6 +1132,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -784,6 +1167,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -805,6 +1199,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -829,14 +1268,119 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "zbus" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "uuid", + "windows-sys", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow", +] diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml index f2fed37..5fb8d24 100644 --- a/plugin/Cargo.toml +++ b/plugin/Cargo.toml @@ -23,6 +23,8 @@ serde_json = "1.0.149" dirs = "6.0.0" tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +zbus = { version = "5.15.0", default-features = false, features = ["tokio"] } +tokio = { version = "1.52.2", features = ["macros", "net", "rt", "sync", "time"] } [build-dependencies] cxx-qt-build = "0.8.1" diff --git a/plugin/build.rs b/plugin/build.rs index 4f4ed35..d975722 100644 --- a/plugin/build.rs +++ b/plugin/build.rs @@ -31,6 +31,7 @@ fn main() { "src/system_stats.rs", "src/cpu_service.rs", "src/modules_service.rs", + "src/systemd_service.rs", "src/theme_service.rs", ]; if !bridge_files.iter().all(|p| manifest.join(p).exists()) { diff --git a/plugin/src/lib.rs b/plugin/src/lib.rs index 7531222..79a8759 100644 --- a/plugin/src/lib.rs +++ b/plugin/src/lib.rs @@ -2,6 +2,7 @@ pub mod cpu_service; pub mod modules_service; pub mod stats; pub mod system_stats; +pub mod systemd_service; pub mod theme_service; #[ctor::ctor(unsafe)] diff --git a/plugin/src/systemd_service.rs b/plugin/src/systemd_service.rs new file mode 100644 index 0000000..42bfddb --- /dev/null +++ b/plugin/src/systemd_service.rs @@ -0,0 +1,274 @@ +// In-process systemd state for nova-shell. +// +// Lists of units and machines are exposed to QML as JSON-encoded QString props +// (`failedUnitsJson`, `containersJson`) rather than QList. cxx-qt +// 0.8.1 does not implement QVariantValue for QVariantMap/QVariantList, and +// cxx-qt main on git regressed qt-build-utils to require QuickControls2.prl +// files that nixpkgs strips. Switch to QList when a release ships +// with both fixes. + +use core::pin::Pin; +use cxx_qt_lib::QString; +use serde::Serialize; +use std::sync::OnceLock; +use tokio::runtime::Runtime; +use zbus::{proxy, Connection}; + +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + extern "RustQt" { + #[qobject] + #[qml_element] + #[qml_singleton] + #[qproperty(QString, hostname)] + #[qproperty(QString, system_state, cxx_name = "systemState")] + #[qproperty(QString, user_state, cxx_name = "userState")] + #[qproperty(i32, failed_count, cxx_name = "failedCount")] + // JSON array: [{ name, description, subState, scope: "system"|"user", machine: "" | name }] + #[qproperty(QString, failed_units_json, cxx_name = "failedUnitsJson")] + // JSON array: [{ name, class, service, systemState, failedUnits: [...] }] + #[qproperty(QString, containers_json, cxx_name = "containersJson")] + type SystemdService = super::SystemdServiceRust; + + #[qinvokable] + fn poll(self: Pin<&mut Self>); + + #[qinvokable] + #[cxx_name = "restartUnit"] + fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString); + } + + impl cxx_qt::Initialize for SystemdService {} +} + +// systemd1.Manager.ListUnitsFiltered returns a(ssssssouso): name, description, +// load_state, active_state, sub_state, follower, unit_path, job_id, job_type, job_path. +type UnitTuple = ( + String, + String, + String, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + u32, + String, + zbus::zvariant::OwnedObjectPath, +); + +#[proxy( + interface = "org.freedesktop.systemd1.Manager", + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +trait SystemdManager { + #[zbus(property)] + fn system_state(&self) -> zbus::Result; + + fn list_units_filtered(&self, states: Vec<&str>) -> zbus::Result>; + + fn restart_unit( + &self, + name: &str, + mode: &str, + ) -> zbus::Result; +} + +#[proxy( + interface = "org.freedesktop.machine1.Manager", + default_service = "org.freedesktop.machine1", + default_path = "/org/freedesktop/machine1" +)] +trait Machined { + fn list_machines( + &self, + ) -> zbus::Result>; +} + +#[derive(Serialize)] +struct UnitJson<'a> { + name: &'a str, + description: &'a str, + #[serde(rename = "subState")] + sub_state: &'a str, + scope: &'a str, + machine: &'a str, +} + +#[derive(Serialize)] +struct ContainerJson<'a> { + name: &'a str, + class: &'a str, + service: &'a str, + #[serde(rename = "systemState")] + system_state: &'a str, + #[serde(rename = "failedUnits")] + failed_units: Vec>, +} + +pub struct SystemdServiceRust { + hostname: QString, + system_state: QString, + user_state: QString, + failed_count: i32, + failed_units_json: QString, + containers_json: QString, +} + +impl Default for SystemdServiceRust { + fn default() -> Self { + Self { + hostname: QString::from(read_hostname()), + system_state: QString::from("unknown"), + user_state: QString::from("unknown"), + failed_count: 0, + failed_units_json: QString::from("[]"), + containers_json: QString::from("[]"), + } + } +} + +fn read_hostname() -> String { + std::fs::read_to_string("/etc/hostname") + .map(|s| s.trim().to_string()) + .unwrap_or_else(|_| "localhost".to_string()) +} + +fn rt() -> &'static Runtime { + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("tokio runtime") + }) +} + +async fn fetch_failed(bus: &Connection) -> (String, Vec) { + let mut state = String::from("unknown"); + let mut units = Vec::new(); + if let Ok(mgr) = SystemdManagerProxy::new(bus).await { + if let Ok(s) = mgr.system_state().await { + state = s; + } + if let Ok(u) = mgr.list_units_filtered(vec!["failed"]).await { + units = u; + } + } + (state, units) +} + +async fn poll_async() -> ( + String, + String, + Vec, + Vec, + Vec<(String, String, String)>, +) { + let mut sys_state = String::from("unknown"); + let mut sys_units = Vec::new(); + let mut user_state = String::from("unknown"); + let mut user_units = Vec::new(); + let mut machines = Vec::new(); + + if let Ok(c) = Connection::system().await { + let (s, u) = fetch_failed(&c).await; + sys_state = s; + sys_units = u; + if let Ok(m) = MachinedProxy::new(&c).await { + if let Ok(list) = m.list_machines().await { + machines = list + .into_iter() + .filter(|(name, _, _, _)| name != ".host") + .map(|(n, c, s, _)| (n, c, s)) + .collect(); + } + } + } + if let Ok(c) = Connection::session().await { + let (s, u) = fetch_failed(&c).await; + user_state = s; + user_units = u; + } + + (sys_state, user_state, sys_units, user_units, machines) +} + +fn unit_jsons<'a>(units: &'a [UnitTuple], scope: &'a str, machine: &'a str) -> Vec> { + units + .iter() + .map(|u| UnitJson { + name: &u.0, + description: &u.1, + sub_state: &u.4, + scope, + machine, + }) + .collect() +} + +impl cxx_qt::Initialize for qobject::SystemdService { + fn initialize(self: Pin<&mut Self>) { + self.poll(); + } +} + +impl qobject::SystemdService { + fn poll(mut self: Pin<&mut Self>) { + let (sys_state, user_state, sys_units, user_units, machines) = rt().block_on(poll_async()); + + let mut all_failed: Vec = Vec::with_capacity(sys_units.len() + user_units.len()); + all_failed.extend(unit_jsons(&sys_units, "system", "")); + all_failed.extend(unit_jsons(&user_units, "user", "")); + let count = all_failed.len() as i32; + let failed_json = serde_json::to_string(&all_failed).unwrap_or_else(|_| "[]".into()); + + let containers: Vec = machines + .iter() + .map(|(n, c, s)| ContainerJson { + name: n, + class: c, + service: s, + system_state: "unknown", + failed_units: Vec::new(), + }) + .collect(); + let containers_json = serde_json::to_string(&containers).unwrap_or_else(|_| "[]".into()); + + self.as_mut().set_system_state(QString::from(sys_state)); + self.as_mut().set_user_state(QString::from(user_state)); + self.as_mut().set_failed_units_json(QString::from(failed_json)); + self.as_mut().set_failed_count(count); + self.as_mut().set_containers_json(QString::from(containers_json)); + } + + fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) { + let name = name.to_string(); + let scope = scope.to_string(); + let machine = machine.to_string(); + let _ = self; + rt().block_on(async move { + // Local-only restart for now. Container/remote restart comes later. + if !machine.is_empty() { + tracing::warn!(target: "nova_plugin", machine = %machine, "container restart not yet implemented"); + return; + } + let conn = match scope.as_str() { + "user" => Connection::session().await, + _ => Connection::system().await, + }; + let Ok(conn) = conn else { return }; + let Ok(mgr) = SystemdManagerProxy::new(&conn).await else { + return; + }; + if let Err(e) = mgr.restart_unit(&name, "replace").await { + tracing::warn!(target: "nova_plugin", unit = %name, error = %e, "restart_unit failed"); + } + }); + } +}