atagen

monolith

public
Star0
Watch0

Commit 7e58494265db

tarball

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

13 files changed+101-82
monolith: group input state into Input and redraw flags into Dirty

This finishes the split from design/state-split.md. State is down to ten fields, each a struct with one concern, so functions can borrow the part they need.
Modified file: crates/monolith/src/activation.rs+1 additions−1 deletions
@@ -27,7 +27,7 @@ impl XdgActivationHandler for State {
2727 }
2828
2929 fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool {
30- let keyboard = self.seat.get_keyboard();
30+ let keyboard = self.input.seat.get_keyboard();
3131 let focused = keyboard
3232 .as_ref()
3333 .and_then(|k| k.current_focus())
Modified file: crates/monolith/src/capture.rs+4 additions−4 deletions
@@ -117,7 +117,7 @@ impl ImageCopyCaptureHandler for State {
117117 .captures
118118 .waiting
119119 .push((session.clone(), frame));
120- self.needs_redraw = true;
120+ self.dirty.redraw = true;
121121 } else {
122122 self.capture(session, frame);
123123 }
@@ -279,11 +279,11 @@ impl State {
279279 let now = now_ns();
280280 let mut elements = Vec::new();
281281 if cursor && let Some(area) = self.wm.layout.output(o).map(|out| out.area) {
282- let local = self.pointer_pos - Point::from((area.x, area.y));
282+ let local = self.input.pointer_pos - Point::from((area.x, area.y));
283283 if local.x >= 0.0 && local.y >= 0.0 && local.x < area.w && local.y < area.h {
284- elements.extend(self.cursor.elements(
284+ elements.extend(self.input.cursor.elements(
285285 &mut self.renderer,
286- &self.cursor_status,
286+ &self.input.cursor_status,
287287 local,
288288 ));
289289 }
Modified file: crates/monolith/src/commands.rs+2 additions−2 deletions
@@ -52,7 +52,7 @@ impl State {
5252 pub fn run(&mut self, cmd: Command) -> Result<(), String> {
5353 let output = self.wm.layout.active_output();
5454 let focused = self.wm.layout.focused();
55- let inverted = std::mem::take(&mut self.move_inverted);
55+ let inverted = std::mem::take(&mut self.input.move_inverted);
5656 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 {
@@ -83,7 +83,7 @@ impl State {
8383 Command::Maximize => {
8484 self.wm.layout.expand(ExpandScope::All);
8585 }
86- Command::InvertMove => self.move_inverted = !inverted,
86+ Command::InvertMove => self.input.move_inverted = !inverted,
8787 Command::Resize { axis, delta } => {
8888 let axis = match axis {
8989 monolith_ipc::Axis::Horizontal => Axis::Horizontal,
Modified file: crates/monolith/src/drm.rs+10 additions−12 deletions
@@ -258,7 +258,7 @@ pub fn init(
258258 s.redraw = true;
259259 }
260260 }
261- state.needs_redraw = true;
261+ state.dirty.redraw = true;
262262 }
263263 })?;
264264 handle.insert_source(drm_events, |event, meta, state| match event {
@@ -584,8 +584,8 @@ pub fn render(state: &mut State) {
584584 let Some(drm) = state.backend.drm_mut() else {
585585 return;
586586 };
587- let cursor = std::mem::take(&mut state.cursor_moved);
588- let all = std::mem::take(&mut state.needs_redraw);
587+ let cursor = std::mem::take(&mut state.dirty.cursor);
588+ let all = std::mem::take(&mut state.dirty.redraw);
589589 for s in drm.surfaces.values_mut() {
590590 s.redraw |= all;
591591 s.cursor_redraw |= cursor;
@@ -611,9 +611,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
611611 wm,
612612 settings,
613613 core,
614- cursor,
615- cursor_status,
616- pointer_pos,
614+ input,
617615 services:
618616 Services {
619617 lock,
@@ -664,9 +662,9 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
664662 return false;
665663 };
666664 let mut elements = Vec::new();
667- let local = *pointer_pos - Point::from((area.x, area.y));
665+ let local = input.pointer_pos - Point::from((area.x, area.y));
668666 if local.x >= 0.0 && local.y >= 0.0 && local.x < area.w && local.y < area.h {
669- elements.extend(cursor.elements(renderer, cursor_status, local));
667+ elements.extend(input.cursor.elements(renderer, &input.cursor_status, local));
670668 }
671669 let scene_began = std::time::Instant::now();
672670 match crate::lock::elements(lock, renderer, &surface.output) {
@@ -756,10 +754,10 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
756754 state.key(event.key_code(), event.state(), time);
757755 }
758756 InputEvent::PointerMotion { event } => {
759- let pos = clamp(state, state.pointer_pos + event.delta());
757+ let pos = clamp(state, state.input.pointer_pos + event.delta());
760758 state.pointer_to(pos, event.time());
761759 state.pointer_frame();
762- state.cursor_moved = true;
760+ state.dirty.cursor = true;
763761 }
764762 InputEvent::PointerMotionAbsolute { event } => {
765763 let Some(area) = state.wm.layout.outputs().next().map(|(_, o)| o.area) else {
@@ -769,7 +767,7 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
769767 let pos = Point::from((area.x + p.x, area.y + p.y));
770768 state.pointer_to(pos, event.time());
771769 state.pointer_frame();
772- state.cursor_moved = true;
770+ state.dirty.cursor = true;
773771 }
774772 InputEvent::PointerButton { event } => {
775773 let time = event.time();
@@ -813,7 +811,7 @@ fn clamp(
813811 }
814812 let Some(a) = areas
815813 .iter()
816- .find(|a| inside(a, state.pointer_pos))
814+ .find(|a| inside(a, state.input.pointer_pos))
817815 .or(areas.first())
818816 else {
819817 return pos;
Modified file: crates/monolith/src/handlers.rs+4 additions−4 deletions
@@ -109,7 +109,7 @@ impl CompositorHandler for State {
109109 let _ = popup.send_configure();
110110 }
111111 self.layer_commit(surface);
112- self.needs_redraw = true;
112+ self.dirty.redraw = true;
113113 }
114114 }
115115
@@ -186,12 +186,12 @@ impl SeatHandler for State {
186186 type TouchFocus = WlSurface;
187187
188188 fn seat_state(&mut self) -> &mut SeatState<State> {
189- &mut self.seat_state
189+ &mut self.input.seat_state
190190 }
191191
192192 fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
193- self.cursor_status = image;
194- self.needs_redraw = true;
193+ self.input.cursor_status = image;
194+ self.dirty.redraw = true;
195195 }
196196
197197 fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&crate::focus::Focus>) {
Modified file: crates/monolith/src/idle.rs+1 additions−1 deletions
@@ -46,7 +46,7 @@ impl IdleInhibitHandler for State {
4646
4747 impl State {
4848 pub fn activity(&mut self) {
49- let seat = self.seat.clone();
49+ let seat = self.input.seat.clone();
5050 self.services.idle.notifier.notify_activity(&seat);
5151 }
5252
Modified file: crates/monolith/src/input.rs+17 additions−16 deletions
@@ -13,12 +13,12 @@ use smithay::wayland::shell::wlr_layer::Layer;
1313
1414 impl State {
1515 pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {
16- self.pointer_pos = pos;
16+ self.input.pointer_pos = pos;
1717 self.activity();
1818 if drag_to(self, pos) {
1919 return;
2020 }
21- let Some(pointer) = self.seat.get_pointer() else {
21+ let Some(pointer) = self.input.seat.get_pointer() else {
2222 return;
2323 };
2424 let under = self.surface_under(pos);
@@ -35,7 +35,7 @@ impl State {
3535
3636 pub fn pointer_button(&mut self, button: u32, pressed: ButtonState, time: InputTime) {
3737 self.activity();
38- if pressed == ButtonState::Released && self.drag.take().is_some() {
38+ if pressed == ButtonState::Released && self.input.drag.take().is_some() {
3939 return;
4040 }
4141 if pressed == ButtonState::Pressed && !self.services.lock.active() {
@@ -44,7 +44,7 @@ impl State {
4444 return;
4545 }
4646 }
47- let Some(pointer) = self.seat.get_pointer() else {
47+ let Some(pointer) = self.input.seat.get_pointer() else {
4848 return;
4949 };
5050 pointer.button(
@@ -60,20 +60,20 @@ impl State {
6060
6161 pub fn pointer_axis(&mut self, frame: AxisFrame) {
6262 self.activity();
63- if let Some(pointer) = self.seat.get_pointer() {
63+ if let Some(pointer) = self.input.seat.get_pointer() {
6464 pointer.axis(self, frame);
6565 }
6666 }
6767
6868 pub fn pointer_frame(&mut self) {
69- if let Some(pointer) = self.seat.get_pointer() {
69+ if let Some(pointer) = self.input.seat.get_pointer() {
7070 pointer.frame(self);
7171 }
7272 }
7373
7474 pub fn key(&mut self, code: Keycode, pressed: KeyState, time: InputTime) {
7575 self.activity();
76- let Some(keyboard) = self.seat.get_keyboard() else {
76+ let Some(keyboard) = self.input.seat.get_keyboard() else {
7777 return;
7878 };
7979 let action = keyboard.input(
@@ -84,9 +84,9 @@ impl State {
8484 time,
8585 |state, mods, handle| {
8686 if pressed == KeyState::Released {
87- let before = state.suppressed.len();
88- state.suppressed.retain(|k| *k != code);
89- return if state.suppressed.len() < before {
87+ let before = state.input.suppressed.len();
88+ state.input.suppressed.retain(|k| *k != code);
89+ return if state.input.suppressed.len() < before {
9090 FilterResult::Intercept(None)
9191 } else {
9292 FilterResult::Forward
@@ -96,7 +96,7 @@ impl State {
9696 if (xkb::keysyms::KEY_XF86Switch_VT_1..=xkb::keysyms::KEY_XF86Switch_VT_12)
9797 .contains(&sym)
9898 {
99- state.suppressed.push(code);
99+ state.input.suppressed.push(code);
100100 let vt = (sym - xkb::keysyms::KEY_XF86Switch_VT_1 + 1) as i32;
101101 return FilterResult::Intercept(Some(Action::Vt(vt)));
102102 }
@@ -106,7 +106,7 @@ impl State {
106106 match state.settings.binds.find(mods, &handle.raw_syms()) {
107107 Some(cmd) => {
108108 tracing::debug!(?cmd, "bind");
109- state.suppressed.push(code);
109+ state.input.suppressed.push(code);
110110 FilterResult::Intercept(Some(Action::Run(cmd)))
111111 }
112112 None => FilterResult::Forward,
@@ -138,7 +138,7 @@ enum Action {
138138 }
139139
140140 fn focus_under_pointer(state: &mut State) {
141- let pos = state.pointer_pos;
141+ let pos = state.input.pointer_pos;
142142 let layers = [Layer::Overlay, Layer::Top, Layer::Bottom, Layer::Background];
143143 if let Some((layer, surface, _)) = state.layer_under(pos, &layers)
144144 && layer.can_receive_keyboard_focus()
@@ -180,6 +180,7 @@ fn start_drag(state: &mut State, button: u32) -> bool {
180180 return false;
181181 }
182182 let held = state
183+ .input
183184 .seat
184185 .get_keyboard()
185186 .map(|k| k.modifier_state())
@@ -204,9 +205,9 @@ fn start_drag(state: &mut State, button: u32) -> bool {
204205 if !held {
205206 return false;
206207 }
207- state.drag = Some(Drag {
208+ state.input.drag = Some(Drag {
208209 window: id,
209- from: state.pointer_pos,
210+ from: state.input.pointer_pos,
210211 rect,
211212 resize: button == BTN_RIGHT,
212213 });
@@ -214,7 +215,7 @@ fn start_drag(state: &mut State, button: u32) -> bool {
214215 }
215216
216217 fn drag_to(state: &mut State, pos: Point<f64, Logical>) -> bool {
217- let Some(d) = &state.drag else {
218+ let Some(d) = &state.input.drag else {
218219 return false;
219220 };
220221 let (dx, dy) = (pos.x - d.from.x, pos.y - d.from.y);
Modified file: crates/monolith/src/ipc.rs+1 additions−1 deletions
@@ -199,7 +199,7 @@ fn snapshot(state: &State) -> monolith_ipc::State {
199199 .collect();
200200 windows.sort_by_key(|w| w.id);
201201 monolith_ipc::State {
202- move_inverted: state.move_inverted,
202+ move_inverted: state.input.move_inverted,
203203 tags,
204204 outputs,
205205 windows,
Modified file: crates/monolith/src/layers.rs+7 additions−1 deletions
@@ -35,7 +35,13 @@ impl WlrLayerShellHandler for State {
3535 let output = output
3636 .as_ref()
3737 .and_then(Output::from_resource)
38- .or_else(|| self.wm.space.output_under(self.pointer_pos).next().cloned())
38+ .or_else(|| {
39+ self.wm
40+ .space
41+ .output_under(self.input.pointer_pos)
42+ .next()
43+ .cloned()
44+ })
3945 .or_else(|| self.wm.space.outputs().next().cloned());
4046 let Some(output) = output else {
4147 surface.send_close();
Modified file: crates/monolith/src/lock.rs+5 additions−5 deletions
@@ -57,16 +57,16 @@ impl SessionLockHandler for State {
5757 tracing::info!("session lock requested");
5858 self.services.lock.pending = Some(confirmation);
5959 self.services.lock.blanked.clear();
60- self.drag = None;
60+ self.input.drag = None;
6161 self.sync_focus();
62- self.needs_redraw = true;
62+ self.dirty.redraw = true;
6363 }
6464
6565 fn unlock(&mut self) {
6666 tracing::info!("session unlocked");
6767 self.services.lock = Lock::default();
6868 self.sync_focus();
69- self.needs_redraw = true;
69+ self.dirty.redraw = true;
7070 }
7171
7272 fn new_surface(&mut self, surface: LockSurface, output: WlOutput) {
@@ -80,7 +80,7 @@ impl SessionLockHandler for State {
8080 .retain(|(o, s)| o != &output && s.alive());
8181 self.services.lock.surfaces.push((output, surface));
8282 self.sync_focus();
83- self.needs_redraw = true;
83+ self.dirty.redraw = true;
8484 }
8585 }
8686
@@ -121,7 +121,7 @@ impl State {
121121 }
122122
123123 pub fn lock_keyboard_focus(&self) -> Option<WlSurface> {
124- let under = self.wm.space.output_under(self.pointer_pos).next();
124+ let under = self.wm.space.output_under(self.input.pointer_pos).next();
125125 under
126126 .and_then(|o| self.services.lock.surface_on(o))
127127 .or_else(|| {
Modified file: crates/monolith/src/nested/mod.rs+5 additions−5 deletions
@@ -175,7 +175,7 @@ pub fn render(state: &mut State) {
175175 backend,
176176 renderer,
177177 wm,
178- needs_redraw,
178+ dirty,
179179 core,
180180 settings,
181181 services:
@@ -190,7 +190,7 @@ pub fn render(state: &mut State) {
190190 let Some(host) = backend.nested_mut() else {
191191 return;
192192 };
193- if !host.configured || host.frame_pending || !*needs_redraw {
193+ if !host.configured || host.frame_pending || !dirty.redraw {
194194 return;
195195 }
196196 if let Some(size) = host.pending_size.take() {
@@ -258,7 +258,7 @@ pub fn render(state: &mut State) {
258258 }
259259 }
260260 };
261- *needs_redraw = wm.anim.active(now);
261+ dirty.redraw = wm.anim.active(now);
262262 wm.anim.prune(now);
263263 lock.shown(&host.output);
264264 let elapsed = core.start.elapsed();
@@ -273,7 +273,7 @@ pub fn render(state: &mut State) {
273273 captures.output_damaged(&host.output);
274274 }
275275 let Some(damage) = damage else {
276- if *needs_redraw {
276+ if dirty.redraw {
277277 host.surface.frame(&host.qh, ());
278278 host.surface.commit();
279279 host.frame_pending = true;
@@ -407,7 +407,7 @@ impl Dispatch<xdg_surface::XdgSurface, ()> for State {
407407 if let Some(host) = state.backend.nested_mut() {
408408 host.configured = true;
409409 }
410- state.needs_redraw = true;
410+ state.dirty.redraw = true;
411411 }
412412 }
413413 }
Modified file: crates/monolith/src/state.rs+40 additions−26 deletions
@@ -35,24 +35,34 @@ use std::time::{Duration, Instant};
3535 pub struct State {
3636 pub core: Core,
3737 pub settings: Settings,
38- pub protocols: Protocols,
38+ pub backend: crate::backend::Backend,
3939 pub renderer: VulkanRenderer,
40+ pub protocols: Protocols,
41+ pub services: Services,
4042 pub wm: Wm,
41- pub backend: crate::backend::Backend,
42- pub cursor: crate::cursor::Cursor,
43- pub cursor_status: CursorImageStatus,
44- pub needs_redraw: bool,
45- /// The pointer moved, which on its own only redraws the cursor.
46- pub cursor_moved: bool,
43+ pub input: Input,
44+ pub dirty: Dirty,
45+}
4746
48- pub suppressed: Vec<Keycode>,
49- pub move_inverted: bool,
50- pub drag: Option<crate::input::Drag>,
47+/// The seat and what monolith tracks about input on it.
48+pub struct Input {
49+ pub seat: Seat<State>,
50+ pub seat_state: SeatState<State>,
5151 /// Global logical position, shared by every backend.
5252 pub pointer_pos: Point<f64, Logical>,
53- pub services: Services,
54- pub seat_state: SeatState<State>,
55- pub seat: Seat<State>,
53+ /// Keys whose press ran a bind, so their release is swallowed too.
54+ pub suppressed: Vec<Keycode>,
55+ pub drag: Option<crate::input::Drag>,
56+ pub move_inverted: bool,
57+ pub cursor: crate::cursor::Cursor,
58+ pub cursor_status: CursorImageStatus,
59+}
60+
61+/// What needs doing after this batch of events.
62+pub struct Dirty {
63+ pub redraw: bool,
64+ /// The pointer moved, which on its own only redraws the cursor.
65+ pub cursor: bool,
5666 }
5767
5868 /// The event loop and Wayland display, which everything uses and nothing owns.
@@ -148,14 +158,20 @@ impl State {
148158 renderer,
149159 wm: Wm::new(layout, Animator::new(animation)),
150160 backend: crate::backend::Backend::Starting,
151- cursor: crate::cursor::Cursor::load(),
152- cursor_status: CursorImageStatus::default_named(),
153- needs_redraw: true,
154- cursor_moved: false,
155- suppressed: Vec::new(),
156- move_inverted: false,
157- drag: None,
158- pointer_pos: Point::default(),
161+ input: Input {
162+ seat,
163+ seat_state,
164+ pointer_pos: Point::default(),
165+ suppressed: Vec::new(),
166+ drag: None,
167+ move_inverted: false,
168+ cursor: crate::cursor::Cursor::load(),
169+ cursor_status: CursorImageStatus::default_named(),
170+ },
171+ dirty: Dirty {
172+ redraw: true,
173+ cursor: false,
174+ },
159175 services: Services {
160176 lock: Default::default(),
161177 captures: Default::default(),
@@ -165,8 +181,6 @@ impl State {
165181 ipc: Default::default(),
166182 idle,
167183 },
168- seat_state,
169- seat,
170184 })
171185 }
172186
@@ -484,7 +498,7 @@ impl State {
484498 }
485499 self.sync_focus();
486500 self.update_inhibit();
487- self.needs_redraw = true;
501+ self.dirty.redraw = true;
488502 }
489503
490504 pub fn sync_focus(&mut self) {
@@ -509,7 +523,7 @@ impl State {
509523 }
510524 })
511525 };
512- if let Some(keyboard) = self.seat.get_keyboard()
526+ if let Some(keyboard) = self.input.seat.get_keyboard()
513527 && keyboard.current_focus() != target
514528 {
515529 keyboard.set_focus(self, target, SERIAL_COUNTER.next_serial());
@@ -558,7 +572,7 @@ impl State {
558572
559573 pub fn reconfigure_keyboard(&mut self) {
560574 let kb = self.settings.config.keyboard.clone();
561- if let Some(keyboard) = self.seat.get_keyboard() {
575+ if let Some(keyboard) = self.input.seat.get_keyboard() {
562576 if let Err(e) = keyboard.set_xkb_config(self, xkb_config(&kb)) {
563577 tracing::warn!("keyboard config rejected: {e:?}");
564578 }
Modified file: crates/monolith/src/xwayland.rs+4 additions−4 deletions
@@ -222,7 +222,7 @@ impl XWaylandShellHandler for State {
222222 self.wm.index(id);
223223 }
224224 self.on_buffer(&surface);
225- self.needs_redraw = true;
225+ self.dirty.redraw = true;
226226 }
227227 }
228228
@@ -253,7 +253,7 @@ impl XwmHandler for State {
253253 x.unmanaged.retain(|w| w != &window);
254254 x.unmanaged.push(window);
255255 }
256- self.needs_redraw = true;
256+ self.dirty.redraw = true;
257257 }
258258
259259 fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
@@ -297,7 +297,7 @@ impl XwmHandler for State {
297297 _above: Option<smithay::xwayland::xwm::X11Window>,
298298 ) {
299299 if window.is_override_redirect() {
300- self.needs_redraw = true;
300+ self.dirty.redraw = true;
301301 }
302302 }
303303
@@ -373,7 +373,7 @@ impl State {
373373 if let Some(id) = self.x11_window_id(window) {
374374 self.remove_window(id);
375375 }
376- self.needs_redraw = true;
376+ self.dirty.redraw = true;
377377 }
378378
379379 fn x11_fullscreen(&mut self, window: &X11Surface, on: bool) {