plugin: rust-side modules + theme services with serde-typed config
This commit is contained in:
parent
a86e90e927
commit
f34f3f2f4e
1 changed files with 2477 additions and 1011 deletions
771
plugin/src/modules_service.rs
Normal file
771
plugin/src/modules_service.rs
Normal file
|
|
@ -0,0 +1,771 @@
|
|||
//! Settings service: parses `$XDG_CONFIG_HOME/nova-shell/modules.json` once at
|
||||
//! plugin load and exposes per-leaf primitive Q_PROPERTYs to QML. Defaults
|
||||
//! mirror the previous QML implementation; a missing file yields all defaults
|
||||
//! silently. Read errors / malformed JSON crash so misconfigurations are loud.
|
||||
//! Unknown fields warn via `tracing` and are otherwise ignored.
|
||||
|
||||
use core::pin::Pin;
|
||||
use cxx_qt::CxxQtType;
|
||||
use cxx_qt_lib::{QList, QString};
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qobject {
|
||||
unsafe extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
|
||||
include!("cxx-qt-lib/core/qlist/qlist_QString.h");
|
||||
type QList_QString = cxx_qt_lib::QList<cxx_qt_lib::QString>;
|
||||
}
|
||||
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// Stats polling interval (-1 = use default).
|
||||
#[qproperty(i32, stats_daemon_interval, cxx_name = "statsDaemonInterval")]
|
||||
// Per-module enables (mirrors `<group>.enable`).
|
||||
#[qproperty(bool, workspaces_enable, cxx_name = "workspacesEnable")]
|
||||
#[qproperty(bool, tray_enable, cxx_name = "trayEnable")]
|
||||
#[qproperty(bool, window_title_enable, cxx_name = "windowTitleEnable")]
|
||||
#[qproperty(bool, clock_enable, cxx_name = "clockEnable")]
|
||||
#[qproperty(bool, mpris_enable, cxx_name = "mprisEnable")]
|
||||
#[qproperty(bool, volume_enable, cxx_name = "volumeEnable")]
|
||||
#[qproperty(bool, bluetooth_enable, cxx_name = "bluetoothEnable")]
|
||||
#[qproperty(bool, network_enable, cxx_name = "networkEnable")]
|
||||
#[qproperty(bool, power_profile_enable, cxx_name = "powerProfileEnable")]
|
||||
#[qproperty(bool, idle_inhibitor_enable, cxx_name = "idleInhibitorEnable")]
|
||||
#[qproperty(bool, cpu_enable, cxx_name = "cpuEnable")]
|
||||
#[qproperty(bool, memory_enable, cxx_name = "memoryEnable")]
|
||||
#[qproperty(bool, privacy_enable, cxx_name = "privacyEnable")]
|
||||
#[qproperty(bool, screen_corners_enable, cxx_name = "screenCornersEnable")]
|
||||
#[qproperty(bool, power_enable, cxx_name = "powerEnable")]
|
||||
#[qproperty(bool, background_overlay_enable, cxx_name = "backgroundOverlayEnable")]
|
||||
#[qproperty(bool, overview_backdrop_enable, cxx_name = "overviewBackdropEnable")]
|
||||
// Notifications.
|
||||
#[qproperty(bool, notifications_enable, cxx_name = "notificationsEnable")]
|
||||
#[qproperty(i32, notifications_timeout, cxx_name = "notificationsTimeout")]
|
||||
#[qproperty(i32, notifications_max_popups, cxx_name = "notificationsMaxPopups")]
|
||||
#[qproperty(i32, notifications_max_visible, cxx_name = "notificationsMaxVisible")]
|
||||
#[qproperty(i32, notifications_max_history, cxx_name = "notificationsMaxHistory")]
|
||||
// Backlight.
|
||||
#[qproperty(bool, backlight_enable, cxx_name = "backlightEnable")]
|
||||
#[qproperty(i32, backlight_step, cxx_name = "backlightStep")]
|
||||
// Weather.
|
||||
#[qproperty(bool, weather_enable, cxx_name = "weatherEnable")]
|
||||
#[qproperty(QList_QString, weather_args, cxx_name = "weatherArgs")]
|
||||
#[qproperty(i32, weather_interval, cxx_name = "weatherInterval")]
|
||||
// Temperature.
|
||||
#[qproperty(bool, temperature_enable, cxx_name = "temperatureEnable")]
|
||||
#[qproperty(i32, temperature_warm, cxx_name = "temperatureWarm")]
|
||||
#[qproperty(i32, temperature_hot, cxx_name = "temperatureHot")]
|
||||
#[qproperty(QString, temperature_device, cxx_name = "temperatureDevice")]
|
||||
// GPU.
|
||||
#[qproperty(bool, gpu_enable, cxx_name = "gpuEnable")]
|
||||
#[qproperty(i32, gpu_warm, cxx_name = "gpuWarm")]
|
||||
#[qproperty(i32, gpu_hot, cxx_name = "gpuHot")]
|
||||
// Disk.
|
||||
#[qproperty(bool, disk_enable, cxx_name = "diskEnable")]
|
||||
#[qproperty(i32, disk_interval, cxx_name = "diskInterval")]
|
||||
#[qproperty(i32, disk_warn_threshold, cxx_name = "diskWarnThreshold")]
|
||||
// Battery.
|
||||
#[qproperty(bool, battery_enable, cxx_name = "batteryEnable")]
|
||||
#[qproperty(bool, battery_poweralertd, cxx_name = "batteryPoweralertd")]
|
||||
#[qproperty(i32, battery_warning, cxx_name = "batteryWarning")]
|
||||
#[qproperty(i32, battery_critical, cxx_name = "batteryCritical")]
|
||||
// Lock screen.
|
||||
#[qproperty(bool, lock_enable, cxx_name = "lockEnable")]
|
||||
#[qproperty(bool, lock_mpris, cxx_name = "lockMpris")]
|
||||
#[qproperty(bool, lock_notifications, cxx_name = "lockNotifications")]
|
||||
#[qproperty(bool, lock_screenshot, cxx_name = "lockScreenshot")]
|
||||
#[qproperty(bool, lock_threat_effect, cxx_name = "lockThreatEffect")]
|
||||
#[qproperty(bool, lock_volume, cxx_name = "lockVolume")]
|
||||
#[qproperty(bool, lock_weather, cxx_name = "lockWeather")]
|
||||
// Dock.
|
||||
#[qproperty(bool, dock_enable, cxx_name = "dockEnable")]
|
||||
#[qproperty(i32, dock_width, cxx_name = "dockWidth")]
|
||||
#[qproperty(bool, dock_applet_clock, cxx_name = "dockAppletClock")]
|
||||
#[qproperty(bool, dock_applet_cpu, cxx_name = "dockAppletCpu")]
|
||||
#[qproperty(bool, dock_applet_gpu, cxx_name = "dockAppletGpu")]
|
||||
#[qproperty(bool, dock_applet_memory, cxx_name = "dockAppletMemory")]
|
||||
#[qproperty(bool, dock_applet_temperature, cxx_name = "dockAppletTemperature")]
|
||||
#[qproperty(bool, dock_applet_disk, cxx_name = "dockAppletDisk")]
|
||||
#[qproperty(bool, dock_applet_battery, cxx_name = "dockAppletBattery")]
|
||||
#[qproperty(bool, dock_applet_network, cxx_name = "dockAppletNetwork")]
|
||||
#[qproperty(bool, dock_applet_bluetooth, cxx_name = "dockAppletBluetooth")]
|
||||
#[qproperty(bool, dock_applet_volume, cxx_name = "dockAppletVolume")]
|
||||
#[qproperty(bool, dock_applet_backlight, cxx_name = "dockAppletBacklight")]
|
||||
#[qproperty(bool, dock_applet_weather, cxx_name = "dockAppletWeather")]
|
||||
#[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.
|
||||
#[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;
|
||||
}
|
||||
|
||||
impl cxx_qt::Initialize for ModulesService {}
|
||||
}
|
||||
|
||||
// ── Pure Rust data layer ─────────────────────────────────────────────────────
|
||||
// One struct per group, plus a top-level `ModulesData`. Defaults mirror the
|
||||
// previous QML implementation, NOT serde's type defaults. `#[serde(default)]`
|
||||
// on every leaf so partial JSON objects get filled in from `Default`.
|
||||
|
||||
mod data {
|
||||
use super::Deserialize;
|
||||
|
||||
fn t() -> bool {
|
||||
true
|
||||
}
|
||||
fn weather_args_default() -> Vec<String> {
|
||||
vec!["--nerd".to_owned()]
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Plain {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
}
|
||||
impl Default for Plain {
|
||||
fn default() -> Self {
|
||||
Self { enable: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Notifications {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Notifications::d_timeout")]
|
||||
pub timeout: i32,
|
||||
#[serde(default = "Notifications::d_max_popups")]
|
||||
pub max_popups: i32,
|
||||
#[serde(default = "Notifications::d_max_visible")]
|
||||
pub max_visible: i32,
|
||||
#[serde(default = "Notifications::d_max_history")]
|
||||
pub max_history: i32,
|
||||
}
|
||||
impl Notifications {
|
||||
fn d_timeout() -> i32 {
|
||||
3000
|
||||
}
|
||||
fn d_max_popups() -> i32 {
|
||||
4
|
||||
}
|
||||
fn d_max_visible() -> i32 {
|
||||
10
|
||||
}
|
||||
fn d_max_history() -> i32 {
|
||||
-1
|
||||
}
|
||||
}
|
||||
impl Default for Notifications {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
timeout: Self::d_timeout(),
|
||||
max_popups: Self::d_max_popups(),
|
||||
max_visible: Self::d_max_visible(),
|
||||
max_history: Self::d_max_history(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Backlight {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Backlight::d_step")]
|
||||
pub step: i32,
|
||||
}
|
||||
impl Backlight {
|
||||
fn d_step() -> i32 {
|
||||
5
|
||||
}
|
||||
}
|
||||
impl Default for Backlight {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
step: Self::d_step(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Weather {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "weather_args_default")]
|
||||
pub args: Vec<String>,
|
||||
#[serde(default = "Weather::d_interval")]
|
||||
pub interval: i32,
|
||||
}
|
||||
impl Weather {
|
||||
fn d_interval() -> i32 {
|
||||
3_600_000
|
||||
}
|
||||
}
|
||||
impl Default for Weather {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
args: weather_args_default(),
|
||||
interval: Self::d_interval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Temperature {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Temperature::d_warm")]
|
||||
pub warm: i32,
|
||||
#[serde(default = "Temperature::d_hot")]
|
||||
pub hot: i32,
|
||||
#[serde(default)]
|
||||
pub device: String,
|
||||
}
|
||||
impl Temperature {
|
||||
fn d_warm() -> i32 {
|
||||
80
|
||||
}
|
||||
fn d_hot() -> i32 {
|
||||
90
|
||||
}
|
||||
}
|
||||
impl Default for Temperature {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
warm: Self::d_warm(),
|
||||
hot: Self::d_hot(),
|
||||
device: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Gpu {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Gpu::d_warm")]
|
||||
pub warm: i32,
|
||||
#[serde(default = "Gpu::d_hot")]
|
||||
pub hot: i32,
|
||||
}
|
||||
impl Gpu {
|
||||
fn d_warm() -> i32 {
|
||||
70
|
||||
}
|
||||
fn d_hot() -> i32 {
|
||||
85
|
||||
}
|
||||
}
|
||||
impl Default for Gpu {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
warm: Self::d_warm(),
|
||||
hot: Self::d_hot(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Disk {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Disk::d_interval")]
|
||||
pub interval: i32,
|
||||
#[serde(default = "Disk::d_warn_threshold")]
|
||||
pub warn_threshold: i32,
|
||||
}
|
||||
impl Disk {
|
||||
fn d_interval() -> i32 {
|
||||
30_000
|
||||
}
|
||||
fn d_warn_threshold() -> i32 {
|
||||
85
|
||||
}
|
||||
}
|
||||
impl Default for Disk {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
interval: Self::d_interval(),
|
||||
warn_threshold: Self::d_warn_threshold(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Battery {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "t")]
|
||||
pub poweralertd: bool,
|
||||
#[serde(default = "Battery::d_warning")]
|
||||
pub warning: i32,
|
||||
#[serde(default = "Battery::d_critical")]
|
||||
pub critical: i32,
|
||||
}
|
||||
impl Battery {
|
||||
fn d_warning() -> i32 {
|
||||
25
|
||||
}
|
||||
fn d_critical() -> i32 {
|
||||
15
|
||||
}
|
||||
}
|
||||
impl Default for Battery {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
poweralertd: true,
|
||||
warning: Self::d_warning(),
|
||||
critical: Self::d_critical(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Lock {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "t")]
|
||||
pub mpris: bool,
|
||||
#[serde(default = "t")]
|
||||
pub notifications: bool,
|
||||
#[serde(default = "t")]
|
||||
pub screenshot: bool,
|
||||
#[serde(default = "t")]
|
||||
pub threat_effect: bool,
|
||||
#[serde(default = "t")]
|
||||
pub volume: bool,
|
||||
#[serde(default = "t")]
|
||||
pub weather: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DockApplets {
|
||||
#[serde(default = "t")]
|
||||
pub clock: bool,
|
||||
#[serde(default = "t")]
|
||||
pub cpu: bool,
|
||||
#[serde(default = "t")]
|
||||
pub gpu: bool,
|
||||
#[serde(default = "t")]
|
||||
pub memory: bool,
|
||||
#[serde(default = "t")]
|
||||
pub temperature: bool,
|
||||
#[serde(default = "t")]
|
||||
pub disk: bool,
|
||||
#[serde(default = "t")]
|
||||
pub battery: bool,
|
||||
#[serde(default = "t")]
|
||||
pub network: bool,
|
||||
#[serde(default = "t")]
|
||||
pub bluetooth: bool,
|
||||
#[serde(default = "t")]
|
||||
pub volume: bool,
|
||||
#[serde(default = "t")]
|
||||
pub backlight: bool,
|
||||
#[serde(default = "t")]
|
||||
pub weather: bool,
|
||||
#[serde(default = "t")]
|
||||
pub mpris: bool,
|
||||
#[serde(default = "t")]
|
||||
pub notifications: bool,
|
||||
#[serde(default = "t")]
|
||||
pub power: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Dock {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "Dock::d_width")]
|
||||
pub width: i32,
|
||||
#[serde(default)]
|
||||
pub applets: DockApplets,
|
||||
}
|
||||
impl Dock {
|
||||
fn d_width() -> i32 {
|
||||
300
|
||||
}
|
||||
}
|
||||
impl Default for Dock {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
width: Self::d_width(),
|
||||
applets: DockApplets::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WithInterval {
|
||||
#[serde(default = "t")]
|
||||
pub enable: bool,
|
||||
#[serde(default = "WithInterval::d_interval")]
|
||||
pub interval: i32,
|
||||
}
|
||||
impl WithInterval {
|
||||
fn d_interval() -> i32 {
|
||||
15_000
|
||||
}
|
||||
}
|
||||
impl Default for WithInterval {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
interval: Self::d_interval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StatsDaemon {
|
||||
#[serde(default = "StatsDaemon::d_interval")]
|
||||
pub interval: i32,
|
||||
}
|
||||
impl StatsDaemon {
|
||||
fn d_interval() -> i32 {
|
||||
-1
|
||||
}
|
||||
}
|
||||
impl Default for StatsDaemon {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interval: Self::d_interval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModulesData {
|
||||
#[serde(default)]
|
||||
pub workspaces: Plain,
|
||||
#[serde(default)]
|
||||
pub tray: Plain,
|
||||
#[serde(default)]
|
||||
pub window_title: Plain,
|
||||
#[serde(default)]
|
||||
pub clock: Plain,
|
||||
#[serde(default)]
|
||||
pub mpris: Plain,
|
||||
#[serde(default)]
|
||||
pub volume: Plain,
|
||||
#[serde(default)]
|
||||
pub bluetooth: Plain,
|
||||
#[serde(default)]
|
||||
pub network: Plain,
|
||||
#[serde(default)]
|
||||
pub power_profile: Plain,
|
||||
#[serde(default)]
|
||||
pub idle_inhibitor: Plain,
|
||||
#[serde(default)]
|
||||
pub cpu: Plain,
|
||||
#[serde(default)]
|
||||
pub memory: Plain,
|
||||
#[serde(default)]
|
||||
pub privacy: Plain,
|
||||
#[serde(default)]
|
||||
pub screen_corners: Plain,
|
||||
#[serde(default)]
|
||||
pub power: Plain,
|
||||
#[serde(default)]
|
||||
pub background_overlay: Plain,
|
||||
#[serde(default)]
|
||||
pub overview_backdrop: Plain,
|
||||
#[serde(default)]
|
||||
pub notifications: Notifications,
|
||||
#[serde(default)]
|
||||
pub backlight: Backlight,
|
||||
#[serde(default)]
|
||||
pub weather: Weather,
|
||||
#[serde(default)]
|
||||
pub temperature: Temperature,
|
||||
#[serde(default)]
|
||||
pub gpu: Gpu,
|
||||
#[serde(default)]
|
||||
pub disk: Disk,
|
||||
#[serde(default)]
|
||||
pub battery: Battery,
|
||||
#[serde(default)]
|
||||
pub lock: Lock,
|
||||
#[serde(default)]
|
||||
pub dock: Dock,
|
||||
#[serde(default)]
|
||||
pub systemd: WithInterval,
|
||||
#[serde(default)]
|
||||
pub machinectl: WithInterval,
|
||||
#[serde(default)]
|
||||
pub stats_daemon: StatsDaemon,
|
||||
}
|
||||
}
|
||||
|
||||
use data::ModulesData;
|
||||
|
||||
pub struct ModulesServiceRust {
|
||||
stats_daemon_interval: i32,
|
||||
workspaces_enable: bool,
|
||||
tray_enable: bool,
|
||||
window_title_enable: bool,
|
||||
clock_enable: bool,
|
||||
mpris_enable: bool,
|
||||
volume_enable: bool,
|
||||
bluetooth_enable: bool,
|
||||
network_enable: bool,
|
||||
power_profile_enable: bool,
|
||||
idle_inhibitor_enable: bool,
|
||||
cpu_enable: bool,
|
||||
memory_enable: bool,
|
||||
privacy_enable: bool,
|
||||
screen_corners_enable: bool,
|
||||
power_enable: bool,
|
||||
background_overlay_enable: bool,
|
||||
overview_backdrop_enable: bool,
|
||||
notifications_enable: bool,
|
||||
notifications_timeout: i32,
|
||||
notifications_max_popups: i32,
|
||||
notifications_max_visible: i32,
|
||||
notifications_max_history: i32,
|
||||
backlight_enable: bool,
|
||||
backlight_step: i32,
|
||||
weather_enable: bool,
|
||||
weather_args: QList<QString>,
|
||||
weather_interval: i32,
|
||||
temperature_enable: bool,
|
||||
temperature_warm: i32,
|
||||
temperature_hot: i32,
|
||||
temperature_device: QString,
|
||||
gpu_enable: bool,
|
||||
gpu_warm: i32,
|
||||
gpu_hot: i32,
|
||||
disk_enable: bool,
|
||||
disk_interval: i32,
|
||||
disk_warn_threshold: i32,
|
||||
battery_enable: bool,
|
||||
battery_poweralertd: bool,
|
||||
battery_warning: i32,
|
||||
battery_critical: i32,
|
||||
lock_enable: bool,
|
||||
lock_mpris: bool,
|
||||
lock_notifications: bool,
|
||||
lock_screenshot: bool,
|
||||
lock_threat_effect: bool,
|
||||
lock_volume: bool,
|
||||
lock_weather: bool,
|
||||
dock_enable: bool,
|
||||
dock_width: i32,
|
||||
dock_applet_clock: bool,
|
||||
dock_applet_cpu: bool,
|
||||
dock_applet_gpu: bool,
|
||||
dock_applet_memory: bool,
|
||||
dock_applet_temperature: bool,
|
||||
dock_applet_disk: bool,
|
||||
dock_applet_battery: bool,
|
||||
dock_applet_network: bool,
|
||||
dock_applet_bluetooth: bool,
|
||||
dock_applet_volume: bool,
|
||||
dock_applet_backlight: bool,
|
||||
dock_applet_weather: bool,
|
||||
dock_applet_mpris: bool,
|
||||
dock_applet_notifications: bool,
|
||||
dock_applet_power: bool,
|
||||
systemd_enable: bool,
|
||||
systemd_interval: i32,
|
||||
machinectl_enable: bool,
|
||||
machinectl_interval: i32,
|
||||
}
|
||||
|
||||
impl Default for ModulesServiceRust {
|
||||
fn default() -> Self {
|
||||
Self::from_data(load_modules_data())
|
||||
}
|
||||
}
|
||||
|
||||
impl ModulesServiceRust {
|
||||
fn from_data(d: ModulesData) -> Self {
|
||||
let mut weather_args = QList::<QString>::default();
|
||||
for s in &d.weather.args {
|
||||
weather_args.append(QString::from(s.as_str()));
|
||||
}
|
||||
Self {
|
||||
stats_daemon_interval: d.stats_daemon.interval,
|
||||
workspaces_enable: d.workspaces.enable,
|
||||
tray_enable: d.tray.enable,
|
||||
window_title_enable: d.window_title.enable,
|
||||
clock_enable: d.clock.enable,
|
||||
mpris_enable: d.mpris.enable,
|
||||
volume_enable: d.volume.enable,
|
||||
bluetooth_enable: d.bluetooth.enable,
|
||||
network_enable: d.network.enable,
|
||||
power_profile_enable: d.power_profile.enable,
|
||||
idle_inhibitor_enable: d.idle_inhibitor.enable,
|
||||
cpu_enable: d.cpu.enable,
|
||||
memory_enable: d.memory.enable,
|
||||
privacy_enable: d.privacy.enable,
|
||||
screen_corners_enable: d.screen_corners.enable,
|
||||
power_enable: d.power.enable,
|
||||
background_overlay_enable: d.background_overlay.enable,
|
||||
overview_backdrop_enable: d.overview_backdrop.enable,
|
||||
notifications_enable: d.notifications.enable,
|
||||
notifications_timeout: d.notifications.timeout,
|
||||
notifications_max_popups: d.notifications.max_popups,
|
||||
notifications_max_visible: d.notifications.max_visible,
|
||||
notifications_max_history: d.notifications.max_history,
|
||||
backlight_enable: d.backlight.enable,
|
||||
backlight_step: d.backlight.step,
|
||||
weather_enable: d.weather.enable,
|
||||
weather_args,
|
||||
weather_interval: d.weather.interval,
|
||||
temperature_enable: d.temperature.enable,
|
||||
temperature_warm: d.temperature.warm,
|
||||
temperature_hot: d.temperature.hot,
|
||||
temperature_device: QString::from(d.temperature.device.as_str()),
|
||||
gpu_enable: d.gpu.enable,
|
||||
gpu_warm: d.gpu.warm,
|
||||
gpu_hot: d.gpu.hot,
|
||||
disk_enable: d.disk.enable,
|
||||
disk_interval: d.disk.interval,
|
||||
disk_warn_threshold: d.disk.warn_threshold,
|
||||
battery_enable: d.battery.enable,
|
||||
battery_poweralertd: d.battery.poweralertd,
|
||||
battery_warning: d.battery.warning,
|
||||
battery_critical: d.battery.critical,
|
||||
lock_enable: d.lock.enable,
|
||||
lock_mpris: d.lock.mpris,
|
||||
lock_notifications: d.lock.notifications,
|
||||
lock_screenshot: d.lock.screenshot,
|
||||
lock_threat_effect: d.lock.threat_effect,
|
||||
lock_volume: d.lock.volume,
|
||||
lock_weather: d.lock.weather,
|
||||
dock_enable: d.dock.enable,
|
||||
dock_width: d.dock.width,
|
||||
dock_applet_clock: d.dock.applets.clock,
|
||||
dock_applet_cpu: d.dock.applets.cpu,
|
||||
dock_applet_gpu: d.dock.applets.gpu,
|
||||
dock_applet_memory: d.dock.applets.memory,
|
||||
dock_applet_temperature: d.dock.applets.temperature,
|
||||
dock_applet_disk: d.dock.applets.disk,
|
||||
dock_applet_battery: d.dock.applets.battery,
|
||||
dock_applet_network: d.dock.applets.network,
|
||||
dock_applet_bluetooth: d.dock.applets.bluetooth,
|
||||
dock_applet_volume: d.dock.applets.volume,
|
||||
dock_applet_backlight: d.dock.applets.backlight,
|
||||
dock_applet_weather: d.dock.applets.weather,
|
||||
dock_applet_mpris: d.dock.applets.mpris,
|
||||
dock_applet_notifications: d.dock.applets.notifications,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn config_path(file: &str) -> PathBuf {
|
||||
let base = dirs::config_dir().unwrap_or_else(|| {
|
||||
// Treat a missing HOME/XDG as a user error, not a silent default; matches
|
||||
// the rule that perm/format issues should surface immediately.
|
||||
panic!("[nova-plugin] could not resolve $XDG_CONFIG_HOME or $HOME for config lookup");
|
||||
});
|
||||
base.join("nova-shell").join(file)
|
||||
}
|
||||
|
||||
fn load_modules_data() -> ModulesData {
|
||||
let path = config_path("modules.json");
|
||||
let raw = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => s,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
tracing::info!(
|
||||
target: "nova_plugin::config",
|
||||
"modules.json not found at {}, using defaults", path.display()
|
||||
);
|
||||
return ModulesData::default();
|
||||
}
|
||||
Err(e) => panic!("[nova-plugin] cannot read {}: {e}", path.display()),
|
||||
};
|
||||
parse_modules(&raw, &path)
|
||||
}
|
||||
|
||||
fn parse_modules(raw: &str, path: &std::path::Path) -> ModulesData {
|
||||
// First pass: catch unknown keys via a generic Value, warn but keep going.
|
||||
if let Ok(serde_json::Value::Object(map)) = serde_json::from_str::<serde_json::Value>(raw) {
|
||||
let known: &[&str] = &[
|
||||
"workspaces",
|
||||
"tray",
|
||||
"windowTitle",
|
||||
"clock",
|
||||
"mpris",
|
||||
"volume",
|
||||
"bluetooth",
|
||||
"network",
|
||||
"powerProfile",
|
||||
"idleInhibitor",
|
||||
"cpu",
|
||||
"memory",
|
||||
"privacy",
|
||||
"screenCorners",
|
||||
"power",
|
||||
"backgroundOverlay",
|
||||
"overviewBackdrop",
|
||||
"notifications",
|
||||
"backlight",
|
||||
"weather",
|
||||
"temperature",
|
||||
"gpu",
|
||||
"disk",
|
||||
"battery",
|
||||
"lock",
|
||||
"dock",
|
||||
"systemd",
|
||||
"machinectl",
|
||||
"statsDaemon",
|
||||
];
|
||||
for key in map.keys() {
|
||||
if !known.contains(&key.as_str()) {
|
||||
tracing::warn!(
|
||||
target: "nova_plugin::config",
|
||||
"{}: unknown top-level key `{key}` ignored", path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_str(raw)
|
||||
.unwrap_or_else(|e| panic!("[nova-plugin] {}: malformed JSON: {e}", path.display()))
|
||||
}
|
||||
|
||||
impl cxx_qt::Initialize for qobject::ModulesService {
|
||||
fn initialize(self: Pin<&mut Self>) {
|
||||
// Default-construct populates from disk; nothing else to do.
|
||||
let _ = self;
|
||||
}
|
||||
}
|
||||
Reference in a new issue