Limit status errors to related control popups

This commit is contained in:
Samuel Onoja committed 2026-10-01 12:06:32 +01:00
1 parent 7a9fd5c864
commit 5250eeca7b
3 files changed
+79 -12

No files matched your search

+1 -1
View File
@@ -165,7 +165,7 @@ struct FereseShell {
status: status::Snapshot, status: status::Snapshot,
recorder: recording::Recorder, recorder: recording::Recorder,
calendar_offset: i32, calendar_offset: i32,
status_error: Option<String>, status_error: Option<status::ActionError>,
theme_error: Option<String>, theme_error: Option<String>,
menu: Option<status_ui::OpenMenu>, menu: Option<status_ui::OpenMenu>,
system_modal: Option<system_modal::SystemModal>, system_modal: Option<system_modal::SystemModal>,
+25 -10
View File
@@ -84,11 +84,17 @@ impl Action {
} }
} }
#[derive(Clone, Debug)]
pub struct ActionError {
pub action: Action,
pub message: String,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Update { pub struct Update {
pub snapshot: Snapshot, pub snapshot: Snapshot,
pub generation: u64, pub generation: u64,
pub error: Option<String>, pub error: Option<ActionError>,
} }
pub struct Service { pub struct Service {
@@ -123,7 +129,7 @@ impl Updates {
} }
} }
type PollState = (Mutex<(u64, bool, Option<String>, bool)>, Condvar); type PollState = (Mutex<(u64, bool, Option<ActionError>, bool)>, Condvar);
fn wait_for_poll(shared: &PollState) -> Option<u64> { fn wait_for_poll(shared: &PollState) -> Option<u64> {
let state = shared let state = shared
@@ -141,7 +147,7 @@ impl Service {
let (updates, rx) = tokio::sync::watch::channel(None); let (updates, rx) = tokio::sync::watch::channel(None);
// Polling and writes have separate workers: a missing D-Bus service must // Polling and writes have separate workers: a missing D-Bus service must
// never hold up volume/brightness changes. Publish only coherent polls. // never hold up volume/brightness changes. Publish only coherent polls.
let shared = Arc::new((Mutex::new((0u64, false, None::<String>, false)), Condvar::new())); let shared = Arc::new((Mutex::new((0u64, false, None::<ActionError>, false)), Condvar::new()));
let polling = shared.clone(); let polling = shared.clone();
thread::spawn(move || { thread::spawn(move || {
@@ -190,7 +196,9 @@ impl Service {
state.1 = true; state.1 = true;
} }
let settings = settings.lock().unwrap().clone(); let settings = settings.lock().unwrap().clone();
let error = execute(&action, settings.as_deref()).err(); let error = execute(&action, settings.as_deref())
.err()
.map(|message| ActionError { action, message });
let mut state = shared.0.lock().unwrap(); let mut state = shared.0.lock().unwrap();
state.1 = false; state.1 = false;
state.2 = error; state.2 = error;
@@ -210,11 +218,13 @@ impl Service {
} }
} }
pub fn send(&mut self, action: Action) -> Result<(), String> { pub fn send(&mut self, action: Action) -> Result<(), ActionError> {
let generation = self.generation + 1; let generation = self.generation + 1;
self.tx let error_action = action.clone();
.try_send((generation, action)) self.tx.try_send((generation, action)).map_err(|_| ActionError {
.map_err(|_| "Controls are busy; please try again".to_owned())?; action: error_action,
message: "Controls are busy; please try again".to_owned(),
})?;
self.generation = generation; self.generation = generation;
Ok(()) Ok(())
@@ -745,7 +755,10 @@ mod tests {
..Snapshot::default() ..Snapshot::default()
}, },
generation, generation,
error: Some("service unavailable".into()), error: Some(ActionError {
action: Action::Bluetooth(true),
message: "service unavailable".into(),
}),
}) })
}; };
let (sender, receiver) = tokio::sync::watch::channel(None); let (sender, receiver) = tokio::sync::watch::channel(None);
@@ -763,7 +776,9 @@ mod tests {
}; };
assert_eq!(update.generation, 2); assert_eq!(update.generation, 2);
assert_eq!(update.snapshot.brightness, Some(42)); assert_eq!(update.snapshot.brightness, Some(42));
assert_eq!(update.error.as_deref(), Some("service unavailable")); let error = update.error.as_ref().unwrap();
assert_eq!(error.message, "service unavailable");
assert!(matches!(error.action, Action::Bluetooth(true)));
assert!(stream.as_mut().poll_next(&mut context).is_pending()); assert!(stream.as_mut().poll_next(&mut context).is_pending());
let before = wakes.0.load(Ordering::Relaxed); let before = wakes.0.load(Ordering::Relaxed);
thread::spawn(move || { thread::spawn(move || {
+53 -1
View File
@@ -24,6 +24,19 @@ pub enum Menu {
} }
impl Menu { impl Menu {
fn action_error(self, error: &status::ActionError) -> Option<&str> {
let relevant = self == Self::System
|| matches!(
(self, &error.action),
(Self::Network, Action::Wifi(_))
| (Self::Bluetooth, Action::Bluetooth(_))
| (Self::Audio, Action::Volume(_) | Action::Mute(_))
| (Self::Battery, Action::Brightness(_) | Action::PowerProfile(_))
| (Self::Notifications, Action::Dnd(_) | Action::Notifications)
);
relevant.then_some(error.message.as_str())
}
fn width(self) -> f32 { fn width(self) -> f32 {
match self { match self {
Self::System => 360.0, Self::System => 360.0,
@@ -818,7 +831,11 @@ impl FereseShell {
} }
} }
if let Some(error) = &self.status_error { if let Some(error) = self
.status_error
.as_ref()
.and_then(|error| menu.kind.action_error(error))
{
rows = rows.push(text(error).size(12).class(theme::Text::Color(Color { rows = rows.push(text(error).size(12).class(theme::Text::Color(Color {
a: p, a: p,
..Color::from_rgb8(230, 172, 90) ..Color::from_rgb8(230, 172, 90)
@@ -1271,6 +1288,41 @@ fn status_icon(kind: Menu, s: &Snapshot) -> (&'static [u8], bool) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test]
fn failed_controls_only_show_errors_in_related_popups() {
for (action, relevant) in [
(Action::Bluetooth(true), Menu::Bluetooth),
(Action::Wifi(true), Menu::Network),
(Action::Volume(50), Menu::Audio),
(Action::Mute(true), Menu::Audio),
(Action::Brightness(50), Menu::Battery),
(Action::PowerProfile("balanced"), Menu::Battery),
(Action::Dnd(true), Menu::Notifications),
] {
let error = status::ActionError {
action,
message: "control failed".into(),
};
for menu in [
Menu::Network,
Menu::Bluetooth,
Menu::Audio,
Menu::Battery,
Menu::Calendar,
Menu::Recording,
Menu::Notifications,
Menu::System,
] {
assert_eq!(
menu.action_error(&error),
(menu == relevant || menu == Menu::System).then_some("control failed"),
"{menu:?} showed the wrong control error: {:?}",
error.action
);
}
}
}
#[test] #[test]
fn connection_captions_fit_one_short_line() { fn connection_captions_fit_one_short_line() {
assert_eq!(super::connection_caption("88%"), "88%"); assert_eq!(super::connection_caption("88%"), "88%");