use std::cmp::min; use std::collections::HashMap; use std::fmt; use std::fmt::Write as _; use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use calloop::EventLoop; use calloop_wayland_source::WaylandSource; use single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1; use smithay::reexports::wayland_protocols::wp::single_pixel_buffer; use smithay::reexports::wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; use smithay::reexports::wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_surface::{self, XdgSurface}; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_toplevel::{self, XdgToplevel}; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_wm_base::{self, XdgWmBase}; use wayland_backend::client::Backend; use wayland_client::globals::Global; use wayland_client::protocol::wl_buffer::{self, WlBuffer}; use wayland_client::protocol::wl_callback::{self, WlCallback}; use wayland_client::protocol::wl_compositor::WlCompositor; use wayland_client::protocol::wl_display::WlDisplay; use wayland_client::protocol::wl_output::{self, WlOutput}; use wayland_client::protocol::wl_registry::{self, WlRegistry}; use wayland_client::protocol::wl_surface::{self, WlSurface}; use wayland_client::{Connection, Dispatch, Proxy as _, QueueHandle}; use crate::utils::id::IdCounter; pub struct Client { pub id: ClientId, pub event_loop: EventLoop<'static, State>, pub connection: Connection, pub qh: QueueHandle, pub display: WlDisplay, pub state: State, } pub struct State { pub qh: QueueHandle, pub globals: Vec, pub outputs: HashMap, pub compositor: Option, pub xdg_wm_base: Option, pub spbm: Option, pub viewporter: Option, pub windows: Vec, } pub struct Window { pub qh: QueueHandle, pub spbm: WpSinglePixelBufferManagerV1, pub surface: WlSurface, pub xdg_surface: XdgSurface, pub xdg_toplevel: XdgToplevel, pub viewport: WpViewport, pub pending_configure: Configure, pub configures_received: Vec<(u32, Configure)>, pub close_requested: bool, pub configures_looked_at: usize, } #[derive(Debug, Clone, Default)] pub struct Configure { pub size: (i32, i32), pub bounds: Option<(i32, i32)>, pub states: Vec, } #[derive(Default)] pub struct SyncData { pub done: AtomicBool, } static CLIENT_ID_COUNTER: IdCounter = IdCounter::new(); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ClientId(u64); impl ClientId { fn next() -> ClientId { ClientId(CLIENT_ID_COUNTER.next()) } } impl fmt::Display for Configure { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "size: {} × {}, ", self.size.0, self.size.1)?; if let Some(bounds) = self.bounds { write!(f, "bounds: {} × {}, ", bounds.0, bounds.1)?; } else { write!(f, "bounds: none, ")?; } write!(f, "states: {:?}", self.states)?; Ok(()) } } impl Client { pub fn new(stream: UnixStream) -> Self { let id = ClientId::next(); let event_loop = EventLoop::try_new().unwrap(); let backend = Backend::connect(stream).unwrap(); let connection = Connection::from_backend(backend); let queue = connection.new_event_queue(); let qh = queue.handle(); WaylandSource::new(connection.clone(), queue) .insert(event_loop.handle()) .unwrap(); let display = connection.display(); let _registry = display.get_registry(&qh, ()); connection.flush().unwrap(); let state = State { qh: qh.clone(), globals: Vec::new(), outputs: HashMap::new(), compositor: None, xdg_wm_base: None, spbm: None, viewporter: None, windows: Vec::new(), }; Self { id, event_loop, connection, qh, display, state, } } pub fn dispatch(&mut self) { self.event_loop .dispatch(Duration::ZERO, &mut self.state) .unwrap(); } pub fn send_sync(&self) -> Arc { let data = Arc::new(SyncData::default()); self.display.sync(&self.qh, data.clone()); self.connection.flush().unwrap(); data } pub fn create_window(&mut self) -> &mut Window { self.state.create_window() } pub fn window(&mut self, surface: &WlSurface) -> &mut Window { self.state.window(surface) } pub fn output(&mut self, name: &str) -> WlOutput { self.state .outputs .iter() .find(|(_, v)| *v == name) .unwrap() .0 .clone() } } impl State { pub fn create_window(&mut self) -> &mut Window { let compositor = self.compositor.as_ref().unwrap(); let xdg_wm_base = self.xdg_wm_base.as_ref().unwrap(); let viewporter = self.viewporter.as_ref().unwrap(); let surface = compositor.create_surface(&self.qh, ()); let xdg_surface = xdg_wm_base.get_xdg_surface(&surface, &self.qh, ()); let xdg_toplevel = xdg_surface.get_toplevel(&self.qh, ()); let viewport = viewporter.get_viewport(&surface, &self.qh, ()); let window = Window { qh: self.qh.clone(), spbm: self.spbm.clone().unwrap(), surface, xdg_surface, xdg_toplevel, viewport, pending_configure: Configure::default(), configures_received: Vec::new(), close_requested: false, configures_looked_at: 0, }; self.windows.push(window); self.windows.last_mut().unwrap() } pub fn window(&mut self, surface: &WlSurface) -> &mut Window { self.windows .iter_mut() .find(|w| w.surface == *surface) .unwrap() } } impl Window { pub fn commit(&self) { self.surface.commit(); } pub fn ack_last(&self) { let serial = self.configures_received.last().unwrap().0; self.xdg_surface.ack_configure(serial); } pub fn ack_last_and_commit(&self) { self.ack_last(); self.commit(); } pub fn attach_new_buffer(&self) { let buffer = self.spbm.create_u32_rgba_buffer(0, 0, 0, 0, &self.qh, ()); self.surface.attach(Some(&buffer), 0, 0); } pub fn set_size(&self, w: u16, h: u16) { self.viewport.set_destination(i32::from(w), i32::from(h)); } pub fn set_fullscreen(&self, output: Option<&WlOutput>) { self.xdg_toplevel.set_fullscreen(output); } pub fn unset_fullscreen(&self) { self.xdg_toplevel.unset_fullscreen(); } pub fn set_parent(&self, parent: Option<&XdgToplevel>) { self.xdg_toplevel.set_parent(parent); } pub fn set_title(&self, title: &str) { self.xdg_toplevel.set_title(title.to_owned()); } pub fn recent_configures(&mut self) -> impl Iterator { let start = self.configures_looked_at; self.configures_looked_at = self.configures_received.len(); self.configures_received[start..].iter().map(|(_, c)| c) } pub fn format_recent_configures(&mut self) -> String { let mut buf = String::new(); for configure in self.recent_configures() { if !buf.is_empty() { buf.push('\n'); } write!(buf, "{configure}").unwrap(); } buf } } impl Dispatch> for State { fn event( _state: &mut Self, _proxy: &WlCallback, event: ::Event, data: &Arc, _conn: &Connection, _qhandle: &QueueHandle, ) { match event { wl_callback::Event::Done { .. } => data.done.store(true, Ordering::Relaxed), _ => unreachable!(), } } } impl Dispatch for State { fn event( state: &mut Self, registry: &WlRegistry, event: ::Event, _data: &(), _conn: &Connection, qh: &QueueHandle, ) { match event { wl_registry::Event::Global { name, interface, version, } => { if interface == WlCompositor::interface().name { let version = min(version, WlCompositor::interface().version); state.compositor = Some(registry.bind(name, version, qh, ())); } else if interface == XdgWmBase::interface().name { let version = min(version, XdgWmBase::interface().version); state.xdg_wm_base = Some(registry.bind(name, version, qh, ())); } else if interface == WpSinglePixelBufferManagerV1::interface().name { let version = min(version, WpSinglePixelBufferManagerV1::interface().version); state.spbm = Some(registry.bind(name, version, qh, ())); } else if interface == WpViewporter::interface().name { let version = min(version, WpViewporter::interface().version); state.viewporter = Some(registry.bind(name, version, qh, ())); } else if interface == WlOutput::interface().name { let version = min(version, WlOutput::interface().version); let output = registry.bind(name, version, qh, ()); state.outputs.insert(output, String::new()); } let global = Global { name, interface, version, }; state.globals.push(global); } wl_registry::Event::GlobalRemove { .. } => (), _ => unreachable!(), } } } impl Dispatch for State { fn event( state: &mut Self, output: &WlOutput, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { match event { wl_output::Event::Geometry { .. } => (), wl_output::Event::Mode { .. } => (), wl_output::Event::Done => (), wl_output::Event::Scale { .. } => (), wl_output::Event::Name { name } => { *state.outputs.get_mut(output).unwrap() = name; } wl_output::Event::Description { .. } => (), _ => unreachable!(), } } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WlCompositor, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!() } } impl Dispatch for State { fn event( _state: &mut Self, xdg_wm_base: &XdgWmBase, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { match event { xdg_wm_base::Event::Ping { serial } => { xdg_wm_base.pong(serial); } _ => unreachable!(), } } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WlSurface, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { match event { wl_surface::Event::Enter { .. } => (), wl_surface::Event::Leave { .. } => (), wl_surface::Event::PreferredBufferScale { .. } => (), wl_surface::Event::PreferredBufferTransform { .. } => (), _ => unreachable!(), } } } impl Dispatch for State { fn event( state: &mut Self, xdg_surface: &XdgSurface, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { match event { xdg_surface::Event::Configure { serial } => { let window = state .windows .iter_mut() .find(|w| w.xdg_surface == *xdg_surface) .unwrap(); let configure = window.pending_configure.clone(); window.configures_received.push((serial, configure)); } _ => unreachable!(), } } } impl Dispatch for State { fn event( state: &mut Self, xdg_toplevel: &XdgToplevel, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { let window = state .windows .iter_mut() .find(|w| w.xdg_toplevel == *xdg_toplevel) .unwrap(); match event { xdg_toplevel::Event::Configure { width, height, states, } => { let configure = &mut window.pending_configure; configure.size = (width, height); configure.states = states .chunks_exact(4) .flat_map(TryInto::<[u8; 4]>::try_into) .map(u32::from_ne_bytes) .flat_map(xdg_toplevel::State::try_from) .collect(); } xdg_toplevel::Event::Close => { window.close_requested = true; } xdg_toplevel::Event::ConfigureBounds { width, height } => { window.pending_configure.bounds = Some((width, height)); } xdg_toplevel::Event::WmCapabilities { .. } => (), _ => unreachable!(), } } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WlBuffer, event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { match event { wl_buffer::Event::Release => (), _ => unreachable!(), } } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WpSinglePixelBufferManagerV1, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!() } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WpViewporter, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!() } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WpViewport, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!() } }