616 lines
21 KiB
Rust
616 lines
21 KiB
Rust
// Integration test: verifies the compositor's Wayland protocol implementation
|
|
// by starting a real compositor instance and connecting as a client.
|
|
|
|
use std::collections::HashMap;
|
|
use std::io::{Read, Write};
|
|
use std::os::unix::net::UnixStream;
|
|
use std::process::{Child, Command};
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
fn push_wayland_string(buf: &mut Vec<u8>, value: &str) {
|
|
let bytes = value.as_bytes();
|
|
buf.extend_from_slice(&((bytes.len() + 1) as u32).to_le_bytes());
|
|
buf.extend_from_slice(bytes);
|
|
buf.push(0);
|
|
while buf.len() % 4 != 0 {
|
|
buf.push(0);
|
|
}
|
|
}
|
|
|
|
fn read_wayland_string(payload: &[u8], cursor: &mut usize) -> String {
|
|
let length = u32::from_le_bytes([
|
|
payload[*cursor],
|
|
payload[*cursor + 1],
|
|
payload[*cursor + 2],
|
|
payload[*cursor + 3],
|
|
]) as usize;
|
|
*cursor += 4;
|
|
let bytes = &payload[*cursor..*cursor + length];
|
|
let string_len = bytes
|
|
.iter()
|
|
.position(|byte| *byte == 0)
|
|
.unwrap_or(bytes.len());
|
|
*cursor += length;
|
|
while *cursor % 4 != 0 {
|
|
*cursor += 1;
|
|
}
|
|
std::str::from_utf8(&bytes[..string_len])
|
|
.unwrap()
|
|
.to_string()
|
|
}
|
|
|
|
fn collect_globals(client: &mut WaylandClient, registry: u32) -> HashMap<String, (u32, u32)> {
|
|
let mut globals = HashMap::new();
|
|
for _ in 0..9 {
|
|
let (object_id, opcode, payload) = client.read_message().expect("read global failed");
|
|
assert_eq!(object_id, registry);
|
|
assert_eq!(opcode, 0); // wl_registry.global
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
let version = u32::from_le_bytes([
|
|
payload[cursor],
|
|
payload[cursor + 1],
|
|
payload[cursor + 2],
|
|
payload[cursor + 3],
|
|
]);
|
|
globals.insert(iface, (name, version));
|
|
}
|
|
globals
|
|
}
|
|
|
|
struct WaylandClient {
|
|
stream: UnixStream,
|
|
next_id: u32,
|
|
}
|
|
|
|
impl WaylandClient {
|
|
fn connect(socket_path: &str) -> std::io::Result<Self> {
|
|
for _ in 0..20 {
|
|
if std::path::Path::new(socket_path).exists() {
|
|
let stream = UnixStream::connect(socket_path)?;
|
|
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
|
|
return Ok(Self { stream, next_id: 2 });
|
|
}
|
|
thread::sleep(Duration::from_millis(100));
|
|
}
|
|
Err(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
"compositor socket did not appear",
|
|
))
|
|
}
|
|
|
|
fn alloc_id(&mut self) -> u32 {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
id
|
|
}
|
|
|
|
fn send_message(&mut self, object_id: u32, opcode: u16, payload: &[u8]) -> std::io::Result<()> {
|
|
let size = 8 + payload.len();
|
|
let mut msg = Vec::with_capacity(size);
|
|
msg.extend_from_slice(&object_id.to_le_bytes());
|
|
let header = ((size as u32) << 16) | opcode as u32;
|
|
msg.extend_from_slice(&header.to_le_bytes());
|
|
msg.extend_from_slice(payload);
|
|
self.stream.write_all(&msg)
|
|
}
|
|
|
|
fn read_message(&mut self) -> std::io::Result<(u32, u16, Vec<u8>)> {
|
|
let mut header = [0u8; 8];
|
|
self.stream.read_exact(&mut header)?;
|
|
let object_id = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
|
|
let size_opcode = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
|
|
let size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
|
let opcode = (size_opcode & 0xFFFF) as u16;
|
|
let mut payload = vec![0u8; size - 8];
|
|
if size > 8 {
|
|
self.stream.read_exact(&mut payload)?;
|
|
}
|
|
Ok((object_id, opcode, payload))
|
|
}
|
|
|
|
fn sync(&mut self) -> std::io::Result<u32> {
|
|
let callback_id = self.alloc_id();
|
|
self.send_message(1, 0, &callback_id.to_le_bytes())?; // wl_display.sync
|
|
Ok(callback_id)
|
|
}
|
|
|
|
fn get_registry(&mut self) -> std::io::Result<u32> {
|
|
let registry_id = self.alloc_id();
|
|
self.send_message(1, 1, ®istry_id.to_le_bytes())?; // wl_display.get_registry
|
|
Ok(registry_id)
|
|
}
|
|
|
|
fn bind(
|
|
&mut self,
|
|
registry_id: u32,
|
|
name: u32,
|
|
iface: &str,
|
|
version: u32,
|
|
) -> std::io::Result<u32> {
|
|
let new_id = self.alloc_id();
|
|
let mut payload = Vec::new();
|
|
payload.extend_from_slice(&name.to_le_bytes());
|
|
push_wayland_string(&mut payload, iface);
|
|
payload.extend_from_slice(&version.to_le_bytes());
|
|
payload.extend_from_slice(&new_id.to_le_bytes());
|
|
self.send_message(registry_id, 0, &payload)?; // wl_registry.bind
|
|
Ok(new_id)
|
|
}
|
|
}
|
|
|
|
fn start_compositor(socket_path: &str) -> Child {
|
|
let compositor_bin = std::env::var("COMPOSITOR_BIN")
|
|
.unwrap_or_else(|_| "target/debug/redbear-compositor".into());
|
|
|
|
let runtime_dir = std::path::Path::new(socket_path).parent().unwrap();
|
|
std::fs::create_dir_all(runtime_dir).ok();
|
|
|
|
let mut cmd = Command::new(&compositor_bin);
|
|
cmd.env(
|
|
"WAYLAND_DISPLAY",
|
|
socket_path.rsplit('/').next().unwrap_or("wayland-0"),
|
|
)
|
|
.env("XDG_RUNTIME_DIR", runtime_dir)
|
|
.env("FRAMEBUFFER_WIDTH", "1280")
|
|
.env("FRAMEBUFFER_HEIGHT", "720")
|
|
.env("FRAMEBUFFER_STRIDE", "5120")
|
|
.env("FRAMEBUFFER_ADDR", "0x80000000");
|
|
|
|
cmd.spawn().expect("failed to start compositor")
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_globals() {
|
|
let socket = "/tmp/test-redbear-compositor.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
// Get registry
|
|
let _registry = client.get_registry().expect("get_registry failed");
|
|
|
|
// Read global events
|
|
let mut globals = Vec::new();
|
|
for _ in 0..9 {
|
|
match client.read_message() {
|
|
Ok((_obj_id, opcode, payload)) => {
|
|
assert_eq!(opcode, 0); // wl_registry.global
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
globals.push((name, iface));
|
|
}
|
|
Err(e) => {
|
|
eprintln!("read error: {}", e);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "wl_compositor"),
|
|
"wl_compositor missing"
|
|
);
|
|
assert!(globals.iter().any(|(_, i)| i == "wl_shm"), "wl_shm missing");
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "wl_shell"),
|
|
"wl_shell missing"
|
|
);
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "wl_seat"),
|
|
"wl_seat missing"
|
|
);
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "wl_output"),
|
|
"wl_output missing"
|
|
);
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "xdg_wm_base"),
|
|
"xdg_wm_base missing"
|
|
);
|
|
assert!(
|
|
globals.iter().any(|(_, i)| i == "wl_fixes"),
|
|
"wl_fixes missing"
|
|
);
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_shm_formats() {
|
|
let socket = "/tmp/test-redbear-compositor-shm.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let registry = client.get_registry().expect("get_registry failed");
|
|
|
|
// Read globals to find wl_shm name
|
|
let mut shm_name = 0u32;
|
|
for _ in 0..9 {
|
|
let (_, _, payload) = client.read_message().expect("read failed");
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
if iface == "wl_shm" {
|
|
shm_name = name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert_ne!(shm_name, 0, "wl_shm global not found");
|
|
|
|
// Bind wl_shm
|
|
let _shm = client
|
|
.bind(registry, shm_name, "wl_shm", 1)
|
|
.expect("bind shm failed");
|
|
|
|
// Should receive format events
|
|
let mut formats = Vec::new();
|
|
for _ in 0..3 {
|
|
match client.read_message() {
|
|
Ok((_, opcode, payload)) => {
|
|
if opcode == 0 && payload.len() >= 4 {
|
|
let format =
|
|
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
formats.push(format);
|
|
}
|
|
}
|
|
Err(_) => break,
|
|
}
|
|
}
|
|
|
|
assert!(!formats.is_empty(), "no wl_shm.format events received");
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_wl_fixes_destroy_registry() {
|
|
let socket = "/tmp/test-redbear-compositor-wl-fixes.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let registry = client.get_registry().expect("get_registry failed");
|
|
|
|
let mut fixes_name = 0u32;
|
|
for _ in 0..9 {
|
|
let (_, opcode, payload) = client.read_message().expect("read failed");
|
|
assert_eq!(opcode, 0); // wl_registry.global
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
if iface == "wl_fixes" {
|
|
fixes_name = name;
|
|
}
|
|
}
|
|
|
|
assert_ne!(fixes_name, 0, "wl_fixes global not found");
|
|
|
|
let fixes = client
|
|
.bind(registry, fixes_name, "wl_fixes", 2)
|
|
.expect("bind wl_fixes failed");
|
|
|
|
let removed_global_name = 5u32; // wl_output in the compositor's fixed global table.
|
|
client
|
|
.send_message(fixes, 2, &removed_global_name.to_le_bytes())
|
|
.expect("wl_fixes.ack_global_remove failed");
|
|
client
|
|
.send_message(fixes, 1, ®istry.to_le_bytes())
|
|
.expect("wl_fixes.destroy_registry failed");
|
|
|
|
let (object_id, opcode, payload) = client.read_message().expect("read delete_id failed");
|
|
assert_eq!(object_id, 1, "delete_id must be sent by wl_display");
|
|
assert_eq!(opcode, 2, "expected wl_display.delete_id");
|
|
assert_eq!(payload, registry.to_le_bytes());
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_output_and_seat_metadata() {
|
|
let socket = "/tmp/test-redbear-compositor-output-seat.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let registry = client.get_registry().expect("get_registry failed");
|
|
let globals = collect_globals(&mut client, registry);
|
|
|
|
let (output_name, output_version) = *globals.get("wl_output").expect("wl_output missing");
|
|
assert!(
|
|
output_version >= 4,
|
|
"wl_output must advertise metadata-capable v4"
|
|
);
|
|
let output_id = client
|
|
.bind(registry, output_name, "wl_output", 4)
|
|
.expect("bind wl_output failed");
|
|
|
|
let mut saw_geometry = false;
|
|
let mut saw_mode = false;
|
|
let mut saw_scale = false;
|
|
let mut saw_name = false;
|
|
let mut saw_description = false;
|
|
let mut saw_done = false;
|
|
for _ in 0..6 {
|
|
let (object_id, opcode, payload) = client.read_message().expect("read output event failed");
|
|
assert_eq!(object_id, output_id);
|
|
match opcode {
|
|
0 => saw_geometry = true,
|
|
1 => {
|
|
assert_eq!(payload.len(), 16);
|
|
saw_mode = true;
|
|
}
|
|
2 => saw_done = true,
|
|
3 => {
|
|
assert_eq!(payload, 1i32.to_le_bytes());
|
|
saw_scale = true;
|
|
}
|
|
4 => {
|
|
let mut cursor = 0;
|
|
assert_eq!(read_wayland_string(&payload, &mut cursor), "RedBear-0");
|
|
saw_name = true;
|
|
}
|
|
5 => {
|
|
let mut cursor = 0;
|
|
assert_eq!(
|
|
read_wayland_string(&payload, &mut cursor),
|
|
"Red Bear OS framebuffer output"
|
|
);
|
|
saw_description = true;
|
|
}
|
|
_ => panic!("unexpected wl_output opcode {opcode}"),
|
|
}
|
|
}
|
|
assert!(saw_geometry && saw_mode && saw_scale && saw_name && saw_description && saw_done);
|
|
|
|
let (seat_name, seat_version) = *globals.get("wl_seat").expect("wl_seat missing");
|
|
assert!(seat_version >= 2, "wl_seat must advertise name-capable v2+");
|
|
let seat_id = client
|
|
.bind(registry, seat_name, "wl_seat", 5)
|
|
.expect("bind wl_seat failed");
|
|
|
|
let mut saw_capabilities = false;
|
|
let mut saw_seat_name = false;
|
|
for _ in 0..2 {
|
|
let (object_id, opcode, payload) = client.read_message().expect("read seat event failed");
|
|
assert_eq!(object_id, seat_id);
|
|
match opcode {
|
|
0 => {
|
|
assert_eq!(payload, 1u32.to_le_bytes());
|
|
saw_capabilities = true;
|
|
}
|
|
1 => {
|
|
let mut cursor = 0;
|
|
assert_eq!(read_wayland_string(&payload, &mut cursor), "seat0");
|
|
saw_seat_name = true;
|
|
}
|
|
_ => panic!("unexpected wl_seat opcode {opcode}"),
|
|
}
|
|
}
|
|
assert!(saw_capabilities && saw_seat_name);
|
|
|
|
client
|
|
.send_message(output_id, 0, &[])
|
|
.expect("wl_output.release failed");
|
|
let (object_id, opcode, payload) = client.read_message().expect("read output delete_id failed");
|
|
assert_eq!(object_id, 1);
|
|
assert_eq!(opcode, 2);
|
|
assert_eq!(payload, output_id.to_le_bytes());
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_real_surface_opcodes() {
|
|
let socket = "/tmp/test-redbear-compositor-real-surface.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let registry = client.get_registry().expect("get_registry failed");
|
|
let mut globals = HashMap::new();
|
|
for _ in 0..9 {
|
|
let (_, opcode, payload) = client.read_message().expect("read failed");
|
|
assert_eq!(opcode, 0); // wl_registry.global
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
globals.insert(iface, name);
|
|
}
|
|
|
|
let compositor_name = *globals
|
|
.get("wl_compositor")
|
|
.expect("wl_compositor global missing");
|
|
let compositor_id = client
|
|
.bind(registry, compositor_name, "wl_compositor", 4)
|
|
.expect("bind wl_compositor failed");
|
|
|
|
let surface_id = client.alloc_id();
|
|
client
|
|
.send_message(compositor_id, 0, &surface_id.to_le_bytes())
|
|
.expect("wl_compositor.create_surface failed");
|
|
|
|
let region_id = client.alloc_id();
|
|
client
|
|
.send_message(compositor_id, 1, ®ion_id.to_le_bytes())
|
|
.expect("wl_compositor.create_region failed");
|
|
|
|
client
|
|
.send_message(surface_id, 4, ®ion_id.to_le_bytes())
|
|
.expect("wl_surface.set_opaque_region failed");
|
|
client
|
|
.send_message(region_id, 0, &[])
|
|
.expect("wl_region.destroy failed");
|
|
let (object_id, opcode, payload) = client.read_message().expect("read region delete_id failed");
|
|
assert_eq!(object_id, 1);
|
|
assert_eq!(opcode, 2);
|
|
assert_eq!(payload, region_id.to_le_bytes());
|
|
|
|
let callback_id = client.alloc_id();
|
|
client
|
|
.send_message(surface_id, 3, &callback_id.to_le_bytes())
|
|
.expect("wl_surface.frame failed");
|
|
let (object_id, opcode, payload) = client.read_message().expect("read frame callback failed");
|
|
assert_eq!(object_id, callback_id);
|
|
assert_eq!(opcode, 0);
|
|
assert_eq!(payload.len(), 4);
|
|
|
|
client
|
|
.send_message(surface_id, 6, &[])
|
|
.expect("wl_surface.commit failed");
|
|
client
|
|
.send_message(surface_id, 0, &[])
|
|
.expect("wl_surface.destroy failed");
|
|
let (object_id, opcode, payload) = client
|
|
.read_message()
|
|
.expect("read surface delete_id failed");
|
|
assert_eq!(object_id, 1);
|
|
assert_eq!(opcode, 2);
|
|
assert_eq!(payload, surface_id.to_le_bytes());
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_xdg_popup_lifecycle() {
|
|
let socket = "/tmp/test-redbear-compositor-xdg-popup.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let registry = client.get_registry().expect("get_registry failed");
|
|
let mut globals = HashMap::new();
|
|
for _ in 0..9 {
|
|
let (_, opcode, payload) = client.read_message().expect("read failed");
|
|
assert_eq!(opcode, 0); // wl_registry.global
|
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
|
let mut cursor = 4;
|
|
let iface = read_wayland_string(&payload, &mut cursor);
|
|
globals.insert(iface, name);
|
|
}
|
|
|
|
let compositor_name = *globals
|
|
.get("wl_compositor")
|
|
.expect("wl_compositor global missing");
|
|
let xdg_name = *globals.get("xdg_wm_base").expect("xdg_wm_base missing");
|
|
let compositor_id = client
|
|
.bind(registry, compositor_name, "wl_compositor", 4)
|
|
.expect("bind wl_compositor failed");
|
|
let xdg_wm_base_id = client
|
|
.bind(registry, xdg_name, "xdg_wm_base", 1)
|
|
.expect("bind xdg_wm_base failed");
|
|
|
|
let parent_surface = client.alloc_id();
|
|
client
|
|
.send_message(compositor_id, 0, &parent_surface.to_le_bytes())
|
|
.expect("create parent surface failed");
|
|
let parent_xdg = client.alloc_id();
|
|
let mut payload = Vec::new();
|
|
payload.extend_from_slice(&parent_xdg.to_le_bytes());
|
|
payload.extend_from_slice(&parent_surface.to_le_bytes());
|
|
client
|
|
.send_message(xdg_wm_base_id, 2, &payload)
|
|
.expect("get parent xdg_surface failed");
|
|
|
|
let positioner = client.alloc_id();
|
|
client
|
|
.send_message(xdg_wm_base_id, 1, &positioner.to_le_bytes())
|
|
.expect("create_positioner failed");
|
|
let mut payload = Vec::new();
|
|
payload.extend_from_slice(&64i32.to_le_bytes());
|
|
payload.extend_from_slice(&32i32.to_le_bytes());
|
|
client
|
|
.send_message(positioner, 1, &payload)
|
|
.expect("positioner.set_size failed");
|
|
|
|
let popup_surface = client.alloc_id();
|
|
client
|
|
.send_message(compositor_id, 0, &popup_surface.to_le_bytes())
|
|
.expect("create popup surface failed");
|
|
let popup_xdg = client.alloc_id();
|
|
let mut payload = Vec::new();
|
|
payload.extend_from_slice(&popup_xdg.to_le_bytes());
|
|
payload.extend_from_slice(&popup_surface.to_le_bytes());
|
|
client
|
|
.send_message(xdg_wm_base_id, 2, &payload)
|
|
.expect("get popup xdg_surface failed");
|
|
|
|
let popup = client.alloc_id();
|
|
let mut payload = Vec::new();
|
|
payload.extend_from_slice(&popup.to_le_bytes());
|
|
payload.extend_from_slice(&parent_xdg.to_le_bytes());
|
|
payload.extend_from_slice(&positioner.to_le_bytes());
|
|
client
|
|
.send_message(popup_xdg, 2, &payload)
|
|
.expect("xdg_surface.get_popup failed");
|
|
|
|
let (object_id, opcode, payload) = client.read_message().expect("read popup configure failed");
|
|
assert_eq!(object_id, popup);
|
|
assert_eq!(opcode, 0);
|
|
assert_eq!(payload.len(), 16);
|
|
let (object_id, opcode, payload) = client
|
|
.read_message()
|
|
.expect("read popup surface configure failed");
|
|
assert_eq!(object_id, popup_xdg);
|
|
assert_eq!(opcode, 0);
|
|
assert_eq!(payload.len(), 4);
|
|
|
|
client
|
|
.send_message(popup, 0, &[])
|
|
.expect("xdg_popup.destroy failed");
|
|
let (object_id, opcode, payload) = client.read_message().expect("read popup delete_id failed");
|
|
assert_eq!(object_id, 1);
|
|
assert_eq!(opcode, 2);
|
|
assert_eq!(payload, popup.to_le_bytes());
|
|
|
|
client
|
|
.send_message(positioner, 0, &[])
|
|
.expect("xdg_positioner.destroy failed");
|
|
let (object_id, opcode, payload) = client
|
|
.read_message()
|
|
.expect("read positioner delete_id failed");
|
|
assert_eq!(object_id, 1);
|
|
assert_eq!(opcode, 2);
|
|
assert_eq!(payload, positioner.to_le_bytes());
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compositor_sync_roundtrip() {
|
|
let socket = "/tmp/test-redbear-compositor-sync.sock";
|
|
let _ = std::fs::remove_file(socket);
|
|
|
|
let mut compositor = start_compositor(socket);
|
|
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
|
|
|
let callback_id = client.sync().expect("sync failed");
|
|
|
|
// Should receive callback.done
|
|
let (obj_id, opcode, payload) = client.read_message().expect("read failed");
|
|
assert_eq!(obj_id, callback_id, "callback id mismatch");
|
|
assert_eq!(opcode, 0, "expected callback.done (opcode 0)");
|
|
assert_eq!(payload.len(), 4, "callback.done payload should be 4 bytes");
|
|
|
|
compositor.kill().ok();
|
|
let _ = std::fs::remove_file(socket);
|
|
}
|