#![no_std]
extern crate alloc;
use rustix::fd::OwnedFd;
use types::ObjectId;
mod wayland;
pub use bitflags;
pub mod objman;
pub use rustix;
pub mod shm;
pub mod types;
pub mod wire;
pub use rustix::io::Errno;
pub struct Waybackend {
pub wire_msg_builder: wire::MessageBuilder,
pub wayland_fd: OwnedFd,
}
impl Waybackend {
#[inline]
#[must_use]
fn new(wayland_fd: OwnedFd) -> Self {
Self {
wire_msg_builder: wire::MessageBuilder::new(),
wayland_fd,
}
}
#[inline]
pub fn flush(&mut self) -> rustix::io::Result<()> {
self.wire_msg_builder.flush(&self.wayland_fd)
}
}
use core::num::NonZeroU32;
pub const WL_DISPLAY: types::ObjectId = types::ObjectId::new(NonZeroU32::new(1).unwrap());
#[derive(Debug)]
pub enum ConnectionError {
InvalidWaylandSocketEnvVar,
InvalidSocketAddrFamily(rustix::net::AddressFamily),
GetSocketNameFailed(rustix::io::Errno),
SocketCreationFailed(rustix::io::Errno),
ConnectionFailed(rustix::io::Errno),
}
impl core::fmt::Display for ConnectionError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
ConnectionError::InvalidWaylandSocketEnvVar => f.write_str(
"WAYLAND_SOCKET environment variable contains a value we failed to parse",
),
ConnectionError::InvalidSocketAddrFamily(actual) => write!(
f,
"socket address in WAYLAND_SOCKET is not a unix socket. It's actual address family is: {actual:?}"
),
ConnectionError::GetSocketNameFailed(errno) => {
write!(f, "failed to get socket name: {errno}")
}
ConnectionError::SocketCreationFailed(errno) => {
write!(f, "failed to create socket: {errno}")
}
ConnectionError::ConnectionFailed(errno) => {
write!(f, "failed to connect to the unix stream: {errno}")
}
}
}
}
impl core::error::Error for ConnectionError {}
#[cfg(feature = "libc")]
fn get_env(var: &core::ffi::CStr) -> Option<&'static core::ffi::CStr> {
unsafe {
let ptr = libc::getenv(var.as_ptr());
if !ptr.is_null() {
Some(core::ffi::CStr::from_ptr(ptr))
} else {
None
}
}
}
#[inline]
#[cfg(feature = "libc")]
pub fn connect<T: Copy + PartialEq>(
display: T,
) -> Result<(Waybackend, objman::ObjectManager<T>, wire::Receiver), ConnectionError> {
use ::alloc::string::ToString;
use ::alloc::vec::Vec;
use rustix::fd::{FromRawFd, OwnedFd};
use rustix::net::AddressFamily;
if let Some(txt) = get_env(c"WAYLAND_SOCKET") {
let fd = txt
.to_str()
.map(str::parse::<i32>)
.map_err(|_| ConnectionError::InvalidWaylandSocketEnvVar)?
.map_err(|_| ConnectionError::InvalidWaylandSocketEnvVar)?;
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
match rustix::net::getsockname(&fd) {
Ok(socket_addr) => {
if socket_addr.address_family() == AddressFamily::UNIX {
Ok(unsafe { connect_from_fd(display, fd) })
} else {
Err(ConnectionError::InvalidSocketAddrFamily(
socket_addr.address_family(),
))
}
}
Err(e) => Err(ConnectionError::GetSocketNameFailed(e)),
}
} else {
let socket_name = get_env(c"WAYLAND_DISPLAY").unwrap_or_else(|| {
log::warn!("WAYLAND_DISPLAY is not set! Defaulting to wayland-0");
c"wayland-0"
});
let unix_addr = if socket_name.to_bytes()[0] == b'/' {
rustix::net::SocketAddrUnix::new(socket_name).unwrap()
} else {
let mut socket_fullpath = Vec::new();
match get_env(c"XDG_RUNTIME_DIR") {
Some(socket_path) => {
socket_fullpath.extend_from_slice(socket_path.to_bytes());
socket_fullpath.push(b'/');
}
None => {
log::warn!("XDG_RUNTIME_DIR is not set! Defaulting to /run/user/UID");
let uid = rustix::process::getuid();
socket_fullpath.extend_from_slice(b"/run/user/");
socket_fullpath.extend_from_slice(uid.as_raw().to_string().as_bytes());
socket_fullpath.push(b'/');
}
}
socket_fullpath.extend_from_slice(socket_name.to_bytes());
rustix::net::SocketAddrUnix::new(socket_fullpath.as_slice()).unwrap()
};
let socket = rustix::net::socket_with(
rustix::net::AddressFamily::UNIX,
rustix::net::SocketType::STREAM,
rustix::net::SocketFlags::CLOEXEC,
None,
)
.map_err(ConnectionError::SocketCreationFailed)?;
connect_to(display, socket, &unix_addr)
}
}
#[inline]
pub fn connect_to<T, Addr>(
display: T,
wayland_fd: OwnedFd,
addr: &Addr,
) -> Result<(Waybackend, objman::ObjectManager<T>, wire::Receiver), ConnectionError>
where
T: Copy + PartialEq,
Addr: rustix::net::addr::SocketAddrArg,
{
rustix::net::connect(&wayland_fd, addr).map_err(ConnectionError::ConnectionFailed)?;
let objman = objman::ObjectManager::new(display);
let receiver = wire::Receiver::new();
Ok((Waybackend::new(wayland_fd), objman, receiver))
}
#[inline]
pub unsafe fn connect_from_fd<T>(
display: T,
wayland_fd: OwnedFd,
) -> (Waybackend, objman::ObjectManager<T>, wire::Receiver)
where
T: Copy + PartialEq,
{
let objman = objman::ObjectManager::new(display);
let receiver = wire::Receiver::new();
(Waybackend::new(wayland_fd), objman, receiver)
}
#[derive(Debug)]
pub enum RoundtripError<'a> {
MessageFromUnknownObject(ObjectId),
RustixIoErrno(Errno),
UnexpectedDeleteId(u32),
UnexpectedGlobalRemove(u32),
WaylandError(ObjectId, u32, &'a str),
WireError(wire::Error),
}
impl<'a> core::fmt::Display for RoundtripError<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use RoundtripError::*;
match self {
MessageFromUnknownObject(id) => {
write!(f, "received message from unknown object of id: {id}")
}
RustixIoErrno(errno) => write!(f, "io error: {errno}"),
UnexpectedDeleteId(id) => {
write!(
f,
"Received a delete_id message from the display for id: {id}.\n\
This should never happen during the roundtrip initialization.\n\
This wayland implementation is probably fucked."
)
}
UnexpectedGlobalRemove(name) => write!(
f,
"Received a request to remove the {name} global. Per the protocol,\
we should not receive global_remove events from the registry\
before we getting the done callback event from the sync request",
),
WaylandError(id, code, msg) => write!(
f,
"Wayland protocol error. Object: {id}. Code: {code}. Message: {msg}"
),
WireError(error) => write!(f, "roundtrip failed due to wayland wire error: {error}"),
}
}
}
impl<'a> core::error::Error for RoundtripError<'a> {}
#[derive(Debug)]
pub struct Global<'a> {
name: u32,
interface: &'a str,
version: u32,
}
impl<'a> Global<'a> {
#[inline]
pub fn name(&self) -> u32 {
self.name
}
#[inline]
pub fn interface(&self) -> &str {
self.interface
}
#[inline]
pub fn version(&self) -> u32 {
self.version
}
#[inline]
pub fn bind<T: Copy + PartialEq>(
&self,
backend: &mut crate::Waybackend,
registry: ObjectId,
objman: &mut objman::ObjectManager<T>,
object: T,
) -> Result<(), Errno> {
let id = objman.create(object);
wayland::wl_registry::req::bind(
backend,
registry,
self.name,
id,
self.interface,
self.version,
)
}
}
#[inline]
pub fn roundtrip<'a>(
backend: &mut Waybackend,
receiver: &'a mut wire::Receiver,
registry_id: ObjectId,
callback_id: ObjectId,
mut global_handler: impl FnMut(&mut Waybackend, Global<'a>),
) -> Result<(), RoundtripError<'a>> {
use RoundtripError::*;
wayland::wl_display::req::get_registry(backend, WL_DISPLAY, registry_id)
.map_err(RustixIoErrno)?;
wayland::wl_display::req::sync(backend, WL_DISPLAY, callback_id).map_err(RustixIoErrno)?;
backend.flush().map_err(RustixIoErrno)?;
loop {
let mut msgs = receiver.recv(&backend.wayland_fd).map_err(WireError)?;
while let Some(sender_id) = msgs.next() {
let sender_id = sender_id.map_err(WireError)?;
match sender_id {
WL_DISPLAY => match msgs.op() {
0 => {
msgs.validate_len([false, false, true]).map_err(WireError)?;
let object_id = msgs
.next_object()
.ok_or(WireError(wire::Error::NullObjectId))?;
let code = msgs.next_u32();
let message = msgs.next_string().map_err(WireError)?;
return Err(WaylandError(object_id, code, message));
}
1 => {
let id = msgs.next_u32();
return Err(UnexpectedDeleteId(id));
}
otherwise => {
return Err(WireError(wire::Error::UnrecognizedEventOpCode(
"wl_display",
otherwise,
)));
}
},
id if id == registry_id => match msgs.op() {
0 => {
msgs.validate_len([false, true, false]).map_err(WireError)?;
let name = msgs.next_u32();
let interface = msgs.next_string().map_err(WireError)?;
let version = msgs.next_u32();
let global = Global {
name,
interface,
version,
};
global_handler(backend, global);
}
1 => {
let name = msgs.next_u32();
return Err(UnexpectedGlobalRemove(name));
}
otherwise => {
return Err(WireError(wire::Error::UnrecognizedEventOpCode(
"wl_registry",
otherwise,
)));
}
},
id if id == callback_id => match msgs.op() {
0 => {
let _callback_data = msgs.next_u32();
return Ok(());
}
otherwise => {
return Err(WireError(wire::Error::UnrecognizedEventOpCode(
"wl_callback",
otherwise,
)));
}
},
otherwise => return Err(MessageFromUnknownObject(otherwise)),
}
}
}
}
#[macro_export]
macro_rules! bind_globals {
(
$backend:ident,
$objman:ident,
$registry:ident,
$global:ident,
$fallback: expr,
$(($interface:ident, $object:path)),*$(,)?
) => {
match $global.interface() {
$(
$interface::NAME => {
$global.bind($backend, $registry, &mut $objman, $object)
.expect("failed to bind global");
}
)*
_ => $fallback($backend, &mut $objman, $global),
}
}
}
#[macro_export]
macro_rules! match_enum_with_interface {
($handler:ident, $object:ident, $msgs:ident, $(($variant:path, $interface:ident)),*$(,)?) => {
match $object {
$(
$variant => {
$interface::event(&mut $handler, &mut $msgs)
.expect("failed to dispatch event handler");
}
)*
}
}
}