use std::any::Any;
use std::collections::HashMap;
use gbm::BufferObject;
use log::info;
use smithay_client_toolkit::seat::keyboard::{
KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers,
};
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_registry,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
};
use std::os::fd::{AsFd, AsRawFd};
use wayland_client::globals::registry_queue_init;
use wayland_client::protocol::{wl_keyboard, wl_seat};
use wayland_client::{
Connection, Dispatch, EventQueue, QueueHandle,
protocol::{wl_buffer, wl_output, wl_surface},
};
use wayland_protocols::wp::linux_dmabuf::zv1::client::{
zwp_linux_buffer_params_v1, zwp_linux_dmabuf_v1,
};
type RenderCB = Box<dyn FnMut(&mut GbmViewer, &mut dyn Any) -> BufferObject<()> + 'static>;
type KvCB = Box<dyn Fn(KeyEvent, &mut dyn Any) + 'static>;
pub struct GbmViewer {
window: Window,
width: u32,
height: u32,
first_configure: bool,
damaged: bool,
frame_pending: bool,
pub pending_size: Option<(u32, u32)>,
}
#[allow(unused)]
pub struct WaylandCommitter {
registry_state: RegistryState,
output_state: OutputState,
compositor_state: CompositorState,
xdg_shell_state: XdgShell,
dmabuf_state: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
seat_state: SeatState,
keyboards: HashMap<wl_seat::WlSeat, wl_keyboard::WlKeyboard>,
bo_map: HashMap<u64, BufferObject<()>>,
wl_buffer_map: HashMap<u64, wl_buffer::WlBuffer>,
windows: Vec<GbmViewer>,
render_cb: RenderCB,
kb_cv: KvCB,
udata: Box<dyn Any>,
}
fn get_dma_buf_id(fd: std::os::unix::io::RawFd) -> Option<u64> {
unsafe {
let mut stat_buf: libc::stat = std::mem::zeroed();
if libc::fstat(fd, &mut stat_buf) == 0 {
Some(stat_buf.st_ino)
} else {
None
}
}
}
impl WaylandCommitter {
pub fn init<F, F2, D>(
f: F,
f2: F2,
user_data: D,
) -> anyhow::Result<(Self, EventQueue<Self>, QueueHandle<Self>)>
where
Self: Sized,
F: FnMut(&mut GbmViewer, &mut dyn Any) -> BufferObject<()> + 'static,
F2: Fn(KeyEvent, &mut dyn Any) + 'static,
D: Any + 'static,
{
let conn = Connection::connect_to_env()?;
let (globals, event_queue) = registry_queue_init(&conn)?;
let qh: QueueHandle<Self> = event_queue.handle();
let dmabuf_state = globals
.bind::<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, _, _>(&qh, 3..=4, ())
.expect("Compositor does not support zwp_linux_dmabuf_v1");
let seat_state = SeatState::new(&globals, &qh);
Ok((
Self {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
compositor_state: CompositorState::bind(&globals, &qh)
.expect("wl_compositor not available"),
xdg_shell_state: XdgShell::bind(&globals, &qh).expect("xdg shell not available"),
dmabuf_state,
seat_state,
keyboards: HashMap::new(),
bo_map: HashMap::new(),
wl_buffer_map: HashMap::new(),
windows: Vec::new(),
render_cb: Box::new(f),
kb_cv: Box::new(f2),
udata: Box::new(user_data),
},
event_queue,
qh,
))
}
pub fn main(&mut self, event_queue: &mut EventQueue<Self>, qh: &QueueHandle<Self>) {
let surface = self.compositor_state.create_surface(qh);
let window =
self.xdg_shell_state
.create_window(surface, WindowDecorations::ServerDefault, qh);
window.set_title("Wayland GBM DMA-BUF Viewer");
window.set_app_id("io.github.gbm.dmabuf_viewer");
window.commit();
self.windows.push(GbmViewer {
window,
width: 0,
height: 0,
first_configure: true,
damaged: true,
frame_pending: false,
pending_size: None,
});
loop {
event_queue.blocking_dispatch(self).unwrap();
if self.windows.is_empty() {
info!("All windows closed, exiting.");
break;
}
}
}
pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
for viewer in &mut self.windows {
if viewer.first_configure || !viewer.damaged {
continue;
}
let cb = &mut self.render_cb;
let bo = cb(viewer, self.udata.as_mut());
let fd = bo.fd().unwrap();
let inode = get_dma_buf_id(fd.as_raw_fd()).expect("Failed to get inode from dmabuf fd");
self.bo_map.insert(inode, bo);
let wl_buffer = if let Some(buf) = self.wl_buffer_map.get(&inode) {
buf
} else {
let bo_ref = self.bo_map.get(&inode).unwrap();
let params = self.dmabuf_state.create_params(qh, ());
let modifier: u64 = bo_ref.modifier().into();
params.add(
fd.as_fd(),
0, bo_ref.offset(0),
bo_ref.stride(),
(modifier >> 32) as u32,
(modifier & 0xFFFFFFFF) as u32,
);
let buf = params.create_immed(
bo_ref.width() as i32,
bo_ref.height() as i32,
bo_ref.format() as u32, zwp_linux_buffer_params_v1::Flags::empty(),
qh,
inode,
);
self.wl_buffer_map.insert(inode, buf.clone());
self.wl_buffer_map.get(&inode).unwrap()
};
let surface = viewer.window.wl_surface();
surface.attach(Some(wl_buffer), 0, 0);
surface.damage_buffer(0, 0, viewer.width as i32, viewer.height as i32);
if !viewer.frame_pending {
surface.frame(qh, surface.clone());
viewer.frame_pending = true;
}
viewer.damaged = false;
surface.commit();
}
}
}
impl Dispatch<wl_buffer::WlBuffer, u64> for WaylandCommitter {
fn event(
state: &mut Self,
_proxy: &wl_buffer::WlBuffer,
event: wl_buffer::Event,
inode: &u64,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let wl_buffer::Event::Release = event {
state.bo_map.remove(inode);
}
}
}
impl Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, ()> for WaylandCommitter {
fn event(
_: &mut Self,
_: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
_: zwp_linux_buffer_params_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for WaylandCommitter {
fn event(
_: &mut Self,
_: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
_: zwp_linux_dmabuf_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl CompositorHandler for WaylandCommitter {
fn scale_factor_changed(
&mut self,
_c: &Connection,
_qh: &QueueHandle<Self>,
_s: &wl_surface::WlSurface,
_f: i32,
) {
}
fn transform_changed(
&mut self,
_c: &Connection,
_qh: &QueueHandle<Self>,
_s: &wl_surface::WlSurface,
_t: wl_output::Transform,
) {
}
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
for viewer in &mut self.windows {
if viewer.window.wl_surface() == _surface {
viewer.frame_pending = false;
viewer.damaged = true;
}
}
self.draw(conn, qh);
}
fn surface_enter(
&mut self,
_c: &Connection,
_qh: &QueueHandle<Self>,
_s: &wl_surface::WlSurface,
_o: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_c: &Connection,
_qh: &QueueHandle<Self>,
_s: &wl_surface::WlSurface,
_o: &wl_output::WlOutput,
) {
}
}
impl WindowHandler for WaylandCommitter {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, window: &Window) {
self.windows.retain(|v| v.window != *window);
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
let mut is_first = false;
let mut needs_draw = false;
for viewer in &mut self.windows {
if viewer.window != *window {
continue;
}
let new_width = configure.new_size.0.map(|v| v.get()).unwrap_or(800);
let new_height = configure.new_size.1.map(|v| v.get()).unwrap_or(600);
if !viewer.first_configure && (viewer.width != new_width || viewer.height != new_height)
{
viewer.pending_size = Some((new_width, new_height));
}
viewer.width = new_width;
viewer.height = new_height;
viewer.damaged = true;
if viewer.first_configure {
viewer.first_configure = false;
is_first = true;
}
if is_first || !viewer.frame_pending {
needs_draw = true;
}
}
if needs_draw {
self.draw(conn, qh);
}
}
}
impl SeatHandler for WaylandCommitter {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Keyboard && !self.keyboards.contains_key(&seat) {
let keyboard = self
.seat_state
.get_keyboard(qh, &seat, None)
.expect("Failed to get keyboard from seat");
self.keyboards.insert(seat, keyboard);
}
}
fn remove_capability(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Keyboard
&& let Some(keyboard) = self.keyboards.remove(&seat)
{
keyboard.release();
}
}
fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
if let Some(keyboard) = self.keyboards.remove(&seat) {
keyboard.release();
}
}
}
impl KeyboardHandler for WaylandCommitter {
fn enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_surface: &wl_surface::WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[Keysym],
) {
}
fn leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_surface: &wl_surface::WlSurface,
_serial: u32,
) {
}
fn press_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
let cb = &mut self.kb_cv;
let u = self.udata.as_mut();
cb(event, u);
}
fn repeat_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
_event: KeyEvent,
) {
}
fn release_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
_event: KeyEvent,
) {
}
fn update_modifiers(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wl_keyboard::WlKeyboard,
_serial: u32,
_modifiers: Modifiers,
_raw_modifiers: RawModifiers,
_layout: u32,
) {
}
}
impl OutputHandler for WaylandCommitter {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _c: &Connection, _qh: &QueueHandle<Self>, _o: wl_output::WlOutput) {}
fn update_output(&mut self, _c: &Connection, _qh: &QueueHandle<Self>, _o: wl_output::WlOutput) {
}
fn output_destroyed(
&mut self,
_c: &Connection,
_qh: &QueueHandle<Self>,
_o: wl_output::WlOutput,
) {
}
}
delegate_registry!(WaylandCommitter);
impl ProvidesRegistryState for WaylandCommitter {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers!(OutputState, SeatState,);
}
smithay_client_toolkit::delegate_seat!(WaylandCommitter);
smithay_client_toolkit::delegate_keyboard!(WaylandCommitter);
smithay_client_toolkit::delegate_compositor!(WaylandCommitter);
smithay_client_toolkit::delegate_xdg_shell!(WaylandCommitter);
smithay_client_toolkit::delegate_xdg_window!(WaylandCommitter);
smithay_client_toolkit::delegate_output!(WaylandCommitter);