aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/actions.rs47
-rw-r--r--src/app/api.rs17
-rw-r--r--src/app/mod.rs5
-rw-r--r--src/app/runtime.rs8
-rw-r--r--src/app/state.rs4
5 files changed, 70 insertions, 11 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index 210b6ccf..83e9bec6 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -15,8 +15,15 @@ fn is_background_completion_transition(prev_state: AgentState, new_state: AgentS
&& matches!(prev_state, AgentState::Working | AgentState::Blocked)
}
-pub fn notification_sound_for_state_change(
+pub fn active_tab_suppresses_notifications(
is_active_tab: bool,
+ outer_terminal_focus: Option<bool>,
+) -> bool {
+ is_active_tab && outer_terminal_focus != Some(false)
+}
+
+pub fn notification_sound_for_state_change(
+ suppress_active_tab_notifications: bool,
prev_state: AgentState,
new_state: AgentState,
) -> Option<crate::sound::Sound> {
@@ -27,7 +34,8 @@ pub fn notification_sound_for_state_change(
match new_state {
AgentState::Blocked => Some(crate::sound::Sound::Request),
AgentState::Idle
- if is_background_completion_transition(prev_state, new_state) && !is_active_tab =>
+ if is_background_completion_transition(prev_state, new_state)
+ && !suppress_active_tab_notifications =>
{
Some(crate::sound::Sound::Done)
}
@@ -36,11 +44,11 @@ pub fn notification_sound_for_state_change(
}
pub fn notification_toast_for_state_change(
- is_active_tab: bool,
+ suppress_active_tab_notifications: bool,
prev_state: AgentState,
new_state: AgentState,
) -> Option<ToastKind> {
- if is_active_tab || new_state == prev_state {
+ if suppress_active_tab_notifications || new_state == prev_state {
return None;
}
@@ -573,6 +581,8 @@ impl AppState {
change: &EffectiveStateChange,
) {
let is_active_tab = self.pane_is_in_active_tab(ws_idx, pane_id);
+ let suppress_active_tab_notifications =
+ active_tab_suppresses_notifications(is_active_tab, self.outer_terminal_focus);
let Some(pane) = self.workspaces[ws_idx]
.tabs
.iter_mut()
@@ -582,14 +592,14 @@ impl AppState {
};
if is_background_completion_transition(change.previous_state, change.state)
- && !is_active_tab
+ && !suppress_active_tab_notifications
{
pane.seen = false;
}
if self.local_sound_playback && self.sound.allows(change.known_agent) {
if let Some(sound) = notification_sound_for_state_change(
- is_active_tab,
+ suppress_active_tab_notifications,
change.previous_state,
change.state,
) {
@@ -1066,6 +1076,31 @@ mod tests {
}
#[test]
+ fn active_workspace_active_tab_keeps_herdr_toast_suppressed_when_outer_terminal_is_unfocused() {
+ let mut state = app_with_workspaces(&["active"]);
+ state.active = Some(0);
+ state.outer_terminal_focus = Some(false);
+ state.toast_config.delivery = crate::config::ToastDelivery::Herdr;
+ let pane_id = *state.workspaces[0].panes.keys().next().unwrap();
+
+ state.handle_app_event(AppEvent::StateChanged {
+ pane_id,
+ agent: Some(Agent::Pi),
+ state: AgentState::Blocked,
+ });
+
+ assert!(state.toast.is_none());
+ }
+
+ #[test]
+ fn active_tab_suppression_preserves_unknown_focus_behavior() {
+ assert!(active_tab_suppresses_notifications(true, None));
+ assert!(active_tab_suppresses_notifications(true, Some(true)));
+ assert!(!active_tab_suppresses_notifications(true, Some(false)));
+ assert!(!active_tab_suppresses_notifications(false, None));
+ }
+
+ #[test]
fn update_ready_sets_manual_update_toast() {
let mut state = AppState::test_new();
state.toast_config.delivery = crate::config::ToastDelivery::Herdr;
diff --git a/src/app/api.rs b/src/app/api.rs
index d7893d6d..5cfa3409 100644
--- a/src/app/api.rs
+++ b/src/app/api.rs
@@ -70,10 +70,12 @@ impl App {
self.restore_overlay_after_exit(overlay);
}
- if matches!(
- self.state.toast_config.delivery,
- crate::config::ToastDelivery::Terminal
- ) {
+ if self.local_terminal_notifications
+ && matches!(
+ self.state.toast_config.delivery,
+ crate::config::ToastDelivery::Terminal
+ )
+ {
if let Some(version) = update_ready_version {
let _ = crate::terminal_notify::show_notification(
&format!("v{version} available"),
@@ -84,8 +86,13 @@ impl App {
let is_active_tab = self
.state
.pane_is_in_active_tab(update.ws_idx, update.pane_id);
+ let suppress_active_tab_notifications =
+ crate::app::actions::active_tab_suppresses_notifications(
+ is_active_tab,
+ self.state.outer_terminal_focus,
+ );
let Some(kind) = crate::app::actions::notification_toast_for_state_change(
- is_active_tab,
+ suppress_active_tab_notifications,
update.previous_state,
update.state,
) else {
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 26090ebf..3d588403 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -77,6 +77,7 @@ pub struct App {
pub render_notify: Arc<Notify>,
pub render_dirty: Arc<AtomicBool>,
pub(crate) overlay_panes: HashMap<crate::layout::PaneId, OverlayPaneState>,
+ pub(crate) local_terminal_notifications: bool,
}
pub(crate) enum LoopEvent {
@@ -315,6 +316,7 @@ impl App {
update_dismissed: false,
config_diagnostic,
toast: None,
+ outer_terminal_focus: None,
prefix_code,
prefix_mods,
default_sidebar_width: config.ui.sidebar_width,
@@ -395,6 +397,7 @@ impl App {
render_notify,
render_dirty,
overlay_panes: HashMap::new(),
+ local_terminal_notifications: true,
}
}
@@ -761,6 +764,8 @@ impl App {
}
}
}
+ crate::raw_input::RawInputEvent::OuterFocusGained
+ | crate::raw_input::RawInputEvent::OuterFocusLost => {}
crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } => {
if apply_host_terminal_theme {
self.update_host_terminal_theme(kind, color);
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
index 739a3670..094b26f1 100644
--- a/src/app/runtime.rs
+++ b/src/app/runtime.rs
@@ -89,6 +89,14 @@ impl App {
self.handle_mouse(mouse);
true
}
+ crate::raw_input::RawInputEvent::OuterFocusGained => {
+ self.state.outer_terminal_focus = Some(true);
+ false
+ }
+ crate::raw_input::RawInputEvent::OuterFocusLost => {
+ self.state.outer_terminal_focus = Some(false);
+ false
+ }
crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } => {
self.update_host_terminal_theme(kind, color)
}
diff --git a/src/app/state.rs b/src/app/state.rs
index 971b1476..facb5187 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -631,6 +631,9 @@ pub struct AppState {
pub update_dismissed: bool,
pub config_diagnostic: Option<String>,
pub toast: Option<ToastNotification>,
+ /// Last reported focus state for the outer terminal hosting herdr.
+ /// None means unsupported or not yet reported, which preserves active-pane suppression.
+ pub outer_terminal_focus: Option<bool>,
// Config
pub prefix_code: KeyCode,
pub prefix_mods: KeyModifiers,
@@ -765,6 +768,7 @@ impl AppState {
update_dismissed: false,
config_diagnostic: None,
toast: None,
+ outer_terminal_focus: None,
prefix_code: KeyCode::Char('b'),
prefix_mods: KeyModifiers::CONTROL,
default_sidebar_width: 26,