diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-11 13:21:05 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-11 13:21:05 +0400 |
| commit | beba87354a1fd30a95eaaf6c98eec72797e4baa7 (patch) | |
| tree | 0298d7911dd5daee544b39ae7ee1f537e119cadf /src/input | |
| parent | 078724369d464d5184a3d93a1b71c10092092d0a (diff) | |
| download | niri-beba87354a1fd30a95eaaf6c98eec72797e4baa7.tar.gz niri-beba87354a1fd30a95eaaf6c98eec72797e4baa7.tar.bz2 niri-beba87354a1fd30a95eaaf6c98eec72797e4baa7.zip | |
Group input-related things in a subfolder
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/mod.rs | 2565 | ||||
| -rw-r--r-- | src/input/resize_grab.rs | 176 | ||||
| -rw-r--r-- | src/input/scroll_tracker.rs | 40 | ||||
| -rw-r--r-- | src/input/swipe_tracker.rs | 87 |
4 files changed, 2868 insertions, 0 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 00000000..69650a12 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,2565 @@ +use std::any::Any; +use std::collections::hash_map::Entry; +use std::collections::HashSet; +use std::time::Duration; + +use calloop::timer::{TimeoutAction, Timer}; +use input::event::gesture::GestureEventCoordinates as _; +use niri_config::{Action, Bind, Binds, Key, Modifiers, Trigger}; +use niri_ipc::LayoutSwitchTarget; +use smithay::backend::input::{ + AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event, + GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, + InputBackend, InputEvent, KeyState, KeyboardKeyEvent, MouseButton, PointerAxisEvent, + PointerButtonEvent, PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent, + TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, +}; +use smithay::backend::libinput::LibinputInputBackend; +use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState}; +use smithay::input::pointer::{ + AxisFrame, ButtonEvent, CursorImageStatus, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, + GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, RelativeMotionEvent, +}; +use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent}; +use smithay::utils::{Logical, Point, SERIAL_COUNTER}; +use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint}; +use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; + +use self::resize_grab::ResizeGrab; +use crate::niri::State; +use crate::ui::screenshot_ui::ScreenshotUi; +use crate::utils::spawning::spawn; +use crate::utils::{center, get_monotonic_time, ResizeEdge}; + +pub mod resize_grab; +pub mod scroll_tracker; +pub mod swipe_tracker; + +pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompositorMod { + Super, + Alt, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TabletData { + pub aspect_ratio: f64, +} + +impl State { + pub fn process_input_event<I: InputBackend + 'static>(&mut self, event: InputEvent<I>) + where + I::Device: 'static, // Needed for downcasting. + { + let _span = tracy_client::span!("process_input_event"); + + // A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it + // doesn't always trigger due to damage, etc. So run it here right before it might prove + // important. Besides, animations affect the input, so it's best to have up-to-date values + // here. + self.niri.layout.advance_animations(get_monotonic_time()); + + if self.niri.monitors_active { + // Notify the idle-notifier of activity. + if should_notify_activity(&event) { + let _span = tracy_client::span!("IdleNotifierState::notify_activity"); + self.niri + .idle_notifier_state + .notify_activity(&self.niri.seat); + } + } else { + // Power on monitors if they were off. + if should_activate_monitors(&event) { + self.niri.activate_monitors(&mut self.backend); + + // Notify the idle-notifier of activity only if we're also powering on the + // monitors. + self.niri + .idle_notifier_state + .notify_activity(&self.niri.seat); + } + } + + let hide_hotkey_overlay = + self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event); + + let hide_exit_confirm_dialog = self + .niri + .exit_confirm_dialog + .as_ref() + .map_or(false, |d| d.is_open()) + && should_hide_exit_confirm_dialog(&event); + + use InputEvent::*; + match event { + DeviceAdded { device } => self.on_device_added(device), + DeviceRemoved { device } => self.on_device_removed(device), + Keyboard { event } => self.on_keyboard::<I>(event), + PointerMotion { event } => self.on_pointer_motion::<I>(event), + PointerMotionAbsolute { event } => self.on_pointer_motion_absolute::<I>(event), + PointerButton { event } => self.on_pointer_button::<I>(event), + PointerAxis { event } => self.on_pointer_axis::<I>(event), + TabletToolAxis { event } => self.on_tablet_tool_axis::<I>(event), + TabletToolTip { event } => self.on_tablet_tool_tip::<I>(event), + TabletToolProximity { event } => self.on_tablet_tool_proximity::<I>(event), + TabletToolButton { event } => self.on_tablet_tool_button::<I>(event), + GestureSwipeBegin { event } => self.on_gesture_swipe_begin::<I>(event), + GestureSwipeUpdate { event } => self.on_gesture_swipe_update::<I>(event), + GestureSwipeEnd { event } => self.on_gesture_swipe_end::<I>(event), + GesturePinchBegin { event } => self.on_gesture_pinch_begin::<I>(event), + GesturePinchUpdate { event } => self.on_gesture_pinch_update::<I>(event), + GesturePinchEnd { event } => self.on_gesture_pinch_end::<I>(event), + GestureHoldBegin { event } => self.on_gesture_hold_begin::<I>(event), + GestureHoldEnd { event } => self.on_gesture_hold_end::<I>(event), + TouchDown { event } => self.on_touch_down::<I>(event), + TouchMotion { event } => self.on_touch_motion::<I>(event), + TouchUp { event } => self.on_touch_up::<I>(event), + TouchCancel { event } => self.on_touch_cancel::<I>(event), + TouchFrame { event } => self.on_touch_frame::<I>(event), + SwitchToggle { .. } => (), + Special(_) => (), + } + + // Do this last so that screenshot still gets it. + // FIXME: do this in a less cursed fashion somehow. + if hide_hotkey_overlay && self.niri.hotkey_overlay.hide() { + self.niri.queue_redraw_all(); + } + + if let Some(dialog) = &mut self.niri.exit_confirm_dialog { + if hide_exit_confirm_dialog && dialog.hide() { + self.niri.queue_redraw_all(); + } + } + } + + pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) { + let _span = tracy_client::span!("process_libinput_event"); + + match event { + InputEvent::DeviceAdded { device } => { + self.niri.devices.insert(device.clone()); + + if device.has_capability(input::DeviceCapability::TabletTool) { + match device.size() { + Some((w, h)) => { + let aspect_ratio = w / h; + let data = TabletData { aspect_ratio }; + self.niri.tablets.insert(device.clone(), data); + } + None => { + warn!("tablet tool device has no size"); + } + } + } + + if device.has_capability(input::DeviceCapability::Keyboard) { + if let Some(led_state) = self + .niri + .seat + .get_keyboard() + .map(|keyboard| keyboard.led_state()) + { + device.led_update(led_state.into()); + } + } + + if device.has_capability(input::DeviceCapability::Touch) { + self.niri.touch.insert(device.clone()); + } + + apply_libinput_settings(&self.niri.config.borrow().input, device); + } + InputEvent::DeviceRemoved { device } => { + self.niri.touch.remove(device); + self.niri.tablets.remove(device); + self.niri.devices.remove(device); + } + _ => (), + } + } + + fn on_device_added(&mut self, device: impl Device) { + if device.has_capability(DeviceCapability::TabletTool) { + let tablet_seat = self.niri.seat.tablet_seat(); + + let desc = TabletDescriptor::from(&device); + tablet_seat.add_tablet::<Self>(&self.niri.display_handle, &desc); + } + if device.has_capability(DeviceCapability::Touch) && self.niri.seat.get_touch().is_none() { + self.niri.seat.add_touch(); + } + } + + fn on_device_removed(&mut self, device: impl Device) { + if device.has_capability(DeviceCapability::TabletTool) { + let tablet_seat = self.niri.seat.tablet_seat(); + + let desc = TabletDescriptor::from(&device); + tablet_seat.remove_tablet(&desc); + + // If there are no tablets in seat we can remove all tools + if tablet_seat.count_tablets() == 0 { + tablet_seat.clear_tools(); + } + } + if device.has_capability(DeviceCapability::Touch) && self.niri.touch.is_empty() { + self.niri.seat.remove_touch(); + } + } + + /// Computes the cursor position for the tablet event. + /// + /// This function handles the tablet output mapping, as well as coordinate clamping and aspect + /// ratio correction. + fn compute_tablet_position<I: InputBackend>( + &self, + event: &(impl Event<I> + TabletToolEvent<I>), + ) -> Option<Point<f64, Logical>> + where + I::Device: 'static, + { + let output = self.niri.output_for_tablet()?; + let output_geo = self.niri.global_space.output_geometry(output).unwrap(); + + let mut pos = event.position_transformed(output_geo.size); + pos.x /= output_geo.size.w as f64; + pos.y /= output_geo.size.h as f64; + + let device = event.device(); + if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() { + if let Some(data) = self.niri.tablets.get(device) { + // This code does the same thing as mutter with "keep aspect ratio" enabled. + let output_aspect_ratio = output_geo.size.w as f64 / output_geo.size.h as f64; + let ratio = data.aspect_ratio / output_aspect_ratio; + + if ratio > 1. { + pos.x *= ratio; + } else { + pos.y /= ratio; + } + } + }; + + pos.x *= output_geo.size.w as f64; + pos.y *= output_geo.size.h as f64; + pos.x = pos.x.clamp(0.0, output_geo.size.w as f64 - 1.); + pos.y = pos.y.clamp(0.0, output_geo.size.h as f64 - 1.); + Some(pos + output_geo.loc.to_f64()) + } + + fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) { + let comp_mod = self.backend.mod_key(); + + let serial = SERIAL_COUNTER.next_serial(); + let time = Event::time_msec(&event); + let pressed = event.state() == KeyState::Pressed; + + let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input( + self, + event.key_code(), + event.state(), + serial, + time, + |this, mods, keysym| { + let bindings = &this.niri.config.borrow().binds; + let key_code = event.key_code(); + let modified = keysym.modified_sym(); + let raw = keysym.raw_latin_sym_or_raw_current_sym(); + + if let Some(dialog) = &this.niri.exit_confirm_dialog { + if dialog.is_open() && pressed && raw == Some(Keysym::Return) { + info!("quitting after confirming exit dialog"); + this.niri.stop_signal.stop(); + } + } + + should_intercept_key( + &mut this.niri.suppressed_keys, + bindings, + comp_mod, + key_code, + modified, + raw, + pressed, + *mods, + &this.niri.screenshot_ui, + this.niri.config.borrow().input.disable_power_key_handling, + ) + }, + ) else { + return; + }; + + // Filter actions when the key is released or the session is locked. + if !pressed { + return; + } + + self.handle_bind(bind); + } + + pub fn handle_bind(&mut self, bind: Bind) { + let Some(cooldown) = bind.cooldown else { + self.do_action(bind.action, bind.allow_when_locked); + return; + }; + + // Check this first so that it doesn't trigger the cooldown. + if self.niri.is_locked() && !(bind.allow_when_locked || allowed_when_locked(&bind.action)) { + return; + } + + match self.niri.bind_cooldown_timers.entry(bind.key) { + // The bind is on cooldown. + Entry::Occupied(_) => (), + Entry::Vacant(entry) => { + let timer = Timer::from_duration(cooldown); + let token = self + .niri + .event_loop + .insert_source(timer, move |_, _, state| { + if state.niri.bind_cooldown_timers.remove(&bind.key).is_none() { + error!("bind cooldown timer entry disappeared"); + } + TimeoutAction::Drop + }) + .unwrap(); + entry.insert(token); + + self.do_action(bind.action, bind.allow_when_locked); + } + } + } + + pub fn do_action(&mut self, action: Action, allow_when_locked: bool) { + if self.niri.is_locked() && !(allow_when_locked || allowed_when_locked(&action)) { + return; + } + + if let Some(touch) = self.niri.seat.get_touch() { + touch.cancel(self); + } + + match action { + Action::Quit(skip_confirmation) => { + if !skip_confirmation { + if let Some(dialog) = &mut self.niri.exit_confirm_dialog { + if dialog.show() { + self.niri.queue_redraw_all(); + } + return; + } + } + + info!("quitting as requested"); + self.niri.stop_signal.stop() + } + Action::ChangeVt(vt) => { + self.backend.change_vt(vt); + // Changing VT may not deliver the key releases, so clear the state. + self.niri.suppressed_keys.clear(); + } + Action::Suspend => { + self.backend.suspend(); + // Suspend may not deliver the key releases, so clear the state. + self.niri.suppressed_keys.clear(); + } + Action::PowerOffMonitors => { + self.niri.deactivate_monitors(&mut self.backend); + } + Action::ToggleDebugTint => { + self.backend.toggle_debug_tint(); + self.niri.queue_redraw_all(); + } + Action::DebugToggleOpaqueRegions => { + self.niri.debug_draw_opaque_regions = !self.niri.debug_draw_opaque_regions; + self.niri.queue_redraw_all(); + } + Action::DebugToggleDamage => { + self.niri.debug_toggle_damage(); + } + Action::Spawn(command) => { + spawn(command); + } + Action::DoScreenTransition(delay_ms) => { + self.backend.with_primary_renderer(|renderer| { + self.niri.do_screen_transition(renderer, delay_ms); + }); + } + Action::ScreenshotScreen => { + let active = self.niri.layout.active_output().cloned(); + if let Some(active) = active { + self.backend.with_primary_renderer(|renderer| { + if let Err(err) = self.niri.screenshot(renderer, &active) { + warn!("error taking screenshot: {err:?}"); + } + }); + } + } + Action::ConfirmScreenshot => { + self.backend.with_primary_renderer(|renderer| { + match self.niri.screenshot_ui.capture(renderer) { + Ok((size, pixels)) => { + if let Err(err) = self.niri.save_screenshot(size, pixels) { + warn!("error saving screenshot: {err:?}"); + } + } + Err(err) => { + warn!("error capturing screenshot: {err:?}"); + } + } + }); + + self.niri.screenshot_ui.close(); + self.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + self.niri.queue_redraw_all(); + } + Action::CancelScreenshot => { + self.niri.screenshot_ui.close(); + self.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + self.niri.queue_redraw_all(); + } + Action::Screenshot => { + self.backend.with_primary_renderer(|renderer| { + self.niri.open_screenshot_ui(renderer); + }); + } + Action::ScreenshotWindow => { + let active = self.niri.layout.active_window(); + if let Some((mapped, output)) = active { + self.backend.with_primary_renderer(|renderer| { + if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) { + warn!("error taking screenshot: {err:?}"); + } + }); + } + } + Action::CloseWindow => { + if let Some(mapped) = self.niri.layout.focus() { + mapped.toplevel().send_close(); + } + } + Action::FullscreenWindow => { + let focus = self.niri.layout.focus().map(|m| m.window.clone()); + if let Some(window) = focus { + self.niri.layout.toggle_fullscreen(&window); + // FIXME: granular + self.niri.queue_redraw_all(); + } + } + Action::SwitchLayout(action) => { + self.niri.seat.get_keyboard().unwrap().with_xkb_state( + self, + |mut state| match action { + LayoutSwitchTarget::Next => state.cycle_next_layout(), + LayoutSwitchTarget::Prev => state.cycle_prev_layout(), + }, + ); + } + Action::MoveColumnLeft => { + self.niri.layout.move_left(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnRight => { + self.niri.layout.move_right(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnToFirst => { + self.niri.layout.move_column_to_first(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnToLast => { + self.niri.layout.move_column_to_last(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowDown => { + self.niri.layout.move_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowUp => { + self.niri.layout.move_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowDownOrToWorkspaceDown => { + self.niri.layout.move_down_or_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowUpOrToWorkspaceUp => { + self.niri.layout.move_up_or_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::ConsumeOrExpelWindowLeft => { + self.niri.layout.consume_or_expel_window_left(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::ConsumeOrExpelWindowRight => { + self.niri.layout.consume_or_expel_window_right(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusColumnLeft => { + self.niri.layout.focus_left(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusColumnRight => { + self.niri.layout.focus_right(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusColumnFirst => { + self.niri.layout.focus_column_first(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusColumnLast => { + self.niri.layout.focus_column_last(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWindowDown => { + self.niri.layout.focus_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWindowUp => { + self.niri.layout.focus_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWindowOrWorkspaceDown => { + self.niri.layout.focus_window_or_workspace_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWindowOrWorkspaceUp => { + self.niri.layout.focus_window_or_workspace_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowToWorkspaceDown => { + self.niri.layout.move_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowToWorkspaceUp => { + self.niri.layout.move_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWindowToWorkspace(idx) => { + let idx = idx.saturating_sub(1) as usize; + self.niri.layout.move_to_workspace(idx); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnToWorkspaceDown => { + self.niri.layout.move_column_to_workspace_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnToWorkspaceUp => { + self.niri.layout.move_column_to_workspace_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveColumnToWorkspace(idx) => { + let idx = idx.saturating_sub(1) as usize; + self.niri.layout.move_column_to_workspace(idx); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWorkspaceDown => { + self.niri.layout.switch_workspace_down(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWorkspaceUp => { + self.niri.layout.switch_workspace_up(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWorkspace(idx) => { + let idx = idx.saturating_sub(1) as usize; + + let config = &self.niri.config; + if config.borrow().input.workspace_auto_back_and_forth { + self.niri.layout.switch_workspace_auto_back_and_forth(idx); + } else { + self.niri.layout.switch_workspace(idx); + } + + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::FocusWorkspacePrevious => { + self.niri.layout.switch_workspace_previous(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWorkspaceDown => { + self.niri.layout.move_workspace_down(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MoveWorkspaceUp => { + self.niri.layout.move_workspace_up(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::ConsumeWindowIntoColumn => { + self.niri.layout.consume_into_column(); + // This does not cause immediate focus or window size change, so warping mouse to + // focus won't do anything here. + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::ExpelWindowFromColumn => { + self.niri.layout.expel_from_column(); + self.maybe_warp_cursor_to_focus(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::SwitchPresetColumnWidth => { + self.niri.layout.toggle_width(); + } + Action::CenterColumn => { + self.niri.layout.center_column(); + // FIXME: granular + self.niri.queue_redraw_all(); + } + Action::MaximizeColumn => { + self.niri.layout.toggle_full_width(); + } + Action::FocusMonitorLeft => { + if let Some(output) = self.niri.output_left() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::FocusMonitorRight => { + if let Some(output) = self.niri.output_right() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::FocusMonitorDown => { + if let Some(output) = self.niri.output_down() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::FocusMonitorUp => { + if let Some(output) = self.niri.output_up() { + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWindowToMonitorLeft => { + if let Some(output) = self.niri.output_left() { + self.niri.layout.move_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWindowToMonitorRight => { + if let Some(output) = self.niri.output_right() { + self.niri.layout.move_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWindowToMonitorDown => { + if let Some(output) = self.niri.output_down() { + self.niri.layout.move_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWindowToMonitorUp => { + if let Some(output) = self.niri.output_up() { + self.niri.layout.move_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveColumnToMonitorLeft => { + if let Some(output) = self.niri.output_left() { + self.niri.layout.move_column_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveColumnToMonitorRight => { + if let Some(output) = self.niri.output_right() { + self.niri.layout.move_column_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveColumnToMonitorDown => { + if let Some(output) = self.niri.output_down() { + self.niri.layout.move_column_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveColumnToMonitorUp => { + if let Some(output) = self.niri.output_up() { + self.niri.layout.move_column_to_output(&output); + self.niri.layout.focus_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::SetColumnWidth(change) => { + self.niri.layout.set_column_width(change); + } + Action::SetWindowHeight(change) => { + self.niri.layout.set_window_height(change); + } + Action::ResetWindowHeight => { + self.niri.layout.reset_window_height(); + } + Action::ShowHotkeyOverlay => { + if self.niri.hotkey_overlay.show() { + self.niri.queue_redraw_all(); + } + } + Action::MoveWorkspaceToMonitorLeft => { + if let Some(output) = self.niri.output_left() { + self.niri.layout.move_workspace_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWorkspaceToMonitorRight => { + if let Some(output) = self.niri.output_right() { + self.niri.layout.move_workspace_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWorkspaceToMonitorDown => { + if let Some(output) = self.niri.output_down() { + self.niri.layout.move_workspace_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + Action::MoveWorkspaceToMonitorUp => { + if let Some(output) = self.niri.output_up() { + self.niri.layout.move_workspace_to_output(&output); + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } + } + } + + fn on_pointer_motion<I: InputBackend>(&mut self, event: I::PointerMotionEvent) { + // We need an output to be able to move the pointer. + if self.niri.global_space.outputs().next().is_none() { + return; + } + + let serial = SERIAL_COUNTER.next_serial(); + + let pointer = self.niri.seat.get_pointer().unwrap(); + + let pos = pointer.current_location(); + + // We have an output, so we can compute the new location and focus. + let mut new_pos = pos + event.delta(); + + // We received an event for the regular pointer, so show it now. + self.niri.pointer_hidden = false; + self.niri.tablet_cursor_location = None; + + // Check if we have an active pointer constraint. + let mut pointer_confined = None; + if let Some(focus) = &self.niri.pointer_focus.surface { + let pos_within_surface = pos.to_i32_round() - focus.1; + + let mut pointer_locked = false; + with_pointer_constraint(&focus.0, &pointer, |constraint| { + let Some(constraint) = constraint else { return }; + if !constraint.is_active() { + return; + } + + // Constraint does not apply if not within region. + if let Some(region) = constraint.region() { + if !region.contains(pos_within_surface) { + return; + } + } + + match &*constraint { + PointerConstraint::Locked(_locked) => { + pointer_locked = true; |
