From 96bfc1fd5a68ce5be478a1774233524c9ba6a974 Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 7 May 2026 12:54:44 +0200 Subject: [PATCH 1/2] 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"); + } + }); + } +} From 6fc7e8bc8afdddb3510491f08270c9e7552b302e Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 7 May 2026 17:42:10 +0200 Subject: [PATCH 2/2] merge systemd + machinectl into one module/applet (step 1) --- plugin/Cargo.toml | 8 +- plugin/src/modules_service.rs | 11 +- plugin/src/systemd_service.rs | 15 +- shell/applets/MachinectlApplet.qml | 354 ------------------------ shell/applets/SystemdApplet.qml | 165 ++--------- shell/applets/SystemdMachineSection.qml | 111 ++++++++ shell/applets/SystemdUnitRow.qml | 284 +++++-------------- shell/applets/qmldir | 2 +- shell/modules/Bar.qml | 1 - shell/modules/MachinectlModule.qml | 47 ---- shell/modules/SystemdModule.qml | 26 +- shell/modules/qmldir | 1 - shell/services/MachinectlService.qml | 152 ---------- shell/services/SystemdService.qml | 115 +++----- shell/services/qmldir | 1 - test/qmllint-baseline.txt | 23 +- 16 files changed, 274 insertions(+), 1042 deletions(-) delete mode 100644 shell/applets/MachinectlApplet.qml create mode 100644 shell/applets/SystemdMachineSection.qml delete mode 100644 shell/modules/MachinectlModule.qml delete mode 100644 shell/services/MachinectlService.qml diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml index 5fb8d24..3a1cce9 100644 --- a/plugin/Cargo.toml +++ b/plugin/Cargo.toml @@ -24,7 +24,13 @@ 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"] } +tokio = { version = "1.52.2", features = [ + "macros", + "net", + "rt", + "sync", + "time", +] } [build-dependencies] cxx-qt-build = "0.8.1" diff --git a/plugin/src/modules_service.rs b/plugin/src/modules_service.rs index aea0e77..05e33ad 100644 --- a/plugin/src/modules_service.rs +++ b/plugin/src/modules_service.rs @@ -101,11 +101,9 @@ pub mod qobject { #[qproperty(bool, dock_applet_mpris, cxx_name = "dockAppletMpris")] #[qproperty(bool, dock_applet_notifications, cxx_name = "dockAppletNotifications")] #[qproperty(bool, dock_applet_power, cxx_name = "dockAppletPower")] - // Systemd / machinectl bar modules. + // Unified systemd bar module (covers local + nspawn containers, and later remotes). #[qproperty(bool, systemd_enable, cxx_name = "systemdEnable")] #[qproperty(i32, systemd_interval, cxx_name = "systemdInterval")] - #[qproperty(bool, machinectl_enable, cxx_name = "machinectlEnable")] - #[qproperty(i32, machinectl_interval, cxx_name = "machinectlInterval")] type ModulesService = super::ModulesServiceRust; } @@ -522,8 +520,6 @@ mod data { #[serde(default)] pub systemd: WithInterval, #[serde(default)] - pub machinectl: WithInterval, - #[serde(default)] pub stats_daemon: StatsDaemon, } } @@ -599,8 +595,6 @@ pub struct ModulesServiceRust { dock_applet_power: bool, systemd_enable: bool, systemd_interval: i32, - machinectl_enable: bool, - machinectl_interval: i32, } impl Default for ModulesServiceRust { @@ -684,8 +678,6 @@ impl ModulesServiceRust { dock_applet_power: d.dock.applets.power, systemd_enable: d.systemd.enable, systemd_interval: d.systemd.interval, - machinectl_enable: d.machinectl.enable, - machinectl_interval: d.machinectl.interval, } } } @@ -746,7 +738,6 @@ fn parse_modules(raw: &str, path: &std::path::Path) -> ModulesData { "lock", "dock", "systemd", - "machinectl", "statsDaemon", ]; for key in map.keys() { diff --git a/plugin/src/systemd_service.rs b/plugin/src/systemd_service.rs index 42bfddb..53021d0 100644 --- a/plugin/src/systemd_service.rs +++ b/plugin/src/systemd_service.rs @@ -12,7 +12,7 @@ use cxx_qt_lib::QString; use serde::Serialize; use std::sync::OnceLock; use tokio::runtime::Runtime; -use zbus::{proxy, Connection}; +use zbus::{Connection, proxy}; #[cxx_qt::bridge] pub mod qobject { @@ -72,11 +72,8 @@ trait SystemdManager { fn list_units_filtered(&self, states: Vec<&str>) -> zbus::Result>; - fn restart_unit( - &self, - name: &str, - mode: &str, - ) -> zbus::Result; + fn restart_unit(&self, name: &str, mode: &str) + -> zbus::Result; } #[proxy( @@ -242,9 +239,11 @@ impl qobject::SystemdService { 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_units_json(QString::from(failed_json)); self.as_mut().set_failed_count(count); - self.as_mut().set_containers_json(QString::from(containers_json)); + self.as_mut() + .set_containers_json(QString::from(containers_json)); } fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) { diff --git a/shell/applets/MachinectlApplet.qml b/shell/applets/MachinectlApplet.qml deleted file mode 100644 index 62c7e0e..0000000 --- a/shell/applets/MachinectlApplet.qml +++ /dev/null @@ -1,354 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import "../services" as S -import NovaStats as NS - -Column { - id: root - - required property color accentColor - property bool active: true - - property bool _localExpanded: true - - // Localhost section header - Item { - width: root.width - height: 32 - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: _localHdrHover.hovered ? NS.ThemeService.base02 : "transparent" - radius: NS.ThemeService.radius - z: -1 - } - - HoverHandler { - id: _localHdrHover - } - - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: " localhost" - color: NS.ThemeService.base05 - font.pixelSize: NS.ThemeService.fontSize - font.family: NS.ThemeService.fontFamily - } - - Rectangle { - id: _localStateChip - anchors.right: _localChevron.left - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - visible: S.SystemdService.systemState !== "unknown" - color: { - const st = S.SystemdService.systemState; - if (st === "running") - return NS.ThemeService.base0B; - if (st === "degraded") - return NS.ThemeService.base0A; - return NS.ThemeService.base08; - } - opacity: 0.85 - radius: 3 - width: _localStateLbl.width + 8 - height: 14 - - Text { - id: _localStateLbl - anchors.centerIn: parent - text: S.SystemdService.systemState - color: NS.ThemeService.base00 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - } - } - - Text { - id: _localChevron - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: root._localExpanded ? "" : "" - color: NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.iconFontFamily - } - - TapHandler { - onTapped: root._localExpanded = !root._localExpanded - } - } - - // Localhost expanded content - Column { - visible: root._localExpanded - width: root.width - - // System sub-section - Item { - width: root.width - height: 22 - - Text { - anchors.left: parent.left - anchors.leftMargin: 24 - anchors.verticalCenter: parent.verticalCenter - text: "SYSTEM" - color: NS.ThemeService.base03 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - font.letterSpacing: 1 - } - } - - Repeater { - model: S.SystemdService.systemUnits - delegate: SystemdUnitRow { - required property var modelData - unitName: modelData.name - description: modelData.description - subState: modelData.subState - isUser: false - machineName: "" - accentColor: root.accentColor - } - } - - Item { - visible: S.SystemdService.systemUnits.length === 0 - width: root.width - height: 22 - Text { - anchors.centerIn: parent - text: "no failures" - color: NS.ThemeService.base0B - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - } - - // User sub-section - Item { - width: root.width - height: 22 - - Text { - anchors.left: parent.left - anchors.leftMargin: 24 - anchors.verticalCenter: parent.verticalCenter - text: "USER" - color: NS.ThemeService.base03 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - font.letterSpacing: 1 - } - } - - Repeater { - model: S.SystemdService.userUnits - delegate: SystemdUnitRow { - required property var modelData - unitName: modelData.name - description: modelData.description - subState: modelData.subState - isUser: true - machineName: "" - accentColor: root.accentColor - } - } - - Item { - visible: S.SystemdService.userUnits.length === 0 - width: root.width - height: 22 - Text { - anchors.centerIn: parent - text: "no failures" - color: NS.ThemeService.base0B - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - } - } - - // Containers - Repeater { - model: S.MachinectlService.machines - - delegate: Column { - id: _machineSection - required property var modelData - required property int index - - property bool _expanded: false - property bool _loading: false - - width: root.width - - Connections { - target: S.MachinectlService - function onMachineReady(machineName) { - if (machineName === _machineSection.modelData.name) - _machineSection._loading = false; - } - } - - Separator {} - - // Machine header - Item { - width: _machineSection.width - height: 32 - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: _mHdrHover.hovered ? NS.ThemeService.base02 : "transparent" - radius: NS.ThemeService.radius - z: -1 - } - - HoverHandler { - id: _mHdrHover - } - - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: " " + _machineSection.modelData.name - color: NS.ThemeService.base05 - font.pixelSize: NS.ThemeService.fontSize - font.family: NS.ThemeService.fontFamily - elide: Text.ElideRight - width: parent.width - 100 - } - - Rectangle { - id: _mStateChip - anchors.right: _mChevron.left - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - visible: _machineSection._expanded && !_machineSection._loading - color: { - const st = S.MachinectlService.machineState(_machineSection.modelData.name); - if (st === "running") - return NS.ThemeService.base0B; - if (st === "degraded") - return NS.ThemeService.base0A; - return st === "unknown" ? "transparent" : NS.ThemeService.base08; - } - opacity: 0.85 - radius: 3 - width: _mStateLbl.width + 8 - height: 14 - - Text { - id: _mStateLbl - anchors.centerIn: parent - text: S.MachinectlService.machineState(_machineSection.modelData.name) - color: NS.ThemeService.base00 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - } - } - - Text { - id: _mChevron - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: _machineSection._expanded ? "" : "" - color: NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.iconFontFamily - } - - TapHandler { - onTapped: { - _machineSection._expanded = !_machineSection._expanded; - if (_machineSection._expanded) { - _machineSection._loading = true; - S.MachinectlService.fetchMachine(_machineSection.modelData.name); - } - } - } - } - - // Machine expanded content - Column { - visible: _machineSection._expanded - width: _machineSection.width - - Item { - visible: _machineSection._loading - width: _machineSection.width - height: 28 - Text { - anchors.centerIn: parent - text: "loading..." - color: NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - } - - // System units inside the container - Item { - visible: !_machineSection._loading - width: _machineSection.width - height: 22 - - Text { - anchors.left: parent.left - anchors.leftMargin: 24 - anchors.verticalCenter: parent.verticalCenter - text: "SYSTEM" - color: NS.ThemeService.base03 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - font.letterSpacing: 1 - } - } - - Repeater { - model: !_machineSection._loading ? S.MachinectlService.machineUnits(_machineSection.modelData.name) : [] - delegate: SystemdUnitRow { - required property var modelData - unitName: modelData.name - description: modelData.description - subState: modelData.subState - isUser: false - machineName: _machineSection.modelData.name - accentColor: root.accentColor - } - } - - Item { - visible: !_machineSection._loading && S.MachinectlService.machineUnits(_machineSection.modelData.name).length === 0 - width: _machineSection.width - height: 22 - Text { - anchors.centerIn: parent - text: "no failures" - color: NS.ThemeService.base0B - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - } - } - } - } - - Item { - width: 1 - height: 4 - } -} diff --git a/shell/applets/SystemdApplet.qml b/shell/applets/SystemdApplet.qml index 6462ed3..bd2f1aa 100644 --- a/shell/applets/SystemdApplet.qml +++ b/shell/applets/SystemdApplet.qml @@ -9,156 +9,43 @@ Column { required property color accentColor property bool active: true - // Emitted when content resizes; parent can connect to extend panel-close grace. signal contentResized - // Section header: state label + unit count - Item { + onHeightChanged: root.contentResized() + + // Local machine header + units + SystemdMachineSection { width: root.width - height: 28 - - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "SYSTEM" - color: NS.ThemeService.base03 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - font.letterSpacing: 1 - } - - Rectangle { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - visible: S.SystemdService.systemState !== "unknown" - color: { - const st = S.SystemdService.systemState; - if (st === "running") - return NS.ThemeService.base0B; - if (st === "degraded") - return NS.ThemeService.base0A; - return NS.ThemeService.base08; - } - opacity: 0.85 - radius: 3 - width: _sysStateLbl.width + 8 - height: 14 - - Text { - id: _sysStateLbl - anchors.centerIn: parent - text: S.SystemdService.systemState - color: NS.ThemeService.base00 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - } - } + accentColor: root.accentColor + machineName: "" + title: S.SystemdService.hostname + marker: " this machine" + systemState: S.SystemdService.systemState + units: S.SystemdService.failedUnits + startExpanded: true } + // Containers Repeater { - model: S.SystemdService.systemUnits + model: S.SystemdService.containers - delegate: SystemdUnitRow { + delegate: Column { + id: _row required property var modelData - unitName: modelData.name - description: modelData.description - subState: modelData.subState - isUser: false - machineName: "" - accentColor: root.accentColor - onHeightChanged: root.contentResized() - } - } + width: root.width - Item { - visible: S.SystemdService.systemUnits.length === 0 - width: root.width - height: 24 + Separator {} - Text { - anchors.centerIn: parent - text: "no failed system units" - color: NS.ThemeService.base0B - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - } - - Separator {} - - // User section - Item { - width: root.width - height: 28 - - Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter - text: "USER" - color: NS.ThemeService.base03 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - font.letterSpacing: 1 - } - - Rectangle { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - visible: S.SystemdService.userState !== "unknown" - color: { - const st = S.SystemdService.userState; - if (st === "running") - return NS.ThemeService.base0B; - if (st === "degraded") - return NS.ThemeService.base0A; - return NS.ThemeService.base08; + SystemdMachineSection { + width: _row.width + accentColor: root.accentColor + machineName: _row.modelData.name + title: _row.modelData.name + marker: "" + systemState: _row.modelData.systemState ?? "unknown" + units: _row.modelData.failedUnits ?? [] + startExpanded: (_row.modelData.failedUnits ?? []).length > 0 } - opacity: 0.85 - radius: 3 - width: _userStateLbl.width + 8 - height: 14 - - Text { - id: _userStateLbl - anchors.centerIn: parent - text: S.SystemdService.userState - color: NS.ThemeService.base00 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - } - } - } - - Repeater { - model: S.SystemdService.userUnits - - delegate: SystemdUnitRow { - required property var modelData - unitName: modelData.name - description: modelData.description - subState: modelData.subState - isUser: true - machineName: "" - accentColor: root.accentColor - onHeightChanged: root.contentResized() - } - } - - Item { - visible: S.SystemdService.userUnits.length === 0 - width: root.width - height: 24 - - Text { - anchors.centerIn: parent - text: "no failed user units" - color: NS.ThemeService.base0B - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily } } diff --git a/shell/applets/SystemdMachineSection.qml b/shell/applets/SystemdMachineSection.qml new file mode 100644 index 0000000..632c9db --- /dev/null +++ b/shell/applets/SystemdMachineSection.qml @@ -0,0 +1,111 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import "../services" as S +import NovaStats as NS + +// One section of the systemd applet: a header with title + state chip, +// expandable list of failed units underneath. +Column { + id: root + + required property color accentColor + required property string machineName + required property string title + required property string marker + required property string systemState + required property var units + property bool startExpanded: false + + width: parent?.width ?? 0 + + property bool _expanded: startExpanded + + Item { + width: root.width + height: 32 + + Rectangle { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + color: _hdrHover.hovered ? NS.ThemeService.base02 : "transparent" + radius: NS.ThemeService.radius + z: -1 + } + + HoverHandler { + id: _hdrHover + } + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: _stateChip.left + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + text: root.title + (root.marker !== "" ? " " + root.marker : "") + color: NS.ThemeService.base05 + font.pixelSize: NS.ThemeService.fontSize + font.family: NS.ThemeService.fontFamily + elide: Text.ElideRight + } + + Rectangle { + id: _stateChip + anchors.right: _chevron.left + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + visible: root.systemState !== "unknown" + color: { + const st = root.systemState; + if (st === "running") + return NS.ThemeService.base0B; + if (st === "degraded") + return NS.ThemeService.base0A; + return NS.ThemeService.base08; + } + opacity: 0.85 + radius: 3 + width: _stateLbl.width + 8 + height: 14 + + Text { + id: _stateLbl + anchors.centerIn: parent + text: root.systemState + color: NS.ThemeService.base00 + font.pixelSize: NS.ThemeService.fontSize - 3 + font.family: NS.ThemeService.fontFamily + } + } + + Text { + id: _chevron + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: root._expanded ? "" : "" + color: NS.ThemeService.base04 + font.pixelSize: NS.ThemeService.fontSize - 2 + font.family: NS.ThemeService.iconFontFamily + } + + TapHandler { + onTapped: root._expanded = !root._expanded + } + } + + Repeater { + model: root._expanded ? root.units : [] + delegate: SystemdUnitRow { + required property var modelData + unitName: modelData.name + description: modelData.description ?? "" + subState: modelData.subState ?? "" + scope: modelData.scope ?? "system" + machineName: root.machineName + accentColor: root.accentColor + } + } +} diff --git a/shell/applets/SystemdUnitRow.qml b/shell/applets/SystemdUnitRow.qml index e05d49f..4c4f2a0 100644 --- a/shell/applets/SystemdUnitRow.qml +++ b/shell/applets/SystemdUnitRow.qml @@ -8,233 +8,95 @@ Item { required property string unitName required property string description required property string subState - required property bool isUser - property string machineName: "" + required property string scope + required property string machineName required property color accentColor - property string _jText: "" - property bool _expanded: false - property bool _loading: false - width: parent?.width ?? 0 - height: _row.height + (_expanded ? _journalArea.height : 0) + height: 28 - Behavior on height { - NumberAnimation { - duration: 150 + HoverHandler { + id: _rowHover + } + + Rectangle { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + color: _rowHover.hovered ? NS.ThemeService.base02 : "transparent" + radius: NS.ThemeService.radius + z: -1 + } + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: _subStateTag.left + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + text: root.unitName + color: NS.ThemeService.base05 + font.pixelSize: NS.ThemeService.fontSize - 1 + font.family: NS.ThemeService.fontFamily + elide: Text.ElideRight + } + + Rectangle { + id: _subStateTag + anchors.right: _restartBtn.left + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + color: NS.ThemeService.base08 + opacity: 0.85 + radius: 3 + width: _subStateLbl.width + 8 + height: 14 + + Text { + id: _subStateLbl + anchors.centerIn: parent + text: root.subState + color: NS.ThemeService.base00 + font.pixelSize: NS.ThemeService.fontSize - 3 + font.family: NS.ThemeService.fontFamily } } - Connections { - target: S.SystemdService - enabled: root.machineName === "" - function onJournalReady(unitName, isUser, text) { - if (unitName === root.unitName && isUser === root.isUser) { - root._jText = text; - root._loading = false; - } - } - } - - Connections { - target: S.MachinectlService - enabled: root.machineName !== "" - function onMachineJournalReady(mname, unitName, text) { - if (mname === root.machineName && unitName === root.unitName) { - root._jText = text; - root._loading = false; - } - } - } - - function _fetchJournal() { - root._loading = true; - root._jText = ""; - if (root.machineName === "") - S.SystemdService.fetchJournal(root.unitName, root.isUser); - else - S.MachinectlService.fetchMachineJournal(root.machineName, root.unitName); - } - - function _doRestart() { - if (root.machineName === "") - S.SystemdService.restartUnit(root.unitName, root.isUser); - else - S.MachinectlService.restartMachineUnit(root.machineName, root.unitName); - } - - // Row + // Restart button only visible while hovering the row. = fa-refresh Item { - id: _row - width: parent.width - height: 32 - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: _rowHover.hovered ? NS.ThemeService.base02 : "transparent" - radius: NS.ThemeService.radius - z: -1 - } - - HoverHandler { - id: _rowHover + id: _restartBtn + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + width: 24 + height: 24 + opacity: _rowHover.hovered ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: 80 + } } Text { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: _subStateTag.left - anchors.rightMargin: 6 - anchors.verticalCenter: parent.verticalCenter - text: root.unitName - color: NS.ThemeService.base05 - font.pixelSize: NS.ThemeService.fontSize - 1 - font.family: NS.ThemeService.fontFamily - elide: Text.ElideRight - } - - Rectangle { - id: _subStateTag - anchors.right: _restartBtn.left - anchors.rightMargin: 6 - anchors.verticalCenter: parent.verticalCenter - color: NS.ThemeService.base08 - opacity: 0.85 - radius: 3 - width: _subStateLbl.width + 8 - height: 14 - - Text { - id: _subStateLbl - anchors.centerIn: parent - text: root.subState - color: NS.ThemeService.base00 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - } - } - - // Restart button -  = fa-refresh - Item { - id: _restartBtn - anchors.right: _expandBtn.left - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - width: 24 - height: 24 - - Text { - anchors.centerIn: parent - text: "" - color: _rHover.hovered ? root.accentColor : NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - font.family: NS.ThemeService.iconFontFamily - Behavior on color { - ColorAnimation { - duration: 80 - } - } - } - - HoverHandler { - id: _rHover - cursorShape: Qt.PointingHandCursor - } - TapHandler { - onTapped: { - root._doRestart(); - if (root._expanded) { - root._jText = ""; - Qt.callLater(root._fetchJournal); - } + anchors.centerIn: parent + text: "" + color: _rHover.hovered ? root.accentColor : NS.ThemeService.base04 + font.pixelSize: NS.ThemeService.fontSize + font.family: NS.ThemeService.iconFontFamily + Behavior on color { + ColorAnimation { + duration: 80 } } } - // Expand chevron -  = fa-chevron-down,  = fa-chevron-up - Item { - id: _expandBtn - anchors.right: parent.right - anchors.rightMargin: 4 - anchors.verticalCenter: parent.verticalCenter - width: 24 - height: 24 - - Text { - anchors.centerIn: parent - text: root._expanded ? "" : "" - color: _expHover.hovered ? root.accentColor : NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 1 - font.family: NS.ThemeService.iconFontFamily - Behavior on color { - ColorAnimation { - duration: 80 - } - } - } - - HoverHandler { - id: _expHover - cursorShape: Qt.PointingHandCursor - } - TapHandler { - onTapped: { - root._expanded = !root._expanded; - if (root._expanded && root._jText === "") - root._fetchJournal(); - } - } + HoverHandler { + id: _rHover + cursorShape: Qt.PointingHandCursor } - } - - // Journal area - Item { - id: _journalArea - anchors.top: _row.bottom - width: parent.width - height: 120 - visible: root._expanded - clip: true - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 8 - anchors.rightMargin: 8 - anchors.bottomMargin: 4 - color: NS.ThemeService.base01 - radius: NS.ThemeService.radius - - Text { - anchors.centerIn: parent - visible: root._loading - text: "loading..." - color: NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 2 - font.family: NS.ThemeService.fontFamily - } - - Flickable { - id: _flick - anchors.fill: parent - anchors.margins: 6 - visible: !root._loading - contentHeight: _jContent.height - clip: true - - Text { - id: _jContent - width: _flick.width - text: root._jText - color: NS.ThemeService.base04 - font.pixelSize: NS.ThemeService.fontSize - 3 - font.family: NS.ThemeService.fontFamily - wrapMode: Text.WrapAnywhere - } - - onContentHeightChanged: contentY = Math.max(0, contentHeight - height) - } + TapHandler { + enabled: _rowHover.hovered + onTapped: S.SystemdService.restartUnit(root.unitName, root.scope, root.machineName) } } } diff --git a/shell/applets/qmldir b/shell/applets/qmldir index 355dd6a..d1908e6 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -10,7 +10,6 @@ GpuApplet 1.0 GpuApplet.qml HexWaveBackground 1.0 HexWaveBackground.qml HoverableListItem 1.0 HoverableListItem.qml InfoRow 1.0 InfoRow.qml -MachinectlApplet 1.0 MachinectlApplet.qml MemoryApplet 1.0 MemoryApplet.qml MprisApplet 1.0 MprisApplet.qml NetworkApplet 1.0 NetworkApplet.qml @@ -19,6 +18,7 @@ PowerApplet 1.0 PowerApplet.qml Separator 1.0 Separator.qml SparklineCanvas 1.0 SparklineCanvas.qml SystemdApplet 1.0 SystemdApplet.qml +SystemdMachineSection 1.0 SystemdMachineSection.qml SystemdUnitRow 1.0 SystemdUnitRow.qml TemperatureApplet 1.0 TemperatureApplet.qml VolumeApplet 1.0 VolumeApplet.qml diff --git a/shell/modules/Bar.qml b/shell/modules/Bar.qml index 36af284..21a9009 100644 --- a/shell/modules/Bar.qml +++ b/shell/modules/Bar.qml @@ -187,7 +187,6 @@ PanelWindow { M.WeatherModule {} M.DiskModule {} M.SystemdModule {} - M.MachinectlModule {} } // Power + Dock diff --git a/shell/modules/MachinectlModule.qml b/shell/modules/MachinectlModule.qml deleted file mode 100644 index 6fa8197..0000000 --- a/shell/modules/MachinectlModule.qml +++ /dev/null @@ -1,47 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import "." as M -import "../services" as S -import "../applets" as C -import NovaStats as NS - -M.BarModule { - id: root - active: NS.ModulesService.machinectlEnable - tooltip: { - const n = S.MachinectlService.machines.length; - return n === 0 ? "no containers" : n + " container" + (n === 1 ? "" : "s"); - } - panelNamespace: "nova-machinectl" - panelContentWidth: 320 - panelComponent: Component { - C.MachinectlApplet { - width: parent.width - accentColor: root.accentColor - active: root._showPanel - } - } - - Connections { - target: S.MachinectlService - function onMachineReady() { - root.keepPanelOpen(300); - } - } - - readonly property color _stateColor: S.MachinectlService.anyUnhealthy ? NS.ThemeService.base0A : root.accentColor - - M.BarIcon { - icon: "" - color: root._stateColor - anchors.verticalCenter: parent.verticalCenter - } - M.BarLabel { - label: S.MachinectlService.machines.length.toString() - minText: "9" - color: root._stateColor - anchors.verticalCenter: parent.verticalCenter - } -} diff --git a/shell/modules/SystemdModule.qml b/shell/modules/SystemdModule.qml index b558592..582bcaf 100644 --- a/shell/modules/SystemdModule.qml +++ b/shell/modules/SystemdModule.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import Quickshell import "." as M import "../services" as S import "../applets" as C @@ -11,12 +10,11 @@ M.BarModule { id: root active: NS.ModulesService.systemdEnable tooltip: { - const sys = S.SystemdService.systemState; - const fc = S.SystemdService.totalFailedCount; - return "systemd: " + sys + (fc > 0 ? " (" + fc + " failed)" : ""); + const fc = S.SystemdService.failedCount; + return fc === 0 ? "systemd: ok" : "systemd: " + fc + " failed"; } panelNamespace: "nova-systemd" - panelContentWidth: 300 + panelContentWidth: 320 panelComponent: Component { C.SystemdApplet { width: parent.width @@ -28,22 +26,12 @@ M.BarModule { Connections { target: S.SystemdService - function onSystemUnitsChanged() { - root.keepPanelOpen(300); - } - function onUserUnitsChanged() { + function onFailedUnitsChanged() { root.keepPanelOpen(300); } } - readonly property color _stateColor: { - const st = S.SystemdService.systemState; - if (st === "running") - return root.accentColor; - if (st === "degraded") - return NS.ThemeService.base0A; - return NS.ThemeService.base08; - } + readonly property color _stateColor: S.SystemdService.failedCount > 0 ? NS.ThemeService.base0A : root.accentColor M.BarIcon { icon: "" @@ -51,8 +39,8 @@ M.BarModule { anchors.verticalCenter: parent.verticalCenter } M.BarLabel { - label: S.SystemdService.totalFailedCount > 0 ? S.SystemdService.totalFailedCount + " failed" : S.SystemdService.systemState - minText: "degraded" + label: S.SystemdService.failedCount > 0 ? S.SystemdService.failedCount + " failed" : "ok" + minText: "0 failed" color: root._stateColor anchors.verticalCenter: parent.verticalCenter } diff --git a/shell/modules/qmldir b/shell/modules/qmldir index fbdec44..87c86df 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -16,7 +16,6 @@ DockModule 1.0 DockModule.qml GpuModule 1.0 GpuModule.qml HoverPanel 1.0 HoverPanel.qml IdleInhibitorModule 1.0 IdleInhibitorModule.qml -MachinectlModule 1.0 MachinectlModule.qml MemoryModule 1.0 MemoryModule.qml MprisModule 1.0 MprisModule.qml NetworkModule 1.0 NetworkModule.qml diff --git a/shell/services/MachinectlService.qml b/shell/services/MachinectlService.qml deleted file mode 100644 index 30a39f0..0000000 --- a/shell/services/MachinectlService.qml +++ /dev/null @@ -1,152 +0,0 @@ -pragma Singleton - -import QtQuick -import Quickshell.Io -import "." as S -import NovaStats as NS - -QtObject { - id: root - - property var machines: [] - - // cache: machineName -> {state, units, loading} - property var _cache: ({}) - - readonly property bool anyUnhealthy: { - for (const k of Object.keys(_cache)) { - if ((_cache[k]?.units?.length ?? 0) > 0) - return true; - } - return false; - } - - function machineState(name) { - return _cache[name]?.state ?? "unknown"; - } - function machineUnits(name) { - return _cache[name]?.units ?? []; - } - function machineLoading(name) { - return _cache[name]?.loading ?? false; - } - - signal machineReady(string machineName, string state, var units) - signal machineJournalReady(string machineName, string unitName, string text) - - // Per-call state: kept on root (not on the inline Process objects) so qmllint can see them. - property string _machineName: "" - property string _journalMachine: "" - property string _journalUnit: "" - property string _restartMachine: "" - - function fetchMachine(name) { - const c = Object.assign({}, _cache); - c[name] = Object.assign({}, c[name] ?? {}, { - loading: true - }); - _cache = c; - - root._machineName = name; - _machineProc.command = ["sh", "-c", "busctl get-property --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl call --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'"]; - if (_machineProc.running) - _machineProc.running = false; - _machineProc.running = true; - } - - function fetchMachineJournal(machineName, unitName) { - root._journalMachine = machineName; - root._journalUnit = unitName; - _machineJournalProc.command = ["journalctl", "-M", machineName, "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"]; - if (_machineJournalProc.running) - _machineJournalProc.running = false; - _machineJournalProc.running = true; - } - - function restartMachineUnit(machineName, unitName) { - root._restartMachine = machineName; - _machineRestartProc.command = ["pkexec", "systemctl", "-M", machineName, "restart", unitName]; - if (_machineRestartProc.running) - _machineRestartProc.running = false; - _machineRestartProc.running = true; - } - - property Timer _poll: Timer { - interval: NS.ModulesService.machinectlInterval ?? 15000 - running: NS.ModulesService.machinectlEnable - repeat: true - triggeredOnStart: true - onTriggered: if (!root._listProc.running) - root._listProc.running = true - } - - property Process _listProc: Process { - command: ["busctl", "call", "--json=short", "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", "ListMachines"] - stdout: StdioCollector { - onStreamFinished: { - try { - const parsed = JSON.parse(text.trim()); - const newMachines = (parsed.data?.[0] || []).map(m => ({ - name: m[0], - class: m[1], - service: m[2] - })); - root.machines = newMachines; - // drop stale cache entries - const names = new Set(newMachines.map(m => m.name)); - const c = {}; - for (const k of Object.keys(root._cache)) { - if (names.has(k)) - c[k] = root._cache[k]; - } - root._cache = c; - } catch (e) { - root.machines = []; - } - } - } - } - - // 2 lines of output: state, failed-units - property Process _machineProc: Process { - stdout: StdioCollector { - onStreamFinished: { - const lines = text.trim().split("\n"); - let state = "unknown"; - let units = []; - try { - state = JSON.parse(lines[0] ?? "").data || "unknown"; - } catch (e) {} - try { - const parsed = JSON.parse(lines[1] ?? ""); - units = (parsed.data?.[0] || []).map(u => ({ - name: u[0], - description: u[1], - loadState: u[2], - activeState: u[3], - subState: u[4] - })); - } catch (e) {} - const c = Object.assign({}, root._cache); - c[root._machineName] = { - state: state, - units: units, - loading: false - }; - root._cache = c; - root.machineReady(root._machineName, state, units); - } - } - } - - property Process _machineJournalProc: Process { - stdout: StdioCollector { - onStreamFinished: root.machineJournalReady(root._journalMachine, root._journalUnit, text) - } - } - - property Process _machineRestartProc: Process { - onRunningChanged: if (!running && root._restartMachine !== "") - root.fetchMachine(root._restartMachine) - } -} diff --git a/shell/services/SystemdService.qml b/shell/services/SystemdService.qml index afe9fcc..4ba5f38 100644 --- a/shell/services/SystemdService.qml +++ b/shell/services/SystemdService.qml @@ -1,100 +1,53 @@ pragma Singleton import QtQuick -import Quickshell.Io -import "." as S import NovaStats as NS +// Thin wrapper around NS.SystemdService: drives the poll Timer and parses the +// JSON-encoded list properties into JS arrays for QML consumers. Restart helper +// proxies through to the Rust singleton. QtObject { id: root - property string systemState: "unknown" - property string userState: "unknown" - property var systemUnits: [] - property var userUnits: [] - readonly property int totalFailedCount: systemUnits.length + userUnits.length + readonly property string hostname: NS.SystemdService.hostname + readonly property string systemState: NS.SystemdService.systemState + readonly property string userState: NS.SystemdService.userState + readonly property int failedCount: NS.SystemdService.failedCount - signal journalReady(string unitName, bool isUser, string text) - - function refresh() { - if (!_pollProc.running) - _pollProc.running = true; - } - - // Per-fetch journal state: kept here (not on the Process) so qmllint can see them. - property string _journalUnitName: "" - property bool _journalIsUser: false - - function fetchJournal(unitName, isUser) { - _journalProc.command = isUser ? ["journalctl", "--user", "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"] : ["journalctl", "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"]; - root._journalUnitName = unitName; - root._journalIsUser = isUser; - if (_journalProc.running) - _journalProc.running = false; - _journalProc.running = true; - } - - function restartUnit(unitName, isUser) { - _restartProc.command = isUser ? ["systemctl", "--user", "restart", unitName] : ["pkexec", "systemctl", "restart", unitName]; - if (_restartProc.running) - _restartProc.running = false; - _restartProc.running = true; - } - - function _parseState(json) { + // Parsed [{ name, description, subState, scope, machine }, ...] + readonly property var failedUnits: { try { - return JSON.parse(json).data || "unknown"; - } catch (e) { - return "unknown"; - } - } - - function _parseUnits(json) { - try { - const parsed = JSON.parse(json); - return (parsed.data?.[0] || []).map(u => ({ - name: u[0], - description: u[1], - loadState: u[2], - activeState: u[3], - subState: u[4] - })); + return JSON.parse(NS.SystemdService.failedUnitsJson); } catch (e) { return []; } } - property Timer _poll: Timer { - interval: NS.ModulesService.systemdInterval ?? 15000 + // Parsed [{ name, class, service, systemState, failedUnits: [...] }, ...] + readonly property var containers: { + try { + return JSON.parse(NS.SystemdService.containersJson); + } catch (e) { + return []; + } + } + + function restartUnit(name, scope, machine) { + NS.SystemdService.restartUnit(name, scope ?? "system", machine ?? ""); + } + + function refresh() { + NS.SystemdService.poll(); + } + + property Timer _pollTimer: Timer { + interval: { + const ms = NS.ModulesService.systemdInterval; + return ms > 0 ? ms : 15000; + } running: NS.ModulesService.systemdEnable repeat: true - triggeredOnStart: true - onTriggered: if (!root._pollProc.running) - root._pollProc.running = true - } - - // 4 lines of output: systemState, systemUnits, userState, userUnits - property Process _pollProc: Process { - command: ["sh", "-c", "busctl get-property --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl call --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'; " + "busctl --user get-property --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl --user call --json=short org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'"] - stdout: StdioCollector { - onStreamFinished: { - const lines = text.trim().split("\n"); - root.systemState = root._parseState(lines[0] ?? ""); - root.systemUnits = root._parseUnits(lines[1] ?? ""); - root.userState = root._parseState(lines[2] ?? ""); - root.userUnits = root._parseUnits(lines[3] ?? ""); - } - } - } - - property Process _journalProc: Process { - stdout: StdioCollector { - onStreamFinished: root.journalReady(root._journalUnitName, root._journalIsUser, text) - } - } - - property Process _restartProc: Process { - onRunningChanged: if (!running) - root.refresh() + triggeredOnStart: false + onTriggered: NS.SystemdService.poll() } } diff --git a/shell/services/qmldir b/shell/services/qmldir index 6169b75..59bfa3d 100644 --- a/shell/services/qmldir +++ b/shell/services/qmldir @@ -8,7 +8,6 @@ singleton CpuService 1.0 CpuService.qml singleton DockState 1.0 DockState.qml singleton IdleInhibitService 1.0 IdleInhibitService.qml singleton LockService 1.0 LockService.qml -singleton MachinectlService 1.0 MachinectlService.qml singleton MprisService 1.0 MprisService.qml singleton NetworkService 1.0 NetworkService.qml singleton NiriIpc 1.0 NiriIpc.qml diff --git a/test/qmllint-baseline.txt b/test/qmllint-baseline.txt index 79d3638..0baf41e 100644 --- a/test/qmllint-baseline.txt +++ b/test/qmllint-baseline.txt @@ -36,14 +36,6 @@ shell/applets/HexWaveBackground.qml: Type "QColor" of property "base0E" not foun shell/applets/HoverableListItem.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/InfoRow.qml: Type "QColor" of property "base04" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/InfoRow.qml: Type "QColor" of property "base05" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base00" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base04" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base05" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/MachinectlApplet.qml: Type "QColor" of property "base0B" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/MemoryApplet.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/MemoryApplet.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/MemoryApplet.qml: Type "QColor" of property "base0D" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] @@ -78,13 +70,14 @@ shell/applets/PowerApplet.qml: Type "QColor" of property "base0D" not found. Thi shell/applets/PowerApplet.qml: Type "QColor" of property "base0E" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/PowerApplet.qml: Unqualified access [unqualified] shell/applets/Separator.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdApplet.qml: Type "QColor" of property "base00" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdApplet.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdApplet.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdApplet.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdApplet.qml: Type "QColor" of property "base0B" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base00" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base04" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base05" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] +shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base0B" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/SystemdUnitRow.qml: Type "QColor" of property "base00" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/applets/SystemdUnitRow.qml: Type "QColor" of property "base01" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/SystemdUnitRow.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/SystemdUnitRow.qml: Type "QColor" of property "base04" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/applets/SystemdUnitRow.qml: Type "QColor" of property "base05" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] @@ -182,7 +175,6 @@ shell/modules/HoverPanel.qml: Type PanelWindow is not creatable. [uncreatable-ty shell/modules/HoverPanel.qml: Type margins is used but it is not resolved [unresolved-type] shell/modules/HoverPanel.qml: unknown grouped property scope margins. [unqualified] shell/modules/IdleInhibitorModule.qml: Type "QColor" of property "base09" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] -shell/modules/MachinectlModule.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/modules/MemoryModule.qml: Unqualified access [unqualified] shell/modules/MprisModule.qml: Unqualified access [unqualified] shell/modules/NetworkModule.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] @@ -218,7 +210,6 @@ shell/modules/ScreenCorners.qml: Type PanelWindow is not creatable. [uncreatable shell/modules/ScreenCorners.qml: Type margins is used but it is not resolved [unresolved-type] shell/modules/ScreenCorners.qml: Unqualified access [unqualified] shell/modules/ScreenCorners.qml: unknown grouped property scope margins. [unqualified] -shell/modules/SystemdModule.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/modules/SystemdModule.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/modules/TemperatureModule.qml: Type "QColor" of property "base08" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type] shell/modules/TemperatureModule.qml: Type "QColor" of property "base0A" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]