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(self, target: Entity, state: &mut ServerState) { 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::() { 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, ) { 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::(target); } } Event::PreferredBufferScale { .. } => {} other => warn!("unhandled surface request: {other:?}"), } drop(surface); cmd.run_on(&mut state.world); } fn xdg_event( event: xdg_surface::Event, target: Entity, state: &mut ServerState, ) { 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::(target); } if let Some(cb) = callback { surface.frame(&state.qh, cb.clone()); cmd.remove_one::(target); } surface.commit(); cmd.run_on(&mut state.world); } fn toplevel_event( event: xdg_toplevel::Event, target: Entity, state: &mut ServerState, ) { 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( event: xdg_popup::Event, target: Entity, state: &mut ServerState, ) { 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(self, target: Entity, state: &mut ServerState) { // 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { // 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::(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::(target); if !surface.is_alive() { return; } debug!("leaving surface ({serial})"); let _ = state.world.remove_one::(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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { 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 { 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, make: String, model: String, transform: WEnum, }, 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: "".to_string(), model: "".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, ) { { 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, ) { 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, ) { 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 for ObjectEvent { fn from(value: client::wl_output::Event) -> Self { Self::Output(OutputEvent::Wl(value)) } } impl From for ObjectEvent { fn from(value: zxdg_output_v1::Event) -> Self { Self::Output(OutputEvent::Xdg(value)) } } impl Event for OutputEvent { fn handle(self, target: Entity, state: &mut ServerState) { 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( event: client::wl_output::Event, target: Entity, state: &mut ServerState, ) { 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( event: zxdg_output_v1::Event, target: Entity, state: &mut ServerState, ) { 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { let seat = state.world.get::<&TabletSeatServer>(target).unwrap(); match self { Self::TabletAdded { id } => { let (e, tab) = from_client::(&id, state); seat.tablet_added(&tab); drop(seat); state.world.spawn_at(e, (tab, id)); } Self::ToolAdded { id } => { let (e, tool) = from_client::(&id, state); seat.tool_added(&tool); drop(seat); state.world.spawn_at(e, (tool, id)); } Self::PadAdded { id } => { let (e, pad) = from_client::(&id, state); seat.pad_added(&pad); drop(seat); state.world.spawn_at(e, (pad, id)); } _ => log::warn!("unhandled {}: {self:?}", std::any::type_name::()), } } } impl Event for zwp_tablet_v2::Event { fn handle(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { let pad = state.world.get::<&TabletPadServer>(target).unwrap(); let s_surf; match self { Self::Group { pad_group } => { let (e, s_group) = from_client::(&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::(&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(self, target: Entity, state: &mut ServerState) { 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| { 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( client: &Client, state: &ServerState, ) -> (Entity, Server) where Client::Event: Send + Into, ServerState: wayland_server::Dispatch, { let entity = state.world.reserve_entity(); let server = state .client .as_ref() .unwrap() .create_resource::<_, _, ServerState>(&state.dh, 1, entity) .unwrap(); let obj_key: &LateInitObjectKey = client.data().unwrap(); obj_key.init(entity); (entity, server) } impl Event for zwp_tablet_pad_group_v2::Event { fn handle(self, target: Entity, state: &mut ServerState) { 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::(&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::(&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::() ), } } } impl Event for zwp_tablet_pad_ring_v2::Event { fn handle(self, target: Entity, state: &mut ServerState) { 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(self, target: Entity, state: &mut ServerState) { let strip = state.world.get::<&TabletPadStripServer>(target).unwrap(); simple_event_shunt! { strip, self => [ Source { |source| convert_wenum(source) }, Position { position }, Stop, Frame { time } ] } } }