Reduce API volume for volume
The HUD no longer polls the volume API, and volume update requests are debounced -- thus reducing the volume of API requests for volume changes (ba dum tss)
This commit is contained in:
parent
68cdc16508
commit
0f3bc8f690
2 changed files with 102 additions and 14 deletions
|
|
@ -11,9 +11,8 @@ use gtk4::prelude::*;
|
|||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||
use shepherd_api::Command;
|
||||
use shepherd_ipc::IpcClient;
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
|
|
@ -216,7 +215,44 @@ fn build_hud_content(state: SharedState) -> gtk4::Box {
|
|||
volume_slider.set_value(info.percent as f64);
|
||||
}
|
||||
|
||||
// Handle slider value changes
|
||||
// Handle slider value changes with debouncing
|
||||
// Create a channel for volume requests - the worker will debounce them
|
||||
let (volume_tx, volume_rx) = mpsc::channel::<u8>();
|
||||
|
||||
// Spawn a dedicated volume worker thread that debounces requests
|
||||
std::thread::spawn(move || {
|
||||
const DEBOUNCE_MS: u64 = 50; // Wait 50ms for more changes before sending
|
||||
|
||||
loop {
|
||||
// Wait for first volume request
|
||||
let Ok(mut latest_percent) = volume_rx.recv() else {
|
||||
break; // Channel closed
|
||||
};
|
||||
|
||||
// Drain any pending requests, keeping only the latest value
|
||||
// Use a short timeout to debounce rapid changes
|
||||
loop {
|
||||
match volume_rx.recv_timeout(std::time::Duration::from_millis(DEBOUNCE_MS)) {
|
||||
Ok(percent) => {
|
||||
latest_percent = percent; // Update to latest value
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
// No more changes for DEBOUNCE_MS, send the request
|
||||
break;
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
return; // Channel closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send only the final value
|
||||
if let Err(e) = crate::volume::set_volume(latest_percent) {
|
||||
tracing::error!("Failed to set volume: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let slider_changing = std::rc::Rc::new(std::cell::Cell::new(false));
|
||||
let slider_changing_clone = slider_changing.clone();
|
||||
|
||||
|
|
@ -224,14 +260,10 @@ fn build_hud_content(state: SharedState) -> gtk4::Box {
|
|||
slider_changing_clone.set(true);
|
||||
let percent = value.clamp(0.0, 100.0) as u8;
|
||||
|
||||
// Update in background thread to avoid blocking UI
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = crate::volume::set_volume(percent) {
|
||||
tracing::error!("Failed to set volume: {}", e);
|
||||
}
|
||||
});
|
||||
// Send to debounce worker (non-blocking)
|
||||
let _ = volume_tx.send(percent);
|
||||
|
||||
// Allow the slider to update
|
||||
// Allow the slider to update immediately in UI
|
||||
slider.set_value(value);
|
||||
glib::Propagation::Stop
|
||||
});
|
||||
|
|
@ -371,8 +403,8 @@ fn build_hud_content(state: SharedState) -> gtk4::Box {
|
|||
battery_label_clone.set_text("--%");
|
||||
}
|
||||
|
||||
// Update volume from daemon
|
||||
if let Some(volume) = crate::volume::get_volume_status() {
|
||||
// Update volume from cached state (updated via events, no polling needed)
|
||||
if let Some(volume) = state.volume_info() {
|
||||
volume_button_clone.set_icon_name(volume.icon_name());
|
||||
volume_label_clone.set_text(&format!("{}%", volume.percent));
|
||||
|
||||
|
|
@ -555,9 +587,25 @@ fn run_event_loop(socket_path: PathBuf, state: SharedState) -> anyhow::Result<()
|
|||
tracing::info!("Connecting to shepherdd at {:?}", socket_path);
|
||||
|
||||
match IpcClient::connect(&socket_path).await {
|
||||
Ok(client) => {
|
||||
Ok(mut client) => {
|
||||
tracing::info!("Connected to shepherdd");
|
||||
|
||||
// Get initial volume before subscribing (can't send commands after subscribe)
|
||||
match client.send(Command::GetVolume).await {
|
||||
Ok(response) => {
|
||||
if let shepherd_api::ResponseResult::Ok(
|
||||
shepherd_api::ResponsePayload::Volume(info),
|
||||
) = response.result
|
||||
{
|
||||
tracing::debug!("Got initial volume: {}%", info.percent);
|
||||
state.set_initial_volume(info);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to get initial volume: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let mut stream = match client.subscribe().await {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//! The HUD subscribes to events from shepherdd and tracks session state.
|
||||
|
||||
use chrono::Local;
|
||||
use shepherd_api::{Event, EventPayload, SessionEndReason};
|
||||
use shepherd_api::{Event, EventPayload, SessionEndReason, VolumeInfo, VolumeRestrictions};
|
||||
use shepherd_util::{EntryId, SessionId};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::watch;
|
||||
|
|
@ -84,18 +84,25 @@ pub struct SharedState {
|
|||
metrics_tx: Arc<watch::Sender<SystemMetrics>>,
|
||||
/// System metrics receiver
|
||||
metrics_rx: watch::Receiver<SystemMetrics>,
|
||||
/// Volume info sender (updated via events, not polling)
|
||||
volume_tx: Arc<watch::Sender<Option<VolumeInfo>>>,
|
||||
/// Volume info receiver
|
||||
volume_rx: watch::Receiver<Option<VolumeInfo>>,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
pub fn new() -> Self {
|
||||
let (session_tx, session_rx) = watch::channel(SessionState::NoSession);
|
||||
let (metrics_tx, metrics_rx) = watch::channel(SystemMetrics::default());
|
||||
let (volume_tx, volume_rx) = watch::channel(None);
|
||||
|
||||
Self {
|
||||
session_tx: Arc::new(session_tx),
|
||||
session_rx,
|
||||
metrics_tx: Arc::new(metrics_tx),
|
||||
metrics_rx,
|
||||
volume_tx: Arc::new(volume_tx),
|
||||
volume_rx,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +131,35 @@ impl SharedState {
|
|||
let _ = self.metrics_tx.send(metrics);
|
||||
}
|
||||
|
||||
/// Get current volume info (cached from events)
|
||||
pub fn volume_info(&self) -> Option<VolumeInfo> {
|
||||
self.volume_rx.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set initial volume info (called once on connect)
|
||||
pub fn set_initial_volume(&self, info: VolumeInfo) {
|
||||
let _ = self.volume_tx.send(Some(info));
|
||||
}
|
||||
|
||||
/// Update volume from VolumeChanged event (preserves restrictions from initial fetch)
|
||||
fn update_volume(&self, percent: u8, muted: bool) {
|
||||
self.volume_tx.send_modify(|vol| {
|
||||
if let Some(v) = vol {
|
||||
v.percent = percent;
|
||||
v.muted = muted;
|
||||
} else {
|
||||
// If we don't have initial volume yet, create a basic one
|
||||
*vol = Some(VolumeInfo {
|
||||
percent,
|
||||
muted,
|
||||
available: true,
|
||||
backend: None,
|
||||
restrictions: VolumeRestrictions::unrestricted(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Update time remaining for current session
|
||||
pub fn update_time_remaining(&self, remaining_secs: u64) {
|
||||
self.session_tx.send_modify(|state| {
|
||||
|
|
@ -230,6 +266,10 @@ impl SharedState {
|
|||
}
|
||||
}
|
||||
|
||||
EventPayload::VolumeChanged { percent, muted } => {
|
||||
self.update_volume(*percent, *muted);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue