Reorganize file layout

Moves satellite to be the root package, also allowing it to be built
by default.
This commit is contained in:
Shawn Wallace 2024-05-17 23:01:57 -04:00
parent 3afc9ffa9d
commit c1fc38c3d2
14 changed files with 71 additions and 67 deletions

202
src/clientside.rs Normal file
View file

@ -0,0 +1,202 @@
use crate::server::{ObjectEvent, ObjectKey};
use std::os::fd::OwnedFd;
use std::os::unix::net::UnixStream;
use std::sync::mpsc;
use wayland_client::protocol::{
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_registry::WlRegistry,
wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
wl_touch::WlTouch,
};
use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, Proxy, QueueHandle};
use wayland_protocols::wp::relative_pointer::zv1::client::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::client::{
self as dmabuf,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as DmabufFeedback,
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
},
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
},
xdg::{
shell::client::{
xdg_popup::XdgPopup, xdg_positioner::XdgPositioner, xdg_surface::XdgSurface,
xdg_toplevel::XdgToplevel, xdg_wm_base::XdgWmBase,
},
xdg_output::zv1::client::{
zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1::ZxdgOutputV1 as XdgOutput,
},
},
};
use wayland_server::protocol as server;
use wl_drm::client::wl_drm::WlDrm;
#[derive(Debug)]
pub struct GlobalData {
pub name: u32,
pub interface: String,
pub version: u32,
}
#[derive(Default)]
pub struct Globals {
pub(crate) events: Vec<(ObjectKey, ObjectEvent)>,
pub new_globals: Vec<GlobalData>,
}
pub type ClientQueueHandle = QueueHandle<Globals>;
pub struct ClientShmPool {
pub pool: WlShmPool,
pub fd: OwnedFd,
}
pub struct ClientState {
pub connection: Connection,
pub queue: EventQueue<Globals>,
pub qh: ClientQueueHandle,
pub globals: Globals,
pub registry: WlRegistry,
}
impl ClientState {
pub fn new(server_connection: Option<UnixStream>) -> Self {
let connection = if let Some(stream) = server_connection {
Connection::from_socket(stream)
} else {
Connection::connect_to_env()
}
.unwrap();
let mut queue = connection.new_event_queue::<Globals>();
let qh = queue.handle();
let mut globals = Globals::default();
let registry = connection.display().get_registry(&qh, ());
// Get initial globals
queue.roundtrip(&mut globals).unwrap();
Self {
connection,
queue,
qh,
globals,
registry,
}
}
}
pub type Event<T> = <T as Proxy>::Event;
delegate_noop!(Globals: WlCompositor);
delegate_noop!(Globals: ignore WlShm);
delegate_noop!(Globals: ignore ZwpLinuxDmabufV1);
delegate_noop!(Globals: ZwpRelativePointerManagerV1);
delegate_noop!(Globals: ignore dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1);
delegate_noop!(Globals: XdgPositioner);
delegate_noop!(Globals: WlShmPool);
delegate_noop!(Globals: WpViewporter);
delegate_noop!(Globals: WpViewport);
delegate_noop!(Globals: ZxdgOutputManagerV1);
impl Dispatch<WlRegistry, ()> for Globals {
fn event(
state: &mut Self,
_: &WlRegistry,
event: <WlRegistry as Proxy>::Event,
_: &(),
_: &wayland_client::Connection,
_: &wayland_client::QueueHandle<Self>,
) {
if let Event::<WlRegistry>::Global {
name,
interface,
version,
} = event
{
state.new_globals.push(GlobalData {
name,
interface,
version,
});
};
}
}
impl Dispatch<XdgWmBase, ()> for Globals {
fn event(
_: &mut Self,
base: &XdgWmBase,
event: <XdgWmBase as Proxy>::Event,
_: &(),
_: &wayland_client::Connection,
_: &wayland_client::QueueHandle<Self>,
) {
if let Event::<XdgWmBase>::Ping { serial } = event {
base.pong(serial);
}
}
}
impl Dispatch<WlCallback, server::wl_callback::WlCallback> for Globals {
fn event(
_: &mut Self,
_: &WlCallback,
event: <WlCallback as Proxy>::Event,
s_callback: &server::wl_callback::WlCallback,
_: &Connection,
_: &QueueHandle<Self>,
) {
if let Event::<WlCallback>::Done { callback_data } = event {
s_callback.done(callback_data);
}
}
}
impl Dispatch<WlOutput, mpsc::Sender<Event<WlOutput>>> for Globals {
fn event(
_: &mut Self,
_: &WlOutput,
event: <WlOutput as Proxy>::Event,
data: &mpsc::Sender<Event<WlOutput>>,
_: &Connection,
_: &QueueHandle<Self>,
) {
let _ = data.send(event);
}
}
macro_rules! push_events {
($type:ident) => {
impl Dispatch<$type, ObjectKey> for Globals {
fn event(
state: &mut Self,
_: &$type,
event: <$type as Proxy>::Event,
key: &ObjectKey,
_: &Connection,
_: &QueueHandle<Self>,
) {
state.events.push((*key, event.into()));
}
}
};
}
push_events!(WlSurface);
push_events!(WlBuffer);
push_events!(XdgSurface);
push_events!(XdgToplevel);
push_events!(XdgPopup);
push_events!(WlSeat);
push_events!(WlPointer);
push_events!(WlOutput);
push_events!(WlKeyboard);
push_events!(ZwpRelativePointerV1);
push_events!(WlDrm);
push_events!(DmabufFeedback);
push_events!(XdgOutput);
push_events!(WlTouch);

163
src/lib.rs Normal file
View file

@ -0,0 +1,163 @@
mod clientside;
mod server;
pub mod xstate;
use crate::server::{PendingSurfaceState, ServerState};
use crate::xstate::XState;
use log::{error, info};
use rustix::event::{poll, PollFd, PollFlags};
use std::io::{BufRead, BufReader, Read, Write};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::process::{Command, Stdio};
use std::sync::Arc;
use wayland_server::{Display, ListeningSocket};
use xcb::x;
pub trait XConnection: Sized + 'static {
type ExtraData: FromServerState<Self>;
fn root_window(&self) -> x::Window;
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, data: Self::ExtraData);
fn focus_window(&mut self, window: x::Window, data: Self::ExtraData);
fn close_window(&mut self, window: x::Window, data: Self::ExtraData);
}
pub trait FromServerState<C: XConnection> {
fn create(state: &ServerState<C>) -> Self;
}
type RealServerState = ServerState<Arc<xcb::Connection>>;
pub trait RunData {
fn display(&self) -> Option<&str>;
fn server(&self) -> Option<UnixStream> {
None
}
fn created_server(&self) {}
fn connected_server(&self) {}
fn xwayland_ready(&self, _display: String) {}
}
pub fn main(data: impl RunData) -> Option<()> {
let socket = ListeningSocket::bind_auto("wayland", 1..=128).unwrap();
let mut display = Display::<RealServerState>::new().unwrap();
let dh = display.handle();
data.created_server();
let mut server_state = RealServerState::new(dh, data.server());
let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap();
// Prevent creation of new Xwayland command from closing fd
rustix::io::fcntl_setfd(&xsock_xwl, rustix::io::FdFlags::empty()).unwrap();
let (ready_tx, ready_rx) = UnixStream::pair().unwrap();
rustix::io::fcntl_setfd(&ready_tx, rustix::io::FdFlags::empty()).unwrap();
let mut xwayland = Command::new("Xwayland");
if let Some(display) = data.display() {
xwayland.arg(display);
}
let mut xwayland = xwayland
.args([
"-rootless",
"-wm",
&xsock_xwl.as_raw_fd().to_string(),
"-displayfd",
&ready_tx.as_raw_fd().to_string(),
])
.env("WAYLAND_DISPLAY", socket.socket_name().unwrap())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let (mut finish_tx, mut finish_rx) = UnixStream::pair().unwrap();
let stderr = xwayland.stderr.take().unwrap();
std::thread::spawn(move || {
let reader = BufReader::new(stderr);
for line in reader.lines() {
let line = line.unwrap();
info!(target: "xwayland_process", "{line}");
}
let status = Box::new(xwayland.wait().unwrap());
let status = Box::into_raw(status) as usize;
finish_tx.write_all(&status.to_ne_bytes()).unwrap();
});
let mut ready_fds = [
PollFd::new(&socket, PollFlags::IN),
PollFd::new(&finish_rx, PollFlags::IN),
];
let connection = match poll(&mut ready_fds, -1) {
Ok(_) => {
if !ready_fds[1].revents().is_empty() {
let mut data = [0; (usize::BITS / 8) as usize];
finish_rx.read_exact(&mut data).unwrap();
let data = usize::from_ne_bytes(data);
let status: Box<std::process::ExitStatus> =
unsafe { Box::from_raw(data as *mut _) };
error!("Xwayland exited early with {status}");
return None;
}
data.connected_server();
socket.accept().unwrap().unwrap()
}
Err(e) => {
panic!("first poll failed: {e:?}")
}
};
drop(finish_rx);
server_state.connect(connection);
server_state.run();
let mut xstate: Option<XState> = None;
// Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for
// the rest of our program anyway
let server_fd = unsafe { BorrowedFd::borrow_raw(server_state.clientside_fd().as_raw_fd()) };
let display_fd = unsafe { BorrowedFd::borrow_raw(display.backend().poll_fd().as_raw_fd()) };
let mut fds = [
PollFd::from_borrowed_fd(server_fd, PollFlags::IN),
PollFd::new(&xsock_wl, PollFlags::IN),
PollFd::from_borrowed_fd(display_fd, PollFlags::IN),
PollFd::new(&ready_rx, PollFlags::IN),
];
let mut ready = false;
loop {
match poll(&mut fds, -1) {
Ok(_) => {
if !fds[3].revents().is_empty() {
ready = true;
}
}
Err(other) => panic!("Poll failed: {other:?}"),
}
if xstate.is_none() && ready {
let xstate = xstate.insert(XState::new(xsock_wl.as_fd()));
let mut reader = BufReader::new(&ready_rx);
let mut display = String::new();
reader.read_line(&mut display).unwrap();
display.pop();
display.insert(0, ':');
info!("Connected to Xwayland on {display}");
data.xwayland_ready(display);
server_state.set_x_connection(xstate.connection.clone());
server_state.atoms = Some(xstate.atoms.clone());
}
if let Some(state) = &mut xstate {
state.handle_events(&mut server_state);
}
display.dispatch_clients(&mut server_state).unwrap();
server_state.run();
display.flush_clients().unwrap();
}
}

24
src/main.rs Normal file
View file

@ -0,0 +1,24 @@
fn main() {
pretty_env_logger::formatted_timed_builder()
.filter_level(log::LevelFilter::Info)
.parse_default_env()
.init();
xwayland_satellite::main(RealData(get_display()));
}
#[repr(transparent)]
struct RealData(Option<String>);
impl xwayland_satellite::RunData for RealData {
fn display(&self) -> Option<&str> {
self.0.as_deref()
}
}
fn get_display() -> Option<String> {
let mut args: Vec<_> = std::env::args().collect();
if args.len() > 2 {
panic!("Unexpected arguments: {:?}", &args[2..]);
}
(args.len() == 2).then(|| args.swap_remove(1))
}

898
src/server/dispatch.rs Normal file
View file

@ -0,0 +1,898 @@
use super::*;
use log::{debug, trace, warn};
use std::sync::{Arc, OnceLock};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
relative_pointer::zv1::{
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManClient,
server::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
},
},
viewporter::{client as c_vp, server as s_vp},
},
xdg::xdg_output::zv1::{
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
server::{
zxdg_output_manager_v1::{
self as s_output_man, ZxdgOutputManagerV1 as OutputManServer,
},
zxdg_output_v1::{self as s_xdgo, ZxdgOutputV1 as XdgOutputServer},
},
},
};
use wayland_server::{
protocol::{
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat,
wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface, wl_touch::WlTouch,
},
Dispatch, DisplayHandle, GlobalDispatch, Resource,
};
// noop
impl<C: XConnection> Dispatch<WlCallback, ()> for ServerState<C> {
fn request(
_: &mut Self,
_: &wayland_server::Client,
_: &WlCallback,
_: <WlCallback as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
unreachable!();
}
}
impl<C: XConnection> Dispatch<WlSurface, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlSurface,
request: <WlSurface as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
let surface: &SurfaceData = state.objects[*key].as_ref();
let configured =
surface.role.is_none() || surface.xdg().is_none() || surface.xdg().unwrap().configured;
match request {
Request::<WlSurface>::Attach { buffer, x, y } => {
if buffer.is_none() {
trace!("xwayland attached null buffer to {:?}", surface.client);
}
let buffer = buffer.as_ref().map(|b| {
let key: &ObjectKey = b.data().unwrap();
let data: &Buffer = state.objects[*key].as_ref();
&data.client
});
if configured {
surface.client.attach(buffer, x, y);
} else {
let buffer = buffer.cloned();
let surface: &mut SurfaceData = state.objects[*key].as_mut();
surface.attach = Some(SurfaceAttach { buffer, x, y });
}
}
Request::<WlSurface>::DamageBuffer {
x,
y,
width,
height,
} => {
if configured {
surface.client.damage_buffer(x, y, width, height);
}
}
Request::<WlSurface>::Frame { callback } => {
let cb = data_init.init(callback, ());
if configured {
surface.client.frame(&state.qh, cb);
} else {
let surface: &mut SurfaceData = state.objects[*key].as_mut();
surface.frame_callback = Some(cb);
}
}
Request::<WlSurface>::Commit => {
if configured {
surface.client.commit();
}
}
Request::<WlSurface>::Destroy => {
let mut object = state.objects.remove(*key).unwrap();
let surface: &mut SurfaceData = object.as_mut();
surface.destroy_role();
surface.client.destroy();
debug!("deleting key: {:?}", key);
}
Request::<WlSurface>::SetBufferScale { scale } => {
surface.client.set_buffer_scale(scale);
}
other => warn!("unhandled surface request: {other:?}"),
}
}
}
impl<C: XConnection>
Dispatch<WlCompositor, ClientGlobalWrapper<client::wl_compositor::WlCompositor>>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlCompositor,
request: <WlCompositor as wayland_server::Resource>::Request,
client: &ClientGlobalWrapper<client::wl_compositor::WlCompositor>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlCompositor>::CreateSurface { id } => {
let mut surface_id = None;
let key = state.objects.insert_with_key(|key| {
debug!("new surface with key {key:?}");
let client = client.create_surface(&state.qh, key);
let server = data_init.init(id, key);
surface_id = Some(server.id().protocol_id());
SurfaceData {
client,
server,
key,
attach: None,
frame_callback: None,
role: None,
}
.into()
});
let surface_id = surface_id.unwrap();
if let Some((win, window_data)) =
state.windows.iter_mut().find_map(|(win, data)| {
Some(*win).zip((data.surface_id == surface_id).then_some(data))
})
{
window_data.surface_key = Some(key);
state.associated_windows.insert(key, win);
debug!("associate surface {surface_id} with window {win:?}");
if window_data.mapped {
state.create_role_window(win, key);
}
}
}
other => {
warn!("unhandled wlcompositor request: {other:?}");
}
}
}
}
impl<C: XConnection> Dispatch<WlBuffer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlBuffer,
request: <WlBuffer as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
assert!(matches!(request, Request::<WlBuffer>::Destroy));
let buf: &Buffer = state.objects[*key].as_ref();
buf.client.destroy();
state.objects.remove(*key);
}
}
impl<C: XConnection> Dispatch<WlShmPool, ClientShmPool> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlShmPool,
request: <WlShmPool as Resource>::Request,
c_pool: &ClientShmPool,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlShmPool>::CreateBuffer {
id,
offset,
width,
height,
stride,
format,
} => {
state.objects.insert_with_key(|key| {
let client = c_pool.pool.create_buffer(
offset,
width,
height,
stride,
convert_wenum(format),
&state.qh,
key,
);
let server = data_init.init(id, key);
Buffer { server, client }.into()
});
}
Request::<WlShmPool>::Destroy => {
c_pool.pool.destroy();
}
other => warn!("unhandled shmpool request: {other:?}"),
}
}
}
impl<C: XConnection> Dispatch<WlShm, ClientGlobalWrapper<client::wl_shm::WlShm>>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlShm,
request: <WlShm as Resource>::Request,
client: &ClientGlobalWrapper<client::wl_shm::WlShm>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlShm>::CreatePool { id, fd, size } => {
let c_pool = client.create_pool(fd.as_fd(), size, &state.qh, ());
let c_pool = ClientShmPool { pool: c_pool, fd };
data_init.init(id, c_pool);
}
other => {
warn!("unhandled shm pool request: {other:?}");
}
}
}
}
impl<C: XConnection> Dispatch<WlPointer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlPointer,
request: <WlPointer as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let Pointer {
client: c_pointer, ..
}: &Pointer = state.objects[*key].as_ref();
match request {
Request::<WlPointer>::SetCursor {
serial,
hotspot_x,
hotspot_y,
surface,
} => {
let c_surface = surface.map(|s| state.get_client_surface_from_server(s));
c_pointer.set_cursor(serial, c_surface, hotspot_x, hotspot_y);
}
Request::<WlPointer>::Release => {
c_pointer.release();
state.objects.remove(*key);
}
_ => warn!("unhandled cursor request: {request:?}"),
}
}
}
impl<C: XConnection> Dispatch<WlKeyboard, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlKeyboard>::Release => {
let Keyboard { client, .. }: &_ = state.objects[*key].as_ref();
client.release();
state.objects.remove(*key);
}
_ => unreachable!(),
}
}
}
impl<C: XConnection> Dispatch<WlTouch, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlTouch,
request: <WlTouch as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlTouch>::Release => {
let Touch { client, .. }: &_ = state.objects[*key].as_ref();
client.release();
state.objects.remove(*key);
}
_ => unreachable!(),
}
}
}
impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlSeat,
request: <WlSeat as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<WlSeat>::GetPointer { id } => {
state
.objects
.insert_from_other_objects([*key], |[seat_obj], key| {
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
let client = client.get_pointer(&state.qh, key);
let server = data_init.init(id, key);
trace!("new pointer: {server:?}");
Pointer::new(server, client).into()
});
}
Request::<WlSeat>::GetKeyboard { id } => {
state
.objects
.insert_from_other_objects([*key], |[seat_obj], key| {
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
let client = client.get_keyboard(&state.qh, key);
let server = data_init.init(id, key);
Keyboard { client, server }.into()
});
}
Request::<WlSeat>::GetTouch { id } => {
state
.objects
.insert_from_other_objects([*key], |[seat_obj], key| {
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
let client = client.get_touch(&state.qh, key);
let server = data_init.init(id, key);
Touch { client, server }.into()
});
}
other => warn!("unhandled seat request: {other:?}"),
}
}
}
impl<C: XConnection> Dispatch<RelativePointerServer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &RelativePointerServer,
request: <RelativePointerServer as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
if let Request::<RelativePointerServer>::Destroy = request {
let obj: &RelativePointer = state.objects[*key].as_ref();
obj.client.destroy();
state.objects.remove(*key);
}
}
}
impl<C: XConnection>
Dispatch<RelativePointerManServer, ClientGlobalWrapper<RelativePointerManClient>>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &RelativePointerManServer,
request: <RelativePointerManServer as Resource>::Request,
client: &ClientGlobalWrapper<RelativePointerManClient>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
Request::<RelativePointerManServer>::GetRelativePointer { id, pointer } => {
let p_key: ObjectKey = pointer.data().copied().unwrap();
state
.objects
.insert_from_other_objects([p_key], |[pointer_obj], key| {
let pointer: &Pointer = pointer_obj.try_into().unwrap();
let client = client.get_relative_pointer(&pointer.client, &state.qh, key);
let server = data_init.init(id, key);
RelativePointer { client, server }.into()
});
}
_ => warn!("unhandled relative pointer request: {request:?}"),
}
}
}
impl<C: XConnection> Dispatch<WlOutput, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlOutput,
request: <WlOutput as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wayland_server::protocol::wl_output::Request::Release => {
let Output { client, .. }: &_ = state.objects[*key].as_ref();
client.release();
todo!("handle wloutput destruction");
}
_ => warn!("unhandled output request {request:?}"),
}
}
}
impl<C: XConnection>
Dispatch<s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
request: <s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
use s_dmabuf::zwp_linux_dmabuf_feedback_v1::Request::*;
match request {
Destroy => {
let dmabuf: &DmabufFeedback = state.objects[*key].as_ref();
dmabuf.client.destroy();
state.objects.remove(*key);
}
_ => unreachable!(),
}
}
}
impl<C: XConnection>
Dispatch<
s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
> for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
request: <s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 as Resource>::Request,
c_params: &c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use s_dmabuf::zwp_linux_buffer_params_v1::Request::*;
match request {
// TODO: Xwayland doesn't actually seem to use the Create request, and I don't feel like implementing it...
Create { .. } => todo!(),
CreateImmed {
buffer_id,
width,
height,
format,
flags,
} => {
state.objects.insert_with_key(|key| {
let client = c_params.create_immed(
width,
height,
format,
convert_wenum(flags),
&state.qh,
key,
);
let server = data_init.init(buffer_id, key);
Buffer { server, client }.into()
});
}
Add {
fd,
plane_idx,
offset,
stride,
modifier_hi,
modifier_lo,
} => {
c_params.add(
fd.as_fd(),
plane_idx,
offset,
stride,
modifier_hi,
modifier_lo,
);
}
Destroy => {
c_params.destroy();
}
_ => warn!("unhandled params request: {request:?}"),
}
}
}
impl<C: XConnection>
Dispatch<
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
> for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
request: <s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 as Resource>::Request,
client: &ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use s_dmabuf::zwp_linux_dmabuf_v1::Request::*;
match request {
Destroy => {
client.destroy();
}
CreateParams { params_id } => {
let c_params = client.create_params(&state.qh, ());
data_init.init(params_id, c_params);
}
GetDefaultFeedback { id } => {
state.objects.insert_with_key(|key| {
let client = client.get_default_feedback(&state.qh, key);
let server = data_init.init(id, key);
DmabufFeedback { client, server }.into()
});
}
GetSurfaceFeedback { id, surface } => {
let surf_key: ObjectKey = surface.data().copied().unwrap();
state
.objects
.insert_from_other_objects([surf_key], |[surface_obj], key| {
let SurfaceData {
client: c_surface, ..
}: &SurfaceData = surface_obj.try_into().unwrap();
let client = client.get_surface_feedback(c_surface, &state.qh, key);
let server = data_init.init(id, key);
DmabufFeedback { client, server }.into()
});
}
_ => warn!("unhandled dmabuf request: {request:?}"),
}
}
}
impl<C: XConnection> Dispatch<WlDrmServer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlDrmServer,
request: <WlDrmServer as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use wl_drm::server::wl_drm::Request::*;
type DrmFn = dyn FnOnce(
&wl_drm::client::wl_drm::WlDrm,
ObjectKey,
&ClientQueueHandle,
) -> client::wl_buffer::WlBuffer;
let mut bufs: Option<(Box<DrmFn>, wayland_server::New<WlBuffer>)> = None;
match request {
CreateBuffer {
id,
name,
width,
height,
stride,
format,
} => {
bufs = Some((
Box::new(move |drm, key, qh| {
drm.create_buffer(name, width, height, stride, format, qh, key)
}),
id,
));
}
CreatePlanarBuffer {
id,
name,
width,
height,
format,
offset0,
stride0,
offset1,
stride1,
offset2,
stride2,
} => {
bufs = Some((
Box::new(move |drm, key, qh| {
drm.create_planar_buffer(
name, width, height, format, offset0, stride0, offset1, stride1,
offset2, stride2, qh, key,
)
}),
id,
));
}
CreatePrimeBuffer {
id,
name,
width,
height,
format,
offset0,
stride0,
offset1,
stride1,
offset2,
stride2,
} => {
bufs = Some((
Box::new(move |drm, key, qh| {
drm.create_prime_buffer(
name.as_fd(),
width,
height,
format,
offset0,
stride0,
offset1,
stride1,
offset2,
stride2,
qh,
key,
)
}),
id,
));
}
Authenticate { id } => {
let drm: &Drm = state.objects[*key].as_ref();
drm.client.authenticate(id);
}
_ => unreachable!(),
}
if let Some((buf_create, id)) = bufs {
state
.objects
.insert_from_other_objects([*key], |[drm_obj], key| {
let drm: &Drm = drm_obj.try_into().unwrap();
let client = buf_create(&drm.client, key, &state.qh);
let server = data_init.init(id, key);
Buffer { client, server }.into()
});
}
}
}
impl<C: XConnection> Dispatch<s_vp::wp_viewport::WpViewport, c_vp::wp_viewport::WpViewport>
for ServerState<C>
{
fn request(
_: &mut Self,
_: &wayland_server::Client,
_: &s_vp::wp_viewport::WpViewport,
request: <s_vp::wp_viewport::WpViewport as Resource>::Request,
c_viewport: &c_vp::wp_viewport::WpViewport,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
simple_event_shunt! {
c_viewport, request: s_vp::wp_viewport::Request => [
SetSource { x, y, width, height },
SetDestination { width, height },
Destroy
]
}
}
}
impl<C: XConnection>
Dispatch<
s_vp::wp_viewporter::WpViewporter,
ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
> for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_vp::wp_viewporter::WpViewporter,
request: <s_vp::wp_viewporter::WpViewporter as Resource>::Request,
client: &ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use s_vp::wp_viewporter;
match request {
wp_viewporter::Request::GetViewport { id, surface } => {
let c_surface = state.get_client_surface_from_server(surface);
let c_viewport = client.get_viewport(c_surface, &state.qh, ());
data_init.init(id, c_viewport);
}
wp_viewporter::Request::Destroy => {
client.destroy();
}
_ => unreachable!(),
}
}
}
impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &XdgOutputServer,
request: <XdgOutputServer as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let s_xdgo::Request::Destroy = request else {
unreachable!();
};
let output: &XdgOutput = state.objects[*key].as_ref();
output.client.destroy();
state.objects.remove(*key);
}
}
impl<C: XConnection> Dispatch<OutputManServer, ClientGlobalWrapper<OutputManClient>>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &OutputManServer,
request: <OutputManServer as Resource>::Request,
client: &ClientGlobalWrapper<OutputManClient>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
s_output_man::Request::GetXdgOutput { id, output } => {
let output_key: ObjectKey = output.data().copied().unwrap();
state
.objects
.insert_from_other_objects([output_key], |[output_obj], key| {
let output: &Output = output_obj.try_into().unwrap();
let client = client.get_xdg_output(&output.client, &state.qh, key);
let server = data_init.init(id, key);
XdgOutput { server, client }.into()
});
}
s_output_man::Request::Destroy => {}
_ => unreachable!(),
}
}
}
#[derive(Clone)]
pub(crate) struct ClientGlobalWrapper<T: Proxy>(Arc<OnceLock<T>>);
impl<T: Proxy> std::ops::Deref for ClientGlobalWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.get().unwrap()
}
}
impl<T: Proxy> Default for ClientGlobalWrapper<T> {
fn default() -> Self {
Self(Arc::default())
}
}
macro_rules! global_dispatch_no_events {
($server:ty, $client:ty) => {
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
where
ServerState<C>: Dispatch<$server, ClientGlobalWrapper<$client>>,
Globals: wayland_client::Dispatch<$client, ()>,
{
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &wayland_server::Client,
resource: wayland_server::New<$server>,
data: &GlobalData,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
let client = ClientGlobalWrapper::<$client>::default();
let server = data_init.init(resource, client.clone());
client
.0
.set(
state
.clientside
.registry
.bind(data.name, server.version(), &state.qh, ()),
)
.unwrap();
}
}
};
}
macro_rules! global_dispatch_with_events {
($server:ty, $client:ty) => {
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
where
$server: Resource,
$client: Proxy,
ServerState<C>: Dispatch<$server, ObjectKey>,
Globals: wayland_client::Dispatch<$client, ObjectKey>,
GenericObject<$server, $client>: Into<Object>,
{
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &wayland_server::Client,
resource: wayland_server::New<$server>,
data: &GlobalData,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
state.objects.insert_with_key(|key| {
let server = data_init.init(resource, key);
let client = state.clientside.registry.bind::<$client, _, _>(
data.name,
server.version(),
&state.qh,
key,
);
GenericObject { server, client }.into()
});
}
}
};
}
global_dispatch_no_events!(WlShm, client::wl_shm::WlShm);
global_dispatch_no_events!(WlCompositor, client::wl_compositor::WlCompositor);
global_dispatch_no_events!(RelativePointerManServer, RelativePointerManClient);
global_dispatch_no_events!(
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
);
global_dispatch_no_events!(OutputManServer, OutputManClient);
global_dispatch_no_events!(
s_vp::wp_viewporter::WpViewporter,
c_vp::wp_viewporter::WpViewporter
);
global_dispatch_with_events!(WlSeat, client::wl_seat::WlSeat);
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput);
global_dispatch_with_events!(WlDrmServer, WlDrmClient);

646
src/server/event.rs Normal file
View file

@ -0,0 +1,646 @@
use super::*;
use log::{debug, trace, warn};
use std::os::fd::AsFd;
use wayland_client::{protocol as client, Proxy};
use wayland_protocols::{
wp::relative_pointer::zv1::{
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1 as RelativePointerClient},
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
},
xdg::{
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
xdg_output::zv1::{
client::zxdg_output_v1::{self, ZxdgOutputV1 as ClientXdgOutput},
server::zxdg_output_v1::ZxdgOutputV1 as ServerXdgOutput,
},
},
};
use wayland_server::protocol::{
wl_buffer::WlBuffer, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer,
wl_seat::WlSeat, wl_touch::WlTouch,
};
/// Lord forgive me, I am a sinner, who's probably gonna sin again
/// This macro takes an enum variant name and a list of the field names of the enum
/// and/or closures that take an argument that must be named the same as the field name,
/// and converts that into a destructured enum
/// shunt_helper_enum!(Foo [a, |b| b.do_thing(), c]) -> Foo {a, b, c}
macro_rules! shunt_helper_enum {
// No fields
($variant:ident) => { $variant };
// Starting state: variant destructure
($variant:ident $([$($body:tt)+])?) => {
shunt_helper_enum!($variant [$($($body)+)?] -> [])
};
// Add field to list
($variant:ident [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
};
// Add closure field to list
($variant:ident [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
};
// Finalize into enum variant
($variant:ident [] -> [,$($body:tt)+]) => { $variant { $($body)+ } };
}
/// This does the same thing as shunt_helper_enum, except it transforms the fields into the given
/// function/method call.
/// shunt_helper_fn!({obj.foo} [a, |b| b.do_thing(), c]) -> obj.foo(a, b.do_thing(), c)
macro_rules! shunt_helper_fn {
// No fields
({$($fn:tt)+}) => { $($fn)+() };
// Starting state
($fn:tt [$($body:tt)+]) => {
shunt_helper_fn!($fn [$($body)+] -> [])
};
// Add field to list
($fn:tt [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $field])
};
// Add closure expression to list
($fn:tt [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $conv])
};
// Finalize into function call
({$($fn:tt)+} [] -> [,$($body:tt)+]) => { $($fn)+($($body)+) };
}
/// Takes an object, the name of a variable holding an event, the event type, and a list of the
/// variants with their fields, and converts them into function calls on their arguments
/// Event { field1, field2 } => obj.event(field1, field2)
macro_rules! simple_event_shunt {
($obj:expr, $event:ident: $event_type:path => [
$( $variant:ident $({ $($fields:tt)* })? ),+
]) => {
{
use $event_type::*;
match $event {
$(
shunt_helper_enum!( $variant $( [ $($fields)* ] )? ) => {
paste::paste! {
shunt_helper_fn!( { $obj.[<$variant:snake>] } $( [ $($fields)* ] )? )
}
}
)+
_ => log::warn!(concat!("unhandled", stringify!($event_type), ": {:?}"), $event)
}
}
}
}
pub(crate) use shunt_helper_enum;
pub(crate) use shunt_helper_fn;
pub(crate) use simple_event_shunt;
#[derive(Debug)]
pub(crate) enum SurfaceEvents {
WlSurface(client::wl_surface::Event),
XdgSurface(xdg_surface::Event),
Toplevel(xdg_toplevel::Event),
Popup(xdg_popup::Event),
}
macro_rules! impl_from {
($type:ty, $variant:ident) => {
impl From<$type> for ObjectEvent {
fn from(value: $type) -> Self {
Self::Surface(SurfaceEvents::$variant(value))
}
}
};
}
impl_from!(client::wl_surface::Event, WlSurface);
impl_from!(xdg_surface::Event, XdgSurface);
impl_from!(xdg_toplevel::Event, Toplevel);
impl_from!(xdg_popup::Event, Popup);
impl HandleEvent for SurfaceData {
type Event = SurfaceEvents;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
match event {
SurfaceEvents::WlSurface(event) => self.surface_event(event, state),
SurfaceEvents::XdgSurface(event) => self.xdg_event(event, state),
SurfaceEvents::Toplevel(event) => self.toplevel_event(event, state),
SurfaceEvents::Popup(event) => self.popup_event(event, state),
}
}
}
impl SurfaceData {
fn surface_event<C: XConnection>(
&self,
event: client::wl_surface::Event,
state: &mut ServerState<C>,
) {
let surface = &self.server;
simple_event_shunt! {
surface, event: client::wl_surface::Event => [
Enter { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
Leave { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
PreferredBufferScale { factor }
]
}
}
fn xdg_event<C: XConnection>(&mut self, event: xdg_surface::Event, state: &mut ServerState<C>) {
let connection = state.connection.as_mut().unwrap();
let xdg_surface::Event::Configure { serial } = event else {
unreachable!();
};
let xdg = self.xdg_mut().unwrap();
xdg.surface.ack_configure(serial);
xdg.configured = true;
if let Some(pending) = xdg.pending.take() {
let window = state.associated_windows[self.key];
let window = state.windows.get_mut(&window).unwrap();
let width = if pending.width > 0 {
pending.width as _
} else {
window.attrs.dims.width
};
let height = if pending.height > 0 {
pending.height as _
} else {
window.attrs.dims.height
};
debug!(
"configuring {:?}: {}x{}, {width}x{height}",
window.window, pending.x, pending.y
);
connection.set_window_dims(
window.window,
PendingSurfaceState {
x: pending.x,
y: pending.y,
width: width as _,
height: height as _,
},
);
window.attrs.dims = WindowDims {
x: pending.x as _,
y: pending.y as _,
width,
height,
};
}
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
self.client.attach(buffer.as_ref(), x, y);
}
if let Some(cb) = self.frame_callback.take() {
self.client.frame(&state.qh, cb);
}
self.client.commit();
}
fn toplevel_event<C: XConnection>(
&mut self,
event: xdg_toplevel::Event,
state: &mut ServerState<C>,
) {
match event {
xdg_toplevel::Event::Configure {
width,
height,
states,
} => {
debug!("configuring toplevel {width}x{height}, {states:?}");
let activated = states.contains(&(u32::from(xdg_toplevel::State::Activated) as u8));
if activated {
let window = state.associated_windows[self.key];
state.to_focus = Some(window);
}
if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
let prev_fs = toplevel.fullscreen;
toplevel.fullscreen =
states.contains(&(u32::from(xdg_toplevel::State::Fullscreen) as u8));
if toplevel.fullscreen != prev_fs {
let window = state.associated_windows[self.key];
let data = C::ExtraData::create(state);
state.connection.as_mut().unwrap().set_fullscreen(
window,
toplevel.fullscreen,
data,
);
}
};
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
width,
height,
..Default::default()
});
}
xdg_toplevel::Event::Close => {
let window = state.associated_windows[self.key];
state.close_x_window(window);
}
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
}
}
fn popup_event<C: XConnection>(&mut self, event: xdg_popup::Event, _: &mut ServerState<C>) {
match event {
xdg_popup::Event::Configure {
x,
y,
width,
height,
} => {
trace!("popup configure: {x}x{y}, {width}x{height}");
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
x,
y,
width,
height,
});
}
xdg_popup::Event::Repositioned { .. } => {}
other => todo!("{other:?}"),
}
}
}
pub struct GenericObject<Server: Resource, Client: Proxy> {
pub server: Server,
pub client: Client,
}
pub type Buffer = GenericObject<WlBuffer, client::wl_buffer::WlBuffer>;
impl HandleEvent for Buffer {
type Event = client::wl_buffer::Event;
fn handle_event<C: XConnection>(&mut self, _: Self::Event, _: &mut ServerState<C>) {
// The only event from a buffer would be the release.
self.server.release();
}
}
pub type XdgOutput = GenericObject<ServerXdgOutput, ClientXdgOutput>;
impl HandleEvent for XdgOutput {
type Event = zxdg_output_v1::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zxdg_output_v1::Event => [
LogicalPosition { x, y },
LogicalSize { width, height },
Done,
Name { name },
Description { description }
]
}
}
}
pub type Seat = GenericObject<WlSeat, client::wl_seat::WlSeat>;
impl HandleEvent for Seat {
type Event = client::wl_seat::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: client::wl_seat::Event => [
Capabilities { |capabilities| convert_wenum(capabilities) },
Name { name }
]
}
}
}
pub struct Pointer {
server: WlPointer,
pub client: client::wl_pointer::WlPointer,
pending_enter: PendingEnter,
}
impl Pointer {
pub fn new(server: WlPointer, client: client::wl_pointer::WlPointer) -> Self {
Self {
server,
client,
pending_enter: PendingEnter(None),
}
}
}
struct PendingEnter(Option<client::wl_pointer::Event>);
impl HandleEvent for Pointer {
type Event = client::wl_pointer::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
// Workaround GTK (stupidly) autoclosing popups if it receives an wl_pointer.enter
// event shortly after creation.
// When Niri creates a popup, it immediately sends wl_pointer.enter on the new surface,
// generating an EnterNotify event, and Xwayland will send a release button event.
// In its menu implementation, GTK treats EnterNotify "this menu is now active" and will
// destroy the menu if this occurs within a 500 ms interval (which it always does with
// Niri). Other compositors do not run into this problem because they appear to not send
// wl_pointer.enter until the user actually moves the mouse in the popup.
let mut process_event = Vec::new();
match event {
client::wl_pointer::Event::Enter {
serial,
ref surface,
surface_x,
surface_y,
} => {
let do_enter = || {
debug!("entering surface ({serial})");
let surface = state.get_server_surface_from_client(surface.clone());
self.server.enter(serial, surface, surface_x, surface_y);
};
let surface_key: ObjectKey = surface.data().copied().unwrap();
let surface_data: &SurfaceData = state.objects[surface_key].as_ref();
if matches!(surface_data.role, Some(SurfaceRole::Popup(_))) {
match self.pending_enter.0.take() {
Some(e) => {
let client::wl_pointer::Event::Enter {
serial: pending_serial,
..
} = e
else {
unreachable!();
};
if serial == pending_serial {
do_enter();
} else {
self.pending_enter.0 = Some(event);
}
}
None => {
self.pending_enter.0 = Some(event);
}
}
} else {
self.pending_enter.0.take();
do_enter();
}
}
client::wl_pointer::Event::Leave { serial, surface } => {
debug!("leaving surface ({serial})");
self.pending_enter.0.take();
self.server
.leave(serial, state.get_server_surface_from_client(surface));
}
client::wl_pointer::Event::Motion {
time,
surface_x,
surface_y,
} => {
if let Some(p) = &self.pending_enter.0 {
let client::wl_pointer::Event::Enter {
serial,
surface,
surface_x,
surface_y,
} = p
else {
unreachable!();
};
process_event.push(client::wl_pointer::Event::Enter {
serial: *serial,
surface: surface.clone(),
surface_x: *surface_x,
surface_y: *surface_y,
});
process_event.push(event);
trace!("resending enter ({serial}) before motion");
} else {
self.server.motion(time, surface_x, surface_y);
}
}
_ => simple_event_shunt! {
self.server, event: client::wl_pointer::Event => [
Enter {
serial,
|surface| state.get_server_surface_from_client(surface),
surface_x,
surface_y
},
Leave {
serial,
|surface| state.get_server_surface_from_client(surface)
},
Motion {
time,
surface_x,
surface_y
},
Frame,
Button {
serial,
time,
button,
|state| convert_wenum(state)
},
Axis {
time,
|axis| convert_wenum(axis),
value
},
AxisSource {
|axis_source| convert_wenum(axis_source)
},
AxisStop {
time,
|axis| convert_wenum(axis)
},
AxisDiscrete {
|axis| convert_wenum(axis),
discrete
},
AxisValue120 {
|axis| convert_wenum(axis),
value120
},
AxisRelativeDirection {
|axis| convert_wenum(axis),
|direction| convert_wenum(direction)
}
]
},
}
for event in process_event {
self.handle_event(event, state);
}
}
}
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
impl HandleEvent for Keyboard {
type Event = client::wl_keyboard::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: client::wl_keyboard::Event => [
Keymap {
|format| convert_wenum(format),
|fd| fd.as_fd(),
size
},
Enter {
serial,
|surface| state.get_server_surface_from_client(surface),
keys
},
Leave {
serial,
|surface| state.get_server_surface_from_client(surface)
},
Key {
serial,
time,
key,
|state| convert_wenum(state)
},
Modifiers {
serial,
mods_depressed,
mods_latched,
mods_locked,
group
},
RepeatInfo {
rate,
delay
}
]
}
}
}
pub type Touch = GenericObject<WlTouch, client::wl_touch::WlTouch>;
impl HandleEvent for Touch {
type Event = client::wl_touch::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: client::wl_touch::Event => [
Down {
serial,
time,
|surface| state.get_server_surface_from_client(surface),
id,
x,
y
},
Up {
serial,
time,
id
},
Motion {
time,
id,
x,
y
},
Frame,
Cancel,
Shape {
id,
major,
minor
},
Orientation {
id,
orientation
}
]
}
}
}
pub type Output = GenericObject<WlOutput, client::wl_output::WlOutput>;
impl HandleEvent for Output {
type Event = client::wl_output::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: client::wl_output::Event => [
Name { name },
Description { description },
Mode {
|flags| convert_wenum(flags),
width,
height,
refresh
},
Scale { factor },
Geometry {
x,
y,
physical_width,
physical_height,
|subpixel| convert_wenum(subpixel),
make,
model,
|transform| convert_wenum(transform)
},
Done
]
}
}
}
pub type Drm = GenericObject<WlDrmServer, WlDrmClient>;
impl HandleEvent for Drm {
type Event = wl_drm::client::wl_drm::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: wl_drm::client::wl_drm::Event => [
Device { name },
Format { format },
Authenticated,
Capabilities { value }
]
}
}
}
pub type DmabufFeedback = GenericObject<
s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
c_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
>;
impl HandleEvent for DmabufFeedback {
type Event = c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event => [
Done,
FormatTable { |fd| fd.as_fd(), size },
MainDevice { device },
TrancheDone,
TrancheTargetDevice { device },
TrancheFormats { indices },
TrancheFlags { |flags| convert_wenum(flags) }
]
}
}
}
pub type RelativePointer = GenericObject<RelativePointerServer, RelativePointerClient>;
impl HandleEvent for RelativePointer {
type Event = zwp_relative_pointer_v1::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: rp::client::zwp_relative_pointer_v1::Event => [
RelativeMotion {
utime_hi,
utime_lo,
dx,
dy,
dx_unaccel,
dy_unaccel
}
]
}
}
}

845
src/server/mod.rs Normal file
View file

@ -0,0 +1,845 @@
mod dispatch;
mod event;
#[cfg(test)]
mod tests;
use self::event::*;
use super::FromServerState;
use crate::clientside::*;
use crate::xstate::{Atoms, WindowDims, WmHints, WmName, WmNormalHints};
use crate::XConnection;
use log::{debug, warn};
use rustix::event::{poll, PollFd, PollFlags};
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
use std::collections::HashMap;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use wayland_client::{protocol as client, Proxy};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
relative_pointer::zv1::{
self as rp, server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
},
viewporter::server as s_vp,
},
xdg::{
shell::client::{
xdg_popup::XdgPopup,
xdg_positioner::{Anchor, Gravity, XdgPositioner},
xdg_surface::XdgSurface,
xdg_toplevel::XdgToplevel,
xdg_wm_base::XdgWmBase,
},
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
},
};
use wayland_server::{
protocol::{
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
wl_shm::WlShm, wl_surface::WlSurface,
},
DisplayHandle, Resource, WEnum,
};
use wl_drm::{client::wl_drm::WlDrm as WlDrmClient, server::wl_drm::WlDrm as WlDrmServer};
use xcb::x;
impl From<&x::CreateNotifyEvent> for WindowDims {
fn from(value: &x::CreateNotifyEvent) -> Self {
Self {
x: value.x(),
y: value.y(),
width: value.width(),
height: value.height(),
}
}
}
type Request<T> = <T as Resource>::Request;
/// Converts a WEnum from its client side version to its server side version
fn convert_wenum<Client, Server>(wenum: WEnum<Client>) -> Server
where
u32: From<WEnum<Client>>,
Server: TryFrom<u32>,
<Server as TryFrom<u32>>::Error: std::fmt::Debug,
{
u32::from(wenum).try_into().unwrap()
}
#[derive(Default, Debug)]
pub struct WindowAttributes {
pub override_redirect: bool,
pub popup_for: Option<x::Window>,
pub dims: WindowDims,
pub size_hints: Option<WmNormalHints>,
pub title: Option<WmName>,
pub class: Option<String>,
pub group: Option<x::Window>,
}
#[derive(Debug)]
struct WindowData {
window: x::Window,
surface_id: u32,
surface_key: Option<ObjectKey>,
mapped: bool,
attrs: WindowAttributes,
}
impl WindowData {
fn new(
window: x::Window,
override_redirect: bool,
dims: WindowDims,
parent: Option<x::Window>,
) -> Self {
Self {
window,
surface_id: 0,
surface_key: None,
mapped: false,
attrs: WindowAttributes {
override_redirect,
dims,
popup_for: parent,
..Default::default()
},
}
}
}
struct SurfaceAttach {
buffer: Option<client::wl_buffer::WlBuffer>,
x: i32,
y: i32,
}
pub struct SurfaceData {
client: client::wl_surface::WlSurface,
server: WlSurface,
key: ObjectKey,
frame_callback: Option<WlCallback>,
attach: Option<SurfaceAttach>,
role: Option<SurfaceRole>,
}
impl SurfaceData {
fn xdg(&self) -> Option<&XdgSurfaceData> {
match self
.role
.as_ref()
.expect("Tried to get XdgSurface for surface without role")
{
SurfaceRole::Toplevel(ref t) => t.as_ref().map(|t| &t.xdg),
SurfaceRole::Popup(ref p) => p.as_ref().map(|p| &p.xdg),
}
}
fn xdg_mut(&mut self) -> Option<&mut XdgSurfaceData> {
match self
.role
.as_mut()
.expect("Tried to get XdgSurface for surface without role")
{
SurfaceRole::Toplevel(ref mut t) => t.as_mut().map(|t| &mut t.xdg),
SurfaceRole::Popup(ref mut p) => p.as_mut().map(|p| &mut p.xdg),
}
}
fn destroy_role(&mut self) {
if let Some(role) = self.role.take() {
match role {
SurfaceRole::Toplevel(Some(t)) => {
t.toplevel.destroy();
t.xdg.surface.destroy();
}
SurfaceRole::Popup(Some(p)) => {
p.positioner.destroy();
p.popup.destroy();
p.xdg.surface.destroy();
}
_ => {}
}
}
}
}
#[derive(Debug)]
enum SurfaceRole {
Toplevel(Option<ToplevelData>),
Popup(Option<PopupData>),
}
#[derive(Debug)]
struct XdgSurfaceData {
surface: XdgSurface,
configured: bool,
pending: Option<PendingSurfaceState>,
}
#[derive(Debug)]
struct ToplevelData {
toplevel: XdgToplevel,
xdg: XdgSurfaceData,
fullscreen: bool,
}
#[derive(Debug)]
struct PopupData {
popup: XdgPopup,
positioner: XdgPositioner,
xdg: XdgSurfaceData,
}
pub(crate) trait HandleEvent {
type Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>);
}
macro_rules! enum_try_from {
(
$(#[$meta:meta])*
$pub:vis enum $enum:ident {
$( $variant:ident($ty:ty) ),+
}
) => {
$(#[$meta])*
$pub enum $enum {
$( $variant($ty) ),+
}
$(
impl TryFrom<$enum> for $ty {
type Error = String;
fn try_from(value: $enum) -> Result<Self, Self::Error> {
enum_try_from!(@variant_match value $enum $variant)
}
}
impl<'a> TryFrom<&'a $enum> for &'a $ty {
type Error = String;
fn try_from(value: &'a $enum) -> Result<Self, Self::Error> {
enum_try_from!(@variant_match value $enum $variant)
}
}
impl<'a> TryFrom<&'a mut $enum> for &'a mut $ty {
type Error = String;
fn try_from(value: &'a mut $enum) -> Result<Self, Self::Error> {
enum_try_from!(@variant_match value $enum $variant)
}
}
impl From<$ty> for $enum {
fn from(value: $ty) -> Self {
$enum::$variant(value)
}
}
)+
};
(@variant_match $value:ident $enum:ident $variant:ident) => {
match $value {
$enum::$variant(obj) => Ok(obj),
other => Err(format!("wrong variant type: {}", std::any::type_name_of_val(&other)))
}
}
}
/// Implement HandleEvent for our enum
macro_rules! handle_event_enum {
(
$(#[$meta:meta])*
$pub:vis enum $name:ident {
$( $variant:ident($ty:ty) ),+
}
) => {
enum_try_from! {
$(#[$meta])*
$pub enum $name {
$( $variant($ty) ),+
}
}
paste::paste! {
enum_try_from! {
#[derive(Debug)]
$pub enum [<$name Event>] {
$( $variant(<$ty as HandleEvent>::Event) ),+
}
}
}
impl HandleEvent for $name {
paste::paste! {
type Event = [<$name Event>];
}
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
match self {
$(
Self::$variant(v) => {
let Self::Event::$variant(event) = event else {
unreachable!();
};
v.handle_event(event, state)
}
),+
}
}
}
}
}
handle_event_enum! {
/// Objects that generate client side events that we will have to process.
pub(crate) enum Object {
Surface(SurfaceData),
Buffer(Buffer),
Seat(Seat),
Pointer(Pointer),
Keyboard(Keyboard),
Output(Output),
RelativePointer(RelativePointer),
DmabufFeedback(DmabufFeedback),
Drm(Drm),
XdgOutput(XdgOutput),
Touch(Touch)
}
}
struct WrappedObject(Option<Object>);
impl<T> From<T> for WrappedObject
where
T: Into<Object>,
{
fn from(value: T) -> Self {
Self(Some(value.into()))
}
}
impl<T> AsRef<T> for WrappedObject
where
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
{
fn as_ref(&self) -> &T {
<&T>::try_from(self.0.as_ref().unwrap()).unwrap()
}
}
impl<T> AsMut<T> for WrappedObject
where
for<'a> &'a mut T: TryFrom<&'a mut Object, Error = String>,
{
fn as_mut(&mut self) -> &mut T {
<&mut T>::try_from(self.0.as_mut().unwrap()).unwrap()
}
}
type ObjectMap = HopSlotMap<ObjectKey, WrappedObject>;
trait ObjectMapExt {
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
where
F: FnOnce([&Object; N], ObjectKey) -> Object;
}
impl ObjectMapExt for ObjectMap {
/// Insert an object into our map that needs some other values from our map as well
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
where
F: FnOnce([&Object; N], ObjectKey) -> Object,
{
let objects = keys.each_ref().map(|key| self[*key].0.take().unwrap());
let key = self.insert(WrappedObject(None));
let obj = insert_fn(objects.each_ref(), key);
let ret = self[key].0.replace(obj);
debug_assert!(ret.is_none());
for (object, key) in objects.into_iter().zip(keys.into_iter()) {
let ret = self[key].0.replace(object);
debug_assert!(ret.is_none());
}
}
}
new_key_type! {
pub struct ObjectKey;
}
pub struct ServerState<C: XConnection> {
pub atoms: Option<Atoms>,
dh: DisplayHandle,
clientside: ClientState,
objects: ObjectMap,
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
windows: HashMap<x::Window, WindowData>,
xdg_wm_base: XdgWmBase,
qh: ClientQueueHandle,
to_focus: Option<x::Window>,
last_focused_toplevel: Option<x::Window>,
connection: Option<C>,
}
const XDG_WM_BASE_VERSION: u32 = 2;
impl<C: XConnection> ServerState<C> {
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
let mut clientside = ClientState::new(server_connection);
let qh = clientside.qh.clone();
let xdg_pos = clientside
.globals
.new_globals
.iter()
.position(|g| g.interface == XdgWmBase::interface().name)
.expect("Did not get an xdg_wm_base global");
let data = clientside.globals.new_globals.swap_remove(xdg_pos);
assert!(
data.version >= XDG_WM_BASE_VERSION,
"xdg_wm_base older than version {XDG_WM_BASE_VERSION}"
);
let xdg_wm_base =
clientside
.registry
.bind::<XdgWmBase, _, _>(data.name, XDG_WM_BASE_VERSION, &qh, ());
let mut ret = Self {
windows: HashMap::new(),
clientside,
atoms: None,
qh,
dh,
to_focus: None,
last_focused_toplevel: None,
connection: None,
objects: Default::default(),
associated_windows: Default::default(),
xdg_wm_base,
};
ret.handle_new_globals();
ret
}
pub fn clientside_fd(&self) -> BorrowedFd<'_> {
self.clientside.queue.as_fd()
}
pub fn connect(&mut self, connection: UnixStream) {
self.dh
.insert_client(connection, std::sync::Arc::new(()))
.unwrap();
}
pub fn set_x_connection(&mut self, connection: C) {
self.connection = Some(connection);
}
fn handle_new_globals(&mut self) {
let globals = std::mem::take(&mut self.clientside.globals.new_globals);
for data in globals {
macro_rules! server_global {
($($global:ty),+) => {
match data.interface {
$(
ref x if x == <$global>::interface().name => {
self.dh.create_global::<Self, $global, GlobalData>(data.version, data);
}
)+
_ => {}
}
}
}
server_global![
WlCompositor,
WlShm,
WlSeat,
WlOutput,
ZwpRelativePointerManagerV1,
WlDrmServer,
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
ZxdgOutputManagerV1,
s_vp::wp_viewporter::WpViewporter
];
}
}
fn get_object_from_client_object<T, P: Proxy>(&self, proxy: &P) -> &T
where
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
Globals: wayland_client::Dispatch<P, ObjectKey>,
{
let key: ObjectKey = proxy.data().copied().unwrap();
self.objects[key].as_ref()
}
pub fn new_window(
&mut self,
window: x::Window,
override_redirect: bool,
dims: WindowDims,
parent: Option<x::Window>,
) {
self.windows.insert(
window,
WindowData::new(window, override_redirect, dims, parent),
);
}
pub fn set_win_title(&mut self, window: x::Window, name: WmName) {
let win = self.windows.get_mut(&window).unwrap();
let new_title = match &mut win.attrs.title {
Some(w) => {
if matches!(w, WmName::NetWmName(_)) && matches!(name, WmName::WmName(_)) {
debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set");
None
} else {
*w = name;
Some(w)
}
}
None => Some(win.attrs.title.insert(name)),
};
let Some(title) = new_title else {
return;
};
if let Some(key) = win.surface_key {
let surface: &SurfaceData = self.objects[key].as_ref();
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
data.toplevel.set_title(title.name().to_string());
}
}
}
pub fn set_win_class(&mut self, window: x::Window, class: String) {
let win = self.windows.get_mut(&window).unwrap();
let class = win.attrs.class.insert(class);
if let Some(key) = win.surface_key {
let surface: &SurfaceData = self.objects[key].as_ref();
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
data.toplevel.set_app_id(class.to_string());
}
}
}
pub fn set_win_hints(&mut self, window: x::Window, hints: WmHints) {
let win = self.windows.get_mut(&window).unwrap();
win.attrs.group = hints.window_group;
}
pub fn set_size_hints(&mut self, window: x::Window, hints: WmNormalHints) {
let win = self.windows.get_mut(&window).unwrap();
if win.attrs.size_hints.is_none() || *win.attrs.size_hints.as_ref().unwrap() != hints {
debug!("setting {window:?} hints {hints:?}");
if let Some(surface) = win.surface_key {
let surface: &SurfaceData = self.objects[surface].as_ref();
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
if let Some(min_size) = &hints.min_size {
data.toplevel.set_min_size(min_size.width, min_size.height);
}
if let Some(max_size) = &hints.max_size {
data.toplevel.set_max_size(max_size.width, max_size.height);
}
}
}
win.attrs.size_hints = Some(hints);
}
}
pub fn associate_window(&mut self, window: x::Window, surface_id: u32) {
let win = self.windows.get_mut(&window).unwrap();
win.surface_id = surface_id;
if let Some(key) = self
.objects
.iter_mut()
.filter_map(|(key, obj)| {
Some(key).zip(<&mut SurfaceData>::try_from(obj.0.as_mut().unwrap()).ok())
})
.find_map(|(key, surface)| {
(surface_id == surface.server.id().protocol_id()).then_some(key)
})
{
win.surface_key = Some(key);
self.associated_windows.insert(key, window);
debug!("associate {:?} with surface {surface_id}", window);
if win.mapped {
self.create_role_window(window, key);
}
}
}
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
let win = self.windows.get_mut(&event.window()).unwrap();
win.attrs.dims = WindowDims {
x: event.x(),
y: event.y(),
width: event.width(),
height: event.height(),
};
}
pub fn map_window(&mut self, window: x::Window) {
debug!("mapping {window:?}");
let window = self.windows.get_mut(&window).unwrap();
window.mapped = true;
}
pub fn unmap_window(&mut self, window: x::Window) {
let Some(win) = self.windows.get_mut(&window) else {
return;
};
if !win.mapped {
return;
}
debug!("unmapping {window:?}");
if matches!(self.last_focused_toplevel, Some(x) if x == window) {
self.last_focused_toplevel.take();
}
win.mapped = false;
if let Some(key) = win.surface_key.take() {
let surface: &mut SurfaceData = self.objects[key].as_mut();
surface.destroy_role();
}
}
pub fn set_fullscreen(&mut self, window: x::Window, state: super::xstate::SetState) {
let win = self.windows.get(&window).unwrap();
let Some(key) = win.surface_key else {
warn!("Tried to set window without surface fullscreen: {window:?}");
return;
};
let surface: &mut SurfaceData = self.objects[key].as_mut();
let Some(SurfaceRole::Toplevel(Some(ref toplevel))) = surface.role else {
warn!("Tried to set an unmapped toplevel or non toplevel fullscreen: {window:?}");
return;
};
match state {
crate::xstate::SetState::Add => toplevel.toplevel.set_fullscreen(None),
crate::xstate::SetState::Remove => toplevel.toplevel.unset_fullscreen(),
crate::xstate::SetState::Toggle => {
if toplevel.fullscreen {
toplevel.toplevel.unset_fullscreen()
} else {
toplevel.toplevel.set_fullscreen(None)
}
}
}
}
pub fn destroy_window(&mut self, window: x::Window) {
let _ = self.windows.remove(&window);
}
pub fn run(&mut self) {
if let Some(r) = self.clientside.queue.prepare_read() {
let fd = r.connection_fd();
let pollfd = PollFd::new(&fd, PollFlags::IN);
if poll(&mut [pollfd], 0).unwrap() > 0 {
let _ = r.read();
}
}
self.clientside
.queue
.dispatch_pending(&mut self.clientside.globals)
.unwrap();
self.handle_clientside_events();
self.clientside.queue.flush().unwrap();
}
pub fn handle_clientside_events(&mut self) {
self.handle_new_globals();
let client_events = std::mem::take(&mut self.clientside.globals.events);
for (key, event) in client_events {
let object = &mut self.objects[key];
let mut object = object.0.take().unwrap();
object.handle_event(event, self);
let ret = self.objects[key].0.replace(object);
debug_assert!(ret.is_none());
}
{
if let Some(win) = self.to_focus.take() {
let data = C::ExtraData::create(self);
let conn = self.connection.as_mut().unwrap();
debug!("focusing window {win:?}");
conn.focus_window(win, data);
self.last_focused_toplevel = Some(win);
}
}
self.clientside.queue.flush().unwrap();
}
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
let surface: &SurfaceData = self.objects[surface_key].as_ref();
let client = &surface.client;
client.attach(None, 0, 0);
client.commit();
let xdg_surface = self
.xdg_wm_base
.get_xdg_surface(client, &self.qh, surface_key);
let window_data = self.windows.get_mut(&window).unwrap();
if window_data.attrs.override_redirect {
// Override redirect is hard to convert to Wayland!
// We will just make them be popups for the last focused toplevel.
if let Some(win) = self.last_focused_toplevel {
window_data.attrs.popup_for = Some(win)
}
}
let window = self.windows.get(&window).unwrap();
let role = if let Some(parent) = window.attrs.popup_for {
debug!(
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
window.window,
parent,
window.attrs.dims,
surface.client.id()
);
let parent_window = self.windows.get(&parent).unwrap();
let parent_surface: &SurfaceData =
self.objects[parent_window.surface_key.unwrap()].as_ref();
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _);
positioner.set_offset(window.attrs.dims.x as i32, window.attrs.dims.y as i32);
positioner.set_anchor(Anchor::TopLeft);
positioner.set_gravity(Gravity::BottomRight);
positioner.set_anchor_rect(
0,
0,
parent_window.attrs.dims.width as _,
parent_window.attrs.dims.height as _,
);
let popup = xdg_surface.get_popup(
Some(&parent_surface.xdg().unwrap().surface),
&positioner,
&self.qh,
surface_key,
);
let popup = PopupData {
popup,
positioner,
xdg: XdgSurfaceData {
surface: xdg_surface,
configured: false,
pending: None,
},
};
SurfaceRole::Popup(Some(popup))
} else {
let data = self.create_toplevel(window, surface_key, xdg_surface);
SurfaceRole::Toplevel(Some(data))
};
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
let new_role_type = std::mem::discriminant(&role);
let prev = surface.role.replace(role);
if let Some(role) = prev {
let old_role_type = std::mem::discriminant(&role);
assert_eq!(
new_role_type, old_role_type,
"Surface for {:?} already had a role: {:?}",
window.window, role
);
}
surface.client.commit();
}
fn create_toplevel(
&self,
window: &WindowData,
surface_key: ObjectKey,
xdg: XdgSurface,
) -> ToplevelData {
debug!("creating toplevel for {:?}", window.window);
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
if let Some(hints) = &window.attrs.size_hints {
if let Some(min) = &hints.min_size {
toplevel.set_min_size(min.width, min.height);
}
if let Some(max) = &hints.max_size {
toplevel.set_max_size(max.width, max.height);
}
}
let group = window.attrs.group.and_then(|win| self.windows.get(&win));
if let Some(class) = window
.attrs
.class
.as_ref()
.or(group.and_then(|g| g.attrs.class.as_ref()))
{
toplevel.set_app_id(class.to_string());
}
if let Some(title) = window
.attrs
.title
.as_ref()
.or(group.and_then(|g| g.attrs.title.as_ref()))
{
toplevel.set_title(title.name().to_string());
}
ToplevelData {
xdg: XdgSurfaceData {
surface: xdg,
configured: false,
pending: None,
},
toplevel,
fullscreen: false,
}
}
fn get_server_surface_from_client(&self, surface: client::wl_surface::WlSurface) -> &WlSurface {
let key: &ObjectKey = surface.data().unwrap();
let surface: &SurfaceData = self.objects[*key].as_ref();
&surface.server
}
fn get_client_surface_from_server(&self, surface: WlSurface) -> &client::wl_surface::WlSurface {
let key: &ObjectKey = surface.data().unwrap();
let surface: &SurfaceData = self.objects[*key].as_ref();
&surface.client
}
fn close_x_window(&mut self, window: x::Window) {
debug!("sending close request to {window:?}");
let data = C::ExtraData::create(self);
self.connection.as_mut().unwrap().close_window(window, data);
if self.last_focused_toplevel == Some(window) {
self.last_focused_toplevel.take();
}
}
}
#[derive(Default, Debug)]
pub struct PendingSurfaceState {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}

947
src/server/tests.rs Normal file
View file

@ -0,0 +1,947 @@
use super::{ServerState, WindowDims};
use crate::xstate::{SetState, WmName};
use paste::paste;
use rustix::event::{poll, PollFd, PollFlags};
use std::collections::HashMap;
use std::os::fd::BorrowedFd;
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use wayland_client::{
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
protocol::{
wl_buffer::WlBuffer,
wl_compositor::WlCompositor,
wl_display::WlDisplay,
wl_registry::WlRegistry,
wl_seat::WlSeat,
wl_shm::{Format, WlShm},
wl_shm_pool::WlShmPool,
wl_surface::WlSurface,
},
Connection, Proxy, WEnum,
};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
viewporter::client::wp_viewporter::WpViewporter,
},
xdg::{
shell::server::{xdg_positioner, xdg_toplevel},
xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1,
},
};
use wayland_server::{protocol as s_proto, Display, Resource};
use wl_drm::client::wl_drm::WlDrm;
use xcb::x::Window;
use xcb::XidNew;
macro_rules! with_optional {
(
$( #[$attr:meta] )?
struct $name:ident$(<$($lifetimes:lifetime),+>)? {
$(
$field:ident: $type:ty
),+$(,)?
}
) => {
$( #[$attr] )?
struct $name$(<$($lifetimes),+>)? {
$(
$field: $type
),+
}
paste! {
#[derive(Default)]
struct [< $name Optional >] {
$(
$field: Option<$type>
),+
}
}
paste! {
impl From<[<$name Optional>]> for $name {
fn from(opt: [<$name Optional>]) -> Self {
Self {
$(
$field: opt.$field.expect(concat!("uninitialized field ", stringify!($field)))
),+
}
}
}
}
}
}
with_optional! {
struct Compositor {
compositor: TestObject<WlCompositor>,
shm: TestObject<WlShm>,
}
}
impl Compositor {
fn create_surface(&self) -> (TestObject<WlBuffer>, TestObject<WlSurface>) {
let fd = unsafe { BorrowedFd::borrow_raw(0) };
let pool = TestObject::<WlShmPool>::from_request(
&self.shm.obj,
Req::<WlShm>::CreatePool { fd, size: 1024 },
);
let buffer = TestObject::<WlBuffer>::from_request(
&pool.obj,
Req::<WlShmPool>::CreateBuffer {
offset: 0,
width: 10,
height: 10,
stride: 1,
format: WEnum::Value(Format::Xrgb8888A8),
},
);
let surface = TestObject::<WlSurface>::from_request(
&self.compositor.obj,
Req::<WlCompositor>::CreateSurface {},
);
surface
.send_request(Req::<WlSurface>::Attach {
buffer: Some(buffer.obj.clone()),
x: 0,
y: 0,
})
.unwrap();
(buffer, surface)
}
}
#[derive(Debug, Default)]
struct WindowData {
mapped: bool,
fullscreen: bool,
dims: WindowDims,
}
struct FakeXConnection {
root: Window,
focused_window: Option<Window>,
windows: HashMap<Window, WindowData>,
}
impl FakeXConnection {
#[track_caller]
fn window(&mut self, window: Window) -> &mut WindowData {
self.windows
.get_mut(&window)
.expect(&format!("Unknown window: {window:?}"))
}
}
impl Default for FakeXConnection {
fn default() -> Self {
Self {
root: unsafe { Window::new(9001) },
focused_window: None,
windows: HashMap::new(),
}
}
}
impl super::FromServerState<FakeXConnection> for () {
fn create(_: &FakeServerState) -> Self {
()
}
}
impl super::XConnection for FakeXConnection {
type ExtraData = ();
fn root_window(&self) -> Window {
self.root
}
#[track_caller]
fn close_window(&mut self, window: Window, _: ()) {
log::debug!("closing window {window:?}");
self.window(window).mapped = false;
}
#[track_caller]
fn set_fullscreen(&mut self, window: xcb::x::Window, fullscreen: bool, _: ()) {
self.window(window).fullscreen = fullscreen;
}
#[track_caller]
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
self.window(window).dims = WindowDims {
x: state.x as _,
y: state.y as _,
width: state.width as _,
height: state.height as _,
};
}
#[track_caller]
fn focus_window(&mut self, window: Window, _: ()) {
assert!(
self.windows.contains_key(&window),
"Unknown window: {window:?}"
);
self.focused_window = window.into();
}
}
type FakeServerState = ServerState<FakeXConnection>;
struct TestFixture {
testwl: testwl::Server,
exwayland: FakeServerState,
/// Our connection to exwayland - i.e., where Xwayland sends requests to
exwl_connection: Arc<Connection>,
/// Exwayland's display - must dispatch this for our server state to advance
exwl_display: Display<FakeServerState>,
}
static INIT: std::sync::Once = std::sync::Once::new();
impl TestFixture {
fn new() -> Self {
INIT.call_once(|| {
env_logger::builder()
.is_test(true)
.filter_level(log::LevelFilter::Trace)
.init()
});
let (client_s, server_s) = UnixStream::pair().unwrap();
let mut testwl = testwl::Server::new(true);
let display = Display::<FakeServerState>::new().unwrap();
testwl.connect(server_s);
// Handle initial globals roundtrip setup requirement
let thread = std::thread::spawn(move || {
let mut pollfd = [PollFd::from_borrowed_fd(testwl.poll_fd(), PollFlags::IN)];
if poll(&mut pollfd, 1000).unwrap() == 0 {
panic!("Did not get events for testwl!");
}
testwl.dispatch();
testwl
});
let mut exwayland = FakeServerState::new(display.handle(), Some(client_s));
let testwl = thread.join().unwrap();
let (fake_client, ex_server) = UnixStream::pair().unwrap();
exwayland.connect(ex_server);
exwayland.set_x_connection(FakeXConnection::default());
let mut f = TestFixture {
testwl,
exwayland,
exwl_connection: Connection::from_socket(fake_client).unwrap().into(),
exwl_display: display,
};
f.run();
f
}
fn new_with_compositor() -> (Self, Compositor) {
let mut f = Self::new();
let compositor = f.compositor();
(f, compositor)
}
fn connection(&self) -> &FakeXConnection {
self.exwayland.connection.as_ref().unwrap()
}
fn compositor(&mut self) -> Compositor {
let mut ret = CompositorOptional::default();
let wl_display = self.exwl_connection.display();
let registry =
TestObject::<WlRegistry>::from_request(&wl_display, Req::<WlDisplay>::GetRegistry {});
self.run();
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
assert!(events.len() > 0);
let bind_req = |name, interface, version| Req::<WlRegistry>::Bind {
name,
id: (interface, version),
};
for event in events {
if let Ev::<WlRegistry>::Global {
name,
interface,
version,
} = event
{
match interface {
x if x == WlCompositor::interface().name => {
ret.compositor = Some(TestObject::from_request(
&registry.obj,
bind_req(name, WlCompositor::interface(), version),
));
}
x if x == WlShm::interface().name => {
ret.shm = Some(TestObject::from_request(
&registry.obj,
bind_req(name, WlShm::interface(), version),
));
}
_ => {}
}
}
}
ret.into()
}
/// Cascade our requests/events through exwayland and testwl
fn run(&mut self) {
// Flush our requests to exwayland
self.exwl_connection.flush().unwrap();
// Have exwayland dispatch our requests
self.exwl_display
.dispatch_clients(&mut self.exwayland)
.unwrap();
self.exwl_display.flush_clients().unwrap();
// Dispatch any clientside requests
self.exwayland.run();
// Have testwl dispatch the clientside requests
self.testwl.dispatch();
// Handle clientside events
self.exwayland.handle_clientside_events();
self.testwl.dispatch();
// Get our events
let res = self.exwl_connection.prepare_read().unwrap().read();
if res.is_err()
&& !matches!(res, Err(WaylandError::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock)
{
panic!("Read failed: {res:?}")
}
}
fn register_window(&mut self, window: Window, data: WindowData) {
self.exwayland
.connection
.as_mut()
.unwrap()
.windows
.insert(window, data);
}
fn create_and_map_window(
&mut self,
comp: &Compositor,
window: Window,
override_redirect: bool,
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
let (_buffer, surface) = comp.create_surface();
let data = WindowData {
mapped: true,
dims: WindowDims {
x: 0,
y: 0,
width: 50,
height: 50,
},
fullscreen: false,
};
let dims = data.dims;
self.register_window(window, data);
self.exwayland
.new_window(window, override_redirect, dims, None);
self.exwayland.map_window(window);
self.exwayland
.associate_window(window, surface.id().protocol_id());
self.run();
let testwl_id = self
.testwl
.last_created_surface_id()
.expect("Surface not created");
assert!(self.testwl.get_surface_data(testwl_id).is_some());
(surface, testwl_id)
}
fn create_toplevel(
&mut self,
comp: &Compositor,
window: Window,
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
let (_buffer, surface) = comp.create_surface();
let data = WindowData {
mapped: true,
dims: WindowDims {
x: 0,
y: 0,
width: 50,
height: 50,
},
fullscreen: false,
};
let dims = data.dims;
self.register_window(window, data);
self.exwayland.new_window(window, false, dims, None);
self.exwayland.map_window(window);
self.exwayland
.associate_window(window, surface.id().protocol_id());
self.run();
let testwl_id = self
.testwl
.last_created_surface_id()
.expect("Toplevel surface not created");
{
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
assert!(
surface_data.surface
== self
.testwl
.get_object::<s_proto::wl_surface::WlSurface>(testwl_id)
.unwrap()
);
assert!(surface_data.buffer.is_none());
assert!(
matches!(surface_data.role, Some(testwl::SurfaceRole::Toplevel(_))),
"surface role: {:?}",
surface_data.role
);
}
self.testwl
.configure_toplevel(testwl_id, 100, 100, vec![xdg_toplevel::State::Activated]);
self.run();
{
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
assert!(surface_data.buffer.is_some());
}
let win_data = self.connection().windows.get(&window).map(|d| &d.dims);
assert!(
matches!(
win_data,
Some(&super::WindowDims {
x: 0,
y: 0,
width: 100,
height: 100
})
),
"Incorrect window geometry: {win_data:?}"
);
(surface, testwl_id)
}
fn create_popup(
&mut self,
comp: &Compositor,
window: Window,
parent_id: testwl::SurfaceId,
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
let (_, popup_surface) = comp.create_surface();
let data = WindowData {
mapped: true,
dims: WindowDims {
x: 10,
y: 10,
width: 50,
height: 50,
},
fullscreen: false,
};
let dims = data.dims;
self.register_window(window, data);
self.exwayland.new_window(window, true, dims, None);
self.exwayland.map_window(window);
self.exwayland
.associate_window(window, popup_surface.id().protocol_id());
self.run();
let popup_id = self.testwl.last_created_surface_id().unwrap();
assert_ne!(popup_id, parent_id);
{
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
assert!(
surface_data.surface
== self
.testwl
.get_object::<s_proto::wl_surface::WlSurface>(popup_id)
.unwrap()
);
assert!(surface_data.buffer.is_none());
assert!(
matches!(surface_data.role, Some(testwl::SurfaceRole::Popup(_))),
"surface was not a popup (role: {:?})",
surface_data.role
);
let toplevel_xdg = &self
.testwl
.get_surface_data(parent_id)
.unwrap()
.xdg()
.surface;
assert_eq!(&surface_data.popup().parent, toplevel_xdg);
let pos = &surface_data.popup().positioner_state;
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
assert_eq!(
pos.anchor_rect.as_ref().unwrap(),
&testwl::Rect {
size: testwl::Vec2 { x: 100, y: 100 },
offset: testwl::Vec2::default()
}
);
assert_eq!(
pos.offset,
testwl::Vec2 {
x: dims.x as _,
y: dims.y as _
}
);
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
assert_eq!(pos.gravity, xdg_positioner::Gravity::BottomRight);
}
self.testwl.configure_popup(popup_id);
self.run();
{
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
assert!(surface_data.buffer.is_some());
}
(popup_surface, popup_id)
}
}
struct TestObjectData<T: Proxy> {
events: Mutex<Vec<T::Event>>,
_phantom: std::marker::PhantomData<T>,
}
impl<T: Proxy> Default for TestObjectData<T> {
fn default() -> Self {
Self {
events: Default::default(),
_phantom: Default::default(),
}
}
}
impl<T: Proxy + Send + Sync + 'static> ObjectData for TestObjectData<T>
where
T::Event: Send + Sync + std::fmt::Debug,
{
fn event(
self: Arc<Self>,
backend: &Backend,
msg: Message<ObjectId, std::os::fd::OwnedFd>,
) -> Option<Arc<dyn ObjectData>> {
let connection = Connection::from_backend(backend.clone());
let event = T::parse_event(&connection, msg).unwrap().1;
self.events.lock().unwrap().push(event);
None
}
fn destroyed(&self, _: ObjectId) {}
}
struct TestObject<T: Proxy> {
obj: T,
data: Arc<TestObjectData<T>>,
}
impl<T: Proxy> std::ops::Deref for TestObject<T> {
type Target = T;
fn deref(&self) -> &T {
&self.obj
}
}
impl<T: Proxy + Sync + Send + 'static> TestObject<T>
where
T::Event: Sync + Send + std::fmt::Debug,
{
fn from_request<P: Proxy>(object: &P, request: P::Request<'_>) -> Self {
let data = Arc::<TestObjectData<T>>::default();
let obj: T = P::send_constructor(object, request, data.clone()).unwrap();
Self { obj, data }
}
}
type Req<'a, T> = <T as Proxy>::Request<'a>;
type Ev<T> = <T as Proxy>::Event;
// TODO: tests to add
// - destroy window before surface
// - destroy surface before window
// - destroy popup and reassociate with new surface
// - reconfigure window (popup) before mapping
// - associate window after surface is already created
// Matches Xwayland flow.
#[test]
fn toplevel_flow() {
let (mut f, compositor) = TestFixture::new_with_compositor();
let window = unsafe { Window::new(1) };
let (surface, testwl_id) = f.create_toplevel(&compositor, window);
{
let surface_data = f.testwl.get_surface_data(testwl_id).unwrap();
assert!(surface_data.buffer.is_some());
}
f.testwl.close_toplevel(testwl_id);
f.run();
assert!(!f.exwayland.connection.as_ref().unwrap().windows[&window].mapped);
assert!(
f.testwl.get_surface_data(testwl_id).is_some(),
"Surface should still exist for closed toplevel"
);
assert!(surface.obj.is_alive());
// For some reason, we can get two UnmapNotify events
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4
f.exwayland.unmap_window(window);
f.exwayland.unmap_window(window);
f.exwayland.destroy_window(window);
surface.obj.destroy();
f.run();
assert!(f.testwl.get_surface_data(testwl_id).is_none());
}
#[test]
fn popup_flow_simple() {
let (mut f, compositor) = TestFixture::new_with_compositor();
let win_toplevel = unsafe { Window::new(1) };
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
let win_popup = unsafe { Window::new(2) };
let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, toplevel_id);
f.exwayland.unmap_window(win_popup);
f.exwayland.destroy_window(win_popup);
popup_surface.obj.destroy();
f.run();
assert!(f.testwl.get_surface_data(popup_id).is_none());
}
#[test]
fn pass_through_globals() {
use wayland_client::protocol::wl_output::WlOutput;
let mut f = TestFixture::new();
const fn check<T: Proxy>() {}
macro_rules! globals_struct {
($($field:ident),+) => {
$( check::<$field>(); )+
#[derive(Default)]
#[allow(non_snake_case)]
struct SupportedGlobals {
$( $field: bool ),+
}
impl SupportedGlobals {
fn check_globals(&self) {
$( assert!(self.$field, "Missing global {}", stringify!($field)); )+
}
fn global_found(&mut self, interface: String) {
match interface {
$(
x if x == $field::interface().name => {
self.$field = true;
}
)+
_ => panic!("Found an unhandled global: {interface}")
}
}
}
}
}
// New globals need to be added here and in testwl.
globals_struct! {
WlCompositor,
WlShm,
WlOutput,
WlSeat,
ZwpLinuxDmabufV1,
ZwpRelativePointerManagerV1,
ZxdgOutputManagerV1,
WpViewporter,
WlDrm
}
let mut globals = SupportedGlobals::default();
let display = f.exwl_connection.display();
let registry =
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
f.run();
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
assert!(events.len() > 0);
for event in events {
let Ev::<WlRegistry>::Global { interface, .. } = event else {
unreachable!();
};
globals.global_found(interface);
}
globals.check_globals();
}
#[test]
fn last_activated_toplevel_is_focused() {
let (mut f, comp) = TestFixture::new_with_compositor();
let win1 = unsafe { Window::new(1) };
let (_surface1, id1) = f.create_toplevel(&comp, win1);
assert_eq!(
f.connection().focused_window,
Some(win1),
"new toplevel's window is not focused"
);
let win2 = unsafe { Window::new(2) };
let _data2 = f.create_toplevel(&comp, win2);
assert_eq!(
f.connection().focused_window,
Some(win2),
"toplevel focus did not switch"
);
f.testwl.configure_toplevel(id1, 100, 100, vec![]);
f.run();
assert_eq!(
f.connection().focused_window,
Some(win2),
"toplevel focus did not stay the same"
);
}
#[test]
fn popup_window_changes_surface() {
let (mut f, comp) = TestFixture::new_with_compositor();
let t_win = unsafe { Window::new(1) };
let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
let win = unsafe { Window::new(2) };
let (surface, old_id) = f.create_popup(&comp, win, toplevel_id);
f.exwayland.unmap_window(win);
surface.obj.destroy();
f.run();
assert!(f.testwl.get_surface_data(old_id).is_none());
let (_, surface) = comp.create_surface();
f.run();
let id = f
.testwl
.last_created_surface_id()
.expect("No surface created");
assert_ne!(old_id, id);
assert!(f.testwl.get_surface_data(id).is_some());
f.exwayland.map_window(win);
f.exwayland
.associate_window(win, surface.id().protocol_id());
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(data.popup().popup.is_alive());
f.testwl.configure_popup(id);
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(data.popup().popup.is_alive());
}
#[test]
fn override_redirect_window_after_toplevel_close() {
let (mut f, comp) = TestFixture::new_with_compositor();
let win1 = unsafe { Window::new(1) };
let (obj, first) = f.create_toplevel(&comp, win1);
f.testwl.close_toplevel(first);
f.run();
f.exwayland.unmap_window(win1);
f.exwayland.destroy_window(win1);
obj.obj.destroy();
f.run();
assert!(f.testwl.get_surface_data(first).is_none());
let win2 = unsafe { Window::new(2) };
let (_, second) = f.create_and_map_window(&comp, win2, true);
let data = f.testwl.get_surface_data(second).unwrap();
assert!(
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
"wrong role: {:?}",
data.role
)
}
#[test]
fn fullscreen() {
let (mut f, comp) = TestFixture::new_with_compositor();
let win = unsafe { Window::new(1) };
let (_, id) = f.create_toplevel(&comp, win);
f.exwayland.set_fullscreen(win, SetState::Add);
f.run();
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(data
.toplevel()
.states
.contains(&xdg_toplevel::State::Fullscreen));
f.exwayland.set_fullscreen(win, SetState::Remove);
f.run();
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(!data
.toplevel()
.states
.contains(&xdg_toplevel::State::Fullscreen));
f.exwayland.set_fullscreen(win, SetState::Toggle);
f.run();
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(data
.toplevel()
.states
.contains(&xdg_toplevel::State::Fullscreen));
f.exwayland.set_fullscreen(win, SetState::Toggle);
f.run();
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert!(!data
.toplevel()
.states
.contains(&xdg_toplevel::State::Fullscreen));
}
#[test]
fn window_title_and_class() {
let (mut f, comp) = TestFixture::new_with_compositor();
let win = unsafe { Window::new(1) };
let (_, id) = f.create_toplevel(&comp, win);
f.exwayland
.set_win_title(win, WmName::WmName("window".into()));
f.exwayland.set_win_class(win, "class".into());
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert_eq!(data.toplevel().title, Some("window".into()));
assert_eq!(data.toplevel().app_id, Some("class".into()));
f.exwayland
.set_win_title(win, WmName::NetWmName("superwindow".into()));
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert_eq!(data.toplevel().title, Some("superwindow".into()));
f.exwayland
.set_win_title(win, WmName::WmName("shwindow".into()));
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
assert_eq!(data.toplevel().title, Some("superwindow".into()));
}
#[test]
fn window_group_properties() {
let (mut f, comp) = TestFixture::new_with_compositor();
let prop_win = unsafe { Window::new(1) };
f.exwayland.new_window(
prop_win,
false,
super::WindowDims {
width: 1,
height: 1,
..Default::default()
},
None,
);
f.exwayland
.set_win_title(prop_win, WmName::WmName("window".into()));
f.exwayland.set_win_class(prop_win, "class".into());
let win = unsafe { Window::new(2) };
let data = WindowData {
mapped: true,
dims: WindowDims {
width: 50,
height: 50,
..Default::default()
},
fullscreen: false,
};
let (_, surface) = comp.create_surface();
let dims = data.dims;
f.register_window(win, data);
f.exwayland.new_window(win, false, dims, None);
f.exwayland.set_win_hints(
win,
super::WmHints {
window_group: Some(prop_win),
..Default::default()
},
);
f.exwayland.map_window(win);
f.exwayland
.associate_window(win, surface.id().protocol_id());
f.run();
let id = f.testwl.last_created_surface_id().unwrap();
let data = f.testwl.get_surface_data(id).unwrap();
assert_eq!(data.toplevel().title, Some("window".into()));
assert_eq!(data.toplevel().app_id, Some("class".into()));
}
/// See Pointer::handle_event for an explanation.
#[test]
fn popup_pointer_motion_workaround() {}

775
src/xstate.rs Normal file
View file

@ -0,0 +1,775 @@
use crate::server::WindowAttributes;
use bitflags::bitflags;
use log::{debug, trace, warn};
use std::ffi::CString;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::sync::Arc;
use xcb::{x, Xid, XidNew};
use xcb_util_cursor::{Cursor, CursorContext};
pub struct XState {
pub connection: Arc<xcb::Connection>,
root: x::Window,
pub atoms: Atoms,
}
/// Essentially a trait alias.
trait PropertyResolver {
type Output;
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output;
}
impl<T, Output> PropertyResolver for T
where
T: FnOnce(x::GetPropertyReply) -> Output,
{
type Output = Output;
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output {
(self)(reply)
}
}
struct PropertyCookieWrapper<'a, F: PropertyResolver> {
connection: &'a xcb::Connection,
cookie: x::GetPropertyCookie,
resolver: F,
}
impl<F: PropertyResolver> PropertyCookieWrapper<'_, F> {
/// Get the result from our property cookie.
fn resolve(self) -> Option<F::Output> {
let reply = self.connection.wait_for_reply(self.cookie).unwrap();
if reply.r#type() == x::ATOM_NONE {
None
} else {
Some(self.resolver.resolve(reply))
}
}
}
#[derive(Debug)]
pub enum WmName {
WmName(String),
NetWmName(String),
}
impl WmName {
pub fn name(&self) -> &str {
match self {
Self::WmName(n) => n,
Self::NetWmName(n) => n,
}
}
}
impl XState {
pub fn new(fd: BorrowedFd) -> Self {
let connection = Arc::new(xcb::Connection::connect_to_fd(fd.as_raw_fd(), None).unwrap());
let setup = connection.get_setup();
let screen = setup.roots().next().unwrap();
let root = screen.root();
connection
.send_and_check_request(&x::ChangeWindowAttributes {
window: root,
value_list: &[x::Cw::EventMask(
x::EventMask::SUBSTRUCTURE_REDIRECT // To have Xwayland send us WL_SURFACE_ID
| x::EventMask::SUBSTRUCTURE_NOTIFY // To get notified whenever new windows are created
| x::EventMask::RESIZE_REDIRECT,
)],
})
.unwrap();
let atoms = Atoms::intern_all(&connection).unwrap();
trace!("atoms: {atoms:#?}");
// This makes Xwayland spit out damage tracking
connection
.send_and_check_request(&xcb::composite::RedirectSubwindows {
window: screen.root(),
update: xcb::composite::Redirect::Manual,
})
.unwrap();
// Setup default cursor theme
let ctx = CursorContext::new(&connection, screen).unwrap();
let left_ptr = ctx.load_cursor(Cursor::LeftPtr);
connection
.send_and_check_request(&x::ChangeWindowAttributes {
window: root,
value_list: &[x::Cw::Cursor(left_ptr)],
})
.unwrap();
let mut r = Self {
connection,
root,
atoms,
};
r.create_ewmh_window();
r
}
fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) {
self.connection
.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window: self.root,
property,
r#type,
data,
})
.unwrap();
}
fn create_ewmh_window(&mut self) {
let window = self.connection.generate_id();
self.connection
.send_and_check_request(&x::CreateWindow {
depth: 0,
wid: window,
parent: self.root,
x: 0,
y: 0,
width: 1,
height: 1,
border_width: 0,
class: x::WindowClass::InputOnly,
visual: x::COPY_FROM_PARENT,
value_list: &[],
})
.unwrap();
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
self.set_root_property(self.atoms.supported, x::ATOM_ATOM, &[self.atoms.active_win]);
self.connection
.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window,
property: self.atoms.wm_check,
r#type: x::ATOM_WINDOW,
data: &[window],
})
.unwrap();
self.connection
.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window,
property: self.atoms.net_wm_name,
r#type: x::ATOM_STRING,
data: b"exwayland wm",
})
.unwrap();
}
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
while let Some(event) = self.connection.poll_for_event().unwrap() {
trace!("x11 event: {event:?}");
match event {
xcb::Event::X(x::Event::CreateNotify(e)) => {
debug!("new window: {:?}", e);
let parent = e.parent();
let parent = if parent.is_none() || parent == self.root {
None
} else {
Some(parent)
};
server_state.new_window(e.window(), e.override_redirect(), (&e).into(), parent);
}
xcb::Event::X(x::Event::ReparentNotify(e)) => {
debug!("reparent event: {e:?}");
if e.parent() == self.root {
let attrs = self.get_window_attributes(e.window());
server_state.new_window(
e.window(),
attrs.override_redirect,
attrs.dims,
None,
);
self.handle_window_attributes(server_state, e.window(), attrs);
} else {
debug!("destroying window since its parent is no longer root!");
server_state.destroy_window(e.window());
}
}
xcb::Event::X(x::Event::MapRequest(e)) => {
debug!("requested to map {:?}", e.window());
self.connection
.send_and_check_request(&x::MapWindow { window: e.window() })
.unwrap();
}
xcb::Event::X(x::Event::MapNotify(e)) => {
let attrs = self.get_window_attributes(e.window());
self.handle_window_attributes(server_state, e.window(), attrs);
server_state.map_window(e.window());
self.connection
.send_and_check_request(&x::ChangeWindowAttributes {
window: e.window(),
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
})
.unwrap();
}
xcb::Event::X(x::Event::ConfigureNotify(e)) => {
server_state.reconfigure_window(e);
}
xcb::Event::X(x::Event::UnmapNotify(e)) => {
trace!("unmap event: {:?}", e.event());
server_state.unmap_window(e.window());
match self
.connection
.send_and_check_request(&x::ChangeWindowAttributes {
window: e.window(),
value_list: &[x::Cw::EventMask(x::EventMask::empty())],
}) {
// Window error may occur if the window has been destroyed,
// which is fine
Ok(_) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
Err(other) => panic!("Error removing event mask from window: {other:?}"),
}
}
xcb::Event::X(x::Event::DestroyNotify(e)) => {
debug!("destroying window {:?}", e.window());
server_state.destroy_window(e.window());
}
xcb::Event::X(x::Event::PropertyNotify(e)) => {
self.handle_property_change(e, server_state);
}
xcb::Event::X(x::Event::ConfigureRequest(e)) => {
debug!("{:?} request: {:?}", e.window(), e.value_mask());
let mut list = Vec::new();
let mask = e.value_mask();
if mask.contains(x::ConfigWindowMask::X) {
list.push(x::ConfigWindow::X(e.x().into()));
}
if mask.contains(x::ConfigWindowMask::Y) {
list.push(x::ConfigWindow::Y(e.y().into()));
}
if mask.contains(x::ConfigWindowMask::WIDTH) {
list.push(x::ConfigWindow::Width(e.width().into()));
}
if mask.contains(x::ConfigWindowMask::HEIGHT) {
list.push(x::ConfigWindow::Height(e.height().into()));
}
self.connection
.send_and_check_request(&x::ConfigureWindow {
window: e.window(),
value_list: &list,
})
.unwrap();
}
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
x if x == self.atoms.wl_surface_id => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
let id: u32 = (data[0] as u64 | ((data[1] as u64) << 32)) as u32;
server_state.associate_window(e.window(), id);
}
x if x == self.atoms.net_wm_state => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
let Ok(action) = SetState::try_from(data[0]) else {
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
continue;
};
let prop1 = unsafe { x::Atom::new(data[1]) };
let prop2 = unsafe { x::Atom::new(data[2]) };
trace!("_NET_WM_STATE ({action:?}) props: {prop1:?} {prop2:?}");
for prop in [prop1, prop2] {
match prop {
x if x == self.atoms.wm_fullscreen => {
server_state.set_fullscreen(e.window(), action);
}
_ => {}
}
}
}
t => warn!("unrecognized message: {t:?}"),
},
xcb::Event::X(x::Event::MappingNotify(_)) => {}
other => {
warn!("unhandled event: {other:?}");
}
}
server_state.run();
}
}
fn get_window_attributes(&self, window: x::Window) -> WindowAttributes {
let geometry = self.connection.send_request(&x::GetGeometry {
drawable: x::Drawable::Window(window),
});
let attrs = self
.connection
.send_request(&x::GetWindowAttributes { window });
let name = self.get_net_wm_name(window);
let class = self.get_wm_class(window);
let wm_hints = self.get_wm_hints(window);
let size_hints = self.get_wm_size_hints(window);
let geometry = self.connection.wait_for_reply(geometry).unwrap();
let attrs = self.connection.wait_for_reply(attrs).unwrap();
let title = name
.resolve()
.or_else(|| self.get_wm_name(window).resolve());
let class = class.resolve();
let wm_hints = wm_hints.resolve();
let size_hints = size_hints.resolve();
WindowAttributes {
override_redirect: attrs.override_redirect(),
popup_for: None,
dims: WindowDims {
x: geometry.x(),
y: geometry.y(),
width: geometry.width(),
height: geometry.height(),
},
title,
class,
group: wm_hints.map(|h| h.window_group).flatten(),
size_hints,
}
}
fn handle_window_attributes(
&self,
server_state: &mut super::RealServerState,
window: x::Window,
attrs: WindowAttributes,
) {
if let Some(name) = attrs.title {
debug!("setting {window:?} title to {name:?}");
server_state.set_win_title(window, name);
}
if let Some(class) = attrs.class {
debug!("setting {window:?} class to {class}");
server_state.set_win_class(window, class);
}
if let Some(hints) = attrs.size_hints {
debug!("{window:?} size hints: {hints:?}");
server_state.set_size_hints(window, hints);
}
}
fn get_property_cookie(
&self,
window: x::Window,
property: x::Atom,
ty: x::Atom,
long_length: u32,
) -> x::GetPropertyCookie {
self.connection.send_request(&x::GetProperty {
delete: false,
window,
property,
r#type: ty,
long_offset: 0,
long_length,
})
}
fn get_wm_class(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = String>> {
let cookie = self.get_property_cookie(window, x::ATOM_WM_CLASS, x::ATOM_STRING, 256);
let resolver = move |reply: x::GetPropertyReply| {
let data: &[u8] = reply.value();
// wm class is instance + class - ignore instance
let class_start = data.iter().copied().position(|b| b == 0u8).unwrap() + 1;
let data = data[class_start..].to_vec();
let class = CString::from_vec_with_nul(data).unwrap();
debug!("{:?} class: {class:?}", window);
class.to_string_lossy().to_string()
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn get_wm_name(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
let cookie = self.get_property_cookie(window, x::ATOM_WM_NAME, x::ATOM_STRING, 256);
let resolver = |reply: x::GetPropertyReply| {
let data: &[u8] = reply.value();
WmName::WmName(String::from_utf8(data.to_vec()).unwrap())
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn get_net_wm_name(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
let cookie =
self.get_property_cookie(window, self.atoms.net_wm_name, self.atoms.utf8_string, 256);
let resolver = |reply: x::GetPropertyReply| {
let data: &[u8] = reply.value();
WmName::NetWmName(String::from_utf8(data.to_vec()).unwrap())
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn get_wm_hints(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmHints>> {
let cookie = self.get_property_cookie(window, x::ATOM_WM_HINTS, x::ATOM_WM_HINTS, 9);
let resolver = |reply: x::GetPropertyReply| {
let data: &[u32] = reply.value();
let hints = WmHints::from(data);
debug!("wm hints: {hints:?}");
hints
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn get_wm_size_hints(
&self,
window: x::Window,
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmNormalHints>> {
let cookie =
self.get_property_cookie(window, x::ATOM_WM_NORMAL_HINTS, x::ATOM_WM_SIZE_HINTS, 9);
let resolver = |reply: x::GetPropertyReply| {
let data: &[u32] = reply.value();
WmNormalHints::from(data)
};
PropertyCookieWrapper {
connection: &self.connection,
cookie,
resolver,
}
}
fn handle_property_change(
&self,
event: x::PropertyNotifyEvent,
server_state: &mut super::RealServerState,
) {
if event.state() != x::Property::NewValue {
println!("ignoring non newvalue for property {:?}", event.atom());
return;
}
let window = event.window();
let get_prop = |r#type, long_length| {
self.connection
.wait_for_reply(self.connection.send_request(&x::GetProperty {
window,
property: event.atom(),
r#type,
long_offset: 0,
long_length,
delete: false,
}))
};
match event.atom() {
x if x == x::ATOM_WM_HINTS => {
let hints = self.get_wm_hints(window).resolve().unwrap();
server_state.set_win_hints(window, hints);
}
x if x == x::ATOM_WM_NORMAL_HINTS => {
let hints = self.get_wm_size_hints(window).resolve().unwrap();
server_state.set_size_hints(window, hints);
}
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => {
let ty = if x == x::ATOM_WM_NAME {
x::ATOM_STRING
} else {
self.atoms.utf8_string
};
let prop = get_prop(ty, 256).unwrap();
let data: &[u8] = prop.value();
let name = String::from_utf8(data.to_vec()).unwrap();
debug!("{:?} named: {name}", window);
let name = if x == x::ATOM_WM_NAME {
WmName::WmName(name)
} else {
WmName::NetWmName(name)
};
server_state.set_win_title(window, name);
}
x if x == x::ATOM_WM_CLASS => {
let class = self.get_wm_class(window).resolve().unwrap();
server_state.set_win_class(window, class);
}
_ => {
if log::log_enabled!(log::Level::Debug) {
let prop = self
.connection
.wait_for_reply(
self.connection
.send_request(&x::GetAtomName { atom: event.atom() }),
)
.unwrap();
debug!("changed property {:?} for {:?}", prop.name(), window);
}
}
}
}
}
xcb::atoms_struct! {
#[derive(Clone, Debug)]
pub struct Atoms {
pub wl_surface_id => b"WL_SURFACE_ID" only_if_exists = false,
pub wm_protocols => b"WM_PROTOCOLS" only_if_exists = false,
pub wm_delete_window => b"WM_DELETE_WINDOW" only_if_exists = false,
pub wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
pub wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
pub net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
pub wm_pid => b"_NET_WM_PID" only_if_exists = false,
pub net_wm_state => b"_NET_WM_STATE" only_if_exists = false,
pub wm_fullscreen => b"_NET_WM_STATE_FULLSCREEN" only_if_exists = false,
pub active_win => b"_NET_ACTIVE_WINDOW" only_if_exists = false,
pub client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
pub supported => b"_NET_SUPPORTED" only_if_exists = false,
pub utf8_string => b"UTF8_STRING" only_if_exists = false,
}
}
xcb::atoms_struct! {
pub struct WindowTypes {
pub normal => b"_NET_WM_WINDOW_TYPE_NORMAL" only_if_exists = false,
pub dialog => b"_NET_WM_WINDOW_TYPE_DIALOG" only_if_exists = false,
pub splash => b"_NET_WM_WINDOW_TYPE_SPLASH" only_if_exists = false,
pub menu => b"_NET_WM_WINDOW_TYPE_MENU" only_if_exists = false,
pub utility => b"_NET_WM_WINDOW_TYPE_UTILITY" only_if_exists = false,
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct WindowDims {
pub x: i16,
pub y: i16,
pub width: u16,
pub height: u16,
}
bitflags! {
/// From ICCCM spec.
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
pub struct WmSizeHintsFlags: u32 {
const ProgramMinSize = 16;
const ProgramMaxSize = 32;
}
}
bitflags! {
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.4
pub struct WmHintsFlags: u32 {
const Input = 1;
const WindowGroup = 64;
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct WinSize {
pub width: i32,
pub height: i32,
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct WmNormalHints {
pub min_size: Option<WinSize>,
pub max_size: Option<WinSize>,
}
impl From<&[u32]> for WmNormalHints {
fn from(value: &[u32]) -> Self {
let mut ret = Self::default();
let flags = WmSizeHintsFlags::from_bits_truncate(value[0]);
if flags.contains(WmSizeHintsFlags::ProgramMinSize) {
ret.min_size = Some(WinSize {
width: value[5] as _,
height: value[6] as _,
});
}
if flags.contains(WmSizeHintsFlags::ProgramMaxSize) {
ret.max_size = Some(WinSize {
width: value[7] as _,
height: value[8] as _,
});
}
ret
}
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct WmHints {
pub input: Option<bool>,
pub window_group: Option<x::Window>,
}
impl From<&[u32]> for WmHints {
fn from(value: &[u32]) -> Self {
let mut ret = Self::default();
let flags = WmHintsFlags::from_bits_truncate(value[0]);
if flags.contains(WmHintsFlags::WindowGroup) {
let window = unsafe { x::Window::new(value[8]) };
ret.window_group = Some(window);
}
ret
}
}
#[derive(Debug, Clone, Copy)]
pub enum SetState {
Remove,
Add,
Toggle,
}
impl TryFrom<u32> for SetState {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Remove),
1 => Ok(Self::Add),
2 => Ok(Self::Toggle),
_ => Err(()),
}
}
}
impl super::XConnection for Arc<xcb::Connection> {
type ExtraData = Atoms;
fn root_window(&self) -> x::Window {
self.get_setup().roots().next().unwrap().root()
}
fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
trace!("reconfiguring window {window:?}");
self.send_and_check_request(&x::ConfigureWindow {
window,
value_list: &[
x::ConfigWindow::X(dims.x),
x::ConfigWindow::Y(dims.y),
x::ConfigWindow::Width(dims.width as _),
x::ConfigWindow::Height(dims.height as _),
],
})
.unwrap();
}
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, atoms: Self::ExtraData) {
let data = if fullscreen {
std::slice::from_ref(&atoms.wm_fullscreen)
} else {
&[]
};
self.send_and_check_request(&x::ChangeProperty::<x::Atom> {
mode: x::PropMode::Replace,
window,
property: atoms.net_wm_state,
r#type: x::ATOM_ATOM,
data,
})
.unwrap();
}
fn focus_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
let prop = self
.wait_for_reply(self.send_request(&x::GetProperty {
delete: false,
window,
property: x::ATOM_WM_HINTS,
r#type: x::ATOM_WM_HINTS,
long_offset: 0,
long_length: 8,
}))
.unwrap();
let fields: &[u32] = prop.value();
let mut input = false;
if !fields.is_empty() {
let flags = fields[0];
if (flags & 0x1) > 0 {
input = fields[1] > 0;
}
}
if input {
// might fail if window is not visible but who cares
let _ = self.send_and_check_request(&x::SetInputFocus {
focus: window,
revert_to: x::InputFocus::None,
time: x::CURRENT_TIME,
});
}
self.send_and_check_request(&x::ConfigureWindow {
window,
value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
})
.unwrap();
self.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window: self.root_window(),
property: atoms.active_win,
r#type: x::ATOM_WINDOW,
data: &[window],
})
.unwrap();
}
fn close_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
let data = [atoms.wm_delete_window.resource_id(), 0, 0, 0, 0];
let event = &x::ClientMessageEvent::new(
window,
atoms.wm_protocols,
x::ClientMessageData::Data32(data),
);
self.send_and_check_request(&x::SendEvent {
destination: x::SendEventDest::Window(window),
propagate: false,
event_mask: x::EventMask::empty(),
event,
})
.unwrap();
}
}
impl super::FromServerState<Arc<xcb::Connection>> for Atoms {
fn create(state: &super::RealServerState) -> Self {
state.atoms.as_ref().unwrap().clone()
}
}