Skip to main content

max / shop

6.2 KB · 169 lines History Blame Raw
1 //! Wayland primitives for shop.
2 //!
3 //! Owns the SCTK client-side objects (compositor, xdg-shell or
4 //! wlr-layer-shell, output, seat, registry, the surface). Does NOT own the wayland event queue or
5 //! implement any SCTK Handler traits — those live in the binary so a single
6 //! app state can be dispatched from a shared event loop (calloop), which is
7 //! also where the PTY fd and any other sources land.
8 //!
9 //! Use [`init`] to construct the chrome against a caller-provided queue
10 //! handle; the caller keeps the [`EventQueue`] to hand to a wayland source.
11
12 use std::{collections::VecDeque, ptr::NonNull};
13
14 pub use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
15 use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle};
16 pub use smithay_client_toolkit::{
17 compositor::Region,
18 compositor::{CompositorHandler, CompositorState},
19 output::{OutputHandler, OutputState},
20 registry::{ProvidesRegistryState, RegistryState},
21 seat::{Capability, SeatHandler, SeatState},
22 shell::{
23 WaylandSurface,
24 wlr_layer::{
25 Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
26 LayerSurfaceConfigure,
27 },
28 xdg::{
29 XdgShell,
30 window::{Window as XdgWindow, WindowConfigure, WindowDecorations, WindowHandler},
31 },
32 },
33 };
34 pub use wayland_client::{
35 Connection, Dispatch, EventQueue, Proxy, QueueHandle,
36 globals::{GlobalList, GlobalListContents, registry_queue_init},
37 protocol::wl_registry::WlRegistry,
38 };
39
40 /// Change reported by the compositor since the last dispatch.
41 #[derive(Debug, Clone)]
42 pub enum Pending {
43 /// The compositor gave us a new size.
44 Resized { width: u32, height: u32 },
45 /// User asked to close (xdg-toplevel close).
46 CloseRequested,
47 }
48
49 /// Where a layer surface sits and how much of the output it claims.
50 ///
51 /// Only meaningful for [`ShopSurface::Layer`]. Anchoring all four edges with a
52 /// size of `(0, 0)` is how a surface asks for the whole output, which is what
53 /// the background case wants.
54 #[derive(Debug, Clone, Copy)]
55 pub struct LayerConfig {
56 pub layer: Layer,
57 pub anchor: Anchor,
58 pub exclusive_zone: i32,
59 pub keyboard_interactivity: KeyboardInteractivity,
60 /// Refuse every pointer event by committing an empty input region. The
61 /// background surface is output only, and a surface that swallows clicks
62 /// over the whole screen is worse than no surface at all.
63 pub no_input: bool,
64 }
65
66 impl LayerConfig {
67 /// Output-sized, output-only: every edge anchored, no exclusive zone, no
68 /// keyboard focus, no pointer events.
69 #[must_use]
70 pub fn background(layer: Layer) -> Self {
71 Self {
72 layer,
73 anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
74 exclusive_zone: 0,
75 keyboard_interactivity: KeyboardInteractivity::None,
76 no_input: true,
77 }
78 }
79 }
80
81 /// Spec for creating the window, whichever shell it lands on.
82 pub struct WindowSpec<'a> {
83 pub title: &'a str,
84 pub app_id: &'a str,
85 pub min_size: (u32, u32),
86 pub initial_size: (u32, u32),
87 /// `None` opens an ordinary xdg toplevel. `Some` opens a wlr-layer-shell
88 /// surface instead.
89 pub layer: Option<LayerConfig>,
90 }
91
92 /// The surface shop draws to: an xdg toplevel, or a wlr-layer-shell surface.
93 ///
94 /// An enum rather than a `WaylandSurface` bound because [`WaylandChrome`] is a
95 /// concrete struct composed into the binary's app state, and a type parameter
96 /// there would spread through every SCTK handler impl for no gain: there are
97 /// two shells and there will not be a third.
98 pub enum ShopSurface {
99 Toplevel(XdgWindow),
100 Layer(LayerSurface),
101 }
102
103 impl ShopSurface {
104 #[must_use]
105 pub fn wl_surface(&self) -> &wayland_client::protocol::wl_surface::WlSurface {
106 match self {
107 Self::Toplevel(window) => window.wl_surface(),
108 Self::Layer(layer) => layer.wl_surface(),
109 }
110 }
111
112 /// A title the compositor shows. Layer surfaces have nowhere to put one,
113 /// so an escape sequence that sets the title is dropped rather than an
114 /// error: a program run on the background should not fail for asking.
115 pub fn set_title(&self, title: impl Into<String>) {
116 match self {
117 Self::Toplevel(window) => window.set_title(title.into()),
118 Self::Layer(_) => {}
119 }
120 }
121
122 pub fn commit(&self) {
123 match self {
124 Self::Toplevel(window) => window.commit(),
125 Self::Layer(layer) => layer.commit(),
126 }
127 }
128 }
129
130 /// Chrome (SCTK client-side state) for one top-level window. Composed into
131 /// the binary's app state so SCTK Handler traits can be implemented once at
132 /// the app layer and dispatched by calloop.
133 pub struct WaylandChrome {
134 pub registry: RegistryState,
135 pub seat: SeatState,
136 pub output: OutputState,
137 pub compositor: CompositorState,
138 /// Held for as long as the surface it made. Both are `Option` because a
139 /// run binds one shell, not both, and the unbound global must not be a
140 /// hard requirement: wlr-layer-shell is missing on plenty of compositors.
141 pub xdg_shell: Option<XdgShell>,
142 pub layer_shell: Option<LayerShell>,
143 pub surface: ShopSurface,
144 pub pending: VecDeque<Pending>,
145 pub width: u32,
146 pub height: u32,
147 pub exit: bool,
148 }
149
150 // Chrome construction lives in the binary because the SCTK `bind` calls need
151 // all the Dispatch impls that `delegate_dispatch2!(App)` provides, and those
152 // impls are only visible in the crate that invokes the macro.
153
154 /// Raw handles for the compositor connection and the drawing surface.
155 /// Both reference wayland pointers owned by `conn`/`chrome`; do not use
156 /// after either is dropped.
157 pub fn raw_handles(
158 conn: &Connection,
159 chrome: &WaylandChrome,
160 ) -> (RawDisplayHandle, RawWindowHandle) {
161 let display_ptr =
162 NonNull::new(conn.backend().display_ptr().cast()).expect("wayland display ptr");
163 let surface_ptr =
164 NonNull::new(chrome.surface.wl_surface().id().as_ptr().cast()).expect("wl_surface ptr");
165 let display = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(display_ptr));
166 let window = RawWindowHandle::Wayland(WaylandWindowHandle::new(surface_ptr));
167 (display, window)
168 }
169