#![allow(unsafe_code)]
use std::io::{self, Write};
use std::os::fd::AsRawFd;
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use crate::geometry::{PhysicalSize, Point, ScaleFactor};
use crate::platform::ControlFlow;
use crate::platform::error::PlatformError;
use crate::platform::event::{ButtonState, Event, KeyCode, Modifiers};
use crate::platform::window::{Window, WindowAttributes};
use crate::render::{Pixmap, Surface};
use core::ffi::{c_char, c_void};
unsafe extern "C" {
fn memfd_create(name: *const c_char, flags: u32) -> i32;
fn ftruncate(fd: i32, length: i64) -> i32;
fn mmap(addr: *mut c_void, len: usize, prot: i32, flags: i32, fd: i32, off: i64)
-> *mut c_void;
fn munmap(addr: *mut c_void, len: usize) -> i32;
fn close(fd: i32) -> i32;
fn sendmsg(sockfd: i32, msg: *const Msghdr, flags: i32) -> isize;
fn recvmsg(sockfd: i32, msg: *mut Msghdr, flags: i32) -> isize;
}
const PROT_READ: i32 = 1;
const PROT_WRITE: i32 = 2;
const MAP_SHARED: i32 = 1;
const MAP_PRIVATE: i32 = 2;
const SOL_SOCKET: i32 = 1;
const SCM_RIGHTS: i32 = 1;
#[repr(C)]
struct Iovec {
iov_base: *mut c_void,
iov_len: usize,
}
#[repr(C)]
struct Msghdr {
msg_name: *mut c_void,
msg_namelen: u32,
msg_iov: *mut Iovec,
msg_iovlen: usize,
msg_control: *mut c_void,
msg_controllen: usize,
msg_flags: i32,
}
#[repr(C)]
struct Cmsghdr {
cmsg_len: usize,
cmsg_level: i32,
cmsg_type: i32,
}
fn sendmsg_fd(sock: i32, bytes: &[u8], fd: i32) -> io::Result<()> {
let mut control = [0u8; 24];
let cmsg = control.as_mut_ptr() as *mut Cmsghdr;
unsafe {
(*cmsg).cmsg_len = 20; (*cmsg).cmsg_level = SOL_SOCKET;
(*cmsg).cmsg_type = SCM_RIGHTS;
let data = control.as_mut_ptr().add(16) as *mut i32;
data.write_unaligned(fd);
}
let mut iov = Iovec {
iov_base: bytes.as_ptr() as *mut c_void,
iov_len: bytes.len(),
};
let msg = Msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: control.as_mut_ptr() as *mut c_void,
msg_controllen: control.len(),
msg_flags: 0,
};
let n = unsafe { sendmsg(sock, &msg, 0) };
if n < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
const WL_DISPLAY: u32 = 1;
const WL_DISPLAY_GET_REGISTRY: u16 = 1;
const WL_DISPLAY_SYNC: u16 = 0;
const WL_REGISTRY_BIND: u16 = 0;
const WL_COMPOSITOR_CREATE_SURFACE: u16 = 0;
const WL_SHM_CREATE_POOL: u16 = 0;
const WL_SHM_POOL_CREATE_BUFFER: u16 = 0;
const WL_SURFACE_ATTACH: u16 = 1;
const WL_SURFACE_DAMAGE: u16 = 2;
const WL_SURFACE_COMMIT: u16 = 6;
const WL_SEAT_GET_KEYBOARD: u16 = 1;
const XDG_WM_BASE_PONG: u16 = 3;
const XDG_WM_BASE_GET_XDG_SURFACE: u16 = 2;
const XDG_SURFACE_GET_TOPLEVEL: u16 = 1;
const XDG_SURFACE_ACK_CONFIGURE: u16 = 4;
const XDG_TOPLEVEL_SET_TITLE: u16 = 2;
const WL_DISPLAY_ERROR: u16 = 0;
const WL_REGISTRY_GLOBAL: u16 = 0;
const WL_CALLBACK_DONE: u16 = 0;
const XDG_WM_BASE_PING: u16 = 0;
const XDG_SURFACE_CONFIGURE: u16 = 0;
const XDG_TOPLEVEL_CONFIGURE: u16 = 0;
const XDG_TOPLEVEL_CLOSE: u16 = 1;
const WL_SEAT_CAPABILITIES: u16 = 0;
const WL_SEAT_CAP_POINTER: u32 = 1;
const WL_SEAT_CAP_KEYBOARD: u32 = 2;
const WL_SEAT_GET_POINTER: u16 = 0;
const WL_KEYBOARD_KEYMAP: u16 = 0;
const WL_KEYBOARD_KEY: u16 = 3;
const WL_POINTER_ENTER: u16 = 0;
const WL_POINTER_MOTION: u16 = 2;
const WL_POINTER_BUTTON: u16 = 3;
const WL_SHM_FORMAT_XRGB8888: u32 = 1;
pub fn available() -> bool {
std::env::var_os("WAYLAND_DISPLAY").is_some()
}
fn dbg(args: std::fmt::Arguments<'_>) {
if std::env::var_os("STIPPLE_WAYLAND_DEBUG").is_some() {
eprintln!("stipple wayland: {args}");
}
}
fn os(e: io::Error) -> PlatformError {
PlatformError::Os(e.to_string())
}
fn evdev_to_event(code: u32, pressed: bool) -> Option<Event> {
let state = if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
};
let key = match code {
15 => Some(KeyCode::Tab),
28 | 96 => Some(KeyCode::Enter), 14 => Some(KeyCode::Backspace),
111 => Some(KeyCode::Delete),
105 => Some(KeyCode::ArrowLeft),
106 => Some(KeyCode::ArrowRight),
103 => Some(KeyCode::ArrowUp),
108 => Some(KeyCode::ArrowDown),
102 => Some(KeyCode::Home),
107 => Some(KeyCode::End),
1 => Some(KeyCode::Escape),
_ => None,
};
if let Some(code) = key {
return Some(Event::Key {
code,
state,
modifiers: Modifiers::default(),
});
}
if !pressed {
return None;
}
let ch = match code {
16..=25 => Some(b"qwertyuiop"[(code - 16) as usize]),
30..=38 => Some(b"asdfghjkl"[(code - 30) as usize]),
44..=50 => Some(b"zxcvbnm"[(code - 44) as usize]),
2..=10 => Some(b"123456789"[(code - 2) as usize]),
11 => Some(b'0'),
57 => Some(b' '),
_ => None,
};
ch.map(|b| Event::Text((b as char).to_string()))
}
fn fixed_to_f64(raw: u32) -> f64 {
(raw as i32) as f64 / 256.0
}
fn pointer_button(code: u32) -> Option<crate::platform::event::PointerButton> {
use crate::platform::event::PointerButton;
match code {
0x110 => Some(PointerButton::Left),
0x111 => Some(PointerButton::Right),
0x112 => Some(PointerButton::Middle),
_ => None,
}
}
fn between(s: &str, a: char, b: char) -> Option<&str> {
let start = s.find(a)? + 1;
let end = s[start..].find(b)? + start;
Some(&s[start..end])
}
fn parse_xkb(text: &str) -> std::collections::HashMap<u32, String> {
use std::collections::HashMap;
let mut codes: HashMap<&str, u32> = HashMap::new();
let mut syms: HashMap<&str, String> = HashMap::new();
for line in text.lines() {
let t = line.trim();
if let Some(rest) = t.strip_prefix("key <") {
if let Some(end) = rest.find('>')
&& let Some(lb) = t.find('[')
{
let name = &rest[..end];
let inner = &t[lb + 1..];
let stop = inner.find([',', ']']).unwrap_or(inner.len());
let sym = inner[..stop].trim();
if !sym.is_empty() {
syms.insert(name, sym.to_string());
}
}
} else if t.starts_with('<') && t.contains('=') {
if let Some(name) = between(t, '<', '>')
&& let Some(eq) = t.find('=')
{
let num: String = t[eq + 1..].chars().filter(|c| c.is_ascii_digit()).collect();
if let Ok(code) = num.parse::<u32>() {
codes.insert(name, code);
}
}
}
}
codes
.into_iter()
.filter_map(|(name, code)| syms.get(name).map(|s| (code, s.clone())))
.collect()
}
fn keysym_name_to_event(name: &str, pressed: bool) -> Option<Event> {
let state = if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
};
let code = match name {
"Tab" => Some(KeyCode::Tab),
"Return" | "KP_Enter" => Some(KeyCode::Enter),
"BackSpace" => Some(KeyCode::Backspace),
"Delete" => Some(KeyCode::Delete),
"Left" => Some(KeyCode::ArrowLeft),
"Right" => Some(KeyCode::ArrowRight),
"Up" => Some(KeyCode::ArrowUp),
"Down" => Some(KeyCode::ArrowDown),
"Home" => Some(KeyCode::Home),
"End" => Some(KeyCode::End),
"Escape" => Some(KeyCode::Escape),
_ => None,
};
if let Some(code) = code {
return Some(Event::Key {
code,
state,
modifiers: Modifiers::default(),
});
}
if !pressed {
return None;
}
let ch = if name.chars().count() == 1 {
name.chars().next()
} else {
match name {
"space" => Some(' '),
"exclam" => Some('!'),
"question" => Some('?'),
"comma" => Some(','),
"period" => Some('.'),
"minus" => Some('-'),
"underscore" => Some('_'),
"slash" => Some('/'),
"colon" => Some(':'),
"semicolon" => Some(';'),
_ => None,
}
};
ch.map(|c| Event::Text(c.to_string()))
}
fn read_keymap(fd: i32, size: usize) -> Option<std::collections::HashMap<u32, String>> {
if size == 0 {
unsafe { close(fd) };
return None;
}
let addr = unsafe { mmap(std::ptr::null_mut(), size, PROT_READ, MAP_PRIVATE, fd, 0) };
unsafe { close(fd) }; if addr as isize == -1 {
return None;
}
let bytes = unsafe { std::slice::from_raw_parts(addr as *const u8, size) };
let text = String::from_utf8_lossy(bytes).into_owned();
let map = parse_xkb(&text);
unsafe { munmap(addr, size) };
Some(map)
}
struct WlConn {
stream: UnixStream,
next_id: u32,
pending_fd: Option<i32>,
}
impl WlConn {
fn connect() -> Result<Self, PlatformError> {
let disp = std::env::var_os("WAYLAND_DISPLAY")
.ok_or_else(|| PlatformError::Os("WAYLAND_DISPLAY not set".into()))?;
let path = std::path::PathBuf::from(&disp);
let full = if path.is_absolute() {
path
} else {
let dir = std::env::var_os("XDG_RUNTIME_DIR")
.ok_or_else(|| PlatformError::Os("XDG_RUNTIME_DIR not set".into()))?;
std::path::Path::new(&dir).join(path)
};
let stream = UnixStream::connect(&full).map_err(os)?;
Ok(Self {
stream,
next_id: 2,
pending_fd: None,
})
}
fn new_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
fn send(&mut self, object: u32, opcode: u16, args: &[u8]) -> io::Result<()> {
let size = (8 + args.len()) as u32;
let mut msg = Vec::with_capacity(size as usize);
msg.extend_from_slice(&object.to_le_bytes());
msg.extend_from_slice(&((size << 16) | opcode as u32).to_le_bytes());
msg.extend_from_slice(args);
self.stream.write_all(&msg)
}
fn send_with_fd(&mut self, object: u32, opcode: u16, args: &[u8], fd: i32) -> io::Result<()> {
let size = (8 + args.len()) as u32;
let mut msg = Vec::with_capacity(size as usize);
msg.extend_from_slice(&object.to_le_bytes());
msg.extend_from_slice(&((size << 16) | opcode as u32).to_le_bytes());
msg.extend_from_slice(args);
sendmsg_fd(self.stream.as_raw_fd(), &msg, fd)
}
fn recv_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
let sock = self.stream.as_raw_fd();
let mut got = 0;
while got < buf.len() {
let mut iov = Iovec {
iov_base: buf[got..].as_mut_ptr() as *mut c_void,
iov_len: buf.len() - got,
};
let mut control = [0u8; 32];
let mut msg = Msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: control.as_mut_ptr() as *mut c_void,
msg_controllen: control.len(),
msg_flags: 0,
};
let n = unsafe { recvmsg(sock, &mut msg, 0) };
if n <= 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"wayland socket closed",
));
}
got += n as usize;
if msg.msg_controllen >= core::mem::size_of::<Cmsghdr>() {
let cmsg = control.as_ptr() as *const Cmsghdr;
let (level, ty) = unsafe { ((*cmsg).cmsg_level, (*cmsg).cmsg_type) };
if level == SOL_SOCKET && ty == SCM_RIGHTS {
let fd = unsafe { (control.as_ptr().add(16) as *const i32).read_unaligned() };
self.pending_fd = Some(fd);
}
}
}
Ok(())
}
fn recv(&mut self) -> io::Result<(u32, u16, Vec<u8>)> {
let mut header = [0u8; 8];
self.recv_exact(&mut header)?;
let object = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
let word = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
let size = (word >> 16) as usize;
let opcode = (word & 0xffff) as u16;
let mut args = vec![0u8; size.saturating_sub(8)];
self.recv_exact(&mut args)?;
Ok((object, opcode, args))
}
}
fn arg_u32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn arg_str(buf: &mut Vec<u8>, s: &str) {
let len = s.len() + 1; arg_u32(buf, len as u32);
buf.extend_from_slice(s.as_bytes());
buf.push(0);
while !buf.len().is_multiple_of(4) {
buf.push(0);
}
}
fn read_str(args: &[u8], off: usize) -> Option<(String, usize)> {
let len = u32::from_le_bytes([
*args.get(off)?,
*args.get(off + 1)?,
*args.get(off + 2)?,
*args.get(off + 3)?,
]) as usize;
let start = off + 4;
let bytes = args.get(start..start + len)?;
let text = String::from_utf8_lossy(&bytes[..len.saturating_sub(1)]).into_owned();
let next = start + len.div_ceil(4) * 4;
Some((text, next))
}
fn rgba_to_xrgb(rgba: &[u8], dst: &mut [u8]) {
for (s, d) in rgba
.as_chunks::<4>()
.0
.iter()
.zip(dst.as_chunks_mut::<4>().0.iter_mut())
{
d[0] = s[2];
d[1] = s[1];
d[2] = s[0];
d[3] = 0;
}
}
struct ShmBuffer {
fd: i32,
ptr: *mut u8,
capacity: usize,
buffer: u32,
size: PhysicalSize,
}
impl ShmBuffer {
fn bytes(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.capacity) }
}
}
impl Drop for ShmBuffer {
fn drop(&mut self) {
unsafe {
munmap(self.ptr as *mut c_void, self.capacity);
close(self.fd);
}
}
}
fn make_shm(conn: &mut WlConn, shm: u32, size: PhysicalSize) -> Result<ShmBuffer, PlatformError> {
let stride = size.width as usize * 4;
let bytes = stride * size.height as usize;
let name = c"stipple-wl";
let fd = unsafe { memfd_create(name.as_ptr(), 0) };
if fd < 0 {
return Err(PlatformError::Os("memfd_create failed".into()));
}
if unsafe { ftruncate(fd, bytes as i64) } < 0 {
unsafe { close(fd) };
return Err(PlatformError::Os("ftruncate failed".into()));
}
let addr = unsafe {
mmap(
std::ptr::null_mut(),
bytes,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0,
)
};
if addr as isize == -1 {
unsafe { close(fd) };
return Err(PlatformError::Os("mmap failed".into()));
}
let pool = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, pool);
arg_u32(&mut a, bytes as u32);
conn.send_with_fd(shm, WL_SHM_CREATE_POOL, &a, fd)
.map_err(os)?;
let buffer = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, buffer);
arg_u32(&mut a, 0); arg_u32(&mut a, size.width);
arg_u32(&mut a, size.height);
arg_u32(&mut a, stride as u32);
arg_u32(&mut a, WL_SHM_FORMAT_XRGB8888);
conn.send(pool, WL_SHM_POOL_CREATE_BUFFER, &a).map_err(os)?;
Ok(ShmBuffer {
fd,
ptr: addr as *mut u8,
capacity: bytes,
buffer,
size,
})
}
type Shared = Arc<Mutex<WlConn>>;
struct WaylandWindow {
conn: Shared,
surface: u32,
shm: u32,
size: PhysicalSize,
}
impl std::fmt::Debug for WaylandWindow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WaylandWindow")
.field("surface", &self.surface)
.field("size", &self.size)
.finish()
}
}
impl Window for WaylandWindow {
fn id(&self) -> crate::platform::WindowId {
crate::platform::WindowId(self.surface as u64)
}
fn inner_size(&self) -> PhysicalSize {
self.size
}
fn scale_factor(&self) -> ScaleFactor {
ScaleFactor::IDENTITY
}
fn request_redraw(&self) {}
fn set_title(&self, _title: &str) {}
fn create_surface(&self) -> Box<dyn Surface> {
Box::new(WaylandSurface {
conn: self.conn.clone(),
surface: self.surface,
shm: self.shm,
size: self.size,
buffer: None,
})
}
}
struct WaylandSurface {
conn: Shared,
surface: u32,
shm: u32,
size: PhysicalSize,
buffer: Option<ShmBuffer>,
}
impl std::fmt::Debug for WaylandSurface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WaylandSurface")
.field("surface", &self.surface)
.field("size", &self.size)
.finish()
}
}
impl Surface for WaylandSurface {
fn resize(&mut self, size: PhysicalSize) {
if size != self.size {
self.size = size;
self.buffer = None; }
}
fn size(&self) -> PhysicalSize {
self.size
}
fn present(&mut self, pixmap: &Pixmap, _damage: &[crate::geometry::Rect]) {
let size = pixmap.size();
if size.width == 0 || size.height == 0 {
return;
}
let mut conn = self.conn.lock().unwrap();
if self.buffer.as_ref().map(|b| b.size) != Some(size) {
self.buffer = make_shm(&mut conn, self.shm, size).ok();
}
let Some(buf) = self.buffer.as_mut() else {
return;
};
let stride = size.width as usize * 4;
let bytes = stride * size.height as usize;
let buffer = buf.buffer;
rgba_to_xrgb(pixmap.as_bytes(), &mut buf.bytes()[..bytes]);
let surface = self.surface;
let mut a = Vec::new();
arg_u32(&mut a, buffer);
arg_u32(&mut a, 0);
arg_u32(&mut a, 0);
let _ = conn.send(surface, WL_SURFACE_ATTACH, &a);
let mut a = Vec::new();
arg_u32(&mut a, 0);
arg_u32(&mut a, 0);
arg_u32(&mut a, size.width);
arg_u32(&mut a, size.height);
let _ = conn.send(surface, WL_SURFACE_DAMAGE, &a);
let _ = conn.send(surface, WL_SURFACE_COMMIT, &[]);
let _ = conn.stream.flush();
dbg(format_args!("present {}x{}", size.width, size.height));
}
}
pub fn run<H>(attrs: WindowAttributes, mut handler: H) -> Result<(), PlatformError>
where
H: FnMut(Event, &dyn Window) -> ControlFlow,
{
let mut conn = WlConn::connect()?;
let size = ScaleFactor::IDENTITY.to_physical(attrs.logical_size);
let size = PhysicalSize::new(size.width.max(1), size.height.max(1));
let registry = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, registry);
conn.send(WL_DISPLAY, WL_DISPLAY_GET_REGISTRY, &a)
.map_err(os)?;
let sync_cb = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, sync_cb);
conn.send(WL_DISPLAY, WL_DISPLAY_SYNC, &a).map_err(os)?;
let mut compositor_name = None;
let mut shm_name = None;
let mut wm_base_name = None;
let mut seat_name = None;
loop {
let (object, opcode, args) = conn.recv().map_err(os)?;
if object == registry && opcode == WL_REGISTRY_GLOBAL {
let name = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
if let Some((iface, _)) = read_str(&args, 4) {
match iface.as_str() {
"wl_compositor" => compositor_name = Some(name),
"wl_shm" => shm_name = Some(name),
"xdg_wm_base" => wm_base_name = Some(name),
"wl_seat" => seat_name = Some(name),
_ => {}
}
}
} else if object == sync_cb && opcode == WL_CALLBACK_DONE {
break;
} else if object == WL_DISPLAY && opcode == WL_DISPLAY_ERROR {
return Err(PlatformError::Os("wl_display error during setup".into()));
}
}
let bind = |conn: &mut WlConn, name: u32, iface: &str, version: u32| -> u32 {
let id = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, name);
arg_str(&mut a, iface);
arg_u32(&mut a, version);
arg_u32(&mut a, id);
let _ = conn.send(registry, WL_REGISTRY_BIND, &a);
id
};
let compositor = bind(
&mut conn,
compositor_name.ok_or_else(|| PlatformError::Os("no wl_compositor".into()))?,
"wl_compositor",
4,
);
let shm = bind(
&mut conn,
shm_name.ok_or_else(|| PlatformError::Os("no wl_shm".into()))?,
"wl_shm",
1,
);
let wm_base = bind(
&mut conn,
wm_base_name.ok_or_else(|| PlatformError::Os("no xdg_wm_base".into()))?,
"xdg_wm_base",
1,
);
let seat = seat_name.map(|name| bind(&mut conn, name, "wl_seat", 1));
dbg(format_args!("seat={seat:?}"));
let surface = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, surface);
conn.send(compositor, WL_COMPOSITOR_CREATE_SURFACE, &a)
.map_err(os)?;
let xdg_surface = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, xdg_surface);
arg_u32(&mut a, surface);
conn.send(wm_base, XDG_WM_BASE_GET_XDG_SURFACE, &a)
.map_err(os)?;
let toplevel = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, toplevel);
conn.send(xdg_surface, XDG_SURFACE_GET_TOPLEVEL, &a)
.map_err(os)?;
let mut a = Vec::new();
arg_str(&mut a, &attrs.title);
conn.send(toplevel, XDG_TOPLEVEL_SET_TITLE, &a)
.map_err(os)?;
conn.send(surface, WL_SURFACE_COMMIT, &[]).map_err(os)?;
conn.stream.flush().map_err(os)?;
dbg(format_args!(
"window: surface={surface} xdg={xdg_surface} toplevel={toplevel} size={}x{}",
size.width, size.height
));
let shared: Shared = Arc::new(Mutex::new(conn));
let win = WaylandWindow {
conn: shared.clone(),
surface,
shm,
size,
};
let mut keyboard: Option<u32> = None;
let mut pointer: Option<u32> = None;
let mut pointer_pos = Point::new(0.0, 0.0);
let mut keymap: Option<std::collections::HashMap<u32, String>> = None;
loop {
let (object, opcode, args) = {
let mut conn = shared.lock().unwrap();
conn.recv().map_err(os)?
};
if Some(object) == seat && opcode == WL_SEAT_CAPABILITIES {
let caps = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
dbg(format_args!("seat capabilities: {caps}"));
let mut conn = shared.lock().unwrap();
if caps & WL_SEAT_CAP_KEYBOARD != 0 {
if keyboard.is_none() {
let kbd = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, kbd);
if conn.send(seat.unwrap(), WL_SEAT_GET_KEYBOARD, &a).is_ok() {
keyboard = Some(kbd);
dbg(format_args!("keyboard={kbd} (caps={caps})"));
}
}
} else if keyboard.take().is_some() {
keymap = None;
dbg(format_args!("keyboard dropped (caps={caps})"));
}
if pointer.is_none() && caps & WL_SEAT_CAP_POINTER != 0 {
let ptr = conn.new_id();
let mut a = Vec::new();
arg_u32(&mut a, ptr);
if conn.send(seat.unwrap(), WL_SEAT_GET_POINTER, &a).is_ok() {
pointer = Some(ptr);
dbg(format_args!("pointer={ptr} (caps={caps})"));
}
}
} else if Some(object) == pointer
&& (opcode == WL_POINTER_ENTER || opcode == WL_POINTER_MOTION)
{
let n = args.len();
let x = u32::from_le_bytes([args[n - 8], args[n - 7], args[n - 6], args[n - 5]]);
let y = u32::from_le_bytes([args[n - 4], args[n - 3], args[n - 2], args[n - 1]]);
pointer_pos = Point::new(fixed_to_f64(x), fixed_to_f64(y));
if handler(
Event::PointerMoved {
position: pointer_pos,
},
&win,
) == ControlFlow::Exit
{
break;
}
} else if Some(object) == pointer && opcode == WL_POINTER_BUTTON {
let button = u32::from_le_bytes([args[8], args[9], args[10], args[11]]);
let pressed = u32::from_le_bytes([args[12], args[13], args[14], args[15]]) == 1;
if let Some(button) = pointer_button(button) {
let state = if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
};
if handler(
Event::PointerButton {
button,
state,
position: pointer_pos,
},
&win,
) == ControlFlow::Exit
{
break;
}
}
} else if object == wm_base && opcode == XDG_WM_BASE_PING {
let serial = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
let mut a = Vec::new();
arg_u32(&mut a, serial);
let mut conn = shared.lock().unwrap();
let _ = conn.send(wm_base, XDG_WM_BASE_PONG, &a);
let _ = conn.stream.flush();
} else if object == xdg_surface && opcode == XDG_SURFACE_CONFIGURE {
let serial = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
{
let mut conn = shared.lock().unwrap();
let mut a = Vec::new();
arg_u32(&mut a, serial);
let _ = conn.send(xdg_surface, XDG_SURFACE_ACK_CONFIGURE, &a);
}
if handler(Event::RedrawRequested, &win) == ControlFlow::Exit {
break;
}
} else if object == toplevel && opcode == XDG_TOPLEVEL_CONFIGURE {
let w = i32::from_le_bytes([args[0], args[1], args[2], args[3]]);
let h = i32::from_le_bytes([args[4], args[5], args[6], args[7]]);
dbg(format_args!("toplevel configure {w}x{h}"));
} else if object == toplevel && opcode == XDG_TOPLEVEL_CLOSE {
handler(Event::CloseRequested, &win);
break;
} else if Some(object) == keyboard && opcode == WL_KEYBOARD_KEYMAP {
let format = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
let size = u32::from_le_bytes([args[4], args[5], args[6], args[7]]) as usize;
let fd = shared.lock().unwrap().pending_fd.take();
if let (1, Some(fd)) = (format, fd) {
keymap = read_keymap(fd, size);
dbg(format_args!(
"keymap parsed: {} keys",
keymap.as_ref().map(|m| m.len()).unwrap_or(0)
));
} else if let Some(fd) = fd {
unsafe { close(fd) };
}
} else if Some(object) == keyboard && opcode == WL_KEYBOARD_KEY {
let key = u32::from_le_bytes([args[8], args[9], args[10], args[11]]);
let pressed = u32::from_le_bytes([args[12], args[13], args[14], args[15]]) == 1;
let ev = match keymap.as_ref().and_then(|m| m.get(&(key + 8))) {
Some(sym) => keysym_name_to_event(sym, pressed),
None => evdev_to_event(key, pressed),
};
if let Some(ev) = ev {
dbg(format_args!("key {key} pressed={pressed} -> {ev:?}"));
if handler(ev, &win) == ControlFlow::Exit {
break;
}
}
} else if object == WL_DISPLAY && opcode == WL_DISPLAY_ERROR {
return Err(PlatformError::Os("wl_display error".into()));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn decode_header(msg: &[u8]) -> (u32, u32, u16) {
let object = u32::from_le_bytes([msg[0], msg[1], msg[2], msg[3]]);
let word = u32::from_le_bytes([msg[4], msg[5], msg[6], msg[7]]);
(object, word >> 16, (word & 0xffff) as u16)
}
#[test]
fn message_header_packs_size_and_opcode() {
let mut msg = Vec::new();
msg.extend_from_slice(&WL_DISPLAY.to_le_bytes());
let mut args = Vec::new();
arg_u32(&mut args, 2);
let size = (8 + args.len()) as u32;
msg.extend_from_slice(&((size << 16) | WL_DISPLAY_GET_REGISTRY as u32).to_le_bytes());
msg.extend_from_slice(&args);
let (object, sz, opcode) = decode_header(&msg);
assert_eq!((object, sz, opcode), (1, 12, 1));
assert_eq!(msg.len(), 12);
}
#[test]
fn string_arg_pads_to_four_bytes() {
let mut buf = Vec::new();
arg_str(&mut buf, "wl_shm"); assert_eq!(buf.len(), 12);
assert_eq!(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), 7);
assert!(buf.len().is_multiple_of(4));
let (s, next) = read_str(&buf, 0).unwrap();
assert_eq!(s, "wl_shm");
assert_eq!(next, 12);
}
#[test]
fn read_str_parses_a_registry_global() {
let mut args = Vec::new();
arg_u32(&mut args, 42);
arg_str(&mut args, "xdg_wm_base");
arg_u32(&mut args, 3);
let name = u32::from_le_bytes([args[0], args[1], args[2], args[3]]);
let (iface, next) = read_str(&args, 4).unwrap();
let version =
u32::from_le_bytes([args[next], args[next + 1], args[next + 2], args[next + 3]]);
assert_eq!((name, iface.as_str(), version), (42, "xdg_wm_base", 3));
}
#[test]
fn xrgb_swizzle_drops_alpha() {
let mut dst = [0u8; 8];
rgba_to_xrgb(&[10, 20, 30, 255, 1, 2, 3, 128], &mut dst);
assert_eq!(dst, [30, 20, 10, 0, 3, 2, 1, 0]);
}
#[test]
fn evdev_maps_editing_keys_and_us_text() {
assert!(matches!(
evdev_to_event(15, true),
Some(Event::Key {
code: KeyCode::Tab,
state: ButtonState::Pressed,
..
})
));
assert!(matches!(
evdev_to_event(105, true),
Some(Event::Key {
code: KeyCode::ArrowLeft,
..
})
));
assert!(matches!(
evdev_to_event(28, false),
Some(Event::Key {
code: KeyCode::Enter,
state: ButtonState::Released,
..
})
));
assert_eq!(evdev_to_event(30, true), Some(Event::Text("a".into()))); assert_eq!(evdev_to_event(57, true), Some(Event::Text(" ".into()))); assert_eq!(evdev_to_event(30, false), None);
assert_eq!(evdev_to_event(0, true), None);
}
#[test]
fn parse_xkb_maps_keycodes_to_keysyms() {
let keymap = "\
xkb_keymap {
xkb_keycodes \"x\" {
<AC01> = 38;
<SPCE> = 65;
<TAB> = 23;
};
xkb_symbols \"x\" {
key <AC01> { [ a, A ] };
key <SPCE> { [ space ] };
key <TAB> { [ Tab ] };
};
};";
let map = parse_xkb(keymap);
assert_eq!(map.get(&38).map(String::as_str), Some("a"));
assert_eq!(map.get(&65).map(String::as_str), Some("space"));
assert_eq!(map.get(&23).map(String::as_str), Some("Tab"));
assert_eq!(
keysym_name_to_event("a", true),
Some(Event::Text("a".into()))
);
assert_eq!(
keysym_name_to_event("space", true),
Some(Event::Text(" ".into()))
);
assert!(matches!(
keysym_name_to_event("Tab", true),
Some(Event::Key {
code: KeyCode::Tab,
..
})
));
assert_eq!(keysym_name_to_event("a", false), None); }
#[test]
fn fixed_point_and_pointer_buttons() {
use crate::platform::event::PointerButton;
assert_eq!(fixed_to_f64(256), 1.0);
assert_eq!(fixed_to_f64(128), 0.5);
assert_eq!(fixed_to_f64((-256i32) as u32), -1.0);
assert_eq!(pointer_button(0x110), Some(PointerButton::Left));
assert_eq!(pointer_button(0x111), Some(PointerButton::Right));
assert_eq!(pointer_button(0x999), None);
}
}