central registry

This commit is contained in:
2026-04-05 11:35:57 +02:00
parent 708317a10b
commit fdfd54b518
5 changed files with 183 additions and 309 deletions
+36
View File
@@ -0,0 +1,36 @@
/// Central module registry. Defines all modules with watch channels in one place.
///
/// Invoke with a callback macro name. The callback receives repeated entries of the form:
/// { $feature:literal, $field:ident, $state:ty, [$($name:literal),+], [$($sig_name:literal),+], $module:path, $signal:ident, [$($default_arg:literal),*], $config:ident }
///
/// Fields:
/// - feature: Cargo feature gate (e.g., "mod-network")
/// - field: AppReceivers field name (e.g., network)
/// - state: State type for the watch channel (e.g., NetworkState)
/// - names: CLI name aliases for dispatch (e.g., ["net", "network"])
/// - signaler_names: Waybar module names to signal when the channel fires
/// (usually [first dispatch name], but audio signals ["vol", "mic"])
/// - module: Module struct implementing WaybarModule (e.g., network::NetworkModule)
/// - signal: SignalsConfig field name (e.g., network)
/// - default_args: Default args for signaler evaluation
/// - config: Config section field name (e.g., network)
///
/// Modules without watch channels (power, game, pool/btrfs) are handled manually.
macro_rules! for_each_watched_module {
($m:ident) => {
$m! {
{ "mod-network", network, crate::state::NetworkState, ["net", "network"], ["net"], crate::modules::network::NetworkModule, network, [], network }
{ "mod-hardware", cpu, crate::state::CpuState, ["cpu"], ["cpu"], crate::modules::cpu::CpuModule, cpu, [], cpu }
{ "mod-hardware", memory, crate::state::MemoryState, ["mem", "memory"], ["mem"], crate::modules::memory::MemoryModule, memory, [], memory }
{ "mod-hardware", sys, crate::state::SysState, ["sys"], ["sys"], crate::modules::sys::SysModule, sys, [], sys }
{ "mod-hardware", gpu, crate::state::GpuState, ["gpu"], ["gpu"], crate::modules::gpu::GpuModule, gpu, [], gpu }
{ "mod-hardware", disks, Vec<crate::state::DiskInfo>, ["disk"], ["disk"], crate::modules::disk::DiskModule, disk, ["/"], disk }
{ "mod-bt", bluetooth, crate::state::BtState, ["bt", "bluetooth"], ["bt"], crate::modules::bt::BtModule, bt, ["show"], bt }
{ "mod-audio", audio, crate::state::AudioState, ["vol", "audio"], ["vol", "mic"], crate::modules::audio::AudioModule, audio, ["sink", "show"], audio }
{ "mod-dbus", mpris, crate::state::MprisState, ["mpris"], ["mpris"], crate::modules::mpris::MprisModule, mpris, [], mpris }
{ "mod-dbus", backlight, crate::state::BacklightState, ["backlight"], ["backlight"], crate::modules::backlight::BacklightModule, backlight, [], backlight }
{ "mod-dbus", keyboard, crate::state::KeyboardState, ["kbd", "keyboard"], ["kbd"], crate::modules::keyboard::KeyboardModule, keyboard, [], keyboard }
{ "mod-dbus", dnd, crate::state::DndState, ["dnd"], ["dnd"], crate::modules::dnd::DndModule, dnd, [], dnd }
}
};
}
+2
View File
@@ -1,3 +1,5 @@
#[macro_use]
mod macros;
mod config; mod config;
mod daemon; mod daemon;
mod error; mod error;
+24 -78
View File
@@ -6,53 +6,27 @@ use crate::state::AppReceivers;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::modules::WaybarModule; use crate::modules::WaybarModule;
pub async fn dispatch( macro_rules! gen_dispatch {
($( { $feature:literal, $field:ident, $state:ty, [$($name:literal),+], [$($sig_name:literal),+], $module:path, $signal:ident, [$($default_arg:literal),*], $config:ident } )*) => {
pub async fn dispatch(
module_name: &str, module_name: &str,
#[allow(unused)] config: &Config, #[allow(unused)] config: &Config,
#[allow(unused)] state: &AppReceivers, #[allow(unused)] state: &AppReceivers,
#[allow(unused)] args: &[&str], #[allow(unused)] args: &[&str],
) -> FluxoResult<WaybarOutput> { ) -> FluxoResult<WaybarOutput> {
if !config.is_module_enabled(module_name) { if !config.is_module_enabled(module_name) {
return Err(FluxoError::Disabled(module_name.to_string())); return Err(FluxoError::Disabled(module_name.to_string()));
} }
match module_name { match module_name {
#[cfg(feature = "mod-network")] $(
"net" | "network" => { #[cfg(feature = $feature)]
crate::modules::network::NetworkModule $($name)|+ => {
.run(config, state, args) $module.run(config, state, args).await
.await
}
#[cfg(feature = "mod-hardware")]
"cpu" => {
crate::modules::cpu::CpuModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-hardware")]
"mem" | "memory" => {
crate::modules::memory::MemoryModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-hardware")]
"disk" => {
crate::modules::disk::DiskModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-hardware")]
"pool" | "btrfs" => {
crate::modules::btrfs::BtrfsModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-audio")]
"vol" | "audio" => {
crate::modules::audio::AudioModule
.run(config, state, args)
.await
} }
)*
// Dispatch-only modules (no watch channel)
#[cfg(feature = "mod-audio")] #[cfg(feature = "mod-audio")]
"mic" => { "mic" => {
crate::modules::audio::AudioModule crate::modules::audio::AudioModule
@@ -60,20 +34,6 @@ pub async fn dispatch(
.await .await
} }
#[cfg(feature = "mod-hardware")] #[cfg(feature = "mod-hardware")]
"gpu" => {
crate::modules::gpu::GpuModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-hardware")]
"sys" => {
crate::modules::sys::SysModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-bt")]
"bt" | "bluetooth" => crate::modules::bt::BtModule.run(config, state, args).await,
#[cfg(feature = "mod-hardware")]
"power" => { "power" => {
crate::modules::power::PowerModule crate::modules::power::PowerModule
.run(config, state, args) .run(config, state, args)
@@ -85,41 +45,27 @@ pub async fn dispatch(
.run(config, state, args) .run(config, state, args)
.await .await
} }
#[cfg(feature = "mod-dbus")] #[cfg(feature = "mod-hardware")]
"backlight" => { "pool" | "btrfs" => {
crate::modules::backlight::BacklightModule crate::modules::btrfs::BtrfsModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-dbus")]
"kbd" | "keyboard" => {
crate::modules::keyboard::KeyboardModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-dbus")]
"dnd" => {
crate::modules::dnd::DndModule
.run(config, state, args)
.await
}
#[cfg(feature = "mod-dbus")]
"mpris" => {
crate::modules::mpris::MprisModule
.run(config, state, args) .run(config, state, args)
.await .await
} }
_ => Err(FluxoError::Ipc(format!("Unknown module: {}", module_name))), _ => Err(FluxoError::Ipc(format!("Unknown module: {}", module_name))),
} }
} }
/// Returns the default args used by the signaler when evaluating a module. /// Returns the default args used by the signaler when evaluating a module.
pub fn signaler_default_args(module_name: &str) -> &'static [&'static str] { pub fn signaler_default_args(module_name: &str) -> &'static [&'static str] {
match module_name { match module_name {
"disk" => &["/"], $(
"vol" | "audio" => &["sink", "show"], $($name)|+ => &[$($default_arg),*],
)*
"mic" => &["source", "show"], "mic" => &["source", "show"],
"bt" | "bluetooth" => &["show"],
_ => &[], _ => &[],
} }
}
};
} }
for_each_watched_module!(gen_dispatch);
+26 -121
View File
@@ -63,7 +63,11 @@ impl WaybarSignaler {
debug!("Waybar process not found, skipping signal."); debug!("Waybar process not found, skipping signal.");
} }
} }
}
macro_rules! gen_signaler_run {
($( { $feature:literal, $field:ident, $state:ty, [$($name:literal),+], [$($sig_name:literal),+], $module:path, $signal:ident, [$($default_arg:literal),*], $config:ident } )*) => {
impl WaybarSignaler {
pub async fn run(mut self, config_lock: Arc<RwLock<Config>>, mut receivers: AppReceivers) { pub async fn run(mut self, config_lock: Arc<RwLock<Config>>, mut receivers: AppReceivers) {
let mut last_outputs: HashMap<&'static str, String> = HashMap::new(); let mut last_outputs: HashMap<&'static str, String> = HashMap::new();
@@ -90,146 +94,47 @@ impl WaybarSignaler {
}; };
} }
// For disabled features, create futures that never resolve // Generate cfg-gated futures for each watched module
#[cfg(not(feature = "mod-network"))] $(
let net_changed = std::future::pending::< #[cfg(not(feature = $feature))]
let $field = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>, std::result::Result<(), tokio::sync::watch::error::RecvError>,
>(); >();
#[cfg(feature = "mod-network")] #[cfg(feature = $feature)]
let net_changed = receivers.network.changed(); let $field = receivers.$field.changed();
)*
#[cfg(not(feature = "mod-hardware"))]
let cpu_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-hardware")]
let cpu_changed = receivers.cpu.changed();
#[cfg(not(feature = "mod-hardware"))]
let mem_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-hardware")]
let mem_changed = receivers.memory.changed();
#[cfg(not(feature = "mod-hardware"))]
let sys_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-hardware")]
let sys_changed = receivers.sys.changed();
#[cfg(not(feature = "mod-hardware"))]
let gpu_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-hardware")]
let gpu_changed = receivers.gpu.changed();
#[cfg(not(feature = "mod-hardware"))]
let disks_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-hardware")]
let disks_changed = receivers.disks.changed();
#[cfg(not(feature = "mod-bt"))]
let bt_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-bt")]
let bt_changed = receivers.bluetooth.changed();
#[cfg(not(feature = "mod-audio"))]
let audio_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-audio")]
let audio_changed = receivers.audio.changed();
// MPRIS scroll tick (special case — not a watched module)
#[cfg(not(feature = "mod-dbus"))] #[cfg(not(feature = "mod-dbus"))]
let backlight_changed = std::future::pending::< let mpris_scroll_tick = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>, std::result::Result<(), tokio::sync::watch::error::RecvError>,
>(); >();
#[cfg(feature = "mod-dbus")] #[cfg(feature = "mod-dbus")]
let backlight_changed = receivers.backlight.changed(); let mpris_scroll_tick = receivers.mpris_scroll_tick.changed();
#[cfg(not(feature = "mod-dbus"))]
let keyboard_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-dbus")]
let keyboard_changed = receivers.keyboard.changed();
#[cfg(not(feature = "mod-dbus"))]
let dnd_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-dbus")]
let dnd_changed = receivers.dnd.changed();
#[cfg(not(feature = "mod-dbus"))]
let mpris_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-dbus")]
let mpris_changed = receivers.mpris.changed();
#[cfg(not(feature = "mod-dbus"))]
let mpris_scroll_tick_changed = std::future::pending::<
std::result::Result<(), tokio::sync::watch::error::RecvError>,
>();
#[cfg(feature = "mod-dbus")]
let mpris_scroll_tick_changed = receivers.mpris_scroll_tick.changed();
tokio::select! { tokio::select! {
res = net_changed, if signals.network.is_some() => { $(
if res.is_ok() { check_and_signal!("net", signals.network); } res = $field, if signals.$signal.is_some() => {
}
res = cpu_changed, if signals.cpu.is_some() => {
if res.is_ok() { check_and_signal!("cpu", signals.cpu); }
}
res = mem_changed, if signals.memory.is_some() => {
if res.is_ok() { check_and_signal!("mem", signals.memory); }
}
res = sys_changed, if signals.sys.is_some() => {
if res.is_ok() { check_and_signal!("sys", signals.sys); }
}
res = gpu_changed, if signals.gpu.is_some() => {
if res.is_ok() { check_and_signal!("gpu", signals.gpu); }
}
res = disks_changed, if signals.disk.is_some() => {
if res.is_ok() { check_and_signal!("disk", signals.disk); }
}
res = bt_changed, if signals.bt.is_some() => {
if res.is_ok() { check_and_signal!("bt", signals.bt); }
}
res = audio_changed, if signals.audio.is_some() => {
if res.is_ok() { if res.is_ok() {
check_and_signal!("vol", signals.audio); $(check_and_signal!($sig_name, signals.$signal);)+
check_and_signal!("mic", signals.audio);
} }
} }
res = backlight_changed, if signals.backlight.is_some() => { )*
if res.is_ok() { check_and_signal!("backlight", signals.backlight); }
} // MPRIS scroll tick (separate from mpris data changes)
res = keyboard_changed, if signals.keyboard.is_some() => { res = mpris_scroll_tick, if signals.mpris.is_some() => {
if res.is_ok() { check_and_signal!("keyboard", signals.keyboard); }
}
res = dnd_changed, if signals.dnd.is_some() => {
if res.is_ok() { check_and_signal!("dnd", signals.dnd); }
}
res = mpris_changed, if signals.mpris.is_some() => {
if res.is_ok() { check_and_signal!("mpris", signals.mpris); }
}
res = mpris_scroll_tick_changed, if signals.mpris.is_some() => {
if res.is_ok() if res.is_ok()
&& let Some(sig) = signals.mpris { self.send_signal(sig); } && let Some(sig) = signals.mpris { self.send_signal(sig); }
} }
_ = sleep(Duration::from_secs(5)) => { _ = sleep(Duration::from_secs(5)) => {
// loop and refresh config // loop and refresh config
} }
} }
} }
} }
}
};
} }
for_each_watched_module!(gen_signaler_run);
+11 -26
View File
@@ -4,34 +4,16 @@ use std::sync::Arc;
use tokio::sync::{RwLock, mpsc, watch}; use tokio::sync::{RwLock, mpsc, watch};
use tokio::time::Instant; use tokio::time::Instant;
#[derive(Clone)] macro_rules! gen_app_receivers {
pub struct AppReceivers { ($( { $feature:literal, $field:ident, $state:ty, [$($name:literal),+], [$($sig_name:literal),+], $module:path, $signal:ident, [$($default_arg:literal),*], $config:ident } )*) => {
#[cfg(feature = "mod-network")] #[derive(Clone)]
pub network: watch::Receiver<NetworkState>, pub struct AppReceivers {
#[cfg(feature = "mod-hardware")] $(
pub cpu: watch::Receiver<CpuState>, #[cfg(feature = $feature)]
#[cfg(feature = "mod-hardware")] pub $field: watch::Receiver<$state>,
pub memory: watch::Receiver<MemoryState>, )*
#[cfg(feature = "mod-hardware")]
pub sys: watch::Receiver<SysState>,
#[cfg(feature = "mod-hardware")]
pub gpu: watch::Receiver<GpuState>,
#[cfg(feature = "mod-hardware")]
pub disks: watch::Receiver<Vec<DiskInfo>>,
#[cfg(feature = "mod-bt")]
pub bluetooth: watch::Receiver<BtState>,
#[cfg(feature = "mod-bt")] #[cfg(feature = "mod-bt")]
pub bt_cycle: Arc<RwLock<usize>>, pub bt_cycle: Arc<RwLock<usize>>,
#[cfg(feature = "mod-audio")]
pub audio: watch::Receiver<AudioState>,
#[cfg(feature = "mod-dbus")]
pub mpris: watch::Receiver<MprisState>,
#[cfg(feature = "mod-dbus")]
pub backlight: watch::Receiver<BacklightState>,
#[cfg(feature = "mod-dbus")]
pub keyboard: watch::Receiver<KeyboardState>,
#[cfg(feature = "mod-dbus")]
pub dnd: watch::Receiver<DndState>,
#[cfg(feature = "mod-dbus")] #[cfg(feature = "mod-dbus")]
pub mpris_scroll: Arc<RwLock<MprisScrollState>>, pub mpris_scroll: Arc<RwLock<MprisScrollState>>,
#[cfg(feature = "mod-dbus")] #[cfg(feature = "mod-dbus")]
@@ -41,7 +23,10 @@ pub struct AppReceivers {
pub bt_force_poll: mpsc::Sender<()>, pub bt_force_poll: mpsc::Sender<()>,
#[cfg(feature = "mod-audio")] #[cfg(feature = "mod-audio")]
pub audio_cmd_tx: mpsc::Sender<crate::modules::audio::AudioCommand>, pub audio_cmd_tx: mpsc::Sender<crate::modules::audio::AudioCommand>,
}
};
} }
for_each_watched_module!(gen_app_receivers);
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct ModuleHealth { pub struct ModuleHealth {