//! Wayland primitives for shop. //! //! Owns the SCTK client-side objects (compositor, xdg-shell or //! wlr-layer-shell, output, seat, registry, the surface). Does NOT own the wayland event queue or //! implement any SCTK Handler traits — those live in the binary so a single //! app state can be dispatched from a shared event loop (calloop), which is //! also where the PTY fd and any other sources land. //! //! Use [`init`] to construct the chrome against a caller-provided queue //! handle; the caller keeps the [`EventQueue`] to hand to a wayland source. use std::{collections::VecDeque, ptr::NonNull}; pub use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; pub use smithay_client_toolkit::{ compositor::Region, compositor::{CompositorHandler, CompositorState}, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, seat::{Capability, SeatHandler, SeatState}, shell::{ WaylandSurface, wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, }, xdg::{ XdgShell, window::{Window as XdgWindow, WindowConfigure, WindowDecorations, WindowHandler}, }, }, }; pub use wayland_client::{ Connection, Dispatch, EventQueue, Proxy, QueueHandle, globals::{GlobalList, GlobalListContents, registry_queue_init}, protocol::wl_registry::WlRegistry, }; /// Change reported by the compositor since the last dispatch. #[derive(Debug, Clone)] pub enum Pending { /// The compositor gave us a new size. Resized { width: u32, height: u32 }, /// User asked to close (xdg-toplevel close). CloseRequested, } /// Where a layer surface sits and how much of the output it claims. /// /// Only meaningful for [`ShopSurface::Layer`]. Anchoring all four edges with a /// size of `(0, 0)` is how a surface asks for the whole output, which is what /// the background case wants. #[derive(Debug, Clone, Copy)] pub struct LayerConfig { pub layer: Layer, pub anchor: Anchor, pub exclusive_zone: i32, pub keyboard_interactivity: KeyboardInteractivity, /// Refuse every pointer event by committing an empty input region. The /// background surface is output only, and a surface that swallows clicks /// over the whole screen is worse than no surface at all. pub no_input: bool, } impl LayerConfig { /// Output-sized, output-only: every edge anchored, no exclusive zone, no /// keyboard focus, no pointer events. #[must_use] pub fn background(layer: Layer) -> Self { Self { layer, anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT, exclusive_zone: 0, keyboard_interactivity: KeyboardInteractivity::None, no_input: true, } } } /// Spec for creating the window, whichever shell it lands on. pub struct WindowSpec<'a> { pub title: &'a str, pub app_id: &'a str, pub min_size: (u32, u32), pub initial_size: (u32, u32), /// `None` opens an ordinary xdg toplevel. `Some` opens a wlr-layer-shell /// surface instead. pub layer: Option, } /// The surface shop draws to: an xdg toplevel, or a wlr-layer-shell surface. /// /// An enum rather than a `WaylandSurface` bound because [`WaylandChrome`] is a /// concrete struct composed into the binary's app state, and a type parameter /// there would spread through every SCTK handler impl for no gain: there are /// two shells and there will not be a third. pub enum ShopSurface { Toplevel(XdgWindow), Layer(LayerSurface), } impl ShopSurface { #[must_use] pub fn wl_surface(&self) -> &wayland_client::protocol::wl_surface::WlSurface { match self { Self::Toplevel(window) => window.wl_surface(), Self::Layer(layer) => layer.wl_surface(), } } /// A title the compositor shows. Layer surfaces have nowhere to put one, /// so an escape sequence that sets the title is dropped rather than an /// error: a program run on the background should not fail for asking. pub fn set_title(&self, title: impl Into) { match self { Self::Toplevel(window) => window.set_title(title.into()), Self::Layer(_) => {} } } pub fn commit(&self) { match self { Self::Toplevel(window) => window.commit(), Self::Layer(layer) => layer.commit(), } } } /// Chrome (SCTK client-side state) for one top-level window. Composed into /// the binary's app state so SCTK Handler traits can be implemented once at /// the app layer and dispatched by calloop. pub struct WaylandChrome { pub registry: RegistryState, pub seat: SeatState, pub output: OutputState, pub compositor: CompositorState, /// Held for as long as the surface it made. Both are `Option` because a /// run binds one shell, not both, and the unbound global must not be a /// hard requirement: wlr-layer-shell is missing on plenty of compositors. pub xdg_shell: Option, pub layer_shell: Option, pub surface: ShopSurface, pub pending: VecDeque, pub width: u32, pub height: u32, pub exit: bool, } // Chrome construction lives in the binary because the SCTK `bind` calls need // all the Dispatch impls that `delegate_dispatch2!(App)` provides, and those // impls are only visible in the crate that invokes the macro. /// Raw handles for the compositor connection and the drawing surface. /// Both reference wayland pointers owned by `conn`/`chrome`; do not use /// after either is dropped. pub fn raw_handles( conn: &Connection, chrome: &WaylandChrome, ) -> (RawDisplayHandle, RawWindowHandle) { let display_ptr = NonNull::new(conn.backend().display_ptr().cast()).expect("wayland display ptr"); let surface_ptr = NonNull::new(chrome.surface.wl_surface().id().as_ptr().cast()).expect("wl_surface ptr"); let display = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(display_ptr)); let window = RawWindowHandle::Wayland(WaylandWindowHandle::new(surface_ptr)); (display, window) }