use core::ffi::{CStr, c_char, c_int, c_long, c_void};
use crate::linux::{CLOCK_MONOTONIC, Nfds};
use core::ptr;
use std::sync::OnceLock;
#[repr(C)]
pub struct WlProxy {
_opaque: [u8; 0],
}
#[repr(C)]
pub struct WlDisplay {
_opaque: [u8; 0],
}
#[repr(C)]
pub struct WlArray {
pub size: usize,
pub alloc: usize,
pub data: *mut c_void,
}
#[repr(C)]
pub struct WlMessage {
pub name: *const c_char,
pub signature: *const c_char,
pub types: *const *const WlInterface,
}
unsafe impl Sync for WlMessage {}
impl WlMessage {
#[must_use]
pub const fn new(
name: &'static CStr,
signature: &'static CStr,
types: *const *const WlInterface,
) -> Self {
Self {
name: name.as_ptr(),
signature: signature.as_ptr(),
types,
}
}
}
#[repr(C)]
pub struct WlInterface {
pub name: *const c_char,
pub version: c_int,
pub method_count: c_int,
pub methods: *const WlMessage,
pub event_count: c_int,
pub events: *const WlMessage,
}
unsafe impl Sync for WlInterface {}
impl WlInterface {
#[must_use]
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "counts come from array lengths in generated code; the largest \
interface in any Wayland protocol has fewer than 100 messages"
)]
pub const fn new(
name: &'static CStr,
version: u32,
methods: &'static [WlMessage],
events: &'static [WlMessage],
) -> Self {
Self {
name: name.as_ptr(),
version: version as c_int,
method_count: methods.len() as c_int,
methods: methods.as_ptr(),
event_count: events.len() as c_int,
events: events.as_ptr(),
}
}
}
#[repr(transparent)]
pub struct WlInterfacePtr(pub *const WlInterface);
unsafe impl Sync for WlInterfacePtr {}
impl WlInterfacePtr {
pub const NULL: Self = Self(ptr::null());
#[must_use]
pub const fn new(interface: &'static WlInterface) -> Self {
Self(ptr::from_ref(interface))
}
}
#[repr(C)]
pub union WlArgument {
pub i: i32,
pub u: u32,
pub f: i32,
pub s: *const c_char,
pub o: *mut WlProxy,
pub n: u32,
pub a: *const WlArray,
pub h: i32,
}
pub type DispatcherFn = unsafe extern "C" fn(
*const c_void,
*mut c_void,
u32,
*const WlMessage,
*mut WlArgument,
) -> c_int;
pub const MARSHAL_FLAG_DESTROY: u32 = 1;
unsafe extern "C" {
fn dlopen(filename: *const c_char, flags: c_int) -> *mut c_void;
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
fn dlerror() -> *const c_char;
fn poll(fds: *mut PollFd, nfds: Nfds, timeout: c_int) -> c_int;
fn clock_gettime(clock_id: c_int, tp: *mut Timespec) -> c_int;
}
#[repr(C)]
struct PollFd {
fd: c_int,
events: i16,
revents: i16,
}
#[repr(C)]
struct Timespec {
tv_sec: c_long,
tv_nsec: c_long,
}
const RTLD_NOW: c_int = 2;
const POLLIN: i16 = 0x001;
const POLLOUT: i16 = 0x004;
mod prototype {
use super::{DispatcherFn, WlArgument, WlDisplay, WlInterface, WlProxy, c_char, c_int, c_void};
pub type DisplayConnect = unsafe extern "C" fn(*const c_char) -> *mut WlDisplay;
pub type DisplayVoid = unsafe extern "C" fn(*mut WlDisplay);
pub type DisplayInt = unsafe extern "C" fn(*mut WlDisplay) -> c_int;
pub type ProxyMarshalArrayFlags = unsafe extern "C" fn(
*mut WlProxy,
u32,
*const WlInterface,
u32,
u32,
*mut WlArgument,
) -> *mut WlProxy;
pub type ProxyAddDispatcher =
unsafe extern "C" fn(*mut WlProxy, DispatcherFn, *const c_void, *mut c_void) -> c_int;
pub type ProxyVoid = unsafe extern "C" fn(*mut WlProxy);
pub type ProxyU32 = unsafe extern "C" fn(*mut WlProxy) -> u32;
}
pub struct Lib {
pub display_connect: prototype::DisplayConnect,
pub display_disconnect: prototype::DisplayVoid,
pub display_get_fd: prototype::DisplayInt,
pub display_roundtrip: prototype::DisplayInt,
pub display_dispatch_pending: prototype::DisplayInt,
pub display_flush: prototype::DisplayInt,
pub display_prepare_read: prototype::DisplayInt,
pub display_read_events: prototype::DisplayInt,
pub display_cancel_read: prototype::DisplayVoid,
pub display_get_error: prototype::DisplayInt,
pub proxy_marshal_array_flags: prototype::ProxyMarshalArrayFlags,
pub proxy_add_dispatcher: prototype::ProxyAddDispatcher,
pub proxy_destroy: prototype::ProxyVoid,
pub proxy_get_version: prototype::ProxyU32,
}
impl core::fmt::Debug for Lib {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Lib(libwayland-client)")
}
}
const SONAMES: [&CStr; 2] = [c"libwayland-client.so.0", c"libwayland-client.so"];
static LIB: OnceLock<Result<Lib, String>> = OnceLock::new();
pub fn load() -> Result<&'static Lib, &'static str> {
match LIB.get_or_init(load_uncached) {
Ok(lib) => Ok(lib),
Err(message) => Err(message.as_str()),
}
}
#[inline]
#[must_use]
pub fn lib() -> &'static Lib {
load().expect("libwayland-client is loaded before any protocol code runs")
}
fn load_uncached() -> Result<Lib, String> {
let mut handle = ptr::null_mut();
for soname in SONAMES {
handle = unsafe { dlopen(soname.as_ptr(), RTLD_NOW) };
if !handle.is_null() {
break;
}
}
if handle.is_null() {
let detail = unsafe {
let message = dlerror();
if message.is_null() {
"not found".to_string()
} else {
CStr::from_ptr(message).to_string_lossy().into_owned()
}
};
return Err(format!("cannot load libwayland-client.so.0: {detail}"));
}
macro_rules! symbol {
($name:literal, $ty:ty) => {{
let raw = unsafe { dlsym(handle, concat!($name, "\0").as_ptr().cast()) };
if raw.is_null() {
return Err(format!("libwayland-client.so.0 has no symbol {}", $name));
}
unsafe { core::mem::transmute::<*mut c_void, $ty>(raw) }
}};
}
Ok(Lib {
display_connect: symbol!("wl_display_connect", prototype::DisplayConnect),
display_disconnect: symbol!("wl_display_disconnect", prototype::DisplayVoid),
display_get_fd: symbol!("wl_display_get_fd", prototype::DisplayInt),
display_roundtrip: symbol!("wl_display_roundtrip", prototype::DisplayInt),
display_dispatch_pending: symbol!("wl_display_dispatch_pending", prototype::DisplayInt),
display_flush: symbol!("wl_display_flush", prototype::DisplayInt),
display_prepare_read: symbol!("wl_display_prepare_read", prototype::DisplayInt),
display_read_events: symbol!("wl_display_read_events", prototype::DisplayInt),
display_cancel_read: symbol!("wl_display_cancel_read", prototype::DisplayVoid),
display_get_error: symbol!("wl_display_get_error", prototype::DisplayInt),
proxy_marshal_array_flags: symbol!(
"wl_proxy_marshal_array_flags",
prototype::ProxyMarshalArrayFlags
),
proxy_add_dispatcher: symbol!("wl_proxy_add_dispatcher", prototype::ProxyAddDispatcher),
proxy_destroy: symbol!("wl_proxy_destroy", prototype::ProxyVoid),
proxy_get_version: symbol!("wl_proxy_get_version", prototype::ProxyU32),
})
}
#[inline]
pub unsafe fn marshal(
proxy: *mut WlProxy,
opcode: u32,
interface: *const WlInterface,
version: u32,
flags: u32,
args: *mut WlArgument,
) -> *mut WlProxy {
unsafe { (lib().proxy_marshal_array_flags)(proxy, opcode, interface, version, flags, args) }
}
#[inline]
#[must_use]
pub unsafe fn proxy_version(proxy: *mut WlProxy) -> u32 {
unsafe { (lib().proxy_get_version)(proxy) }
}
#[inline]
#[must_use]
pub unsafe fn cstr_arg<'a>(ptr: *const c_char) -> &'a CStr {
if ptr.is_null() {
return c"";
}
unsafe { CStr::from_ptr(ptr) }
}
#[inline]
#[must_use]
pub unsafe fn cstr_arg_opt<'a>(ptr: *const c_char) -> Option<&'a CStr> {
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) })
}
#[inline]
#[must_use]
pub unsafe fn array_arg<'a>(ptr: *const WlArray) -> &'a [u8] {
if ptr.is_null() {
return &[];
}
unsafe {
let array = &*ptr;
if array.data.is_null() || array.size == 0 {
&[]
} else {
core::slice::from_raw_parts(array.data.cast::<u8>(), array.size)
}
}
}
#[inline]
#[must_use]
pub const fn display_as_proxy(display: *mut WlDisplay) -> *mut WlProxy {
display.cast()
}
pub fn poll_readable(fd: c_int, timeout_ms: c_int) -> bool {
let mut fds = PollFd {
fd,
events: POLLIN,
revents: 0,
};
let result = unsafe { poll(&raw mut fds, 1, timeout_ms) };
result > 0 && fds.revents & POLLIN != 0
}
#[must_use]
pub fn poll_ready(fd: c_int, for_write: bool, timeout_ms: c_int) -> bool {
let mut fds = PollFd {
fd,
events: if for_write { POLLOUT } else { POLLIN },
revents: 0,
};
let result = unsafe { poll(&raw mut fds, 1, timeout_ms) };
result > 0 && fds.revents != 0
}
#[must_use]
pub fn poll_readable_with(primary: c_int, aux: &[c_int], timeout_ms: c_int) -> bool {
if aux.is_empty() {
return poll_readable(primary, timeout_ms);
}
let mut fds: Vec<PollFd> = core::iter::once(PollFd {
fd: primary,
events: POLLIN,
revents: 0,
})
.chain(aux.iter().map(|fd| PollFd {
fd: *fd,
events: POLLIN | POLLOUT,
revents: 0,
}))
.collect();
let count = Nfds::try_from(fds.len()).unwrap_or(1);
let result = unsafe { poll(fds.as_mut_ptr(), count, timeout_ms) };
result > 0 && fds[0].revents & POLLIN != 0
}
#[must_use]
pub fn monotonic_nanos() -> u64 {
let mut ts = Timespec {
tv_sec: 0,
tv_nsec: 0,
};
if unsafe { clock_gettime(CLOCK_MONOTONIC, &raw mut ts) } != 0 {
return 0;
}
let seconds = u64::try_from(ts.tv_sec).unwrap_or(0);
let nanos = u64::try_from(ts.tv_nsec).unwrap_or(0);
seconds * 1_000_000_000 + nanos
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_descriptor_types_match_the_c_layout() {
assert_eq!(
size_of::<WlMessage>(),
3 * size_of::<*const c_void>(),
"struct wl_message is name + signature + types"
);
assert_eq!(
size_of::<WlInterface>(),
5 * size_of::<*const c_void>(),
"struct wl_interface is name, version+method_count, methods, \
event_count+padding, events"
);
assert_eq!(
size_of::<WlArgument>(),
size_of::<*const c_void>(),
"union wl_argument is one pointer wide"
);
assert_eq!(align_of::<WlArgument>(), align_of::<*const c_void>());
assert_eq!(size_of::<WlInterfacePtr>(), size_of::<*const c_void>());
}
#[test]
fn the_monotonic_clock_advances_and_is_not_the_epoch() {
let first = monotonic_nanos();
assert!(first > 0, "CLOCK_MONOTONIC is available on Linux");
let second = monotonic_nanos();
assert!(second >= first, "CLOCK_MONOTONIC never goes backwards");
}
#[test]
fn a_null_array_argument_is_an_empty_slice_rather_than_a_crash() {
assert_eq!(unsafe { array_arg(ptr::null()) }, &[] as &[u8]);
let empty = WlArray {
size: 0,
alloc: 0,
data: ptr::null_mut(),
};
assert_eq!(unsafe { array_arg(&raw const empty) }, &[] as &[u8]);
let bytes = [1u8, 2, 3, 4];
let array = WlArray {
size: bytes.len(),
alloc: bytes.len(),
data: bytes.as_ptr().cast_mut().cast(),
};
assert_eq!(unsafe { array_arg(&raw const array) }, &bytes);
}
#[test]
fn null_strings_degrade_to_empty_rather_than_panicking() {
unsafe {
assert_eq!(cstr_arg(ptr::null()), c"");
assert_eq!(cstr_arg_opt(ptr::null()), None);
assert_eq!(cstr_arg(c"DP-2".as_ptr()), c"DP-2");
}
}
#[test]
fn polling_a_closed_descriptor_does_not_block() {
assert!(!poll_readable(-1, 0));
assert!(!poll_ready(-1, true, 0));
assert!(!poll_ready(-1, false, 0));
assert!(!poll_readable_with(-1, &[-1, -1], 0));
}
#[test]
fn a_fresh_pipe_is_writable_and_becomes_readable_when_written() {
use std::io::Write as _;
use std::os::fd::AsRawFd as _;
let (reader, mut writer) = std::io::pipe().expect("pipe");
assert!(
poll_ready(writer.as_raw_fd(), true, 0),
"an empty pipe takes a PIPE_BUF write without blocking"
);
assert!(
!poll_ready(reader.as_raw_fd(), false, 0),
"nothing in it yet"
);
writer.write_all(b"x").expect("write");
assert!(poll_ready(reader.as_raw_fd(), false, 0));
let (empty, closed) = std::io::pipe().expect("pipe");
drop(closed);
assert!(
!poll_readable(empty.as_raw_fd(), 0),
"no POLLIN, only POLLHUP"
);
assert!(poll_ready(empty.as_raw_fd(), false, 0));
assert!(!poll_readable_with(-1, &[reader.as_raw_fd()], 0));
assert!(poll_readable_with(reader.as_raw_fd(), &[-1], 0));
}
}