mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Implement niri msg focused-window
This commit is contained in:
@@ -14,6 +14,8 @@ pub const SOCKET_PATH_ENV: &str = "NIRI_SOCKET";
|
|||||||
pub enum Request {
|
pub enum Request {
|
||||||
/// Request information about connected outputs.
|
/// Request information about connected outputs.
|
||||||
Outputs,
|
Outputs,
|
||||||
|
/// Request information about the focused window.
|
||||||
|
FocusedWindow,
|
||||||
/// Perform an action.
|
/// Perform an action.
|
||||||
Action(Action),
|
Action(Action),
|
||||||
}
|
}
|
||||||
@@ -37,6 +39,8 @@ pub enum Response {
|
|||||||
///
|
///
|
||||||
/// Map from connector name to output info.
|
/// Map from connector name to output info.
|
||||||
Outputs(HashMap<String, Output>),
|
Outputs(HashMap<String, Output>),
|
||||||
|
/// Information about the focused window.
|
||||||
|
FocusedWindow(Option<Window>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actions that niri can perform.
|
/// Actions that niri can perform.
|
||||||
@@ -308,6 +312,15 @@ pub enum Transform {
|
|||||||
Flipped270,
|
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 {
|
impl FromStr for SizeChange {
|
||||||
type Err = &'static str;
|
type Err = &'static str;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ pub enum Sub {
|
|||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
/// List connected outputs.
|
/// List connected outputs.
|
||||||
Outputs,
|
Outputs,
|
||||||
|
/// Print information about the focused window.
|
||||||
|
FocusedWindow,
|
||||||
/// Perform an action.
|
/// Perform an action.
|
||||||
Action {
|
Action {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let request = match &msg {
|
let request = match &msg {
|
||||||
Msg::Outputs => Request::Outputs,
|
Msg::Outputs => Request::Outputs,
|
||||||
|
Msg::FocusedWindow => Request::FocusedWindow,
|
||||||
Msg::Action { action } => Request::Action(action.clone()),
|
Msg::Action { action } => Request::Action(action.clone()),
|
||||||
};
|
};
|
||||||
let mut buf = serde_json::to_vec(&request).unwrap();
|
let mut buf = serde_json::to_vec(&request).unwrap();
|
||||||
@@ -147,6 +148,35 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
|||||||
println!();
|
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 { .. } => {
|
Msg::Action { .. } => {
|
||||||
let Response::Handled = response else {
|
let Response::Handled = response else {
|
||||||
bail!("unexpected response: expected Handled, got {response:?}");
|
bail!("unexpected response: expected Handled, got {response:?}");
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ use directories::BaseDirs;
|
|||||||
use futures_util::io::{AsyncReadExt, BufReader};
|
use futures_util::io::{AsyncReadExt, BufReader};
|
||||||
use futures_util::{AsyncBufReadExt, AsyncWriteExt};
|
use futures_util::{AsyncBufReadExt, AsyncWriteExt};
|
||||||
use niri_ipc::{Request, Response};
|
use niri_ipc::{Request, Response};
|
||||||
|
use smithay::desktop::Window;
|
||||||
use smithay::reexports::calloop::generic::Generic;
|
use smithay::reexports::calloop::generic::Generic;
|
||||||
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
||||||
use smithay::reexports::rustix::fs::unlink;
|
use smithay::reexports::rustix::fs::unlink;
|
||||||
|
use smithay::wayland::compositor::with_states;
|
||||||
|
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
||||||
|
|
||||||
use crate::backend::IpcOutputMap;
|
use crate::backend::IpcOutputMap;
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
@@ -23,6 +26,7 @@ pub struct IpcServer {
|
|||||||
struct ClientCtx {
|
struct ClientCtx {
|
||||||
event_loop: LoopHandle<'static, State>,
|
event_loop: LoopHandle<'static, State>,
|
||||||
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
|
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
|
||||||
|
ipc_focused_window: Arc<Mutex<Option<Window>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IpcServer {
|
impl IpcServer {
|
||||||
@@ -87,6 +91,7 @@ fn on_new_ipc_client(state: &mut State, stream: UnixStream) {
|
|||||||
let ctx = ClientCtx {
|
let ctx = ClientCtx {
|
||||||
event_loop: state.niri.event_loop.clone(),
|
event_loop: state.niri.event_loop.clone(),
|
||||||
ipc_outputs: state.backend.ipc_outputs(),
|
ipc_outputs: state.backend.ipc_outputs(),
|
||||||
|
ipc_focused_window: state.niri.ipc_focused_window.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let future = async move {
|
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();
|
let ipc_outputs = ctx.ipc_outputs.lock().unwrap().clone();
|
||||||
Response::Outputs(ipc_outputs)
|
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) => {
|
Request::Action(action) => {
|
||||||
let action = niri_config::Action::from(action);
|
let action = niri_config::Action::from(action);
|
||||||
ctx.event_loop.insert_idle(move |state| {
|
ctx.event_loop.insert_idle(move |state| {
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ pub struct Niri {
|
|||||||
|
|
||||||
pub ipc_server: Option<IpcServer>,
|
pub ipc_server: Option<IpcServer>,
|
||||||
pub ipc_outputs_changed: bool,
|
pub ipc_outputs_changed: bool,
|
||||||
|
pub ipc_focused_window: Arc<Mutex<Option<Window>>>,
|
||||||
|
|
||||||
// Casts are dropped before PipeWire to prevent a double-free (yay).
|
// Casts are dropped before PipeWire to prevent a double-free (yay).
|
||||||
pub casts: Vec<Cast>,
|
pub casts: Vec<Cast>,
|
||||||
@@ -715,6 +716,8 @@ impl State {
|
|||||||
focus
|
focus
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut newly_focused_window = None;
|
||||||
|
|
||||||
// Tell the windows their new focus state for window rule purposes.
|
// Tell the windows their new focus state for window rule purposes.
|
||||||
if let KeyboardFocus::Layout {
|
if let KeyboardFocus::Layout {
|
||||||
surface: Some(surface),
|
surface: Some(surface),
|
||||||
@@ -730,9 +733,12 @@ impl State {
|
|||||||
{
|
{
|
||||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
||||||
mapped.set_is_focused(true);
|
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 let Some(grab) = self.niri.popup_grab.as_mut() {
|
||||||
if Some(&grab.root) != focus.surface() {
|
if Some(&grab.root) != focus.surface() {
|
||||||
trace!(
|
trace!(
|
||||||
@@ -1390,6 +1396,7 @@ impl Niri {
|
|||||||
|
|
||||||
ipc_server,
|
ipc_server,
|
||||||
ipc_outputs_changed: false,
|
ipc_outputs_changed: false,
|
||||||
|
ipc_focused_window: Arc::new(Mutex::new(None)),
|
||||||
|
|
||||||
pipewire,
|
pipewire,
|
||||||
casts: vec![],
|
casts: vec![],
|
||||||
|
|||||||
Reference in New Issue
Block a user