diff options
| author | bbb651 🇮🇱 <53972231+bbb651@users.noreply.github.com> | 2025-02-26 14:22:27 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-26 15:22:27 +0300 |
| commit | 16405b9b2b99edaf9388df6a7228ca07f110769d (patch) | |
| tree | d05734655900336c809bb212230cb4d34a6325b2 /src | |
| parent | 4719cc6d5942c70f43ae167d66d2383708ae3536 (diff) | |
| download | niri-16405b9b2b99edaf9388df6a7228ca07f110769d.tar.gz niri-16405b9b2b99edaf9388df6a7228ca07f110769d.tar.bz2 niri-16405b9b2b99edaf9388df6a7228ca07f110769d.zip | |
Implement `niri msg pick-window`
* feat: `niri msg pick-window`
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 2 | ||||
| -rw-r--r-- | src/input/mod.rs | 15 | ||||
| -rw-r--r-- | src/input/pick_window_grab.rs | 167 | ||||
| -rw-r--r-- | src/ipc/client.rs | 18 | ||||
| -rw-r--r-- | src/ipc/server.rs | 34 | ||||
| -rw-r--r-- | src/niri.rs | 5 |
6 files changed, 240 insertions, 1 deletions
@@ -72,6 +72,8 @@ pub enum Msg { FocusedOutput, /// Print information about the focused window. FocusedWindow, + /// Pick a window with the mouse and print information about it. + PickWindow, /// Perform an action. Action { #[command(subcommand)] diff --git a/src/input/mod.rs b/src/input/mod.rs index 51555a96..8e22098c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -48,6 +48,7 @@ use crate::utils::{center, get_monotonic_time, ResizeEdge}; 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; @@ -367,7 +368,6 @@ impl 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(); @@ -383,6 +383,19 @@ impl State { } } + 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; should_intercept_key( &mut this.niri.suppressed_keys, bindings, diff --git a/src/input/pick_window_grab.rs b/src/input/pick_window_grab.rs new file mode 100644 index 00000000..9a3b22ae --- /dev/null +++ b/src/input/pick_window_grab.rs @@ -0,0 +1,167 @@ +use smithay::backend::input::ButtonState; +use smithay::input::pointer::{ + AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, + GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, +}; +use smithay::input::SeatHandler; +use smithay::utils::{Logical, Point}; + +use crate::niri::State; +use crate::window::Mapped; + +pub struct PickWindowGrab { + start_data: PointerGrabStartData<State>, +} + +impl PickWindowGrab { + pub fn new(start_data: PointerGrabStartData<State>) -> 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<State> for PickWindowGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, + event: &MotionEvent, + ) { + handle.motion(data, None, event); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, None, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &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 PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details); + } + + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &PointerGrabStartData<State> { + &self.start_data + } + + fn unset(&mut self, data: &mut State) { + self.on_ungrab(data); + } +} diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 8682d8d3..66cdaf7d 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -19,6 +19,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { Msg::Outputs => Request::Outputs, Msg::FocusedWindow => Request::FocusedWindow, Msg::FocusedOutput => Request::FocusedOutput, + Msg::PickWindow => Request::PickWindow, Msg::Action { action } => Request::Action(action.clone()), Msg::Output { output, action } => Request::Output { output: output.clone(), @@ -252,6 +253,23 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { println!("No output is focused."); } } + Msg::PickWindow => { + let Response::PickedWindow(window) = response else { + bail!("unexpected response: expected PickedWindow, got {response:?}"); + }; + + if json { + let window = serde_json::to_string(&window).context("error formatting response")?; + println!("{window}"); + return Ok(()); + } + + if let Some(window) = window { + print_window(&window); + } else { + println!("No window selected."); + } + } Msg::Action { .. } => { let Response::Handled = response else { bail!("unexpected response: expected Handled, got {response:?}"); diff --git a/src/ipc/server.rs b/src/ipc/server.rs index 6706928a..c8f8edcf 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}; @@ -322,6 +327,35 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply { let window = windows.values().find(|win| win.is_focused).cloned(); Response::FocusedWindow(window) } + Request::PickWindow => { + let (tx, rx) = async_channel::bounded(1); + ctx.event_loop.insert_idle(move |state| { + let pointer = state.niri.seat.get_pointer().unwrap(); + 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::Clear); + 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(); + }); + let result = rx.recv().await; + let id = result.map_err(|_| String::from("error getting picked window info"))?; + let window = id.and_then(|id| { + let state = ctx.event_stream_state.borrow(); + state.windows.windows.get(&id.get()).cloned() + }); + Response::PickedWindow(window) + } Request::Action(action) => { let (tx, rx) = async_channel::bounded(1); diff --git a/src/niri.rs b/src/niri.rs index b013927b..f3b6d3c2 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -159,6 +159,7 @@ use crate::utils::{ center, center_f64, expand_home, get_monotonic_time, ipc_transform_to_smithay, logical_output, make_screenshot_path, output_matches_name, output_size, send_scale_transform, write_png_rgba8, }; +use crate::window::mapped::MappedId; use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped, WindowRef}; const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.]; @@ -356,6 +357,8 @@ pub struct Niri { pub hotkey_overlay: HotkeyOverlay, pub exit_confirm_dialog: Option<ExitConfirmDialog>, + pub pick_window: Option<async_channel::Sender<Option<MappedId>>>, + pub debug_draw_opaque_regions: bool, pub debug_draw_damage: bool, @@ -2172,6 +2175,8 @@ impl Niri { hotkey_overlay, exit_confirm_dialog, + pick_window: None, + debug_draw_opaque_regions: false, debug_draw_damage: false, |
