waybackend/
lib.rs

1//! # Waybackend
2//!
3//! Welcome. Waybackend is a low-level wayland backend that allows you to do wayland stuff without
4//! wrapping everything in `Arc`s.
5
6use rustix::{
7    fd::{FromRawFd, OwnedFd},
8    net::AddressFamily,
9};
10use types::ObjectId;
11
12mod wayland;
13
14pub use bitflags;
15pub use rustix;
16pub mod objman;
17pub mod shm;
18pub mod types;
19pub mod wire;
20
21pub use wire::Error;
22
23/// This struct holds the message builder and the wayland file descriptor
24///
25/// To create this struct, use the [`connect()`] function.
26pub struct Waybackend {
27    /// the message builder incrementally builds up wire messages
28    pub wire_msg_builder: wire::MessageBuilder,
29    /// the wayland file descriptor. You can pass this to `poll` to poll events
30    pub wayland_fd: OwnedFd,
31}
32
33impl Waybackend {
34    #[inline]
35    #[must_use]
36    fn new(wayland_fd: OwnedFd) -> Self {
37        Self {
38            wire_msg_builder: wire::MessageBuilder::new(),
39            wayland_fd,
40        }
41    }
42
43    #[inline]
44    pub fn flush(&mut self) -> Result<(), wire::Error> {
45        self.wire_msg_builder.flush(&self.wayland_fd)
46    }
47}
48
49use std::{num::NonZeroU32, path::PathBuf};
50
51/// The wayland display global object always has the same id: 1
52pub const WL_DISPLAY: types::ObjectId = types::ObjectId::new(NonZeroU32::new(1).unwrap());
53
54#[derive(Debug)]
55pub enum ConnectionError {
56    InvalidWaylandSocketEnvVar,
57    InvalidSocketAddrFamily(rustix::net::AddressFamily),
58    GetSocketNameFailed(rustix::io::Errno),
59    SocketCreationFailed(rustix::io::Errno),
60    ConnectionFailed(rustix::io::Errno),
61}
62
63impl std::fmt::Display for ConnectionError {
64    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65        match self {
66            ConnectionError::InvalidWaylandSocketEnvVar => write!(
67                f,
68                "WAYLAND_SOCKET environment variable contains a value we failed to parse"
69            ),
70            ConnectionError::InvalidSocketAddrFamily(actual) => write!(
71                f,
72                "socket address in WAYLAND_SOCKET is not a unix socket. It's actual address family is: {actual:?}"
73            ),
74            ConnectionError::GetSocketNameFailed(errno) => {
75                write!(f, "failed to get socket name: {errno}")
76            }
77            ConnectionError::SocketCreationFailed(errno) => {
78                write!(f, "failed to create socket: {errno}")
79            }
80            ConnectionError::ConnectionFailed(errno) => {
81                write!(f, "failed to connect to the unix stream: {errno}")
82            }
83        }
84    }
85}
86
87impl std::error::Error for ConnectionError {}
88
89/// Mostly copy-pasted from `wayland-client.rs`
90///
91/// This will connect to the wayland server using several fallback heuristics.
92///
93/// It first tries the `WAYLAND_SOCKET` environment variable. Failing that, it tries to read the
94/// `WAYLAND_DISPLAY` variable. If it isn't set, we default to `wayland-0`.
95///
96/// Returns a [`Waybackend`] with the wayland file descriptor and wire message builder.
97#[inline]
98pub fn connect() -> Result<Waybackend, ConnectionError> {
99    if let Ok(txt) = std::env::var("WAYLAND_SOCKET") {
100        // We should connect to the provided WAYLAND_SOCKET
101        let fd = txt
102            .parse::<i32>()
103            .map_err(|_| ConnectionError::InvalidWaylandSocketEnvVar)?;
104        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
105
106        match rustix::net::getsockname(&fd) {
107            Ok(socket_addr) => {
108                if socket_addr.address_family() == AddressFamily::UNIX {
109                    Ok(Waybackend::new(fd))
110                } else {
111                    Err(ConnectionError::InvalidSocketAddrFamily(
112                        socket_addr.address_family(),
113                    ))
114                }
115            }
116            Err(e) => Err(ConnectionError::GetSocketNameFailed(e)),
117        }
118    } else {
119        let socket_name: PathBuf = std::env::var_os("WAYLAND_DISPLAY")
120            .unwrap_or_else(|| {
121                log::warn!("WAYLAND_DISPLAY is not set! Defaulting to wayland-0");
122                std::ffi::OsString::from("wayland-0")
123            })
124            .into();
125
126        let socket_path = if socket_name.is_absolute() {
127            socket_name
128        } else {
129            let mut socket_path: PathBuf = std::env::var_os("XDG_RUNTIME_DIR")
130                .unwrap_or_else(|| {
131                    log::warn!("XDG_RUNTIME_DIR is not set! Defaulting to /run/user/UID");
132                    let uid = rustix::process::getuid();
133                    let mut p: PathBuf = ["run", "user"].iter().collect();
134                    p.push(format!("{}", uid.as_raw()));
135                    p.into_os_string()
136                })
137                .into();
138
139            socket_path.push(socket_name);
140            socket_path
141        };
142
143        let unix_addr =
144            rustix::net::SocketAddrUnix::new(socket_path.display().to_string().as_str()).unwrap();
145        let socket = rustix::net::socket_with(
146            rustix::net::AddressFamily::UNIX,
147            rustix::net::SocketType::STREAM,
148            rustix::net::SocketFlags::CLOEXEC,
149            None,
150        )
151        .map_err(ConnectionError::SocketCreationFailed)?;
152
153        rustix::net::connect(&socket, &unix_addr).map_err(ConnectionError::ConnectionFailed)?;
154        Ok(Waybackend::new(socket))
155    }
156}
157
158#[derive(Debug)]
159pub enum RoundtripError {
160    WireError(wire::Error),
161    WaylandError((ObjectId, u32, String)),
162    MessageFromUnknownObject(ObjectId),
163    UnexpectedDeleteId(u32),
164}
165
166impl std::fmt::Display for RoundtripError {
167    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
168        match self {
169            RoundtripError::WireError(error) => {
170                write!(f, "roundtrip failed due to wayland wire error: {error}")
171            }
172            RoundtripError::MessageFromUnknownObject(id) => {
173                write!(f, "received message from unknown object of id: {id}")
174            }
175            RoundtripError::UnexpectedDeleteId(id) => {
176                write!(
177                    f,
178                    "Received a delete_id message from the display for id: {id}.\n\
179                    This should never happen during the roundtrip initialization.\n\
180                    This wayland implementation is probably fucked."
181                )
182            }
183            RoundtripError::WaylandError((id, code, msg)) => write!(
184                f,
185                "Wayland protocol error. Object: {id}. Code: {code}. Message: {msg}"
186            ),
187        }
188    }
189}
190
191impl std::error::Error for RoundtripError {}
192
193#[derive(Debug)]
194/// A wayland global. Use this to bind the interfaces you want
195pub struct Global {
196    name: u32,
197    interface: String,
198    version: u32,
199}
200
201impl Global {
202    #[inline]
203    pub fn name(&self) -> u32 {
204        self.name
205    }
206
207    #[inline]
208    pub fn interface(&self) -> &str {
209        &self.interface
210    }
211
212    #[inline]
213    pub fn version(&self) -> u32 {
214        self.version
215    }
216
217    #[inline]
218    pub fn bind<T: Copy + PartialEq>(
219        &self,
220        backend: &mut crate::Waybackend,
221        registry: ObjectId,
222        objman: &mut objman::ObjectManager<T>,
223        object: T,
224    ) -> Result<(), wire::Error> {
225        let id = objman.create(object);
226        wayland::wl_registry::req::bind(
227            backend,
228            registry,
229            self.name,
230            id,
231            &self.interface,
232            self.version,
233        )
234    }
235}
236
237struct GlobalHandler {
238    globals: Vec<Global>,
239    error: Option<RoundtripError>,
240    done: bool,
241    delete_callback: bool,
242}
243
244impl GlobalHandler {
245    fn new() -> Self {
246        Self {
247            globals: Vec::new(),
248            error: None,
249            done: false,
250            delete_callback: false,
251        }
252    }
253}
254
255impl wayland::wl_display::EvHandler for GlobalHandler {
256    fn error(&mut self, _: ObjectId, object_id: ObjectId, code: u32, message: &str) {
257        self.error = Some(RoundtripError::WaylandError((
258            object_id,
259            code,
260            message.to_string(),
261        )));
262    }
263
264    fn delete_id(&mut self, _: ObjectId, id: u32) {
265        if id != 3 {
266            self.error = Some(RoundtripError::UnexpectedDeleteId(id));
267        } else {
268            self.delete_callback = true;
269        }
270    }
271}
272
273impl wayland::wl_registry::EvHandler for GlobalHandler {
274    fn global(&mut self, _: ObjectId, name: u32, interface: &str, version: u32) {
275        self.globals.push(Global {
276            name,
277            interface: interface.to_string(),
278            version,
279        });
280    }
281
282    fn global_remove(&mut self, _: ObjectId, name: u32) {
283        self.globals.retain(|g| g.name != name)
284    }
285}
286
287impl wayland::wl_callback::EvHandler for GlobalHandler {
288    fn done(&mut self, _: ObjectId, _: u32) {
289        self.done = true;
290    }
291}
292
293/// Does a roundtrip to gather all the globals during program initialization
294///
295/// We return: a list of globals and whether or not you should delete the callback with id
296/// `callback_id`.
297#[inline]
298pub fn roundtrip(
299    backend: &mut Waybackend,
300    receiver: &mut wire::Receiver,
301    registry_id: ObjectId,
302    callback_id: ObjectId,
303) -> Result<(Vec<Global>, bool), RoundtripError> {
304    let mut global_handler = GlobalHandler::new();
305    wayland::wl_display::req::get_registry(backend, WL_DISPLAY, registry_id)
306        .map_err(RoundtripError::WireError)?;
307    wayland::wl_display::req::sync(backend, WL_DISPLAY, callback_id)
308        .map_err(RoundtripError::WireError)?;
309    backend.flush().map_err(RoundtripError::WireError)?;
310
311    while !global_handler.done && global_handler.error.is_none() {
312        let mut msg = receiver
313            .recv(&backend.wayland_fd)
314            .map_err(RoundtripError::WireError)?;
315        while msg.has_next().map_err(RoundtripError::WireError)? {
316            match msg.sender_id() {
317                WL_DISPLAY => wayland::wl_display::event(&mut global_handler, &mut msg)
318                    .map_err(RoundtripError::WireError)?,
319                id if id == registry_id => {
320                    wayland::wl_registry::event(&mut global_handler, &mut msg)
321                        .map_err(RoundtripError::WireError)?
322                }
323                id if id == callback_id => {
324                    wayland::wl_callback::event(&mut global_handler, &mut msg)
325                        .map_err(RoundtripError::WireError)?
326                }
327                otherwise => return Err(RoundtripError::MessageFromUnknownObject(otherwise)),
328            }
329        }
330    }
331
332    if let Some(error) = global_handler.error {
333        return Err(error);
334    }
335
336    Ok((global_handler.globals, global_handler.delete_callback))
337}
338
339#[macro_export]
340macro_rules! bind_globals {
341    (
342        $backend:ident,
343        $objman:ident,
344        $registry:ident,
345        $globals:ident,
346        $(($interface:ident, $object:path)),*$(,)?
347    ) => {
348        for global in $globals.iter() {
349            match global.interface() {
350                $($interface::NAME => global.bind(&mut $backend, $registry, &mut $objman, $object)?),*,
351                _ => (),
352            }
353        }
354    }
355}