269 lines
7.6 KiB
Rust
269 lines
7.6 KiB
Rust
use rustix::event::{poll, PollFd, PollFlags};
|
|
use std::mem::ManuallyDrop;
|
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
|
use std::os::unix::net::UnixStream;
|
|
use std::sync::mpsc;
|
|
use std::sync::Once;
|
|
use std::thread::JoinHandle;
|
|
use std::time::Duration;
|
|
use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
|
use wayland_server::Resource;
|
|
use xcb::{x, Xid};
|
|
use xwayland_satellite as xwls;
|
|
|
|
struct Fixture {
|
|
testwl: testwl::Server,
|
|
thread: ManuallyDrop<JoinHandle<Option<()>>>,
|
|
pollfd: PollFd<'static>,
|
|
}
|
|
|
|
impl Drop for Fixture {
|
|
fn drop(&mut self) {
|
|
let thread = unsafe { ManuallyDrop::take(&mut self.thread) };
|
|
if thread.is_finished() {
|
|
thread.join().expect("Main thread panicked");
|
|
}
|
|
}
|
|
}
|
|
|
|
xcb::atoms_struct! {
|
|
struct Atoms {
|
|
wm_protocols => b"WM_PROTOCOLS",
|
|
wm_delete_window => b"WM_DELETE_WINDOW",
|
|
}
|
|
}
|
|
|
|
static INIT: Once = Once::new();
|
|
impl Fixture {
|
|
#[track_caller]
|
|
fn new() -> Self {
|
|
INIT.call_once(|| {
|
|
env_logger::builder()
|
|
.is_test(true)
|
|
.filter_level(log::LevelFilter::Debug)
|
|
.init();
|
|
});
|
|
|
|
let (a, b) = UnixStream::pair().unwrap();
|
|
let mut testwl = testwl::Server::new(false);
|
|
testwl.connect(a);
|
|
|
|
let (send, recv) = mpsc::channel();
|
|
let thread = std::thread::spawn(move || xwls::main(Some(b), Some(send)));
|
|
|
|
// wait for connection
|
|
let fd = unsafe { BorrowedFd::borrow_raw(testwl.poll_fd().as_raw_fd()) };
|
|
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
|
assert!(poll(&mut [pollfd.clone()], 100).unwrap() > 0);
|
|
testwl.dispatch();
|
|
|
|
let wait = Duration::from_secs(1);
|
|
assert_eq!(
|
|
recv.recv_timeout(wait),
|
|
Ok(xwls::StateEvent::CreatedServer),
|
|
"creating server"
|
|
);
|
|
assert_eq!(
|
|
recv.recv_timeout(wait),
|
|
Ok(xwls::StateEvent::ConnectedServer),
|
|
"connecting to server"
|
|
);
|
|
|
|
let mut f = [pollfd.clone()];
|
|
let start = std::time::Instant::now();
|
|
// Give Xwayland time to do its thing
|
|
while start.elapsed() < Duration::from_millis(500) {
|
|
let n = poll(&mut f, 100).unwrap();
|
|
if n > 0 {
|
|
testwl.dispatch();
|
|
}
|
|
}
|
|
|
|
assert_eq!(
|
|
recv.try_recv(),
|
|
Ok(xwls::StateEvent::XwaylandReady),
|
|
"connecting to xwayland"
|
|
);
|
|
|
|
Self {
|
|
testwl,
|
|
thread: ManuallyDrop::new(thread),
|
|
pollfd,
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn wait_and_dispatch(&mut self) {
|
|
let mut pollfd = [self.pollfd.clone()];
|
|
assert!(
|
|
poll(&mut pollfd, 50).unwrap() > 0,
|
|
"Did not receive any events"
|
|
);
|
|
self.pollfd.clear_revents();
|
|
self.testwl.dispatch();
|
|
|
|
while poll(&mut pollfd, 50).unwrap() > 0 {
|
|
self.testwl.dispatch();
|
|
self.pollfd.clear_revents();
|
|
}
|
|
}
|
|
|
|
fn create_window(
|
|
&mut self,
|
|
connection: &xcb::Connection,
|
|
override_redirect: bool,
|
|
x: i16,
|
|
y: i16,
|
|
width: u16,
|
|
height: u16,
|
|
) -> (x::Window, testwl::SurfaceId) {
|
|
let screen = connection.get_setup().roots().next().unwrap();
|
|
let wid = connection.generate_id();
|
|
let req = x::CreateWindow {
|
|
depth: x::COPY_FROM_PARENT as _,
|
|
wid,
|
|
parent: screen.root(),
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
border_width: 0,
|
|
class: x::WindowClass::InputOutput,
|
|
visual: screen.root_visual(),
|
|
value_list: &[
|
|
x::Cw::BackPixel(screen.white_pixel()),
|
|
x::Cw::OverrideRedirect(override_redirect),
|
|
],
|
|
};
|
|
connection.send_and_check_request(&req).unwrap();
|
|
|
|
let req = x::MapWindow { window: wid };
|
|
connection.send_and_check_request(&req).unwrap();
|
|
self.wait_and_dispatch();
|
|
|
|
let id = self
|
|
.testwl
|
|
.last_created_surface_id()
|
|
.expect("No surface created for window");
|
|
|
|
(wid, id)
|
|
}
|
|
|
|
fn create_toplevel(
|
|
&mut self,
|
|
connection: &xcb::Connection,
|
|
width: u16,
|
|
height: u16,
|
|
) -> (x::Window, testwl::SurfaceId) {
|
|
let (window, surface) = self.create_window(connection, false, 0, 0, width, height);
|
|
let data = self
|
|
.testwl
|
|
.get_surface_data(surface)
|
|
.expect("No surface data");
|
|
assert!(
|
|
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
|
"surface role was wrong: {:?}",
|
|
data.role
|
|
);
|
|
|
|
self.testwl
|
|
.configure_toplevel(surface, 100, 100, vec![xdg_toplevel::State::Activated]);
|
|
self.wait_and_dispatch();
|
|
let geometry = connection
|
|
.wait_for_reply(connection.send_request(&x::GetGeometry {
|
|
drawable: x::Drawable::Window(window),
|
|
}))
|
|
.unwrap();
|
|
|
|
assert_eq!(geometry.x(), 0);
|
|
assert_eq!(geometry.y(), 0);
|
|
assert_eq!(geometry.width(), 100);
|
|
assert_eq!(geometry.height(), 100);
|
|
|
|
(window, surface)
|
|
}
|
|
|
|
/// Triggers a Wayland side toplevel Close event and processes the corresponding
|
|
/// X11 side WM_DELETE_WINDOW client message
|
|
fn close_toplevel(
|
|
&mut self,
|
|
connection: &mut Connection,
|
|
window: x::Window,
|
|
surface: testwl::SurfaceId,
|
|
) {
|
|
self.testwl.close_toplevel(surface);
|
|
connection.await_event();
|
|
let event = connection
|
|
.inner
|
|
.poll_for_event()
|
|
.unwrap()
|
|
.expect("No close event");
|
|
|
|
let xcb::Event::X(x::Event::ClientMessage(event)) = event else {
|
|
panic!("Expected ClientMessage event, got {event:?}");
|
|
};
|
|
|
|
assert_eq!(event.window(), window);
|
|
assert_eq!(event.format(), 32);
|
|
assert_eq!(event.r#type(), connection.atoms.wm_protocols);
|
|
match event.data() {
|
|
x::ClientMessageData::Data32(d) => {
|
|
assert_eq!(d[0], connection.atoms.wm_delete_window.resource_id())
|
|
}
|
|
other => panic!("wrong data type: {other:?}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Connection {
|
|
inner: xcb::Connection,
|
|
pollfd: PollFd<'static>,
|
|
atoms: Atoms,
|
|
}
|
|
|
|
impl std::ops::Deref for Connection {
|
|
type Target = xcb::Connection;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl Connection {
|
|
fn new() -> Self {
|
|
// TODO: this will not work if there is an Xserver at 1024, or whenever we add multiple
|
|
// tests.
|
|
let (inner, _) = xcb::Connection::connect(Some(":0")).unwrap();
|
|
let fd = unsafe { BorrowedFd::borrow_raw(inner.as_raw_fd()) };
|
|
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
|
let atoms = Atoms::intern_all(&inner).unwrap();
|
|
|
|
Self {
|
|
inner,
|
|
pollfd,
|
|
atoms,
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn await_event(&mut self) {
|
|
assert!(
|
|
poll(&mut [self.pollfd.clone()], 100).expect("poll failed") > 0,
|
|
"Did not get any X11 events"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn toplevel_flow() {
|
|
let mut f = Fixture::new();
|
|
let mut connection = Connection::new();
|
|
let (window, surface) = f.create_toplevel(&connection.inner, 200, 200);
|
|
f.close_toplevel(&mut connection, window, surface);
|
|
|
|
// Simulate killing client
|
|
drop(connection);
|
|
f.wait_and_dispatch();
|
|
|
|
let data = f.testwl.get_surface_data(surface).expect("No surface data");
|
|
assert!(!data.toplevel().toplevel.is_alive());
|
|
}
|