Add tablet support

Closes #47
This commit is contained in:
Shawn Wallace 2024-10-17 01:33:56 -04:00
parent 73ca9c91f1
commit c77b66cc93
6 changed files with 925 additions and 66 deletions

View file

@ -20,11 +20,9 @@ use wayland_protocols::{
},
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,
},
server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
},
tablet::zv2::{client as c_tablet, server as s_tablet},
viewporter::{client as c_vp, server as s_vp},
},
xdg::xdg_output::zv1::{
@ -59,6 +57,40 @@ use wayland_server::{
Dispatch, DisplayHandle, GlobalDispatch, Resource,
};
macro_rules! only_destroy_request_impl {
($object_type:ty) => {
impl<C: XConnection> Dispatch<<$object_type as GenericObjectExt>::Server, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &Client,
_: &<$object_type as GenericObjectExt>::Server,
request: <<$object_type as GenericObjectExt>::Server as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
if !matches!(
request,
<<$object_type as GenericObjectExt>::Server as Resource>::Request::Destroy
) {
warn!(
"unrecognized {} request: {:?}",
stringify!($object_type),
request
);
return;
}
let obj: &$object_type = state.objects[*key].as_ref();
obj.client.destroy();
state.objects.remove(*key);
}
}
};
}
// noop
impl<C: XConnection> Dispatch<WlCallback, ()> for ServerState<C> {
fn request(
@ -166,7 +198,7 @@ impl<C: XConnection> Dispatch<WlRegion, client::wl_region::WlRegion> for ServerS
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
simple_event_shunt! {
macros::simple_event_shunt! {
client, request: wl_region::Request => [
Add { x, y, width, height },
Subtract { x, y, width, height },
@ -432,24 +464,7 @@ impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
}
}
}
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);
}
}
}
only_destroy_request_impl!(RelativePointer);
impl<C: XConnection>
Dispatch<RelativePointerManServer, ClientGlobalWrapper<RelativePointerManClient>>
@ -980,6 +995,170 @@ impl<C: XConnection>
}
}
impl<C: XConnection>
Dispatch<
s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2,
ClientGlobalWrapper<c_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2>,
> for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2,
request: <s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2 as Resource>::Request,
client: &ClientGlobalWrapper<c_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use s_tablet::zwp_tablet_manager_v2::Request::*;
match request {
GetTabletSeat { tablet_seat, seat } => {
let seat_key: ObjectKey = seat.data().copied().unwrap();
state
.objects
.insert_from_other_objects([seat_key], |[seat_obj], key| {
let Seat { client: c_seat, .. }: &Seat = seat_obj.try_into().unwrap();
let client = client.get_tablet_seat(c_seat, &state.qh, key);
let server = data_init.init(tablet_seat, key);
TabletSeat { client, server }.into()
});
}
other => {
warn!("unhandled tablet request: {other:?}");
}
}
}
}
only_destroy_request_impl!(TabletSeat);
only_destroy_request_impl!(Tablet);
only_destroy_request_impl!(TabletPadGroup);
impl<C: XConnection> Dispatch<s_tablet::zwp_tablet_pad_v2::ZwpTabletPadV2, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &Client,
_: &s_tablet::zwp_tablet_pad_v2::ZwpTabletPadV2,
request: <s_tablet::zwp_tablet_pad_v2::ZwpTabletPadV2 as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let pad: &TabletPad = state.objects[*key].as_ref();
match request {
s_tablet::zwp_tablet_pad_v2::Request::SetFeedback {
button,
description,
serial,
} => {
pad.client.set_feedback(button, description, serial);
}
s_tablet::zwp_tablet_pad_v2::Request::Destroy => {
pad.client.destroy();
state.objects.remove(*key);
}
other => warn!("unhandled tablet pad request: {other:?}"),
}
}
}
impl<C: XConnection> Dispatch<s_tablet::zwp_tablet_tool_v2::ZwpTabletToolV2, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &Client,
_: &s_tablet::zwp_tablet_tool_v2::ZwpTabletToolV2,
request: <s_tablet::zwp_tablet_tool_v2::ZwpTabletToolV2 as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let tool: &TabletTool = state.objects[*key].as_ref();
match request {
s_tablet::zwp_tablet_tool_v2::Request::SetCursor {
serial,
surface,
hotspot_x,
hotspot_y,
} => {
let surf_key: Option<ObjectKey> = surface.map(|s| s.data().copied().unwrap());
let c_surface = surf_key.map(|key| {
let d: &SurfaceData = state.objects[key].as_ref();
&d.client
});
tool.client
.set_cursor(serial, c_surface, hotspot_x, hotspot_y);
}
s_tablet::zwp_tablet_tool_v2::Request::Destroy => {
tool.client.destroy();
state.objects.remove(*key);
}
other => warn!("unhandled tablet tool request: {other:?}"),
}
}
}
impl<C: XConnection> Dispatch<s_tablet::zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &Client,
_: &s_tablet::zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2,
request: <s_tablet::zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2 as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let ring: &TabletPadRing = state.objects[*key].as_ref();
match request {
s_tablet::zwp_tablet_pad_ring_v2::Request::SetFeedback {
description,
serial,
} => {
ring.client.set_feedback(description, serial);
}
s_tablet::zwp_tablet_pad_ring_v2::Request::Destroy => {
ring.client.destroy();
state.objects.remove(*key);
}
other => warn!("unhandled tablet pad ring requst: {other:?}"),
}
}
}
impl<C: XConnection> Dispatch<s_tablet::zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, ObjectKey>
for ServerState<C>
{
fn request(
state: &mut Self,
_: &Client,
_: &s_tablet::zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2,
request: <s_tablet::zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2 as Resource>::Request,
key: &ObjectKey,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let strip: &TabletPadStrip = state.objects[*key].as_ref();
match request {
s_tablet::zwp_tablet_pad_strip_v2::Request::SetFeedback {
description,
serial,
} => {
strip.client.set_feedback(description, serial);
}
s_tablet::zwp_tablet_pad_strip_v2::Request::Destroy => {
strip.client.destroy();
state.objects.remove(*key);
}
other => warn!("unhandled tablet pad strip requst: {other:?}"),
}
}
}
#[derive(Clone)]
pub(crate) struct ClientGlobalWrapper<T: Proxy>(Arc<OnceLock<T>>);
impl<T: Proxy> std::ops::Deref for ClientGlobalWrapper<T> {
@ -1072,6 +1251,10 @@ global_dispatch_no_events!(
c_vp::wp_viewporter::WpViewporter
);
global_dispatch_no_events!(PointerConstraintsServer, PointerConstraintsClient);
global_dispatch_no_events!(
s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2,
c_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2
);
impl<C: XConnection> GlobalDispatch<WlSeat, Global> for ServerState<C>
where

View file

@ -1,4 +1,5 @@
use super::*;
use crate::clientside::LateInitObjectKey;
use log::{debug, trace, warn};
use macros::simple_event_shunt;
use std::collections::HashSet;
@ -22,6 +23,26 @@ use wayland_protocols::{
},
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
},
tablet::zv2::{
client::{
zwp_tablet_pad_group_v2::{self, ZwpTabletPadGroupV2 as TabletPadGroupClient},
zwp_tablet_pad_ring_v2::{self, ZwpTabletPadRingV2 as TabletPadRingClient},
zwp_tablet_pad_strip_v2::{self, ZwpTabletPadStripV2 as TabletPadStripClient},
zwp_tablet_pad_v2::{self, ZwpTabletPadV2 as TabletPadClient},
zwp_tablet_seat_v2::{self, ZwpTabletSeatV2 as TabletSeatClient},
zwp_tablet_tool_v2::{self, ZwpTabletToolV2 as TabletToolClient},
zwp_tablet_v2::{self, ZwpTabletV2 as TabletClient},
},
server::{
zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2 as TabletPadGroupServer,
zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2 as TabletPadRingServer,
zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2 as TabletPadStripServer,
zwp_tablet_pad_v2::ZwpTabletPadV2 as TabletPadServer,
zwp_tablet_seat_v2::ZwpTabletSeatV2 as TabletSeatServer,
zwp_tablet_tool_v2::ZwpTabletToolV2 as TabletToolServer,
zwp_tablet_v2::ZwpTabletV2 as TabletServer,
},
},
},
xdg::{
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
@ -237,6 +258,41 @@ pub struct GenericObject<Server: Resource, Client: Proxy> {
pub client: Client,
}
impl<S: Resource + 'static, C: Proxy + 'static> GenericObject<S, C> {
fn from_client<XC: XConnection>(client: C, state: &mut ServerState<XC>) -> &Self
where
Self: Into<WrappedObject>,
for<'a> &'a Self: TryFrom<&'a Object, Error = String>,
ServerState<XC>: wayland_server::Dispatch<S, ObjectKey>,
C::Event: Send + Into<ObjectEvent>,
{
let key = state.objects.insert_with_key(|key| {
let server = state
.client
.as_ref()
.unwrap()
.create_resource::<_, _, ServerState<XC>>(&state.dh, 1, key)
.unwrap();
let obj_key: &LateInitObjectKey<C> = client.data().unwrap();
obj_key.init(key);
Self { client, server }.into()
});
state.objects[key].as_ref()
}
}
pub trait GenericObjectExt {
type Server: Resource;
type Client: Proxy;
}
impl<S: Resource, C: Proxy> GenericObjectExt for GenericObject<S, C> {
type Server = S;
type Client = C;
}
pub type Buffer = GenericObject<WlBuffer, client::wl_buffer::WlBuffer>;
impl HandleEvent for Buffer {
type Event = client::wl_buffer::Event;
@ -790,3 +846,183 @@ impl HandleEvent for ConfinedPointer {
}
}
}
pub type TabletSeat = GenericObject<TabletSeatServer, TabletSeatClient>;
impl HandleEvent for TabletSeat {
type Event = zwp_tablet_seat_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_seat_v2::Event => [
TabletAdded {
|id| &Tablet::from_client(id, state).server
},
ToolAdded {
|id| &TabletTool::from_client(id, state).server
},
PadAdded {
|id| &TabletPad::from_client(id, state).server
}
]
}
}
}
pub type Tablet = GenericObject<TabletServer, TabletClient>;
impl HandleEvent for Tablet {
type Event = zwp_tablet_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_v2::Event => [
Name { name },
Id { vid, pid },
Path { path },
Done,
Removed
]
}
}
}
pub type TabletPad = GenericObject<TabletPadServer, TabletPadClient>;
impl HandleEvent for TabletPad {
type Event = zwp_tablet_pad_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_pad_v2::Event => [
Group { |pad_group| &TabletPadGroup::from_client(pad_group, state).server },
Path { path },
Buttons { buttons },
Done,
Button {
time,
button,
|state| convert_wenum(state)
},
Enter {
serial,
|tablet| {
let key: &LateInitObjectKey<TabletClient> = tablet.data().unwrap();
let Some(tablet): Option<&Tablet> = state.objects.get(**key).map(|o| o.as_ref()) else {
return;
};
&tablet.server
},
|surface| {
let Some(surface_data) = state.get_server_surface_from_client(surface) else {
return;
};
surface_data
}
},
Leave {
serial,
|surface| {
let Some(surface_data) = state.get_server_surface_from_client(surface) else {
return;
};
surface_data
}
},
Removed
]
}
}
}
pub type TabletTool = GenericObject<TabletToolServer, TabletToolClient>;
impl HandleEvent for TabletTool {
type Event = zwp_tablet_tool_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_tool_v2::Event => [
Type { |tool_type| convert_wenum(tool_type) },
HardwareSerial { hardware_serial_hi, hardware_serial_lo },
HardwareIdWacom { hardware_id_hi, hardware_id_lo },
Capability { |capability| convert_wenum(capability) },
Done,
Removed,
ProximityIn {
serial,
|tablet| {
let key: &LateInitObjectKey<TabletClient> = tablet.data().unwrap();
let Some(tablet): Option<&Tablet> = state.objects.get(**key).map(|o| o.as_ref()) else {
return;
};
&tablet.server
},
|surface| {
let Some(surface_data) = state.get_server_surface_from_client(surface) else {
return;
};
surface_data
}
},
ProximityOut,
Down { serial },
Up,
Motion { x, y },
Pressure { pressure },
Tilt { tilt_x, tilt_y },
Rotation { degrees },
Slider { position },
Wheel { degrees, clicks },
Button { serial, button, |state| convert_wenum(state) },
Frame { time },
]
}
}
}
pub type TabletPadGroup = GenericObject<TabletPadGroupServer, TabletPadGroupClient>;
impl HandleEvent for TabletPadGroup {
type Event = zwp_tablet_pad_group_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_pad_group_v2::Event => [
Buttons { buttons },
Ring { |ring| &TabletPadRing::from_client(ring, state).server },
Strip { |strip| &TabletPadStrip::from_client(strip, state).server },
Modes { modes },
Done,
ModeSwitch { time, serial, mode }
]
}
}
}
pub type TabletPadRing = GenericObject<TabletPadRingServer, TabletPadRingClient>;
impl HandleEvent for TabletPadRing {
type Event = zwp_tablet_pad_ring_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_pad_ring_v2::Event => [
Source { |source| convert_wenum(source) },
Angle { degrees },
Stop,
Frame { time }
]
}
}
}
pub type TabletPadStrip = GenericObject<TabletPadStripServer, TabletPadStripClient>;
impl HandleEvent for TabletPadStrip {
type Event = zwp_tablet_pad_strip_v2::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) {
simple_event_shunt! {
self.server, event: zwp_tablet_pad_strip_v2::Event => [
Source { |source| convert_wenum(source) },
Position { position },
Stop,
Frame { time }
]
}
}
}

View file

@ -28,6 +28,7 @@ use wayland_protocols::{
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
tablet::zv2::server::zwp_tablet_manager_v2::ZwpTabletManagerV2,
viewporter::server as s_vp,
},
xdg::{
@ -49,7 +50,7 @@ use wayland_server::{
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
wl_shm::WlShm, wl_surface::WlSurface,
},
DisplayHandle, Resource, WEnum,
Client, DisplayHandle, Resource, WEnum,
};
use wl_drm::{client::wl_drm::WlDrm as WlDrmClient, server::wl_drm::WlDrm as WlDrmServer};
use xcb::x;
@ -362,12 +363,20 @@ pub(crate) enum Object {
Drm(Drm),
Touch(Touch),
ConfinedPointer(ConfinedPointer),
LockedPointer(LockedPointer)
LockedPointer(LockedPointer),
TabletSeat(TabletSeat),
Tablet(Tablet),
TabletTool(TabletTool),
TabletPad(TabletPad),
TabletPadGroup(TabletPadGroup),
TabletPadRing(TabletPadRing),
TabletPadStrip(TabletPadStrip)
}
}
struct WrappedObject(Option<Object>);
#[derive(Default)]
pub(crate) struct WrappedObject(Option<Object>);
impl<T> From<T> for WrappedObject
where
@ -450,7 +459,8 @@ fn handle_globals<'a, C: XConnection>(
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
ZxdgOutputManagerV1,
s_vp::wp_viewporter::WpViewporter,
ZwpPointerConstraintsV1
ZwpPointerConstraintsV1,
ZwpTabletManagerV2
];
}
}
@ -467,6 +477,7 @@ pub struct ServerState<C: XConnection> {
windows: HashMap<x::Window, WindowData>,
qh: ClientQueueHandle,
client: Option<Client>,
to_focus: Option<x::Window>,
last_focused_toplevel: Option<x::Window>,
last_hovered: Option<x::Window>,
@ -511,6 +522,7 @@ impl<C: XConnection> ServerState<C> {
Self {
windows: HashMap::new(),
clientside,
client: None,
atoms: None,
qh,
dh,
@ -531,9 +543,11 @@ impl<C: XConnection> ServerState<C> {
}
pub fn connect(&mut self, connection: UnixStream) {
self.dh
.insert_client(connection, std::sync::Arc::new(()))
.unwrap();
self.client = Some(
self.dh
.insert_client(connection, std::sync::Arc::new(()))
.unwrap(),
);
}
pub fn set_x_connection(&mut self, connection: C) {
@ -800,8 +814,7 @@ impl<C: XConnection> ServerState<C> {
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 {
for (key, event) in self.clientside.read_events() {
let Some(object) = &mut self.objects.get_mut(key) else {
warn!("could not handle clientside event: stale surface");
continue;

View file

@ -23,11 +23,27 @@ use wayland_client::{
},
Connection, Proxy, WEnum,
};
use wayland_protocols::{
wp::{
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
pointer_constraints::zv1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
tablet::zv2::client::{
zwp_tablet_manager_v2::{self, ZwpTabletManagerV2},
zwp_tablet_pad_group_v2::{
self, ZwpTabletPadGroupV2, EVT_RING_OPCODE, EVT_STRIP_OPCODE,
},
zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2,
zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2,
zwp_tablet_pad_v2::{self, ZwpTabletPadV2, EVT_GROUP_OPCODE},
zwp_tablet_seat_v2::{
self, ZwpTabletSeatV2, EVT_PAD_ADDED_OPCODE, EVT_TABLET_ADDED_OPCODE,
EVT_TOOL_ADDED_OPCODE,
},
zwp_tablet_tool_v2::{self, ZwpTabletToolV2},
zwp_tablet_v2::{self, ZwpTabletV2},
},
viewporter::client::wp_viewporter::WpViewporter,
},
xdg::{
@ -93,6 +109,7 @@ struct Compositor {
shm: TestObject<WlShm>,
shell: TestObject<XwaylandShellV1>,
seat: TestObject<WlSeat>,
tablet_man: TestObject<ZwpTabletManagerV2>
}
}
@ -298,6 +315,23 @@ impl TestFixture {
let events = std::mem::take(&mut *self.registry.data.events.lock().unwrap());
assert!(!events.is_empty());
fn bind<T: Proxy + Sync + Send + 'static>(
registry: &TestObject<WlRegistry>,
name: u32,
version: u32,
) -> TestObject<T>
where
T::Event: Sync + Send + std::fmt::Debug,
{
TestObject::from_request(
&registry.obj,
Req::<WlRegistry>::Bind {
name,
id: (T::interface(), version),
},
)
}
for event in events {
if let Ev::<WlRegistry>::Global {
name,
@ -305,36 +339,18 @@ impl TestFixture {
version,
} = event
{
let bind_req = |interface| Req::<WlRegistry>::Bind {
name,
id: (interface, version),
};
macro_rules! bind {
($field:ident) => {
ret.$field = Some(bind(&self.registry, name, version))
};
}
match interface {
x if x == WlCompositor::interface().name => {
ret.compositor = Some(TestObject::from_request(
&self.registry.obj,
bind_req(WlCompositor::interface()),
));
}
x if x == WlShm::interface().name => {
ret.shm = Some(TestObject::from_request(
&self.registry.obj,
bind_req(WlShm::interface()),
));
}
x if x == XwaylandShellV1::interface().name => {
ret.shell = Some(TestObject::from_request(
&self.registry.obj,
bind_req(XwaylandShellV1::interface()),
));
}
x if x == WlSeat::interface().name => {
ret.seat = Some(TestObject::from_request(
&self.registry.obj,
bind_req(WlSeat::interface()),
));
}
x if x == WlCompositor::interface().name => bind!(compositor),
x if x == WlShm::interface().name => bind!(shm),
x if x == XwaylandShellV1::interface().name => bind!(shell),
x if x == WlSeat::interface().name => bind!(seat),
x if x == ZwpTabletManagerV2::interface().name => bind!(tablet_man),
_ => {}
}
}
@ -349,6 +365,18 @@ impl TestFixture {
ret.into()
}
fn object_data<P>(&self, obj: &P) -> Arc<TestObjectData<P>>
where
P: Proxy + Send + Sync + 'static,
P::Event: Send + Sync + std::fmt::Debug,
{
self.xwls_connection
.get_object_data(obj.id())
.unwrap()
.downcast_arc::<TestObjectData<P>>()
.unwrap()
}
/// Cascade our requests/events through satellite and testwl
fn run(&mut self) {
// Flush our requests to satellite
@ -709,10 +737,35 @@ where
backend: &Backend,
msg: Message<ObjectId, std::os::fd::OwnedFd>,
) -> Option<Arc<dyn ObjectData>> {
fn obj_data<T: Proxy>() -> Arc<dyn ObjectData>
where
T: Send + Sync + 'static,
T::Event: Send + Sync + std::fmt::Debug,
{
Arc::new(TestObjectData::<T>::default())
}
let new_data = match (msg.sender_id.interface().name, msg.opcode) {
(x, opcode) if x == ZwpTabletSeatV2::interface().name => match opcode {
EVT_TABLET_ADDED_OPCODE => Some(obj_data::<ZwpTabletV2>()),
EVT_TOOL_ADDED_OPCODE => Some(obj_data::<ZwpTabletToolV2>()),
EVT_PAD_ADDED_OPCODE => Some(obj_data::<ZwpTabletPadV2>()),
_ => None,
},
(x, EVT_GROUP_OPCODE) if x == ZwpTabletPadV2::interface().name => {
Some(obj_data::<ZwpTabletPadGroupV2>())
}
(x, opcode) if x == ZwpTabletPadGroupV2::interface().name => match opcode {
EVT_RING_OPCODE => Some(obj_data::<ZwpTabletPadRingV2>()),
EVT_STRIP_OPCODE => Some(obj_data::<ZwpTabletPadStripV2>()),
_ => None,
},
_ => None,
};
let connection = Connection::from_backend(backend.clone());
let event = T::parse_event(&connection, msg).unwrap().1;
self.events.lock().unwrap().push(event);
None
new_data
}
fn destroyed(&self, _: ObjectId) {}
@ -849,7 +902,8 @@ fn pass_through_globals() {
WpViewporter,
WlDrm,
ZwpPointerConstraintsV1,
XwaylandShellV1
XwaylandShellV1,
ZwpTabletManagerV2
}
let mut globals = SupportedGlobals::default();
@ -1457,6 +1511,136 @@ fn ignore_toplevel_reconfigure() {
);
}
#[track_caller]
fn events_check<'a, Event: std::fmt::Debug, const N: usize>(
mut it: impl Iterator<Item = Event>,
mut matchers: [Box<dyn FnMut(&Event) -> bool + 'a>; N],
) {
for (idx, matcher) in matchers.iter_mut().enumerate() {
let item = it.next();
if item.is_none() {
panic!("event {idx} does not exist");
}
if !matcher(item.as_ref().unwrap()) {
panic!("event {idx} was wrong ({item:?})");
}
}
let mut remaining = it.peekable();
if remaining.peek().is_some() {
panic!("remaining events: {:?}", remaining.collect::<Vec<_>>());
}
}
#[test]
fn tablet_smoke_test() {
let (mut f, comp) = TestFixture::new_with_compositor();
let seat = TestObject::<ZwpTabletSeatV2>::from_request(
&comp.tablet_man.obj,
zwp_tablet_manager_v2::Request::GetTabletSeat {
seat: comp.seat.obj,
},
);
// Not sure why exactly this requires 4 runs but it works so idk
for _ in 0..4 {
f.run();
}
let events = std::mem::take(&mut *seat.data.events.lock().unwrap()).into_iter();
let (mut tab_id, mut tool_id, mut pad_id) = (None, None, None);
events_check(
events,
[
Box::new(|e| match e {
zwp_tablet_seat_v2::Event::TabletAdded { id } => {
tab_id = Some(id.clone());
true
}
_ => false,
}),
Box::new(|e| match e {
zwp_tablet_seat_v2::Event::ToolAdded { id } => {
tool_id = Some(id.clone());
true
}
_ => false,
}),
Box::new(|e| match e {
zwp_tablet_seat_v2::Event::PadAdded { id } => {
pad_id = Some(id.clone());
true
}
_ => false,
}),
],
);
let (tab_id, tool_id, pad_id) = (tab_id.unwrap(), tool_id.unwrap(), pad_id.unwrap());
// For reasons beyond my mortal understanding, `id.object_data()` does not work properly.
let tab_data = f.object_data(&tab_id);
let tab_events = std::mem::take(&mut *tab_data.events.lock().unwrap()).into_iter();
events_check(
tab_events,
[
Box::new(|e| match e {
zwp_tablet_v2::Event::Name { name } if name == "tabby" => true,
_ => false,
}),
Box::new(|e| matches!(e, zwp_tablet_v2::Event::Done)),
],
);
let tool_data = f.object_data(&tool_id);
let tool_events = std::mem::take(&mut *tool_data.events.lock().unwrap()).into_iter();
events_check(
tool_events,
[
Box::new(|e| {
matches!(
e,
zwp_tablet_tool_v2::Event::Type {
tool_type: WEnum::Value(zwp_tablet_tool_v2::Type::Finger)
}
)
}),
Box::new(|e| matches!(e, zwp_tablet_tool_v2::Event::Done)),
],
);
let pad_data = f.object_data(&pad_id);
let pad_events = std::mem::take(&mut *pad_data.events.lock().unwrap()).into_iter();
let mut group = None;
events_check(
pad_events,
[
Box::new(|e| matches!(e, zwp_tablet_pad_v2::Event::Buttons { buttons: 5 })),
Box::new(|e| match e {
zwp_tablet_pad_v2::Event::Group { pad_group } => {
group = Some(pad_group.clone());
true
}
_ => false,
}),
Box::new(|e| matches!(e, zwp_tablet_pad_v2::Event::Done)),
],
);
let group = group.unwrap();
let g_data = f.object_data(&group);
let g_events = std::mem::take(&mut *g_data.events.lock().unwrap()).into_iter();
events_check(
g_events,
[
Box::new(|e| match e {
zwp_tablet_pad_group_v2::Event::Buttons { buttons } if buttons.is_empty() => true,
_ => false,
}),
Box::new(|e| matches!(e, zwp_tablet_pad_group_v2::Event::Ring { .. })),
Box::new(|e| matches!(e, zwp_tablet_pad_group_v2::Event::Strip { .. })),
Box::new(|e| matches!(e, zwp_tablet_pad_group_v2::Event::Done)),
],
)
}
/// See Pointer::handle_event for an explanation.
#[test]
fn popup_pointer_motion_workaround() {}