Initial commit

This commit is contained in:
Shawn Wallace 2024-04-29 00:27:22 -04:00
commit 85b940e427
21 changed files with 6825 additions and 0 deletions

839
testwl/src/lib.rs Normal file
View file

@ -0,0 +1,839 @@
use std::collections::{hash_map, HashMap, HashSet};
use std::os::fd::BorrowedFd;
use std::os::unix::net::UnixStream;
use std::time::Instant;
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
viewporter::server::wp_viewporter::WpViewporter,
},
xdg::{
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::ZxdgOutputManagerV1,
},
};
use wayland_server::{
backend::protocol::ProtocolError,
protocol::{
self as proto,
wl_buffer::WlBuffer,
wl_callback::WlCallback,
wl_compositor::WlCompositor,
wl_output::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,
};
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 SurfaceData {
pub surface: WlSurface,
pub buffer: Option<WlBuffer>,
pub last_damage: Option<BufferDamage>,
pub role: Option<SurfaceRole>,
}
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 min_size: Option<Vec2>,
pub max_size: Option<Vec2>,
pub states: Vec<xdg_toplevel::State>,
pub closed: bool,
}
#[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);
struct State {
surfaces: HashMap<SurfaceId, SurfaceData>,
positioners: HashMap<PositionerId, PositionerState>,
buffers: HashSet<WlBuffer>,
begin: Instant,
last_surface_id: Option<SurfaceId>,
callbacks: Vec<WlCallback>,
pointer: Option<WlPointer>,
configure_serial: u32,
}
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 = 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 get_toplevel(&mut self, surface_id: SurfaceId) -> &mut Toplevel {
let surface = self.surfaces.get_mut(&surface_id).unwrap();
match &mut surface.role {
Some(SurfaceRole::Toplevel(t)) => t,
other => panic!("Surface does not have toplevel role: {:?}", other),
}
}
}
impl Default for State {
fn default() -> Self {
Self {
surfaces: Default::default(),
buffers: Default::default(),
positioners: Default::default(),
begin: Instant::now(),
last_surface_id: None,
callbacks: Vec::new(),
pointer: None,
configure_serial: 0,
}
}
}
macro_rules! simple_global_dispatch {
($type:ty) => {
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>,
configure_serial: u32,
}
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);
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, WlShm, _>(1, ());
dh.create_global::<State, XdgWmBase, _>(6, ());
dh.create_global::<State, WlSeat, _>(5, ());
global_noop!(WlOutput);
global_noop!(ZwpLinuxDmabufV1);
global_noop!(ZwpRelativePointerManagerV1);
global_noop!(ZxdgOutputManagerV1);
global_noop!(WpViewporter);
global_noop!(WlDrm);
Self {
display,
dh,
state: State::default(),
client: None,
configure_serial: 1,
}
}
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"
);
//self.dispatch();
}
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 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 configure_popup(&mut self, surface_id: SurfaceId) {
let surface = self.state.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;
self.dispatch();
}
#[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()
}
}
simple_global_dispatch!(WlShm);
simple_global_dispatch!(WlCompositor);
simple_global_dispatch!(XdgWmBase);
impl GlobalDispatch<WlSeat, ()> for State {
fn bind(
_: &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);
}
}
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(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(surface.id().protocol_id()))
.unwrap();
assert!(
data.role.replace(SurfaceRole::Cursor).is_none(),
"Surface already had a role!"
);
}
}
wl_pointer::Request::Release => {
state.pointer.take();
}
other => todo!("unhandled pointer request: {other:?}"),
}
}
}
impl Dispatch<XdgPopup, SurfaceId> for State {
fn request(
_: &mut Self,
_: &Client,
_: &XdgPopup,
request: <XdgPopup as Resource>::Request,
_: &SurfaceId,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
xdg_popup::Request::Destroy => {}
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 => {}
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,
min_size: None,
max_size: None,
states: Vec::new(),
closed: false,
};
let data = state.surfaces.get_mut(surface_id).unwrap();
data.role = Some(SurfaceRole::Toplevel(t));
}
xdg_surface::Request::GetPopup {
id,
parent,
positioner,
} => {
let popup = data_init.init(id, *surface_id);
let p = Popup {
xdg: XdgSurfaceData::new(resource.clone()),
popup,
parent: parent.unwrap(),
positioner_state: state.positioners
[&PositionerId(positioner.id().protocol_id())]
.clone(),
};
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<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,
resource: &XdgPositioner,
request: <XdgPositioner as Resource>::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<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, .. } => {
let buf = data_init.init(id, ());
state.buffers.insert(buf);
}
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,
},
);
state.last_surface_id = Some(SurfaceId(id));
}
_ => unreachable!(),
}
}
}
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!("{:?} 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 => {
state
.surfaces
.remove(&SurfaceId(resource.id().protocol_id()));
}
other => todo!("unhandled 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!()
}
}