xwayland-satellite/testwl/src/lib.rs
En-En 0947c4685f
feat: handle global removals, recalc output scale (#367)
All `GlobalRemove` events sent from the server are now handled by
recording them in a new clientside `vec` and passing the identifier
returned by `create_global` (now stored by a map in the state) to
`disable_global`. `handle_globals` (the top-level function) and
`handle_new_globals` (the `InnerServerState` member function) have
swapped names to better represent their new purposes.

This enables action to be taken when globals are removed. In this case,
the desired action is to forward output removal, so that the scaling
calculation does not account for disconnected monitors in its logic.

Resolves #351.
2026-02-03 20:19:49 -05:00

2495 lines
81 KiB
Rust

use std::collections::{hash_map, HashMap, HashSet};
#[rustversion::since(1.87)]
use std::io::PipeWriter;
use std::io::{Read, 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::linux_drm_syncobj::v1::server::wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1;
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1;
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1;
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1;
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_locked_pointer_v1::{self, ZwpLockedPointerV1},
zwp_pointer_constraints_v1::{self, 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::GlobalId;
use wayland_server::protocol::wl_subcompositor::WlSubcompositor;
use wayland_server::protocol::wl_subsurface::WlSubsurface;
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,
wl_touch::{self, WlTouch},
},
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<WlBuffer>,
pub last_damage: Option<BufferDamage>,
pub role: Option<SurfaceRole>,
pub last_enter_serial: Option<u32>,
pub fractional: Option<WpFractionalScaleV1>,
pub viewport: Option<Viewport>,
pub moving: bool,
pub resizing: Option<xdg_toplevel::ResizeEdge>,
}
impl SurfaceData {
pub fn xdg(&self) -> &XdgSurfaceData {
match self.role.as_ref().expect("Surface missing role") {
SurfaceRole::Toplevel(t) => &t.xdg,
SurfaceRole::Popup(p) => &p.xdg,
SurfaceRole::Subsurface(_) => panic!("subsurface doesn't have an XdgSurface"),
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(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(p) => p,
other => panic!("Surface role was not popup: {other:?}"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum SurfaceRole {
Toplevel(Toplevel),
Popup(Popup),
Cursor,
Subsurface(Subsurface),
}
#[derive(Debug, PartialEq, Eq)]
pub struct Toplevel {
pub xdg: XdgSurfaceData,
pub toplevel: XdgToplevel,
pub parent: Option<XdgToplevel>,
pub min_size: Option<Vec2>,
pub max_size: Option<Vec2>,
pub states: Vec<xdg_toplevel::State>,
pub closed: bool,
pub title: Option<String>,
pub app_id: Option<String>,
pub decoration: Option<(
ZxdgToplevelDecorationV1,
Option<zxdg_toplevel_decoration_v1::Mode>,
)>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Popup {
pub xdg: XdgSurfaceData,
pub parent: XdgSurface,
pub popup: XdgPopup,
pub positioner_state: PositionerState,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Subsurface {
pub subsurface: WlSubsurface,
pub position: Vec2,
pub parent: SurfaceId,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct Vec2 {
pub x: i32,
pub y: i32,
}
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct Vec2f {
pub x: f64,
pub y: f64,
}
#[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);
impl From<&WlSurface> for SurfaceId {
fn from(value: &WlSurface) -> Self {
Self(value.id().protocol_id())
}
}
#[derive(Hash, Clone, Copy, Eq, PartialEq)]
struct PositionerId(u32);
#[derive(Default)]
struct DataSourceData {
mimes: Vec<String>,
}
struct Output {
name: String,
xdg: Option<ZxdgOutputV1>,
global_id: Option<GlobalId>,
}
struct KeyboardState {
keyboard: WlKeyboard,
current_focus: Option<SurfaceId>,
}
#[derive(Default)]
struct ActivationTokenData {
serial: Option<(u32, WlSeat)>,
app_id: Option<String>,
surface: Option<WlSurface>,
constructed: bool,
}
pub struct LockedPointer {
pub surface: SurfaceId,
pub cursor_hint: Option<Vec2f>,
}
struct PointerState {
pointer: WlPointer,
locked: Option<LockedPointer>,
}
struct State {
surfaces: HashMap<SurfaceId, SurfaceData>,
outputs: HashMap<WlOutput, Output>,
positioners: HashMap<PositionerId, PositionerState>,
buffers: HashMap<WlBuffer, Vec2>,
begin: Instant,
last_surface_id: Option<SurfaceId>,
created_surfaces: Vec<SurfaceId>,
last_output: Option<WlOutput>,
last_output_global: Option<GlobalId>,
output_counter: u32,
callbacks: Vec<WlCallback>,
seat: Option<WlSeat>,
pointer: Option<PointerState>,
keyboard: Option<KeyboardState>,
touch: Option<WlTouch>,
tablet: Option<ZwpTabletV2>,
tablet_tool: Option<ZwpTabletToolV2>,
configure_serial: u32,
clipboard: Option<WlDataSource>,
primary: Option<ZwpPrimarySelectionSourceV1>,
data_device_man: Option<WlDataDeviceManager>,
data_device: Option<WlDataDevice>,
primary_man: Option<ZwpPrimarySelectionDeviceManagerV1>,
primary_device: Option<ZwpPrimarySelectionDeviceV1>,
xdg_activation: Option<XdgActivationV1>,
valid_tokens: HashSet<String>,
token_counter: u32,
}
impl Default for State {
fn default() -> Self {
Self {
surfaces: Default::default(),
created_surfaces: Default::default(),
outputs: Default::default(),
buffers: Default::default(),
positioners: Default::default(),
begin: Instant::now(),
last_surface_id: None,
last_output: None,
last_output_global: None,
output_counter: 0,
callbacks: Vec::new(),
seat: None,
pointer: None,
keyboard: None,
touch: None,
tablet: None,
tablet_tool: None,
configure_serial: 0,
clipboard: None,
primary: None,
primary_man: None,
primary_device: 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<xdg_toplevel::State>,
) {
let last_serial = self.configure_serial;
let toplevel = self.get_toplevel(surface_id);
toplevel.states = states.clone();
let states: Vec<u8> = 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<SurfaceId> {
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<State>,
dh: DisplayHandle,
state: State,
client: Option<Client>,
decorations_global: GlobalId,
}
pub trait SendDataForMimeFn: FnMut(&str, &mut Server) -> bool {}
impl<T> SendDataForMimeFn for T where T: FnMut(&str, &mut Server) -> bool {}
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::<State, $type, _>(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::<State, WlCompositor, _>(6, ());
dh.create_global::<State, WlSubcompositor, _>(1, ());
dh.create_global::<State, WlShm, _>(1, ());
dh.create_global::<State, XdgWmBase, _>(6, ());
dh.create_global::<State, WlSeat, _>(5, ());
dh.create_global::<State, WlDataDeviceManager, _>(3, ());
dh.create_global::<State, ZwpPrimarySelectionDeviceManagerV1, _>(1, ());
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
dh.create_global::<State, XdgActivationV1, _>(1, ());
let decorations_global = dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
dh.create_global::<State, WpViewporter, _>(1, ());
dh.create_global::<State, ZwpPointerConstraintsV1, _>(1, ());
global_noop!(ZwpLinuxDmabufV1);
global_noop!(ZwpRelativePointerManagerV1);
global_noop!(WpLinuxDrmSyncobjManagerV1);
struct HandlerData;
impl ObjectData<State> for HandlerData {
fn request(
self: Arc<Self>,
_: &wayland_server::backend::Handle,
_: &mut State,
_: wayland_server::backend::ClientId,
_: wayland_server::backend::protocol::Message<
wayland_server::backend::ObjectId,
OwnedFd,
>,
) -> Option<Arc<dyn ObjectData<State>>> {
None
}
fn destroyed(
self: Arc<Self>,
_: &wayland_server::backend::Handle,
_: &mut State,
_: wayland_server::backend::ClientId,
_: wayland_server::backend::ObjectId,
) {
}
}
struct Handler;
impl GlobalHandler<State> for Handler {
fn bind(
self: Arc<Self>,
_: &wayland_server::backend::Handle,
_: &mut State,
_: wayland_server::backend::ClientId,
_: wayland_server::backend::GlobalId,
_: wayland_server::backend::ObjectId,
) -> Arc<dyn wayland_server::backend::ObjectData<State>> {
Arc::new(HandlerData)
}
}
// Simulate interface with higher interface than supported client side
static IF: OnceLock<Interface> = 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,
decorations_global,
}
}
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<SurfaceId> {
self.state.last_surface_id
}
pub fn created_surfaces(&self) -> &[SurfaceId] {
&self.state.created_surfaces
}
/// Finish the initialization of an output created by `new_output`.
/// This function must be called after the globals have been dispatched in order to use the
/// output on the server side created by `new_output` (this function's return value).
#[track_caller]
pub fn finalize_output(&mut self) -> WlOutput {
let output_s = self.state.last_output.take().expect("No new outputs");
let output_data = self.state.outputs.get_mut(&output_s).unwrap();
output_data.global_id = self.state.last_output_global.take();
output_s
}
pub fn get_object<T: Resource + 'static>(
&self,
id: SurfaceId,
) -> Result<T, wayland_server::backend::InvalidId> {
let client = self.client.as_ref().unwrap();
client.object_from_protocol_id::<T>(&self.display.handle(), id.0)
}
#[track_caller]
pub fn configure_toplevel(
&mut self,
surface_id: SurfaceId,
width: i32,
height: i32,
states: Vec<xdg_toplevel::State>,
) {
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<SurfaceId> {
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().map(|p| &p.pointer).unwrap()
}
#[track_caller]
pub fn locked_pointer(&self) -> Option<&LockedPointer> {
self.state.pointer.as_ref().unwrap().locked.as_ref()
}
#[track_caller]
pub fn data_source_mimes(&self) -> Vec<String> {
let Some(selection) = &self.state.clipboard else {
panic!("No selection set on data device");
};
let data: &Mutex<DataSourceData> = selection.data().unwrap();
let data = data.lock().unwrap();
data.mimes.to_vec()
}
#[track_caller]
pub fn primary_source_mimes(&self) -> Vec<String> {
let Some(selection) = &self.state.primary else {
panic!("No selection set on primary device");
};
let data: &Mutex<DataSourceData> = selection.data().unwrap();
let data = data.lock().unwrap();
data.mimes.to_vec()
}
fn paste_impl(
&mut self,
data: &Mutex<DataSourceData>,
mut send_data_for_mime: impl SendDataForMimeFn,
mut send_selection: impl FnMut(String, std::os::unix::io::BorrowedFd),
) -> Vec<PasteData> {
struct PendingData {
rx: std::fs::File,
data: Vec<u8>,
}
type PendingRet = Vec<(String, Option<PendingData>)>;
let mut pending_ret: PendingRet = {
data.lock()
.unwrap()
.mimes
.iter()
.rev()
.map(|mime| (mime.clone(), None))
.collect()
};
let mut ret = Vec::new();
while let Some((mime, pending)) = pending_ret.pop() {
let mut pending = pending.unwrap_or_else(|| {
let (rx, tx) = rustix::pipe::pipe().unwrap();
send_selection(mime.clone(), tx.as_fd());
drop(tx);
let rx = std::fs::File::from(rx);
PendingData {
rx,
data: Vec::new(),
}
});
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();
}
}
ret
}
#[track_caller]
pub fn clipboard_paste_data(
&mut self,
send_data_for_mime: impl SendDataForMimeFn,
) -> Vec<PasteData> {
let Some(selection) = self.state.clipboard.take() else {
panic!("No selection set on data device");
};
let ret = self.paste_impl(
selection.data().unwrap(),
send_data_for_mime,
|mime_type, fd| selection.send(mime_type, fd),
);
self.state.clipboard = Some(selection);
ret
}
#[track_caller]
pub fn primary_paste_data(
&mut self,
send_data_for_mime: impl SendDataForMimeFn,
) -> Vec<PasteData> {
let Some(selection) = self.state.primary.take() else {
panic!("No selection set on primary data device");
};
let ret = self.paste_impl(
selection.data().unwrap(),
send_data_for_mime,
|mime_type, fd| selection.send(mime_type, fd),
);
self.state.primary = Some(selection);
ret
}
pub fn data_source_exists(&self) -> bool {
self.state.clipboard.is_none()
}
#[track_caller]
pub fn create_data_offer(&mut self, data: Vec<PasteData>) {
let Some(dev) = &self.state.data_device else {
panic!("No data device created");
};
if let Some(selection) = self.state.clipboard.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 create_primary_offer(&mut self, data: Vec<PasteData>) {
let Some(dev) = &self.state.primary_device else {
panic!("No primary device created");
};
if let Some(selection) = self.state.primary.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, 1, 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 empty_data_offer(&mut self) {
let Some(dev) = &self.state.data_device else {
panic!("No data device created");
};
if let Some(selection) = self.state.clipboard.take() {
selection.cancelled();
}
dev.selection(None);
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.pointer.enter(24, &data.surface, x, y);
pointer.pointer.frame();
self.display.flush_clients().unwrap();
}
pub fn new_output(&mut self, x: i32, y: i32) {
self.state.last_output_global =
Some(self.dh.create_global::<State, WlOutput, _>(4, (x, y)));
self.display.flush_clients().unwrap();
}
pub fn get_output(&mut self, name: &str) -> Option<WlOutput> {
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 remove_output(&mut self, output: WlOutput) {
let output = self.state.outputs.remove(&output).unwrap();
self.dh.remove_global::<State>(output.global_id.unwrap());
self.display.flush_clients().unwrap();
}
pub fn enable_xdg_output_manager(&mut self) {
self.dh
.create_global::<State, ZxdgOutputManagerV1, _>(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::<State, WpFractionalScaleManagerV1, _>(1, ());
self.display.flush_clients().unwrap();
}
#[track_caller]
pub fn touch(&mut self) -> &WlTouch {
self.state.touch.as_ref().expect("No touch object created")
}
#[track_caller]
pub fn tablet_tool(&mut self) -> &ZwpTabletToolV2 {
self.state
.tablet_tool
.as_ref()
.expect("No tablet tool created")
}
#[track_caller]
pub fn tablet(&mut self) -> &ZwpTabletV2 {
self.state.tablet.as_ref().expect("No tablet created")
}
pub fn force_decoration_mode(
&mut self,
surface: SurfaceId,
mode: zxdg_toplevel_decoration_v1::Mode,
) {
let toplevel = self.state.get_toplevel(surface);
toplevel
.decoration
.as_mut()
.expect("Missing toplevel decoration")
.0
.configure(mode);
self.display.flush_clients().unwrap();
}
pub fn disable_decorations_global(&self) {
self.display
.handle()
.remove_global::<State>(self.decorations_global.clone());
}
#[track_caller]
pub fn get_buffer_dimensions(&self, buffer: &WlBuffer) -> Vec2 {
*self
.state
.buffers
.get(buffer)
.expect("buffer does not exist!")
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct PasteData {
pub mime_type: String,
pub data: Vec<u8>,
}
#[rustversion::since(1.87)]
pub struct TransferFd(PipeWriter);
#[rustversion::since(1.87)]
impl From<OwnedFd> for TransferFd {
fn from(value: OwnedFd) -> Self {
Self(PipeWriter::from(value))
}
}
#[rustversion::before(1.87)]
pub struct TransferFd(UnixStream);
#[rustversion::before(1.87)]
impl From<OwnedFd> for TransferFd {
fn from(value: OwnedFd) -> Self {
Self(UnixStream::from(value))
}
}
impl Write for TransferFd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
simple_global_dispatch!(WlShm);
simple_global_dispatch!(WlCompositor);
simple_global_dispatch!(WlSubcompositor);
simple_global_dispatch!(XdgWmBase);
simple_global_dispatch!(ZxdgOutputManagerV1);
simple_global_dispatch!(ZwpTabletManagerV2);
simple_global_dispatch!(ZxdgDecorationManagerV1);
simple_global_dispatch!(WpViewporter);
simple_global_dispatch!(WpFractionalScaleManagerV1);
simple_global_dispatch!(ZwpPointerConstraintsV1);
impl Dispatch<ZwpTabletManagerV2, ()> for State {
fn request(
state: &mut Self,
client: &Client,
_: &ZwpTabletManagerV2,
request: <ZwpTabletManagerV2 as Resource>::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();
state.tablet = Some(tablet);
let tool = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap();
seat.tool_added(&tool);
tool._type(zwp_tablet_tool_v2::Type::Finger);
tool.done();
state.tablet_tool = Some(tool);
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<ZxdgOutputManagerV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZxdgOutputManagerV1,
request: <ZxdgOutputManagerV1 as Resource>::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<ZxdgOutputV1, WlOutput> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZxdgOutputV1,
request: <ZxdgOutputV1 as Resource>::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<WlOutput, (i32, i32)> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WlOutput>,
&(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,
);
state.output_counter += 1;
let name = format!("WL-{}", state.output_counter);
output.name(name.clone());
output.mode(wl_output::Mode::Current, 1000, 1000, 0);
output.done();
state.outputs.insert(
output.clone(),
Output {
name,
xdg: None,
global_id: None,
},
);
state.last_output = Some(output);
}
}
impl Dispatch<WlOutput, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WlOutput,
_: <WlOutput as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
unreachable!();
}
}
impl GlobalDispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<ZwpPrimarySelectionDeviceManagerV1>,
_: &(),
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
state.primary_man = Some(data_init.init(resource, ()));
}
}
impl Dispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionDeviceManagerV1,
request: <ZwpPrimarySelectionDeviceManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_manager_v1::Request;
match request {
Request::CreateSource { id } => {
data_init.init(id, DataSourceData::default().into());
}
Request::GetDevice { id, seat } => {
state.primary_device = Some(data_init.init(id, seat));
}
Request::Destroy => {
state.primary_man = None;
}
_ => todo!("{request:?}"),
}
}
}
impl Dispatch<ZwpPrimarySelectionOfferV1, Vec<PasteData>> for State {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionOfferV1,
request: <ZwpPrimarySelectionOfferV1 as Resource>::Request,
data: &Vec<PasteData>,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::Request;
match request {
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}"));
TransferFd::from(fd).write_all(&data[pos].data).unwrap();
}
Request::Destroy => {}
other => todo!("{other:?}"),
}
}
}
impl Dispatch<ZwpPrimarySelectionSourceV1, Mutex<DataSourceData>> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionSourceV1,
request: <ZwpPrimarySelectionSourceV1 as Resource>::Request,
data: &Mutex<DataSourceData>,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::Request;
match request {
Request::Offer { mime_type } => {
data.lock().unwrap().mimes.push(mime_type);
}
Request::Destroy => {
state.primary = None;
}
_ => todo!("{request:?}"),
}
}
}
impl Dispatch<ZwpPrimarySelectionDeviceV1, WlSeat> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionDeviceV1,
request: <ZwpPrimarySelectionDeviceV1 as Resource>::Request,
_: &WlSeat,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::Request;
match request {
Request::SetSelection { source, .. } => {
state.primary = source;
}
Request::Destroy => {
state.primary_device = None;
}
other => todo!("unhandled request {other:?}"),
}
}
}
impl GlobalDispatch<WlDataDeviceManager, ()> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WlDataDeviceManager>,
_: &(),
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
state.data_device_man = Some(data_init.init(resource, ()));
}
}
impl Dispatch<WlDataOffer, Vec<PasteData>> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WlDataOffer,
request: <WlDataOffer as Resource>::Request,
data: &Vec<PasteData>,
_: &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}"));
TransferFd::from(fd).write_all(&data[pos].data).unwrap();
}
wl_data_offer::Request::Destroy => {}
other => todo!("unhandled request: {other:?}"),
}
}
}
impl Dispatch<WlDataSource, Mutex<DataSourceData>> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataSource,
request: <WlDataSource as Resource>::Request,
data: &Mutex<DataSourceData>,
_: &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.clipboard = None;
}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<WlDataDevice, WlSeat> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataDevice,
request: <WlDataDevice as Resource>::Request,
_: &WlSeat,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wl_data_device::Request::SetSelection { source, .. } => {
state.clipboard = source;
}
wl_data_device::Request::Release => {
state.data_device = None;
}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<WlDataDeviceManager, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataDeviceManager,
request: <WlDataDeviceManager as Resource>::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<WlSeat, ()> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WlSeat>,
_: &(),
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<WlSeat, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlSeat,
request: <WlSeat as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wl_seat::Request::GetPointer { id } => {
state.pointer = Some(PointerState {
pointer: data_init.init(id, ()),
locked: None,
});
}
wl_seat::Request::GetKeyboard { id } => {
state.keyboard = Some(KeyboardState {
keyboard: data_init.init(id, ()),
current_focus: None,
});
}
wl_seat::Request::GetTouch { id } => {
state.touch = Some(data_init.init(id, ()));
}
wl_seat::Request::Release => {}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<WlPointer, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlPointer,
request: <WlPointer as Resource>::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::from(&surface)).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<WlKeyboard, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wl_keyboard::Request::Release => {}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<WlTouch, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WlTouch,
request: <WlTouch as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wl_touch::Request::Release => {}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<XdgPopup, SurfaceId> for State {
fn request(
state: &mut Self,
_: &Client,
_: &XdgPopup,
request: <XdgPopup as Resource>::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<XdgToplevel, SurfaceId> for State {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &XdgToplevel,
request: <XdgToplevel as Resource>::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;
}
xdg_toplevel::Request::Move { seat: _, serial: _ } => {
let data = state.surfaces.get_mut(surface_id).unwrap();
data.moving = true;
}
xdg_toplevel::Request::Resize {
seat: _,
serial: _,
edges,
} => {
let data = state.surfaces.get_mut(surface_id).unwrap();
let WEnum::Value(edge) = edges else {
unreachable!()
};
data.resizing = Some(edge);
}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<XdgSurface, SurfaceId> for State {
fn request(
state: &mut Self,
client: &wayland_server::Client,
resource: &XdgSurface,
request: <XdgSurface as Resource>::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_positioner::Error::InvalidInput.into(),
object_id: positioner.id().protocol_id(),
object_interface: XdgPositioner::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!(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(),
_ => unreachable!(),
};
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<Vec2>,
pub anchor_rect: Option<Rect>,
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<XdgPositioner, ()> for State {
fn request(
state: &mut Self,
client: &Client,
resource: &XdgPositioner,
request: <XdgPositioner as Resource>::Request,
_: &(),
handle: &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 } => {
if width <= 0 || height <= 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 {width}x{height}");
eprintln!("{message}");
client.kill(
handle,
ProtocolError {
code: xdg_positioner::Error::InvalidInput.into(),
object_id: resource.id().protocol_id(),
object_interface: XdgPositioner::interface().name.to_string(),
message,
},
);
return;
}
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();
}
xdg_positioner::Request::SetConstraintAdjustment { .. } => {}
other => todo!("unhandled positioner request {other:?}"),
}
}
}
impl Dispatch<XdgWmBase, ()> for State {
fn request(
state: &mut Self,
client: &wayland_server::Client,
_: &XdgWmBase,
request: <XdgWmBase as Resource>::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<WlShm, ()> for State {
fn request(
_: &mut Self,
_: &wayland_server::Client,
_: &WlShm,
request: <WlShm as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
proto::wl_shm::Request::CreatePool { id, .. } => {
data_init.init(id, ());
}
_ => unreachable!(),
}
}
}
impl Dispatch<WlShmPool, ()> for State {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlShmPool,
request: <WlShmPool as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use proto::wl_shm_pool::Request::*;
match request {
CreateBuffer {
id, width, height, ..
} => {
let buf = data_init.init(id, ());
state.buffers.insert(
buf,
Vec2 {
x: width,
y: height,
},
);
}
Resize { .. } => {}
Destroy => {}
other => todo!("unhandled request {other:?}"),
}
}
}
impl Dispatch<WlBuffer, ()> for State {
fn request(
state: &mut Self,
_: &wayland_server::Client,
resource: &WlBuffer,
request: <WlBuffer as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
proto::wl_buffer::Request::Destroy => {
state.buffers.remove(resource);
}
_ => unreachable!(),
}
}
}
impl Dispatch<WlCompositor, ()> for State {
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &WlCompositor,
request: <WlCompositor as wayland_server::Resource>::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,
moving: false,
resizing: None,
},
);
state.last_surface_id = Some(SurfaceId(id));
state.created_surfaces.push(SurfaceId(id));
}
_ => unreachable!(),
}
}
}
impl Dispatch<WlSubcompositor, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlSubcompositor,
request: <WlSubcompositor as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use proto::wl_subcompositor::Request::*;
match request {
GetSubsurface {
id,
surface,
parent,
} => {
let surface_id = SurfaceId::from(&surface);
let data = state.surfaces.get_mut(&surface_id).unwrap();
data.role = Some(SurfaceRole::Subsurface(Subsurface {
parent: SurfaceId::from(&parent),
subsurface: data_init.init(id, surface_id),
position: Vec2::default(),
}));
}
Destroy => {}
other => todo!("unhandled subcompositor request {other:?}"),
}
}
}
impl Dispatch<WlSurface, ()> for State {
fn request(
state: &mut Self,
_: &wayland_server::Client,
resource: &WlSurface,
request: <WlSurface as Resource>::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!("{resource:?} missing from surface map"));
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<WlSubsurface, SurfaceId> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WlSubsurface,
request: <WlSubsurface as Resource>::Request,
surface_id: &SurfaceId,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
use proto::wl_subsurface::Request::*;
match request {
SetPosition { x, y } => {
let data = state.surfaces.get_mut(surface_id).unwrap();
let Some(SurfaceRole::Subsurface(subsurface)) = &mut data.role else {
unreachable!();
};
subsurface.position = Vec2 { x, y };
}
SetDesync | Destroy => {}
other => todo!("unhandled wl_subsurface request: {other:?}"),
}
}
}
impl Dispatch<WlCallback, ()> for State {
fn request(
_: &mut Self,
_: &wayland_server::Client,
_: &WlCallback,
_: <WlCallback as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
unreachable!()
}
}
impl GlobalDispatch<XdgActivationV1, ()> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<XdgActivationV1>,
_: &(),
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
state.xdg_activation = Some(data_init.init(resource, ()));
}
}
impl Dispatch<XdgActivationV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &XdgActivationV1,
request: <XdgActivationV1 as Resource>::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<XdgActivationTokenV1, Mutex<ActivationTokenData>> for State {
fn request(
state: &mut Self,
_: &Client,
token: &XdgActivationTokenV1,
request: <XdgActivationTokenV1 as Resource>::Request,
data: &Mutex<ActivationTokenData>,
_: &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<ZxdgDecorationManagerV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
resource: &ZxdgDecorationManagerV1,
request: <ZxdgDecorationManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => {
let surface_id = *toplevel.data::<SurfaceId>().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<ZxdgToplevelDecorationV1, SurfaceId> for State {
fn request(
state: &mut Self,
_: &Client,
resource: &ZxdgToplevelDecorationV1,
request: <ZxdgToplevelDecorationV1 as Resource>::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);
resource.configure(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<WpViewporter, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WpViewporter,
request: <WpViewporter as Resource>::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<WpViewport, SurfaceId> for State {
fn request(
state: &mut Self,
_: &Client,
viewport: &WpViewport,
request: <WpViewport as Resource>::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<WpFractionalScaleManagerV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WpFractionalScaleManagerV1,
request: <WpFractionalScaleManagerV1 as Resource>::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<WpFractionalScaleV1, SurfaceId> for State {
fn request(
state: &mut Self,
_: &Client,
_: &WpFractionalScaleV1,
request: <WpFractionalScaleV1 as Resource>::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!(),
}
}
}
impl Dispatch<ZwpPointerConstraintsV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPointerConstraintsV1,
request: <ZwpPointerConstraintsV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
zwp_pointer_constraints_v1::Request::LockPointer {
id,
surface,
pointer,
region: _,
lifetime: _,
} => {
let pointer_state = state.pointer.as_mut().unwrap();
assert_eq!(pointer, pointer_state.pointer);
let surface_id = SurfaceId::from(&surface);
assert!(pointer_state.locked.is_none());
data_init.init(id, ());
pointer_state.locked = Some(LockedPointer {
surface: surface_id,
cursor_hint: None,
});
}
_ => todo!("{request:?}"),
}
}
}
impl Dispatch<ZwpLockedPointerV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpLockedPointerV1,
request: <ZwpLockedPointerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
zwp_locked_pointer_v1::Request::SetCursorPositionHint {
surface_x,
surface_y,
} => {
let state = state.pointer.as_mut().unwrap();
let lock = state.locked.as_mut().unwrap();
lock.cursor_hint = Some(Vec2f {
x: surface_x,
y: surface_y,
});
}
_ => todo!("{request:?}"),
}
}
}