diff options
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/mod.rs | 373 |
1 files changed, 303 insertions, 70 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs index 5e9e321e..a6fc549f 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -6,7 +6,9 @@ use std::time::Duration; use calloop::timer::{TimeoutAction, Timer}; use input::event::gesture::GestureEventCoordinates as _; -use niri_config::{Action, Bind, Binds, Key, ModKey, Modifiers, SwitchBinds, Trigger}; +use niri_config::{ + Action, Bind, Binds, Config, Key, ModKey, Modifiers, MruDirection, SwitchBinds, Trigger, +}; use niri_ipc::LayoutSwitchTarget; use smithay::backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event, @@ -43,6 +45,7 @@ use self::spatial_movement_grab::SpatialMovementGrab; use crate::layout::scrolling::ScrollDirection; use crate::layout::{ActivateWindow, LayoutElement as _}; use crate::niri::{CastTarget, PointerVisibility, State}; +use crate::ui::mru::{WindowMru, WindowMruUi}; use crate::ui::screenshot_ui::ScreenshotUi; use crate::utils::spawning::{spawn, spawn_sh}; use crate::utils::{center, get_monotonic_time, ResizeEdge}; @@ -385,6 +388,7 @@ impl State { let key_code = event.key_code(); let modified = keysym.modified_sym(); let raw = keysym.raw_latin_sym_or_raw_current_sym(); + let modifiers = modifiers_from_state(*mods); if this.niri.exit_confirm_dialog.is_open() && pressed { if raw == Some(Keysym::Return) { @@ -397,6 +401,18 @@ impl State { return FilterResult::Intercept(None); } + // Check if all modifiers were released while the MRU UI was open. If so, close the + // UI (which will also transfer the focus to the current MRU UI selection). + if this.niri.window_mru_ui.is_open() && !pressed && modifiers.is_empty() { + this.do_action(Action::MruConfirm, false); + + if this.niri.suppressed_keys.remove(&key_code) { + return FilterResult::Intercept(None); + } else { + return FilterResult::Forward; + } + } + if pressed && raw == Some(Keysym::Escape) && (this.niri.pick_window.is_some() || this.niri.pick_color.is_some()) @@ -416,20 +432,25 @@ impl State { this.niri.screenshot_ui.set_space_down(pressed); } - let bindings = &this.niri.config.borrow().binds; - let res = should_intercept_key( - &mut this.niri.suppressed_keys, - &bindings.0, - mod_key, - key_code, - modified, - raw, - pressed, - *mods, - &this.niri.screenshot_ui, - this.niri.config.borrow().input.disable_power_key_handling, - is_inhibiting_shortcuts, - ); + let res = { + let config = this.niri.config.borrow(); + let bindings = + make_binds_iter(&config, &mut this.niri.window_mru_ui, modifiers); + + should_intercept_key( + &mut this.niri.suppressed_keys, + bindings, + mod_key, + key_code, + modified, + raw, + pressed, + *mods, + &this.niri.screenshot_ui, + this.niri.config.borrow().input.disable_power_key_handling, + is_inhibiting_shortcuts, + ) + }; if matches!(res, FilterResult::Forward) { // If we didn't find any bind, try other hardcoded keys. @@ -440,6 +461,10 @@ impl State { return FilterResult::Intercept(Some(bind)); } } + + // Interaction with the active window, immediately update the active window's + // focus timestamp without waiting for a possible pending MRU lock-in delay. + this.niri.mru_apply_keyboard_commit(); } res @@ -641,6 +666,7 @@ impl State { } Action::Screenshot(show_cursor, path) => { self.open_screenshot_ui(show_cursor, path); + self.niri.cancel_mru(); } Action::ScreenshotWindow(write_to_disk, path) => { let focus = self.niri.layout.focus_with_output(); @@ -2179,6 +2205,90 @@ impl State { watcher.load_config(); } } + Action::MruConfirm => { + self.confirm_mru(); + } + Action::MruCancel => { + self.niri.cancel_mru(); + } + Action::MruAdvance { + direction, + scope, + filter, + } => { + if self.niri.window_mru_ui.is_open() { + self.niri.window_mru_ui.advance(direction, filter); + self.niri.queue_redraw_mru_output(); + } else if self.niri.config.borrow().recent_windows.on { + self.niri.mru_apply_keyboard_commit(); + + let config = self.niri.config.borrow(); + let scope = scope.unwrap_or(self.niri.window_mru_ui.scope()); + + let mut wmru = WindowMru::new(&self.niri); + if !wmru.is_empty() { + wmru.set_scope(scope); + if let Some(filter) = filter { + wmru.set_filter(filter); + } + + if let Some(output) = self.niri.layout.active_output() { + self.niri.window_mru_ui.open( + self.niri.clock.clone(), + wmru, + output.clone(), + ); + + // Only select the *next* window if some window (which should be the + // first one) is already focused. If nothing is focused, keep the first + // window (which is logically the "previously selected" one). + let keep_first = direction == MruDirection::Forward + && self.niri.layout.focus().is_none(); + if !keep_first { + self.niri.window_mru_ui.advance(direction, None); + } + + drop(config); + self.niri.queue_redraw_all(); + } + } + } + } + Action::MruCloseCurrentWindow => { + if self.niri.window_mru_ui.is_open() { + if let Some(id) = self.niri.window_mru_ui.current_window_id() { + if let Some(w) = self.niri.find_window_by_id(id) { + if let Some(tl) = w.toplevel() { + tl.send_close(); + } + } + } + } + } + Action::MruFirst => { + if self.niri.window_mru_ui.is_open() { + self.niri.window_mru_ui.first(); + self.niri.queue_redraw_mru_output(); + } + } + Action::MruLast => { + if self.niri.window_mru_ui.is_open() { + self.niri.window_mru_ui.last(); + self.niri.queue_redraw_mru_output(); + } + } + Action::MruSetScope(scope) => { + if self.niri.window_mru_ui.is_open() { + self.niri.window_mru_ui.set_scope(scope); + self.niri.queue_redraw_mru_output(); + } + } + Action::MruCycleScope => { + if self.niri.window_mru_ui.is_open() { + self.niri.window_mru_ui.cycle_scope(); + self.niri.queue_redraw_mru_output(); + } + } } } @@ -2301,6 +2411,14 @@ impl State { self.niri.screenshot_ui.pointer_motion(point, None); } + if let Some(mru_output) = self.niri.window_mru_ui.output() { + if let Some((output, pos_within_output)) = self.niri.output_under(new_pos) { + if mru_output == output { + self.niri.window_mru_ui.pointer_motion(pos_within_output); + } + } + } + let under = self.niri.contents_under(new_pos); // Handle confined pointer. @@ -2431,6 +2549,14 @@ impl State { self.niri.screenshot_ui.pointer_motion(point, None); } + if let Some(mru_output) = self.niri.window_mru_ui.output() { + if let Some((output, pos_within_output)) = self.niri.output_under(pos) { + if mru_output == output { + self.niri.window_mru_ui.pointer_motion(pos_within_output); + } + } + } + let under = self.niri.contents_under(pos); self.niri.handle_focus_follows_mouse(&under); @@ -2509,7 +2635,29 @@ impl State { let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); let modifiers = modifiers_from_state(mods); - if self.niri.mods_with_mouse_binds.contains(&modifiers) { + let mut is_mru_open = false; + if let Some(mru_output) = self.niri.window_mru_ui.output() { + is_mru_open = true; + if let Some(MouseButton::Left) = button { + let location = pointer.current_location(); + let (output, pos_within_output) = self.niri.output_under(location).unwrap(); + if mru_output == output { + let id = self.niri.window_mru_ui.pointer_motion(pos_within_output); + if id.is_some() { + self.confirm_mru(); + } else { + self.niri.cancel_mru(); + } + } else { + self.niri.cancel_mru(); + } + + self.niri.suppressed_buttons.insert(button_code); + return; + } + } + + if is_mru_open || self.niri.mods_with_mouse_binds.contains(&modifiers) { if let Some(bind) = match button { Some(MouseButton::Left) => Some(Trigger::MouseLeft), Some(MouseButton::Right) => Some(Trigger::MouseRight), @@ -2520,7 +2668,8 @@ impl State { } .and_then(|trigger| { let config = self.niri.config.borrow(); - let bindings = &config.binds.0; + let bindings = + make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers); find_configured_bind(bindings, mod_key, trigger, mods) }) { self.niri.suppressed_buttons.insert(button_code); @@ -2824,59 +2973,66 @@ impl State { false }; + let is_mru_open = self.niri.window_mru_ui.is_open(); + // Handle wheel scroll bindings. if source == AxisSource::Wheel { // If we have a scroll bind with current modifiers, then accumulate and don't pass to // Wayland. If there's no bind, reset the accumulator. let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); let modifiers = modifiers_from_state(mods); - let should_handle = - should_handle_in_overview || self.niri.mods_with_wheel_binds.contains(&modifiers); + let should_handle = should_handle_in_overview + || is_mru_open + || self.niri.mods_with_wheel_binds.contains(&modifiers); if should_handle { let horizontal = horizontal_amount_v120.unwrap_or(0.); let ticks = self.niri.horizontal_wheel_tracker.accumulate(horizontal); if ticks != 0 { - let (bind_left, bind_right) = if should_handle_in_overview - && modifiers.is_empty() - { - let bind_left = Some(Bind { - key: Key { - trigger: Trigger::WheelScrollLeft, - modifiers: Modifiers::empty(), - }, - action: Action::FocusColumnLeftUnderMouse, - repeat: true, - cooldown: None, - allow_when_locked: false, - allow_inhibiting: false, - hotkey_overlay_title: None, - }); - let bind_right = Some(Bind { - key: Key { - trigger: Trigger::WheelScrollRight, - modifiers: Modifiers::empty(), - }, - action: Action::FocusColumnRightUnderMouse, - repeat: true, - cooldown: None, - allow_when_locked: false, - allow_inhibiting: false, - hotkey_overlay_title: None, - }); - (bind_left, bind_right) - } else { - let config = self.niri.config.borrow(); - let bindings = &config.binds.0; - let bind_left = - find_configured_bind(bindings, mod_key, Trigger::WheelScrollLeft, mods); - let bind_right = find_configured_bind( - bindings, - mod_key, - Trigger::WheelScrollRight, - mods, - ); - (bind_left, bind_right) - }; + let (bind_left, bind_right) = + if should_handle_in_overview && modifiers.is_empty() { + let bind_left = Some(Bind { + key: Key { + trigger: Trigger::WheelScrollLeft, + modifiers: Modifiers::empty(), + }, + action: Action::FocusColumnLeftUnderMouse, + repeat: true, + cooldown: None, + allow_when_locked: false, + allow_inhibiting: false, + hotkey_overlay_title: None, + }); + let bind_right = Some(Bind { + key: Key { + trigger: Trigger::WheelScrollRight, + modifiers: Modifiers::empty(), + }, + action: Action::FocusColumnRightUnderMouse, + repeat: true, + cooldown: None, + allow_when_locked: false, + allow_inhibiting: false, + hotkey_overlay_title: None, + }); + (bind_left, bind_right) + } else { + let config = self.niri.config.borrow(); + let bindings = + make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers); + let bind_left = find_configured_bind( + bindings.clone(), + mod_key, + Trigger::WheelScrollLeft, + mods, + ); + let bind_right = find_configured_bind( + bindings, + mod_key, + Trigger::WheelScrollRight, + mods, + ); + (bind_left, bind_right) + }; if let Some(right) = bind_right { for _ in 0..ticks { @@ -2948,9 +3104,14 @@ impl State { (bind_up, bind_down) } else { let config = self.niri.config.borrow(); - let bindings = &config.binds.0; - let bind_up = - find_configured_bind(bindings, mod_key, Trigger::WheelScrollUp, mods); + let bindings = + make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers); + let bind_up = find_configured_bind( + bindings.clone(), + mod_key, + Trigger::WheelScrollUp, + mods, + ); let bind_down = find_configured_bind(bindings, mod_key, Trigger::WheelScrollDown, mods); (bind_up, bind_down) @@ -3081,16 +3242,21 @@ impl State { } } - if self.niri.mods_with_finger_scroll_binds.contains(&modifiers) { + if is_mru_open || self.niri.mods_with_finger_scroll_binds.contains(&modifiers) { let ticks = self .niri .horizontal_finger_scroll_tracker .accumulate(horizontal); if ticks != 0 { let config = self.niri.config.borrow(); - let bindings = &config.binds.0; - let bind_left = - find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollLeft, mods); + let bindings = + make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers); + let bind_left = find_configured_bind( + bindings.clone(), + mod_key, + Trigger::TouchpadScrollLeft, + mods, + ); let bind_right = find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollRight, mods); drop(config); @@ -3113,9 +3279,14 @@ impl State { .accumulate(vertical); if ticks != 0 { let config = self.niri.config.borrow(); - let bindings = &config.binds.0; - let bind_up = - find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollUp, mods); + let bindings = + make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers); + let bind_up = find_configured_bind( + bindings.clone(), + mod_key, + Trigger::TouchpadScrollUp, + mods, + ); let bind_down = find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollDown, mods); drop(config); @@ -3234,6 +3405,14 @@ impl State { self.niri.screenshot_ui.pointer_motion(point, None); } + if let Some(mru_output) = self.niri.window_mru_ui.output() { + if let Some((output, pos_within_output)) = self.niri.output_under(pos) { + if mru_output == output { + self.niri.window_mru_ui.pointer_motion(pos_within_output); + } + } + } + let under = self.niri.contents_under(pos); let tablet_seat = self.niri.seat.tablet_seat(); @@ -3311,6 +3490,19 @@ impl State { self.niri.queue_redraw_all(); } } + } else if let Some(mru_output) = self.niri.window_mru_ui.output() { + if let Some((output, pos_within_output)) = self.niri.output_under(pos) { + if mru_output == output { + let id = self.niri.window_mru_ui.pointer_motion(pos_within_output); + if id.is_some() { + self.confirm_mru(); + } else { + self.niri.cancel_mru(); + } + } else { + self.niri.cancel_mru(); + } + } } else if let Some((window, _)) = under.window { if let Some(output) = is_overview_open.then_some(under.output).flatten() { let mut workspaces = self.niri.layout.workspaces(); @@ -3425,6 +3617,11 @@ impl State { } fn on_gesture_swipe_begin<I: InputBackend>(&mut self, event: I::GestureSwipeBeginEvent) { + if self.niri.window_mru_ui.is_open() { + // Don't start swipe gestures while in the MRU. + return; + } + if event.fingers() == 3 { self.niri.gesture_swipe_3f_cumulative = Some((0., 0.)); @@ -3772,6 +3969,19 @@ impl State { self.niri.queue_redraw_all(); } } + } else if let Some(mru_output) = self.niri.window_mru_ui.output() { + if let Some((output, pos_within_output)) = self.niri.output_under(pos) { + if mru_output == output { + let id = self.niri.window_mru_ui.pointer_motion(pos_within_output); + if id.is_some() { + self.confirm_mru(); + } else { + self.niri.cancel_mru(); + } + } else { + self.niri.cancel_mru(); + } + } } else if !handle.is_grabbed() { let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); let mods = modifiers_from_state(mods); @@ -4696,6 +4906,29 @@ fn grab_allows_hot_corner(grab: &(dyn PointerGrab<State> + 'static)) -> bool { true } +/// Returns an iterator over bindings. +/// +/// Includes dynamically populated bindings like the MRU UI. +fn make_binds_iter<'a>( + config: &'a Config, + mru: &'a mut WindowMruUi, + mods: Modifiers, +) -> impl Iterator<Item = &'a Bind> + Clone { + // Figure out the binds to use depending on whether the MRU is enabled and/or open. + let general_binds = (!mru.is_open()).then_some(config.binds.0.iter()); + let general_binds = general_binds.into_iter().flatten(); + + let mru_binds = + (config.recent_windows.on || mru.is_open()).then_some(config.recent_windows.binds.iter()); + let mru_binds = mru_binds.into_iter().flatten(); + + let mru_open_binds = mru.is_open().then(|| mru.opened_bindings(mods)); + let mru_open_binds = mru_open_binds.into_iter().flatten(); + + // MRU binds take precedence over general ones. + mru_binds.chain(mru_open_binds).chain(general_binds) +} + #[cfg(test)] mod tests { use std::cell::Cell; |
