examples/plop.rs
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
const ID_REGISTRY: u32 = 2;
const ID_CALLBACK: u32 = 3;
const ID_COMPOSITOR: u32 = 4;
const ID_WM_BASE: u32 = 5;
const ID_SHM: u32 = 6;
const ID_SURFACE: u32 = 7;
const ID_XDG_SURFACE: u32 = 8;
const ID_TOPLEVEL: u32 = 9;
const ID_SHM_POOL: u32 = 10;
const ID_BUFFER: u32 = 11;
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
fn main() {
let mut stream = UnixStream::connect(PathBuf::new().join("/run/user/1000/wayland-0"))
.expect("failed to connect");
// Bootstrap : récupère le registry puis sync
stream
.write_all(&msg::encode(1, 1, &[ID_REGISTRY]))
.unwrap(); // get_registry
stream
.write_all(&msg::encode(1, 0, &[ID_CALLBACK]))
.unwrap(); // sync
let (mut compositor_name, mut wm_base_name, mut shm_name) = (0u32, 0u32, 0u32);
pump(&mut stream, |_, object_id, opcode, payload| {
match (object_id, opcode) {
(ID_REGISTRY, 0) => {
println!("{}", payload.len());
let name = msg::read_u32(payload, 0);
let (iface, _) = msg::read_string(payload, 4);
match iface {
"wl_compositor" => compositor_name = name,
"xdg_wm_base" => wm_base_name = name,
"wl_shm" => shm_name = name,
_ => {}
}
false
}
(ID_CALLBACK, 0) => true,
_ => false,
}
});
println!(
"wl_compositor name={}, xdg_wm_base name={}, wl_shm={}",
compositor_name, wm_base_name, shm_name
);
// --- Phase 2 : bind wl_compositor et xdg_wm_base ---
// wl_registry.bind(name, interface, version, new_id) opcode=0
stream
.write_all(&msg::encode_with_str(
ID_REGISTRY,
0,
&[compositor_name],
"wl_compositor",
&[4, ID_COMPOSITOR],
))
.unwrap();
stream
.write_all(&msg::encode_with_str(
ID_REGISTRY,
0,
&[wm_base_name],
"xdg_wm_base",
&[2, ID_WM_BASE],
))
.unwrap();
stream
.write_all(&msg::encode_with_str(
ID_REGISTRY,
0,
&[shm_name],
"wl_shm",
&[1, ID_SHM],
))
.unwrap();
// --- Phase 3 : crée surface + xdg_surface + toplevel ---
stream
.write_all(&msg::encode(ID_COMPOSITOR, 0, &[ID_SURFACE]))
.unwrap(); // create_surface
stream
.write_all(&msg::encode(ID_WM_BASE, 2, &[ID_XDG_SURFACE, ID_SURFACE]))
.unwrap(); // get_xdg_surface
stream
.write_all(&msg::encode(ID_XDG_SURFACE, 1, &[ID_TOPLEVEL]))
.unwrap(); // get_toplevel
// set_title (opcode=2) et set_app_id (opcode=3)
stream
.write_all(&msg::encode_with_str(
ID_TOPLEVEL,
2,
&[],
"Hello Wayland",
&[],
))
.unwrap();
stream
.write_all(&msg::encode_with_str(
ID_TOPLEVEL,
3,
&[],
"wayland-light",
&[],
))
.unwrap();
// Premier commit : demande au compositor de configurer la fenêtre
stream.write_all(&msg::encode(ID_SURFACE, 6, &[])).unwrap(); // wl_surface.commit
// --- Phase 4 : attend xdg_surface.configure puis ack ---
pump(&mut stream, |stream, object_id, opcode, payload| {
match (object_id, opcode) {
(ID_WM_BASE, 0) => {
// xdg_wm_base.ping → pong obligatoire
let serial = msg::read_u32(payload, 0);
stream
.write_all(&msg::encode(ID_WM_BASE, 3, &[serial]))
.unwrap();
false
}
(ID_XDG_SURFACE, 0) => {
// xdg_surface.configure
let serial = msg::read_u32(payload, 0);
stream
.write_all(&msg::encode(ID_XDG_SURFACE, 4, &[serial]))
.unwrap(); // ack
stream.write_all(&msg::encode(ID_SURFACE, 6, &[])).unwrap(); // commit
println!("Window configured! serial={}", serial);
true // stop
}
_ => {
println!("server id: {object_id}");
false
}
}
});
// --- Phase 5 : crée le buffer SHM et dessine dedans ---
let size = (WIDTH * HEIGHT * 4) as usize;
let stride = WIDTH * 4;
let (memfd, pixels) = shm::create(size);
// Rempli d'un bleu-vert
unsafe {
for i in 0..(WIDTH * HEIGHT) as isize {
*pixels.offset(i) = 0xFF2080A0; // ARGB
}
}
// wl_shm.create_pool(new_id, fd, size) — le fd passe via SCM_RIGHTS
let create_pool = msg::encode(ID_SHM, 0, &[ID_SHM_POOL, size as u32]);
shm::send_with_fd(stream.as_raw_fd(), &create_pool, memfd);
// wl_shm_pool.create_buffer(new_id, offset, width, height, stride, format=XRGB8888=1)
stream
.write_all(&msg::encode(
ID_SHM_POOL,
0,
&[ID_BUFFER, 0, WIDTH, HEIGHT, stride, 1],
))
.unwrap();
// attach + damage + commit
stream
.write_all(&msg::encode(ID_SURFACE, 1, &[ID_BUFFER, 0, 0]))
.unwrap();
stream
.write_all(&msg::encode(ID_SURFACE, 2, &[0, 0, WIDTH, HEIGHT]))
.unwrap();
stream.write_all(&msg::encode(ID_SURFACE, 6, &[])).unwrap();
println!("Fenêtre créée. Appuie sur Entrée pour quitter.");
let _ = std::io::stdin().read_line(&mut String::new());
}
// Pompe les messages jusqu'à ce que le callback retourne true
fn pump<F: FnMut(&mut UnixStream, u32, u16, &[u8]) -> bool>(
stream: &mut UnixStream,
mut on_msg: F,
) {
let mut buf = vec![0u8; 4096];
let mut pending = Vec::<u8>::new();
loop {
let n = stream.read(&mut buf).expect("read");
pending.extend_from_slice(&buf[..n]);
let mut offset = 0;
while let Some((m, consumed)) = msg::decode(&pending[offset..]) {
let stop = on_msg(stream, m.object_id, m.opcode, m.payload);
offset += consumed;
if stop {
pending.drain(..offset);
return;
}
}
pending.drain(..offset);
}
}
mod msg {
// Encode un message Wayland : [object_id][size<<16|opcode][args...]
pub fn encode(object_id: u32, opcode: u16, args: &[u32]) -> Vec<u8> {
let size = (8 + args.len() * 4) as u32;
let mut buf = Vec::with_capacity(size as usize);
buf.extend_from_slice(&object_id.to_ne_bytes());
buf.extend_from_slice(&((size << 16) | opcode as u32).to_ne_bytes());
for arg in args {
buf.extend_from_slice(&arg.to_ne_bytes());
}
buf
}
// Un message décodé
pub struct Msg<'a> {
pub object_id: u32,
pub opcode: u16,
pub payload: &'a [u8],
}
// Décode le prochain message dans buf, retourne (msg, bytes_consommés) ou None
pub fn decode(buf: &[u8]) -> Option<(Msg<'_>, usize)> {
if buf.len() < 8 {
return None;
}
let object_id = u32::from_ne_bytes(buf[0..4].try_into().unwrap());
let word2 = u32::from_ne_bytes(buf[4..8].try_into().unwrap());
let size = (word2 >> 16) as usize;
let opcode = (word2 & 0xFFFF) as u16;
if buf.len() < size {
return None;
}
Some((
Msg {
object_id,
opcode,
payload: &buf[8..size],
},
size,
))
}
// Lit un u32 dans un payload à l'offset donné
pub fn read_u32(payload: &[u8], offset: usize) -> u32 {
u32::from_ne_bytes(payload[offset..offset + 4].try_into().unwrap())
}
// Lit une string Wayland (u32 len + bytes + padding) dans un payload
// Retourne (string, offset_après_padding)
pub fn read_string(payload: &[u8], offset: usize) -> (&str, usize) {
let len = read_u32(payload, offset) as usize; // inclut le \0
let s = std::str::from_utf8(&payload[offset + 4..offset + 4 + len - 1]).unwrap_or("?");
let next = offset + 4 + len + (4 - len % 4) % 4;
(s, next)
}
// Encode un message dont un argument est une string Wayland
// before/after = les args u32 autour de la string
pub fn encode_with_str(
object_id: u32,
opcode: u16,
before: &[u32],
s: &str,
after: &[u32],
) -> Vec<u8> {
let slen = s.len() + 1; // +1 pour le \0
let padding = (4 - slen % 4) % 4;
let size = 8 + before.len() * 4 + 4 + slen + padding + after.len() * 4;
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&object_id.to_ne_bytes());
buf.extend_from_slice(&((size as u32) << 16 | opcode as u32).to_ne_bytes());
for a in before {
buf.extend_from_slice(&a.to_ne_bytes());
}
buf.extend_from_slice(&(slen as u32).to_ne_bytes());
buf.extend_from_slice(s.as_bytes());
buf.push(0);
for _ in 0..padding {
buf.push(0);
}
for a in after {
buf.extend_from_slice(&a.to_ne_bytes());
}
buf
}
}
mod shm {
use std::os::unix::io::RawFd;
// Crée un memfd de la taille donnée et le mmap en lecture/écriture.
// Retourne (fd, pointeur vers les pixels).
pub fn create(size: usize) -> (RawFd, *mut u32) {
unsafe {
// memfd_create via syscall direct
let fd = libc::syscall(libc::SYS_memfd_create, b"shm\0".as_ptr(), 0) as RawFd;
assert!(fd >= 0, "memfd_create failed");
let ret = libc::ftruncate(fd, size as i64);
assert_eq!(ret, 0, "ftruncate failed");
let ptr = libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
assert_ne!(ptr, libc::MAP_FAILED, "mmap failed");
(fd, ptr as *mut u32)
}
}
// Envoie un message Wayland + un fd via SCM_RIGHTS sur le socket.
pub fn send_with_fd(socket_fd: RawFd, data: &[u8], fd_to_send: RawFd) {
unsafe {
// Ancillary buffer pour SCM_RIGHTS
let cmsg_space = libc::CMSG_SPACE(std::mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut iov = libc::iovec {
iov_base: data.as_ptr() as *mut _,
iov_len: data.len(),
};
let mut mhdr: libc::msghdr = std::mem::zeroed();
mhdr.msg_iov = &mut iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
mhdr.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&mhdr);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<RawFd>() as u32) as _;
*(libc::CMSG_DATA(cmsg) as *mut RawFd) = fd_to_send;
let ret = libc::sendmsg(socket_fd, &mhdr, 0);
assert!(
ret >= 0,
"sendmsg failed: {}",
std::io::Error::last_os_error()
);
}
}
}