atagen

monolith

public
Star0
Watch0

Commit d4a64ce87164

tarball

atagen <boss@atagen.co>2026-10-16 09:46 UTC

9 files changed+1066-18
monolith: run nested on the Vulkan renderer

The host window is fed through a linux-dmabuf swapchain allocated by render-vk, so the nested path exercises the same buffer sharing the DRM backend will need. Placement is plain side-by-side columns until the planner is wired in at M4. Shm clients go through ImportMemWl, which copies on every commit for now.
Modified file: Cargo.lock+30 additions−7 deletions
@@ -100,6 +100,18 @@ dependencies = [
100100 ]
101101
102102 [[package]]
103+name = "calloop-wayland-source"
104+version = "0.4.1"
105+source = "registry+https://github.com/rust-lang/crates.io-index"
106+checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
107+dependencies = [
108+ "calloop",
109+ "rustix",
110+ "wayland-backend",
111+ "wayland-client",
112+]
113+
114+[[package]]
103115 name = "cc"
104116 version = "1.8.0"
105117 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -376,16 +388,14 @@ dependencies = [
376388 name = "monolith"
377389 version = "0.0.0"
378390 dependencies = [
379- "monolith-config",
380- "monolith-ipc",
381- "monolith-layout",
382- "monolith-motion",
383- "monolith-planner",
384- "monolith-protocols",
391+ "calloop-wayland-source",
385392 "monolith-render-vk",
386- "monolith-testkit",
387393 "smithay",
394+ "tracing",
395+ "tracing-subscriber",
388396 "tracy-client",
397+ "wayland-client",
398+ "wayland-protocols",
389399 ]
390400
391401 [[package]]
@@ -1008,6 +1018,18 @@ dependencies = [
10081018 ]
10091019
10101020 [[package]]
1021+name = "wayland-client"
1022+version = "0.31.15"
1023+source = "registry+https://github.com/rust-lang/crates.io-index"
1024+checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
1025+dependencies = [
1026+ "bitflags",
1027+ "rustix",
1028+ "wayland-backend",
1029+ "wayland-scanner",
1030+]
1031+
1032+[[package]]
10111033 name = "wayland-protocols"
10121034 version = "0.32.13"
10131035 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1015,6 +1037,7 @@ checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
10151037 dependencies = [
10161038 "bitflags",
10171039 "wayland-backend",
1040+ "wayland-client",
10181041 "wayland-scanner",
10191042 "wayland-server",
10201043 ]
Modified file: crates/monolith/Cargo.toml+6 additions−10 deletions
@@ -12,15 +12,11 @@ workspace = true
1212 profile = ["dep:tracy-client", "tracy-client/enable"]
1313
1414 [dependencies]
15-monolith-config.workspace = true
16-monolith-ipc.workspace = true
17-monolith-layout.workspace = true
18-monolith-motion.workspace = true
19-monolith-planner.workspace = true
20-monolith-protocols.workspace = true
15+calloop-wayland-source = "0.4"
2116 monolith-render-vk.workspace = true
22-smithay.workspace = true
17+smithay = { workspace = true, features = ["desktop"] }
18+tracing = "0.1"
19+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2320 tracy-client = { workspace = true, optional = true }
24-
25-[dev-dependencies]
26-monolith-testkit.workspace = true
21+wayland-client = "0.31"
22+wayland-protocols = { version = "0.32", features = ["client", "unstable", "staging"] }
Added file: crates/monolith/src/handlers.rs+206 additions
@@ -0,0 +1,206 @@
1+use crate::state::{ClientState, State};
2+use smithay::backend::allocator::dmabuf::Dmabuf;
3+use smithay::backend::renderer::ImportDma;
4+use smithay::backend::renderer::utils::on_commit_buffer_handler;
5+use smithay::desktop::{PopupKind, Window};
6+use smithay::input::dnd::{DndGrabHandler, GrabType, Source};
7+use smithay::input::pointer::CursorImageStatus;
8+use smithay::input::{Seat, SeatHandler, SeatState};
9+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
10+use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
11+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
12+use smithay::reexports::wayland_server::{Client, Resource};
13+use smithay::utils::Serial;
14+use smithay::wayland::buffer::BufferHandler;
15+use smithay::wayland::compositor::{
16+ CompositorClientState, CompositorHandler, CompositorState, get_parent, is_sync_subsurface,
17+};
18+use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
19+use smithay::wayland::output::OutputHandler;
20+use smithay::wayland::selection::SelectionHandler;
21+use smithay::wayland::selection::data_device::{
22+ DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
23+};
24+use smithay::wayland::shell::xdg::{
25+ PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
26+};
27+use smithay::wayland::shm::{ShmHandler, ShmState};
28+
29+impl CompositorHandler for State {
30+ fn compositor_state(&mut self) -> &mut CompositorState {
31+ &mut self.compositor_state
32+ }
33+
34+ fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
35+ &client
36+ .get_data::<ClientState>()
37+ .expect("every client has ClientState")
38+ .compositor_state
39+ }
40+
41+ fn commit(&mut self, surface: &WlSurface) {
42+ on_commit_buffer_handler::<Self>(surface);
43+ if !is_sync_subsurface(surface) {
44+ let mut root = surface.clone();
45+ while let Some(parent) = get_parent(&root) {
46+ root = parent;
47+ }
48+ if let Some(window) = self
49+ .space
50+ .elements()
51+ .find(|w| w.toplevel().is_some_and(|t| t.wl_surface() == &root))
52+ {
53+ window.on_commit();
54+ }
55+ }
56+ if let Some(window) = self
57+ .space
58+ .elements()
59+ .find(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface))
60+ .cloned()
61+ && let Some(toplevel) = window.toplevel()
62+ && !toplevel.is_initial_configure_sent()
63+ {
64+ toplevel.send_configure();
65+ }
66+ self.popups.commit(surface);
67+ if let Some(PopupKind::Xdg(popup)) = self.popups.find_popup(surface)
68+ && !popup.is_initial_configure_sent()
69+ {
70+ let _ = popup.send_configure();
71+ }
72+ self.needs_redraw = true;
73+ }
74+}
75+
76+impl BufferHandler for State {
77+ fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
78+}
79+
80+impl ShmHandler for State {
81+ fn shm_state(&self) -> &ShmState {
82+ &self.shm_state
83+ }
84+}
85+
86+impl DmabufHandler for State {
87+ fn dmabuf_state(&mut self) -> &mut DmabufState {
88+ &mut self.dmabuf_state
89+ }
90+
91+ fn dmabuf_imported(
92+ &mut self,
93+ _global: &DmabufGlobal,
94+ dmabuf: Dmabuf,
95+ notifier: ImportNotifier,
96+ ) {
97+ if self.renderer.import_dmabuf(&dmabuf, None).is_ok() {
98+ let _ = notifier.successful::<State>();
99+ } else {
100+ notifier.failed();
101+ }
102+ }
103+}
104+
105+impl XdgShellHandler for State {
106+ fn xdg_shell_state(&mut self) -> &mut XdgShellState {
107+ &mut self.xdg_shell_state
108+ }
109+
110+ fn new_toplevel(&mut self, surface: ToplevelSurface) {
111+ let focus = surface.wl_surface().clone();
112+ self.space
113+ .map_element(Window::new_wayland_window(surface), (0, 0), true);
114+ self.relayout();
115+ if let Some(keyboard) = self.seat.get_keyboard() {
116+ keyboard.set_focus(self, Some(focus), Serial::from(0));
117+ }
118+ }
119+
120+ fn toplevel_destroyed(&mut self, _surface: ToplevelSurface) {
121+ self.relayout();
122+ }
123+
124+ fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
125+ surface.with_pending_state(|s| s.geometry = positioner.get_geometry());
126+ let _ = self.popups.track_popup(PopupKind::Xdg(surface));
127+ }
128+
129+ fn reposition_request(
130+ &mut self,
131+ surface: PopupSurface,
132+ positioner: PositionerState,
133+ token: u32,
134+ ) {
135+ surface.with_pending_state(|s| {
136+ s.geometry = positioner.get_geometry();
137+ s.positioner = positioner;
138+ });
139+ surface.send_repositioned(token);
140+ }
141+
142+ fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}
143+}
144+
145+impl SeatHandler for State {
146+ type KeyboardFocus = WlSurface;
147+ type PointerFocus = WlSurface;
148+ type TouchFocus = WlSurface;
149+
150+ fn seat_state(&mut self) -> &mut SeatState<State> {
151+ &mut self.seat_state
152+ }
153+
154+ fn cursor_image(&mut self, _seat: &Seat<Self>, _image: CursorImageStatus) {}
155+
156+ fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
157+ let client = focused.and_then(|s| self.display.get_client(s.id()).ok());
158+ set_data_device_focus(&self.display, seat, client);
159+ }
160+}
161+
162+impl SelectionHandler for State {
163+ type SelectionUserData = ();
164+}
165+
166+impl DataDeviceHandler for State {
167+ fn data_device_state(&mut self) -> &mut DataDeviceState {
168+ &mut self.data_device_state
169+ }
170+}
171+
172+impl DndGrabHandler for State {}
173+
174+impl WaylandDndGrabHandler for State {
175+ fn dnd_requested<S: Source>(
176+ &mut self,
177+ source: S,
178+ _icon: Option<WlSurface>,
179+ _seat: Seat<Self>,
180+ _serial: Serial,
181+ _type: GrabType,
182+ ) {
183+ source.cancel();
184+ }
185+}
186+
187+impl OutputHandler for State {}
188+
189+impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State {
190+ fn new_constraint(
191+ &mut self,
192+ _surface: &WlSurface,
193+ _pointer: &smithay::input::pointer::PointerHandle<Self>,
194+ ) {
195+ }
196+
197+ fn cursor_position_hint(
198+ &mut self,
199+ _surface: &WlSurface,
200+ _pointer: &smithay::input::pointer::PointerHandle<Self>,
201+ _location: smithay::utils::Point<f64, smithay::utils::Logical>,
202+ ) {
203+ }
204+}
205+
206+smithay::delegate_dispatch2!(State);
Added file: crates/monolith/src/input.rs+163 additions
@@ -0,0 +1,163 @@
1+//! Host input forwarded into monolith's seat.
2+
3+use crate::state::State;
4+use smithay::backend::input::{Axis, ButtonState, InputTime, KeyState};
5+use smithay::input::keyboard::{FilterResult, Keycode};
6+use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
7+use smithay::utils::{Point, SERIAL_COUNTER};
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 Some(pointer) = state.seat.get_pointer() else {
44+ return;
45+ };
46+ let time = InputTime::now();
47+ match event {
48+ wl_pointer::Event::Enter {
49+ surface_x,
50+ surface_y,
51+ ..
52+ }
53+ | wl_pointer::Event::Motion {
54+ surface_x,
55+ surface_y,
56+ ..
57+ } => {
58+ let pos = Point::from((surface_x, surface_y));
59+ if let Some(host) = state.host.as_mut() {
60+ host.pointer_pos = pos;
61+ }
62+ let under = state.surface_under(pos);
63+ pointer.motion(
64+ state,
65+ under,
66+ &MotionEvent {
67+ location: pos,
68+ serial: SERIAL_COUNTER.next_serial(),
69+ time,
70+ },
71+ );
72+ }
73+ wl_pointer::Event::Button {
74+ button,
75+ state: WEnum::Value(pressed),
76+ ..
77+ } => {
78+ let serial = SERIAL_COUNTER.next_serial();
79+ let button_state = match pressed {
80+ wl_pointer::ButtonState::Pressed => ButtonState::Pressed,
81+ _ => ButtonState::Released,
82+ };
83+ if button_state == ButtonState::Pressed {
84+ focus_under_pointer(state);
85+ }
86+ pointer.button(
87+ state,
88+ &ButtonEvent {
89+ button,
90+ state: button_state,
91+ serial,
92+ time,
93+ },
94+ );
95+ }
96+ wl_pointer::Event::Axis {
97+ axis: WEnum::Value(axis),
98+ value,
99+ ..
100+ } => {
101+ let axis = match axis {
102+ wl_pointer::Axis::HorizontalScroll => Axis::Horizontal,
103+ _ => Axis::Vertical,
104+ };
105+ pointer.axis(state, AxisFrame::new(time).value(axis, value));
106+ }
107+ wl_pointer::Event::Frame => pointer.frame(state),
108+ _ => {}
109+ }
110+ }
111+}
112+
113+impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
114+ fn event(
115+ state: &mut Self,
116+ _: &wl_keyboard::WlKeyboard,
117+ event: wl_keyboard::Event,
118+ _: &(),
119+ _: &Connection,
120+ _: &QueueHandle<Self>,
121+ ) {
122+ let Some(keyboard) = state.seat.get_keyboard() else {
123+ return;
124+ };
125+ if let wl_keyboard::Event::Key {
126+ key,
127+ state: WEnum::Value(pressed),
128+ ..
129+ } = event
130+ {
131+ let key_state = match pressed {
132+ wl_keyboard::KeyState::Pressed => KeyState::Pressed,
133+ _ => KeyState::Released,
134+ };
135+ let time = InputTime::now();
136+ keyboard.input::<(), _>(
137+ state,
138+ Keycode::new(key + 8),
139+ key_state,
140+ SERIAL_COUNTER.next_serial(),
141+ time,
142+ |_, _, _| FilterResult::Forward,
143+ );
144+ }
145+ }
146+}
147+
148+fn focus_under_pointer(state: &mut State) {
149+ let Some(pos) = state.host.as_ref().map(|h| h.pointer_pos) else {
150+ return;
151+ };
152+ let Some((window, _)) = state.space.element_under(pos).map(|(w, l)| (w.clone(), l)) else {
153+ return;
154+ };
155+ state.space.raise_element(&window, true);
156+ if let (Some(keyboard), Some(toplevel)) = (state.seat.get_keyboard(), window.toplevel()) {
157+ keyboard.set_focus(
158+ state,
159+ Some(toplevel.wl_surface().clone()),
160+ SERIAL_COUNTER.next_serial(),
161+ );
162+ }
163+}
Modified file: crates/monolith/src/main.rs+34 additions−1 deletions
@@ -1 +1,34 @@
1-fn main() {}
1+mod handlers;
2+mod input;
3+mod nested;
4+mod state;
5+
6+use smithay::reexports::calloop::EventLoop;
7+use smithay::reexports::wayland_server::Display;
8+use state::State;
9+use std::process::Command;
10+use tracing_subscriber::EnvFilter;
11+
12+fn main() -> Result<(), Box<dyn std::error::Error>> {
13+ tracing_subscriber::fmt()
14+ .with_env_filter(
15+ EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
16+ )
17+ .init();
18+ let mut event_loop: EventLoop<State> = EventLoop::try_new()?;
19+ let display: Display<State> = Display::new()?;
20+ let mut state = State::new(&mut event_loop, display)?;
21+ nested::init(&mut event_loop, &mut state)?;
22+ tracing::info!(socket = ?state.socket_name, device = state.renderer.device_name(), "monolith running nested");
23+
24+ let args: Vec<String> = std::env::args().skip(1).collect();
25+ if let Some((program, rest)) = args.split_first() {
26+ Command::new(program)
27+ .args(rest)
28+ .env("WAYLAND_DISPLAY", &state.socket_name)
29+ .spawn()?;
30+ }
31+
32+ event_loop.run(None, &mut state, |state| state.after_dispatch())?;
33+ Ok(())
34+}
Added file: crates/monolith/src/nested.rs+431 additions
@@ -0,0 +1,431 @@
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_render_vk::VulkanAllocator;
9+use smithay::backend::allocator::dmabuf::Dmabuf;
10+use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};
11+use smithay::backend::renderer::Bind;
12+use smithay::backend::renderer::damage::OutputDamageTracker;
13+use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
14+use smithay::desktop::space::render_output;
15+use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
16+use smithay::reexports::calloop::EventLoop;
17+use smithay::utils::{Logical, Point, Size, Transform};
18+use std::time::Duration;
19+use wayland_client::globals::{GlobalListContents, registry_queue_init};
20+use wayland_client::protocol::{wl_buffer, wl_callback, wl_compositor, wl_registry, wl_surface};
21+use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
22+use wayland_protocols::wp::linux_dmabuf::zv1::client::{
23+ zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,
24+};
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+
33+pub struct Host {
34+ pub size: Size<i32, Logical>,
35+ pub pointer_pos: Point<f64, Logical>,
36+ pub output: Output,
37+ conn: Connection,
38+ qh: QueueHandle<State>,
39+ surface: wl_surface::WlSurface,
40+ _xdg_surface: xdg_surface::XdgSurface,
41+ _toplevel: xdg_toplevel::XdgToplevel,
42+ dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
43+ formats: Vec<Format>,
44+ swapchain: Option<Swapchain<VulkanAllocator>>,
45+ in_flight: Vec<(wl_buffer::WlBuffer, Slot<Dmabuf>)>,
46+ damage: OutputDamageTracker,
47+ configured: bool,
48+ frame_pending: bool,
49+ pending_size: Option<Size<i32, Logical>>,
50+}
51+
52+/// Destroys the host-side buffer when its swapchain slot goes away.
53+struct HostBuffer(wl_buffer::WlBuffer);
54+
55+impl Drop for HostBuffer {
56+ fn drop(&mut self) {
57+ self.0.destroy();
58+ }
59+}
60+
61+pub fn init(
62+ event_loop: &mut EventLoop<State>,
63+ state: &mut State,
64+) -> Result<(), Box<dyn std::error::Error>> {
65+ let conn = Connection::connect_to_env()?;
66+ let (globals, mut queue) = registry_queue_init::<State>(&conn)?;
67+ let qh = queue.handle();
68+ let compositor: wl_compositor::WlCompositor = globals.bind(&qh, 4..=6, ())?;
69+ let wm_base: xdg_wm_base::XdgWmBase = globals.bind(&qh, 1..=6, ())?;
70+ let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 = globals.bind(&qh, 3..=3, ())?;
71+ // Dropping a proxy handle does not destroy it, so seat events keep reaching input.rs.
72+ let _ = globals.bind::<wayland_client::protocol::wl_seat::WlSeat, _, _>(&qh, 1..=7, ());
73+
74+ let surface = compositor.create_surface(&qh, ());
75+ let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
76+ let toplevel = xdg_surface.get_toplevel(&qh, ());
77+ toplevel.set_title("monolith".into());
78+ toplevel.set_app_id("monolith".into());
79+ if let Ok(deco) =
80+ globals.bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(&qh, 1..=1, ())
81+ {
82+ deco.get_toplevel_decoration(&toplevel, &qh, ())
83+ .set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
84+ }
85+ surface.commit();
86+
87+ let output = Output::new(
88+ "nested".into(),
89+ PhysicalProperties {
90+ size: (0, 0).into(),
91+ subpixel: Subpixel::Unknown,
92+ make: "monolith".into(),
93+ model: "nested".into(),
94+ serial_number: "0".into(),
95+ },
96+ );
97+ let _global = output.create_global::<State>(&state.display);
98+ let mode = Mode {
99+ size: DEFAULT_SIZE.into(),
100+ refresh: 60_000,
101+ };
102+ output.change_current_state(
103+ Some(mode),
104+ Some(Transform::Normal),
105+ None,
106+ Some((0, 0).into()),
107+ );
108+ output.set_preferred(mode);
109+ state.space.map_output(&output, (0, 0));
110+
111+ state.host = Some(Host {
112+ size: DEFAULT_SIZE.into(),
113+ pointer_pos: (0.0, 0.0).into(),
114+ damage: OutputDamageTracker::from_output(&output),
115+ output,
116+ conn: conn.clone(),
117+ qh,
118+ surface,
119+ _xdg_surface: xdg_surface,
120+ _toplevel: toplevel,
121+ dmabuf,
122+ formats: Vec::new(),
123+ swapchain: None,
124+ in_flight: Vec::new(),
125+ configured: false,
126+ frame_pending: false,
127+ pending_size: None,
128+ });
129+ queue.roundtrip(state)?;
130+ queue.roundtrip(state)?;
131+ WaylandSource::new(conn, queue).insert(event_loop.handle())?;
132+ Ok(())
133+}
134+
135+/// Renders a frame if something changed and the host is ready for one.
136+pub fn render(state: &mut State) {
137+ let State {
138+ host,
139+ renderer,
140+ space,
141+ needs_redraw,
142+ start,
143+ ..
144+ } = state;
145+ let Some(host) = host.as_mut() else { return };
146+ if !host.configured || host.frame_pending || !*needs_redraw {
147+ return;
148+ }
149+ if let Some(size) = host.pending_size.take() {
150+ host.size = size;
151+ let mode = Mode {
152+ size: (size.w, size.h).into(),
153+ refresh: 60_000,
154+ };
155+ host.output
156+ .change_current_state(Some(mode), None, None, None);
157+ host.output.set_preferred(mode);
158+ if let Some(sc) = host.swapchain.as_mut() {
159+ sc.resize(size.w as u32, size.h as u32);
160+ }
161+ }
162+ if host.swapchain.is_none() {
163+ host.swapchain = make_swapchain(renderer, &host.formats, host.size);
164+ }
165+ let Some(swapchain) = host.swapchain.as_mut() else {
166+ return;
167+ };
168+ let slot = match swapchain.acquire() {
169+ Ok(Some(slot)) => slot,
170+ Ok(None) => return,
171+ Err(e) => {
172+ tracing::warn!("failed to allocate a frame buffer: {e}");
173+ return;
174+ }
175+ };
176+ let mut dmabuf = (*slot).clone();
177+ let damage = {
178+ let mut fb = match renderer.bind(&mut dmabuf) {
179+ Ok(fb) => fb,
180+ Err(e) => {
181+ tracing::warn!("cannot render into the swapchain: {e}");
182+ return;
183+ }
184+ };
185+ let elements: &[WaylandSurfaceRenderElement<monolith_render_vk::VulkanRenderer>] = &[];
186+ match render_output(
187+ &host.output,
188+ renderer,
189+ &mut fb,
190+ 1.0,
191+ slot.age() as usize,
192+ [&*space],
193+ elements,
194+ &mut host.damage,
195+ CLEAR,
196+ ) {
197+ Ok(result) => {
198+ if let Err(e) = result.sync.wait() {
199+ tracing::warn!("waiting for the frame failed: {e:?}");
200+ }
201+ result.damage.cloned()
202+ }
203+ Err(e) => {
204+ tracing::warn!("rendering failed: {e:?}");
205+ return;
206+ }
207+ }
208+ };
209+ *needs_redraw = false;
210+ let elapsed = start.elapsed();
211+ for window in space.elements() {
212+ window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {
213+ Some(host.output.clone())
214+ });
215+ }
216+ let Some(damage) = damage else { return };
217+ let buffer = match slot.userdata().get::<HostBuffer>() {
218+ Some(b) => b.0.clone(),
219+ None => {
220+ let b = host_buffer(&host.dmabuf, &host.qh, &dmabuf);
221+ slot.userdata().insert_if_missing(|| HostBuffer(b.clone()));
222+ b
223+ }
224+ };
225+ host.surface.attach(Some(&buffer), 0, 0);
226+ for r in &damage {
227+ host.surface
228+ .damage_buffer(r.loc.x, r.loc.y, r.size.w, r.size.h);
229+ }
230+ host.surface.frame(&host.qh, ());
231+ host.surface.commit();
232+ swapchain.submitted(&slot);
233+ host.in_flight.push((buffer, slot));
234+ host.frame_pending = true;
235+ let _ = host.conn.flush();
236+}
237+
238+fn make_swapchain(
239+ renderer: &monolith_render_vk::VulkanRenderer,
240+ host: &[Format],
241+ size: Size<i32, Logical>,
242+) -> Option<Swapchain<VulkanAllocator>> {
243+ let ours = renderer.render_formats();
244+ for fourcc in [Fourcc::Xrgb8888, Fourcc::Argb8888] {
245+ let modifiers: Vec<Modifier> = host
246+ .iter()
247+ .filter(|f| f.code == fourcc && ours.contains(f))
248+ .map(|f| f.modifier)
249+ .collect();
250+ if !modifiers.is_empty() {
251+ return Some(Swapchain::new(
252+ renderer.allocator(),
253+ size.w as u32,
254+ size.h as u32,
255+ fourcc,
256+ modifiers,
257+ ));
258+ }
259+ }
260+ tracing::error!("the host compositor and the renderer share no dmabuf format");
261+ None
262+}
263+
264+fn host_buffer(
265+ dmabuf: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
266+ qh: &QueueHandle<State>,
267+ buf: &Dmabuf,
268+) -> wl_buffer::WlBuffer {
269+ use smithay::backend::allocator::Buffer;
270+ let params = dmabuf.create_params(qh, ());
271+ let modifier = u64::from(buf.format().modifier);
272+ for (i, ((fd, offset), stride)) in buf
273+ .handles()
274+ .zip(buf.offsets())
275+ .zip(buf.strides())
276+ .enumerate()
277+ {
278+ params.add(
279+ fd,
280+ i as u32,
281+ offset,
282+ stride,
283+ (modifier >> 32) as u32,
284+ modifier as u32,
285+ );
286+ }
287+ let buffer = params.create_immed(
288+ buf.width() as i32,
289+ buf.height() as i32,
290+ buf.format().code as u32,
291+ zwp_linux_buffer_params_v1::Flags::empty(),
292+ qh,
293+ (),
294+ );
295+ params.destroy();
296+ buffer
297+}
298+
299+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
300+ fn event(
301+ _: &mut Self,
302+ _: &wl_registry::WlRegistry,
303+ _: wl_registry::Event,
304+ _: &GlobalListContents,
305+ _: &Connection,
306+ _: &QueueHandle<Self>,
307+ ) {
308+ }
309+}
310+
311+impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
312+ fn event(
313+ _: &mut Self,
314+ base: &xdg_wm_base::XdgWmBase,
315+ event: xdg_wm_base::Event,
316+ _: &(),
317+ _: &Connection,
318+ _: &QueueHandle<Self>,
319+ ) {
320+ if let xdg_wm_base::Event::Ping { serial } = event {
321+ base.pong(serial);
322+ }
323+ }
324+}
325+
326+impl Dispatch<xdg_surface::XdgSurface, ()> for State {
327+ fn event(
328+ state: &mut Self,
329+ surface: &xdg_surface::XdgSurface,
330+ event: xdg_surface::Event,
331+ _: &(),
332+ _: &Connection,
333+ _: &QueueHandle<Self>,
334+ ) {
335+ if let xdg_surface::Event::Configure { serial } = event {
336+ surface.ack_configure(serial);
337+ if let Some(host) = state.host.as_mut() {
338+ host.configured = true;
339+ }
340+ state.needs_redraw = true;
341+ }
342+ }
343+}
344+
345+impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
346+ fn event(
347+ state: &mut Self,
348+ _: &xdg_toplevel::XdgToplevel,
349+ event: xdg_toplevel::Event,
350+ _: &(),
351+ _: &Connection,
352+ _: &QueueHandle<Self>,
353+ ) {
354+ match event {
355+ xdg_toplevel::Event::Configure { width, height, .. } if width > 0 && height > 0 => {
356+ let size = Size::from((width, height));
357+ if let Some(host) = state.host.as_mut()
358+ && host.size != size
359+ {
360+ host.pending_size = Some(size);
361+ host.size = size;
362+ state.relayout();
363+ }
364+ }
365+ xdg_toplevel::Event::Close => state.signal.stop(),
366+ _ => {}
367+ }
368+ }
369+}
370+
371+impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for State {
372+ fn event(
373+ state: &mut Self,
374+ _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
375+ event: zwp_linux_dmabuf_v1::Event,
376+ _: &(),
377+ _: &Connection,
378+ _: &QueueHandle<Self>,
379+ ) {
380+ if let zwp_linux_dmabuf_v1::Event::Modifier {
381+ format,
382+ modifier_hi,
383+ modifier_lo,
384+ } = event
385+ && let (Ok(code), Some(host)) = (Fourcc::try_from(format), state.host.as_mut())
386+ {
387+ let modifier = Modifier::from((u64::from(modifier_hi) << 32) | u64::from(modifier_lo));
388+ host.formats.push(Format { code, modifier });
389+ }
390+ }
391+}
392+
393+impl Dispatch<wl_buffer::WlBuffer, ()> for State {
394+ fn event(
395+ state: &mut Self,
396+ buffer: &wl_buffer::WlBuffer,
397+ event: wl_buffer::Event,
398+ _: &(),
399+ _: &Connection,
400+ _: &QueueHandle<Self>,
401+ ) {
402+ if let wl_buffer::Event::Release = event
403+ && let Some(host) = state.host.as_mut()
404+ {
405+ host.in_flight.retain(|(b, _)| b != buffer);
406+ }
407+ }
408+}
409+
410+impl Dispatch<wl_callback::WlCallback, ()> for State {
411+ fn event(
412+ state: &mut Self,
413+ _: &wl_callback::WlCallback,
414+ event: wl_callback::Event,
415+ _: &(),
416+ _: &Connection,
417+ _: &QueueHandle<Self>,
418+ ) {
419+ if let wl_callback::Event::Done { .. } = event
420+ && let Some(host) = state.host.as_mut()
421+ {
422+ host.frame_pending = false;
423+ }
424+ }
425+}
426+
427+delegate_noop!(State: wl_compositor::WlCompositor);
428+delegate_noop!(State: ignore wl_surface::WlSurface);
429+delegate_noop!(State: ignore zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
430+delegate_noop!(State: zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
431+delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1);
Added file: crates/monolith/src/state.rs+159 additions
@@ -0,0 +1,159 @@
1+use crate::nested::Host;
2+use monolith_render_vk::VulkanRenderer;
3+use smithay::backend::renderer::ImportDma;
4+use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
5+use smithay::input::{Seat, SeatState};
6+use smithay::reexports::calloop::generic::Generic;
7+use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction};
8+use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
9+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
10+use smithay::reexports::wayland_server::{Display, DisplayHandle};
11+use smithay::utils::{Logical, Point, Size};
12+use smithay::wayland::compositor::{CompositorClientState, CompositorState};
13+use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};
14+use smithay::wayland::output::OutputManagerState;
15+use smithay::wayland::selection::data_device::DataDeviceState;
16+use smithay::wayland::shell::xdg::XdgShellState;
17+use smithay::wayland::shm::ShmState;
18+use smithay::wayland::socket::ListeningSocketSource;
19+use std::ffi::OsString;
20+use std::sync::Arc;
21+use std::time::Instant;
22+
23+pub struct State {
24+ pub start: Instant,
25+ pub socket_name: OsString,
26+ pub display: DisplayHandle,
27+ pub signal: LoopSignal,
28+ pub renderer: VulkanRenderer,
29+ pub space: Space<Window>,
30+ pub popups: PopupManager,
31+ pub host: Option<Host>,
32+ pub needs_redraw: bool,
33+
34+ pub compositor_state: CompositorState,
35+ pub xdg_shell_state: XdgShellState,
36+ pub shm_state: ShmState,
37+ pub dmabuf_state: DmabufState,
38+ pub _dmabuf_global: DmabufGlobal,
39+ pub _output_manager_state: OutputManagerState,
40+ pub seat_state: SeatState<State>,
41+ pub data_device_state: DataDeviceState,
42+ pub seat: Seat<State>,
43+}
44+
45+impl State {
46+ pub fn new(
47+ event_loop: &mut EventLoop<State>,
48+ display: Display<State>,
49+ ) -> Result<Self, Box<dyn std::error::Error>> {
50+ let dh = display.handle();
51+ let renderer = VulkanRenderer::new()?;
52+ let mut dmabuf_state = DmabufState::new();
53+ let dmabuf_global = dmabuf_state.create_global::<State>(&dh, renderer.dmabuf_formats());
54+ let mut seat_state = SeatState::new();
55+ let mut seat = seat_state.new_wl_seat(&dh, "seat0");
56+ seat.add_keyboard(Default::default(), 200, 25)?;
57+ seat.add_pointer();
58+
59+ let listener = ListeningSocketSource::new_auto()?;
60+ let socket_name = listener.socket_name().to_os_string();
61+ let handle = event_loop.handle();
62+ handle.insert_source(listener, |stream, _, state| {
63+ if let Err(e) = state
64+ .display
65+ .insert_client(stream, Arc::new(ClientState::default()))
66+ {
67+ tracing::warn!("failed to accept client: {e}");
68+ }
69+ })?;
70+ handle.insert_source(
71+ Generic::new(display, Interest::READ, Mode::Level),
72+ |_, display, state| {
73+ // SAFETY: the display is never dropped while the event loop runs.
74+ unsafe { display.get_mut().dispatch_clients(state)? };
75+ Ok(PostAction::Continue)
76+ },
77+ )?;
78+
79+ Ok(Self {
80+ start: Instant::now(),
81+ socket_name,
82+ signal: event_loop.get_signal(),
83+ renderer,
84+ space: Space::default(),
85+ popups: PopupManager::default(),
86+ host: None,
87+ needs_redraw: true,
88+ compositor_state: CompositorState::new::<State>(&dh),
89+ xdg_shell_state: XdgShellState::new::<State>(&dh),
90+ shm_state: ShmState::new::<State>(&dh, vec![]),
91+ dmabuf_state,
92+ _dmabuf_global: dmabuf_global,
93+ _output_manager_state: OutputManagerState::new_with_xdg_output::<State>(&dh),
94+ seat_state,
95+ data_device_state: DataDeviceState::new::<State>(&dh),
96+ seat,
97+ display: dh,
98+ })
99+ }
100+
101+ /// Runs after every batch of events, so many client commits produce one frame.
102+ pub fn after_dispatch(&mut self) {
103+ self.space.refresh();
104+ self.popups.cleanup();
105+ crate::nested::render(self);
106+ let _ = self.display.flush_clients();
107+ }
108+
109+ pub fn output_size(&self) -> Size<i32, Logical> {
110+ self.host.as_ref().map_or((1280, 800).into(), |h| h.size)
111+ }
112+
113+ /// Lays toplevels out side by side across the output. A stand-in until
114+ /// the layout crate takes over.
115+ pub fn relayout(&mut self) {
116+ let windows: Vec<Window> = self.space.elements().cloned().collect();
117+ let size = self.output_size();
118+ let n = windows.len() as i32;
119+ if n == 0 {
120+ return;
121+ }
122+ let gap = 8;
123+ let w = (size.w - gap * (n + 1)) / n;
124+ for (i, window) in windows.into_iter().enumerate() {
125+ let loc = (gap + i as i32 * (w + gap), gap);
126+ if let Some(toplevel) = window.toplevel() {
127+ toplevel.with_pending_state(|s| s.size = Some((w, size.h - 2 * gap).into()));
128+ if toplevel.is_initial_configure_sent() {
129+ toplevel.send_pending_configure();
130+ }
131+ }
132+ self.space.map_element(window, loc, false);
133+ }
134+ self.needs_redraw = true;
135+ }
136+
137+ pub fn surface_under(
138+ &self,
139+ pos: Point<f64, Logical>,
140+ ) -> Option<(WlSurface, Point<f64, Logical>)> {
141+ self.space
142+ .element_under(pos)
143+ .and_then(|(window, location)| {
144+ window
145+ .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)
146+ .map(|(s, p)| (s, (p + location).to_f64()))
147+ })
148+ }
149+}
150+
151+#[derive(Default)]
152+pub struct ClientState {
153+ pub compositor_state: CompositorClientState,
154+}
155+
156+impl ClientData for ClientState {
157+ fn initialized(&self, _client_id: ClientId) {}
158+ fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
159+}
Modified file: crates/render-vk/src/error.rs+2 additions
@@ -21,6 +21,8 @@ pub enum Error {
2121 DmabufUnsupported,
2222 #[error("cannot import dmabuf with format {0:?}")]
2323 UnsupportedDmabuf(smithay::backend::allocator::Format),
24+ #[error("cannot read the shm buffer")]
25+ ShmAccess,
2426 #[error("interrupted while waiting for a sync point")]
2527 Interrupted,
2628 #[error("invalid size {0}x{1}")]
Modified file: crates/render-vk/src/renderer.rs+35 additions
@@ -13,12 +13,16 @@ use gpu_allocator::MemoryLocation;
1313 use smithay::backend::allocator::Fourcc;
1414 use smithay::backend::allocator::dmabuf::{Dmabuf, WeakDmabuf};
1515 use smithay::backend::allocator::format::FormatSet;
16+use smithay::backend::renderer::ImportMemWl;
1617 use smithay::backend::renderer::sync::SyncPoint;
1718 use smithay::backend::renderer::{
1819 Bind, ContextId, DebugFlags, ExportMem, ImportDma, ImportDmaWl, ImportMem, Offscreen, Renderer,
1920 RendererSuper, Texture, TextureFilter,
2021 };
22+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
2123 use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};
24+use smithay::wayland::compositor::SurfaceData;
25+use smithay::wayland::shm::{shm_format_to_fourcc, with_buffer_contents};
2226 use std::collections::HashMap;
2327 use std::fmt;
2428 use std::marker::PhantomData;
@@ -873,3 +877,34 @@ impl ExportMem for VulkanRenderer {
873877 Ok(&mapping.data)
874878 }
875879 }
880+
881+impl ImportMemWl for VulkanRenderer {
882+ fn import_shm_buffer(
883+ &mut self,
884+ buffer: &WlBuffer,
885+ _surface: Option<&SurfaceData>,
886+ _damage: &[Rectangle<i32, Buffer>],
887+ ) -> Result<VulkanTexture, Error> {
888+ let (pixels, fourcc, size) = with_buffer_contents(buffer, |ptr, len, data| {
889+ let fourcc = shm_format_to_fourcc(data.format).ok_or(Error::ShmAccess)?;
890+ let (w, h, stride, offset) = (
891+ data.width as usize,
892+ data.height as usize,
893+ data.stride as usize,
894+ data.offset as usize,
895+ );
896+ let row = w * BYTES_PER_PIXEL;
897+ if w == 0 || h == 0 || stride < row || offset + stride * (h - 1) + row > len {
898+ return Err(Error::ShmAccess);
899+ }
900+ let mut pixels = Vec::with_capacity(row * h);
901+ for y in 0..h {
902+ let line = unsafe { std::slice::from_raw_parts(ptr.add(offset + y * stride), row) };
903+ pixels.extend_from_slice(line);
904+ }
905+ Ok((pixels, fourcc, Size::from((data.width, data.height))))
906+ })
907+ .map_err(|_| Error::ShmAccess)??;
908+ self.import_memory(&pixels, fourcc, size, false)
909+ }
910+}