diff --git a/src/input/mod.rs b/src/input/mod.rs index 279028a87..962a8c839 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -45,10 +45,10 @@ use crate::niri::State; use crate::ui::screenshot_ui::ScreenshotUi; use crate::utils::spawning::spawn; use crate::utils::{center, get_monotonic_time, ResizeEdge}; -use crate::window::Mapped; pub mod backend_ext; pub mod move_grab; +pub mod pick_window_grab; pub mod resize_grab; pub mod scroll_tracker; pub mod spatial_movement_grab; @@ -383,14 +383,16 @@ impl State { } } - if pressed && raw == Some(Keysym::Escape) { - if let Some(tx) = this.niri.pick_window.take() { - this.niri.suppressed_keys.insert(key_code); - let _ = tx.send_blocking(None); - // Redraw to update the cursor. - this.niri.queue_redraw_all(); - return FilterResult::Intercept(None); - } + if pressed && raw == Some(Keysym::Escape) && this.niri.pick_window.is_some() { + // We window picking state so the pick window grab must be active. + // Unsetting it cancels window picking. + this.niri + .seat + .get_pointer() + .unwrap() + .unset_grab(this, serial, time); + this.niri.suppressed_keys.insert(key_code); + return FilterResult::Intercept(None); } let bindings = &this.niri.config.borrow().binds; @@ -2032,13 +2034,6 @@ impl State { self.niri.pointer_hidden = false; self.niri.tablet_cursor_location = None; - if let Some(tx) = self.niri.pick_window.take() { - let _ = tx.send_blocking(self.niri.window_under_cursor().map(Mapped::id)); - // Redraw to update the cursor. - self.niri.queue_redraw_all(); - return; - } - if let Some(mapped) = self.niri.window_under_cursor() { let window = mapped.window.clone(); diff --git a/src/input/pick_window_grab.rs b/src/input/pick_window_grab.rs new file mode 100644 index 000000000..7ec501a7f --- /dev/null +++ b/src/input/pick_window_grab.rs @@ -0,0 +1,172 @@ +use smithay::backend::input::ButtonState; +use smithay::input::pointer::{ + CursorImageStatus, GrabStartData as PointerGrabStartData, PointerGrab, +}; + +use crate::niri::State; +use crate::window::Mapped; + +pub struct PickWindowGrab { + start_data: PointerGrabStartData, +} + +impl PickWindowGrab { + pub fn new(start_data: PointerGrabStartData) -> Self { + Self { start_data } + } + + fn on_ungrab(&mut self, state: &mut State) { + if let Some(tx) = state.niri.pick_window.take() { + let _ = tx.send_blocking(None); + } + state + .niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + // Redraw to update the cursor. + state.niri.queue_redraw_all(); + } +} + +impl PointerGrab for PickWindowGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + _focus: Option<( + ::PointerFocus, + smithay::utils::Point, + )>, + event: &smithay::input::pointer::MotionEvent, + ) { + handle.motion(data, None, event); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + _focus: Option<( + ::PointerFocus, + smithay::utils::Point, + )>, + event: &smithay::input::pointer::RelativeMotionEvent, + ) { + handle.relative_motion(data, None, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::ButtonEvent, + ) { + if event.state == ButtonState::Pressed { + if let Some(tx) = data.niri.pick_window.take() { + let _ = tx.send_blocking( + data.niri + .window_under(handle.current_location()) + .map(Mapped::id), + ); + } + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + details: smithay::input::pointer::AxisFrame, + ) { + handle.axis(data, details); + } + + fn frame( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + ) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut smithay::input::pointer::PointerInnerHandle<'_, State>, + event: &smithay::input::pointer::GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &smithay::input::pointer::GrabStartData { + &self.start_data + } + + fn unset(&mut self, data: &mut State) { + self.on_ungrab(data); + } +} diff --git a/src/ipc/server.rs b/src/ipc/server.rs index f60c3ff73..d2a160f9b 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -18,12 +18,17 @@ use niri_config::OutputName; use niri_ipc::state::{EventStreamState, EventStreamStatePart as _}; use niri_ipc::{Event, KeyboardLayouts, OutputConfigChanged, Reply, Request, Response, Workspace}; use smithay::desktop::layer_map_for_output; +use smithay::input::pointer::{ + CursorIcon, CursorImageStatus, Focus, GrabStartData as PointerGrabStartData, +}; use smithay::reexports::calloop::generic::Generic; use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction}; use smithay::reexports::rustix::fs::unlink; +use smithay::utils::SERIAL_COUNTER; use smithay::wayland::shell::wlr_layer::{KeyboardInteractivity, Layer}; use crate::backend::IpcOutputMap; +use crate::input::pick_window_grab::PickWindowGrab; use crate::layout::workspace::WorkspaceId; use crate::niri::State; use crate::utils::{version, with_toplevel_role}; @@ -325,11 +330,24 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply { Request::PickWindow => { let (tx, rx) = async_channel::bounded(1); ctx.event_loop.insert_idle(move |state| { - if let Some(tx) = state.niri.pick_window.replace(tx) { - // Cancel previous ongoing pick, if any. + let Some(pointer) = state.niri.seat.get_pointer() else { let _ = tx.send_blocking(None); - } - + return; + }; + let start_data = PointerGrabStartData { + focus: None, + button: 0, + location: pointer.current_location(), + }; + let grab = PickWindowGrab::new(start_data); + // The `WindowPickGrab` ungrab handler will cancel the previous ongoing pick, if + // any. + pointer.set_grab(state, grab, SERIAL_COUNTER.next_serial(), Focus::Keep); + state.niri.pick_window = Some(tx); + state + .niri + .cursor_manager + .set_cursor_image(CursorImageStatus::Named(CursorIcon::Crosshair)); // Redraw to update the cursor. state.niri.queue_redraw_all(); }); diff --git a/src/niri.rs b/src/niri.rs index 7264334c7..f3b6d3c2a 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -3012,12 +3012,7 @@ impl Niri { // Get the render cursor to draw. let cursor_scale = output_scale.integer_scale(); - let render_cursor = if self.pick_window.is_some() { - self.cursor_manager - .get_render_cursor_named(CursorIcon::Crosshair, cursor_scale) - } else { - self.cursor_manager.get_render_cursor(cursor_scale) - }; + let render_cursor = self.cursor_manager.get_render_cursor(cursor_scale); let output_scale = Scale::from(output.current_scale().fractional_scale());