use std::{env, path::Path};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_simple,
delegate_xdg_shell, delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState, SimpleGlobal},
registry_handlers,
shell::{
xdg::{
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
XdgShell,
},
WaylandSurface,
},
shm::{slot::SlotPool, Shm, ShmHandler},
};
use wayland_client::{
globals::registry_queue_init,
protocol::{wl_output, wl_shm, wl_surface},
Connection, Dispatch, QueueHandle,
};
use wayland_protocols::wp::viewporter::client::{
wp_viewport::{self, WpViewport},
wp_viewporter::{self, WpViewporter},
};
fn main() {
env_logger::init();
let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle();
let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available");
let shm = Shm::bind(&globals, &qh).expect("wl shm is not available.");
let wp_viewporter = SimpleGlobal::<wp_viewporter::WpViewporter, 1>::bind(&globals, &qh)
.expect("wp_viewporter not available");
let mut windows = Vec::new();
let mut pool_size = 0;
for path in env::args_os().skip(1) {
let image = match image::open(&path) {
Ok(i) => i,
Err(e) => {
println!("Failed to open image {}.", path.to_string_lossy());
println!("Error was: {e:?}");
return;
}
};
let image = image.to_rgba8();
pool_size += image.width() * image.height() * 4;
let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
window.set_app_id("io.github.smithay.client-toolkit.ImageViewer");
window.set_min_size(Some((256, 256)));
let path: &Path = path.as_os_str().as_ref();
window.set_title(path.components().last().unwrap().as_os_str().to_string_lossy());
window.commit();
let viewport = wp_viewporter.get().expect("Requires wp_viewporter").get_viewport(
window.wl_surface(),
&qh,
(),
);
windows.push(ImageViewer {
width: image.width(),
height: image.height(),
window,
viewport,
image,
first_configure: true,
damaged: true,
});
}
if windows.is_empty() {
println!("USAGE: ./image_viewer <PATH> [<PATH>]...");
return;
}
let pool = SlotPool::new(pool_size as usize, &shm).expect("Failed to create pool");
let mut state = State {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
shm,
wp_viewporter,
pool,
windows,
};
loop {
event_queue.blocking_dispatch(&mut state).unwrap();
if state.windows.is_empty() {
println!("exiting example");
break;
}
}
}
struct State {
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,
wp_viewporter: SimpleGlobal<WpViewporter, 1>,
pool: SlotPool,
windows: Vec<ImageViewer>,
}
struct ImageViewer {
window: Window,
image: image::RgbaImage,
viewport: WpViewport,
width: u32,
height: u32,
first_configure: bool,
damaged: bool,
}
impl CompositorHandler for State {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_transform: wl_output::Transform,
) {
}
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(conn, qh);
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
}
impl OutputHandler for State {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
}
impl WindowHandler for State {
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,
) {
for viewer in &mut self.windows {
if viewer.window != *window {
continue;
}
if let (Some(width), Some(height)) = configure.new_size {
viewer.width = width.get();
viewer.height = height.get();
viewer.viewport.set_destination(width.get() as _, height.get() as _);
if !viewer.first_configure {
viewer.window.commit();
}
}
viewer.first_configure = false;
}
self.draw(conn, qh);
}
}
impl ShmHandler for State {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl State {
pub fn draw(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {
for viewer in &mut self.windows {
if viewer.first_configure || !viewer.damaged {
continue;
}
let window = &viewer.window;
let width = viewer.image.width();
let height = viewer.image.height();
let stride = width as i32 * 4;
let (buffer, canvas) = self
.pool
.create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888)
.expect("create buffer");
for (pixel, argb) in viewer.image.pixels().zip(canvas.chunks_exact_mut(4)) {
argb[3] = pixel.0[3];
argb[2] = pixel.0[0];
argb[1] = pixel.0[1];
argb[0] = pixel.0[2];
}
window.wl_surface().damage_buffer(0, 0, viewer.width as i32, viewer.height as i32);
viewer.damaged = false;
viewer.viewport.set_source(0.0, 0.0, viewer.width as f64, viewer.height as f64);
buffer.attach_to(window.wl_surface()).expect("buffer attach");
window.wl_surface().commit();
}
}
}
delegate_compositor!(State);
delegate_output!(State);
delegate_shm!(State);
delegate_xdg_shell!(State);
delegate_xdg_window!(State);
delegate_simple!(State, WpViewporter, 1);
delegate_registry!(State);
impl ProvidesRegistryState for State {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers!(OutputState);
}
impl AsMut<SimpleGlobal<WpViewporter, 1>> for State {
fn as_mut(&mut self) -> &mut SimpleGlobal<WpViewporter, 1> {
&mut self.wp_viewporter
}
}
impl Dispatch<WpViewport, ()> for State {
fn event(
_: &mut State,
_: &WpViewport,
_: wp_viewport::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
unreachable!("wp_viewport::Event is empty in version 1")
}
}
impl Drop for ImageViewer {
fn drop(&mut self) {
self.viewport.destroy()
}
}