ipc: Add focus_timestsamp and WindowFocusTimestampChanged

This commit is contained in:
Ivan Molodetskikh
2025-11-16 11:22:55 +03:00
parent 933ffcb229
commit 3769e5da46
4 changed files with 72 additions and 1 deletions
+45
View File
@@ -54,6 +54,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -1298,6 +1299,24 @@ pub struct Window {
pub is_urgent: bool, pub is_urgent: bool,
/// Position- and size-related properties of the window. /// Position- and size-related properties of the window.
pub layout: WindowLayout, pub layout: WindowLayout,
/// Timestamp when the window was most recently focused.
///
/// This timestamp is intended for most-recently-used window switchers, i.e. Alt-Tab. It only
/// updates after some debounce time so that quick window switching doesn't mark intermediate
/// windows as recently focused.
///
/// The timestamp comes from the monotonic clock.
pub focus_timestamp: Option<Timestamp>,
}
/// A moment in time.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Timestamp {
/// Number of whole seconds.
pub secs: u64,
/// Fractional part of the timestamp in nanoseconds (10<sup>-9</sup> seconds).
pub nanos: u32,
} }
/// Position- and size-related properties of a [`Window`]. /// Position- and size-related properties of a [`Window`].
@@ -1513,6 +1532,17 @@ pub enum Event {
/// Id of the newly focused window, or `None` if no window is now focused. /// Id of the newly focused window, or `None` if no window is now focused.
id: Option<u64>, id: Option<u64>,
}, },
/// Window focus timestamp changed.
///
/// This event is separate from [`Event::WindowFocusChanged`] because the focus timestamp only
/// updates after some debounce time so that quick window switching doesn't mark intermediate
/// windows as recently focused.
WindowFocusTimestampChanged {
/// Id of the window.
id: u64,
/// The new focus timestamp.
focus_timestamp: Option<Timestamp>,
},
/// Window urgency changed. /// Window urgency changed.
WindowUrgencyChanged { WindowUrgencyChanged {
/// Id of the window. /// Id of the window.
@@ -1560,6 +1590,21 @@ pub enum Event {
}, },
} }
impl From<Duration> for Timestamp {
fn from(value: Duration) -> Self {
Timestamp {
secs: value.as_secs(),
nanos: value.subsec_nanos(),
}
}
}
impl From<Timestamp> for Duration {
fn from(value: Timestamp) -> Self {
Duration::new(value.secs, value.nanos)
}
}
impl FromStr for WorkspaceReferenceArg { impl FromStr for WorkspaceReferenceArg {
type Err = &'static str; type Err = &'static str;
+11
View File
@@ -193,6 +193,17 @@ impl EventStreamStatePart for WindowsState {
win.is_focused = Some(win.id) == id; win.is_focused = Some(win.id) == id;
} }
} }
Event::WindowFocusTimestampChanged {
id,
focus_timestamp,
} => {
for win in self.windows.values_mut() {
if win.id == id {
win.focus_timestamp = focus_timestamp;
break;
}
}
}
Event::WindowUrgencyChanged { id, urgent } => { Event::WindowUrgencyChanged { id, urgent } => {
for win in self.windows.values_mut() { for win in self.windows.values_mut() {
if win.id == id { if win.id == id {
+6
View File
@@ -458,6 +458,12 @@ pub fn handle_msg(mut msg: Msg, json: bool) -> anyhow::Result<()> {
Event::WindowFocusChanged { id } => { Event::WindowFocusChanged { id } => {
println!("Window focus changed: {id:?}"); println!("Window focus changed: {id:?}");
} }
Event::WindowFocusTimestampChanged {
id,
focus_timestamp,
} => {
println!("Window {id}: focus timestamp changed to {focus_timestamp:?}");
}
Event::WindowUrgencyChanged { id, urgent } => { Event::WindowUrgencyChanged { id, urgent } => {
println!("Window {id}: urgency changed to {urgent}"); println!("Window {id}: urgency changed to {urgent}");
} }
+10 -1
View File
@@ -18,7 +18,7 @@ use niri_config::OutputName;
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _}; use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
use niri_ipc::{ use niri_ipc::{
Action, Event, KeyboardLayouts, OutputConfigChanged, Overview, Reply, Request, Response, Action, Event, KeyboardLayouts, OutputConfigChanged, Overview, Reply, Request, Response,
WindowLayout, Workspace, Timestamp, WindowLayout, Workspace,
}; };
use smithay::desktop::layer_map_for_output; use smithay::desktop::layer_map_for_output;
use smithay::input::pointer::{ use smithay::input::pointer::{
@@ -514,6 +514,7 @@ fn make_ipc_window(
is_floating: mapped.is_floating(), is_floating: mapped.is_floating(),
is_urgent: mapped.is_urgent(), is_urgent: mapped.is_urgent(),
layout, layout,
focus_timestamp: mapped.get_focus_timestamp().map(Timestamp::from),
}) })
} }
@@ -725,6 +726,14 @@ impl State {
events.push(Event::WindowFocusChanged { id: Some(id) }); events.push(Event::WindowFocusChanged { id: Some(id) });
} }
let focus_timestamp = mapped.get_focus_timestamp().map(Timestamp::from);
if focus_timestamp != ipc_win.focus_timestamp {
events.push(Event::WindowFocusTimestampChanged {
id,
focus_timestamp,
});
}
let urgent = mapped.is_urgent(); let urgent = mapped.is_urgent();
if urgent != ipc_win.is_urgent { if urgent != ipc_win.is_urgent {
events.push(Event::WindowUrgencyChanged { id, urgent }) events.push(Event::WindowUrgencyChanged { id, urgent })