use std::{
env, fmt,
io::ErrorKind,
os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
os::unix::net::UnixStream,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use wayland_backend::{
client::{Backend, InvalidId, ObjectData, ObjectId, ReadEventsGuard, WaylandError},
protocol::{ObjectInfo, ProtocolError},
};
use crate::{protocol::wl_display::WlDisplay, EventQueue, Proxy};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Connection {
pub(crate) backend: Backend,
}
impl Connection {
pub fn connect_to_env() -> Result<Self, ConnectError> {
let stream = if let Ok(txt) = env::var("WAYLAND_SOCKET") {
let fd = txt.parse::<i32>().map_err(|_| ConnectError::InvalidFd)?;
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
env::remove_var("WAYLAND_SOCKET");
let flags = rustix::io::fcntl_getfd(&fd);
let result = flags
.map(|f| f | rustix::io::FdFlags::CLOEXEC)
.and_then(|f| rustix::io::fcntl_setfd(&fd, f));
match result {
Ok(_) => {
UnixStream::from(fd)
}
Err(_) => {
return Err(ConnectError::InvalidFd);
}
}
} else {
let socket_name = env::var_os("WAYLAND_DISPLAY")
.map(Into::<PathBuf>::into)
.ok_or(ConnectError::NoCompositor)?;
let socket_path = if socket_name.is_absolute() {
socket_name
} else {
let mut socket_path = env::var_os("XDG_RUNTIME_DIR")
.map(Into::<PathBuf>::into)
.ok_or(ConnectError::NoCompositor)?;
if !socket_path.is_absolute() {
return Err(ConnectError::NoCompositor);
}
socket_path.push(socket_name);
socket_path
};
UnixStream::connect(socket_path).map_err(|_| ConnectError::NoCompositor)?
};
let backend = Backend::connect(stream).map_err(|_| ConnectError::NoWaylandLib)?;
Ok(Self { backend })
}
pub fn from_socket(stream: UnixStream) -> Result<Self, ConnectError> {
let backend = Backend::connect(stream).map_err(|_| ConnectError::NoWaylandLib)?;
Ok(Self { backend })
}
pub fn display(&self) -> WlDisplay {
let display_id = self.backend.display_id();
Proxy::from_id(self, display_id).unwrap()
}
pub fn new_event_queue<State>(&self) -> EventQueue<State> {
EventQueue::new(self.clone())
}
pub fn from_backend(backend: Backend) -> Self {
Self { backend }
}
pub fn backend(&self) -> Backend {
self.backend.clone()
}
pub fn flush(&self) -> Result<(), WaylandError> {
self.backend.flush()
}
#[must_use]
pub fn prepare_read(&self) -> Option<ReadEventsGuard> {
self.backend.prepare_read()
}
pub fn roundtrip(&self) -> Result<usize, WaylandError> {
let done = Arc::new(SyncData::default());
let display = self.display();
self.send_request(
&display,
crate::protocol::wl_display::Request::Sync {},
Some(done.clone()),
)
.map_err(|_| WaylandError::Io(rustix::io::Errno::PIPE.into()))?;
let mut dispatched = 0;
loop {
self.backend.flush()?;
if let Some(guard) = self.backend.prepare_read() {
dispatched += blocking_read(guard)?;
} else {
dispatched += self.backend.dispatch_inner_queue()?;
}
if done.done.load(Ordering::Relaxed) {
break;
}
}
Ok(dispatched)
}
pub fn protocol_error(&self) -> Option<ProtocolError> {
match self.backend.last_error()? {
WaylandError::Protocol(err) => Some(err),
WaylandError::Io(_) => None,
}
}
pub fn send_request<I: Proxy>(
&self,
proxy: &I,
request: I::Request<'_>,
data: Option<Arc<dyn ObjectData>>,
) -> Result<ObjectId, InvalidId> {
let (msg, child_spec) = proxy.write_request(self, request)?;
let msg = msg.map_fd(|fd| fd.as_raw_fd());
self.backend.send_request(msg, data, child_spec)
}
pub fn object_info(&self, id: ObjectId) -> Result<ObjectInfo, InvalidId> {
self.backend.info(id)
}
pub fn get_object_data(&self, id: ObjectId) -> Result<Arc<dyn ObjectData>, InvalidId> {
self.backend.get_data(id)
}
#[cfg(feature = "libwayland_1_23")]
pub fn set_max_buffer_size(&self, max_buffer_size: Option<usize>) {
self.backend.set_max_buffer_size(max_buffer_size);
}
}
pub(crate) fn blocking_read(guard: ReadEventsGuard) -> Result<usize, WaylandError> {
let fd = guard.connection_fd();
let mut fds = [rustix::event::PollFd::new(
&fd,
rustix::event::PollFlags::IN | rustix::event::PollFlags::ERR,
)];
loop {
match rustix::event::poll(&mut fds, None) {
Ok(_) => break,
Err(rustix::io::Errno::INTR) => continue,
Err(e) => return Err(WaylandError::Io(e.into())),
}
}
match guard.read() {
Ok(n) => Ok(n),
Err(WaylandError::Io(e)) if e.kind() == ErrorKind::WouldBlock => Ok(0),
Err(e) => Err(e),
}
}
#[derive(Debug)]
pub enum ConnectError {
NoWaylandLib,
NoCompositor,
InvalidFd,
}
impl std::error::Error for ConnectError {}
impl fmt::Display for ConnectError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConnectError::NoWaylandLib => {
write!(f, "The wayland library could not be loaded")
}
ConnectError::NoCompositor => {
write!(f, "Could not find wayland compositor")
}
ConnectError::InvalidFd => {
write!(f, "WAYLAND_SOCKET was set but contained garbage")
}
}
}
}
impl AsFd for Connection {
fn as_fd(&self) -> BorrowedFd<'_> {
self.backend.poll_fd()
}
}
#[derive(Default)]
pub(crate) struct SyncData {
pub(crate) done: AtomicBool,
}
impl ObjectData for SyncData {
fn event(
self: Arc<Self>,
_handle: &Backend,
_msg: wayland_backend::protocol::Message<ObjectId, OwnedFd>,
) -> Option<Arc<dyn ObjectData>> {
self.done.store(true, Ordering::Relaxed);
None
}
fn destroyed(&self, _: ObjectId) {}
}