Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c5d0148f8 | |||
| fdfd54b518 | |||
| 708317a10b | |||
| f1e601f9ed |
+144
-110
@@ -31,6 +31,64 @@ use tokio::time::{Duration, sleep};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
/// Spawn a health-tracked polling loop. `$poll_expr` is awaited each cycle.
|
||||||
|
macro_rules! spawn_poll_loop {
|
||||||
|
($name:expr, $interval:expr, $health:expr, $token:expr, $poll_expr:expr) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
info!(concat!("Starting ", $name, " polling task"));
|
||||||
|
loop {
|
||||||
|
if !crate::health::is_poll_in_backoff($name, &$health).await {
|
||||||
|
let res: crate::error::Result<()> = $poll_expr.await;
|
||||||
|
crate::health::handle_poll_result($name, res, &$health).await;
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
_ = $token.cancelled() => break,
|
||||||
|
_ = sleep($interval) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!(concat!($name, " task shut down."));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a health-tracked polling loop with an extra trigger channel for early wake-up.
|
||||||
|
macro_rules! spawn_poll_loop_triggered {
|
||||||
|
($name:expr, $interval:expr, $health:expr, $token:expr, $trigger:expr, $poll_expr:expr) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
info!(concat!("Starting ", $name, " polling task"));
|
||||||
|
loop {
|
||||||
|
if !crate::health::is_poll_in_backoff($name, &$health).await {
|
||||||
|
let res: crate::error::Result<()> = $poll_expr.await;
|
||||||
|
crate::health::handle_poll_result($name, res, &$health).await;
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
_ = $token.cancelled() => break,
|
||||||
|
_ = $trigger.recv() => {},
|
||||||
|
_ = sleep($interval) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!(concat!($name, " task shut down."));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a simple polling loop without health tracking.
|
||||||
|
macro_rules! spawn_poll_loop_simple {
|
||||||
|
($name:expr, $interval:expr, $token:expr, $poll_expr:expr) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
info!(concat!("Starting ", $name, " polling task"));
|
||||||
|
loop {
|
||||||
|
$poll_expr.await;
|
||||||
|
tokio::select! {
|
||||||
|
_ = $token.cancelled() => break,
|
||||||
|
_ = sleep($interval) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!(concat!($name, " task shut down."));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct SocketGuard {
|
struct SocketGuard {
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
@@ -144,75 +202,21 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
|
|
||||||
let resolved_config_path = get_config_path(config_path.clone());
|
let resolved_config_path = get_config_path(config_path.clone());
|
||||||
let config = Arc::new(RwLock::new(crate::config::load_config(config_path.clone())));
|
let config = Arc::new(RwLock::new(crate::config::load_config(config_path.clone())));
|
||||||
|
spawn_config_watchers(&config, &resolved_config_path);
|
||||||
// 0. Config Watcher (Hot Reload)
|
|
||||||
let watcher_config = Arc::clone(&config);
|
|
||||||
let watcher_path = resolved_config_path.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let (ev_tx, mut ev_rx) = mpsc::channel(1);
|
|
||||||
let mut watcher = RecommendedWatcher::new(
|
|
||||||
move |res: notify::Result<Event>| {
|
|
||||||
if let Ok(event) = res
|
|
||||||
&& (event.kind.is_modify() || event.kind.is_create())
|
|
||||||
{
|
|
||||||
let _ = ev_tx.blocking_send(());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
NotifyConfig::default(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Some(parent) = watcher_path.parent() {
|
|
||||||
let _ = watcher.watch(parent, RecursiveMode::NonRecursive);
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Config watcher started on {:?}", watcher_path);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = ev_rx.recv() => {
|
|
||||||
// Debounce reloads
|
|
||||||
sleep(Duration::from_millis(100)).await;
|
|
||||||
while ev_rx.try_recv().is_ok() {}
|
|
||||||
reload_config(&watcher_config, Some(watcher_path.clone())).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 0.1 SIGHUP Handler
|
|
||||||
let hup_config = Arc::clone(&config);
|
|
||||||
let hup_path = resolved_config_path.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
use tokio::signal::unix::{SignalKind, signal};
|
|
||||||
let mut stream = signal(SignalKind::hangup()).unwrap();
|
|
||||||
loop {
|
|
||||||
stream.recv().await;
|
|
||||||
info!("Received SIGHUP, reloading config...");
|
|
||||||
reload_config(&hup_config, Some(hup_path.clone())).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. Network Task
|
// 1. Network Task
|
||||||
#[cfg(feature = "mod-network")]
|
#[cfg(feature = "mod-network")]
|
||||||
if config.read().await.network.enabled {
|
if config.read().await.network.enabled {
|
||||||
let token = cancel_token.clone();
|
|
||||||
let net_health = Arc::clone(&health);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
info!("Starting Network polling task");
|
|
||||||
let mut daemon = NetworkDaemon::new();
|
let mut daemon = NetworkDaemon::new();
|
||||||
loop {
|
let token = cancel_token.clone();
|
||||||
if !crate::health::is_poll_in_backoff("net", &net_health).await {
|
let h = Arc::clone(&health);
|
||||||
let res = daemon.poll(&net_tx).await;
|
spawn_poll_loop!(
|
||||||
crate::health::handle_poll_result("net", res, &net_health).await;
|
"net",
|
||||||
}
|
Duration::from_secs(1),
|
||||||
tokio::select! {
|
h,
|
||||||
_ = token.cancelled() => break,
|
token,
|
||||||
_ = sleep(Duration::from_secs(1)) => {}
|
daemon.poll(&net_tx)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
info!("Network task shut down.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fast Hardware Task (CPU, Mem, Load)
|
// 2. Fast Hardware Task (CPU, Mem, Load)
|
||||||
@@ -222,22 +226,14 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
let fast_enabled = cfg.cpu.enabled || cfg.memory.enabled || cfg.sys.enabled;
|
let fast_enabled = cfg.cpu.enabled || cfg.memory.enabled || cfg.sys.enabled;
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
if fast_enabled {
|
if fast_enabled {
|
||||||
let token = cancel_token.clone();
|
|
||||||
let hw_health = Arc::clone(&health);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
info!("Starting Fast Hardware polling task");
|
|
||||||
let mut daemon = HardwareDaemon::new();
|
let mut daemon = HardwareDaemon::new();
|
||||||
loop {
|
let token = cancel_token.clone();
|
||||||
if !crate::health::is_poll_in_backoff("cpu", &hw_health).await {
|
spawn_poll_loop_simple!(
|
||||||
daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx).await;
|
"fast_hw",
|
||||||
}
|
Duration::from_secs(1),
|
||||||
tokio::select! {
|
token,
|
||||||
_ = token.cancelled() => break,
|
daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx)
|
||||||
_ = sleep(Duration::from_secs(1)) => {}
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Fast Hardware task shut down.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,47 +244,29 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
let slow_enabled = cfg.gpu.enabled || cfg.disk.enabled;
|
let slow_enabled = cfg.gpu.enabled || cfg.disk.enabled;
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
if slow_enabled {
|
if slow_enabled {
|
||||||
let token = cancel_token.clone();
|
|
||||||
let slow_health = Arc::clone(&health);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
info!("Starting Slow Hardware polling task");
|
|
||||||
let mut daemon = HardwareDaemon::new();
|
let mut daemon = HardwareDaemon::new();
|
||||||
loop {
|
let token = cancel_token.clone();
|
||||||
if !crate::health::is_poll_in_backoff("gpu", &slow_health).await {
|
spawn_poll_loop_simple!(
|
||||||
daemon.poll_slow(&gpu_tx, &disks_tx).await;
|
"slow_hw",
|
||||||
}
|
Duration::from_secs(5),
|
||||||
tokio::select! {
|
token,
|
||||||
_ = token.cancelled() => break,
|
daemon.poll_slow(&gpu_tx, &disks_tx)
|
||||||
_ = sleep(Duration::from_secs(5)) => {}
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Slow Hardware task shut down.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Bluetooth Task
|
// 4. Bluetooth Task
|
||||||
#[cfg(feature = "mod-bt")]
|
#[cfg(feature = "mod-bt")]
|
||||||
if config.read().await.bt.enabled {
|
if config.read().await.bt.enabled {
|
||||||
|
let mut daemon = BtDaemon::new();
|
||||||
let token = cancel_token.clone();
|
let token = cancel_token.clone();
|
||||||
let bt_health = Arc::clone(&health);
|
let h = Arc::clone(&health);
|
||||||
let poll_config = Arc::clone(&config);
|
let poll_config = Arc::clone(&config);
|
||||||
let poll_receivers = receivers.clone();
|
let poll_receivers = receivers.clone();
|
||||||
tokio::spawn(async move {
|
spawn_poll_loop_triggered!("bt", Duration::from_secs(2), h, token, bt_force_rx, async {
|
||||||
info!("Starting Bluetooth polling task");
|
|
||||||
let mut daemon = BtDaemon::new();
|
|
||||||
loop {
|
|
||||||
if !crate::health::is_poll_in_backoff("bt", &bt_health).await {
|
|
||||||
let config = poll_config.read().await;
|
let config = poll_config.read().await;
|
||||||
daemon.poll(&bt_tx, &poll_receivers, &config).await;
|
daemon.poll(&bt_tx, &poll_receivers, &config).await;
|
||||||
}
|
Ok(())
|
||||||
tokio::select! {
|
|
||||||
_ = token.cancelled() => break,
|
|
||||||
_ = bt_force_rx.recv() => {},
|
|
||||||
_ = sleep(Duration::from_secs(2)) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Bluetooth task shut down.");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,12 +329,68 @@ pub async fn run_daemon(config_path: Option<PathBuf>) -> Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
info!("Fluxo daemon successfully bound to socket: {}", sock_path);
|
info!("Fluxo daemon successfully bound to socket: {}", sock_path);
|
||||||
|
run_ipc_loop(listener, receivers, config, config_path, cancel_token).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_config_watchers(config: &Arc<RwLock<Config>>, resolved_path: &std::path::Path) {
|
||||||
|
// File watcher (hot reload on modify/create)
|
||||||
|
let watcher_config = Arc::clone(config);
|
||||||
|
let watcher_path = resolved_path.to_path_buf();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let (ev_tx, mut ev_rx) = mpsc::channel(1);
|
||||||
|
let mut watcher = RecommendedWatcher::new(
|
||||||
|
move |res: notify::Result<Event>| {
|
||||||
|
if let Ok(event) = res
|
||||||
|
&& (event.kind.is_modify() || event.kind.is_create())
|
||||||
|
{
|
||||||
|
let _ = ev_tx.blocking_send(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NotifyConfig::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(parent) = watcher_path.parent() {
|
||||||
|
let _ = watcher.watch(parent, RecursiveMode::NonRecursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Config watcher started on {:?}", watcher_path);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancel_token.cancelled() => {
|
_ = ev_rx.recv() => {
|
||||||
break;
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
while ev_rx.try_recv().is_ok() {}
|
||||||
|
reload_config(&watcher_config, Some(watcher_path.clone())).await;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SIGHUP handler
|
||||||
|
let hup_config = Arc::clone(config);
|
||||||
|
let hup_path = resolved_path.to_path_buf();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use tokio::signal::unix::{SignalKind, signal};
|
||||||
|
let mut stream = signal(SignalKind::hangup()).unwrap();
|
||||||
|
loop {
|
||||||
|
stream.recv().await;
|
||||||
|
info!("Received SIGHUP, reloading config...");
|
||||||
|
reload_config(&hup_config, Some(hup_path.clone())).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_ipc_loop(
|
||||||
|
listener: UnixListener,
|
||||||
|
receivers: AppReceivers,
|
||||||
|
config: Arc<RwLock<Config>>,
|
||||||
|
config_path: Option<PathBuf>,
|
||||||
|
cancel_token: CancellationToken,
|
||||||
|
) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = cancel_token.cancelled() => break,
|
||||||
res = listener.accept() => {
|
res = listener.accept() => {
|
||||||
match res {
|
match res {
|
||||||
Ok((mut stream, _)) => {
|
Ok((mut stream, _)) => {
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
mod config;
|
mod config;
|
||||||
mod daemon;
|
mod daemon;
|
||||||
mod error;
|
mod error;
|
||||||
|
|||||||
+115
-82
@@ -2,7 +2,7 @@ use crate::config::Config;
|
|||||||
use crate::error::{FluxoError, Result};
|
use crate::error::{FluxoError, Result};
|
||||||
use crate::modules::WaybarModule;
|
use crate::modules::WaybarModule;
|
||||||
use crate::output::WaybarOutput;
|
use crate::output::WaybarOutput;
|
||||||
use crate::state::{AppReceivers, AudioState};
|
use crate::state::{AppReceivers, AudioDeviceInfo, AudioState};
|
||||||
use crate::utils::{TokenValue, format_template};
|
use crate::utils::{TokenValue, format_template};
|
||||||
use libpulse_binding::callbacks::ListResult;
|
use libpulse_binding::callbacks::ListResult;
|
||||||
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet};
|
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet};
|
||||||
@@ -230,91 +230,124 @@ fn fetch_audio_data_sync(
|
|||||||
let _ = tx_server.send(current);
|
let _ = tx_server.send(current);
|
||||||
});
|
});
|
||||||
|
|
||||||
let tx_sink = state_tx.clone();
|
fetch_sinks(context, state_tx);
|
||||||
let pending_sinks: Arc<std::sync::Mutex<Vec<String>>> =
|
fetch_sources(context, state_tx);
|
||||||
Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
||||||
let pending_sinks_cb = Arc::clone(&pending_sinks);
|
|
||||||
context.introspect().get_sink_info_list(move |res| {
|
|
||||||
let mut current = tx_sink.borrow().clone();
|
|
||||||
match res {
|
|
||||||
ListResult::Item(item) => {
|
|
||||||
if let Some(name) = item.name.as_ref() {
|
|
||||||
let name_str = name.to_string();
|
|
||||||
pending_sinks_cb.lock().unwrap().push(name_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_default = item
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_ref() == current.sink.name)
|
|
||||||
.unwrap_or(false);
|
|
||||||
if is_default {
|
|
||||||
current.sink.description = item
|
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
current.sink.volume = ((item.volume.avg().0 as f64 / Volume::NORMAL.0 as f64)
|
|
||||||
* 100.0)
|
|
||||||
.round() as u8;
|
|
||||||
current.sink.muted = item.mute;
|
|
||||||
current.sink.channels = item.volume.len();
|
|
||||||
}
|
|
||||||
let _ = tx_sink.send(current);
|
|
||||||
}
|
|
||||||
ListResult::End => {
|
|
||||||
current.available_sinks = pending_sinks_cb.lock().unwrap().drain(..).collect();
|
|
||||||
let _ = tx_sink.send(current);
|
|
||||||
}
|
|
||||||
ListResult::Error => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let tx_source = state_tx.clone();
|
|
||||||
let pending_sources: Arc<std::sync::Mutex<Vec<String>>> =
|
|
||||||
Arc::new(std::sync::Mutex::new(Vec::new()));
|
|
||||||
let pending_sources_cb = Arc::clone(&pending_sources);
|
|
||||||
context.introspect().get_source_info_list(move |res| {
|
|
||||||
let mut current = tx_source.borrow().clone();
|
|
||||||
match res {
|
|
||||||
ListResult::Item(item) => {
|
|
||||||
if let Some(name) = item.name.as_ref() {
|
|
||||||
let name_str = name.to_string();
|
|
||||||
if !name_str.contains(".monitor") {
|
|
||||||
pending_sources_cb.lock().unwrap().push(name_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_default = item
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_ref() == current.source.name)
|
|
||||||
.unwrap_or(false);
|
|
||||||
if is_default {
|
|
||||||
current.source.description = item
|
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
current.source.volume = ((item.volume.avg().0 as f64 / Volume::NORMAL.0 as f64)
|
|
||||||
* 100.0)
|
|
||||||
.round() as u8;
|
|
||||||
current.source.muted = item.mute;
|
|
||||||
current.source.channels = item.volume.len();
|
|
||||||
}
|
|
||||||
let _ = tx_source.send(current);
|
|
||||||
}
|
|
||||||
ListResult::End => {
|
|
||||||
current.available_sources = pending_sources_cb.lock().unwrap().drain(..).collect();
|
|
||||||
let _ = tx_source.send(current);
|
|
||||||
}
|
|
||||||
ListResult::Error => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shared bookkeeping for a device list fetch.
|
||||||
|
struct PendingList {
|
||||||
|
names: Arc<std::sync::Mutex<Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingList {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
names: Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&self, name: String) {
|
||||||
|
self.names.lock().unwrap().push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drain(&self) -> Vec<String> {
|
||||||
|
self.names.lock().unwrap().drain(..).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract common device info from a pulse item's volume/mute/description fields.
|
||||||
|
fn device_info_from(
|
||||||
|
description: Option<&str>,
|
||||||
|
volume: &libpulse_binding::volume::ChannelVolumes,
|
||||||
|
muted: bool,
|
||||||
|
) -> (String, u8, bool, u8) {
|
||||||
|
let desc = description.unwrap_or_default().to_string();
|
||||||
|
let vol = ((volume.avg().0 as f64 / Volume::NORMAL.0 as f64) * 100.0).round() as u8;
|
||||||
|
let channels = volume.len();
|
||||||
|
(desc, vol, muted, channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_device_info(target: &mut AudioDeviceInfo, item_name: &str, info: (String, u8, bool, u8)) {
|
||||||
|
if item_name == target.name {
|
||||||
|
target.description = info.0;
|
||||||
|
target.volume = info.1;
|
||||||
|
target.muted = info.2;
|
||||||
|
target.channels = info.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_sinks(context: &mut Context, state_tx: &watch::Sender<AudioState>) {
|
||||||
|
let tx = state_tx.clone();
|
||||||
|
let pending = PendingList::new();
|
||||||
|
let pending_cb = PendingList {
|
||||||
|
names: Arc::clone(&pending.names),
|
||||||
|
};
|
||||||
|
context.introspect().get_sink_info_list(move |res| {
|
||||||
|
let mut current = tx.borrow().clone();
|
||||||
|
match res {
|
||||||
|
ListResult::Item(item) => {
|
||||||
|
let name_str = item
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !name_str.is_empty() {
|
||||||
|
pending_cb.push(name_str.clone());
|
||||||
|
}
|
||||||
|
let info = device_info_from(
|
||||||
|
item.description.as_ref().map(|s| s.as_ref()),
|
||||||
|
&item.volume,
|
||||||
|
item.mute,
|
||||||
|
);
|
||||||
|
apply_device_info(&mut current.sink, &name_str, info);
|
||||||
|
let _ = tx.send(current);
|
||||||
|
}
|
||||||
|
ListResult::End => {
|
||||||
|
current.available_sinks = pending_cb.drain();
|
||||||
|
let _ = tx.send(current);
|
||||||
|
}
|
||||||
|
ListResult::Error => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_sources(context: &mut Context, state_tx: &watch::Sender<AudioState>) {
|
||||||
|
let tx = state_tx.clone();
|
||||||
|
let pending = PendingList::new();
|
||||||
|
let pending_cb = PendingList {
|
||||||
|
names: Arc::clone(&pending.names),
|
||||||
|
};
|
||||||
|
context.introspect().get_source_info_list(move |res| {
|
||||||
|
let mut current = tx.borrow().clone();
|
||||||
|
match res {
|
||||||
|
ListResult::Item(item) => {
|
||||||
|
let name_str = item
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !name_str.is_empty() && !name_str.contains(".monitor") {
|
||||||
|
pending_cb.push(name_str.clone());
|
||||||
|
}
|
||||||
|
let info = device_info_from(
|
||||||
|
item.description.as_ref().map(|s| s.as_ref()),
|
||||||
|
&item.volume,
|
||||||
|
item.mute,
|
||||||
|
);
|
||||||
|
apply_device_info(&mut current.source, &name_str, info);
|
||||||
|
let _ = tx.send(current);
|
||||||
|
}
|
||||||
|
ListResult::End => {
|
||||||
|
current.available_sources = pending_cb.drain();
|
||||||
|
let _ = tx.send(current);
|
||||||
|
}
|
||||||
|
ListResult::Error => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AudioModule;
|
pub struct AudioModule;
|
||||||
|
|
||||||
impl WaybarModule for AudioModule {
|
impl WaybarModule for AudioModule {
|
||||||
|
|||||||
+45
-58
@@ -7,10 +7,42 @@ use futures::StreamExt;
|
|||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
use zbus::{Connection, fdo::PropertiesProxy, names::InterfaceName, proxy};
|
use zbus::proxy;
|
||||||
|
use zbus::zvariant::OwnedValue;
|
||||||
|
use zbus::{Connection, fdo::PropertiesProxy};
|
||||||
|
|
||||||
pub struct DndModule;
|
pub struct DndModule;
|
||||||
|
|
||||||
|
/// Read dunst's `paused` property via raw D-Bus call.
|
||||||
|
async fn dunst_get_paused(connection: &Connection) -> anyhow::Result<bool> {
|
||||||
|
let reply = connection
|
||||||
|
.call_method(
|
||||||
|
Some("org.freedesktop.Notifications"),
|
||||||
|
"/org/freedesktop/Notifications",
|
||||||
|
Some("org.freedesktop.DBus.Properties"),
|
||||||
|
"Get",
|
||||||
|
&("org.dunstproject.cmd0", "paused"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let value: OwnedValue = reply.body().deserialize()?;
|
||||||
|
Ok(bool::try_from(&*value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set dunst's `paused` property via raw D-Bus call.
|
||||||
|
async fn dunst_set_paused(connection: &Connection, paused: bool) -> anyhow::Result<()> {
|
||||||
|
let value = zbus::zvariant::Value::from(paused);
|
||||||
|
connection
|
||||||
|
.call_method(
|
||||||
|
Some("org.freedesktop.Notifications"),
|
||||||
|
"/org/freedesktop/Notifications",
|
||||||
|
Some("org.freedesktop.DBus.Properties"),
|
||||||
|
"Set",
|
||||||
|
&("org.dunstproject.cmd0", "paused", value),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl WaybarModule for DndModule {
|
impl WaybarModule for DndModule {
|
||||||
async fn run(
|
async fn run(
|
||||||
&self,
|
&self,
|
||||||
@@ -29,7 +61,7 @@ impl WaybarModule for DndModule {
|
|||||||
message: format!("DBus connection failed: {}", e),
|
message: format!("DBus connection failed: {}", e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Try toggling SwayNC
|
// Try SwayNC
|
||||||
if let Ok(proxy) = SwayncControlProxy::new(&connection).await
|
if let Ok(proxy) = SwayncControlProxy::new(&connection).await
|
||||||
&& let Ok(is_dnd) = proxy.dnd().await
|
&& let Ok(is_dnd) = proxy.dnd().await
|
||||||
{
|
{
|
||||||
@@ -37,11 +69,9 @@ impl WaybarModule for DndModule {
|
|||||||
return Ok(WaybarOutput::default());
|
return Ok(WaybarOutput::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try toggling Dunst
|
// Try Dunst via raw D-Bus
|
||||||
if let Ok(proxy) = DunstControlProxy::new(&connection).await
|
if let Ok(is_paused) = dunst_get_paused(&connection).await {
|
||||||
&& let Ok(is_paused) = proxy.paused().await
|
let _ = dunst_set_paused(&connection, !is_paused).await;
|
||||||
{
|
|
||||||
let _ = proxy.set_paused(!is_paused).await;
|
|
||||||
return Ok(WaybarOutput::default());
|
return Ok(WaybarOutput::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,18 +115,6 @@ trait SwayncControl {
|
|||||||
fn set_dnd(&self, value: bool) -> zbus::Result<()>;
|
fn set_dnd(&self, value: bool) -> zbus::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proxy(
|
|
||||||
interface = "org.dunstproject.cmd0",
|
|
||||||
default_service = "org.freedesktop.Notifications",
|
|
||||||
default_path = "/org/freedesktop/Notifications"
|
|
||||||
)]
|
|
||||||
trait DunstControl {
|
|
||||||
#[zbus(property)]
|
|
||||||
fn paused(&self) -> zbus::Result<bool>;
|
|
||||||
#[zbus(property)]
|
|
||||||
fn set_paused(&self, value: bool) -> zbus::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DndDaemon {
|
impl DndDaemon {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
@@ -146,55 +164,21 @@ impl DndDaemon {
|
|||||||
return Err(anyhow::anyhow!("SwayNC DND stream ended"));
|
return Err(anyhow::anyhow!("SwayNC DND stream ended"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try Dunst (polling via raw D-Bus Properties.Get for maximum compatibility)
|
// Try Dunst via raw D-Bus calls (bypasses zbus proxy issues)
|
||||||
let props_proxy = PropertiesProxy::builder(&connection)
|
match dunst_get_paused(&connection).await {
|
||||||
.destination("org.freedesktop.Notifications")?
|
Ok(is_paused) => {
|
||||||
.path("/org/freedesktop/Notifications")?
|
|
||||||
.build()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match &props_proxy {
|
|
||||||
Ok(_) => debug!("Created Properties proxy for Dunst"),
|
|
||||||
Err(e) => debug!("Failed to create Properties proxy for Dunst: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(props) = props_proxy {
|
|
||||||
// Read paused property via raw Properties.Get
|
|
||||||
let initial = props
|
|
||||||
.get(
|
|
||||||
InterfaceName::from_static_str_unchecked("org.dunstproject.cmd0"),
|
|
||||||
"paused",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match &initial {
|
|
||||||
Ok(_) => debug!("Successfully read Dunst paused property"),
|
|
||||||
Err(e) => debug!("Failed to read Dunst paused property: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(value) = initial
|
|
||||||
&& let Ok(is_paused) = bool::try_from(&*value)
|
|
||||||
{
|
|
||||||
info!("Found Dunst, using polling-based DND monitoring");
|
info!("Found Dunst, using polling-based DND monitoring");
|
||||||
let _ = tx.send(DndState { is_dnd: is_paused });
|
let _ = tx.send(DndState { is_dnd: is_paused });
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
match props
|
match dunst_get_paused(&connection).await {
|
||||||
.get(
|
Ok(is_paused) => {
|
||||||
InterfaceName::from_static_str_unchecked("org.dunstproject.cmd0"),
|
|
||||||
"paused",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(value) => {
|
|
||||||
if let Ok(is_paused) = bool::try_from(&*value) {
|
|
||||||
let current = tx.borrow().is_dnd;
|
let current = tx.borrow().is_dnd;
|
||||||
if current != is_paused {
|
if current != is_paused {
|
||||||
let _ = tx.send(DndState { is_dnd: is_paused });
|
let _ = tx.send(DndState { is_dnd: is_paused });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Dunst paused() poll failed: {}", e);
|
debug!("Dunst paused() poll failed: {}", e);
|
||||||
break;
|
break;
|
||||||
@@ -204,6 +188,9 @@ impl DndDaemon {
|
|||||||
|
|
||||||
return Err(anyhow::anyhow!("Dunst connection lost"));
|
return Err(anyhow::anyhow!("Dunst connection lost"));
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("Dunst not available: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(anyhow::anyhow!(
|
Err(anyhow::anyhow!(
|
||||||
|
|||||||
+19
-73
@@ -6,6 +6,8 @@ use crate::state::AppReceivers;
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::modules::WaybarModule;
|
use crate::modules::WaybarModule;
|
||||||
|
|
||||||
|
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(
|
pub async fn dispatch(
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
#[allow(unused)] config: &Config,
|
#[allow(unused)] config: &Config,
|
||||||
@@ -17,42 +19,14 @@ pub async fn dispatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,27 +45,9 @@ 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
|
||||||
}
|
}
|
||||||
@@ -116,10 +58,14 @@ pub async fn dispatch(
|
|||||||
/// 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
@@ -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,142 +94,39 @@ 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
|
||||||
}
|
}
|
||||||
@@ -233,3 +134,7 @@ impl WaybarSignaler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for_each_watched_module!(gen_signaler_run);
|
||||||
|
|||||||
+10
-34
@@ -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;
|
||||||
|
|
||||||
|
macro_rules! gen_app_receivers {
|
||||||
|
($( { $feature:literal, $field:ident, $state:ty, [$($name:literal),+], [$($sig_name:literal),+], $module:path, $signal:ident, [$($default_arg:literal),*], $config:ident } )*) => {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppReceivers {
|
pub struct AppReceivers {
|
||||||
#[cfg(feature = "mod-network")]
|
$(
|
||||||
pub network: watch::Receiver<NetworkState>,
|
#[cfg(feature = $feature)]
|
||||||
#[cfg(feature = "mod-hardware")]
|
pub $field: watch::Receiver<$state>,
|
||||||
pub cpu: watch::Receiver<CpuState>,
|
)*
|
||||||
#[cfg(feature = "mod-hardware")]
|
|
||||||
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")]
|
||||||
@@ -42,6 +24,9 @@ pub struct AppReceivers {
|
|||||||
#[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 {
|
||||||
@@ -54,7 +39,7 @@ pub struct ModuleHealth {
|
|||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct AudioState {
|
pub struct AudioState {
|
||||||
pub sink: AudioDeviceInfo,
|
pub sink: AudioDeviceInfo,
|
||||||
pub source: AudioSourceInfo,
|
pub source: AudioDeviceInfo,
|
||||||
pub available_sinks: Vec<String>,
|
pub available_sinks: Vec<String>,
|
||||||
pub available_sources: Vec<String>,
|
pub available_sources: Vec<String>,
|
||||||
}
|
}
|
||||||
@@ -68,15 +53,6 @@ pub struct AudioDeviceInfo {
|
|||||||
pub channels: u8,
|
pub channels: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct AudioSourceInfo {
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub volume: u8,
|
|
||||||
pub muted: bool,
|
|
||||||
pub channels: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct BtDeviceInfo {
|
pub struct BtDeviceInfo {
|
||||||
pub device_alias: String,
|
pub device_alias: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user