use std::collections::{hash_map, HashMap, HashSet}; use std::io::Read; use std::io::Write; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; use wayland_protocols::{ wp::{ fractional_scale::v1::server::{ wp_fractional_scale_manager_v1::{self, WpFractionalScaleManagerV1}, wp_fractional_scale_v1::{self, WpFractionalScaleV1}, }, linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, tablet::zv2::server::{ zwp_tablet_manager_v2::ZwpTabletManagerV2, zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, zwp_tablet_tool_v2::{self, ZwpTabletToolV2}, zwp_tablet_v2::ZwpTabletV2, }, viewporter::server::{ wp_viewport::{self, WpViewport}, wp_viewporter::{self, WpViewporter}, }, }, xdg::{ activation::v1::server::{ xdg_activation_token_v1::{self, XdgActivationTokenV1}, xdg_activation_v1::{self, XdgActivationV1}, }, decoration::zv1::server::{ zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1}, zxdg_toplevel_decoration_v1::{self, ZxdgToplevelDecorationV1}, }, shell::server::{ xdg_popup::{self, XdgPopup}, xdg_positioner::{self, XdgPositioner}, xdg_surface::XdgSurface, xdg_toplevel::{self, XdgToplevel}, xdg_wm_base::{self, XdgWmBase}, }, xdg_output::zv1::server::{ zxdg_output_manager_v1::{self, ZxdgOutputManagerV1}, zxdg_output_v1::{self, ZxdgOutputV1}, }, }, }; use wayland_server::{ backend::{ protocol::{Interface, ProtocolError}, GlobalHandler, ObjectData, }, protocol::{ self as proto, wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor, wl_data_device::{self, WlDataDevice}, wl_data_device_manager::{self, WlDataDeviceManager}, wl_data_offer::{self, WlDataOffer}, wl_data_source::{self, WlDataSource}, wl_keyboard::{self, WlKeyboard}, wl_output::{self, WlOutput}, wl_pointer::{self, WlPointer}, wl_seat::{self, WlSeat}, wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface, }, Client, Dispatch, Display, DisplayHandle, GlobalDispatch, Resource, WEnum, }; use wl_drm::server::wl_drm::WlDrm; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct BufferDamage { pub x: i32, pub y: i32, pub width: i32, pub height: i32, } #[derive(Debug, PartialEq, Eq)] pub struct Viewport { pub width: i32, pub height: i32, viewport: WpViewport, } #[derive(Debug, PartialEq, Eq)] pub struct SurfaceData { pub surface: WlSurface, pub buffer: Option, pub last_damage: Option, pub role: Option, pub last_enter_serial: Option, pub fractional: Option, pub viewport: Option, } impl SurfaceData { pub fn xdg(&self) -> &XdgSurfaceData { match self.role.as_ref().expect("Surface missing role") { SurfaceRole::Toplevel(ref t) => &t.xdg, SurfaceRole::Popup(ref p) => &p.xdg, SurfaceRole::Cursor => panic!("cursor surface doesn't have an XdgSurface"), } } pub fn toplevel(&self) -> &Toplevel { match self.role.as_ref().expect("Surface missing role") { SurfaceRole::Toplevel(ref t) => t, other => panic!("Surface role was not toplevel: {other:?}"), } } pub fn popup(&self) -> &Popup { match self.role.as_ref().expect("Surface missing role") { SurfaceRole::Popup(ref p) => p, other => panic!("Surface role was not popup: {other:?}"), } } } #[derive(Debug, PartialEq, Eq)] pub enum SurfaceRole { Toplevel(Toplevel), Popup(Popup), Cursor, } #[derive(Debug, PartialEq, Eq)] pub struct Toplevel { pub xdg: XdgSurfaceData, pub toplevel: XdgToplevel, pub parent: Option, pub min_size: Option, pub max_size: Option, pub states: Vec, pub closed: bool, pub title: Option, pub app_id: Option, pub decoration: Option<( ZxdgToplevelDecorationV1, Option, )>, } #[derive(Debug, PartialEq, Eq)] pub struct Popup { pub xdg: XdgSurfaceData, pub parent: XdgSurface, pub popup: XdgPopup, pub positioner_state: PositionerState, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct Vec2 { pub x: i32, pub y: i32, } #[derive(Debug, PartialEq, Eq)] pub struct XdgSurfaceData { pub surface: XdgSurface, pub last_configure_serial: u32, } impl XdgSurfaceData { fn new(surface: XdgSurface) -> Self { Self { surface, last_configure_serial: 0, } } fn configure(&mut self, serial: u32) { self.surface.configure(serial); self.last_configure_serial = serial; } } #[derive(Debug, Hash, Clone, Copy, Eq, PartialEq)] pub struct SurfaceId(u32); #[derive(Hash, Clone, Copy, Eq, PartialEq)] struct PositionerId(u32); #[derive(Default)] struct DataSourceData { mimes: Vec, } struct Output { name: String, xdg: Option, } struct KeyboardState { keyboard: WlKeyboard, current_focus: Option, } #[derive(Default)] struct ActivationTokenData { serial: Option<(u32, WlSeat)>, app_id: Option, surface: Option, constructed: bool, } struct State { surfaces: HashMap, outputs: HashMap, positioners: HashMap, buffers: HashSet, begin: Instant, last_surface_id: Option, last_output: Option, callbacks: Vec, seat: Option, pointer: Option, keyboard: Option, configure_serial: u32, selection: Option, data_device_man: Option, data_device: Option, xdg_activation: Option, valid_tokens: HashSet, token_counter: u32, } impl Default for State { fn default() -> Self { Self { surfaces: Default::default(), outputs: Default::default(), buffers: Default::default(), positioners: Default::default(), begin: Instant::now(), last_surface_id: None, last_output: None, callbacks: Vec::new(), seat: None, pointer: None, keyboard: None, configure_serial: 0, selection: None, data_device_man: None, data_device: None, xdg_activation: None, valid_tokens: HashSet::new(), token_counter: 0, } } } impl State { #[track_caller] fn configure_toplevel( &mut self, surface_id: SurfaceId, width: i32, height: i32, states: Vec, ) { let last_serial = self.configure_serial; let toplevel = self.get_toplevel(surface_id); toplevel.states = states.clone(); let states: Vec = states .into_iter() .map(|state| u32::from(state) as u8) .collect(); toplevel.toplevel.configure(width, height, states); toplevel.xdg.configure(last_serial); self.configure_serial += 1; } #[track_caller] fn focus_toplevel(&mut self, surface_id: SurfaceId) { let KeyboardState { keyboard, current_focus, } = self.keyboard.as_mut().expect("Keyboard should be created"); if let Some(id) = current_focus { keyboard.leave(self.configure_serial, &self.surfaces[id].surface); } let surface = self.surfaces.get_mut(&surface_id).unwrap(); keyboard.enter(self.configure_serial, &surface.surface, Vec::default()); surface.last_enter_serial = Some(self.configure_serial); *current_focus = Some(surface_id); } #[track_caller] fn unfocus_toplevel(&mut self) { let KeyboardState { current_focus, keyboard, } = self.keyboard.as_mut().expect("Keyboard should be created"); if let Some(id) = current_focus.take() { keyboard.leave(self.configure_serial, &self.surfaces[&id].surface); } } fn get_focused(&self) -> Option { self.keyboard.as_ref()?.current_focus } #[track_caller] fn configure_popup(&mut self, surface_id: SurfaceId) { let surface = self.surfaces.get_mut(&surface_id).unwrap(); let Some(SurfaceRole::Popup(p)) = &mut surface.role else { panic!("Surface does not have popup role: {:?}", surface.role); }; let PositionerState { size, offset, .. } = &p.positioner_state; let size = size.unwrap(); p.popup.configure(offset.x, offset.y, size.x, size.y); p.xdg.configure(self.configure_serial); self.configure_serial += 1; } #[track_caller] fn get_toplevel(&mut self, surface_id: SurfaceId) -> &mut Toplevel { let surface = self .surfaces .get_mut(&surface_id) .expect("Surface does not exist"); match &mut surface.role { Some(SurfaceRole::Toplevel(t)) => t, other => panic!("Surface does not have toplevel role: {:?}", other), } } #[track_caller] fn popup_done(&mut self, surface_id: SurfaceId) { let surface = self.surfaces.get_mut(&surface_id).unwrap(); let Some(SurfaceRole::Popup(p)) = &mut surface.role else { panic!("Surface does not have popup role: {:?}", surface.role); }; p.popup.popup_done(); } } macro_rules! simple_global_dispatch { ($type:ty) => { #[allow(non_local_definitions)] impl GlobalDispatch<$type, ()> for State { fn bind( _: &mut Self, _: &DisplayHandle, _: &wayland_server::Client, resource: wayland_server::New<$type>, _: &(), data_init: &mut wayland_server::DataInit<'_, Self>, ) { data_init.init(resource, ()); } } }; } pub struct Server { display: Display, dh: DisplayHandle, state: State, client: Option, } impl Server { pub fn new(noops: bool) -> Self { let display = Display::new().unwrap(); let dh = display.handle(); macro_rules! global_noop { ($type:ty) => { if noops { dh.create_global::(1, ()); } simple_global_dispatch!($type); #[allow(non_local_definitions)] impl Dispatch<$type, ()> for State { fn request( _: &mut Self, _: &Client, _: &$type, _: <$type as Resource>::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { todo!("Dispatch for {} is no-op", stringify!($type)); } } }; } dh.create_global::(6, ()); dh.create_global::(1, ()); dh.create_global::(6, ()); dh.create_global::(5, ()); dh.create_global::(3, ()); dh.create_global::(1, ()); dh.create_global::(1, ()); dh.create_global::(1, ()); dh.create_global::(1, ()); global_noop!(ZwpLinuxDmabufV1); global_noop!(ZwpRelativePointerManagerV1); global_noop!(ZwpPointerConstraintsV1); struct HandlerData; impl ObjectData for HandlerData { fn request( self: Arc, _: &wayland_server::backend::Handle, _: &mut State, _: wayland_server::backend::ClientId, _: wayland_server::backend::protocol::Message< wayland_server::backend::ObjectId, OwnedFd, >, ) -> Option>> { None } fn destroyed( self: Arc, _: &wayland_server::backend::Handle, _: &mut State, _: wayland_server::backend::ClientId, _: wayland_server::backend::ObjectId, ) { } } struct Handler; impl GlobalHandler for Handler { fn bind( self: Arc, _: &wayland_server::backend::Handle, _: &mut State, _: wayland_server::backend::ClientId, _: wayland_server::backend::GlobalId, _: wayland_server::backend::ObjectId, ) -> Arc> { Arc::new(HandlerData) } } // Simulate interface with higher interface than supported client side static IF: OnceLock = OnceLock::new(); let interface = IF.get_or_init(|| Interface { version: WlDrm::interface().version + 1, ..*WlDrm::interface() }); if noops { dh.backend_handle() .create_global(interface, interface.version, Arc::new(Handler)); } Self { display, dh, state: State::default(), client: None, } } pub fn poll_fd(&mut self) -> BorrowedFd<'_> { self.display.backend().poll_fd() } pub fn connect(&mut self, stream: UnixStream) { let client = self .dh .insert_client(stream, std::sync::Arc::new(())) .unwrap(); assert!( self.client.replace(client).is_none(), "Client already connected to test server" ); } pub fn dispatch(&mut self) { self.display.dispatch_clients(&mut self.state).unwrap(); for callback in std::mem::take(&mut self.state.callbacks) { callback.done(self.state.begin.elapsed().as_millis().try_into().unwrap()); } self.display.flush_clients().unwrap(); } pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> { self.state.surfaces.get(&surface_id) } pub fn last_created_surface_id(&self) -> Option { self.state.last_surface_id } #[track_caller] pub fn last_created_output(&self) -> WlOutput { self.state .last_output .as_ref() .expect("No outputs created!") .clone() } pub fn get_object( &self, id: SurfaceId, ) -> Result { let client = self.client.as_ref().unwrap(); client.object_from_protocol_id::(&self.display.handle(), id.0) } #[track_caller] pub fn configure_toplevel( &mut self, surface_id: SurfaceId, width: i32, height: i32, states: Vec, ) { self.state .configure_toplevel(surface_id, width, height, states); self.display.flush_clients().unwrap(); } #[track_caller] pub fn focus_toplevel(&mut self, surface_id: SurfaceId) { self.state.focus_toplevel(surface_id); self.display.flush_clients().unwrap(); } #[track_caller] pub fn unfocus_toplevel(&mut self) { self.state.unfocus_toplevel(); self.display.flush_clients().unwrap(); } #[track_caller] pub fn get_focused(&self) -> Option { self.state.get_focused() } #[track_caller] pub fn configure_popup(&mut self, surface_id: SurfaceId) { self.state.configure_popup(surface_id); self.display.flush_clients().unwrap(); } #[track_caller] pub fn popup_done(&mut self, surface_id: SurfaceId) { self.state.popup_done(surface_id); self.display.flush_clients().unwrap(); } #[track_caller] pub fn close_toplevel(&mut self, surface_id: SurfaceId) { let toplevel = self.state.get_toplevel(surface_id); toplevel.toplevel.close(); self.dispatch(); } #[track_caller] pub fn pointer(&self) -> &WlPointer { self.state.pointer.as_ref().unwrap() } #[track_caller] pub fn data_source_mimes(&self) -> Vec { let Some(selection) = &self.state.selection else { panic!("No selection set on data device"); }; let data: &Mutex = selection.data().unwrap(); let data = data.lock().unwrap(); data.mimes.to_vec() } #[track_caller] pub fn paste_data( &mut self, mut send_data_for_mime: impl FnMut(&str, &mut Self) -> bool, ) -> Vec { struct PendingData { rx: std::fs::File, data: Vec, } let Some(selection) = self.state.selection.take() else { panic!("No selection set on data device"); }; type PendingRet = Vec<(String, Option)>; let mut pending_ret: PendingRet = { let data: &Mutex = selection.data().unwrap(); data.lock() .unwrap() .mimes .iter() .rev() .map(|mime| (mime.clone(), None)) .collect() }; let mut ret = Vec::new(); let mut try_transfer = |pending_ret: &mut PendingRet, mime: String, mut pending: PendingData| { self.display.flush_clients().unwrap(); let transfer_complete = send_data_for_mime(&mime, self); if transfer_complete { pending.rx.read_to_end(&mut pending.data).unwrap(); ret.push(PasteData { mime_type: mime, data: pending.data, }); } else { loop { match pending.rx.read(&mut pending.data) { Ok(0) => break, Ok(_) => {} Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, Err(e) => panic!("Failed reading data for mime {mime}: {e:?}"), } } pending_ret.push((mime, Some(pending))); self.dispatch(); } }; while let Some((mime, pending)) = pending_ret.pop() { match pending { Some(pending) => try_transfer(&mut pending_ret, mime, pending), None => { let (rx, tx) = rustix::pipe::pipe().unwrap(); selection.send(mime.clone(), tx.as_fd()); drop(tx); let rx = std::fs::File::from(rx); try_transfer( &mut pending_ret, mime, PendingData { rx, data: Vec::new(), }, ); } } } self.state.selection = Some(selection); ret } pub fn data_source_exists(&self) -> bool { self.state.selection.is_none() } #[track_caller] pub fn create_data_offer(&mut self, data: Vec) { let Some(dev) = &self.state.data_device else { panic!("No data device created"); }; if let Some(selection) = self.state.selection.take() { selection.cancelled(); } let mimes: Vec<_> = data.iter().map(|m| m.mime_type.clone()).collect(); let offer = self .client .as_ref() .unwrap() .create_resource::<_, _, State>(&self.dh, 3, data) .unwrap(); dev.data_offer(&offer); for mime in mimes { offer.offer(mime); } dev.selection(Some(&offer)); self.display.flush_clients().unwrap(); } #[track_caller] pub fn move_pointer_to(&mut self, surface: SurfaceId, x: f64, y: f64) { let pointer = self.state.pointer.as_ref().expect("No pointer created"); let data = self.state.surfaces.get(&surface).expect("No such surface"); pointer.enter(24, &data.surface, x, y); pointer.frame(); self.display.flush_clients().unwrap(); } pub fn new_output(&mut self, x: i32, y: i32) { self.dh.create_global::(4, (x, y)); self.display.flush_clients().unwrap(); } pub fn get_output(&mut self, name: &str) -> Option { self.state .outputs .iter() .find_map(|(output, data)| (data.name == name).then_some(output.clone())) } pub fn move_output(&mut self, output: &WlOutput, x: i32, y: i32) { output.geometry( x, y, 0, 0, wl_output::Subpixel::None, "".into(), "".into(), wl_output::Transform::Normal, ); output.done(); self.display.flush_clients().unwrap(); } pub fn move_surface_to_output(&mut self, surface: SurfaceId, output: &WlOutput) { let data = self.state.surfaces.get(&surface).expect("No such surface"); data.surface.enter(output); self.display.flush_clients().unwrap(); } pub fn enable_xdg_output_manager(&mut self) { self.dh .create_global::(3, ()); self.display.flush_clients().unwrap(); } pub fn move_xdg_output(&mut self, output: &WlOutput, x: i32, y: i32) { let xdg = self.state.outputs[output] .xdg .as_ref() .expect("Output doesn't have an xdg output"); xdg.logical_position(x, y); xdg.done(); self.display.flush_clients().unwrap(); } pub fn enable_fractional_scale(&mut self) { self.dh .create_global::(1, ()); self.display.flush_clients().unwrap(); } } #[derive(Clone, Eq, PartialEq, Debug)] pub struct PasteData { pub mime_type: String, pub data: Vec, } simple_global_dispatch!(WlShm); simple_global_dispatch!(WlCompositor); simple_global_dispatch!(XdgWmBase); simple_global_dispatch!(ZxdgOutputManagerV1); simple_global_dispatch!(ZwpTabletManagerV2); simple_global_dispatch!(ZxdgDecorationManagerV1); simple_global_dispatch!(WpViewporter); simple_global_dispatch!(WpFractionalScaleManagerV1); impl Dispatch for State { fn request( _: &mut Self, client: &Client, _: &ZwpTabletManagerV2, request: ::Request, _: &(), dhandle: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { wayland_protocols::wp::tablet::zv2::server::zwp_tablet_manager_v2::Request::GetTabletSeat { tablet_seat, seat: _ } => { let seat = data_init.init(tablet_seat, ()); let tablet = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); seat.tablet_added(&tablet); tablet.name("tabby".to_owned()); tablet.done(); let tool = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); seat.tool_added(&tool); tool._type(zwp_tablet_tool_v2::Type::Finger); tool.done(); let pad = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); let group = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); let ring = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); let strip = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); seat.pad_added(&pad); pad.buttons(5); pad.group(&group); pad.done(); group.buttons(vec![]); group.ring(&ring); group.strip(&strip); group.done(); } wayland_protocols::wp::tablet::zv2::server::zwp_tablet_manager_v2::Request::Destroy => {} other => todo!("unhandled tablet manager request: {other:?}") } } } macro_rules! unhandled { ($type:ty) => { impl Dispatch<$type, ()> for State { fn request( _: &mut Self, _: &Client, _: &$type, _: <$type as Resource>::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { todo!(concat!(stringify!($type), " unhandled")); } } }; } unhandled!(ZwpTabletSeatV2); unhandled!(ZwpTabletV2); unhandled!(ZwpTabletToolV2); unhandled!(ZwpTabletPadV2); unhandled!(ZwpTabletPadGroupV2); unhandled!(ZwpTabletPadRingV2); unhandled!(ZwpTabletPadStripV2); impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &ZxdgOutputManagerV1, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { zxdg_output_manager_v1::Request::GetXdgOutput { id, output } => { let xdg = data_init.init(id, output.clone()); xdg.logical_position(0, 0); xdg.logical_size(1000, 1000); xdg.done(); state.outputs.get_mut(&output).unwrap().xdg = Some(xdg); } other => todo!("unhandled request: {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &ZxdgOutputV1, request: ::Request, output: &WlOutput, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { zxdg_output_v1::Request::Destroy => { state.outputs.get_mut(output).unwrap().xdg = None; } other => todo!("unhandled request: {other:?}"), } } } impl GlobalDispatch for State { fn bind( state: &mut Self, _: &DisplayHandle, _: &Client, resource: wayland_server::New, &(x, y): &(i32, i32), data_init: &mut wayland_server::DataInit<'_, Self>, ) { let output = data_init.init(resource, ()); output.geometry( x, y, 0, 0, wl_output::Subpixel::None, "xwls".to_string(), "fake monitor".to_string(), wl_output::Transform::Normal, ); let name = format!("WL-{}", state.outputs.len() + 1); output.name(name.clone()); output.mode(wl_output::Mode::Current, 1000, 1000, 0); output.done(); state .outputs .insert(output.clone(), Output { name, xdg: None }); state.last_output = Some(output); } } impl Dispatch for State { fn request( _: &mut Self, _: &Client, _: &WlOutput, _: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { unreachable!(); } } impl GlobalDispatch for State { fn bind( state: &mut Self, _: &DisplayHandle, _: &Client, resource: wayland_server::New, _: &(), data_init: &mut wayland_server::DataInit<'_, Self>, ) { state.data_device_man = Some(data_init.init(resource, ())); } } impl Dispatch> for State { fn request( _: &mut Self, _: &Client, _: &WlDataOffer, request: ::Request, data: &Vec, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_data_offer::Request::Receive { mime_type, fd } => { let pos = data .iter() .position(|data| data.mime_type == mime_type) .unwrap_or_else(|| panic!("Invalid mime type: {mime_type}")); let mut stream = UnixStream::from(fd); stream.write_all(&data[pos].data).unwrap(); } wl_data_offer::Request::Destroy => {} other => todo!("unhandled request: {other:?}"), } } } impl Dispatch> for State { fn request( state: &mut Self, _: &Client, _: &WlDataSource, request: ::Request, data: &Mutex, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { let mut data = data.lock().unwrap(); match request { wl_data_source::Request::Offer { mime_type } => { data.mimes.push(mime_type); } wl_data_source::Request::Destroy => { state.selection = None; } other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WlDataDevice, request: ::Request, _: &WlSeat, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_data_device::Request::SetSelection { source, .. } => { state.selection = source; } wl_data_device::Request::Release => { state.data_device = None; } other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WlDataDeviceManager, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_data_device_manager::Request::CreateDataSource { id } => { data_init.init(id, DataSourceData::default().into()); } wl_data_device_manager::Request::GetDataDevice { id, seat } => { state.data_device = Some(data_init.init(id, seat)); } other => todo!("unhandled request: {other:?}"), } } } impl GlobalDispatch for State { fn bind( state: &mut Self, _: &DisplayHandle, _: &Client, resource: wayland_server::New, _: &(), data_init: &mut wayland_server::DataInit<'_, Self>, ) { let seat = data_init.init(resource, ()); seat.capabilities(wl_seat::Capability::Pointer | wl_seat::Capability::Keyboard); state.seat = Some(seat); } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WlSeat, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_seat::Request::GetPointer { id } => { state.pointer = Some(data_init.init(id, ())); } wl_seat::Request::GetKeyboard { id } => { state.keyboard = Some(KeyboardState { keyboard: data_init.init(id, ()), current_focus: None, }); } wl_seat::Request::Release => {} other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WlPointer, request: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_pointer::Request::SetCursor { surface, .. } => { if let Some(surface) = surface { let data = state .surfaces .get_mut(&SurfaceId(surface.id().protocol_id())) .unwrap(); assert!( matches!( data.role.replace(SurfaceRole::Cursor), None | Some(SurfaceRole::Cursor) ), "Surface already had a non cursor role!" ); } } wl_pointer::Request::Release => { state.pointer.take(); } other => todo!("unhandled pointer request: {other:?}"), } } } impl Dispatch for State { fn request( _: &mut Self, _: &Client, _: &WlKeyboard, request: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wl_keyboard::Request::Release => {} other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &XdgPopup, request: ::Request, surface_id: &SurfaceId, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { xdg_popup::Request::Destroy => {} xdg_popup::Request::Reposition { positioner, token } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Popup(p)) = &mut data.role else { unreachable!(); }; let positioner_data = &state.positioners[&PositionerId(positioner.id().protocol_id())]; p.positioner_state = positioner_data.clone(); p.popup.repositioned(token); state.configure_popup(*surface_id); } other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &wayland_server::Client, _: &XdgToplevel, request: ::Request, surface_id: &SurfaceId, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { xdg_toplevel::Request::SetMinSize { width, height } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.min_size = Some(Vec2 { x: width, y: height, }); } xdg_toplevel::Request::SetMaxSize { width, height } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.max_size = Some(Vec2 { x: width, y: height, }); } xdg_toplevel::Request::SetFullscreen { .. } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.states.push(xdg_toplevel::State::Fullscreen); let states = toplevel.states.clone(); state.configure_toplevel(*surface_id, 100, 100, states); } xdg_toplevel::Request::UnsetFullscreen { .. } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; let Some(pos) = toplevel .states .iter() .copied() .position(|p| p == xdg_toplevel::State::Fullscreen) else { return; }; toplevel.states.swap_remove(pos); let states = toplevel.states.clone(); state.configure_toplevel(*surface_id, 100, 100, states); } xdg_toplevel::Request::Destroy => {} xdg_toplevel::Request::SetTitle { title } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.title = title.into(); } xdg_toplevel::Request::SetAppId { app_id } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.app_id = app_id.into(); } xdg_toplevel::Request::SetParent { parent } => { let data = state.surfaces.get_mut(surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.parent = parent; } other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, client: &wayland_server::Client, resource: &XdgSurface, request: ::Request, surface_id: &SurfaceId, dh: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { use wayland_protocols::xdg::shell::server::xdg_surface; match request { xdg_surface::Request::GetToplevel { id } => { let toplevel = data_init.init(id, *surface_id); let t = Toplevel { xdg: XdgSurfaceData::new(resource.clone()), toplevel, parent: None, min_size: None, max_size: None, states: Vec::new(), closed: false, title: None, app_id: None, decoration: None, }; let data = state.surfaces.get_mut(surface_id).unwrap(); data.role = Some(SurfaceRole::Toplevel(t)); } xdg_surface::Request::GetPopup { id, parent, positioner, } => { let positioner_state = state.positioners[&PositionerId(positioner.id().protocol_id())].clone(); if positioner_state .size .is_none_or(|size| size.x <= 0 || size.y <= 0) { // TODO: figure out why the client.kill here doesn't make satellite print the error message let message = format!("positioner had an invalid size {:?}", positioner_state.size); eprintln!("{message}"); client.kill( dh, ProtocolError { code: xdg_surface::Error::InvalidSize.into(), object_id: resource.id().protocol_id(), object_interface: XdgSurface::interface().name.to_string(), message, }, ); return; } let popup = data_init.init(id, *surface_id); let p = Popup { xdg: XdgSurfaceData::new(resource.clone()), popup, parent: parent.unwrap(), positioner_state, }; let data = state.surfaces.get_mut(surface_id).unwrap(); data.role = Some(SurfaceRole::Popup(p)); } xdg_surface::Request::AckConfigure { serial } => { let data = state.surfaces.get_mut(surface_id).unwrap(); assert_eq!(data.xdg().last_configure_serial, serial); } xdg_surface::Request::Destroy => { let data = state.surfaces.get_mut(surface_id).unwrap(); let role_alive = data.role.is_none() || match data.role.as_ref().unwrap() { SurfaceRole::Toplevel(t) => t.toplevel.is_alive(), SurfaceRole::Popup(p) => p.popup.is_alive(), SurfaceRole::Cursor => false, }; if role_alive { client.kill( dh, ProtocolError { code: xdg_surface::Error::DefunctRoleObject.into(), object_id: resource.id().protocol_id(), object_interface: XdgSurface::interface().name.to_string(), message: "destroyed xdg surface before role".to_string(), }, ); } } other => todo!("unhandled request {other:?}"), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Rect { pub size: Vec2, pub offset: Vec2, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PositionerState { pub size: Option, pub anchor_rect: Option, pub offset: Vec2, pub anchor: xdg_positioner::Anchor, pub gravity: xdg_positioner::Gravity, } impl Default for PositionerState { fn default() -> Self { Self { size: None, anchor_rect: None, offset: Vec2 { x: 0, y: 0 }, anchor: xdg_positioner::Anchor::None, gravity: xdg_positioner::Gravity::None, } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, resource: &XdgPositioner, request: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { let hash_map::Entry::Occupied(mut data) = state .positioners .entry(PositionerId(resource.id().protocol_id())) else { unreachable!(); }; match request { xdg_positioner::Request::SetSize { width, height } => { data.get_mut().size = Some(Vec2 { x: width, y: height, }); } xdg_positioner::Request::SetAnchorRect { x, y, width, height, } => { data.get_mut().anchor_rect = Some(Rect { size: Vec2 { x: width, y: height, }, offset: Vec2 { x, y }, }); } xdg_positioner::Request::SetOffset { x, y } => { data.get_mut().offset = Vec2 { x, y }; } xdg_positioner::Request::SetAnchor { anchor } => { data.get_mut().anchor = anchor.into_result().unwrap(); } xdg_positioner::Request::SetGravity { gravity } => { data.get_mut().gravity = gravity.into_result().unwrap(); } xdg_positioner::Request::Destroy => { data.remove(); } other => todo!("unhandled positioner request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, client: &wayland_server::Client, _: &XdgWmBase, request: ::Request, _: &(), dhandle: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { xdg_wm_base::Request::GetXdgSurface { id, surface } => { let surface_id = SurfaceId(surface.id().protocol_id()); let data = state.surfaces.get(&surface_id).unwrap(); if data.buffer.is_some() { client.kill( dhandle, ProtocolError { code: xdg_wm_base::Error::InvalidSurfaceState.into(), object_id: surface_id.0, object_interface: XdgWmBase::interface().name.to_string(), message: "Buffer already attached to surface".to_string(), }, ); return; } data_init.init(id, surface_id); } xdg_wm_base::Request::CreatePositioner { id } => { let pos = data_init.init(id, ()); state.positioners.insert( PositionerId(pos.id().protocol_id()), PositionerState::default(), ); } other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( _: &mut Self, _: &wayland_server::Client, _: &WlShm, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { proto::wl_shm::Request::CreatePool { id, .. } => { data_init.init(id, ()); } _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &wayland_server::Client, _: &WlShmPool, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { use proto::wl_shm_pool::Request::*; match request { CreateBuffer { id, .. } => { let buf = data_init.init(id, ()); state.buffers.insert(buf); } Destroy => {} other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &wayland_server::Client, resource: &WlBuffer, request: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { proto::wl_buffer::Request::Destroy => { state.buffers.remove(resource); } _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &wayland_server::Client, _: &WlCompositor, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { proto::wl_compositor::Request::CreateSurface { id } => { let surface = data_init.init(id, ()); let id = surface.id().protocol_id(); state.surfaces.insert( SurfaceId(id), SurfaceData { surface, buffer: None, last_damage: None, role: None, last_enter_serial: None, fractional: None, viewport: None, }, ); state.last_surface_id = Some(SurfaceId(id)); } _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &wayland_server::Client, resource: &WlSurface, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { use proto::wl_surface::Request::*; let data = state .surfaces .get_mut(&SurfaceId(resource.id().protocol_id())) .unwrap_or_else(|| panic!("{:?} missing from surface map", resource)); match request { Attach { buffer, .. } => { data.buffer = buffer; } Frame { callback } => { // XXX: calling done immediately will cause wayland_backend to panic, // report upstream state.callbacks.push(data_init.init(callback, ())); } DamageBuffer { x, y, width, height, } => { data.last_damage = Some(BufferDamage { x, y, width, height, }); } Commit => {} Destroy => { let id = SurfaceId(resource.id().protocol_id()); if let Some(kb) = state .keyboard .as_mut() .filter(|kb| kb.current_focus == Some(id)) { kb.keyboard.leave(state.configure_serial, resource); kb.current_focus.take(); } state.surfaces.remove(&id); } SetInputRegion { .. } => {} SetBufferScale { .. } => {} other => todo!("unhandled request {other:?}"), } } } impl Dispatch for State { fn request( _: &mut Self, _: &wayland_server::Client, _: &WlCallback, _: ::Request, _: &(), _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { unreachable!() } } impl GlobalDispatch for State { fn bind( state: &mut Self, _: &DisplayHandle, _: &Client, resource: wayland_server::New, _: &(), data_init: &mut wayland_server::DataInit<'_, Self>, ) { state.xdg_activation = Some(data_init.init(resource, ())); } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &XdgActivationV1, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { xdg_activation_v1::Request::Destroy => {} xdg_activation_v1::Request::GetActivationToken { id } => { data_init.init(id, Mutex::new(ActivationTokenData::default())); } xdg_activation_v1::Request::Activate { token, surface } => { if state.valid_tokens.remove(&token) { let surface_id = SurfaceId(surface.id().protocol_id()); state.focus_toplevel(surface_id); } } _ => unreachable!(), } } } impl Dispatch> for State { fn request( state: &mut Self, _: &Client, token: &XdgActivationTokenV1, request: ::Request, data: &Mutex, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { let mut data = data.lock().unwrap(); match request { xdg_activation_token_v1::Request::SetSerial { serial, seat } => { if data.constructed { token.post_error( xdg_activation_token_v1::Error::AlreadyUsed, "The activation token has already been constructed", ); return; } data.serial = Some((serial, seat)); } xdg_activation_token_v1::Request::SetAppId { app_id } => { if data.constructed { token.post_error( xdg_activation_token_v1::Error::AlreadyUsed, "The activation token has already been constructed", ); return; } data.app_id = Some(app_id); } xdg_activation_token_v1::Request::SetSurface { surface } => { if data.constructed { token.post_error( xdg_activation_token_v1::Error::AlreadyUsed, "The activation token has already been constructed", ); return; } data.surface = Some(surface); } xdg_activation_token_v1::Request::Commit => { if data.constructed { token.post_error( xdg_activation_token_v1::Error::AlreadyUsed, "The activation token has already been constructed", ); return; } data.constructed = true; // Require a valid serial, otherwise ignore the activation. // This matches niri's behavior: https://github.com/YaLTeR/niri/blob/5e549e13238a853f8860e29621ab6b31ee1b9ee4/src/handlers/mod.rs#L712-L723 let valid = if let (Some((serial, seat)), Some(surface_data)) = ( data.serial.take(), data.surface.take().and_then(|surface| { state.surfaces.get(&SurfaceId(surface.id().protocol_id())) }), ) { state.seat == Some(seat) && surface_data .last_enter_serial .is_some_and(|last_enter| serial >= last_enter) } else { false }; let activation_token = state.token_counter.to_string(); state.token_counter += 1; if valid { state.valid_tokens.insert(activation_token.clone()); } token.done(activation_token); } xdg_activation_token_v1::Request::Destroy => {} _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, resource: &ZxdgDecorationManagerV1, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => { let surface_id = *toplevel.data::().unwrap(); let data = state.surfaces.get_mut(&surface_id).unwrap(); let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; if toplevel.decoration.is_some() { resource.post_error( zxdg_toplevel_decoration_v1::Error::AlreadyConstructed, "Toplevel already has an decoration object", ); return; } toplevel.decoration = Some((data_init.init(id, surface_id), None)); } zxdg_decoration_manager_v1::Request::Destroy => {} _ => todo!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, resource: &ZxdgToplevelDecorationV1, request: ::Request, surface_id: &SurfaceId, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { zxdg_toplevel_decoration_v1::Request::SetMode { mode } => { let WEnum::Value(mode) = mode else { resource.post_error( zxdg_toplevel_decoration_v1::Error::InvalidMode, "Invalid decoration mode", ); return; }; if let Some(data) = state.surfaces.get_mut(surface_id) { let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; *toplevel .decoration .as_mut() .map(|(_, decoration)| decoration) .unwrap() = Some(mode); } else { resource.post_error( zxdg_toplevel_decoration_v1::Error::Orphaned, "Toplevel was destroyed", ); } } zxdg_toplevel_decoration_v1::Request::UnsetMode => { if let Some(data) = state.surfaces.get_mut(surface_id) { let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; *toplevel .decoration .as_mut() .map(|(_, decoration)| decoration) .unwrap() = None; } else { resource.post_error( zxdg_toplevel_decoration_v1::Error::Orphaned, "Toplevel was destroyed", ); } } zxdg_toplevel_decoration_v1::Request::Destroy => { if let Some(data) = state.surfaces.get_mut(surface_id) { let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else { unreachable!(); }; toplevel.decoration = None; } else { resource.post_error( zxdg_toplevel_decoration_v1::Error::Orphaned, "Toplevel was destroyed", ); } } _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WpViewporter, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { wp_viewporter::Request::GetViewport { surface, id } => { let surface_id = SurfaceId(surface.id().protocol_id()); let viewport = data_init.init(id, surface_id); state .surfaces .get_mut(&surface_id) .expect("Unknown surface") .viewport = Some(Viewport { viewport, width: -1, height: -1, }) } wp_viewporter::Request::Destroy => {} _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, viewport: &WpViewport, request: ::Request, surface_id: &SurfaceId, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wp_viewport::Request::SetDestination { width, height } => { if width == 0 || width < -1 || height == 0 || height < -1 { panic!( "Bad viewport width/height ({width}x{height}) - {}", viewport.id() ); } let viewport = state .surfaces .get_mut(surface_id) .unwrap_or_else(|| panic!("Missing surface id {surface_id:?}")) .viewport .as_mut() .unwrap(); viewport.width = width; viewport.height = height; } wp_viewport::Request::Destroy => { if let Some(surface) = state.surfaces.get_mut(surface_id) { surface.viewport.take(); } } _ => unimplemented!("{request:?}"), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WpFractionalScaleManagerV1, request: ::Request, _: &(), _: &DisplayHandle, data_init: &mut wayland_server::DataInit<'_, Self>, ) { match request { wp_fractional_scale_manager_v1::Request::GetFractionalScale { id, surface } => { let surface_id = SurfaceId(surface.id().protocol_id()); let fractional = data_init.init(id, surface_id); let surface_data = state.surfaces.get_mut(&surface_id).unwrap(); surface_data.fractional = Some(fractional); } _ => unreachable!(), } } } impl Dispatch for State { fn request( state: &mut Self, _: &Client, _: &WpFractionalScaleV1, request: ::Request, data: &SurfaceId, _: &DisplayHandle, _: &mut wayland_server::DataInit<'_, Self>, ) { match request { wp_fractional_scale_v1::Request::Destroy => { if let Some(surface_data) = state.surfaces.get_mut(data) { surface_data.fractional.take(); } } _ => unreachable!(), } } }