use std::{collections::BTreeMap, fmt::Display, ops::Deref};
use niri_ipc::{Event, Window as NiriWindow, WindowLayout, Workspace};
#[derive(Debug)]
pub struct WindowSet(Option<Inner>);
impl WindowSet {
pub fn new() -> Self {
Self(None)
}
#[tracing::instrument(level = "TRACE", skip(self))]
pub fn with_event(&mut self, event: Event) -> Option<Snapshot> {
match event {
Event::WindowsChanged { windows } => match self.0.take() {
Some(Inner::WorkspacesOnly(workspaces)) => {
self.0 = Some(Inner::Ready(Niri::new(windows, workspaces)));
}
Some(Inner::WindowsOnly(_)) | None => {
self.0 = Some(Inner::WindowsOnly(windows));
}
Some(Inner::Ready(mut state)) => {
state.replace_windows(windows);
self.0 = Some(Inner::Ready(state));
}
},
Event::WorkspacesChanged { workspaces } => match self.0.take() {
Some(Inner::WindowsOnly(windows)) => {
self.0 = Some(Inner::Ready(Niri::new(windows, workspaces)));
}
Some(Inner::WorkspacesOnly(_)) | None => {
self.0 = Some(Inner::WorkspacesOnly(workspaces));
}
Some(Inner::Ready(mut state)) => {
state.replace_workspaces(workspaces);
self.0 = Some(Inner::Ready(state));
}
},
Event::WindowClosed { id } => {
if let Some(Inner::Ready(state)) = &mut self.0 {
state.remove_window(id);
} else {
tracing::warn!(%self, "unexpected state for WindowClosed event");
}
}
Event::WindowOpenedOrChanged { window } => {
if let Some(Inner::Ready(state)) = &mut self.0 {
state.upsert_window(window);
} else {
tracing::warn!(%self, "unexpected state for WindowOpenedOrChanged event");
}
}
Event::WindowFocusChanged { id } => {
if let Some(Inner::Ready(state)) = &mut self.0 {
state.set_focus(id);
} else {
tracing::warn!(%self, "unexpected state for WindowFocusChanged event");
}
}
Event::WindowLayoutsChanged { changes } => {
if let Some(Inner::Ready(state)) = &mut self.0 {
for (window_id, layout) in changes.into_iter() {
state.update_window_layout(window_id, layout);
}
}
}
_ => {}
}
if let Some(Inner::Ready(state)) = &self.0 {
Some(state.snapshot())
} else {
None
}
}
}
impl Display for WindowSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match &self.0 {
Some(Inner::Ready(_)) => "ready",
Some(Inner::WindowsOnly(_)) => "windows only",
Some(Inner::WorkspacesOnly(_)) => "workspaces only",
None => "uninitialised",
}
)
}
}
#[derive(Debug)]
enum Inner {
WindowsOnly(Vec<NiriWindow>),
WorkspacesOnly(Vec<Workspace>),
Ready(Niri),
}
#[derive(Debug)]
struct Niri {
windows: BTreeMap<u64, NiriWindow>,
workspaces: BTreeMap<u64, Workspace>,
}
impl Niri {
fn new(windows: Vec<NiriWindow>, workspaces: Vec<Workspace>) -> Self {
let mut niri = Niri {
windows: Default::default(),
workspaces: Default::default(),
};
niri.replace_workspaces(workspaces);
niri.replace_windows(windows);
niri
}
fn remove_window(&mut self, id: u64) {
self.windows.remove(&id);
}
fn replace_windows(&mut self, windows: Vec<NiriWindow>) {
self.windows = windows
.into_iter()
.map(|window| (window.id, window))
.collect();
}
fn replace_workspaces(&mut self, workspaces: Vec<Workspace>) {
self.workspaces = workspaces.into_iter().map(|ws| (ws.id, ws)).collect();
}
fn set_focus(&mut self, id: Option<u64>) {
for window in self.windows.values_mut() {
window.is_focused = Some(window.id) == id;
}
}
fn update_window_layout(&mut self, window_id: u64, layout: WindowLayout) {
if let Some(window) = self.windows.get_mut(&window_id) {
window.layout = layout;
} else {
tracing::warn!(window_id, ?layout, "got window layout for unknown window");
}
}
fn upsert_window(&mut self, window: NiriWindow) {
if window.is_focused {
self.windows.values_mut().for_each(|window| {
window.is_focused = false;
})
}
self.windows.insert(window.id, window);
}
fn snapshot(&self) -> Snapshot {
struct WindowWorkspace<'a> {
window: &'a NiriWindow,
workspace: &'a Workspace,
}
let mut wws: Vec<_> = self
.windows
.values()
.filter_map(|window| {
if let Some(ws_id) = window.workspace_id {
if let Some(workspace) = self.workspaces.get(&ws_id) {
return Some(WindowWorkspace { window, workspace });
}
}
None
})
.collect();
wws.sort_by(|a, b| {
a.workspace
.idx
.cmp(&b.workspace.idx)
.then_with(|| {
let a_pos = a.window.layout.pos_in_scrolling_layout.unwrap_or_default();
let b_pos = b.window.layout.pos_in_scrolling_layout.unwrap_or_default();
a_pos.0.cmp(&b_pos.0).then_with(|| a_pos.1.cmp(&b_pos.1))
})
.then_with(|| a.window.id.cmp(&b.window.id))
});
wws.into_iter()
.map(|ww| Window {
window: ww.window.clone(),
output: ww.workspace.output.clone(),
})
.collect()
}
}
pub type Snapshot = Vec<Window>;
#[derive(Debug, Clone)]
pub struct Window {
window: NiriWindow,
output: Option<String>,
}
impl Window {
pub fn output(&self) -> Option<&str> {
self.output.as_deref()
}
}
impl Deref for Window {
type Target = NiriWindow;
fn deref(&self) -> &Self::Target {
&self.window
}
}