diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/handlers/compositor.rs | 35 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 6 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 2 | ||||
| -rw-r--r-- | src/layout/mod.rs | 110 | ||||
| -rw-r--r-- | src/window/unmapped.rs | 4 |
5 files changed, 117 insertions, 40 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index ceb22b78..ed36100b 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -18,6 +18,8 @@ use smithay::wayland::shm::{ShmHandler, ShmState}; use smithay::{delegate_compositor, delegate_shm}; use super::xdg_shell::add_mapped_toplevel_pre_commit_hook; +use crate::handlers::XDG_ACTIVATION_TOKEN_TIMEOUT; +use crate::layout::ActivateWindow; use crate::niri::{ClientState, State}; use crate::utils::send_scale_transform; use crate::utils::transaction::Transaction; @@ -84,7 +86,11 @@ impl CompositorHandler for State { if is_mapped { // The toplevel got mapped. - let Unmapped { window, state } = entry.remove(); + let Unmapped { + window, + state, + activation_token_data, + } = entry.remove(); window.on_commit(); @@ -133,8 +139,20 @@ impl CompositorHandler for State { let mapped = Mapped::new(window, rules, hook); let window = mapped.window.clone(); + // Check the token timestamp again in case the window took a while between + // requesting activation and mapping. + let activate = match activation_token_data + .filter(|token| token.timestamp.elapsed() < XDG_ACTIVATION_TOKEN_TIMEOUT) + { + Some(_) => ActivateWindow::Yes, + None => ActivateWindow::Smart, + }; + let output = if let Some(p) = parent { // Open dialogs immediately to the right of their parent window. + // + // FIXME: do we want to use activate here? How do we want things to behave + // exactly? self.niri .layout .add_window_right_of(&p, mapped, width, is_full_width) @@ -144,14 +162,21 @@ impl CompositorHandler for State { mapped, width, is_full_width, + activate, ) } else if let Some(output) = &output { - self.niri - .layout - .add_window_on_output(output, mapped, width, is_full_width); + self.niri.layout.add_window_on_output( + output, + mapped, + width, + is_full_width, + activate, + ); Some(output) } else { - self.niri.layout.add_window(mapped, width, is_full_width) + self.niri + .layout + .add_window(mapped, width, is_full_width, activate) }; if let Some(output) = output.cloned() { diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index f7340231..b51fc515 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -670,10 +670,12 @@ impl XdgActivationHandler for State { self.niri.layout.activate_window(&window); self.niri.layer_shell_on_demand_focus = None; self.niri.queue_redraw_all(); - - self.niri.activation_state.remove_token(&token); + } else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(&surface) { + unmapped.activation_token_data = Some(token_data); } } + + self.niri.activation_state.remove_token(&token); } } delegate_xdg_activation!(State); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 0ac82d79..834bbf64 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -752,7 +752,7 @@ impl State { self.niri.is_at_startup, ); - let Unmapped { window, state } = unmapped; + let Unmapped { window, state, .. } = unmapped; let InitialConfigureState::NotConfigured { wants_fullscreen } = state else { error!("window must not be already configured in send_initial_configure()"); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index ad6f1b2b..c2424ba8 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -358,6 +358,18 @@ pub struct RemovedTile<W: LayoutElement> { is_full_width: bool, } +/// Whether to activate a newly added window. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum ActivateWindow { + /// Activate unconditionally. + Yes, + /// Activate based on heuristics. + #[default] + Smart, + /// Do not activate. + No, +} + impl<W: LayoutElement> InteractiveMoveState<W> { fn moving(&self) -> Option<&InteractiveMoveData<W>> { match self { @@ -383,6 +395,16 @@ impl<W: LayoutElement> InteractiveMoveData<W> { } } +impl ActivateWindow { + pub fn map_smart(self, f: impl FnOnce() -> bool) -> bool { + match self { + ActivateWindow::Yes => true, + ActivateWindow::Smart => f(), + ActivateWindow::No => false, + } + } +} + impl Options { fn from_config(config: &Config) -> Self { let layout = &config.layout; @@ -753,6 +775,7 @@ impl<W: LayoutElement> Layout<W> { window: W, width: Option<ColumnWidth>, is_full_width: bool, + activate: ActivateWindow, ) -> Option<&Output> { let width = self.resolve_default_width(&window, width); @@ -771,20 +794,27 @@ impl<W: LayoutElement> Layout<W> { }) .unwrap(); - // Don't steal focus from an active fullscreen window. - let mut activate = true; - let ws = &mon.workspaces[ws_idx]; - if mon_idx == *active_monitor_idx - && !ws.columns.is_empty() - && ws.columns[ws.active_column_idx].is_fullscreen - { - activate = false; + if activate == ActivateWindow::Yes { + *active_monitor_idx = mon_idx; } - // Don't activate if on a different workspace. - if mon.active_workspace_idx != ws_idx { - activate = false; - } + let activate = activate.map_smart(|| { + // Don't steal focus from an active fullscreen window. + let ws = &mon.workspaces[ws_idx]; + if mon_idx == *active_monitor_idx + && !ws.columns.is_empty() + && ws.columns[ws.active_column_idx].is_fullscreen + { + return false; + } + + // Don't activate if on a different workspace. + if mon.active_workspace_idx != ws_idx { + return false; + } + + true + }); mon.add_window(ws_idx, window, activate, width, is_full_width); Some(&mon.output) @@ -798,7 +828,8 @@ impl<W: LayoutElement> Layout<W> { .map_or(false, |name| name.eq_ignore_ascii_case(workspace_name)) }) .unwrap(); - ws.add_window(None, window, true, width, is_full_width); + let activate = activate.map_smart(|| true); + ws.add_window(None, window, activate, width, is_full_width); None } } @@ -835,6 +866,7 @@ impl<W: LayoutElement> Layout<W> { window: W, width: Option<ColumnWidth>, is_full_width: bool, + activate: ActivateWindow, ) -> Option<&Output> { let width = self.resolve_default_width(&window, width); @@ -846,12 +878,11 @@ impl<W: LayoutElement> Layout<W> { } => { let mon = &mut monitors[*active_monitor_idx]; - // Don't steal focus from an active fullscreen window. - let mut activate = true; - let ws = &mon.workspaces[mon.active_workspace_idx]; - if !ws.columns.is_empty() && ws.columns[ws.active_column_idx].is_fullscreen { - activate = false; - } + let activate = activate.map_smart(|| { + // Don't steal focus from an active fullscreen window. + let ws = &mon.workspaces[mon.active_workspace_idx]; + ws.columns.is_empty() || !ws.columns[ws.active_column_idx].is_fullscreen + }); mon.add_window( mon.active_workspace_idx, @@ -872,7 +903,8 @@ impl<W: LayoutElement> Layout<W> { )); &mut workspaces[0] }; - ws.add_window(None, window, true, width, is_full_width); + let activate = activate.map_smart(|| true); + ws.add_window(None, window, activate, width, is_full_width); None } } @@ -893,11 +925,12 @@ impl<W: LayoutElement> Layout<W> { if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if right_of == move_.tile.window().id() { let output = move_.output.clone(); + let activate = ActivateWindow::default(); if self.monitor_for_output(&output).is_some() { - self.add_window_on_output(&output, window, width, is_full_width); + self.add_window_on_output(&output, window, width, is_full_width, activate); return Some(&self.monitor_for_output(&output).unwrap().output); } else { - return self.add_window(window, width, is_full_width); + return self.add_window(window, width, is_full_width, activate); } } } @@ -932,6 +965,7 @@ impl<W: LayoutElement> Layout<W> { window: W, width: Option<ColumnWidth>, is_full_width: bool, + activate: ActivateWindow, ) { let width = self.resolve_default_width(&window, width); @@ -950,16 +984,22 @@ impl<W: LayoutElement> Layout<W> { .find(|(_, mon)| mon.output == *output) .unwrap(); - // Don't steal focus from an active fullscreen window. - let mut activate = true; - let ws = &mon.workspaces[mon.active_workspace_idx]; - if mon_idx == *active_monitor_idx - && !ws.columns.is_empty() - && ws.columns[ws.active_column_idx].is_fullscreen - { - activate = false; + if activate == ActivateWindow::Yes { + *active_monitor_idx = mon_idx; } + let activate = activate.map_smart(|| { + // Don't steal focus from an active fullscreen window. + let ws = &mon.workspaces[mon.active_workspace_idx]; + if mon_idx == *active_monitor_idx + && !ws.columns.is_empty() + && ws.columns[ws.active_column_idx].is_fullscreen + { + return false; + } + true + }); + mon.add_window( mon.active_workspace_idx, window, @@ -4365,7 +4405,7 @@ mod tests { } let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1); - layout.add_window(win, None, false); + layout.add_window(win, None, false, ActivateWindow::default()); } Op::AddWindowRightOf { id, @@ -4482,7 +4522,13 @@ mod tests { } let win = TestWindow::new(id, bbox, min_max_size.0, min_max_size.1); - layout.add_window_to_named_workspace(&ws_name, win, None, false); + layout.add_window_to_named_workspace( + &ws_name, + win, + None, + false, + ActivateWindow::default(), + ); } Op::CloseWindow(id) => { layout.remove_window(&id, Transaction::new()); diff --git a/src/window/unmapped.rs b/src/window/unmapped.rs index a74c4e24..98f57aad 100644 --- a/src/window/unmapped.rs +++ b/src/window/unmapped.rs @@ -1,6 +1,7 @@ use smithay::desktop::Window; use smithay::output::Output; use smithay::wayland::shell::xdg::ToplevelSurface; +use smithay::wayland::xdg_activation::XdgActivationTokenData; use super::ResolvedWindowRules; use crate::layout::workspace::ColumnWidth; @@ -9,6 +10,8 @@ use crate::layout::workspace::ColumnWidth; pub struct Unmapped { pub window: Window, pub state: InitialConfigureState, + /// Activation token, if one was used on this unmapped window. + pub activation_token_data: Option<XdgActivationTokenData>, } #[allow(clippy::large_enum_variant)] @@ -57,6 +60,7 @@ impl Unmapped { state: InitialConfigureState::NotConfigured { wants_fullscreen: None, }, + activation_token_data: None, } } |
