1484 lines
52 KiB
Rust
1484 lines
52 KiB
Rust
use super::clientside::LateInitObjectKey;
|
|
use super::*;
|
|
use hecs::CommandBuffer;
|
|
use log::{debug, trace, warn};
|
|
use macros::simple_event_shunt;
|
|
use std::os::fd::AsFd;
|
|
use wayland_client::{protocol as client, Proxy};
|
|
use wayland_protocols::{
|
|
wp::{
|
|
fractional_scale::v1::client::wp_fractional_scale_v1,
|
|
pointer_constraints::zv1::server::{
|
|
zwp_confined_pointer_v1::ZwpConfinedPointerV1 as ConfinedPointerServer,
|
|
zwp_locked_pointer_v1::ZwpLockedPointerV1 as LockedPointerServer,
|
|
},
|
|
relative_pointer::zv1::{
|
|
client::zwp_relative_pointer_v1,
|
|
server::zwp_relative_pointer_v1::ZwpRelativePointerV1 as RelativePointerServer,
|
|
},
|
|
tablet::zv2::{
|
|
client::{
|
|
zwp_tablet_pad_group_v2, zwp_tablet_pad_ring_v2, zwp_tablet_pad_strip_v2,
|
|
zwp_tablet_pad_v2, zwp_tablet_seat_v2, zwp_tablet_tool_v2,
|
|
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},
|
|
xdg_output::zv1::{
|
|
client::zxdg_output_v1, server::zxdg_output_v1::ZxdgOutputV1 as XdgOutputServer,
|
|
},
|
|
},
|
|
};
|
|
use wayland_server::protocol::{
|
|
wl_buffer::WlBuffer, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer,
|
|
wl_seat::WlSeat, wl_touch::WlTouch,
|
|
};
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub(super) struct SurfaceScaleFactor(pub f64);
|
|
|
|
#[derive(hecs::Bundle)]
|
|
pub(super) struct SurfaceBundle {
|
|
pub client: client::wl_surface::WlSurface,
|
|
pub server: WlSurface,
|
|
pub viewport: WpViewport,
|
|
pub scale: SurfaceScaleFactor,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum SurfaceEvents {
|
|
WlSurface(client::wl_surface::Event),
|
|
XdgSurface(xdg_surface::Event),
|
|
Toplevel(xdg_toplevel::Event),
|
|
Popup(xdg_popup::Event),
|
|
FractionalScale(wp_fractional_scale_v1::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_from!(wp_fractional_scale_v1::Event, FractionalScale);
|
|
|
|
impl Event for SurfaceEvents {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
match self {
|
|
SurfaceEvents::WlSurface(event) => Self::surface_event(event, target, state),
|
|
SurfaceEvents::XdgSurface(event) => Self::xdg_event(event, target, state),
|
|
SurfaceEvents::Toplevel(event) => Self::toplevel_event(event, target, state),
|
|
SurfaceEvents::Popup(event) => Self::popup_event(event, target, state),
|
|
SurfaceEvents::FractionalScale(event) => match event {
|
|
wp_fractional_scale_v1::Event::PreferredScale { scale } => {
|
|
let entity = state.world.entity(target).unwrap();
|
|
let factor = scale as f64 / 120.0;
|
|
debug!(
|
|
"{} scale factor: {}",
|
|
entity.get::<&WlSurface>().unwrap().id(),
|
|
factor
|
|
);
|
|
|
|
entity.get::<&mut SurfaceScaleFactor>().unwrap().0 = factor;
|
|
|
|
if let Some(OnOutput(output)) = entity.get::<&OnOutput>().as_deref().copied() {
|
|
if update_output_scale(
|
|
state.world.query_one(output).unwrap(),
|
|
OutputScaleFactor::Fractional(factor),
|
|
) {
|
|
state.updated_outputs.push(output);
|
|
}
|
|
}
|
|
if entity.has::<WindowData>() {
|
|
update_surface_viewport(state.world.query_one(target).unwrap());
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SurfaceEvents {
|
|
fn surface_event(
|
|
event: client::wl_surface::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<impl XConnection>,
|
|
) {
|
|
use client::wl_surface::Event;
|
|
|
|
let data = state.world.entity(target).unwrap();
|
|
let surface = data.get::<&WlSurface>().unwrap();
|
|
let mut cmd = CommandBuffer::new();
|
|
match event {
|
|
Event::Enter { output } => {
|
|
let output_entity = output.data().copied().unwrap();
|
|
let Ok(output_data) = state.world.entity(output_entity) else {
|
|
return;
|
|
};
|
|
let Some(output) = output_data.get::<&WlOutput>() else {
|
|
return;
|
|
};
|
|
|
|
surface.enter(&output);
|
|
let on_output = OnOutput(output_entity);
|
|
|
|
debug!("{} entered {}", surface.id(), output.id());
|
|
|
|
let mut query = data.query::<(&x::Window, &mut WindowData)>();
|
|
if let Some((window, win_data)) = query.get() {
|
|
let dimensions = output_data.get::<&OutputDimensions>().unwrap();
|
|
win_data.update_output_offset(
|
|
*window,
|
|
WindowOutputOffset {
|
|
x: dimensions.x - state.global_output_offset.x.value,
|
|
y: dimensions.y - state.global_output_offset.y.value,
|
|
},
|
|
&mut state.connection,
|
|
);
|
|
if state.last_focused_toplevel == Some(*window) {
|
|
let output = get_output_name(Some(&on_output), &state.world);
|
|
let conn = state.connection.as_mut().unwrap();
|
|
debug!("focused window changed outputs - resetting primary output");
|
|
conn.focus_window(*window, output);
|
|
}
|
|
|
|
if state.fractional_scale.is_none() {
|
|
let output_scale = output_data.get::<&OutputScaleFactor>().unwrap().get();
|
|
data.get::<&mut SurfaceScaleFactor>().unwrap().0 = output_scale;
|
|
drop(query);
|
|
update_surface_viewport(state.world.query_one(target).unwrap());
|
|
} else {
|
|
let scale = data.get::<&SurfaceScaleFactor>().unwrap();
|
|
if update_output_scale(
|
|
state.world.query_one(on_output.0).unwrap(),
|
|
OutputScaleFactor::Fractional(scale.0),
|
|
) {
|
|
state.updated_outputs.push(on_output.0);
|
|
}
|
|
}
|
|
}
|
|
cmd.insert_one(target, on_output);
|
|
}
|
|
Event::Leave { output } => {
|
|
let output_entity = output.data().copied().unwrap();
|
|
let Ok(output) = state.world.get::<&WlOutput>(output_entity) else {
|
|
return;
|
|
};
|
|
surface.leave(&output);
|
|
if data
|
|
.get::<&OnOutput>()
|
|
.is_some_and(|o| o.0 == output_entity)
|
|
{
|
|
cmd.remove_one::<OnOutput>(target);
|
|
}
|
|
}
|
|
Event::PreferredBufferScale { .. } => {}
|
|
other => warn!("unhandled surface request: {other:?}"),
|
|
}
|
|
|
|
drop(surface);
|
|
cmd.run_on(&mut state.world);
|
|
}
|
|
|
|
fn xdg_event<C: XConnection>(
|
|
event: xdg_surface::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<C>,
|
|
) {
|
|
let connection = state.connection.as_mut().unwrap();
|
|
let xdg_surface::Event::Configure { serial } = event else {
|
|
unreachable!();
|
|
};
|
|
|
|
let data = state.world.entity(target).unwrap();
|
|
let mut xdg = hecs::RefMut::map(data.get::<&mut SurfaceRole>().unwrap(), |r| {
|
|
r.xdg_mut().unwrap()
|
|
});
|
|
xdg.surface.ack_configure(serial);
|
|
xdg.configured = true;
|
|
|
|
let pending = xdg.pending.take();
|
|
drop(xdg);
|
|
|
|
if let Some(pending) = pending {
|
|
let mut query = data.query::<(&SurfaceScaleFactor, &x::Window, &mut WindowData)>();
|
|
let (scale_factor, window, window_data) = query.get().unwrap();
|
|
|
|
let window = *window;
|
|
let x = (pending.x as f64 * scale_factor.0) as i32 + window_data.output_offset.x;
|
|
let y = (pending.y as f64 * scale_factor.0) as i32 + window_data.output_offset.y;
|
|
let width = if pending.width > 0 {
|
|
(pending.width as f64 * scale_factor.0) as u16
|
|
} else {
|
|
window_data.attrs.dims.width
|
|
};
|
|
let height = if pending.height > 0 {
|
|
(pending.height as f64 * scale_factor.0) as u16
|
|
} else {
|
|
window_data.attrs.dims.height
|
|
};
|
|
debug!(
|
|
"configuring {} ({window:?}): {x}x{y}, {width}x{height}",
|
|
data.get::<&WlSurface>().unwrap().id(),
|
|
);
|
|
connection.set_window_dims(
|
|
window,
|
|
PendingSurfaceState {
|
|
x,
|
|
y,
|
|
width: width as _,
|
|
height: height as _,
|
|
},
|
|
);
|
|
window_data.attrs.dims = WindowDims {
|
|
x: x as i16,
|
|
y: y as i16,
|
|
width,
|
|
height,
|
|
};
|
|
|
|
drop(query);
|
|
update_surface_viewport(state.world.query_one(target).unwrap());
|
|
}
|
|
|
|
let (surface, attach, callback) = state
|
|
.world
|
|
.query_one_mut::<(
|
|
&client::wl_surface::WlSurface,
|
|
Option<&SurfaceAttach>,
|
|
Option<&WlCallback>,
|
|
)>(target)
|
|
.unwrap();
|
|
|
|
let mut cmd = CommandBuffer::new();
|
|
|
|
if let Some(SurfaceAttach { buffer, x, y }) = attach {
|
|
surface.attach(buffer.as_ref(), *x, *y);
|
|
cmd.remove_one::<SurfaceAttach>(target);
|
|
}
|
|
if let Some(cb) = callback {
|
|
surface.frame(&state.qh, cb.clone());
|
|
cmd.remove_one::<client::wl_callback::WlCallback>(target);
|
|
}
|
|
surface.commit();
|
|
cmd.run_on(&mut state.world);
|
|
}
|
|
|
|
fn toplevel_event<C: XConnection>(
|
|
event: xdg_toplevel::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<C>,
|
|
) {
|
|
let data = state.world.entity(target).unwrap();
|
|
match event {
|
|
xdg_toplevel::Event::Configure {
|
|
width,
|
|
height,
|
|
states,
|
|
} => {
|
|
debug!(
|
|
"configuring toplevel {} {width}x{height}, {states:?}",
|
|
data.get::<&WlSurface>().unwrap().id()
|
|
);
|
|
|
|
let mut role = data.get::<&mut SurfaceRole>().unwrap();
|
|
if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role {
|
|
let prev_fs = toplevel.fullscreen;
|
|
toplevel.fullscreen =
|
|
states.contains(&(u32::from(xdg_toplevel::State::Fullscreen) as u8));
|
|
if toplevel.fullscreen != prev_fs {
|
|
state.connection.as_mut().unwrap().set_fullscreen(
|
|
*data.get::<&x::Window>().unwrap(),
|
|
toplevel.fullscreen,
|
|
);
|
|
}
|
|
};
|
|
|
|
role.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
|
|
width,
|
|
height,
|
|
..Default::default()
|
|
});
|
|
}
|
|
xdg_toplevel::Event::Close => {
|
|
let window = *data.get::<&x::Window>().unwrap();
|
|
state.close_x_window(window);
|
|
}
|
|
// TODO: support capabilities (minimize, maximize, etc)
|
|
xdg_toplevel::Event::WmCapabilities { .. } => {}
|
|
xdg_toplevel::Event::ConfigureBounds { .. } => {}
|
|
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
|
|
}
|
|
}
|
|
|
|
fn popup_event<C: XConnection>(
|
|
event: xdg_popup::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<C>,
|
|
) {
|
|
let data = state.world.entity(target).unwrap();
|
|
match event {
|
|
xdg_popup::Event::Configure {
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
} => {
|
|
trace!(
|
|
"popup configure {}: {x}x{y}, {width}x{height}",
|
|
data.get::<&WlSurface>().unwrap().id()
|
|
);
|
|
data.get::<&mut SurfaceRole>()
|
|
.unwrap()
|
|
.xdg_mut()
|
|
.unwrap()
|
|
.pending = Some(PendingSurfaceState {
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
});
|
|
}
|
|
xdg_popup::Event::Repositioned { .. } => {}
|
|
xdg_popup::Event::PopupDone => {
|
|
state
|
|
.connection
|
|
.as_mut()
|
|
.unwrap()
|
|
.unmap_window(*data.get::<&x::Window>().unwrap());
|
|
}
|
|
other => todo!("{other:?}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn update_surface_viewport(
|
|
mut surface_query: hecs::QueryOne<(
|
|
&WindowData,
|
|
&WpViewport,
|
|
&SurfaceScaleFactor,
|
|
Option<&SurfaceRole>,
|
|
&WlSurface,
|
|
)>,
|
|
) {
|
|
let (window_data, viewport, scale_factor, role, surface) = surface_query.get().unwrap();
|
|
let dims = &window_data.attrs.dims;
|
|
let size_hints = &window_data.attrs.size_hints;
|
|
|
|
let width = (dims.width as f64 / scale_factor.0) as i32;
|
|
let height = (dims.height as f64 / scale_factor.0) as i32;
|
|
if width > 0 && height > 0 {
|
|
viewport.set_destination(width, height);
|
|
}
|
|
debug!("{} viewport: {width}x{height}", surface.id());
|
|
if let Some(hints) = size_hints {
|
|
let Some(SurfaceRole::Toplevel(Some(data))) = &role else {
|
|
warn!(
|
|
"Trying to update size hints on {}, but toplevel role data is missing",
|
|
surface.id()
|
|
);
|
|
return;
|
|
};
|
|
|
|
if let Some(min) = hints.min_size {
|
|
data.toplevel.set_min_size(
|
|
(min.width as f64 / scale_factor.0) as i32,
|
|
(min.height as f64 / scale_factor.0) as i32,
|
|
);
|
|
}
|
|
if let Some(max) = hints.max_size {
|
|
data.toplevel.set_max_size(
|
|
(max.width as f64 / scale_factor.0) as i32,
|
|
(max.height as f64 / scale_factor.0) as i32,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for client::wl_buffer::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
// The only event from a buffer would be the release.
|
|
state.world.get::<&WlBuffer>(target).unwrap().release();
|
|
}
|
|
}
|
|
|
|
impl Event for client::wl_seat::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state.world.get::<&WlSeat>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
Capabilities { |capabilities| convert_wenum(capabilities) },
|
|
Name { name }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PendingEnter(client::wl_pointer::Event);
|
|
|
|
impl Event for client::wl_pointer::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, 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.
|
|
|
|
match self {
|
|
Self::Enter {
|
|
serial,
|
|
ref surface,
|
|
surface_x,
|
|
surface_y,
|
|
} => {
|
|
let mut cmd = CommandBuffer::new();
|
|
let pending_enter = state.world.remove_one::<PendingEnter>(target).ok();
|
|
let server = state.world.get::<&WlPointer>(target).unwrap();
|
|
let mut query = surface.data().copied().and_then(|e| {
|
|
state
|
|
.world
|
|
.query_one::<(&WlSurface, &SurfaceRole, &SurfaceScaleFactor, &x::Window)>(e)
|
|
.ok()
|
|
});
|
|
let Some((surface, role, scale, window)) = query.as_mut().and_then(|q| q.get())
|
|
else {
|
|
warn!("could not enter surface: stale surface");
|
|
return;
|
|
};
|
|
|
|
cmd.insert(target, (*scale,));
|
|
|
|
let surface_is_popup = matches!(role, SurfaceRole::Popup(_));
|
|
let mut do_enter = || {
|
|
debug!("pointer entering {} ({serial} {})", surface.id(), scale.0);
|
|
server.enter(serial, surface, surface_x * scale.0, surface_y * scale.0);
|
|
state.connection.as_mut().unwrap().raise_to_top(*window);
|
|
if !surface_is_popup {
|
|
state.last_hovered = Some(*window);
|
|
}
|
|
};
|
|
|
|
if !surface_is_popup {
|
|
do_enter();
|
|
} else {
|
|
match pending_enter {
|
|
Some(e) => {
|
|
let PendingEnter(client::wl_pointer::Event::Enter {
|
|
serial: pending_serial,
|
|
..
|
|
}) = e
|
|
else {
|
|
unreachable!();
|
|
};
|
|
if serial == pending_serial {
|
|
do_enter();
|
|
} else {
|
|
cmd.insert(target, (PendingEnter(self),));
|
|
}
|
|
}
|
|
None => {
|
|
cmd.insert(target, (PendingEnter(self),));
|
|
}
|
|
}
|
|
}
|
|
drop(query);
|
|
drop(server);
|
|
cmd.run_on(&mut state.world);
|
|
}
|
|
client::wl_pointer::Event::Leave { serial, surface } => {
|
|
let _ = state.world.remove_one::<PendingEnter>(target);
|
|
if !surface.is_alive() {
|
|
return;
|
|
}
|
|
debug!("leaving surface ({serial})");
|
|
let _ = state.world.remove_one::<PendingEnter>(target);
|
|
if let Some(surface) = surface
|
|
.data()
|
|
.copied()
|
|
.and_then(|key| state.world.get::<&WlSurface>(key).ok())
|
|
{
|
|
state
|
|
.world
|
|
.get::<&WlPointer>(target)
|
|
.unwrap()
|
|
.leave(serial, &surface);
|
|
} else {
|
|
warn!("could not leave surface: stale surface");
|
|
}
|
|
}
|
|
client::wl_pointer::Event::Motion {
|
|
time,
|
|
surface_x,
|
|
surface_y,
|
|
} => {
|
|
let pending_enter = state.world.get::<&PendingEnter>(target).ok();
|
|
match pending_enter.as_deref() {
|
|
Some(p) => {
|
|
let PendingEnter(client::wl_pointer::Event::Enter {
|
|
serial,
|
|
surface,
|
|
surface_x,
|
|
surface_y,
|
|
}) = p
|
|
else {
|
|
unreachable!();
|
|
};
|
|
if surface
|
|
.data()
|
|
.copied()
|
|
.is_some_and(|key| state.world.contains(key))
|
|
{
|
|
trace!("resending enter ({serial}) before motion");
|
|
let enter_event = client::wl_pointer::Event::Enter {
|
|
serial: *serial,
|
|
surface: surface.clone(),
|
|
surface_x: *surface_x,
|
|
surface_y: *surface_y,
|
|
};
|
|
|
|
drop(pending_enter);
|
|
Self::handle(enter_event, target, state);
|
|
Self::handle(self, target, state);
|
|
} else {
|
|
warn!("could not move pointer to surface ({serial}): stale surface");
|
|
}
|
|
}
|
|
None => {
|
|
drop(pending_enter);
|
|
let (server, scale) = state
|
|
.world
|
|
.query_one_mut::<(&WlPointer, &SurfaceScaleFactor)>(target)
|
|
.unwrap();
|
|
trace!(
|
|
target: "pointer_position",
|
|
"pointer motion {} {}",
|
|
surface_x * scale.0,
|
|
surface_y * scale.0
|
|
);
|
|
server.motion(time, surface_x * scale.0, surface_y * scale.0);
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
let server = state.world.get::<&WlPointer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
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)
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for client::wl_keyboard::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let data = state.world.entity(target).unwrap();
|
|
let keyboard = data.get::<&WlKeyboard>().unwrap();
|
|
match self {
|
|
client::wl_keyboard::Event::Enter {
|
|
serial,
|
|
surface,
|
|
keys,
|
|
} => {
|
|
let mut query = surface.data().copied().and_then(|key| {
|
|
state
|
|
.world
|
|
.query_one::<(&x::Window, &WlSurface, Option<&OnOutput>)>(key)
|
|
.ok()
|
|
});
|
|
let Some((window, surface, output)) = query.as_mut().and_then(|q| q.get()) else {
|
|
return;
|
|
};
|
|
state.last_kb_serial = Some((
|
|
data.get::<&client::wl_seat::WlSeat>()
|
|
.as_deref()
|
|
.unwrap()
|
|
.clone(),
|
|
serial,
|
|
));
|
|
let output_name = get_output_name(output, &state.world);
|
|
state.to_focus = Some(FocusData {
|
|
window: *window,
|
|
output_name,
|
|
});
|
|
keyboard.enter(serial, surface, keys);
|
|
}
|
|
client::wl_keyboard::Event::Leave { serial, surface } => {
|
|
if !surface.is_alive() {
|
|
return;
|
|
}
|
|
let mut query = surface
|
|
.data()
|
|
.copied()
|
|
.and_then(|key| state.world.query_one::<(&x::Window, &WlSurface)>(key).ok());
|
|
let Some((window, surface)) = query.as_mut().and_then(|q| q.get()) else {
|
|
return;
|
|
};
|
|
if state.to_focus.as_ref().map(|d| d.window) == Some(*window) {
|
|
state.to_focus.take();
|
|
} else {
|
|
state.unfocus = true;
|
|
}
|
|
keyboard.leave(serial, surface);
|
|
}
|
|
client::wl_keyboard::Event::Key {
|
|
serial,
|
|
time,
|
|
key,
|
|
state: key_state,
|
|
} => {
|
|
state.last_kb_serial = Some((
|
|
data.get::<&client::wl_seat::WlSeat>()
|
|
.as_deref()
|
|
.unwrap()
|
|
.clone(),
|
|
serial,
|
|
));
|
|
keyboard.key(serial, time, key, convert_wenum(key_state));
|
|
}
|
|
_ => simple_event_shunt! {
|
|
keyboard, self => [
|
|
Keymap {
|
|
|format| convert_wenum(format),
|
|
|fd| fd.as_fd(),
|
|
size
|
|
},
|
|
Modifiers {
|
|
serial,
|
|
mods_depressed,
|
|
mods_latched,
|
|
mods_locked,
|
|
group
|
|
},
|
|
RepeatInfo {
|
|
rate,
|
|
delay
|
|
}
|
|
]
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for client::wl_touch::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
match self {
|
|
Self::Down {
|
|
serial,
|
|
time,
|
|
surface,
|
|
id,
|
|
x,
|
|
y,
|
|
} => {
|
|
let mut cmd = CommandBuffer::new();
|
|
{
|
|
let mut s_query = surface.data().copied().and_then(|key| {
|
|
state
|
|
.world
|
|
.query_one::<(&WlSurface, &SurfaceScaleFactor)>(key)
|
|
.ok()
|
|
});
|
|
let Some((s_surface, s_factor)) = s_query.as_mut().and_then(|q| q.get()) else {
|
|
return;
|
|
};
|
|
|
|
cmd.insert(target, (*s_factor,));
|
|
let touch = state.world.get::<&WlTouch>(target).unwrap();
|
|
touch.down(serial, time, s_surface, id, x * s_factor.0, y * s_factor.0);
|
|
}
|
|
cmd.run_on(&mut state.world);
|
|
}
|
|
Self::Motion { time, id, x, y } => {
|
|
let (touch, scale) = state
|
|
.world
|
|
.query_one_mut::<(&WlTouch, &SurfaceScaleFactor)>(target)
|
|
.unwrap();
|
|
touch.motion(time, id, x * scale.0, y * scale.0);
|
|
}
|
|
_ => {
|
|
let touch = state.world.get::<&WlTouch>(target).unwrap();
|
|
simple_event_shunt! {
|
|
touch, self => [
|
|
Up { serial, time, id },
|
|
Frame,
|
|
Cancel,
|
|
Shape { id, major, minor },
|
|
Orientation { id, orientation }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub(super) struct OnOutput(pub Entity);
|
|
struct OutputName(String);
|
|
fn get_output_name(output: Option<&OnOutput>, world: &World) -> Option<String> {
|
|
output.map(|o| world.get::<&OutputName>(o.0).unwrap().0.clone())
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
pub(super) enum OutputScaleFactor {
|
|
Output(i32),
|
|
Fractional(f64),
|
|
}
|
|
|
|
impl OutputScaleFactor {
|
|
pub(super) fn get(&self) -> f64 {
|
|
match *self {
|
|
Self::Output(o) => o as _,
|
|
Self::Fractional(f) => f,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn update_output_scale(
|
|
mut output_scale: hecs::QueryOne<&mut OutputScaleFactor>,
|
|
factor: OutputScaleFactor,
|
|
) -> bool {
|
|
let output_scale = output_scale.get().unwrap();
|
|
if matches!(output_scale, OutputScaleFactor::Fractional(..))
|
|
&& matches!(factor, OutputScaleFactor::Output(..))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if *output_scale != factor {
|
|
*output_scale = factor;
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
enum OutputDimensionsSource {
|
|
// The data in this variant is the values needed for the wl_output.geometry event.
|
|
Wl {
|
|
physical_width: i32,
|
|
physical_height: i32,
|
|
subpixel: WEnum<client::wl_output::Subpixel>,
|
|
make: String,
|
|
model: String,
|
|
transform: WEnum<client::wl_output::Transform>,
|
|
},
|
|
Xdg,
|
|
}
|
|
|
|
pub(super) struct OutputDimensions {
|
|
source: OutputDimensionsSource,
|
|
pub x: i32,
|
|
pub y: i32,
|
|
pub width: i32,
|
|
pub height: i32,
|
|
rotated_90: bool,
|
|
}
|
|
|
|
impl Default for OutputDimensions {
|
|
fn default() -> Self {
|
|
Self {
|
|
source: OutputDimensionsSource::Wl {
|
|
physical_height: 0,
|
|
physical_width: 0,
|
|
subpixel: WEnum::Value(client::wl_output::Subpixel::Unknown),
|
|
make: "<unknown>".to_string(),
|
|
model: "<unknown>".to_string(),
|
|
transform: WEnum::Value(client::wl_output::Transform::Normal),
|
|
},
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
rotated_90: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_output_offset(
|
|
output: Entity,
|
|
source: OutputDimensionsSource,
|
|
x: i32,
|
|
y: i32,
|
|
state: &mut ServerState<impl XConnection>,
|
|
) {
|
|
{
|
|
let mut dimensions = state.world.get::<&mut OutputDimensions>(output).unwrap();
|
|
if matches!(source, OutputDimensionsSource::Wl { .. })
|
|
&& matches!(dimensions.source, OutputDimensionsSource::Xdg)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let global_offset = &mut state.global_output_offset;
|
|
let mut maybe_update_dimension = |value, dim: &mut GlobalOutputOffsetDimension| {
|
|
if value < dim.value {
|
|
*dim = GlobalOutputOffsetDimension {
|
|
owner: Some(output),
|
|
value,
|
|
};
|
|
state.global_offset_updated = true;
|
|
} else if dim.owner == Some(output) && value > dim.value {
|
|
*dim = Default::default();
|
|
state.global_offset_updated = true;
|
|
}
|
|
};
|
|
|
|
maybe_update_dimension(x, &mut global_offset.x);
|
|
maybe_update_dimension(y, &mut global_offset.y);
|
|
|
|
dimensions.source = source;
|
|
dimensions.x = x;
|
|
dimensions.y = y;
|
|
debug!(
|
|
"moving {} to {x}x{y}",
|
|
state.world.get::<&WlOutput>(output).unwrap().id()
|
|
);
|
|
}
|
|
|
|
update_window_output_offsets(
|
|
output,
|
|
&state.global_output_offset,
|
|
&state.world,
|
|
&mut state.connection,
|
|
);
|
|
}
|
|
|
|
fn update_window_output_offsets(
|
|
output: Entity,
|
|
global_output_offset: &GlobalOutputOffset,
|
|
world: &World,
|
|
connection: &mut Option<impl XConnection>,
|
|
) {
|
|
let dimensions = world.get::<&OutputDimensions>(output).unwrap();
|
|
let mut query = world.query::<(&x::Window, &mut WindowData, &OnOutput)>();
|
|
|
|
for (_, (window, data, _)) in query
|
|
.into_iter()
|
|
.filter(|(_, (_, _, on_output))| on_output.0 == output)
|
|
{
|
|
data.update_output_offset(
|
|
*window,
|
|
WindowOutputOffset {
|
|
x: dimensions.x - global_output_offset.x.value,
|
|
y: dimensions.y - global_output_offset.y.value,
|
|
},
|
|
connection,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub(super) fn update_global_output_offset(
|
|
output: Entity,
|
|
global_output_offset: &GlobalOutputOffset,
|
|
world: &World,
|
|
connection: &mut Option<impl XConnection>,
|
|
) {
|
|
let entity = world.entity(output).unwrap();
|
|
let mut query = entity.query::<(&OutputDimensions, &WlOutput)>();
|
|
let (dimensions, server) = query.get().unwrap();
|
|
|
|
let x = dimensions.x - global_output_offset.x.value;
|
|
let y = dimensions.y - global_output_offset.y.value;
|
|
|
|
match &dimensions.source {
|
|
OutputDimensionsSource::Wl {
|
|
physical_width,
|
|
physical_height,
|
|
subpixel,
|
|
make,
|
|
model,
|
|
transform,
|
|
} => {
|
|
server.geometry(
|
|
x,
|
|
y,
|
|
*physical_width,
|
|
*physical_height,
|
|
convert_wenum(*subpixel),
|
|
make.clone(),
|
|
model.clone(),
|
|
convert_wenum(*transform),
|
|
);
|
|
}
|
|
OutputDimensionsSource::Xdg => {
|
|
entity
|
|
.get::<&XdgOutputServer>()
|
|
.unwrap()
|
|
.logical_position(x, y);
|
|
}
|
|
}
|
|
|
|
server.done();
|
|
drop(query);
|
|
|
|
update_window_output_offsets(output, global_output_offset, world, connection);
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum OutputEvent {
|
|
Wl(client::wl_output::Event),
|
|
Xdg(zxdg_output_v1::Event),
|
|
}
|
|
|
|
impl From<client::wl_output::Event> for ObjectEvent {
|
|
fn from(value: client::wl_output::Event) -> Self {
|
|
Self::Output(OutputEvent::Wl(value))
|
|
}
|
|
}
|
|
|
|
impl From<zxdg_output_v1::Event> for ObjectEvent {
|
|
fn from(value: zxdg_output_v1::Event) -> Self {
|
|
Self::Output(OutputEvent::Xdg(value))
|
|
}
|
|
}
|
|
|
|
impl Event for OutputEvent {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
match self {
|
|
OutputEvent::Xdg(event) => Self::xdg_event(event, target, state),
|
|
OutputEvent::Wl(event) => Self::wl_event(event, target, state),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl OutputEvent {
|
|
fn wl_event<C: XConnection>(
|
|
event: client::wl_output::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<C>,
|
|
) {
|
|
use client::wl_output::Event;
|
|
match event {
|
|
Event::Geometry {
|
|
x,
|
|
y,
|
|
physical_width,
|
|
physical_height,
|
|
subpixel,
|
|
make,
|
|
model,
|
|
transform,
|
|
} => {
|
|
update_output_offset(
|
|
target,
|
|
OutputDimensionsSource::Wl {
|
|
physical_width,
|
|
physical_height,
|
|
subpixel,
|
|
make: make.clone(),
|
|
model: model.clone(),
|
|
transform,
|
|
},
|
|
x,
|
|
y,
|
|
state,
|
|
);
|
|
|
|
let (output, dimensions, xdg) = state
|
|
.world
|
|
.query_one_mut::<(&WlOutput, &mut OutputDimensions, Option<&XdgOutputServer>)>(
|
|
target,
|
|
)
|
|
.unwrap();
|
|
|
|
output.geometry(
|
|
x - state.global_output_offset.x.value,
|
|
y - state.global_output_offset.y.value,
|
|
physical_width,
|
|
physical_height,
|
|
convert_wenum(subpixel),
|
|
make,
|
|
model,
|
|
convert_wenum(transform),
|
|
);
|
|
dimensions.rotated_90 = transform.into_result().is_ok_and(|t| {
|
|
matches!(
|
|
t,
|
|
client::wl_output::Transform::_90
|
|
| client::wl_output::Transform::_270
|
|
| client::wl_output::Transform::Flipped90
|
|
| client::wl_output::Transform::Flipped270
|
|
)
|
|
});
|
|
if let Some(xdg) = xdg {
|
|
if dimensions.rotated_90 {
|
|
xdg.logical_size(dimensions.height, dimensions.width);
|
|
} else {
|
|
xdg.logical_size(dimensions.width, dimensions.height);
|
|
}
|
|
}
|
|
}
|
|
Event::Mode {
|
|
flags,
|
|
width,
|
|
height,
|
|
refresh,
|
|
} => {
|
|
let (output, dimensions) = state
|
|
.world
|
|
.query_one_mut::<(&WlOutput, &mut OutputDimensions)>(target)
|
|
.unwrap();
|
|
|
|
if flags
|
|
.into_result()
|
|
.is_ok_and(|f| f.contains(client::wl_output::Mode::Current))
|
|
{
|
|
dimensions.width = width;
|
|
dimensions.height = height;
|
|
debug!("{} dimensions: {width}x{height}", output.id());
|
|
}
|
|
output.mode(convert_wenum(flags), width, height, refresh);
|
|
}
|
|
Event::Scale { factor } => {
|
|
debug!(
|
|
"{} scale: {factor}",
|
|
state.world.get::<&WlOutput>(target).unwrap().id()
|
|
);
|
|
if update_output_scale(
|
|
state.world.query_one(target).unwrap(),
|
|
OutputScaleFactor::Output(factor),
|
|
) {
|
|
state.updated_outputs.push(target);
|
|
}
|
|
if state.fractional_scale.is_none() {
|
|
state.world.get::<&WlOutput>(target).unwrap().scale(factor);
|
|
}
|
|
}
|
|
Event::Name { name } => {
|
|
state
|
|
.world
|
|
.get::<&WlOutput>(target)
|
|
.unwrap()
|
|
.name(name.clone());
|
|
state.world.insert(target, (OutputName(name),)).unwrap();
|
|
}
|
|
_ => simple_event_shunt! {
|
|
state.world.get::<&WlOutput>(target).unwrap(),
|
|
event: client::wl_output::Event => [
|
|
Description { description },
|
|
Done
|
|
]
|
|
},
|
|
}
|
|
}
|
|
|
|
fn xdg_event<C: XConnection>(
|
|
event: zxdg_output_v1::Event,
|
|
target: Entity,
|
|
state: &mut ServerState<C>,
|
|
) {
|
|
use zxdg_output_v1::Event;
|
|
|
|
match event {
|
|
Event::LogicalPosition { x, y } => {
|
|
update_output_offset(target, OutputDimensionsSource::Xdg, x, y, state);
|
|
state
|
|
.world
|
|
.get::<&XdgOutputServer>(target)
|
|
.unwrap()
|
|
.logical_position(
|
|
x - state.global_output_offset.x.value,
|
|
y - state.global_output_offset.y.value,
|
|
);
|
|
}
|
|
Event::LogicalSize { .. } => {
|
|
let (xdg, dimensions) = state
|
|
.world
|
|
.query_one_mut::<(&XdgOutputServer, &OutputDimensions)>(target)
|
|
.unwrap();
|
|
if dimensions.rotated_90 {
|
|
xdg.logical_size(dimensions.height, dimensions.width);
|
|
} else {
|
|
xdg.logical_size(dimensions.width, dimensions.height);
|
|
}
|
|
}
|
|
_ => simple_event_shunt! {
|
|
state.world.get::<&XdgOutputServer>(target).unwrap(),
|
|
event: zxdg_output_v1::Event => [
|
|
Done,
|
|
Name { name },
|
|
Description { description }
|
|
]
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for wl_drm::client::wl_drm::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state.world.get::<&WlDrmServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
Device { name },
|
|
Format { format },
|
|
Authenticated,
|
|
Capabilities { value }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for c_dmabuf::zwp_linux_dmabuf_feedback_v1::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state
|
|
.world
|
|
.get::<&s_dmabuf::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1>(target)
|
|
.unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
Done,
|
|
FormatTable { |fd| fd.as_fd(), size },
|
|
MainDevice { device },
|
|
TrancheDone,
|
|
TrancheTargetDevice { device },
|
|
TrancheFormats { indices },
|
|
TrancheFlags { |flags| convert_wenum(flags) }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_relative_pointer_v1::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state.world.get::<&RelativePointerServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
RelativeMotion {
|
|
utime_hi,
|
|
utime_lo,
|
|
dx,
|
|
dy,
|
|
dx_unaccel,
|
|
dy_unaccel
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_locked_pointer_v1::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state.world.get::<&LockedPointerServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
Locked,
|
|
Unlocked
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_confined_pointer_v1::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let server = state.world.get::<&ConfinedPointerServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
server, self => [
|
|
Confined,
|
|
Unconfined
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_seat_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let seat = state.world.get::<&TabletSeatServer>(target).unwrap();
|
|
match self {
|
|
Self::TabletAdded { id } => {
|
|
let (e, tab) = from_client::<TabletServer, _, _>(&id, state);
|
|
seat.tablet_added(&tab);
|
|
drop(seat);
|
|
state.world.spawn_at(e, (tab, id));
|
|
}
|
|
Self::ToolAdded { id } => {
|
|
let (e, tool) = from_client::<TabletToolServer, _, _>(&id, state);
|
|
seat.tool_added(&tool);
|
|
drop(seat);
|
|
state.world.spawn_at(e, (tool, id));
|
|
}
|
|
Self::PadAdded { id } => {
|
|
let (e, pad) = from_client::<TabletPadServer, _, _>(&id, state);
|
|
seat.pad_added(&pad);
|
|
drop(seat);
|
|
state.world.spawn_at(e, (pad, id));
|
|
}
|
|
_ => log::warn!("unhandled {}: {self:?}", std::any::type_name::<Self>()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let tab = state.world.get::<&TabletServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
tab, self => [
|
|
Name { name },
|
|
Id { vid, pid },
|
|
Path { path },
|
|
Done,
|
|
Removed
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_pad_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let pad = state.world.get::<&TabletPadServer>(target).unwrap();
|
|
let s_surf;
|
|
match self {
|
|
Self::Group { pad_group } => {
|
|
let (e, s_group) = from_client::<TabletPadGroupServer, _, _>(&pad_group, state);
|
|
pad.group(&s_group);
|
|
drop(pad);
|
|
state.world.spawn_at(e, (pad_group, s_group));
|
|
}
|
|
Self::Enter {
|
|
serial,
|
|
tablet,
|
|
surface,
|
|
} => {
|
|
let (e_tab, s_tablet) = from_client::<TabletServer, _, _>(&tablet, state);
|
|
let Some(surface) = surface
|
|
.data()
|
|
.copied()
|
|
.and_then(|key| state.world.get::<&WlSurface>(key).ok())
|
|
else {
|
|
return;
|
|
};
|
|
pad.enter(serial, &s_tablet, &surface);
|
|
drop(pad);
|
|
drop(surface);
|
|
state.world.spawn_at(e_tab, (tablet, s_tablet));
|
|
}
|
|
_ => simple_event_shunt! {
|
|
pad, self => [
|
|
Path { path },
|
|
Buttons { buttons },
|
|
Done,
|
|
Button {
|
|
time,
|
|
button,
|
|
|state| convert_wenum(state)
|
|
},
|
|
Leave {
|
|
serial,
|
|
|surface| {
|
|
s_surf = surface.data().copied().and_then(|key| state.world.get::<&WlSurface>(key).ok());
|
|
if let Some(s) = &s_surf { s } else { return; }
|
|
}
|
|
},
|
|
Removed
|
|
]
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_tool_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
match self {
|
|
Self::ProximityIn {
|
|
serial,
|
|
tablet,
|
|
surface,
|
|
} => {
|
|
let mut cmd = CommandBuffer::new();
|
|
{
|
|
let Some(mut query) = surface.data().copied().and_then(|key| {
|
|
state
|
|
.world
|
|
.query_one::<(&WlSurface, &SurfaceScaleFactor)>(key)
|
|
.ok()
|
|
}) else {
|
|
warn!("tablet tool proximity_in failed: stale surface");
|
|
return;
|
|
};
|
|
let (surface, scale) = query.get().unwrap();
|
|
cmd.insert(target, (*scale,));
|
|
|
|
let Some(s_tablet) =
|
|
tablet
|
|
.data()
|
|
.and_then(|key: &LateInitObjectKey<TabletClient>| {
|
|
state.world.get::<&TabletServer>(key.get()).ok()
|
|
})
|
|
else {
|
|
warn!("tablet tool proximity_in failed: stale tablet");
|
|
return;
|
|
};
|
|
|
|
state
|
|
.world
|
|
.get::<&TabletToolServer>(target)
|
|
.unwrap()
|
|
.proximity_in(serial, &s_tablet, surface);
|
|
}
|
|
cmd.run_on(&mut state.world);
|
|
}
|
|
Self::Motion { x, y } => {
|
|
let (tool, scale) = state
|
|
.world
|
|
.query_one_mut::<(&TabletToolServer, Option<&SurfaceScaleFactor>)>(target)
|
|
.unwrap();
|
|
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
|
tool.motion(x * scale, y * scale);
|
|
}
|
|
_ => {
|
|
let tool = state.world.get::<&TabletToolServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
tool, self => [
|
|
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,
|
|
ProximityOut,
|
|
Down { serial },
|
|
Up,
|
|
Distance { distance },
|
|
Pressure { pressure },
|
|
Tilt { tilt_x, tilt_y },
|
|
Rotation { degrees },
|
|
Slider { position },
|
|
Wheel { degrees, clicks },
|
|
Button { serial, button, |state| convert_wenum(state) },
|
|
Frame { time },
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn from_client<Server: Resource + 'static, Client: Proxy + Send + Sync + 'static, C: XConnection>(
|
|
client: &Client,
|
|
state: &ServerState<C>,
|
|
) -> (Entity, Server)
|
|
where
|
|
Client::Event: Send + Into<ObjectEvent>,
|
|
ServerState<C>: wayland_server::Dispatch<Server, Entity>,
|
|
{
|
|
let entity = state.world.reserve_entity();
|
|
let server = state
|
|
.client
|
|
.as_ref()
|
|
.unwrap()
|
|
.create_resource::<_, _, ServerState<C>>(&state.dh, 1, entity)
|
|
.unwrap();
|
|
let obj_key: &LateInitObjectKey<Client> = client.data().unwrap();
|
|
obj_key.init(entity);
|
|
(entity, server)
|
|
}
|
|
|
|
impl Event for zwp_tablet_pad_group_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let group = state.world.get::<&TabletPadGroupServer>(target).unwrap();
|
|
match self {
|
|
Self::Buttons { buttons } => group.buttons(buttons),
|
|
Self::Ring { ring } => {
|
|
let (e, s_ring) = from_client::<TabletPadRingServer, _, _>(&ring, state);
|
|
group.ring(&s_ring);
|
|
drop(group);
|
|
state.world.spawn_at(e, (s_ring, ring));
|
|
}
|
|
Self::Strip { strip } => {
|
|
let (e, s_strip) = from_client::<TabletPadStripServer, _, _>(&strip, state);
|
|
group.strip(&s_strip);
|
|
drop(group);
|
|
state.world.spawn_at(e, (s_strip, strip));
|
|
}
|
|
Self::Modes { modes } => group.modes(modes),
|
|
Self::ModeSwitch { time, serial, mode } => group.mode_switch(time, serial, mode),
|
|
Self::Done => group.done(),
|
|
_ => log::warn!(
|
|
"unhandled {} event: {self:?}",
|
|
std::any::type_name::<Self>()
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_pad_ring_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let ring = state.world.get::<&TabletPadRingServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
ring, self => [
|
|
Source { |source| convert_wenum(source) },
|
|
Angle { degrees },
|
|
Stop,
|
|
Frame { time }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Event for zwp_tablet_pad_strip_v2::Event {
|
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
|
let strip = state.world.get::<&TabletPadStripServer>(target).unwrap();
|
|
simple_event_shunt! {
|
|
strip, self => [
|
|
Source { |source| convert_wenum(source) },
|
|
Position { position },
|
|
Stop,
|
|
Frame { time }
|
|
]
|
|
}
|
|
}
|
|
}
|