Commit d4a64ce87164
tarball9 files changed+1066-18
@@ -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
]@@ -12,15 +12,11 @@ workspace = true
1212
profile = ["dep:tracy-client", "tracy-client/enable"]1313
1414
[dependencies]15
-monolith-config.workspace = true16
-monolith-ipc.workspace = true17
-monolith-layout.workspace = true18
-monolith-motion.workspace = true19
-monolith-planner.workspace = true20
-monolith-protocols.workspace = true15
+calloop-wayland-source = "0.4"2116
monolith-render-vk.workspace = true22
-smithay.workspace = true17
+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 = true21
+wayland-client = "0.31"22
+wayland-protocols = { version = "0.32", features = ["client", "unstable", "staging"] }@@ -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_state32
+ }33
+34
+ fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {35
+ &client36
+ .get_data::<ClientState>()37
+ .expect("every client has ClientState")38
+ .compositor_state39
+ }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) = self49
+ .space50
+ .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) = self57
+ .space58
+ .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_state83
+ }84
+}85
+86
+impl DmabufHandler for State {87
+ fn dmabuf_state(&mut self) -> &mut DmabufState {88
+ &mut self.dmabuf_state89
+ }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_state108
+ }109
+110
+ fn new_toplevel(&mut self, surface: ToplevelSurface) {111
+ let focus = surface.wl_surface().clone();112
+ self.space113
+ .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_state152
+ }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_state169
+ }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);@@ -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
+ } = event23
+ {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
+ } = event130
+ {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
+}@@ -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
+}@@ -0,0 +1,431 @@
1
+//! Runs monolith as a window inside another Wayland compositor. Frames are2
+//! rendered into dmabufs from our own swapchain and handed to the host3
+//! through linux-dmabuf, so the nested path exercises the same4
+//! 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.output156
+ .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
+ b223
+ }224
+ };225
+ host.surface.attach(Some(&buffer), 0, 0);226
+ for r in &damage {227
+ host.surface228
+ .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> = host246
+ .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
+ None262
+}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 buf273
+ .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
+ buffer297
+}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 != size359
+ {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
+ } = event385
+ && 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 = event403
+ && 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 { .. } = event420
+ && 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);@@ -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) = state64
+ .display65
+ .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 until114
+ /// 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.space142
+ .element_under(pos)143
+ .and_then(|(window, location)| {144
+ window145
+ .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
+}@@ -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}")]@@ -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
+}