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,
recorder: recording::Recorder,
calendar_offset: i32,
status_error: Option<String>,
status_error: Option<status::ActionError>,
theme_error: Option<String>,
menu: Option<status_ui::OpenMenu>,
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)]
pub struct Update {
pub snapshot: Snapshot,
pub generation: u64,
pub error: Option<String>,
pub error: Option<ActionError>,
}
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> {
let state = shared
@@ -141,7 +147,7 @@ impl Service {
let (updates, rx) = tokio::sync::watch::channel(None);
// Polling and writes have separate workers: a missing D-Bus service must
// 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();
thread::spawn(move || {
@@ -190,7 +196,9 @@ impl Service {
state.1 = true;
}
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();
state.1 = false;
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;
self.tx
.try_send((generation, action))
.map_err(|_| "Controls are busy; please try again".to_owned())?;
let error_action = action.clone();
self.tx.try_send((generation, action)).map_err(|_| ActionError {
action: error_action,
message: "Controls are busy; please try again".to_owned(),
})?;
self.generation = generation;
Ok(())
@@ -745,7 +755,10 @@ mod tests {
..Snapshot::default()
},
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);
@@ -763,7 +776,9 @@ mod tests {
};
assert_eq!(update.generation, 2);
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());
let before = wakes.0.load(Ordering::Relaxed);
thread::spawn(move || {
+53 -1
View File
@@ -24,6 +24,19 @@ pub enum 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 {
match self {
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 {
a: p,
..Color::from_rgb8(230, 172, 90)
@@ -1271,6 +1288,41 @@ fn status_icon(kind: Menu, s: &Snapshot) -> (&'static [u8], bool) {
#[cfg(test)]
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]
fn connection_captions_fit_one_short_line() {
assert_eq!(super::connection_caption("88%"), "88%");