aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-03-28 13:45:24 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-03-28 13:45:24 +0400
commit30911023654b1f1028eb41bade9b1627d3c8344a (patch)
treec60e76aff3f8fc4a86ef0a190e4cd0e029c2e954
parenta7b38192142440c59a4e793cf5de99b9a7f1c2c3 (diff)
downloadniri-30911023654b1f1028eb41bade9b1627d3c8344a.tar.gz
niri-30911023654b1f1028eb41bade9b1627d3c8344a.tar.bz2
niri-30911023654b1f1028eb41bade9b1627d3c8344a.zip
Implement niri msg focused-window
-rw-r--r--niri-ipc/src/lib.rs13
-rw-r--r--src/cli.rs2
-rw-r--r--src/ipc/client.rs30
-rw-r--r--src/ipc/server.rs25
-rw-r--r--src/niri.rs7
5 files changed, 77 insertions, 0 deletions
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs
index e43b9437..fb515e5d 100644
--- a/niri-ipc/src/lib.rs
+++ b/niri-ipc/src/lib.rs
@@ -14,6 +14,8 @@ pub const SOCKET_PATH_ENV: &str = "NIRI_SOCKET";
pub enum Request {
/// Request information about connected outputs.
Outputs,
+ /// Request information about the focused window.
+ FocusedWindow,
/// Perform an action.
Action(Action),
}
@@ -37,6 +39,8 @@ pub enum Response {
///
/// Map from connector name to output info.
Outputs(HashMap<String, Output>),
+ /// Information about the focused window.
+ FocusedWindow(Option<Window>),
}
/// Actions that niri can perform.
@@ -308,6 +312,15 @@ pub enum Transform {
Flipped270,
}
+/// Toplevel window.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Window {
+ /// Title, if set.
+ pub title: Option<String>,
+ /// Application ID, if set.
+ pub app_id: Option<String>,
+}
+
impl FromStr for SizeChange {
type Err = &'static str;
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<Mutex<IpcOutputMap>>,
+ ipc_focused_window: Arc<Mutex<Option<Window>>>,
}
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<Response> {
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::<XdgToplevelSurfaceData>()
+ .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<IpcServer>,
pub ipc_outputs_changed: bool,
+ pub ipc_focused_window: Arc<Mutex<Option<Window>>>,
// Casts are dropped before PipeWire to prevent a double-free (yay).
pub casts: Vec<Cast>,
@@ -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![],