Initial commit
This commit is contained in:
commit
85b940e427
21 changed files with 6825 additions and 0 deletions
867
satellite/src/server/dispatch.rs
Normal file
867
satellite/src/server/dispatch.rs
Normal file
|
|
@ -0,0 +1,867 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManClient,
|
||||
server::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
},
|
||||
viewporter::{client as c_vp, server as s_vp},
|
||||
},
|
||||
xdg::xdg_output::zv1::{
|
||||
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
|
||||
server::{
|
||||
zxdg_output_manager_v1::{
|
||||
self as s_output_man, ZxdgOutputManagerV1 as OutputManServer,
|
||||
},
|
||||
zxdg_output_v1::{self as s_xdgo, ZxdgOutputV1 as XdgOutputServer},
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
|
||||
},
|
||||
Dispatch, DisplayHandle, GlobalDispatch, Resource,
|
||||
};
|
||||
|
||||
// noop
|
||||
impl<C: XConnection> Dispatch<WlCallback, ()> for ServerState<C> {
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCallback,
|
||||
_: <WlCallback as Resource>::Request,
|
||||
_: &(),
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSurface, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSurface,
|
||||
request: <WlSurface as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let surface: &SurfaceData = state.objects[*key].as_ref();
|
||||
let configured =
|
||||
surface.role.is_none() || surface.xdg().is_none() || surface.xdg().unwrap().configured;
|
||||
|
||||
match request {
|
||||
Request::<WlSurface>::Attach { buffer, x, y } => {
|
||||
if buffer.is_none() {
|
||||
trace!("xwayland attached null buffer to {:?}", surface.client);
|
||||
}
|
||||
let buffer = buffer.as_ref().map(|b| {
|
||||
let key: &ObjectKey = b.data().unwrap();
|
||||
let data: &Buffer = state.objects[*key].as_ref();
|
||||
&data.client
|
||||
});
|
||||
|
||||
if configured {
|
||||
surface.client.attach(buffer, x, y);
|
||||
} else {
|
||||
let buffer = buffer.cloned();
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.attach = Some(SurfaceAttach { buffer, x, y });
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::DamageBuffer {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
if configured {
|
||||
surface.client.damage_buffer(x, y, width, height);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Frame { callback } => {
|
||||
let cb = data_init.init(callback, ());
|
||||
if configured {
|
||||
surface.client.frame(&state.qh, cb);
|
||||
} else {
|
||||
let surface: &mut SurfaceData = state.objects[*key].as_mut();
|
||||
surface.frame_callback = Some(cb);
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Commit => {
|
||||
if configured {
|
||||
surface.client.commit();
|
||||
}
|
||||
}
|
||||
Request::<WlSurface>::Destroy => {
|
||||
let mut object = state.objects.remove(*key).unwrap();
|
||||
let surface: &mut SurfaceData = object.as_mut();
|
||||
surface.destroy_role();
|
||||
surface.client.destroy();
|
||||
debug!("deleting key: {:?}", key);
|
||||
}
|
||||
Request::<WlSurface>::SetBufferScale { scale } => {
|
||||
surface.client.set_buffer_scale(scale);
|
||||
}
|
||||
other => warn!("unhandled surface request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<WlCompositor, ClientGlobalWrapper<client::wl_compositor::WlCompositor>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlCompositor,
|
||||
request: <WlCompositor as wayland_server::Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_compositor::WlCompositor>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlCompositor>::CreateSurface { id } => {
|
||||
let mut surface_id = None;
|
||||
|
||||
let key = state.objects.insert_with_key(|key| {
|
||||
debug!("new surface with key {key:?}");
|
||||
let client = client.create_surface(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
surface_id = Some(server.id().protocol_id());
|
||||
|
||||
SurfaceData {
|
||||
client,
|
||||
server,
|
||||
key,
|
||||
attach: None,
|
||||
frame_callback: None,
|
||||
role: None,
|
||||
}
|
||||
.into()
|
||||
});
|
||||
|
||||
let surface_id = surface_id.unwrap();
|
||||
|
||||
if let Some((win, window_data)) =
|
||||
state.windows.iter_mut().find_map(|(win, data)| {
|
||||
Some(*win).zip((data.surface_id == surface_id).then_some(data))
|
||||
})
|
||||
{
|
||||
window_data.surface_key = Some(key);
|
||||
state.associated_windows.insert(key, win);
|
||||
debug!("associate surface {surface_id} with window {win:?}");
|
||||
if window_data.mapped {
|
||||
state.create_role_window(win, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled wlcompositor request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlBuffer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlBuffer,
|
||||
request: <WlBuffer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
assert!(matches!(request, Request::<WlBuffer>::Destroy));
|
||||
|
||||
let buf: &Buffer = state.objects[*key].as_ref();
|
||||
buf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShmPool, ClientShmPool> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShmPool,
|
||||
request: <WlShmPool as Resource>::Request,
|
||||
c_pool: &ClientShmPool,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShmPool>::CreateBuffer {
|
||||
id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_pool.pool.create_buffer(
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
convert_wenum(format),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Request::<WlShmPool>::Destroy => {
|
||||
c_pool.pool.destroy();
|
||||
}
|
||||
other => warn!("unhandled shmpool request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlShm, ClientGlobalWrapper<client::wl_shm::WlShm>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlShm,
|
||||
request: <WlShm as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<client::wl_shm::WlShm>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlShm>::CreatePool { id, fd, size } => {
|
||||
let c_pool = client.create_pool(fd.as_fd(), size, &state.qh, ());
|
||||
let c_pool = ClientShmPool { pool: c_pool, fd };
|
||||
data_init.init(id, c_pool);
|
||||
}
|
||||
other => {
|
||||
warn!("unhandled shm pool request: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlPointer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlPointer,
|
||||
request: <WlPointer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let Pointer {
|
||||
client: c_pointer, ..
|
||||
}: &Pointer = state.objects[*key].as_ref();
|
||||
|
||||
match request {
|
||||
Request::<WlPointer>::SetCursor {
|
||||
serial,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
surface,
|
||||
} => {
|
||||
let c_surface = surface.map(|s| state.get_client_surface_from_server(s));
|
||||
c_pointer.set_cursor(serial, c_surface, hotspot_x, hotspot_y);
|
||||
}
|
||||
Request::<WlPointer>::Release => {
|
||||
c_pointer.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => warn!("unhandled cursor request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlKeyboard, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlKeyboard,
|
||||
request: <WlKeyboard as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlKeyboard>::Release => {
|
||||
let Keyboard { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlSeat,
|
||||
request: <WlSeat as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<WlSeat>::GetPointer { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_pointer(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
trace!("new pointer: {server:?}");
|
||||
Pointer::new(server, client).into()
|
||||
});
|
||||
}
|
||||
Request::<WlSeat>::GetKeyboard { id } => {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_keyboard(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
Keyboard { client, server }.into()
|
||||
});
|
||||
}
|
||||
other => warn!("unhandled seat request: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<RelativePointerServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerServer,
|
||||
request: <RelativePointerServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
if let Request::<RelativePointerServer>::Destroy = request {
|
||||
let obj: &RelativePointer = state.objects[*key].as_ref();
|
||||
obj.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<RelativePointerManServer, ClientGlobalWrapper<RelativePointerManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &RelativePointerManServer,
|
||||
request: <RelativePointerManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<RelativePointerManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
Request::<RelativePointerManServer>::GetRelativePointer { id, pointer } => {
|
||||
let p_key: ObjectKey = pointer.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([p_key], |[pointer_obj], key| {
|
||||
let pointer: &Pointer = pointer_obj.try_into().unwrap();
|
||||
let client = client.get_relative_pointer(&pointer.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
RelativePointer { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled relative pointer request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlOutput, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlOutput,
|
||||
request: <WlOutput as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wayland_server::protocol::wl_output::Request::Release => {
|
||||
let Output { client, .. }: &_ = state.objects[*key].as_ref();
|
||||
client.release();
|
||||
todo!("handle wloutput destruction");
|
||||
}
|
||||
_ => warn!("unhandled output request {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ObjectKey>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1 as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_feedback_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
let dmabuf: &DmabufFeedback = state.objects[*key].as_ref();
|
||||
dmabuf.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
request: <s_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 as Resource>::Request,
|
||||
c_params: &c_dmabuf::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_buffer_params_v1::Request::*;
|
||||
match request {
|
||||
// TODO: Xwayland doesn't actually seem to use the Create request, and I don't feel like implementing it...
|
||||
Create { .. } => todo!(),
|
||||
CreateImmed {
|
||||
buffer_id,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
flags,
|
||||
} => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = c_params.create_immed(
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
convert_wenum(flags),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let server = data_init.init(buffer_id, key);
|
||||
Buffer { server, client }.into()
|
||||
});
|
||||
}
|
||||
Add {
|
||||
fd,
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
} => {
|
||||
c_params.add(
|
||||
fd.as_fd(),
|
||||
plane_idx,
|
||||
offset,
|
||||
stride,
|
||||
modifier_hi,
|
||||
modifier_lo,
|
||||
);
|
||||
}
|
||||
Destroy => {
|
||||
c_params.destroy();
|
||||
}
|
||||
_ => warn!("unhandled params request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
request: <s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_dmabuf::zwp_linux_dmabuf_v1::Request::*;
|
||||
match request {
|
||||
Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
CreateParams { params_id } => {
|
||||
let c_params = client.create_params(&state.qh, ());
|
||||
data_init.init(params_id, c_params);
|
||||
}
|
||||
GetDefaultFeedback { id } => {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let client = client.get_default_feedback(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
GetSurfaceFeedback { id, surface } => {
|
||||
let surf_key: ObjectKey = surface.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([surf_key], |[surface_obj], key| {
|
||||
let SurfaceData {
|
||||
client: c_surface, ..
|
||||
}: &SurfaceData = surface_obj.try_into().unwrap();
|
||||
let client = client.get_surface_feedback(c_surface, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
DmabufFeedback { client, server }.into()
|
||||
});
|
||||
}
|
||||
_ => warn!("unhandled dmabuf request: {request:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<WlDrmServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &WlDrmServer,
|
||||
request: <WlDrmServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use wl_drm::server::wl_drm::Request::*;
|
||||
|
||||
type DrmFn = dyn FnOnce(
|
||||
&wl_drm::client::wl_drm::WlDrm,
|
||||
ObjectKey,
|
||||
&ClientQueueHandle,
|
||||
) -> client::wl_buffer::WlBuffer;
|
||||
|
||||
let mut bufs: Option<(Box<DrmFn>, wayland_server::New<WlBuffer>)> = None;
|
||||
match request {
|
||||
CreateBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_buffer(name, width, height, stride, format, qh, key)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePlanarBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_planar_buffer(
|
||||
name, width, height, format, offset0, stride0, offset1, stride1,
|
||||
offset2, stride2, qh, key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
CreatePrimeBuffer {
|
||||
id,
|
||||
name,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
} => {
|
||||
bufs = Some((
|
||||
Box::new(move |drm, key, qh| {
|
||||
drm.create_prime_buffer(
|
||||
name.as_fd(),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
offset0,
|
||||
stride0,
|
||||
offset1,
|
||||
stride1,
|
||||
offset2,
|
||||
stride2,
|
||||
qh,
|
||||
key,
|
||||
)
|
||||
}),
|
||||
id,
|
||||
));
|
||||
}
|
||||
Authenticate { id } => {
|
||||
let drm: &Drm = state.objects[*key].as_ref();
|
||||
drm.client.authenticate(id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
if let Some((buf_create, id)) = bufs {
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[drm_obj], key| {
|
||||
let drm: &Drm = drm_obj.try_into().unwrap();
|
||||
let client = buf_create(&drm.client, key, &state.qh);
|
||||
let server = data_init.init(id, key);
|
||||
Buffer { client, server }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<s_vp::wp_viewport::WpViewport, c_vp::wp_viewport::WpViewport>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
_: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewport::WpViewport,
|
||||
request: <s_vp::wp_viewport::WpViewport as Resource>::Request,
|
||||
c_viewport: &c_vp::wp_viewport::WpViewport,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
simple_event_shunt! {
|
||||
c_viewport, request: s_vp::wp_viewport::Request => [
|
||||
SetSource { x, y, width, height },
|
||||
SetDestination { width, height },
|
||||
Destroy
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection>
|
||||
Dispatch<
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
> for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &s_vp::wp_viewporter::WpViewporter,
|
||||
request: <s_vp::wp_viewporter::WpViewporter as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
use s_vp::wp_viewporter;
|
||||
match request {
|
||||
wp_viewporter::Request::GetViewport { id, surface } => {
|
||||
let c_surface = state.get_client_surface_from_server(surface);
|
||||
let c_viewport = client.get_viewport(c_surface, &state.qh, ());
|
||||
data_init.init(id, c_viewport);
|
||||
}
|
||||
wp_viewporter::Request::Destroy => {
|
||||
client.destroy();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &XdgOutputServer,
|
||||
request: <XdgOutputServer as Resource>::Request,
|
||||
key: &ObjectKey,
|
||||
_: &DisplayHandle,
|
||||
_: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let s_xdgo::Request::Destroy = request else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let output: &XdgOutput = state.objects[*key].as_ref();
|
||||
output.client.destroy();
|
||||
state.objects.remove(*key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: XConnection> Dispatch<OutputManServer, ClientGlobalWrapper<OutputManClient>>
|
||||
for ServerState<C>
|
||||
{
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_: &wayland_server::Client,
|
||||
_: &OutputManServer,
|
||||
request: <OutputManServer as Resource>::Request,
|
||||
client: &ClientGlobalWrapper<OutputManClient>,
|
||||
_: &DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
s_output_man::Request::GetXdgOutput { id, output } => {
|
||||
let output_key: ObjectKey = output.data().copied().unwrap();
|
||||
state
|
||||
.objects
|
||||
.insert_from_other_objects([output_key], |[output_obj], key| {
|
||||
let output: &Output = output_obj.try_into().unwrap();
|
||||
let client = client.get_xdg_output(&output.client, &state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
XdgOutput { server, client }.into()
|
||||
});
|
||||
}
|
||||
s_output_man::Request::Destroy => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientGlobalWrapper<T: Proxy>(Arc<OnceLock<T>>);
|
||||
impl<T: Proxy> std::ops::Deref for ClientGlobalWrapper<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for ClientGlobalWrapper<T> {
|
||||
fn default() -> Self {
|
||||
Self(Arc::default())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_no_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
ServerState<C>: Dispatch<$server, ClientGlobalWrapper<$client>>,
|
||||
Globals: wayland_client::Dispatch<$client, ()>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let client = ClientGlobalWrapper::<$client>::default();
|
||||
let server = data_init.init(resource, client.clone());
|
||||
client
|
||||
.0
|
||||
.set(
|
||||
state
|
||||
.clientside
|
||||
.registry
|
||||
.bind(data.name, server.version(), &state.qh, ()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! global_dispatch_with_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
where
|
||||
$server: Resource,
|
||||
$client: Proxy,
|
||||
ServerState<C>: Dispatch<$server, ObjectKey>,
|
||||
Globals: wayland_client::Dispatch<$client, ObjectKey>,
|
||||
GenericObject<$server, $client>: Into<Object>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let server = data_init.init(resource, key);
|
||||
let client = state.clientside.registry.bind::<$client, _, _>(
|
||||
data.name,
|
||||
server.version(),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
GenericObject { server, client }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
global_dispatch_no_events!(WlShm, client::wl_shm::WlShm);
|
||||
global_dispatch_no_events!(WlCompositor, client::wl_compositor::WlCompositor);
|
||||
global_dispatch_no_events!(RelativePointerManServer, RelativePointerManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
|
||||
);
|
||||
global_dispatch_no_events!(OutputManServer, OutputManClient);
|
||||
global_dispatch_no_events!(
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
c_vp::wp_viewporter::WpViewporter
|
||||
);
|
||||
|
||||
global_dispatch_with_events!(WlSeat, client::wl_seat::WlSeat);
|
||||
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput);
|
||||
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
||||
605
satellite/src/server/event.rs
Normal file
605
satellite/src/server/event.rs
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
use super::*;
|
||||
use log::{debug, trace, warn};
|
||||
use std::os::fd::AsFd;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1 as RelativePointerClient},
|
||||
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
||||
xdg_output::zv1::{
|
||||
client::zxdg_output_v1::{self, ZxdgOutputV1 as ClientXdgOutput},
|
||||
server::zxdg_output_v1::ZxdgOutputV1 as ServerXdgOutput,
|
||||
},
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol::{
|
||||
wl_buffer::WlBuffer, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer,
|
||||
wl_seat::WlSeat,
|
||||
};
|
||||
|
||||
/// Lord forgive me, I am a sinner, who's probably gonna sin again
|
||||
/// This macro takes an enum variant name and a list of the field names of the enum
|
||||
/// and/or closures that take an argument that must be named the same as the field name,
|
||||
/// and converts that into a destructured enum
|
||||
/// shunt_helper_enum!(Foo [a, |b| b.do_thing(), c]) -> Foo {a, b, c}
|
||||
macro_rules! shunt_helper_enum {
|
||||
// No fields
|
||||
($variant:ident) => { $variant };
|
||||
// Starting state: variant destructure
|
||||
($variant:ident $([$($body:tt)+])?) => {
|
||||
shunt_helper_enum!($variant [$($($body)+)?] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($variant:ident [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure field to list
|
||||
($variant:ident [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_enum!($variant [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Finalize into enum variant
|
||||
($variant:ident [] -> [,$($body:tt)+]) => { $variant { $($body)+ } };
|
||||
}
|
||||
|
||||
/// This does the same thing as shunt_helper_enum, except it transforms the fields into the given
|
||||
/// function/method call.
|
||||
/// shunt_helper_fn!({obj.foo} [a, |b| b.do_thing(), c]) -> obj.foo(a, b.do_thing(), c)
|
||||
macro_rules! shunt_helper_fn {
|
||||
// No fields
|
||||
({$($fn:tt)+}) => { $($fn)+() };
|
||||
// Starting state
|
||||
($fn:tt [$($body:tt)+]) => {
|
||||
shunt_helper_fn!($fn [$($body)+] -> [])
|
||||
};
|
||||
// Add field to list
|
||||
($fn:tt [$field:ident $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $field])
|
||||
};
|
||||
// Add closure expression to list
|
||||
($fn:tt [|$field:ident| $conv:expr $(, $($rest:tt)+)?] -> [$($body:tt)*]) => {
|
||||
shunt_helper_fn!($fn [$($($rest)+)?] -> [$($body)*, $conv])
|
||||
};
|
||||
// Finalize into function call
|
||||
({$($fn:tt)+} [] -> [,$($body:tt)+]) => { $($fn)+($($body)+) };
|
||||
}
|
||||
|
||||
/// Takes an object, the name of a variable holding an event, the event type, and a list of the
|
||||
/// variants with their fields, and converts them into function calls on their arguments
|
||||
/// Event { field1, field2 } => obj.event(field1, field2)
|
||||
macro_rules! simple_event_shunt {
|
||||
($obj:expr, $event:ident: $event_type:path => [
|
||||
$( $variant:ident $({ $($fields:tt)* })? ),+
|
||||
]) => {
|
||||
{
|
||||
use $event_type::*;
|
||||
match $event {
|
||||
$(
|
||||
shunt_helper_enum!( $variant $( [ $($fields)* ] )? ) => {
|
||||
paste::paste! {
|
||||
shunt_helper_fn!( { $obj.[<$variant:snake>] } $( [ $($fields)* ] )? )
|
||||
}
|
||||
}
|
||||
)+
|
||||
_ => log::warn!(concat!("unhandled", stringify!($event_type), ": {:?}"), $event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use shunt_helper_enum;
|
||||
pub(crate) use shunt_helper_fn;
|
||||
pub(crate) use simple_event_shunt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SurfaceEvents {
|
||||
WlSurface(client::wl_surface::Event),
|
||||
XdgSurface(xdg_surface::Event),
|
||||
Toplevel(xdg_toplevel::Event),
|
||||
Popup(xdg_popup::Event),
|
||||
}
|
||||
macro_rules! impl_from {
|
||||
($type:ty, $variant:ident) => {
|
||||
impl From<$type> for ObjectEvent {
|
||||
fn from(value: $type) -> Self {
|
||||
Self::Surface(SurfaceEvents::$variant(value))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from!(client::wl_surface::Event, WlSurface);
|
||||
impl_from!(xdg_surface::Event, XdgSurface);
|
||||
impl_from!(xdg_toplevel::Event, Toplevel);
|
||||
impl_from!(xdg_popup::Event, Popup);
|
||||
|
||||
impl HandleEvent for SurfaceData {
|
||||
type Event = SurfaceEvents;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match event {
|
||||
SurfaceEvents::WlSurface(event) => self.surface_event(event, state),
|
||||
SurfaceEvents::XdgSurface(event) => self.xdg_event(event, state),
|
||||
SurfaceEvents::Toplevel(event) => self.toplevel_event(event, state),
|
||||
SurfaceEvents::Popup(event) => self.popup_event(event, state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn surface_event<C: XConnection>(
|
||||
&self,
|
||||
event: client::wl_surface::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
let surface = &self.server;
|
||||
simple_event_shunt! {
|
||||
surface, event: client::wl_surface::Event => [
|
||||
Enter { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
Leave { |output| &state.get_object_from_client_object::<Output, _>(&output).server },
|
||||
PreferredBufferScale { factor }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_event<C: XConnection>(&mut self, event: xdg_surface::Event, state: &mut ServerState<C>) {
|
||||
let connection = state.connection.as_mut().unwrap();
|
||||
let xdg_surface::Event::Configure { serial } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let xdg = self.xdg_mut().unwrap();
|
||||
xdg.surface.ack_configure(serial);
|
||||
xdg.configured = true;
|
||||
|
||||
if let Some(pending) = xdg.pending.take() {
|
||||
let window = state.associated_windows[self.key];
|
||||
let window = state.windows.get_mut(&window).unwrap();
|
||||
let width = if pending.width > 0 {
|
||||
pending.width as _
|
||||
} else {
|
||||
window.dims.width
|
||||
};
|
||||
let height = if pending.height > 0 {
|
||||
pending.height as _
|
||||
} else {
|
||||
window.dims.height
|
||||
};
|
||||
debug!(
|
||||
"configuring {:?}: {}x{}, {width}x{height}",
|
||||
window.window, pending.x, pending.y
|
||||
);
|
||||
connection.set_window_dims(
|
||||
window.window,
|
||||
PendingSurfaceState {
|
||||
x: pending.x,
|
||||
y: pending.y,
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
},
|
||||
);
|
||||
window.dims = WindowDims {
|
||||
x: pending.x as _,
|
||||
y: pending.y as _,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
|
||||
self.client.attach(buffer.as_ref(), x, y);
|
||||
}
|
||||
if let Some(cb) = self.frame_callback.take() {
|
||||
self.client.frame(&state.qh, cb);
|
||||
}
|
||||
self.client.commit();
|
||||
}
|
||||
|
||||
fn toplevel_event<C: XConnection>(
|
||||
&mut self,
|
||||
event: xdg_toplevel::Event,
|
||||
state: &mut ServerState<C>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
states,
|
||||
} => {
|
||||
debug!("configuring toplevel {width}x{height}, {states:?}");
|
||||
let activated = states.contains(&(u32::from(xdg_toplevel::State::Activated) as u8));
|
||||
|
||||
if activated {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.to_focus = Some(window);
|
||||
}
|
||||
|
||||
if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
|
||||
let prev_fs = toplevel.fullscreen;
|
||||
toplevel.fullscreen =
|
||||
states.contains(&(u32::from(xdg_toplevel::State::Fullscreen) as u8));
|
||||
if toplevel.fullscreen != prev_fs {
|
||||
let window = state.associated_windows[self.key];
|
||||
let data = C::ExtraData::create(state);
|
||||
state.connection.as_mut().unwrap().set_fullscreen(
|
||||
window,
|
||||
toplevel.fullscreen,
|
||||
data,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
let window = state.associated_windows[self.key];
|
||||
state.close_x_window(window);
|
||||
}
|
||||
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn popup_event<C: XConnection>(&mut self, event: xdg_popup::Event, _: &mut ServerState<C>) {
|
||||
match event {
|
||||
xdg_popup::Event::Configure {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
trace!("popup configure: {x}x{y}, {width}x{height}");
|
||||
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
xdg_popup::Event::Repositioned { .. } => {}
|
||||
other => todo!("{other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GenericObject<Server: Resource, Client: Proxy> {
|
||||
pub server: Server,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
pub type Buffer = GenericObject<WlBuffer, client::wl_buffer::WlBuffer>;
|
||||
impl HandleEvent for Buffer {
|
||||
type Event = client::wl_buffer::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, _: Self::Event, _: &mut ServerState<C>) {
|
||||
// The only event from a buffer would be the release.
|
||||
self.server.release();
|
||||
}
|
||||
}
|
||||
|
||||
pub type XdgOutput = GenericObject<ServerXdgOutput, ClientXdgOutput>;
|
||||
impl HandleEvent for XdgOutput {
|
||||
type Event = zxdg_output_v1::Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: zxdg_output_v1::Event => [
|
||||
LogicalPosition { x, y },
|
||||
LogicalSize { width, height },
|
||||
Done,
|
||||
Name { name },
|
||||
Description { description }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Seat = GenericObject<WlSeat, client::wl_seat::WlSeat>;
|
||||
impl HandleEvent for Seat {
|
||||
type Event = client::wl_seat::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_seat::Event => [
|
||||
Capabilities { |capabilities| convert_wenum(capabilities) },
|
||||
Name { name }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pointer {
|
||||
server: WlPointer,
|
||||
pub client: client::wl_pointer::WlPointer,
|
||||
pending_enter: PendingEnter,
|
||||
}
|
||||
|
||||
impl Pointer {
|
||||
pub fn new(server: WlPointer, client: client::wl_pointer::WlPointer) -> Self {
|
||||
Self {
|
||||
server,
|
||||
client,
|
||||
pending_enter: PendingEnter(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingEnter(Option<client::wl_pointer::Event>);
|
||||
|
||||
impl HandleEvent for Pointer {
|
||||
type Event = client::wl_pointer::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
// Workaround GTK (stupidly) autoclosing popups if it receives an wl_pointer.enter
|
||||
// event shortly after creation.
|
||||
// When Niri creates a popup, it immediately sends wl_pointer.enter on the new surface,
|
||||
// generating an EnterNotify event, and Xwayland will send a release button event.
|
||||
// In its menu implementation, GTK treats EnterNotify "this menu is now active" and will
|
||||
// destroy the menu if this occurs within a 500 ms interval (which it always does with
|
||||
// Niri). Other compositors do not run into this problem because they appear to not send
|
||||
// wl_pointer.enter until the user actually moves the mouse in the popup.
|
||||
let mut process_event = Vec::new();
|
||||
match event {
|
||||
client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
ref surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
let do_enter = || {
|
||||
debug!("entering surface ({serial})");
|
||||
let surface = state.get_server_surface_from_client(surface.clone());
|
||||
self.server.enter(serial, surface, surface_x, surface_y);
|
||||
};
|
||||
let surface_key: ObjectKey = surface.data().copied().unwrap();
|
||||
let surface_data: &SurfaceData = state.objects[surface_key].as_ref();
|
||||
|
||||
if matches!(surface_data.role, Some(SurfaceRole::Popup(_))) {
|
||||
match self.pending_enter.0.take() {
|
||||
Some(e) => {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial: pending_serial,
|
||||
..
|
||||
} = e
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
if serial == pending_serial {
|
||||
do_enter();
|
||||
} else {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.pending_enter.0 = Some(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.pending_enter.0.take();
|
||||
do_enter();
|
||||
}
|
||||
}
|
||||
client::wl_pointer::Event::Leave { serial, surface } => {
|
||||
debug!("leaving surface ({serial})");
|
||||
self.pending_enter.0.take();
|
||||
self.server
|
||||
.leave(serial, state.get_server_surface_from_client(surface));
|
||||
}
|
||||
client::wl_pointer::Event::Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
if let Some(p) = &self.pending_enter.0 {
|
||||
let client::wl_pointer::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} = p
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
process_event.push(client::wl_pointer::Event::Enter {
|
||||
serial: *serial,
|
||||
surface: surface.clone(),
|
||||
surface_x: *surface_x,
|
||||
surface_y: *surface_y,
|
||||
});
|
||||
process_event.push(event);
|
||||
trace!("resending enter ({serial}) before motion");
|
||||
} else {
|
||||
self.server.motion(time, surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
_ => simple_event_shunt! {
|
||||
self.server, event: client::wl_pointer::Event => [
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Motion {
|
||||
time,
|
||||
surface_x,
|
||||
surface_y
|
||||
},
|
||||
Frame,
|
||||
Button {
|
||||
serial,
|
||||
time,
|
||||
button,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Axis {
|
||||
time,
|
||||
|axis| convert_wenum(axis),
|
||||
value
|
||||
},
|
||||
AxisSource {
|
||||
|axis_source| convert_wenum(axis_source)
|
||||
},
|
||||
AxisStop {
|
||||
time,
|
||||
|axis| convert_wenum(axis)
|
||||
},
|
||||
AxisDiscrete {
|
||||
|axis| convert_wenum(axis),
|
||||
discrete
|
||||
},
|
||||
AxisValue120 {
|
||||
|axis| convert_wenum(axis),
|
||||
value120
|
||||
},
|
||||
AxisRelativeDirection {
|
||||
|axis| convert_wenum(axis),
|
||||
|direction| convert_wenum(direction)
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
for event in process_event {
|
||||
self.handle_event(event, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
|
||||
impl HandleEvent for Keyboard {
|
||||
type Event = client::wl_keyboard::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|format| convert_wenum(format),
|
||||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
keys
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group
|
||||
},
|
||||
RepeatInfo {
|
||||
rate,
|
||||
delay
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Output = GenericObject<WlOutput, client::wl_output::WlOutput>;
|
||||
impl HandleEvent for Output {
|
||||
type Event = client::wl_output::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_output::Event => [
|
||||
Name { name },
|
||||
Description { description },
|
||||
Mode {
|
||||
|flags| convert_wenum(flags),
|
||||
width,
|
||||
height,
|
||||
refresh
|
||||
},
|
||||
Scale { factor },
|
||||
Geometry {
|
||||
x,
|
||||
y,
|
||||
physical_width,
|
||||
physical_height,
|
||||
|subpixel| convert_wenum(subpixel),
|
||||
make,
|
||||
model,
|
||||
|transform| convert_wenum(transform)
|
||||
},
|
||||
Done
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Drm = GenericObject<WlDrmServer, WlDrmClient>;
|
||||
impl HandleEvent for Drm {
|
||||
type Event = wl_drm::client::wl_drm::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: wl_drm::client::wl_drm::Event => [
|
||||
Device { name },
|
||||
Format { format },
|
||||
Authenticated,
|
||||
Capabilities { value }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DmabufFeedback = GenericObject<
|
||||
s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
c_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||
>;
|
||||
impl HandleEvent for DmabufFeedback {
|
||||
type Event = c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event => [
|
||||
Done,
|
||||
FormatTable { |fd| fd.as_fd(), size },
|
||||
MainDevice { device },
|
||||
TrancheDone,
|
||||
TrancheTargetDevice { device },
|
||||
TrancheFormats { indices },
|
||||
TrancheFlags { |flags| convert_wenum(flags) }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type RelativePointer = GenericObject<RelativePointerServer, RelativePointerClient>;
|
||||
impl HandleEvent for RelativePointer {
|
||||
type Event = zwp_relative_pointer_v1::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: rp::client::zwp_relative_pointer_v1::Event => [
|
||||
RelativeMotion {
|
||||
utime_hi,
|
||||
utime_lo,
|
||||
dx,
|
||||
dy,
|
||||
dx_unaccel,
|
||||
dy_unaccel
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
768
satellite/src/server/mod.rs
Normal file
768
satellite/src/server/mod.rs
Normal file
|
|
@ -0,0 +1,768 @@
|
|||
mod dispatch;
|
||||
mod event;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use self::event::*;
|
||||
use super::FromServerState;
|
||||
use crate::clientside::*;
|
||||
use crate::xstate::{Atoms, WindowDims, WmNormalHints};
|
||||
use crate::XConnection;
|
||||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
relative_pointer::zv1::{
|
||||
self as rp, server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
},
|
||||
viewporter::server as s_vp,
|
||||
},
|
||||
xdg::{
|
||||
shell::client::{
|
||||
xdg_popup::XdgPopup,
|
||||
xdg_positioner::{Anchor, Gravity, XdgPositioner},
|
||||
xdg_surface::XdgSurface,
|
||||
xdg_toplevel::XdgToplevel,
|
||||
xdg_wm_base::XdgWmBase,
|
||||
},
|
||||
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_surface::WlSurface,
|
||||
},
|
||||
DisplayHandle, Resource, WEnum,
|
||||
};
|
||||
use wl_drm::{client::wl_drm::WlDrm as WlDrmClient, server::wl_drm::WlDrm as WlDrmServer};
|
||||
use xcb::x;
|
||||
|
||||
impl From<&x::CreateNotifyEvent> for WindowDims {
|
||||
fn from(value: &x::CreateNotifyEvent) -> Self {
|
||||
Self {
|
||||
x: value.x(),
|
||||
y: value.y(),
|
||||
width: value.width(),
|
||||
height: value.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Request<T> = <T as Resource>::Request;
|
||||
|
||||
/// Converts a WEnum from its client side version to its server side version
|
||||
fn convert_wenum<Client, Server>(wenum: WEnum<Client>) -> Server
|
||||
where
|
||||
u32: From<WEnum<Client>>,
|
||||
Server: TryFrom<u32>,
|
||||
<Server as TryFrom<u32>>::Error: std::fmt::Debug,
|
||||
{
|
||||
u32::from(wenum).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
window: x::Window,
|
||||
surface_key: Option<ObjectKey>,
|
||||
mapped: bool,
|
||||
surface_id: u32,
|
||||
popup_for: Option<x::Window>,
|
||||
dims: WindowDims,
|
||||
hints: Option<WmNormalHints>,
|
||||
override_redirect: bool,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
surface_key: None,
|
||||
mapped: false,
|
||||
popup_for: parent,
|
||||
surface_id: 0,
|
||||
dims,
|
||||
hints: None,
|
||||
override_redirect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SurfaceAttach {
|
||||
buffer: Option<client::wl_buffer::WlBuffer>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
pub struct SurfaceData {
|
||||
client: client::wl_surface::WlSurface,
|
||||
server: WlSurface,
|
||||
key: ObjectKey,
|
||||
frame_callback: Option<WlCallback>,
|
||||
attach: Option<SurfaceAttach>,
|
||||
role: Option<SurfaceRole>,
|
||||
}
|
||||
|
||||
impl SurfaceData {
|
||||
fn xdg(&self) -> Option<&XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_ref()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref t) => t.as_ref().map(|t| &t.xdg),
|
||||
SurfaceRole::Popup(ref p) => p.as_ref().map(|p| &p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn xdg_mut(&mut self) -> Option<&mut XdgSurfaceData> {
|
||||
match self
|
||||
.role
|
||||
.as_mut()
|
||||
.expect("Tried to get XdgSurface for surface without role")
|
||||
{
|
||||
SurfaceRole::Toplevel(ref mut t) => t.as_mut().map(|t| &mut t.xdg),
|
||||
SurfaceRole::Popup(ref mut p) => p.as_mut().map(|p| &mut p.xdg),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_role(&mut self) {
|
||||
if let Some(role) = self.role.take() {
|
||||
match role {
|
||||
SurfaceRole::Toplevel(Some(t)) => {
|
||||
t.toplevel.destroy();
|
||||
t.xdg.surface.destroy();
|
||||
}
|
||||
SurfaceRole::Popup(Some(p)) => {
|
||||
p.positioner.destroy();
|
||||
p.popup.destroy();
|
||||
p.xdg.surface.destroy();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SurfaceRole {
|
||||
Toplevel(Option<ToplevelData>),
|
||||
Popup(Option<PopupData>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct XdgSurfaceData {
|
||||
surface: XdgSurface,
|
||||
configured: bool,
|
||||
pending: Option<PendingSurfaceState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ToplevelData {
|
||||
toplevel: XdgToplevel,
|
||||
xdg: XdgSurfaceData,
|
||||
fullscreen: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PopupData {
|
||||
popup: XdgPopup,
|
||||
positioner: XdgPositioner,
|
||||
xdg: XdgSurfaceData,
|
||||
}
|
||||
|
||||
pub(crate) trait HandleEvent {
|
||||
type Event;
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>);
|
||||
}
|
||||
|
||||
macro_rules! enum_try_from {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $enum:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
$pub enum $enum {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
|
||||
$(
|
||||
impl TryFrom<$enum> for $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a $enum> for &'a $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a mut $enum> for &'a mut $ty {
|
||||
type Error = String;
|
||||
fn try_from(value: &'a mut $enum) -> Result<Self, Self::Error> {
|
||||
enum_try_from!(@variant_match value $enum $variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ty> for $enum {
|
||||
fn from(value: $ty) -> Self {
|
||||
$enum::$variant(value)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
(@variant_match $value:ident $enum:ident $variant:ident) => {
|
||||
match $value {
|
||||
$enum::$variant(obj) => Ok(obj),
|
||||
other => Err(format!("wrong variant type: {}", std::any::type_name_of_val(&other)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement HandleEvent for our enum
|
||||
macro_rules! handle_event_enum {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$pub:vis enum $name:ident {
|
||||
$( $variant:ident($ty:ty) ),+
|
||||
}
|
||||
) => {
|
||||
enum_try_from! {
|
||||
$(#[$meta])*
|
||||
$pub enum $name {
|
||||
$( $variant($ty) ),+
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
enum_try_from! {
|
||||
#[derive(Debug)]
|
||||
$pub enum [<$name Event>] {
|
||||
$( $variant(<$ty as HandleEvent>::Event) ),+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleEvent for $name {
|
||||
paste::paste! {
|
||||
type Event = [<$name Event>];
|
||||
}
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant(v) => {
|
||||
let Self::Event::$variant(event) = event else {
|
||||
unreachable!();
|
||||
};
|
||||
v.handle_event(event, state)
|
||||
}
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_event_enum! {
|
||||
|
||||
/// Objects that generate client side events that we will have to process.
|
||||
pub(crate) enum Object {
|
||||
Surface(SurfaceData),
|
||||
Buffer(Buffer),
|
||||
Seat(Seat),
|
||||
Pointer(Pointer),
|
||||
Keyboard(Keyboard),
|
||||
Output(Output),
|
||||
RelativePointer(RelativePointer),
|
||||
DmabufFeedback(DmabufFeedback),
|
||||
Drm(Drm),
|
||||
XdgOutput(XdgOutput)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct WrappedObject(Option<Object>);
|
||||
|
||||
impl<T> From<T> for WrappedObject
|
||||
where
|
||||
T: Into<Object>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(Some(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
<&T>::try_from(self.0.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for WrappedObject
|
||||
where
|
||||
for<'a> &'a mut T: TryFrom<&'a mut Object, Error = String>,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
<&mut T>::try_from(self.0.as_mut().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
type ObjectMap = HopSlotMap<ObjectKey, WrappedObject>;
|
||||
trait ObjectMapExt {
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object;
|
||||
}
|
||||
|
||||
impl ObjectMapExt for ObjectMap {
|
||||
/// Insert an object into our map that needs some other values from our map as well
|
||||
fn insert_from_other_objects<F, const N: usize>(&mut self, keys: [ObjectKey; N], insert_fn: F)
|
||||
where
|
||||
F: FnOnce([&Object; N], ObjectKey) -> Object,
|
||||
{
|
||||
let objects = keys.each_ref().map(|key| self[*key].0.take().unwrap());
|
||||
let key = self.insert(WrappedObject(None));
|
||||
let obj = insert_fn(objects.each_ref(), key);
|
||||
debug_assert!(self[key].0.replace(obj).is_none());
|
||||
for (object, key) in objects.into_iter().zip(keys.into_iter()) {
|
||||
debug_assert!(self[key].0.replace(object).is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_key_type! {
|
||||
pub struct ObjectKey;
|
||||
}
|
||||
pub struct ServerState<C: XConnection> {
|
||||
pub atoms: Option<Atoms>,
|
||||
dh: DisplayHandle,
|
||||
clientside: ClientState,
|
||||
objects: ObjectMap,
|
||||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||
windows: HashMap<x::Window, WindowData>,
|
||||
|
||||
xdg_wm_base: XdgWmBase,
|
||||
qh: ClientQueueHandle,
|
||||
to_focus: Option<x::Window>,
|
||||
last_focused_toplevel: Option<x::Window>,
|
||||
connection: Option<C>,
|
||||
}
|
||||
|
||||
const XDG_WM_BASE_VERSION: u32 = 2;
|
||||
|
||||
impl<C: XConnection> ServerState<C> {
|
||||
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
|
||||
let mut clientside = ClientState::new(server_connection);
|
||||
let qh = clientside.qh.clone();
|
||||
|
||||
let xdg_pos = clientside
|
||||
.globals
|
||||
.new_globals
|
||||
.iter()
|
||||
.position(|g| g.interface == XdgWmBase::interface().name)
|
||||
.expect("Did not get an xdg_wm_base global");
|
||||
|
||||
let data = clientside.globals.new_globals.swap_remove(xdg_pos);
|
||||
|
||||
assert!(
|
||||
data.version >= XDG_WM_BASE_VERSION,
|
||||
"xdg_wm_base older than version {XDG_WM_BASE_VERSION}"
|
||||
);
|
||||
|
||||
let xdg_wm_base =
|
||||
clientside
|
||||
.registry
|
||||
.bind::<XdgWmBase, _, _>(data.name, XDG_WM_BASE_VERSION, &qh, ());
|
||||
|
||||
let mut ret = Self {
|
||||
windows: HashMap::new(),
|
||||
clientside,
|
||||
atoms: None,
|
||||
qh,
|
||||
dh,
|
||||
to_focus: None,
|
||||
last_focused_toplevel: None,
|
||||
connection: None,
|
||||
objects: Default::default(),
|
||||
associated_windows: Default::default(),
|
||||
xdg_wm_base,
|
||||
};
|
||||
ret.handle_new_globals();
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn clientside_fd(&self) -> BorrowedFd<'_> {
|
||||
self.clientside.queue.as_fd()
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, connection: UnixStream) {
|
||||
self.dh
|
||||
.insert_client(connection, std::sync::Arc::new(()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_x_connection(&mut self, connection: C) {
|
||||
self.connection = Some(connection);
|
||||
}
|
||||
|
||||
fn handle_new_globals(&mut self) {
|
||||
let globals = std::mem::take(&mut self.clientside.globals.new_globals);
|
||||
for data in globals {
|
||||
macro_rules! server_global {
|
||||
($($global:ty),+) => {
|
||||
match data.interface {
|
||||
$(
|
||||
ref x if x == <$global>::interface().name => {
|
||||
self.dh.create_global::<Self, $global, GlobalData>(data.version, data);
|
||||
}
|
||||
)+
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_global![
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlSeat,
|
||||
WlOutput,
|
||||
ZwpRelativePointerManagerV1,
|
||||
WlDrmServer,
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ZxdgOutputManagerV1,
|
||||
s_vp::wp_viewporter::WpViewporter
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
fn get_object_from_client_object<T, P: Proxy>(&self, proxy: &P) -> &T
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Object, Error = String>,
|
||||
Globals: wayland_client::Dispatch<P, ObjectKey>,
|
||||
{
|
||||
let key: ObjectKey = proxy.data().copied().unwrap();
|
||||
self.objects[key].as_ref()
|
||||
}
|
||||
|
||||
pub fn new_window(
|
||||
&mut self,
|
||||
window: x::Window,
|
||||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
) {
|
||||
self.windows.insert(
|
||||
window,
|
||||
WindowData::new(window, override_redirect, dims, parent),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
if win.hints.is_none() || *win.hints.as_ref().unwrap() != hints {
|
||||
debug!("setting {window:?} hints {hints:?}");
|
||||
if let Some(surface) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[surface].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
if let Some(min_size) = &hints.min_size {
|
||||
data.toplevel.set_min_size(min_size.width, min_size.height);
|
||||
}
|
||||
if let Some(max_size) = &hints.max_size {
|
||||
data.toplevel.set_max_size(max_size.width, max_size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
win.hints = Some(hints);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associate_window(&mut self, window: x::Window, surface_id: u32) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
win.surface_id = surface_id;
|
||||
|
||||
if let Some(key) = self
|
||||
.objects
|
||||
.iter_mut()
|
||||
.filter_map(|(key, obj)| {
|
||||
Some(key).zip(<&mut SurfaceData>::try_from(obj.0.as_mut().unwrap()).ok())
|
||||
})
|
||||
.find_map(|(key, surface)| {
|
||||
(surface_id == surface.server.id().protocol_id()).then_some(key)
|
||||
})
|
||||
{
|
||||
win.surface_key = Some(key);
|
||||
self.associated_windows.insert(key, window);
|
||||
debug!("associate {:?} with surface {surface_id}", window);
|
||||
if win.mapped {
|
||||
self.create_role_window(window, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
|
||||
let win = self.windows.get_mut(&event.window()).unwrap();
|
||||
win.dims = WindowDims {
|
||||
x: event.x(),
|
||||
y: event.y(),
|
||||
width: event.width(),
|
||||
height: event.height(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn map_window(&mut self, window: x::Window) {
|
||||
debug!("mapping {window:?}");
|
||||
|
||||
let window = self.windows.get_mut(&window).unwrap();
|
||||
window.mapped = true;
|
||||
}
|
||||
|
||||
pub fn unmap_window(&mut self, window: x::Window) {
|
||||
let Some(win) = self.windows.get_mut(&window) else {
|
||||
return;
|
||||
};
|
||||
if !win.mapped {
|
||||
return;
|
||||
}
|
||||
debug!("unmapping {window:?}");
|
||||
|
||||
if matches!(self.last_focused_toplevel, Some(x) if x == window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
win.mapped = false;
|
||||
|
||||
if let Some(key) = win.surface_key.take() {
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
surface.destroy_role();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: x::Window, state: super::xstate::SetState) {
|
||||
let win = self.windows.get(&window).unwrap();
|
||||
let Some(key) = win.surface_key else {
|
||||
warn!("Tried to set window without surface fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
let surface: &mut SurfaceData = self.objects[key].as_mut();
|
||||
let Some(SurfaceRole::Toplevel(Some(ref toplevel))) = surface.role else {
|
||||
warn!("Tried to set an unmapped toplevel or non toplevel fullscreen: {window:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
match state {
|
||||
crate::xstate::SetState::Add => toplevel.toplevel.set_fullscreen(None),
|
||||
crate::xstate::SetState::Remove => toplevel.toplevel.unset_fullscreen(),
|
||||
crate::xstate::SetState::Toggle => {
|
||||
if toplevel.fullscreen {
|
||||
toplevel.toplevel.unset_fullscreen()
|
||||
} else {
|
||||
toplevel.toplevel.set_fullscreen(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_window(&mut self, window: x::Window) {
|
||||
let _ = self.windows.remove(&window);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
if let Some(r) = self.clientside.queue.prepare_read() {
|
||||
let fd = r.connection_fd();
|
||||
let pollfd = PollFd::new(&fd, PollFlags::IN);
|
||||
if poll(&mut [pollfd], 0).unwrap() > 0 {
|
||||
let _ = r.read();
|
||||
}
|
||||
}
|
||||
self.clientside
|
||||
.queue
|
||||
.dispatch_pending(&mut self.clientside.globals)
|
||||
.unwrap();
|
||||
self.handle_clientside_events();
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_clientside_events(&mut self) {
|
||||
self.handle_new_globals();
|
||||
|
||||
let client_events = std::mem::take(&mut self.clientside.globals.events);
|
||||
for (key, event) in client_events {
|
||||
let object = &mut self.objects[key];
|
||||
let mut object = object.0.take().unwrap();
|
||||
object.handle_event(event, self);
|
||||
debug_assert!(self.objects[key].0.replace(object).is_none());
|
||||
}
|
||||
|
||||
{
|
||||
if let Some(win) = self.to_focus.take() {
|
||||
let data = C::ExtraData::create(self);
|
||||
let conn = self.connection.as_mut().unwrap();
|
||||
debug!("focusing window {win:?}");
|
||||
conn.focus_window(win, data);
|
||||
self.last_focused_toplevel = Some(win);
|
||||
}
|
||||
}
|
||||
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
||||
let surface: &SurfaceData = self.objects[surface_key].as_ref();
|
||||
let client = &surface.client;
|
||||
client.attach(None, 0, 0);
|
||||
client.commit();
|
||||
|
||||
let xdg_surface = self
|
||||
.xdg_wm_base
|
||||
.get_xdg_surface(client, &self.qh, surface_key);
|
||||
|
||||
let window_data = self.windows.get_mut(&window).unwrap();
|
||||
if window_data.override_redirect {
|
||||
// Override redirect is hard to convert to Wayland!
|
||||
// We will just make them be popups for the last focused toplevel.
|
||||
if let Some(win) = self.last_focused_toplevel {
|
||||
window_data.popup_for = Some(win)
|
||||
}
|
||||
}
|
||||
let window = self.windows.get(&window).unwrap();
|
||||
|
||||
let role = if let Some(parent) = window.popup_for {
|
||||
debug!(
|
||||
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
|
||||
window.window,
|
||||
parent,
|
||||
window.dims,
|
||||
surface.client.id()
|
||||
);
|
||||
|
||||
let parent_window = self.windows.get(&parent).unwrap();
|
||||
let parent_surface: &SurfaceData =
|
||||
self.objects[parent_window.surface_key.unwrap()].as_ref();
|
||||
|
||||
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
|
||||
positioner.set_size(window.dims.width as _, window.dims.height as _);
|
||||
positioner.set_offset(window.dims.x as i32, window.dims.y as i32);
|
||||
positioner.set_anchor(Anchor::TopLeft);
|
||||
positioner.set_gravity(Gravity::BottomRight);
|
||||
positioner.set_anchor_rect(
|
||||
0,
|
||||
0,
|
||||
parent_window.dims.width as _,
|
||||
parent_window.dims.height as _,
|
||||
);
|
||||
let popup = xdg_surface.get_popup(
|
||||
Some(&parent_surface.xdg().unwrap().surface),
|
||||
&positioner,
|
||||
&self.qh,
|
||||
surface_key,
|
||||
);
|
||||
let popup = PopupData {
|
||||
popup,
|
||||
positioner,
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg_surface,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
};
|
||||
SurfaceRole::Popup(Some(popup))
|
||||
} else {
|
||||
let data = self.create_toplevel(window, surface_key, xdg_surface);
|
||||
SurfaceRole::Toplevel(Some(data))
|
||||
};
|
||||
|
||||
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
|
||||
|
||||
let new_role_type = std::mem::discriminant(&role);
|
||||
let prev = surface.role.replace(role);
|
||||
if let Some(role) = prev {
|
||||
let old_role_type = std::mem::discriminant(&role);
|
||||
assert_eq!(
|
||||
new_role_type, old_role_type,
|
||||
"Surface for {:?} already had a role: {:?}",
|
||||
window.window, role
|
||||
);
|
||||
}
|
||||
|
||||
surface.client.commit();
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&self,
|
||||
window: &WindowData,
|
||||
surface_key: ObjectKey,
|
||||
xdg: XdgSurface,
|
||||
) -> ToplevelData {
|
||||
debug!("creating toplevel for {:?}", window.window);
|
||||
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
||||
if let Some(hints) = &window.hints {
|
||||
if let Some(min) = &hints.min_size {
|
||||
toplevel.set_min_size(min.width, min.height);
|
||||
}
|
||||
if let Some(max) = &hints.max_size {
|
||||
toplevel.set_max_size(max.width, max.height);
|
||||
}
|
||||
}
|
||||
|
||||
ToplevelData {
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg,
|
||||
configured: false,
|
||||
pending: None,
|
||||
},
|
||||
toplevel,
|
||||
fullscreen: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_surface_from_client(&self, surface: client::wl_surface::WlSurface) -> &WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.server
|
||||
}
|
||||
|
||||
fn get_client_surface_from_server(&self, surface: WlSurface) -> &client::wl_surface::WlSurface {
|
||||
let key: &ObjectKey = surface.data().unwrap();
|
||||
let surface: &SurfaceData = self.objects[*key].as_ref();
|
||||
&surface.client
|
||||
}
|
||||
|
||||
fn close_x_window(&mut self, window: x::Window) {
|
||||
debug!("sending close request to {window:?}");
|
||||
let data = C::ExtraData::create(self);
|
||||
self.connection.as_mut().unwrap().close_window(window, data);
|
||||
if self.last_focused_toplevel == Some(window) {
|
||||
self.last_focused_toplevel.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PendingSurfaceState {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
866
satellite/src/server/tests.rs
Normal file
866
satellite/src/server/tests.rs
Normal file
|
|
@ -0,0 +1,866 @@
|
|||
use super::{ServerState, WindowDims};
|
||||
use crate::xstate::SetState;
|
||||
use paste::paste;
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::{
|
||||
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer,
|
||||
wl_compositor::WlCompositor,
|
||||
wl_display::WlDisplay,
|
||||
wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat,
|
||||
wl_shm::{Format, WlShm},
|
||||
wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Connection, Proxy, WEnum,
|
||||
};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
viewporter::client::wp_viewporter::WpViewporter,
|
||||
},
|
||||
xdg::{
|
||||
shell::server::{xdg_positioner, xdg_toplevel},
|
||||
xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::{protocol as s_proto, Display, Resource};
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
use xcb::x::Window;
|
||||
|
||||
use xcb::XidNew;
|
||||
|
||||
macro_rules! with_optional {
|
||||
(
|
||||
$( #[$attr:meta] )?
|
||||
struct $name:ident$(<$($lifetimes:lifetime),+>)? {
|
||||
$(
|
||||
$field:ident: $type:ty
|
||||
),+$(,)?
|
||||
}
|
||||
) => {
|
||||
$( #[$attr] )?
|
||||
struct $name$(<$($lifetimes),+>)? {
|
||||
$(
|
||||
$field: $type
|
||||
),+
|
||||
}
|
||||
|
||||
paste! {
|
||||
#[derive(Default)]
|
||||
struct [< $name Optional >] {
|
||||
$(
|
||||
$field: Option<$type>
|
||||
),+
|
||||
}
|
||||
}
|
||||
|
||||
paste! {
|
||||
impl From<[<$name Optional>]> for $name {
|
||||
fn from(opt: [<$name Optional>]) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: opt.$field.expect(concat!("uninitialized field ", stringify!($field)))
|
||||
),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with_optional! {
|
||||
|
||||
struct Compositor {
|
||||
compositor: TestObject<WlCompositor>,
|
||||
shm: TestObject<WlShm>,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
fn create_surface(&self) -> (TestObject<WlBuffer>, TestObject<WlSurface>) {
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(0) };
|
||||
let pool = TestObject::<WlShmPool>::from_request(
|
||||
&self.shm.obj,
|
||||
Req::<WlShm>::CreatePool { fd, size: 1024 },
|
||||
);
|
||||
let buffer = TestObject::<WlBuffer>::from_request(
|
||||
&pool.obj,
|
||||
Req::<WlShmPool>::CreateBuffer {
|
||||
offset: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
stride: 1,
|
||||
format: WEnum::Value(Format::Xrgb8888A8),
|
||||
},
|
||||
);
|
||||
let surface = TestObject::<WlSurface>::from_request(
|
||||
&self.compositor.obj,
|
||||
Req::<WlCompositor>::CreateSurface {},
|
||||
);
|
||||
surface
|
||||
.send_request(Req::<WlSurface>::Attach {
|
||||
buffer: Some(buffer.obj.clone()),
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(buffer, surface)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
mapped: bool,
|
||||
fullscreen: bool,
|
||||
dims: WindowDims,
|
||||
}
|
||||
struct FakeXConnection {
|
||||
root: Window,
|
||||
focused_window: Option<Window>,
|
||||
windows: HashMap<Window, WindowData>,
|
||||
}
|
||||
|
||||
impl FakeXConnection {
|
||||
#[track_caller]
|
||||
fn window(&mut self, window: Window) -> &mut WindowData {
|
||||
self.windows
|
||||
.get_mut(&window)
|
||||
.expect("Unknown window: {window:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FakeXConnection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: unsafe { Window::new(9001) },
|
||||
focused_window: None,
|
||||
windows: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::FromServerState<FakeXConnection> for () {
|
||||
fn create(_: &FakeServerState) -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for FakeXConnection {
|
||||
type ExtraData = ();
|
||||
fn root_window(&self) -> Window {
|
||||
self.root
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn close_window(&mut self, window: Window, _: ()) {
|
||||
log::debug!("closing window {window:?}");
|
||||
self.window(window).mapped = false;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_fullscreen(&mut self, window: xcb::x::Window, fullscreen: bool, _: ()) {
|
||||
self.window(window).fullscreen = fullscreen;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
|
||||
self.window(window).dims = WindowDims {
|
||||
x: state.x as _,
|
||||
y: state.y as _,
|
||||
width: state.width as _,
|
||||
height: state.height as _,
|
||||
};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn focus_window(&mut self, window: Window, _: ()) {
|
||||
assert!(
|
||||
self.windows.contains_key(&window),
|
||||
"Unknown window: {window:?}"
|
||||
);
|
||||
self.focused_window = window.into();
|
||||
}
|
||||
}
|
||||
|
||||
type FakeServerState = ServerState<FakeXConnection>;
|
||||
|
||||
struct TestFixture {
|
||||
testwl: testwl::Server,
|
||||
exwayland: FakeServerState,
|
||||
/// Our connection to exwayland - i.e., where Xwayland sends requests to
|
||||
exwl_connection: Arc<Connection>,
|
||||
/// Exwayland's display - must dispatch this for our server state to advance
|
||||
exwl_display: Display<FakeServerState>,
|
||||
}
|
||||
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
impl TestFixture {
|
||||
fn new() -> Self {
|
||||
INIT.call_once(|| {
|
||||
env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(log::LevelFilter::Trace)
|
||||
.init()
|
||||
});
|
||||
|
||||
let (client_s, server_s) = UnixStream::pair().unwrap();
|
||||
let mut testwl = testwl::Server::new(true);
|
||||
let display = Display::<FakeServerState>::new().unwrap();
|
||||
testwl.connect(server_s);
|
||||
// Handle initial globals roundtrip setup requirement
|
||||
let thread = std::thread::spawn(move || {
|
||||
let mut pollfd = [PollFd::from_borrowed_fd(testwl.poll_fd(), PollFlags::IN)];
|
||||
if poll(&mut pollfd, 1000).unwrap() == 0 {
|
||||
panic!("Did not get events for testwl!");
|
||||
}
|
||||
testwl.dispatch();
|
||||
testwl
|
||||
});
|
||||
let mut exwayland = FakeServerState::new(display.handle(), Some(client_s));
|
||||
let testwl = thread.join().unwrap();
|
||||
|
||||
let (fake_client, ex_server) = UnixStream::pair().unwrap();
|
||||
exwayland.connect(ex_server);
|
||||
|
||||
exwayland.set_x_connection(FakeXConnection::default());
|
||||
let mut f = TestFixture {
|
||||
testwl,
|
||||
exwayland,
|
||||
exwl_connection: Connection::from_socket(fake_client).unwrap().into(),
|
||||
exwl_display: display,
|
||||
};
|
||||
f.run();
|
||||
f
|
||||
}
|
||||
|
||||
fn new_with_compositor() -> (Self, Compositor) {
|
||||
let mut f = Self::new();
|
||||
let compositor = f.compositor();
|
||||
(f, compositor)
|
||||
}
|
||||
|
||||
fn connection(&self) -> &FakeXConnection {
|
||||
self.exwayland.connection.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn compositor(&mut self) -> Compositor {
|
||||
let mut ret = CompositorOptional::default();
|
||||
let wl_display = self.exwl_connection.display();
|
||||
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&wl_display, Req::<WlDisplay>::GetRegistry {});
|
||||
self.run();
|
||||
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
|
||||
let bind_req = |name, interface, version| Req::<WlRegistry>::Bind {
|
||||
name,
|
||||
id: (interface, version),
|
||||
};
|
||||
|
||||
for event in events {
|
||||
if let Ev::<WlRegistry>::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
match interface {
|
||||
x if x == WlCompositor::interface().name => {
|
||||
ret.compositor = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlCompositor::interface(), version),
|
||||
));
|
||||
}
|
||||
x if x == WlShm::interface().name => {
|
||||
ret.shm = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlShm::interface(), version),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.into()
|
||||
}
|
||||
|
||||
/// Cascade our requests/events through exwayland and testwl
|
||||
fn run(&mut self) {
|
||||
// Flush our requests to exwayland
|
||||
self.exwl_connection.flush().unwrap();
|
||||
|
||||
// Have exwayland dispatch our requests
|
||||
self.exwl_display
|
||||
.dispatch_clients(&mut self.exwayland)
|
||||
.unwrap();
|
||||
self.exwl_display.flush_clients().unwrap();
|
||||
|
||||
// Dispatch any clientside requests
|
||||
self.exwayland.run();
|
||||
|
||||
// Have testwl dispatch the clientside requests
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Handle clientside events
|
||||
self.exwayland.handle_clientside_events();
|
||||
|
||||
self.testwl.dispatch();
|
||||
|
||||
// Get our events
|
||||
let res = self.exwl_connection.prepare_read().unwrap().read();
|
||||
if res.is_err()
|
||||
&& !matches!(res, Err(WaylandError::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock)
|
||||
{
|
||||
panic!("Read failed: {res:?}")
|
||||
}
|
||||
}
|
||||
|
||||
fn register_window(&mut self, window: Window, data: WindowData) {
|
||||
self.exwayland
|
||||
.connection
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.windows
|
||||
.insert(window, data);
|
||||
}
|
||||
|
||||
fn create_and_map_window(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
override_redirect: bool,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland
|
||||
.new_window(window, override_redirect, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Surface not created");
|
||||
|
||||
assert!(self.testwl.get_surface_data(testwl_id).is_some());
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_toplevel(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_buffer, surface) = comp.create_surface();
|
||||
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, false, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, surface.id().protocol_id());
|
||||
|
||||
self.run();
|
||||
|
||||
let testwl_id = self
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("Toplevel surface not created");
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(testwl_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"surface role: {:?}",
|
||||
surface_data.role
|
||||
);
|
||||
}
|
||||
|
||||
self.testwl
|
||||
.configure_toplevel(testwl_id, 100, 100, vec![xdg_toplevel::State::Activated]);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
let win_data = self.connection().windows.get(&window).map(|d| &d.dims);
|
||||
assert!(
|
||||
matches!(
|
||||
win_data,
|
||||
Some(&super::WindowDims {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
),
|
||||
"Incorrect window geometry: {win_data:?}"
|
||||
);
|
||||
|
||||
(surface, testwl_id)
|
||||
}
|
||||
|
||||
fn create_popup(
|
||||
&mut self,
|
||||
comp: &Compositor,
|
||||
window: Window,
|
||||
parent_id: testwl::SurfaceId,
|
||||
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
|
||||
let (_, popup_surface) = comp.create_surface();
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
};
|
||||
let dims = data.dims;
|
||||
self.register_window(window, data);
|
||||
self.exwayland.new_window(window, true, dims, None);
|
||||
self.exwayland.map_window(window);
|
||||
self.exwayland
|
||||
.associate_window(window, popup_surface.id().protocol_id());
|
||||
self.run();
|
||||
let popup_id = self.testwl.last_created_surface_id().unwrap();
|
||||
assert_ne!(popup_id, parent_id);
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(
|
||||
surface_data.surface
|
||||
== self
|
||||
.testwl
|
||||
.get_object::<s_proto::wl_surface::WlSurface>(popup_id)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(surface_data.buffer.is_none());
|
||||
assert!(
|
||||
matches!(surface_data.role, Some(testwl::SurfaceRole::Popup(_))),
|
||||
"surface was not a popup (role: {:?})",
|
||||
surface_data.role
|
||||
);
|
||||
|
||||
let toplevel_xdg = &self
|
||||
.testwl
|
||||
.get_surface_data(parent_id)
|
||||
.unwrap()
|
||||
.xdg()
|
||||
.surface;
|
||||
assert_eq!(&surface_data.popup().parent, toplevel_xdg);
|
||||
|
||||
let pos = &surface_data.popup().positioner_state;
|
||||
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
|
||||
assert_eq!(
|
||||
pos.anchor_rect.as_ref().unwrap(),
|
||||
&testwl::Rect {
|
||||
size: testwl::Vec2 { x: 100, y: 100 },
|
||||
offset: testwl::Vec2::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
pos.offset,
|
||||
testwl::Vec2 {
|
||||
x: dims.x as _,
|
||||
y: dims.y as _
|
||||
}
|
||||
);
|
||||
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
|
||||
assert_eq!(pos.gravity, xdg_positioner::Gravity::BottomRight);
|
||||
}
|
||||
|
||||
self.testwl.configure_popup(popup_id);
|
||||
self.run();
|
||||
|
||||
{
|
||||
let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
(popup_surface, popup_id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestObjectData<T: Proxy> {
|
||||
events: Mutex<Vec<T::Event>>,
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> Default for TestObjectData<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
events: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Send + Sync + 'static> ObjectData for TestObjectData<T>
|
||||
where
|
||||
T::Event: Send + Sync + std::fmt::Debug,
|
||||
{
|
||||
fn event(
|
||||
self: Arc<Self>,
|
||||
backend: &Backend,
|
||||
msg: Message<ObjectId, std::os::fd::OwnedFd>,
|
||||
) -> Option<Arc<dyn ObjectData>> {
|
||||
let connection = Connection::from_backend(backend.clone());
|
||||
let event = T::parse_event(&connection, msg).unwrap().1;
|
||||
self.events.lock().unwrap().push(event);
|
||||
None
|
||||
}
|
||||
|
||||
fn destroyed(&self, _: ObjectId) {}
|
||||
}
|
||||
|
||||
struct TestObject<T: Proxy> {
|
||||
obj: T,
|
||||
data: Arc<TestObjectData<T>>,
|
||||
}
|
||||
|
||||
impl<T: Proxy> std::ops::Deref for TestObject<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.obj
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Proxy + Sync + Send + 'static> TestObject<T>
|
||||
where
|
||||
T::Event: Sync + Send + std::fmt::Debug,
|
||||
{
|
||||
fn from_request<P: Proxy>(object: &P, request: P::Request<'_>) -> Self {
|
||||
let data = Arc::<TestObjectData<T>>::default();
|
||||
let obj: T = P::send_constructor(object, request, data.clone()).unwrap();
|
||||
Self { obj, data }
|
||||
}
|
||||
}
|
||||
|
||||
type Req<'a, T> = <T as Proxy>::Request<'a>;
|
||||
type Ev<T> = <T as Proxy>::Event;
|
||||
|
||||
// TODO: tests to add
|
||||
// - destroy window before surface
|
||||
// - destroy surface before window
|
||||
// - destroy popup and reassociate with new surface
|
||||
// - reconfigure window (popup) before mapping
|
||||
// - associate window after surface is already created
|
||||
|
||||
// Matches Xwayland flow.
|
||||
#[test]
|
||||
fn toplevel_flow() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let window = unsafe { Window::new(1) };
|
||||
let (surface, testwl_id) = f.create_toplevel(&compositor, window);
|
||||
{
|
||||
let surface_data = f.testwl.get_surface_data(testwl_id).unwrap();
|
||||
assert!(surface_data.buffer.is_some());
|
||||
}
|
||||
|
||||
f.testwl.close_toplevel(testwl_id);
|
||||
f.run();
|
||||
|
||||
assert!(!f.exwayland.connection.as_ref().unwrap().windows[&window].mapped);
|
||||
|
||||
assert!(
|
||||
f.testwl.get_surface_data(testwl_id).is_some(),
|
||||
"Surface should still exist for closed toplevel"
|
||||
);
|
||||
assert!(surface.obj.is_alive());
|
||||
|
||||
// For some reason, we can get two UnmapNotify events
|
||||
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.unmap_window(window);
|
||||
f.exwayland.destroy_window(window);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(testwl_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_flow_simple() {
|
||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||
|
||||
let win_toplevel = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
|
||||
|
||||
let win_popup = unsafe { Window::new(2) };
|
||||
let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win_popup);
|
||||
f.exwayland.destroy_window(win_popup);
|
||||
popup_surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(popup_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_through_globals() {
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
|
||||
let mut f = TestFixture::new();
|
||||
|
||||
const fn check<T: Proxy>() {}
|
||||
|
||||
macro_rules! globals_struct {
|
||||
($($field:ident),+) => {
|
||||
$( check::<$field>(); )+
|
||||
#[derive(Default)]
|
||||
#[allow(non_snake_case)]
|
||||
struct SupportedGlobals {
|
||||
$( $field: bool ),+
|
||||
}
|
||||
|
||||
impl SupportedGlobals {
|
||||
fn check_globals(&self) {
|
||||
$( assert!(self.$field, "Missing global {}", stringify!($field)); )+
|
||||
}
|
||||
|
||||
fn global_found(&mut self, interface: String) {
|
||||
match interface {
|
||||
$(
|
||||
x if x == $field::interface().name => {
|
||||
self.$field = true;
|
||||
}
|
||||
)+
|
||||
_ => panic!("Found an unhandled global: {interface}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New globals need to be added here and in testwl.
|
||||
globals_struct! {
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlOutput,
|
||||
WlSeat,
|
||||
ZwpLinuxDmabufV1,
|
||||
ZwpRelativePointerManagerV1,
|
||||
ZxdgOutputManagerV1,
|
||||
WpViewporter,
|
||||
WlDrm
|
||||
}
|
||||
|
||||
let mut globals = SupportedGlobals::default();
|
||||
let display = f.exwl_connection.display();
|
||||
let registry =
|
||||
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
|
||||
f.run();
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
for event in events {
|
||||
let Ev::<WlRegistry>::Global { interface, .. } = event else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
globals.global_found(interface);
|
||||
}
|
||||
|
||||
globals.check_globals();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_activated_toplevel_is_focused() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
|
||||
let (_surface1, id1) = f.create_toplevel(&comp, win1);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win1),
|
||||
"new toplevel's window is not focused"
|
||||
);
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let _data2 = f.create_toplevel(&comp, win2);
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not switch"
|
||||
);
|
||||
|
||||
f.testwl.configure_toplevel(id1, 100, 100, vec![]);
|
||||
f.run();
|
||||
assert_eq!(
|
||||
f.connection().focused_window,
|
||||
Some(win2),
|
||||
"toplevel focus did not stay the same"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_window_changes_surface() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let t_win = unsafe { Window::new(1) };
|
||||
let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
|
||||
|
||||
let win = unsafe { Window::new(2) };
|
||||
let (surface, old_id) = f.create_popup(&comp, win, toplevel_id);
|
||||
|
||||
f.exwayland.unmap_window(win);
|
||||
surface.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(old_id).is_none());
|
||||
|
||||
let (_, surface) = comp.create_surface();
|
||||
f.run();
|
||||
let id = f
|
||||
.testwl
|
||||
.last_created_surface_id()
|
||||
.expect("No surface created");
|
||||
|
||||
assert_ne!(old_id, id);
|
||||
assert!(f.testwl.get_surface_data(id).is_some());
|
||||
|
||||
f.exwayland.map_window(win);
|
||||
f.exwayland
|
||||
.associate_window(win, surface.id().protocol_id());
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
|
||||
f.testwl.configure_popup(id);
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data.popup().popup.is_alive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn override_redirect_window_after_toplevel_close() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win1 = unsafe { Window::new(1) };
|
||||
let (obj, first) = f.create_toplevel(&comp, win1);
|
||||
f.testwl.close_toplevel(first);
|
||||
f.run();
|
||||
|
||||
f.exwayland.unmap_window(win1);
|
||||
f.exwayland.destroy_window(win1);
|
||||
obj.obj.destroy();
|
||||
f.run();
|
||||
|
||||
assert!(f.testwl.get_surface_data(first).is_none());
|
||||
|
||||
let win2 = unsafe { Window::new(2) };
|
||||
let (_, second) = f.create_and_map_window(&comp, win2, true);
|
||||
let data = f.testwl.get_surface_data(second).unwrap();
|
||||
assert!(
|
||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||
"wrong role: {:?}",
|
||||
data.role
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_, id) = f.create_toplevel(&comp, win);
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Add);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Remove);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
|
||||
f.exwayland.set_fullscreen(win, SetState::Toggle);
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert!(!data
|
||||
.toplevel()
|
||||
.states
|
||||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
}
|
||||
|
||||
/// See Pointer::handle_event for an explanation.
|
||||
#[test]
|
||||
fn popup_pointer_motion_workaround() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue