diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index f8d393654..3dbc3a537 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -63,6 +63,8 @@ pub enum Request { FocusedOutput, /// Request information about the focused window. FocusedWindow, + /// Request picking a window and get its information. + PickWindow, /// Perform an action. Action(Action), /// Change output configuration temporarily. @@ -129,6 +131,10 @@ pub enum Response { FocusedOutput(Option), /// Information about the focused window. FocusedWindow(Option), + /// Information about the picked window. + /// + /// `None` if the request was cancelled by the user. + PickedWindow(Option), /// Output configuration change result. OutputConfigChanged(OutputConfigChanged), } diff --git a/src/cli.rs b/src/cli.rs index 041b03439..1ec16597f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -72,6 +72,8 @@ pub enum Msg { FocusedOutput, /// Print information about the focused window. FocusedWindow, + /// Request picking a window. + PickWindow, /// Perform an action. Action { #[command(subcommand)] diff --git a/src/input/mod.rs b/src/input/mod.rs index b08fe3808..6bba9f712 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -361,6 +361,14 @@ impl State { } } + if let Some(tx) = this + .niri + .pick_window + .take_if(move |_| raw == Some(Keysym::Escape)) + { + let _ = tx.send_blocking(None); + } + should_intercept_key( &mut this.niri.suppressed_keys, bindings, @@ -1833,6 +1841,14 @@ impl State { self.niri.pointer_hidden = false; self.niri.tablet_cursor_location = None; + if let Some(tx) = self.niri.pick_window.take() { + if let Some(mapped_id) = self.niri.window_under_cursor().map(|mapped| mapped.id()) { + let _ = tx.send_blocking(Some(mapped_id)); + return; + } + self.niri.pick_window = Some(tx); + } + if let Some(mapped) = self.niri.window_under_cursor() { let window = mapped.window.clone(); diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 8682d8d3f..f53411281 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!("Picking window was cancelled."); + } + } 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 f8b661e17..eb050fcef 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -309,6 +309,19 @@ 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| { + state.niri.pick_window = Some(tx); + }); + 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 16758ab57..aaf3e2554 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -153,6 +153,7 @@ use crate::utils::{ center, center_f64, 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.]; @@ -332,6 +333,8 @@ pub struct Niri { pub hotkey_overlay: HotkeyOverlay, pub exit_confirm_dialog: Option, + pub pick_window: Option>>, + pub debug_draw_opaque_regions: bool, pub debug_draw_damage: bool, @@ -2075,6 +2078,8 @@ impl Niri { hotkey_overlay, exit_confirm_dialog, + pick_window: None, + debug_draw_opaque_regions: false, debug_draw_damage: false,