aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/mod.rs373
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;