atagen

monolith

public
Star0
Watch0

Commit 272bdf93aead

tarball

atagen <boss@atagen.co>2026-10-17 03:14 UTC

6 files changed+71-21
monolith: publish client snapshots only when something changed

IPC, ext-workspace and wlr-foreign-toplevel each rebuilt their view after every batch of events, reading every window's title under its lock, even at 165 commits a second from a single game. They now run only when a layout pass, a title or app id change, or a newly bound wl_output marks the snapshot dirty. Titles and app ids are cached per window and refreshed on that window's commits and X11 property changes.
Modified file: crates/monolith/src/handlers.rs+13 additions−5 deletions
@@ -23,6 +23,7 @@ use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, Dr
2323 use smithay::wayland::dmabuf::{
2424 DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,
2525 };
26+use smithay::output::Output;
2627 use smithay::wayland::output::OutputHandler;
2728 use smithay::wayland::seat::WaylandFocus;
2829 use smithay::wayland::selection::SelectionHandler;
@@ -82,10 +83,11 @@ impl CompositorHandler for State {
8283 if let Some(window) = window {
8384 window.on_commit();
8485 }
85- if self.services.captures.wants_damage()
86- && let Some(id) = self.window_id(&root)
87- {
88- self.services.captures.window_damaged(id);
86+ if let Some(id) = self.window_id(&root) {
87+ self.refresh_identity(id);
88+ if self.services.captures.wants_damage() {
89+ self.services.captures.window_damaged(id);
90+ }
8991 }
9092 }
9193 if let Some(i) = self
@@ -254,7 +256,13 @@ impl WaylandDndGrabHandler for State {
254256 }
255257 }
256258
257-impl OutputHandler for State {}
259+impl OutputHandler for State {
260+ /// Workspace and toplevel lists name outputs by the client's wl_output,
261+ /// so a newly bound one needs announcing.
262+ fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {
263+ self.dirty.snapshot = true;
264+ }
265+}
258266
259267 impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State {
260268 fn new_constraint(
Modified file: crates/monolith/src/ipc.rs+5 additions−2 deletions
@@ -2,7 +2,6 @@
22 //! subcommands that talk to it.
33
44 use crate::state::State;
5-use crate::wm::identity;
65 use monolith_ipc::{Answer, Envelope, Query, Reply, Request, VERSION};
76 use smithay::reexports::calloop::generic::Generic;
87 use smithay::reexports::calloop::{EventLoop, Interest, LoopHandle, Mode, PostAction};
@@ -174,7 +173,11 @@ fn snapshot(state: &State) -> monolith_ipc::State {
174173 let mut windows: Vec<monolith_ipc::Window> = l
175174 .windows()
176175 .map(|(id, w)| {
177- let (app_id, title) = state.wm.window(id).map(identity).unwrap_or_default();
176+ let (app_id, title) = state
177+ .wm
178+ .get(id)
179+ .map(|w| (w.app_id.clone(), w.title.clone()))
180+ .unwrap_or_default();
178181 monolith_ipc::Window {
179182 id: slotmap::Key::data(&id).as_ffi(),
180183 app_id,
Modified file: crates/monolith/src/state.rs+24 additions−5 deletions
@@ -63,6 +63,8 @@ pub struct Dirty {
6363 pub redraw: bool,
6464 /// The pointer moved, which on its own only redraws the cursor.
6565 pub cursor: bool,
66+ /// Something clients outside see changed: windows, titles, tags, focus.
67+ pub snapshot: bool,
6668 }
6769
6870 /// The event loop and Wayland display, which everything uses and nothing owns.
@@ -171,6 +173,7 @@ impl State {
171173 dirty: Dirty {
172174 redraw: true,
173175 cursor: false,
176+ snapshot: true,
174177 },
175178 services: Services {
176179 lock: Default::default(),
@@ -196,9 +199,11 @@ impl State {
196199 self.wm.anim.prune(now_ns());
197200 self.confirm_lock();
198201 self.complete_captures();
199- crate::ipc::broadcast(self);
200- self.publish_workspaces();
201- self.publish_toplevels();
202+ if std::mem::take(&mut self.dirty.snapshot) {
203+ crate::ipc::broadcast(self);
204+ self.publish_workspaces();
205+ self.publish_toplevels();
206+ }
202207 let _ = self.core.display.flush_clients();
203208 }
204209
@@ -266,8 +271,8 @@ impl State {
266271 if let Some(name) = &rule.scratchpad {
267272 self.wm.layout.scratchpad_add(id, name);
268273 }
269- self.wm
270- .insert(id, window, rule.tag.is_some() || rule.output.is_some());
274+ let by_rule = rule.tag.is_some() || rule.output.is_some();
275+ self.wm.insert(id, window, by_rule, app_id, title);
271276 self.await_first_buffer(id);
272277 if self.wm.layout.visible(id) {
273278 self.wm.layout.focus(id);
@@ -376,6 +381,19 @@ impl State {
376381 }
377382 }
378383
384+ /// Picks up a changed title or app id, which clients send at any time.
385+ pub fn refresh_identity(&mut self, id: WindowId) {
386+ let Some(w) = self.wm.get_mut(id) else {
387+ return;
388+ };
389+ let (app_id, title) = identity(&w.window);
390+ if w.app_id != app_id || w.title != title {
391+ w.app_id = app_id;
392+ w.title = title;
393+ self.dirty.snapshot = true;
394+ }
395+ }
396+
379397 pub fn window_id(&self, surface: &WlSurface) -> Option<WindowId> {
380398 self.wm.id_of(surface)
381399 }
@@ -500,6 +518,7 @@ impl State {
500518 self.sync_focus();
501519 self.update_inhibit();
502520 self.dirty.redraw = true;
521+ self.dirty.snapshot = true;
503522 }
504523
505524 pub fn sync_focus(&mut self) {
Modified file: crates/monolith/src/toplevels.rs+8 additions−7 deletions
@@ -2,7 +2,6 @@
22 //! and wlr-foreign-toplevel-management for taskbars that also act on them.
33
44 use crate::state::State;
5-use crate::wm::identity;
65 use monolith_layout::WindowId;
76 use monolith_protocols::toplevel::{
87 Action, ForeignToplevelHandler, ForeignToplevelState, Snapshot, Toplevel,
@@ -75,16 +74,18 @@ impl State {
7574 pub fn publish_toplevels(&mut self) {
7675 let list = &mut self.protocols.toplevel_list;
7776 for (_, w) in self.wm.iter_mut() {
78- let (app_id, title) = identity(&w.window);
7977 match &w.list_handle {
8078 Some(h) => {
81- if h.title() != title || h.app_id() != app_id {
82- h.send_title(&title);
83- h.send_app_id(&app_id);
79+ if h.title() != w.title || h.app_id() != w.app_id {
80+ h.send_title(&w.title);
81+ h.send_app_id(&w.app_id);
8482 h.send_done();
8583 }
8684 }
87- None => w.list_handle = Some(list.new_toplevel::<State>(title, app_id)),
85+ None => {
86+ let h = list.new_toplevel::<State>(w.title.clone(), w.app_id.clone());
87+ w.list_handle = Some(h);
88+ }
8889 }
8990 }
9091 list.cleanup_closed_handles();
@@ -97,7 +98,7 @@ impl State {
9798 .wm
9899 .iter()
99100 .map(|(id, w)| {
100- let (app_id, title) = identity(&w.window);
101+ let (app_id, title) = (w.app_id.clone(), w.title.clone());
101102 let w = self.wm.layout.window(id);
102103 Toplevel {
103104 key: key(id),
Modified file: crates/monolith/src/wm.rs+12 additions−1 deletions
@@ -41,6 +41,8 @@ pub struct WindowState {
4141 pub placed_by_rule: bool,
4242 pub list_handle: Option<ForeignToplevelHandle>,
4343 pub borders: Option<[SolidColorBuffer; 4]>,
44+ pub app_id: String,
45+ pub title: String,
4446 }
4547
4648 /// Asks a window to close, through xdg-shell or X11.
@@ -70,7 +72,14 @@ impl Wm {
7072 }
7173 }
7274
73- pub fn insert(&mut self, id: WindowId, window: Window, placed_by_rule: bool) {
75+ pub fn insert(
76+ &mut self,
77+ id: WindowId,
78+ window: Window,
79+ placed_by_rule: bool,
80+ app_id: String,
81+ title: String,
82+ ) {
7483 self.windows.insert(
7584 id,
7685 WindowState {
@@ -80,6 +89,8 @@ impl Wm {
8089 placed_by_rule,
8190 list_handle: None,
8291 borders: None,
92+ app_id,
93+ title,
8394 },
8495 );
8596 self.index(id);
Modified file: crates/monolith/src/xwayland.rs+9 additions−1 deletions
@@ -11,7 +11,7 @@ use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};
1111 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1212 use smithay::utils::{Logical, Rectangle};
1313 use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};
14-use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowType, XwmId};
14+use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};
1515 use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};
1616 use std::os::fd::AsFd;
1717 use std::os::linux::net::SocketAddrExt;
@@ -320,6 +320,14 @@ impl XwmHandler for State {
320320 self.x11_fullscreen(&window, false);
321321 }
322322
323+ fn property_notify(&mut self, _xwm: XwmId, window: X11Surface, property: WmWindowProperty) {
324+ if matches!(property, WmWindowProperty::Title | WmWindowProperty::Class)
325+ && let Some(id) = self.x11_window_id(&window)
326+ {
327+ self.refresh_identity(id);
328+ }
329+ }
330+
323331 fn disconnected(&mut self, _xwm: XwmId) {
324332 tracing::info!("Xwayland exited");
325333 let gone: Vec<_> = self