Merge branch 'fix/audit-findings' into dev

# Conflicts:
#	assets/icons-needed.txt
#	src/main.rs
#	src/notifications/mod.rs
#	src/notifications/popup.rs
This commit is contained in:
Breadway 2026-07-17 14:39:23 +08:00
commit 2cd2fbc7f6
5 changed files with 232 additions and 50 deletions

View file

@ -10,28 +10,57 @@ use relm4::ComponentSender;
use crate::AppInput;
/// Fetches the current workspace list + active workspace and pushes both to
/// the app — used both for the initial state and to re-sync after the event
/// stream reconnects (state may have changed while we were disconnected).
async fn sync_state(sender: &ComponentSender<crate::App>) {
if let Ok(ws) = Workspaces::get_async().await {
sender.input(AppInput::WorkspaceList(ws.to_vec()));
}
if let Ok(active) = Workspace::get_active_async().await {
sender.input(AppInput::ActiveWorkspace(active.id));
}
}
pub fn spawn_watcher(sender: ComponentSender<crate::App>) {
relm4::spawn(async move {
if let Ok(ws) = Workspaces::get_async().await {
sender.input(AppInput::WorkspaceList(ws.to_vec()));
}
if let Ok(active) = Workspace::get_active_async().await {
sender.input(AppInput::ActiveWorkspace(active.id));
}
sync_state(&sender).await;
let mut stream = EventStream::new();
while let Some(Ok(event)) = stream.next().await {
match event {
Event::WorkspaceChanged(data) => {
sender.input(AppInput::ActiveWorkspace(data.id));
}
Event::WorkspaceAdded(_) | Event::WorkspaceDeleted(_) => {
if let Ok(ws) = Workspaces::get_async().await {
sender.input(AppInput::WorkspaceList(ws.to_vec()));
// Hyprland's IPC event socket can drop out from under us — a
// Hyprland restart/reload, or just a transient hiccup — at which
// point `stream.next()` yields `None` (or an `Err`, also excluded
// by this `while let Some(Ok(..))` pattern). That used to just fall
// through and end this whole task permanently, freezing every
// workspace button for the rest of the bar's life. Reconnect with a
// capped exponential backoff instead of giving up.
let mut backoff = std::time::Duration::from_millis(500);
const MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(30);
loop {
let mut stream = EventStream::new();
while let Some(Ok(event)) = stream.next().await {
backoff = std::time::Duration::from_millis(500);
match event {
Event::WorkspaceChanged(data) => {
sender.input(AppInput::ActiveWorkspace(data.id));
}
Event::WorkspaceAdded(_) | Event::WorkspaceDeleted(_) => {
if let Ok(ws) = Workspaces::get_async().await {
sender.input(AppInput::WorkspaceList(ws.to_vec()));
}
}
_ => {}
}
_ => {}
}
eprintln!(
"breadbar: Hyprland event stream ended (restart/reload/IPC hiccup); \
reconnecting in {:?}",
backoff
);
tokio::time::sleep(backoff).await;
backoff = (backoff * 2).min(MAX_BACKOFF);
sync_state(&sender).await;
}
});
}