atagen

monolith

public
Star0
Watch0

Commit a772647a949f

tarball

atagen <boss@atagen.co>2026-10-17 02:45 UTC

17 files changed+233-183
monolith: group protocol globals, core plumbing and settings out of State

State had grown to 70 flat fields, most of them protocol globals that only their own handler touches. They now live in Protocols, the event loop and display in Core, and the config with its path and binds in Settings, which reload can swap as one. First step of the split sketched in design/state-split.md.
Modified file: crates/monolith/src/activation.rs+2 additions−2 deletions
@@ -23,7 +23,7 @@ struct Context {
2323
2424 impl XdgActivationHandler for State {
2525 fn activation_state(&mut self) -> &mut XdgActivationState {
26- &mut self.activation_state
26+ &mut self.protocols.activation_state
2727 }
2828
2929 fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool {
@@ -86,7 +86,7 @@ impl State {
8686 tag,
8787 may_focus: true,
8888 });
89- let (token, _) = self.activation_state.create_external_token(data);
89+ let (token, _) = self.protocols.activation_state.create_external_token(data);
9090 token.to_string()
9191 }
9292 }
Modified file: crates/monolith/src/capture.rs+4 additions−4 deletions
@@ -69,7 +69,7 @@ impl ImageCaptureSourceHandler for State {
6969
7070 impl OutputCaptureSourceHandler for State {
7171 fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState {
72- &mut self.output_capture_source_state
72+ &mut self.protocols.output_capture_source_state
7373 }
7474
7575 fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) {
@@ -79,7 +79,7 @@ impl OutputCaptureSourceHandler for State {
7979
8080 impl ToplevelCaptureSourceHandler for State {
8181 fn toplevel_capture_source_state(&mut self) -> &mut ToplevelCaptureSourceState {
82- &mut self.toplevel_capture_source_state
82+ &mut self.protocols.toplevel_capture_source_state
8383 }
8484
8585 fn toplevel_source_created(
@@ -95,7 +95,7 @@ impl ToplevelCaptureSourceHandler for State {
9595
9696 impl ImageCopyCaptureHandler for State {
9797 fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
98- &mut self.image_copy_capture_state
98+ &mut self.protocols.image_copy_capture_state
9999 }
100100
101101 fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {
@@ -284,7 +284,7 @@ impl State {
284284 &self.anim,
285285 &mut self.renderer,
286286 now,
287- &self.config.decoration,
287+ &self.settings.config.decoration,
288288 &mut self.borders,
289289 Some(output),
290290 self.xwayland.as_ref().map_or(&[], |x| &x.unmanaged),
Modified file: crates/monolith/src/commands.rs+7 additions−7 deletions
@@ -53,7 +53,7 @@ impl State {
5353 let output = self.layout.active_output();
5454 let focused = self.layout.focused();
5555 let inverted = std::mem::take(&mut self.move_inverted);
56- let merge = (self.config.general.move_mode == MoveMode::Merge) != inverted;
56+ let merge = (self.settings.config.general.move_mode == MoveMode::Merge) != inverted;
5757 let on_focused = |state: &mut Self, f: fn(&mut monolith_layout::Layout, WindowId)| {
5858 if let Some(w) = focused {
5959 f(&mut state.layout, w);
@@ -72,7 +72,7 @@ impl State {
7272 }
7373 }
7474 }
75- Command::Quit => self.signal.stop(),
75+ Command::Quit => self.core.signal.stop(),
7676 Command::Reload => self.reload()?,
7777 Command::Focus { dir: d } => {
7878 self.layout.focus_dir(dir(d));
@@ -171,7 +171,7 @@ impl State {
171171 let (program, args) = argv.split_first().ok_or("nothing to spawn")?;
172172 let mut cmd = std::process::Command::new(program);
173173 cmd.args(args)
174- .env("WAYLAND_DISPLAY", &self.socket_name)
174+ .env("WAYLAND_DISPLAY", &self.core.socket_name)
175175 .stdin(Stdio::null());
176176 if let Some(sock) = &self.ipc.path {
177177 cmd.env("MONOLITH_SOCKET", sock);
@@ -197,12 +197,12 @@ impl State {
197197 /// Swaps in a freshly parsed config. A broken file leaves the running
198198 /// config untouched.
199199 pub fn reload(&mut self) -> Result<(), String> {
200- let config = Config::load(self.config_path.as_deref()).map_err(|e| e.to_string())?;
201- let keyboard_changed = config.keyboard != self.config.keyboard;
202- self.binds = Binds::new(&config);
200+ let config = Config::load(self.settings.path.as_deref()).map_err(|e| e.to_string())?;
201+ let keyboard_changed = config.keyboard != self.settings.config.keyboard;
202+ self.settings.binds = Binds::new(&config);
203203 self.layout.gaps = crate::state::gaps(&config);
204204 self.anim.settings = config.animation.clone();
205- self.config = config;
205+ self.settings.config = config;
206206 if keyboard_changed {
207207 self.reconfigure_keyboard();
208208 }
Modified file: crates/monolith/src/drm.rs+11 additions−8 deletions
@@ -193,7 +193,10 @@ pub fn init(
193193 .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))
194194 .unwrap_or(false);
195195 if supports_syncobj_eventfd(&fd) {
196- state.syncobj = Some(DrmSyncobjState::new::<State>(&state.display, fd.clone()));
196+ state.syncobj = Some(DrmSyncobjState::new::<State>(
197+ &state.core.display,
198+ fd.clone(),
199+ ));
197200 } else {
198201 tracing::info!("no syncobj eventfd support, clients stay on implicit sync");
199202 }
@@ -335,7 +338,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
335338 renderer,
336339 layout,
337340 space,
338- display,
341+ core,
339342 ..
340343 } = state
341344 else {
@@ -365,7 +368,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
365368 Some(position),
366369 );
367370 output.set_preferred(wl_mode);
368- output.create_global::<State>(display);
371+ output.create_global::<State>(&core.display);
369372 let mut planes = drm.manager.device().planes(&crtc).ok();
370373 if drm.nvidia
371374 && let Some(p) = planes.as_mut()
@@ -590,9 +593,9 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
590593 windows,
591594 anim,
592595 borders,
593- config,
596+ settings,
594597 space,
595- start,
598+ core,
596599 cursor,
597600 cursor_status,
598601 pointer_pos,
@@ -615,7 +618,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
615618 }
616619 surface.cursor_redraw = false;
617620 let want_vrr = surface.vrr_capable
618- && match config.output.vrr {
621+ && match settings.config.output.vrr {
619622 Vrr::Off => false,
620623 Vrr::On => true,
621624 Vrr::Fullscreen => layout.arrange(surface.id).fullscreen.is_some(),
@@ -656,7 +659,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
656659 anim,
657660 renderer,
658661 now,
659- &config.decoration,
662+ &settings.config.decoration,
660663 borders,
661664 Some(&surface.output),
662665 xwayland.as_ref().map_or(&[], |x| &x.unmanaged),
@@ -706,7 +709,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
706709 surface.target_ns = None;
707710 }
708711 surface.redraw = animating;
709- let elapsed = start.elapsed();
712+ let elapsed = core.start.elapsed();
710713 let output = surface.output.clone();
711714 if lock.active() {
712715 crate::lock::send_frames(lock, &output, elapsed);
Added file: crates/monolith/src/globals.rs+107 additions
@@ -0,0 +1,107 @@
1+//! The Wayland globals monolith advertises. Most are only touched by their
2+//! own handler impl, the rest publish compositor state to clients.
3+
4+use crate::state::State;
5+use monolith_protocols::toplevel::ForeignToplevelState;
6+use monolith_protocols::workspace::WorkspaceState;
7+use monolith_render_vk::VulkanRenderer;
8+use smithay::backend::renderer::ImportDma;
9+use smithay::reexports::calloop::LoopHandle;
10+use smithay::reexports::wayland_server::DisplayHandle;
11+use smithay::wayland::compositor::CompositorState;
12+use smithay::wayland::cursor_shape::CursorShapeManagerState;
13+use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};
14+use smithay::wayland::foreign_toplevel_list::ForeignToplevelListState;
15+use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
16+use smithay::wayland::idle_notify::IdleNotifierState;
17+use smithay::wayland::image_capture_source::{
18+ ImageCaptureSourceState, OutputCaptureSourceState, ToplevelCaptureSourceState,
19+};
20+use smithay::wayland::image_copy_capture::ImageCopyCaptureState;
21+use smithay::wayland::output::OutputManagerState;
22+use smithay::wayland::selection::data_device::DataDeviceState;
23+use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;
24+use smithay::wayland::selection::primary_selection::PrimarySelectionState;
25+use smithay::wayland::selection::wlr_data_control::DataControlState as WlrDataControlState;
26+use smithay::wayland::session_lock::SessionLockManagerState;
27+use smithay::wayland::shell::wlr_layer::WlrLayerShellState;
28+use smithay::wayland::shell::xdg::XdgShellState;
29+use smithay::wayland::shell::xdg::decoration::XdgDecorationState;
30+use smithay::wayland::shm::ShmState;
31+use smithay::wayland::xdg_activation::XdgActivationState;
32+use smithay::wayland::xwayland_shell::XWaylandShellState;
33+
34+pub struct Protocols {
35+ pub compositor_state: CompositorState,
36+ pub xdg_shell_state: XdgShellState,
37+ pub layer_shell_state: WlrLayerShellState,
38+ pub output_capture_source_state: OutputCaptureSourceState,
39+ pub toplevel_capture_source_state: ToplevelCaptureSourceState,
40+ pub image_copy_capture_state: ImageCopyCaptureState,
41+ pub activation_state: XdgActivationState,
42+ pub primary_selection_state: PrimarySelectionState,
43+ pub wlr_data_control_state: WlrDataControlState,
44+ pub ext_data_control_state: ExtDataControlState,
45+ pub session_lock_state: SessionLockManagerState,
46+ pub idle_notifier: IdleNotifierState<State>,
47+ pub shm_state: ShmState,
48+ pub dmabuf_state: DmabufState,
49+ pub data_device_state: DataDeviceState,
50+ pub xwayland_shell_state: XWaylandShellState,
51+ pub workspaces: WorkspaceState,
52+ pub foreign_toplevels: ForeignToplevelState,
53+ pub toplevel_list: ForeignToplevelListState,
54+ _image_capture_source: ImageCaptureSourceState,
55+ _idle_inhibit: IdleInhibitManagerState,
56+ _xdg_decoration: XdgDecorationState,
57+ _dmabuf_global: DmabufGlobal,
58+ _output_manager: OutputManagerState,
59+ _cursor_shape: CursorShapeManagerState,
60+}
61+
62+impl Protocols {
63+ pub fn new(
64+ dh: &DisplayHandle,
65+ handle: LoopHandle<'static, State>,
66+ renderer: &VulkanRenderer,
67+ ) -> Self {
68+ let mut dmabuf_state = DmabufState::new();
69+ let dmabuf_global = dmabuf_state.create_global::<State>(dh, renderer.dmabuf_formats());
70+ let primary_selection_state = PrimarySelectionState::new::<State>(dh);
71+ Self {
72+ compositor_state: CompositorState::new::<State>(dh),
73+ xdg_shell_state: XdgShellState::new::<State>(dh),
74+ layer_shell_state: WlrLayerShellState::new::<State>(dh),
75+ output_capture_source_state: OutputCaptureSourceState::new::<State>(dh),
76+ toplevel_capture_source_state: ToplevelCaptureSourceState::new::<State>(dh),
77+ image_copy_capture_state: ImageCopyCaptureState::new::<State>(dh),
78+ activation_state: XdgActivationState::new::<State>(dh),
79+ wlr_data_control_state: WlrDataControlState::new::<State, _>(
80+ dh,
81+ Some(&primary_selection_state),
82+ |_| true,
83+ ),
84+ ext_data_control_state: ExtDataControlState::new::<State, _>(
85+ dh,
86+ Some(&primary_selection_state),
87+ |_| true,
88+ ),
89+ primary_selection_state,
90+ session_lock_state: SessionLockManagerState::new::<State, _>(dh, |_| true),
91+ idle_notifier: IdleNotifierState::new(dh, handle),
92+ shm_state: ShmState::new::<State>(dh, vec![]),
93+ dmabuf_state,
94+ data_device_state: DataDeviceState::new::<State>(dh),
95+ xwayland_shell_state: XWaylandShellState::new::<State>(dh),
96+ workspaces: WorkspaceState::new::<State>(dh),
97+ foreign_toplevels: ForeignToplevelState::new::<State>(dh),
98+ toplevel_list: ForeignToplevelListState::new::<State>(dh),
99+ _image_capture_source: ImageCaptureSourceState::new(),
100+ _idle_inhibit: IdleInhibitManagerState::new::<State>(dh),
101+ _xdg_decoration: XdgDecorationState::new::<State>(dh),
102+ _dmabuf_global: dmabuf_global,
103+ _output_manager: OutputManagerState::new_with_xdg_output::<State>(dh),
104+ _cursor_shape: CursorShapeManagerState::new::<State>(dh),
105+ }
106+ }
107+}
Modified file: crates/monolith/src/handlers.rs+16 additions−16 deletions
@@ -41,7 +41,7 @@ use smithay::wayland::shm::{ShmHandler, ShmState};
4141
4242 impl CompositorHandler for State {
4343 fn compositor_state(&mut self) -> &mut CompositorState {
44- &mut self.compositor_state
44+ &mut self.protocols.compositor_state
4545 }
4646
4747 fn new_surface(&mut self, surface: &WlSurface) {
@@ -116,13 +116,13 @@ impl BufferHandler for State {
116116
117117 impl ShmHandler for State {
118118 fn shm_state(&self) -> &ShmState {
119- &self.shm_state
119+ &self.protocols.shm_state
120120 }
121121 }
122122
123123 impl DmabufHandler for State {
124124 fn dmabuf_state(&mut self) -> &mut DmabufState {
125- &mut self.dmabuf_state
125+ &mut self.protocols.dmabuf_state
126126 }
127127
128128 fn dmabuf_imported(
@@ -137,7 +137,7 @@ impl DmabufHandler for State {
137137
138138 impl XdgShellHandler for State {
139139 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
140- &mut self.xdg_shell_state
140+ &mut self.protocols.xdg_shell_state
141141 }
142142
143143 fn new_toplevel(&mut self, surface: ToplevelSurface) {
@@ -194,9 +194,9 @@ impl SeatHandler for State {
194194 fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&crate::focus::Focus>) {
195195 let client = focused
196196 .and_then(|f| f.wl_surface())
197- .and_then(|s| self.display.get_client(s.id()).ok());
198- set_data_device_focus(&self.display, seat, client.clone());
199- set_primary_focus(&self.display, seat, client);
197+ .and_then(|s| self.core.display.get_client(s.id()).ok());
198+ set_data_device_focus(&self.core.display, seat, client.clone());
199+ set_primary_focus(&self.core.display, seat, client);
200200 }
201201 }
202202
@@ -206,13 +206,13 @@ impl SelectionHandler for State {
206206
207207 impl DataDeviceHandler for State {
208208 fn data_device_state(&mut self) -> &mut DataDeviceState {
209- &mut self.data_device_state
209+ &mut self.protocols.data_device_state
210210 }
211211 }
212212
213213 impl PrimarySelectionHandler for State {
214214 fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
215- &mut self.primary_selection_state
215+ &mut self.protocols.primary_selection_state
216216 }
217217 }
218218
@@ -220,7 +220,7 @@ impl smithay::wayland::selection::wlr_data_control::DataControlHandler for State
220220 fn data_control_state(
221221 &mut self,
222222 ) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {
223- &mut self.wlr_data_control_state
223+ &mut self.protocols.wlr_data_control_state
224224 }
225225 }
226226
@@ -228,7 +228,7 @@ impl smithay::wayland::selection::ext_data_control::DataControlHandler for State
228228 fn data_control_state(
229229 &mut self,
230230 ) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {
231- &mut self.ext_data_control_state
231+ &mut self.protocols.ext_data_control_state
232232 }
233233 }
234234
@@ -310,8 +310,8 @@ fn hold_until_readable(state: &mut State, surface: &WlSurface) {
310310 if let Some(point) = acquire
311311 && let Ok((blocker, source)) = point.generate_blocker()
312312 {
313- let inserted = state.handle.insert_source(source, move |_, _, state| {
314- let dh = state.display.clone();
313+ let inserted = state.core.handle.insert_source(source, move |_, _, state| {
314+ let dh = state.core.display.clone();
315315 state
316316 .client_compositor_state(&client)
317317 .blocker_cleared(state, &dh);
@@ -323,8 +323,8 @@ fn hold_until_readable(state: &mut State, surface: &WlSurface) {
323323 return;
324324 }
325325 if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
326- let inserted = state.handle.insert_source(source, move |_, _, state| {
327- let dh = state.display.clone();
326+ let inserted = state.core.handle.insert_source(source, move |_, _, state| {
327+ let dh = state.core.display.clone();
328328 state
329329 .client_compositor_state(&client)
330330 .blocker_cleared(state, &dh);
@@ -371,7 +371,7 @@ impl State {
371371
372372 impl WorkspaceHandler for State {
373373 fn workspace_state(&mut self) -> &mut WorkspaceState {
374- &mut self.workspaces
374+ &mut self.protocols.workspaces
375375 }
376376
377377 fn workspace_actions(&mut self, actions: Vec<Action>) {
Modified file: crates/monolith/src/idle.rs+3 additions−3 deletions
@@ -10,7 +10,7 @@ use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
1010
1111 impl IdleNotifierHandler for State {
1212 fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self> {
13- &mut self.idle_notifier
13+ &mut self.protocols.idle_notifier
1414 }
1515 }
1616
@@ -29,7 +29,7 @@ impl IdleInhibitHandler for State {
2929 impl State {
3030 pub fn activity(&mut self) {
3131 let seat = self.seat.clone();
32- self.idle_notifier.notify_activity(&seat);
32+ self.protocols.idle_notifier.notify_activity(&seat);
3333 }
3434
3535 /// An inhibitor only counts while its surface is on screen, as the
@@ -48,7 +48,7 @@ impl State {
4848 });
4949 if shown != self.inhibited {
5050 self.inhibited = shown;
51- self.idle_notifier.set_is_inhibited(shown);
51+ self.protocols.idle_notifier.set_is_inhibited(shown);
5252 }
5353 }
5454 }
Modified file: crates/monolith/src/import.rs+1 additions−1 deletions
@@ -124,7 +124,7 @@ impl State {
124124 }
125125 }
126126 waiters.done.store(true, Ordering::Release);
127- let dh = self.display.clone();
127+ let dh = self.core.display.clone();
128128 for client in waiters.clients {
129129 self.client_compositor_state(&client)
130130 .blocker_cleared(self, &dh);
Modified file: crates/monolith/src/input.rs+2 additions−2 deletions
@@ -103,7 +103,7 @@ impl State {
103103 if state.lock.active() {
104104 return FilterResult::Forward;
105105 }
106- match state.binds.find(mods, &handle.raw_syms()) {
106+ match state.settings.binds.find(mods, &handle.raw_syms()) {
107107 Some(cmd) => {
108108 tracing::debug!(?cmd, "bind");
109109 state.suppressed.push(code);
@@ -182,7 +182,7 @@ fn start_drag(state: &mut State, button: u32) -> bool {
182182 .seat
183183 .get_keyboard()
184184 .map(|k| k.modifier_state())
185- .is_some_and(|m| match state.config.general.mod_key {
185+ .is_some_and(|m| match state.settings.config.general.mod_key {
186186 Modifier::Super => m.logo,
187187 Modifier::Alt => m.alt,
188188 Modifier::Ctrl => m.ctrl,
Modified file: crates/monolith/src/ipc.rs+1 additions
@@ -28,6 +28,7 @@ impl Drop for Server {
2828
2929 pub fn init(event_loop: &EventLoop<'static, State>, state: &mut State) -> std::io::Result<()> {
3030 let Some(path) = state
31+ .core
3132 .socket_name
3233 .to_str()
3334 .and_then(monolith_ipc::socket_path_for)
Modified file: crates/monolith/src/layers.rs+1 additions−1 deletions
@@ -22,7 +22,7 @@ use std::time::Duration;
2222
2323 impl WlrLayerShellHandler for State {
2424 fn shell_state(&mut self) -> &mut WlrLayerShellState {
25- &mut self.layer_shell_state
25+ &mut self.protocols.layer_shell_state
2626 }
2727
2828 fn new_layer_surface(
Modified file: crates/monolith/src/lock.rs+1 additions−1 deletions
@@ -50,7 +50,7 @@ impl Lock {
5050
5151 impl SessionLockHandler for State {
5252 fn lock_state(&mut self) -> &mut SessionLockManagerState {
53- &mut self.session_lock_state
53+ &mut self.protocols.session_lock_state
5454 }
5555
5656 fn lock(&mut self, confirmation: SessionLocker) {
Modified file: crates/monolith/src/main.rs+4 additions−3 deletions
@@ -5,6 +5,7 @@ mod commands;
55 mod cursor;
66 mod drm;
77 mod focus;
8+mod globals;
89 mod handlers;
910 mod idle;
1011 mod import;
@@ -157,7 +158,7 @@ fn export_session(state: &State) {
157158 .arg("--systemd")
158159 .arg(format!(
159160 "WAYLAND_DISPLAY={}",
160- state.socket_name.to_string_lossy()
161+ state.core.socket_name.to_string_lossy()
161162 ))
162163 .arg("XDG_CURRENT_DESKTOP=monolith")
163164 .arg("XDG_SESSION_TYPE=wayland")
@@ -203,12 +204,12 @@ fn run(
203204 }
204205 ipc::init(&event_loop, &mut state)?;
205206 xwayland::init(&mut state);
206- tracing::info!(socket = ?state.socket_name, device = state.renderer.device_name(), "monolith running");
207+ tracing::info!(socket = ?state.core.socket_name, device = state.renderer.device_name(), "monolith running");
207208
208209 if gpu.is_some() {
209210 export_session(&state);
210211 }
211- let mut startup = state.config.startup.clone();
212+ let mut startup = state.settings.config.startup.clone();
212213 if !program.is_empty() {
213214 startup.push(program.to_vec());
214215 }
Modified file: crates/monolith/src/nested/mod.rs+6 additions−6 deletions
@@ -117,7 +117,7 @@ pub fn init(
117117 serial_number: "0".into(),
118118 },
119119 );
120- let _global = output.create_global::<State>(&state.display);
120+ let _global = output.create_global::<State>(&state.core.display);
121121 let mode = Mode {
122122 size: DEFAULT_SIZE.into(),
123123 refresh: 60_000,
@@ -172,12 +172,12 @@ pub fn render(state: &mut State) {
172172 renderer,
173173 space,
174174 needs_redraw,
175- start,
175+ core,
176176 layout,
177177 windows,
178178 anim,
179179 borders,
180- config,
180+ settings,
181181 output_id,
182182 lock,
183183 captures,
@@ -234,7 +234,7 @@ pub fn render(state: &mut State) {
234234 anim,
235235 renderer,
236236 now,
237- &config.decoration,
237+ &settings.config.decoration,
238238 borders,
239239 Some(&host.output),
240240 xwayland.as_ref().map_or(&[], |x| &x.unmanaged),
@@ -259,7 +259,7 @@ pub fn render(state: &mut State) {
259259 *needs_redraw = anim.active(now);
260260 anim.prune(now);
261261 lock.shown(&host.output);
262- let elapsed = start.elapsed();
262+ let elapsed = core.start.elapsed();
263263 crate::lock::send_frames(lock, &host.output, elapsed);
264264 for window in space.elements().filter(|_| !lock.active()) {
265265 window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {
@@ -430,7 +430,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
430430 state.resize_output(size);
431431 }
432432 }
433- xdg_toplevel::Event::Close => state.signal.stop(),
433+ xdg_toplevel::Event::Close => state.core.signal.stop(),
434434 _ => {}
435435 }
436436 }
Modified file: crates/monolith/src/state.rs+48 additions−115 deletions
@@ -1,13 +1,13 @@
11 use crate::anim::{Animator, Ghost};
22 use crate::commands::Binds;
33 use crate::focus::Focus;
4+use crate::globals::Protocols;
45 use crate::nested::Host;
56 use monolith_config::Config;
67 use monolith_layout::{Layout, OutputId, Rect, WindowId};
7-use monolith_protocols::workspace::{Group, Snapshot, Workspace, WorkspaceState};
8+use monolith_protocols::workspace::{Group, Snapshot, Workspace};
89 use monolith_render_vk::VulkanRenderer;
910 use monolith_render_vk::VulkanTexture;
10-use smithay::backend::renderer::ImportDma;
1111 use smithay::backend::renderer::Renderer;
1212 use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};
1313 use smithay::desktop::utils::under_from_surface_tree;
@@ -24,28 +24,11 @@ use smithay::reexports::wayland_server::backend::{ClientData, ClientId, Disconne
2424 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
2525 use smithay::reexports::wayland_server::{Display, DisplayHandle, Resource};
2626 use smithay::utils::{Clock, Logical, Monotonic, Point, Rectangle, SERIAL_COUNTER, Size};
27-use smithay::wayland::compositor::{CompositorClientState, CompositorState, with_states};
28-use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};
29-use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
30-use smithay::wayland::idle_notify::IdleNotifierState;
31-use smithay::wayland::image_capture_source::{
32- ImageCaptureSourceState, OutputCaptureSourceState, ToplevelCaptureSourceState,
33-};
34-use smithay::wayland::image_copy_capture::ImageCopyCaptureState;
35-use smithay::wayland::output::OutputManagerState;
27+use smithay::wayland::compositor::{CompositorClientState, with_states};
3628 use smithay::wayland::seat::WaylandFocus;
37-use smithay::wayland::selection::data_device::DataDeviceState;
38-use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;
39-use smithay::wayland::selection::primary_selection::PrimarySelectionState;
40-use smithay::wayland::selection::wlr_data_control::DataControlState as WlrDataControlState;
41-use smithay::wayland::session_lock::SessionLockManagerState;
42-use smithay::wayland::shell::wlr_layer::{Layer, WlrLayerShellState};
43-use smithay::wayland::shell::xdg::decoration::XdgDecorationState;
44-use smithay::wayland::shell::xdg::{XdgShellState, XdgToplevelSurfaceData};
45-use smithay::wayland::shm::ShmState;
29+use smithay::wayland::shell::wlr_layer::Layer;
30+use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
4631 use smithay::wayland::socket::ListeningSocketSource;
47-use smithay::wayland::xdg_activation::XdgActivationState;
48-use smithay::wayland::xwayland_shell::XWaylandShellState;
4932 use std::collections::{HashMap, HashSet};
5033 use std::ffi::OsString;
5134 use std::path::PathBuf;
@@ -53,10 +36,9 @@ use std::sync::Arc;
5336 use std::time::{Duration, Instant};
5437
5538 pub struct State {
56- pub start: Instant,
57- pub socket_name: OsString,
58- pub display: DisplayHandle,
59- pub signal: LoopSignal,
39+ pub core: Core,
40+ pub settings: Settings,
41+ pub protocols: Protocols,
6042 pub renderer: VulkanRenderer,
6143 pub space: Space<Window>,
6244 pub popups: PopupManager,
@@ -75,9 +57,6 @@ pub struct State {
7557 /// title, which clients set after creating the toplevel.
7658 pub unmapped: Vec<Window>,
7759 pub output_id: Option<OutputId>,
78- pub config: Config,
79- pub config_path: Option<PathBuf>,
80- pub binds: Binds,
8160 pub suppressed: Vec<Keycode>,
8261 pub move_inverted: bool,
8362 pub drag: Option<crate::input::Drag>,
@@ -85,9 +64,6 @@ pub struct State {
8564 pub pointer_pos: Point<f64, Logical>,
8665 pub ipc: crate::ipc::Server,
8766 pub launches: crate::launch::Launches,
88- pub workspaces: WorkspaceState,
89- pub foreign_toplevels: monolith_protocols::toplevel::ForeignToplevelState,
90- pub toplevel_list: smithay::wayland::foreign_toplevel_list::ForeignToplevelListState,
9167 pub list_handles: crate::toplevels::ListHandles,
9268 pub anim: Animator,
9369 pub borders: crate::scene::Borders,
@@ -100,7 +76,6 @@ pub struct State {
10076 /// A layer surface given the keyboard by a click, like a launcher.
10177 pub layer_focus: Option<WlSurface>,
10278 pub xwayland: Option<crate::xwayland::Xwayland>,
103- pub xwayland_shell_state: XWaylandShellState,
10479 pub captures: crate::capture::Captures,
10580 pub imports: crate::import::Imports,
10681 /// The render node clients get dmabufs for, known on bare metal.
@@ -108,33 +83,26 @@ pub struct State {
10883 pub lock: crate::lock::Lock,
10984 pub inhibitors: HashSet<WlSurface>,
11085 pub inhibited: bool,
111- pub handle: LoopHandle<'static, State>,
112-
113- pub compositor_state: CompositorState,
114- pub xdg_shell_state: XdgShellState,
115- pub layer_shell_state: WlrLayerShellState,
116- pub _image_capture_source_state: ImageCaptureSourceState,
117- pub output_capture_source_state: OutputCaptureSourceState,
118- pub toplevel_capture_source_state: ToplevelCaptureSourceState,
119- pub image_copy_capture_state: ImageCopyCaptureState,
120- pub activation_state: XdgActivationState,
121- pub primary_selection_state: PrimarySelectionState,
122- pub wlr_data_control_state: WlrDataControlState,
123- pub ext_data_control_state: ExtDataControlState,
124- pub session_lock_state: SessionLockManagerState,
125- pub idle_notifier: IdleNotifierState<State>,
126- pub _idle_inhibit: IdleInhibitManagerState,
127- pub _xdg_decoration_state: XdgDecorationState,
128- pub shm_state: ShmState,
129- pub dmabuf_state: DmabufState,
130- pub _dmabuf_global: DmabufGlobal,
131- pub _output_manager_state: OutputManagerState,
13286 pub seat_state: SeatState<State>,
133- pub data_device_state: DataDeviceState,
134- pub _cursor_shape_state: smithay::wayland::cursor_shape::CursorShapeManagerState,
13587 pub seat: Seat<State>,
13688 }
13789
90+/// The event loop and Wayland display, which everything uses and nothing owns.
91+pub struct Core {
92+ pub display: DisplayHandle,
93+ pub handle: LoopHandle<'static, State>,
94+ pub signal: LoopSignal,
95+ pub socket_name: OsString,
96+ pub start: Instant,
97+}
98+
99+/// The config and what is derived from it, swapped whole on reload.
100+pub struct Settings {
101+ pub config: Config,
102+ pub path: Option<PathBuf>,
103+ pub binds: Binds,
104+}
105+
138106 impl State {
139107 pub fn new(
140108 event_loop: &mut EventLoop<'static, State>,
@@ -152,8 +120,6 @@ impl State {
152120 Some(node) => VulkanRenderer::for_node(node)?,
153121 None => VulkanRenderer::new()?,
154122 };
155- let mut dmabuf_state = DmabufState::new();
156- let dmabuf_global = dmabuf_state.create_global::<State>(&dh, renderer.dmabuf_formats());
157123 let mut seat_state = SeatState::new();
158124 let mut seat = seat_state.new_wl_seat(&dh, "seat0");
159125 let kb = &config.keyboard;
@@ -165,6 +131,7 @@ impl State {
165131 let handle = event_loop.handle();
166132 handle.insert_source(listener, |stream, _, state| {
167133 if let Err(e) = state
134+ .core
168135 .display
169136 .insert_client(stream, Arc::new(ClientState::default()))
170137 {
@@ -182,14 +149,22 @@ impl State {
182149
183150 let mut layout = Layout::default();
184151 layout.gaps = gaps(&config);
185- let binds = Binds::new(&config);
186152 let animation = config.animation.clone();
187- let primary_selection_state = PrimarySelectionState::new::<State>(&dh);
188153 let imports = crate::import::Imports::new(&handle, renderer.dmabuf_importer())?;
189154 Ok(Self {
190- start: Instant::now(),
191- socket_name,
192- signal: event_loop.get_signal(),
155+ protocols: Protocols::new(&dh, handle.clone(), &renderer),
156+ core: Core {
157+ display: dh,
158+ handle,
159+ signal: event_loop.get_signal(),
160+ socket_name,
161+ start: Instant::now(),
162+ },
163+ settings: Settings {
164+ binds: Binds::new(&config),
165+ config,
166+ path: config_path,
167+ },
193168 renderer,
194169 space: Space::default(),
195170 popups: PopupManager::default(),
@@ -204,22 +179,12 @@ impl State {
204179 windows: HashMap::new(),
205180 unmapped: Vec::new(),
206181 output_id: None,
207- config,
208- config_path,
209- binds,
210182 suppressed: Vec::new(),
211183 move_inverted: false,
212184 drag: None,
213185 pointer_pos: Point::default(),
214186 ipc: crate::ipc::Server::default(),
215187 launches: Default::default(),
216- workspaces: WorkspaceState::new::<State>(&dh),
217- foreign_toplevels: monolith_protocols::toplevel::ForeignToplevelState::new::<State>(
218- &dh,
219- ),
220- toplevel_list: smithay::wayland::foreign_toplevel_list::ForeignToplevelListState::new::<
221- State,
222- >(&dh),
223188 list_handles: Default::default(),
224189 anim: Animator::new(animation),
225190 borders: Default::default(),
@@ -228,48 +193,14 @@ impl State {
228193 placed_by_rule: HashSet::new(),
229194 layer_focus: None,
230195 xwayland: None,
231- xwayland_shell_state: XWaylandShellState::new::<State>(&dh),
232196 captures: Default::default(),
233197 imports,
234198 render_node: None,
235199 lock: Default::default(),
236200 inhibitors: HashSet::new(),
237201 inhibited: false,
238- handle: event_loop.handle(),
239- compositor_state: CompositorState::new::<State>(&dh),
240- xdg_shell_state: XdgShellState::new::<State>(&dh),
241- layer_shell_state: WlrLayerShellState::new::<State>(&dh),
242- _image_capture_source_state: ImageCaptureSourceState::new(),
243- output_capture_source_state: OutputCaptureSourceState::new::<State>(&dh),
244- toplevel_capture_source_state: ToplevelCaptureSourceState::new::<State>(&dh),
245- image_copy_capture_state: ImageCopyCaptureState::new::<State>(&dh),
246- activation_state: XdgActivationState::new::<State>(&dh),
247- wlr_data_control_state: WlrDataControlState::new::<State, _>(
248- &dh,
249- Some(&primary_selection_state),
250- |_| true,
251- ),
252- ext_data_control_state: ExtDataControlState::new::<State, _>(
253- &dh,
254- Some(&primary_selection_state),
255- |_| true,
256- ),
257- primary_selection_state,
258- session_lock_state: SessionLockManagerState::new::<State, _>(&dh, |_| true),
259- idle_notifier: IdleNotifierState::new(&dh, event_loop.handle()),
260- _idle_inhibit: IdleInhibitManagerState::new::<State>(&dh),
261- _xdg_decoration_state: XdgDecorationState::new::<State>(&dh),
262- shm_state: ShmState::new::<State>(&dh, vec![]),
263- dmabuf_state,
264- _dmabuf_global: dmabuf_global,
265- _output_manager_state: OutputManagerState::new_with_xdg_output::<State>(&dh),
266202 seat_state,
267- data_device_state: DataDeviceState::new::<State>(&dh),
268- _cursor_shape_state: smithay::wayland::cursor_shape::CursorShapeManagerState::new::<
269- State,
270- >(&dh),
271203 seat,
272- display: dh,
273204 })
274205 }
275206
@@ -284,7 +215,7 @@ impl State {
284215 crate::ipc::broadcast(self);
285216 self.publish_workspaces();
286217 self.publish_toplevels();
287- let _ = self.display.flush_clients();
218+ let _ = self.core.display.flush_clients();
288219 }
289220
290221 pub fn resize_output(&mut self, size: Size<i32, Logical>) {
@@ -309,13 +240,13 @@ impl State {
309240 /// and title are known and the initial configure can carry its size.
310241 pub fn map_window(&mut self, window: Window, float: Option<Rectangle<i32, Logical>>) {
311242 let (app_id, title) = identity(&window);
312- let rule = self.config.placement(&app_id, &title);
243+ let rule = self.settings.config.placement(&app_id, &title);
313244 let pid = match window.x11_surface() {
314245 Some(x) => x.pid(),
315246 None => window
316247 .toplevel()
317248 .and_then(|t| t.wl_surface().client())
318- .and_then(|c| c.get_credentials(&self.display).ok())
249+ .and_then(|c| c.get_credentials(&self.core.display).ok())
319250 .map(|c| c.pid as u32),
320251 };
321252 let launch = pid.and_then(|pid| self.launches.find(pid));
@@ -368,7 +299,7 @@ impl State {
368299 fn await_first_buffer(&mut self, id: WindowId) {
369300 self.awaiting.insert(id);
370301 let timeout = Timer::from_duration(Duration::from_millis(300));
371- let _ = self.handle.insert_source(timeout, move |_, _, state| {
302+ let _ = self.core.handle.insert_source(timeout, move |_, _, state| {
372303 state.first_buffer(id);
373304 TimeoutAction::Drop
374305 });
@@ -613,7 +544,7 @@ impl State {
613544 }
614545
615546 fn publish_workspaces(&mut self) {
616- if !self.workspaces.has_clients() {
547+ if !self.protocols.workspaces.has_clients() {
617548 return;
618549 }
619550 let l = &self.layout;
@@ -646,12 +577,14 @@ impl State {
646577 Some(Group { output, workspaces })
647578 })
648579 .collect();
649- let dh = self.display.clone();
650- self.workspaces.update::<State>(&dh, Snapshot { groups });
580+ let dh = self.core.display.clone();
581+ self.protocols
582+ .workspaces
583+ .update::<State>(&dh, Snapshot { groups });
651584 }
652585
653586 pub fn reconfigure_keyboard(&mut self) {
654- let kb = self.config.keyboard.clone();
587+ let kb = self.settings.config.keyboard.clone();
655588 if let Some(keyboard) = self.seat.get_keyboard() {
656589 if let Err(e) = keyboard.set_xkb_config(self, xkb_config(&kb)) {
657590 tracing::warn!("keyboard config rejected: {e:?}");
Modified file: crates/monolith/src/toplevels.rs+11 additions−7 deletions
@@ -29,13 +29,13 @@ pub fn key(id: WindowId) -> u64 {
2929
3030 impl ForeignToplevelListHandler for State {
3131 fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
32- &mut self.toplevel_list
32+ &mut self.protocols.toplevel_list
3333 }
3434 }
3535
3636 impl ForeignToplevelHandler for State {
3737 fn foreign_toplevel_state(&mut self) -> &mut ForeignToplevelState {
38- &mut self.foreign_toplevels
38+ &mut self.protocols.foreign_toplevels
3939 }
4040
4141 fn foreign_toplevel_action(&mut self, action: Action) {
@@ -91,7 +91,7 @@ impl State {
9191 handles.retain(|id, h| {
9292 let keep = self.windows.contains_key(id);
9393 if !keep {
94- self.toplevel_list.remove_toplevel(h);
94+ self.protocols.toplevel_list.remove_toplevel(h);
9595 }
9696 keep
9797 });
@@ -106,14 +106,17 @@ impl State {
106106 }
107107 }
108108 None => {
109- let h = self.toplevel_list.new_toplevel::<State>(title, app_id);
109+ let h = self
110+ .protocols
111+ .toplevel_list
112+ .new_toplevel::<State>(title, app_id);
110113 handles.insert(*id, h);
111114 }
112115 }
113116 }
114117 self.list_handles.0 = handles;
115118
116- if !self.foreign_toplevels.has_clients() {
119+ if !self.protocols.foreign_toplevels.has_clients() {
117120 return;
118121 }
119122 let focused = self.layout.focused();
@@ -138,8 +141,9 @@ impl State {
138141 }
139142 })
140143 .collect();
141- let dh = self.display.clone();
142- self.foreign_toplevels
144+ let dh = self.core.display.clone();
145+ self.protocols
146+ .foreign_toplevels
143147 .update::<State>(&dh, Snapshot { toplevels });
144148 }
145149 }
Modified file: crates/monolith/src/xwayland.rs+8 additions−7 deletions
@@ -101,7 +101,7 @@ pub fn init(state: &mut State) {
101101 let mut watch = Vec::new();
102102 for (i, l) in listeners.iter().enumerate() {
103103 let Ok(l) = l.try_clone() else { continue };
104- let token = state.handle.insert_source(
104+ let token = state.core.handle.insert_source(
105105 Generic::new(l, Interest::READ, Mode::Level),
106106 move |_, _, state| {
107107 start(state, i);
@@ -130,7 +130,7 @@ fn rearm(state: &mut State) {
130130 };
131131 x.running = false;
132132 for (_, t) in &x.watch {
133- let _ = state.handle.enable(t);
133+ let _ = state.core.handle.enable(t);
134134 }
135135 }
136136
@@ -156,7 +156,7 @@ fn start(state: &mut State, fired: usize) {
156156 x.running = true;
157157 for (i, t) in &x.watch {
158158 if *i != fired {
159- state.handle.disable(t).ok();
159+ state.core.handle.disable(t).ok();
160160 }
161161 }
162162 let mut args = Vec::new();
@@ -168,7 +168,7 @@ fn start(state: &mut State, fired: usize) {
168168 // made inheritable just for this one spawn.
169169 set_inherit(&x.listeners, true);
170170 let spawned = XWayland::spawn(
171- &state.display,
171+ &state.core.display,
172172 None,
173173 std::iter::empty::<(String, String)>(),
174174 args,
@@ -187,11 +187,12 @@ fn start(state: &mut State, fired: usize) {
187187 };
188188 tracing::info!("starting Xwayland");
189189 let inserted = state
190+ .core
190191 .handle
191192 .insert_source(xwayland, move |event, _, state| match event {
192193 XWaylandEvent::Ready { x11_socket, .. } => {
193- let dh = state.display.clone();
194- match X11Wm::start_wm(state.handle.clone(), &dh, x11_socket, client.clone()) {
194+ let dh = state.core.display.clone();
195+ match X11Wm::start_wm(state.core.handle.clone(), &dh, x11_socket, client.clone()) {
195196 Ok(wm) => {
196197 tracing::info!("Xwayland ready");
197198 if let Some(x) = state.xwayland.as_mut() {
@@ -213,7 +214,7 @@ fn start(state: &mut State, fired: usize) {
213214
214215 impl XWaylandShellHandler for State {
215216 fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
216- &mut self.xwayland_shell_state
217+ &mut self.protocols.xwayland_shell_state
217218 }
218219
219220 fn surface_associated(&mut self, _xwm: XwmId, surface: WlSurface, _window: X11Surface) {