From 30911023654b1f1028eb41bade9b1627d3c8344a Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 28 Mar 2024 13:45:24 +0400 Subject: Implement niri msg focused-window --- src/cli.rs | 2 ++ src/ipc/client.rs | 30 ++++++++++++++++++++++++++++++ src/ipc/server.rs | 25 +++++++++++++++++++++++++ src/niri.rs | 7 +++++++ 4 files changed, 64 insertions(+) (limited to 'src') diff --git a/src/cli.rs b/src/cli.rs index c02df4f9..1a1b3eb9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -54,6 +54,8 @@ pub enum Sub { pub enum Msg { /// List connected outputs. Outputs, + /// Print information about the focused window. + FocusedWindow, /// Perform an action. Action { #[command(subcommand)] diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 18d49fcb..424b243d 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -21,6 +21,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { let request = match &msg { Msg::Outputs => Request::Outputs, + Msg::FocusedWindow => Request::FocusedWindow, Msg::Action { action } => Request::Action(action.clone()), }; let mut buf = serde_json::to_vec(&request).unwrap(); @@ -147,6 +148,35 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { println!(); } } + Msg::FocusedWindow => { + let Response::FocusedWindow(window) = response else { + bail!("unexpected response: expected FocusedWindow, got {response:?}"); + }; + + if json { + let window = serde_json::to_string(&window).context("error formatting response")?; + println!("{window}"); + return Ok(()); + } + + if let Some(window) = window { + println!("Focused window:"); + + if let Some(title) = window.title { + println!(" Title: \"{title}\""); + } else { + println!(" Title: (unset)"); + } + + if let Some(app_id) = window.app_id { + println!(" App ID: \"{app_id}\""); + } else { + println!(" App ID: (unset)"); + } + } else { + println!("No window is focused."); + } + } 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 43cbb8b4..38a9b3dd 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -9,9 +9,12 @@ use directories::BaseDirs; use futures_util::io::{AsyncReadExt, BufReader}; use futures_util::{AsyncBufReadExt, AsyncWriteExt}; use niri_ipc::{Request, Response}; +use smithay::desktop::Window; use smithay::reexports::calloop::generic::Generic; use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction}; use smithay::reexports::rustix::fs::unlink; +use smithay::wayland::compositor::with_states; +use smithay::wayland::shell::xdg::XdgToplevelSurfaceData; use crate::backend::IpcOutputMap; use crate::niri::State; @@ -23,6 +26,7 @@ pub struct IpcServer { struct ClientCtx { event_loop: LoopHandle<'static, State>, ipc_outputs: Arc>, + ipc_focused_window: Arc>>, } impl IpcServer { @@ -87,6 +91,7 @@ fn on_new_ipc_client(state: &mut State, stream: UnixStream) { let ctx = ClientCtx { event_loop: state.niri.event_loop.clone(), ipc_outputs: state.backend.ipc_outputs(), + ipc_focused_window: state.niri.ipc_focused_window.clone(), }; let future = async move { @@ -128,6 +133,26 @@ fn process(ctx: &ClientCtx, buf: &str) -> anyhow::Result { let ipc_outputs = ctx.ipc_outputs.lock().unwrap().clone(); Response::Outputs(ipc_outputs) } + Request::FocusedWindow => { + let window = ctx.ipc_focused_window.lock().unwrap().clone(); + let window = window.map(|window| { + let wl_surface = window.toplevel().expect("no X11 support").wl_surface(); + with_states(wl_surface, |states| { + let role = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + + niri_ipc::Window { + title: role.title.clone(), + app_id: role.app_id.clone(), + } + }) + }); + Response::FocusedWindow(window) + } Request::Action(action) => { let action = niri_config::Action::from(action); ctx.event_loop.insert_idle(move |state| { diff --git a/src/niri.rs b/src/niri.rs index 98d714c2..b36b0f55 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -230,6 +230,7 @@ pub struct Niri { pub ipc_server: Option, pub ipc_outputs_changed: bool, + pub ipc_focused_window: Arc>>, // Casts are dropped before PipeWire to prevent a double-free (yay). pub casts: Vec, @@ -715,6 +716,8 @@ impl State { focus ); + let mut newly_focused_window = None; + // Tell the windows their new focus state for window rule purposes. if let KeyboardFocus::Layout { surface: Some(surface), @@ -730,9 +733,12 @@ impl State { { if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) { mapped.set_is_focused(true); + newly_focused_window = Some(mapped.window.clone()); } } + *self.niri.ipc_focused_window.lock().unwrap() = newly_focused_window; + if let Some(grab) = self.niri.popup_grab.as_mut() { if Some(&grab.root) != focus.surface() { trace!( @@ -1390,6 +1396,7 @@ impl Niri { ipc_server, ipc_outputs_changed: false, + ipc_focused_window: Arc::new(Mutex::new(None)), pipewire, casts: vec![], -- cgit