atagen

monolith

public
Star0
Watch0

Commit 1576c9318a4a

tarball

atagen <boss@atagen.co>2026-10-16 12:15 UTC

5 files changed+720-694
monolith: separate input handling from the nested host

Pointer, button, axis and key handling now live on State and take global coordinates, so the DRM backend can drive the same paths from libinput. The host seat only translates wl_pointer and wl_keyboard events into those calls.
Modified file: crates/monolith/src/input.rs+85 additions−167 deletions
@@ -1,190 +1,111 @@
1-//! Host input forwarded into monolith's seat.
1+//! Input handling shared by every backend. Backends translate their events
2+//! into these calls, with pointer positions in global logical coordinates.
23
34 use crate::state::State;
45 use monolith_config::Modifier;
56 use monolith_layout::{Rect, WindowId};
6-use smithay::backend::input::{Axis, ButtonState, InputTime, KeyState};
7+use smithay::backend::input::{ButtonState, InputTime, KeyState};
78 use smithay::input::keyboard::{FilterResult, Keycode};
89 use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
9-use smithay::utils::Logical;
10-use smithay::utils::{Point, SERIAL_COUNTER};
11-use wayland_client::protocol::{wl_keyboard, wl_pointer, wl_seat};
12-use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};
10+use smithay::utils::{Logical, Point, SERIAL_COUNTER};
1311
14-impl Dispatch<wl_seat::WlSeat, ()> for State {
15- fn event(
16- _: &mut Self,
17- seat: &wl_seat::WlSeat,
18- event: wl_seat::Event,
19- _: &(),
20- _: &Connection,
21- qh: &QueueHandle<Self>,
22- ) {
23- if let wl_seat::Event::Capabilities {
24- capabilities: WEnum::Value(caps),
25- } = event
26- {
27- if caps.contains(wl_seat::Capability::Pointer) {
28- seat.get_pointer(qh, ());
29- }
30- if caps.contains(wl_seat::Capability::Keyboard) {
31- seat.get_keyboard(qh, ());
32- }
12+impl State {
13+ pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {
14+ self.pointer_pos = pos;
15+ if drag_to(self, pos) {
16+ return;
3317 }
18+ let Some(pointer) = self.seat.get_pointer() else {
19+ return;
20+ };
21+ let under = self.surface_under(pos);
22+ pointer.motion(
23+ self,
24+ under,
25+ &MotionEvent {
26+ location: pos,
27+ serial: SERIAL_COUNTER.next_serial(),
28+ time,
29+ },
30+ );
3431 }
35-}
3632
37-impl Dispatch<wl_pointer::WlPointer, ()> for State {
38- fn event(
39- state: &mut Self,
40- _: &wl_pointer::WlPointer,
41- event: wl_pointer::Event,
42- _: &(),
43- _: &Connection,
44- _: &QueueHandle<Self>,
45- ) {
46- let Some(pointer) = state.seat.get_pointer() else {
33+ pub fn pointer_button(&mut self, button: u32, pressed: ButtonState, time: InputTime) {
34+ if pressed == ButtonState::Released && self.drag.take().is_some() {
4735 return;
48- };
49- let time = InputTime::now();
50- match event {
51- wl_pointer::Event::Enter {
52- surface_x,
53- surface_y,
54- ..
55- }
56- | wl_pointer::Event::Motion {
57- surface_x,
58- surface_y,
59- ..
60- } => {
61- let pos = Point::from((surface_x, surface_y));
62- if let Some(host) = state.host.as_mut() {
63- host.pointer_pos = pos;
64- }
65- if drag_to(state, pos) {
66- return;
67- }
68- let under = state.surface_under(pos);
69- pointer.motion(
70- state,
71- under,
72- &MotionEvent {
73- location: pos,
74- serial: SERIAL_COUNTER.next_serial(),
75- time,
76- },
77- );
36+ }
37+ if pressed == ButtonState::Pressed {
38+ focus_under_pointer(self);
39+ if start_drag(self, button) {
40+ return;
7841 }
79- wl_pointer::Event::Button {
42+ }
43+ let Some(pointer) = self.seat.get_pointer() else {
44+ return;
45+ };
46+ pointer.button(
47+ self,
48+ &ButtonEvent {
8049 button,
81- state: WEnum::Value(pressed),
82- ..
83- } => {
84- let serial = SERIAL_COUNTER.next_serial();
85- let button_state = match pressed {
86- wl_pointer::ButtonState::Pressed => ButtonState::Pressed,
87- _ => ButtonState::Released,
88- };
89- if button_state == ButtonState::Released && state.drag.take().is_some() {
90- return;
91- }
92- if button_state == ButtonState::Pressed {
93- focus_under_pointer(state);
94- if start_drag(state, button) {
95- return;
96- }
97- }
98- pointer.button(
99- state,
100- &ButtonEvent {
101- button,
102- state: button_state,
103- serial,
104- time,
105- },
106- );
107- }
108- wl_pointer::Event::Axis {
109- axis: WEnum::Value(axis),
110- value,
111- ..
112- } => {
113- let axis = match axis {
114- wl_pointer::Axis::HorizontalScroll => Axis::Horizontal,
115- _ => Axis::Vertical,
116- };
117- pointer.axis(state, AxisFrame::new(time).value(axis, value));
118- }
119- wl_pointer::Event::Frame => pointer.frame(state),
120- _ => {}
50+ state: pressed,
51+ serial: SERIAL_COUNTER.next_serial(),
52+ time,
53+ },
54+ );
55+ }
56+
57+ pub fn pointer_axis(&mut self, frame: AxisFrame) {
58+ if let Some(pointer) = self.seat.get_pointer() {
59+ pointer.axis(self, frame);
12160 }
12261 }
123-}
12462
125-impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
126- fn event(
127- state: &mut Self,
128- _: &wl_keyboard::WlKeyboard,
129- event: wl_keyboard::Event,
130- _: &(),
131- _: &Connection,
132- _: &QueueHandle<Self>,
133- ) {
134- let Some(keyboard) = state.seat.get_keyboard() else {
63+ pub fn pointer_frame(&mut self) {
64+ if let Some(pointer) = self.seat.get_pointer() {
65+ pointer.frame(self);
66+ }
67+ }
68+
69+ pub fn key(&mut self, code: Keycode, pressed: KeyState, time: InputTime) {
70+ let Some(keyboard) = self.seat.get_keyboard() else {
13571 return;
13672 };
137- if let wl_keyboard::Event::Key {
138- key,
139- state: WEnum::Value(pressed),
140- ..
141- } = event
142- {
143- let key_state = match pressed {
144- wl_keyboard::KeyState::Pressed => KeyState::Pressed,
145- _ => KeyState::Released,
146- };
147- let time = InputTime::now();
148- let code = Keycode::new(key + 8);
149- let action = keyboard.input(
150- state,
151- code,
152- key_state,
153- SERIAL_COUNTER.next_serial(),
154- time,
155- |state, mods, handle| {
156- if key_state == KeyState::Released {
157- let before = state.suppressed.len();
158- state.suppressed.retain(|k| *k != code);
159- return if state.suppressed.len() < before {
160- FilterResult::Intercept(None)
161- } else {
162- FilterResult::Forward
163- };
164- }
165- match state.binds.find(mods, &handle.raw_syms()) {
166- Some(cmd) => {
167- tracing::debug!(?cmd, "bind");
168- state.suppressed.push(code);
169- FilterResult::Intercept(Some(cmd))
170- }
171- None => FilterResult::Forward,
73+ let action = keyboard.input(
74+ self,
75+ code,
76+ pressed,
77+ SERIAL_COUNTER.next_serial(),
78+ time,
79+ |state, mods, handle| {
80+ if pressed == KeyState::Released {
81+ let before = state.suppressed.len();
82+ state.suppressed.retain(|k| *k != code);
83+ return if state.suppressed.len() < before {
84+ FilterResult::Intercept(None)
85+ } else {
86+ FilterResult::Forward
87+ };
88+ }
89+ match state.binds.find(mods, &handle.raw_syms()) {
90+ Some(cmd) => {
91+ tracing::debug!(?cmd, "bind");
92+ state.suppressed.push(code);
93+ FilterResult::Intercept(Some(cmd))
17294 }
173- },
174- );
175- if let Some(Some(cmd)) = action
176- && let Err(e) = state.run(cmd)
177- {
178- tracing::warn!("bind failed: {e}");
179- }
95+ None => FilterResult::Forward,
96+ }
97+ },
98+ );
99+ if let Some(Some(cmd)) = action
100+ && let Err(e) = self.run(cmd)
101+ {
102+ tracing::warn!("bind failed: {e}");
180103 }
181104 }
182105 }
183106
184107 fn focus_under_pointer(state: &mut State) {
185- let Some(pos) = state.host.as_ref().map(|h| h.pointer_pos) else {
186- return;
187- };
108+ let pos = state.pointer_pos;
188109 let id = state
189110 .space
190111 .element_under(pos)
@@ -223,9 +144,6 @@ fn start_drag(state: &mut State, button: u32) -> bool {
223144 Modifier::Ctrl => m.ctrl,
224145 Modifier::Shift => m.shift,
225146 });
226- let Some(from) = state.host.as_ref().map(|h| h.pointer_pos) else {
227- return false;
228- };
229147 let Some(id) = state.layout.focused() else {
230148 return false;
231149 };
@@ -242,7 +160,7 @@ fn start_drag(state: &mut State, button: u32) -> bool {
242160 }
243161 state.drag = Some(Drag {
244162 window: id,
245- from,
163+ from: state.pointer_pos,
246164 rect,
247165 resize: button == BTN_RIGHT,
248166 });
Deleted file: crates/monolith/src/nested.rs−527 deletions
@@ -1,527 +0,0 @@
1-//! Runs monolith as a window inside another Wayland compositor. Frames are
2-//! rendered into dmabufs from our own swapchain and handed to the host
3-//! through linux-dmabuf, so the nested path exercises the same
4-//! render-into-dmabuf code as bare metal.
5-
6-use crate::state::State;
7-use calloop_wayland_source::WaylandSource;
8-use monolith_layout::Rect;
9-use monolith_render_vk::VulkanAllocator;
10-use smithay::backend::allocator::dmabuf::Dmabuf;
11-use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};
12-use smithay::backend::renderer::Bind;
13-use smithay::backend::renderer::damage::OutputDamageTracker;
14-use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
15-use smithay::reexports::calloop::EventLoop;
16-use smithay::utils::{Logical, Point, Size, Transform};
17-use std::time::Duration;
18-use wayland_client::globals::{GlobalListContents, registry_queue_init};
19-use wayland_client::protocol::{wl_buffer, wl_callback, wl_compositor, wl_registry, wl_surface};
20-use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
21-use wayland_protocols::wp::linux_dmabuf::zv1::client::{
22- zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,
23-};
24-use wayland_protocols::wp::presentation_time::client::{wp_presentation, wp_presentation_feedback};
25-use wayland_protocols::xdg::decoration::zv1::client::{
26- zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
27-};
28-use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
29-
30-const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];
31-const DEFAULT_SIZE: (i32, i32) = (1280, 800);
32-const CLOCK_MONOTONIC: u32 = 1;
33-
34-pub struct Host {
35- pub size: Size<i32, Logical>,
36- pub pointer_pos: Point<f64, Logical>,
37- pub output: Output,
38- conn: Connection,
39- qh: QueueHandle<State>,
40- surface: wl_surface::WlSurface,
41- _xdg_surface: xdg_surface::XdgSurface,
42- _toplevel: xdg_toplevel::XdgToplevel,
43- dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
44- formats: Vec<Format>,
45- swapchain: Option<Swapchain<VulkanAllocator>>,
46- in_flight: Vec<(wl_buffer::WlBuffer, Slot<Dmabuf>)>,
47- damage: OutputDamageTracker,
48- configured: bool,
49- frame_pending: bool,
50- pending_size: Option<Size<i32, Logical>>,
51- presentation: Option<wp_presentation::WpPresentation>,
52- /// The host clock is CLOCK_MONOTONIC, so its timestamps share our timeline.
53- monotonic: bool,
54- last_present_ns: Option<u64>,
55- refresh_ns: u64,
56-}
57-
58-impl Host {
59- /// When a frame committed now will reach the screen, the next host
60- /// vblank after `now`.
61- pub fn predict(&self, now: u64) -> u64 {
62- match self.last_present_ns.filter(|_| self.monotonic) {
63- Some(last) if self.refresh_ns > 0 && last <= now => {
64- last + ((now - last) / self.refresh_ns + 1) * self.refresh_ns
65- }
66- _ => now,
67- }
68- }
69-}
70-
71-/// Destroys the host-side buffer when its swapchain slot goes away.
72-struct HostBuffer(wl_buffer::WlBuffer);
73-
74-impl Drop for HostBuffer {
75- fn drop(&mut self) {
76- self.0.destroy();
77- }
78-}
79-
80-pub fn init(
81- event_loop: &mut EventLoop<State>,
82- state: &mut State,
83-) -> Result<(), Box<dyn std::error::Error>> {
84- let conn = Connection::connect_to_env()?;
85- let (globals, mut queue) = registry_queue_init::<State>(&conn)?;
86- let qh = queue.handle();
87- let compositor: wl_compositor::WlCompositor = globals.bind(&qh, 4..=6, ())?;
88- let wm_base: xdg_wm_base::XdgWmBase = globals.bind(&qh, 1..=6, ())?;
89- let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = globals.bind(&qh, 3..=3, ())?;
90- let presentation = globals
91- .bind::<wp_presentation::WpPresentation, _, _>(&qh, 1..=2, ())
92- .ok();
93- // Dropping a proxy handle does not destroy it, so seat events keep reaching input.rs.
94- let _ = globals.bind::<wayland_client::protocol::wl_seat::WlSeat, _, _>(&qh, 1..=7, ());
95-
96- let surface = compositor.create_surface(&qh, ());
97- let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
98- let toplevel = xdg_surface.get_toplevel(&qh, ());
99- toplevel.set_title("monolith".into());
100- toplevel.set_app_id("monolith".into());
101- if let Ok(deco) =
102- globals.bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(&qh, 1..=1, ())
103- {
104- deco.get_toplevel_decoration(&toplevel, &qh, ())
105- .set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
106- }
107- surface.commit();
108-
109- let output = Output::new(
110- "nested".into(),
111- PhysicalProperties {
112- size: (0, 0).into(),
113- subpixel: Subpixel::Unknown,
114- make: "monolith".into(),
115- model: "nested".into(),
116- serial_number: "0".into(),
117- },
118- );
119- let _global = output.create_global::<State>(&state.display);
120- let mode = Mode {
121- size: DEFAULT_SIZE.into(),
122- refresh: 60_000,
123- };
124- output.change_current_state(
125- Some(mode),
126- Some(Transform::Normal),
127- None,
128- Some((0, 0).into()),
129- );
130- output.set_preferred(mode);
131- state.space.map_output(&output, (0, 0));
132- let area = Rect::new(
133- 0.0,
134- 0.0,
135- f64::from(DEFAULT_SIZE.0),
136- f64::from(DEFAULT_SIZE.1),
137- );
138- state.output_id = Some(state.layout.add_output("nested", area));
139-
140- state.host = Some(Host {
141- size: DEFAULT_SIZE.into(),
142- pointer_pos: (0.0, 0.0).into(),
143- damage: OutputDamageTracker::from_output(&output),
144- output,
145- conn: conn.clone(),
146- qh,
147- surface,
148- _xdg_surface: xdg_surface,
149- _toplevel: toplevel,
150- dmabuf,
151- formats: Vec::new(),
152- swapchain: None,
153- in_flight: Vec::new(),
154- configured: false,
155- frame_pending: false,
156- pending_size: None,
157- presentation,
158- monotonic: false,
159- last_present_ns: None,
160- refresh_ns: 16_666_667,
161- });
162- queue.roundtrip(state)?;
163- queue.roundtrip(state)?;
164- WaylandSource::new(conn, queue).insert(event_loop.handle())?;
165- Ok(())
166-}
167-
168-/// Renders a frame if something changed and the host is ready for one.
169-pub fn render(state: &mut State) {
170- let State {
171- host,
172- renderer,
173- space,
174- needs_redraw,
175- start,
176- layout,
177- windows,
178- anim,
179- ..
180- } = state;
181- let Some(host) = host.as_mut() else { return };
182- if !host.configured || host.frame_pending || !*needs_redraw {
183- return;
184- }
185- if let Some(size) = host.pending_size.take() {
186- host.size = size;
187- let mode = Mode {
188- size: (size.w, size.h).into(),
189- refresh: (1_000_000_000_000 / host.refresh_ns) as i32,
190- };
191- host.output
192- .change_current_state(Some(mode), None, None, None);
193- host.output.set_preferred(mode);
194- if let Some(sc) = host.swapchain.as_mut() {
195- sc.resize(size.w as u32, size.h as u32);
196- }
197- }
198- let now = host.predict(crate::state::now_ns());
199- if host.swapchain.is_none() {
200- host.swapchain = make_swapchain(renderer, &host.formats, host.size);
201- }
202- let Some(swapchain) = host.swapchain.as_mut() else {
203- return;
204- };
205- let slot = match swapchain.acquire() {
206- Ok(Some(slot)) => slot,
207- Ok(None) => return,
208- Err(e) => {
209- tracing::warn!("failed to allocate a frame buffer: {e}");
210- return;
211- }
212- };
213- let mut dmabuf = (*slot).clone();
214- let damage = {
215- let mut fb = match renderer.bind(&mut dmabuf) {
216- Ok(fb) => fb,
217- Err(e) => {
218- tracing::warn!("cannot render into the swapchain: {e}");
219- return;
220- }
221- };
222- let elements = crate::scene::build(layout, windows, anim, renderer, now);
223- match host
224- .damage
225- .render_output(renderer, &mut fb, slot.age() as usize, &elements, CLEAR)
226- {
227- Ok(result) => {
228- if let Err(e) = result.sync.wait() {
229- tracing::warn!("waiting for the frame failed: {e:?}");
230- }
231- result.damage.cloned()
232- }
233- Err(e) => {
234- tracing::warn!("rendering failed: {e:?}");
235- return;
236- }
237- }
238- };
239- *needs_redraw = anim.active(now);
240- anim.prune(now);
241- let elapsed = start.elapsed();
242- for window in space.elements() {
243- window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {
244- Some(host.output.clone())
245- });
246- }
247- let Some(damage) = damage else {
248- if *needs_redraw {
249- host.surface.frame(&host.qh, ());
250- host.surface.commit();
251- host.frame_pending = true;
252- let _ = host.conn.flush();
253- }
254- return;
255- };
256- let buffer = match slot.userdata().get::<HostBuffer>() {
257- Some(b) => b.0.clone(),
258- None => {
259- let b = host_buffer(&host.dmabuf, &host.qh, &dmabuf);
260- slot.userdata().insert_if_missing(|| HostBuffer(b.clone()));
261- b
262- }
263- };
264- host.surface.attach(Some(&buffer), 0, 0);
265- for r in &damage {
266- host.surface
267- .damage_buffer(r.loc.x, r.loc.y, r.size.w, r.size.h);
268- }
269- host.surface.frame(&host.qh, ());
270- if let Some(p) = &host.presentation {
271- p.feedback(&host.surface, &host.qh, ());
272- }
273- host.surface.commit();
274- swapchain.submitted(&slot);
275- host.in_flight.push((buffer, slot));
276- host.frame_pending = true;
277- let _ = host.conn.flush();
278-}
279-
280-fn make_swapchain(
281- renderer: &monolith_render_vk::VulkanRenderer,
282- host: &[Format],
283- size: Size<i32, Logical>,
284-) -> Option<Swapchain<VulkanAllocator>> {
285- let ours = renderer.render_formats();
286- for fourcc in [Fourcc::Xrgb8888, Fourcc::Argb8888] {
287- let modifiers: Vec<Modifier> = host
288- .iter()
289- .filter(|f| f.code == fourcc && ours.contains(f))
290- .map(|f| f.modifier)
291- .collect();
292- if !modifiers.is_empty() {
293- return Some(Swapchain::new(
294- renderer.allocator(),
295- size.w as u32,
296- size.h as u32,
297- fourcc,
298- modifiers,
299- ));
300- }
301- }
302- tracing::error!("the host compositor and the renderer share no dmabuf format");
303- None
304-}
305-
306-fn host_buffer(
307- dmabuf: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
308- qh: &QueueHandle<State>,
309- buf: &Dmabuf,
310-) -> wl_buffer::WlBuffer {
311- use smithay::backend::allocator::Buffer;
312- let params = dmabuf.create_params(qh, ());
313- let modifier = u64::from(buf.format().modifier);
314- for (i, ((fd, offset), stride)) in buf
315- .handles()
316- .zip(buf.offsets())
317- .zip(buf.strides())
318- .enumerate()
319- {
320- params.add(
321- fd,
322- i as u32,
323- offset,
324- stride,
325- (modifier >> 32) as u32,
326- modifier as u32,
327- );
328- }
329- let buffer = params.create_immed(
330- buf.width() as i32,
331- buf.height() as i32,
332- buf.format().code as u32,
333- zwp_linux_buffer_params_v1::Flags::empty(),
334- qh,
335- (),
336- );
337- params.destroy();
338- buffer
339-}
340-
341-impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
342- fn event(
343- _: &mut Self,
344- _: &wl_registry::WlRegistry,
345- _: wl_registry::Event,
346- _: &GlobalListContents,
347- _: &Connection,
348- _: &QueueHandle<Self>,
349- ) {
350- }
351-}
352-
353-impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
354- fn event(
355- _: &mut Self,
356- base: &xdg_wm_base::XdgWmBase,
357- event: xdg_wm_base::Event,
358- _: &(),
359- _: &Connection,
360- _: &QueueHandle<Self>,
361- ) {
362- if let xdg_wm_base::Event::Ping { serial } = event {
363- base.pong(serial);
364- }
365- }
366-}
367-
368-impl Dispatch<xdg_surface::XdgSurface, ()> for State {
369- fn event(
370- state: &mut Self,
371- surface: &xdg_surface::XdgSurface,
372- event: xdg_surface::Event,
373- _: &(),
374- _: &Connection,
375- _: &QueueHandle<Self>,
376- ) {
377- if let xdg_surface::Event::Configure { serial } = event {
378- surface.ack_configure(serial);
379- if let Some(host) = state.host.as_mut() {
380- host.configured = true;
381- }
382- state.needs_redraw = true;
383- }
384- }
385-}
386-
387-impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
388- fn event(
389- state: &mut Self,
390- _: &xdg_toplevel::XdgToplevel,
391- event: xdg_toplevel::Event,
392- _: &(),
393- _: &Connection,
394- _: &QueueHandle<Self>,
395- ) {
396- match event {
397- xdg_toplevel::Event::Configure { width, height, .. } if width > 0 && height > 0 => {
398- let size = Size::from((width, height));
399- if let Some(host) = state.host.as_mut()
400- && host.size != size
401- {
402- host.pending_size = Some(size);
403- host.size = size;
404- state.resize_output(size);
405- }
406- }
407- xdg_toplevel::Event::Close => state.signal.stop(),
408- _ => {}
409- }
410- }
411-}
412-
413-impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for State {
414- fn event(
415- state: &mut Self,
416- _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
417- event: zwp_linux_dmabuf_v1::Event,
418- _: &(),
419- _: &Connection,
420- _: &QueueHandle<Self>,
421- ) {
422- if let zwp_linux_dmabuf_v1::Event::Modifier {
423- format,
424- modifier_hi,
425- modifier_lo,
426- } = event
427- && let (Ok(code), Some(host)) = (Fourcc::try_from(format), state.host.as_mut())
428- {
429- let modifier = Modifier::from((u64::from(modifier_hi) << 32) | u64::from(modifier_lo));
430- host.formats.push(Format { code, modifier });
431- }
432- }
433-}
434-
435-impl Dispatch<wl_buffer::WlBuffer, ()> for State {
436- fn event(
437- state: &mut Self,
438- buffer: &wl_buffer::WlBuffer,
439- event: wl_buffer::Event,
440- _: &(),
441- _: &Connection,
442- _: &QueueHandle<Self>,
443- ) {
444- if let wl_buffer::Event::Release = event
445- && let Some(host) = state.host.as_mut()
446- {
447- host.in_flight.retain(|(b, _)| b != buffer);
448- }
449- }
450-}
451-
452-impl Dispatch<wl_callback::WlCallback, ()> for State {
453- fn event(
454- state: &mut Self,
455- _: &wl_callback::WlCallback,
456- event: wl_callback::Event,
457- _: &(),
458- _: &Connection,
459- _: &QueueHandle<Self>,
460- ) {
461- if let wl_callback::Event::Done { .. } = event
462- && let Some(host) = state.host.as_mut()
463- {
464- host.frame_pending = false;
465- }
466- }
467-}
468-
469-delegate_noop!(State: wl_compositor::WlCompositor);
470-delegate_noop!(State: ignore wl_surface::WlSurface);
471-delegate_noop!(State: ignore zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
472-delegate_noop!(State: zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
473-delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1);
474-
475-impl Dispatch<wp_presentation::WpPresentation, ()> for State {
476- fn event(
477- state: &mut Self,
478- _: &wp_presentation::WpPresentation,
479- event: wp_presentation::Event,
480- _: &(),
481- _: &Connection,
482- _: &QueueHandle<Self>,
483- ) {
484- if let wp_presentation::Event::ClockId { clk_id } = event
485- && let Some(host) = state.host.as_mut()
486- {
487- host.monotonic = clk_id == CLOCK_MONOTONIC;
488- }
489- }
490-}
491-
492-impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, ()> for State {
493- fn event(
494- state: &mut Self,
495- _: &wp_presentation_feedback::WpPresentationFeedback,
496- event: wp_presentation_feedback::Event,
497- _: &(),
498- _: &Connection,
499- _: &QueueHandle<Self>,
500- ) {
501- let wp_presentation_feedback::Event::Presented {
502- tv_sec_hi,
503- tv_sec_lo,
504- tv_nsec,
505- refresh,
506- ..
507- } = event
508- else {
509- return;
510- };
511- let Some(host) = state.host.as_mut() else {
512- return;
513- };
514- let secs = (u64::from(tv_sec_hi) << 32) | u64::from(tv_sec_lo);
515- host.last_present_ns = Some(secs * 1_000_000_000 + u64::from(tv_nsec));
516- if refresh > 0 && u64::from(refresh) != host.refresh_ns {
517- host.refresh_ns = u64::from(refresh);
518- let mode = Mode {
519- size: (host.size.w, host.size.h).into(),
520- refresh: (1_000_000_000_000 / u64::from(refresh)) as i32,
521- };
522- host.output
523- .change_current_state(Some(mode), None, None, None);
524- host.output.set_preferred(mode);
525- }
526- }
527-}
Added file: crates/monolith/src/nested/input.rs+105 additions
@@ -0,0 +1,105 @@
1+//! The host seat, forwarded into monolith's own.
2+
3+use crate::state::State;
4+use smithay::backend::input::{Axis, ButtonState, InputTime, KeyState};
5+use smithay::input::keyboard::Keycode;
6+use smithay::input::pointer::AxisFrame;
7+use smithay::utils::Point;
8+use wayland_client::protocol::{wl_keyboard, wl_pointer, wl_seat};
9+use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};
10+
11+impl Dispatch<wl_seat::WlSeat, ()> for State {
12+ fn event(
13+ _: &mut Self,
14+ seat: &wl_seat::WlSeat,
15+ event: wl_seat::Event,
16+ _: &(),
17+ _: &Connection,
18+ qh: &QueueHandle<Self>,
19+ ) {
20+ if let wl_seat::Event::Capabilities {
21+ capabilities: WEnum::Value(caps),
22+ } = event
23+ {
24+ if caps.contains(wl_seat::Capability::Pointer) {
25+ seat.get_pointer(qh, ());
26+ }
27+ if caps.contains(wl_seat::Capability::Keyboard) {
28+ seat.get_keyboard(qh, ());
29+ }
30+ }
31+ }
32+}
33+
34+impl Dispatch<wl_pointer::WlPointer, ()> for State {
35+ fn event(
36+ state: &mut Self,
37+ _: &wl_pointer::WlPointer,
38+ event: wl_pointer::Event,
39+ _: &(),
40+ _: &Connection,
41+ _: &QueueHandle<Self>,
42+ ) {
43+ let time = InputTime::now();
44+ match event {
45+ wl_pointer::Event::Enter {
46+ surface_x,
47+ surface_y,
48+ ..
49+ }
50+ | wl_pointer::Event::Motion {
51+ surface_x,
52+ surface_y,
53+ ..
54+ } => state.pointer_to(Point::from((surface_x, surface_y)), time),
55+ wl_pointer::Event::Button {
56+ button,
57+ state: WEnum::Value(pressed),
58+ ..
59+ } => {
60+ let pressed = match pressed {
61+ wl_pointer::ButtonState::Pressed => ButtonState::Pressed,
62+ _ => ButtonState::Released,
63+ };
64+ state.pointer_button(button, pressed, time);
65+ }
66+ wl_pointer::Event::Axis {
67+ axis: WEnum::Value(axis),
68+ value,
69+ ..
70+ } => {
71+ let axis = match axis {
72+ wl_pointer::Axis::HorizontalScroll => Axis::Horizontal,
73+ _ => Axis::Vertical,
74+ };
75+ state.pointer_axis(AxisFrame::new(time).value(axis, value));
76+ }
77+ wl_pointer::Event::Frame => state.pointer_frame(),
78+ _ => {}
79+ }
80+ }
81+}
82+
83+impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
84+ fn event(
85+ state: &mut Self,
86+ _: &wl_keyboard::WlKeyboard,
87+ event: wl_keyboard::Event,
88+ _: &(),
89+ _: &Connection,
90+ _: &QueueHandle<Self>,
91+ ) {
92+ if let wl_keyboard::Event::Key {
93+ key,
94+ state: WEnum::Value(pressed),
95+ ..
96+ } = event
97+ {
98+ let pressed = match pressed {
99+ wl_keyboard::KeyState::Pressed => KeyState::Pressed,
100+ _ => KeyState::Released,
101+ };
102+ state.key(Keycode::new(key + 8), pressed, InputTime::now());
103+ }
104+ }
105+}
Added file: crates/monolith/src/nested/mod.rs+527 additions
@@ -0,0 +1,527 @@
1+//! Runs monolith as a window inside another Wayland compositor. Frames are
2+//! rendered into dmabufs from our own swapchain and handed to the host
3+//! through linux-dmabuf, so the nested path exercises the same
4+//! render-into-dmabuf code as bare metal.
5+
6+mod input;
7+
8+use crate::state::State;
9+use calloop_wayland_source::WaylandSource;
10+use monolith_layout::Rect;
11+use monolith_render_vk::VulkanAllocator;
12+use smithay::backend::allocator::dmabuf::Dmabuf;
13+use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};
14+use smithay::backend::renderer::Bind;
15+use smithay::backend::renderer::damage::OutputDamageTracker;
16+use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
17+use smithay::reexports::calloop::EventLoop;
18+use smithay::utils::{Logical, Size, Transform};
19+use std::time::Duration;
20+use wayland_client::globals::{GlobalListContents, registry_queue_init};
21+use wayland_client::protocol::{wl_buffer, wl_callback, wl_compositor, wl_registry, wl_surface};
22+use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
23+use wayland_protocols::wp::linux_dmabuf::zv1::client::{
24+ zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,
25+};
26+use wayland_protocols::wp::presentation_time::client::{wp_presentation, wp_presentation_feedback};
27+use wayland_protocols::xdg::decoration::zv1::client::{
28+ zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
29+};
30+use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
31+
32+const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];
33+const DEFAULT_SIZE: (i32, i32) = (1280, 800);
34+const CLOCK_MONOTONIC: u32 = 1;
35+
36+pub struct Host {
37+ pub size: Size<i32, Logical>,
38+ pub output: Output,
39+ conn: Connection,
40+ qh: QueueHandle<State>,
41+ surface: wl_surface::WlSurface,
42+ _xdg_surface: xdg_surface::XdgSurface,
43+ _toplevel: xdg_toplevel::XdgToplevel,
44+ dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
45+ formats: Vec<Format>,
46+ swapchain: Option<Swapchain<VulkanAllocator>>,
47+ in_flight: Vec<(wl_buffer::WlBuffer, Slot<Dmabuf>)>,
48+ damage: OutputDamageTracker,
49+ configured: bool,
50+ frame_pending: bool,
51+ pending_size: Option<Size<i32, Logical>>,
52+ presentation: Option<wp_presentation::WpPresentation>,
53+ /// The host clock is CLOCK_MONOTONIC, so its timestamps share our timeline.
54+ monotonic: bool,
55+ last_present_ns: Option<u64>,
56+ refresh_ns: u64,
57+}
58+
59+impl Host {
60+ /// When a frame committed now will reach the screen, the next host
61+ /// vblank after `now`.
62+ pub fn predict(&self, now: u64) -> u64 {
63+ match self.last_present_ns.filter(|_| self.monotonic) {
64+ Some(last) if self.refresh_ns > 0 && last <= now => {
65+ last + ((now - last) / self.refresh_ns + 1) * self.refresh_ns
66+ }
67+ _ => now,
68+ }
69+ }
70+}
71+
72+/// Destroys the host-side buffer when its swapchain slot goes away.
73+struct HostBuffer(wl_buffer::WlBuffer);
74+
75+impl Drop for HostBuffer {
76+ fn drop(&mut self) {
77+ self.0.destroy();
78+ }
79+}
80+
81+pub fn init(
82+ event_loop: &mut EventLoop<State>,
83+ state: &mut State,
84+) -> Result<(), Box<dyn std::error::Error>> {
85+ let conn = Connection::connect_to_env()?;
86+ let (globals, mut queue) = registry_queue_init::<State>(&conn)?;
87+ let qh = queue.handle();
88+ let compositor: wl_compositor::WlCompositor = globals.bind(&qh, 4..=6, ())?;
89+ let wm_base: xdg_wm_base::XdgWmBase = globals.bind(&qh, 1..=6, ())?;
90+ let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = globals.bind(&qh, 3..=3, ())?;
91+ let presentation = globals
92+ .bind::<wp_presentation::WpPresentation, _, _>(&qh, 1..=2, ())
93+ .ok();
94+ // Dropping a proxy handle does not destroy it, so seat events keep reaching input.rs.
95+ let _ = globals.bind::<wayland_client::protocol::wl_seat::WlSeat, _, _>(&qh, 1..=7, ());
96+
97+ let surface = compositor.create_surface(&qh, ());
98+ let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
99+ let toplevel = xdg_surface.get_toplevel(&qh, ());
100+ toplevel.set_title("monolith".into());
101+ toplevel.set_app_id("monolith".into());
102+ if let Ok(deco) =
103+ globals.bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(&qh, 1..=1, ())
104+ {
105+ deco.get_toplevel_decoration(&toplevel, &qh, ())
106+ .set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
107+ }
108+ surface.commit();
109+
110+ let output = Output::new(
111+ "nested".into(),
112+ PhysicalProperties {
113+ size: (0, 0).into(),
114+ subpixel: Subpixel::Unknown,
115+ make: "monolith".into(),
116+ model: "nested".into(),
117+ serial_number: "0".into(),
118+ },
119+ );
120+ let _global = output.create_global::<State>(&state.display);
121+ let mode = Mode {
122+ size: DEFAULT_SIZE.into(),
123+ refresh: 60_000,
124+ };
125+ output.change_current_state(
126+ Some(mode),
127+ Some(Transform::Normal),
128+ None,
129+ Some((0, 0).into()),
130+ );
131+ output.set_preferred(mode);
132+ state.space.map_output(&output, (0, 0));
133+ let area = Rect::new(
134+ 0.0,
135+ 0.0,
136+ f64::from(DEFAULT_SIZE.0),
137+ f64::from(DEFAULT_SIZE.1),
138+ );
139+ state.output_id = Some(state.layout.add_output("nested", area));
140+
141+ state.host = Some(Host {
142+ size: DEFAULT_SIZE.into(),
143+ damage: OutputDamageTracker::from_output(&output),
144+ output,
145+ conn: conn.clone(),
146+ qh,
147+ surface,
148+ _xdg_surface: xdg_surface,
149+ _toplevel: toplevel,
150+ dmabuf,
151+ formats: Vec::new(),
152+ swapchain: None,
153+ in_flight: Vec::new(),
154+ configured: false,
155+ frame_pending: false,
156+ pending_size: None,
157+ presentation,
158+ monotonic: false,
159+ last_present_ns: None,
160+ refresh_ns: 16_666_667,
161+ });
162+ queue.roundtrip(state)?;
163+ queue.roundtrip(state)?;
164+ WaylandSource::new(conn, queue).insert(event_loop.handle())?;
165+ Ok(())
166+}
167+
168+/// Renders a frame if something changed and the host is ready for one.
169+pub fn render(state: &mut State) {
170+ let State {
171+ host,
172+ renderer,
173+ space,
174+ needs_redraw,
175+ start,
176+ layout,
177+ windows,
178+ anim,
179+ ..
180+ } = state;
181+ let Some(host) = host.as_mut() else { return };
182+ if !host.configured || host.frame_pending || !*needs_redraw {
183+ return;
184+ }
185+ if let Some(size) = host.pending_size.take() {
186+ host.size = size;
187+ let mode = Mode {
188+ size: (size.w, size.h).into(),
189+ refresh: (1_000_000_000_000 / host.refresh_ns) as i32,
190+ };
191+ host.output
192+ .change_current_state(Some(mode), None, None, None);
193+ host.output.set_preferred(mode);
194+ if let Some(sc) = host.swapchain.as_mut() {
195+ sc.resize(size.w as u32, size.h as u32);
196+ }
197+ }
198+ let now = host.predict(crate::state::now_ns());
199+ if host.swapchain.is_none() {
200+ host.swapchain = make_swapchain(renderer, &host.formats, host.size);
201+ }
202+ let Some(swapchain) = host.swapchain.as_mut() else {
203+ return;
204+ };
205+ let slot = match swapchain.acquire() {
206+ Ok(Some(slot)) => slot,
207+ Ok(None) => return,
208+ Err(e) => {
209+ tracing::warn!("failed to allocate a frame buffer: {e}");
210+ return;
211+ }
212+ };
213+ let mut dmabuf = (*slot).clone();
214+ let damage = {
215+ let mut fb = match renderer.bind(&mut dmabuf) {
216+ Ok(fb) => fb,
217+ Err(e) => {
218+ tracing::warn!("cannot render into the swapchain: {e}");
219+ return;
220+ }
221+ };
222+ let elements = crate::scene::build(layout, windows, anim, renderer, now);
223+ match host
224+ .damage
225+ .render_output(renderer, &mut fb, slot.age() as usize, &elements, CLEAR)
226+ {
227+ Ok(result) => {
228+ if let Err(e) = result.sync.wait() {
229+ tracing::warn!("waiting for the frame failed: {e:?}");
230+ }
231+ result.damage.cloned()
232+ }
233+ Err(e) => {
234+ tracing::warn!("rendering failed: {e:?}");
235+ return;
236+ }
237+ }
238+ };
239+ *needs_redraw = anim.active(now);
240+ anim.prune(now);
241+ let elapsed = start.elapsed();
242+ for window in space.elements() {
243+ window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {
244+ Some(host.output.clone())
245+ });
246+ }
247+ let Some(damage) = damage else {
248+ if *needs_redraw {
249+ host.surface.frame(&host.qh, ());
250+ host.surface.commit();
251+ host.frame_pending = true;
252+ let _ = host.conn.flush();
253+ }
254+ return;
255+ };
256+ let buffer = match slot.userdata().get::<HostBuffer>() {
257+ Some(b) => b.0.clone(),
258+ None => {
259+ let b = host_buffer(&host.dmabuf, &host.qh, &dmabuf);
260+ slot.userdata().insert_if_missing(|| HostBuffer(b.clone()));
261+ b
262+ }
263+ };
264+ host.surface.attach(Some(&buffer), 0, 0);
265+ for r in &damage {
266+ host.surface
267+ .damage_buffer(r.loc.x, r.loc.y, r.size.w, r.size.h);
268+ }
269+ host.surface.frame(&host.qh, ());
270+ if let Some(p) = &host.presentation {
271+ p.feedback(&host.surface, &host.qh, ());
272+ }
273+ host.surface.commit();
274+ swapchain.submitted(&slot);
275+ host.in_flight.push((buffer, slot));
276+ host.frame_pending = true;
277+ let _ = host.conn.flush();
278+}
279+
280+fn make_swapchain(
281+ renderer: &monolith_render_vk::VulkanRenderer,
282+ host: &[Format],
283+ size: Size<i32, Logical>,
284+) -> Option<Swapchain<VulkanAllocator>> {
285+ let ours = renderer.render_formats();
286+ for fourcc in [Fourcc::Xrgb8888, Fourcc::Argb8888] {
287+ let modifiers: Vec<Modifier> = host
288+ .iter()
289+ .filter(|f| f.code == fourcc && ours.contains(f))
290+ .map(|f| f.modifier)
291+ .collect();
292+ if !modifiers.is_empty() {
293+ return Some(Swapchain::new(
294+ renderer.allocator(),
295+ size.w as u32,
296+ size.h as u32,
297+ fourcc,
298+ modifiers,
299+ ));
300+ }
301+ }
302+ tracing::error!("the host compositor and the renderer share no dmabuf format");
303+ None
304+}
305+
306+fn host_buffer(
307+ dmabuf: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
308+ qh: &QueueHandle<State>,
309+ buf: &Dmabuf,
310+) -> wl_buffer::WlBuffer {
311+ use smithay::backend::allocator::Buffer;
312+ let params = dmabuf.create_params(qh, ());
313+ let modifier = u64::from(buf.format().modifier);
314+ for (i, ((fd, offset), stride)) in buf
315+ .handles()
316+ .zip(buf.offsets())
317+ .zip(buf.strides())
318+ .enumerate()
319+ {
320+ params.add(
321+ fd,
322+ i as u32,
323+ offset,
324+ stride,
325+ (modifier >> 32) as u32,
326+ modifier as u32,
327+ );
328+ }
329+ let buffer = params.create_immed(
330+ buf.width() as i32,
331+ buf.height() as i32,
332+ buf.format().code as u32,
333+ zwp_linux_buffer_params_v1::Flags::empty(),
334+ qh,
335+ (),
336+ );
337+ params.destroy();
338+ buffer
339+}
340+
341+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
342+ fn event(
343+ _: &mut Self,
344+ _: &wl_registry::WlRegistry,
345+ _: wl_registry::Event,
346+ _: &GlobalListContents,
347+ _: &Connection,
348+ _: &QueueHandle<Self>,
349+ ) {
350+ }
351+}
352+
353+impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
354+ fn event(
355+ _: &mut Self,
356+ base: &xdg_wm_base::XdgWmBase,
357+ event: xdg_wm_base::Event,
358+ _: &(),
359+ _: &Connection,
360+ _: &QueueHandle<Self>,
361+ ) {
362+ if let xdg_wm_base::Event::Ping { serial } = event {
363+ base.pong(serial);
364+ }
365+ }
366+}
367+
368+impl Dispatch<xdg_surface::XdgSurface, ()> for State {
369+ fn event(
370+ state: &mut Self,
371+ surface: &xdg_surface::XdgSurface,
372+ event: xdg_surface::Event,
373+ _: &(),
374+ _: &Connection,
375+ _: &QueueHandle<Self>,
376+ ) {
377+ if let xdg_surface::Event::Configure { serial } = event {
378+ surface.ack_configure(serial);
379+ if let Some(host) = state.host.as_mut() {
380+ host.configured = true;
381+ }
382+ state.needs_redraw = true;
383+ }
384+ }
385+}
386+
387+impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
388+ fn event(
389+ state: &mut Self,
390+ _: &xdg_toplevel::XdgToplevel,
391+ event: xdg_toplevel::Event,
392+ _: &(),
393+ _: &Connection,
394+ _: &QueueHandle<Self>,
395+ ) {
396+ match event {
397+ xdg_toplevel::Event::Configure { width, height, .. } if width > 0 && height > 0 => {
398+ let size = Size::from((width, height));
399+ if let Some(host) = state.host.as_mut()
400+ && host.size != size
401+ {
402+ host.pending_size = Some(size);
403+ host.size = size;
404+ state.resize_output(size);
405+ }
406+ }
407+ xdg_toplevel::Event::Close => state.signal.stop(),
408+ _ => {}
409+ }
410+ }
411+}
412+
413+impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for State {
414+ fn event(
415+ state: &mut Self,
416+ _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
417+ event: zwp_linux_dmabuf_v1::Event,
418+ _: &(),
419+ _: &Connection,
420+ _: &QueueHandle<Self>,
421+ ) {
422+ if let zwp_linux_dmabuf_v1::Event::Modifier {
423+ format,
424+ modifier_hi,
425+ modifier_lo,
426+ } = event
427+ && let (Ok(code), Some(host)) = (Fourcc::try_from(format), state.host.as_mut())
428+ {
429+ let modifier = Modifier::from((u64::from(modifier_hi) << 32) | u64::from(modifier_lo));
430+ host.formats.push(Format { code, modifier });
431+ }
432+ }
433+}
434+
435+impl Dispatch<wl_buffer::WlBuffer, ()> for State {
436+ fn event(
437+ state: &mut Self,
438+ buffer: &wl_buffer::WlBuffer,
439+ event: wl_buffer::Event,
440+ _: &(),
441+ _: &Connection,
442+ _: &QueueHandle<Self>,
443+ ) {
444+ if let wl_buffer::Event::Release = event
445+ && let Some(host) = state.host.as_mut()
446+ {
447+ host.in_flight.retain(|(b, _)| b != buffer);
448+ }
449+ }
450+}
451+
452+impl Dispatch<wl_callback::WlCallback, ()> for State {
453+ fn event(
454+ state: &mut Self,
455+ _: &wl_callback::WlCallback,
456+ event: wl_callback::Event,
457+ _: &(),
458+ _: &Connection,
459+ _: &QueueHandle<Self>,
460+ ) {
461+ if let wl_callback::Event::Done { .. } = event
462+ && let Some(host) = state.host.as_mut()
463+ {
464+ host.frame_pending = false;
465+ }
466+ }
467+}
468+
469+delegate_noop!(State: wl_compositor::WlCompositor);
470+delegate_noop!(State: ignore wl_surface::WlSurface);
471+delegate_noop!(State: ignore zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
472+delegate_noop!(State: zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
473+delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1);
474+
475+impl Dispatch<wp_presentation::WpPresentation, ()> for State {
476+ fn event(
477+ state: &mut Self,
478+ _: &wp_presentation::WpPresentation,
479+ event: wp_presentation::Event,
480+ _: &(),
481+ _: &Connection,
482+ _: &QueueHandle<Self>,
483+ ) {
484+ if let wp_presentation::Event::ClockId { clk_id } = event
485+ && let Some(host) = state.host.as_mut()
486+ {
487+ host.monotonic = clk_id == CLOCK_MONOTONIC;
488+ }
489+ }
490+}
491+
492+impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, ()> for State {
493+ fn event(
494+ state: &mut Self,
495+ _: &wp_presentation_feedback::WpPresentationFeedback,
496+ event: wp_presentation_feedback::Event,
497+ _: &(),
498+ _: &Connection,
499+ _: &QueueHandle<Self>,
500+ ) {
501+ let wp_presentation_feedback::Event::Presented {
502+ tv_sec_hi,
503+ tv_sec_lo,
504+ tv_nsec,
505+ refresh,
506+ ..
507+ } = event
508+ else {
509+ return;
510+ };
511+ let Some(host) = state.host.as_mut() else {
512+ return;
513+ };
514+ let secs = (u64::from(tv_sec_hi) << 32) | u64::from(tv_sec_lo);
515+ host.last_present_ns = Some(secs * 1_000_000_000 + u64::from(tv_nsec));
516+ if refresh > 0 && u64::from(refresh) != host.refresh_ns {
517+ host.refresh_ns = u64::from(refresh);
518+ let mode = Mode {
519+ size: (host.size.w, host.size.h).into(),
520+ refresh: (1_000_000_000_000 / u64::from(refresh)) as i32,
521+ };
522+ host.output
523+ .change_current_state(Some(mode), None, None, None);
524+ host.output.set_preferred(mode);
525+ }
526+ }
527+}
Modified file: crates/monolith/src/state.rs+3 additions
@@ -56,6 +56,8 @@ pub struct State {
5656 pub suppressed: Vec<Keycode>,
5757 pub move_inverted: bool,
5858 pub drag: Option<crate::input::Drag>,
59+ /// Global logical position, shared by every backend.
60+ pub pointer_pos: Point<f64, Logical>,
5961 pub ipc: crate::ipc::Server,
6062 pub launches: crate::launch::Launches,
6163 pub workspaces: WorkspaceState,
@@ -140,6 +142,7 @@ impl State {
140142 suppressed: Vec::new(),
141143 move_inverted: false,
142144 drag: None,
145+ pointer_pos: Point::default(),
143146 ipc: crate::ipc::Server::default(),
144147 launches: Default::default(),
145148 workspaces: WorkspaceState::new::<State>(&dh),