use crate::ClipboardError;
use super::wayland_socket::WaylandSocket;
use super::wayland_wire::{encode_message, encode_u32, parse_string, parse_u32};
const WL_DISPLAY_ID: u32 = 1; const WL_REGISTRY_ID: u32 = 2; const WL_CALLBACK_ID: u32 = 3;
const WL_DISPLAY_SYNC: u16 = 0;
const WL_DISPLAY_GET_REGISTRY: u16 = 1;
const WL_DISPLAY_ERROR: u16 = 0;
const WL_REGISTRY_GLOBAL: u16 = 0;
const WL_CALLBACK_DONE: u16 = 0;
#[derive(Debug, Clone)]
pub struct Global {
pub name: u32,
pub interface: String,
pub version: u32,
}
pub struct WaylandConnection {
socket: WaylandSocket,
globals: Vec<Global>,
next_id: u32,
}
impl WaylandConnection {
pub(crate) fn open() -> Result<Self, ClipboardError> {
let mut socket = WaylandSocket::connect()?;
{
let mut args = Vec::new();
encode_u32(&mut args, WL_REGISTRY_ID);
let msg = encode_message(WL_DISPLAY_ID, WL_DISPLAY_GET_REGISTRY, &args);
socket.send(&msg, &[])?;
}
{
let mut args = Vec::new();
encode_u32(&mut args, WL_CALLBACK_ID);
let msg = encode_message(WL_DISPLAY_ID, WL_DISPLAY_SYNC, &args);
socket.send(&msg, &[])?;
}
let mut globals = Vec::new();
let mut done = false;
for _ in 0..4096 {
socket.recv(true)?;
while let Some((hdr, args)) = socket.next_message() {
match (hdr.object_id, hdr.opcode) {
(WL_REGISTRY_ID, WL_REGISTRY_GLOBAL) => {
if let Some(g) = parse_global(&args) {
globals.push(g);
}
}
(WL_CALLBACK_ID, WL_CALLBACK_DONE) => {
done = true;
}
(WL_DISPLAY_ID, WL_DISPLAY_ERROR) => {
let msg = parse_display_error(&args);
return Err(ClipboardError::io(std::io::Error::other(format!(
"wl_display error: {msg}"
))));
}
_ => {}
}
if done {
break;
}
}
if done {
break;
}
}
if !done {
return Err(ClipboardError::io_other(
"timed out waiting for wl_callback.done",
));
}
Ok(Self {
socket,
globals,
next_id: 4,
})
}
#[allow(dead_code)]
pub(crate) fn globals(&self) -> &[Global] {
&self.globals
}
pub(crate) fn find_global(&self, interface: &str) -> Option<&Global> {
self.globals.iter().find(|g| g.interface == interface)
}
#[allow(dead_code)]
pub(crate) fn alloc_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
#[allow(dead_code)]
pub(crate) fn socket_mut(&mut self) -> &mut WaylandSocket {
&mut self.socket
}
pub(crate) fn into_parts(self) -> (WaylandSocket, u32) {
(self.socket, self.next_id)
}
}
fn parse_global(args: &[u8]) -> Option<Global> {
let (name, rest) = parse_u32(args)?;
let (interface, rest) = parse_string(rest)?;
let (version, _) = parse_u32(rest)?;
Some(Global {
name,
interface: interface.to_owned(),
version,
})
}
fn parse_display_error(args: &[u8]) -> String {
let Some((obj_id, rest)) = parse_u32(args) else {
return "(malformed error event)".to_owned();
};
let Some((code, rest)) = parse_u32(rest) else {
return format!("object={obj_id} (malformed code)");
};
let msg = if let Some((s, _)) = parse_string(rest) {
s.to_owned()
} else {
"(no message)".to_owned()
};
format!("object={obj_id} code={code} msg={msg:?}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_globals_if_compositor_available() {
if std::env::var_os("WAYLAND_DISPLAY").is_none() {
eprintln!("SKIP probe_globals: no WAYLAND_DISPLAY");
return;
}
match WaylandConnection::open() {
Ok(conn) => {
for g in conn.globals() {
eprintln!("global: {} v{} (id={})", g.interface, g.version, g.name);
}
assert!(!conn.globals().is_empty(), "no globals returned");
}
Err(ClipboardError::NoDisplay) | Err(ClipboardError::Io(_)) => {
eprintln!("SKIP probe_globals: connection failed");
}
Err(e) => panic!("unexpected error: {e}"),
}
}
}