aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbbb651 🇮🇱 <53972231+bbb651@users.noreply.github.com>2025-02-26 14:22:27 +0200
committerGitHub <noreply@github.com>2025-02-26 15:22:27 +0300
commit16405b9b2b99edaf9388df6a7228ca07f110769d (patch)
treed05734655900336c809bb212230cb4d34a6325b2 /src
parent4719cc6d5942c70f43ae167d66d2383708ae3536 (diff)
downloadniri-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.rs2
-rw-r--r--src/input/mod.rs15
-rw-r--r--src/input/pick_window_grab.rs167
-rw-r--r--src/ipc/client.rs18
-rw-r--r--src/ipc/server.rs34
-rw-r--r--src/niri.rs5
6 files changed, 240 insertions, 1 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 041b0343..9b106485 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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,