diff options
Diffstat (limited to 'crates/wayland/src/protocols.rs')
| -rw-r--r-- | crates/wayland/src/protocols.rs | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/crates/wayland/src/protocols.rs b/crates/wayland/src/protocols.rs new file mode 100644 index 0000000..582a622 --- /dev/null +++ b/crates/wayland/src/protocols.rs @@ -0,0 +1,237 @@ +//! smithay protocol-handler implementations for [`CompState`], plus the +//! `delegate_*!` macros that route each protocol's dispatch to them. +//! +//! Deliberately thin: these methods translate a protocol event into a call +//! on [`crate::state`] (window bookkeeping) or [`crate::input`] (focus), and +//! hold no logic of their own beyond what the protocol itself dictates. The +//! session-lock handler is the one exception, living in [`crate::lock`] +//! alongside the rest of that feature. + +use smithay::desktop::{layer_map_for_output, LayerSurface as DesktopLayerSurface}; +use smithay::input::pointer::CursorImageStatus; +use smithay::input::{Seat, SeatHandler, SeatState}; +use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode; +use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; +use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; +use smithay::reexports::wayland_server::protocol::wl_seat; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::reexports::wayland_server::Client; +use smithay::utils::Serial; +use smithay::wayland::buffer::BufferHandler; +use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState}; +use smithay::wayland::selection::data_device::{ + ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, +}; +use smithay::wayland::selection::primary_selection::{PrimarySelectionHandler, PrimarySelectionState}; +use smithay::wayland::selection::wlr_data_control::{DataControlHandler, DataControlState}; +use smithay::wayland::selection::SelectionHandler; +use smithay::wayland::shell::wlr_layer::{ + Layer, LayerSurface as WlrLayerSurface, WlrLayerShellHandler, WlrLayerShellState, +}; +use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler; +use smithay::wayland::shell::xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState}; +use smithay::wayland::shm::{ShmHandler, ShmState}; +use smithay::{ + delegate_compositor, delegate_data_control, delegate_data_device, delegate_layer_shell, delegate_output, + delegate_primary_selection, delegate_seat, delegate_session_lock, delegate_shm, delegate_xdg_decoration, + delegate_xdg_shell, +}; + +use crate::state::{ClientState, CompState}; + +impl CompositorHandler for CompState { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { + // Two possible client kinds now: our own `ClientState` for regular + // Wayland clients, or smithay's `XWaylandClientData` for the single + // XWayland client (see `xwayland.rs`) - both carry a + // `CompositorClientState`, just under different wrapper types. + if let Some(state) = client.get_data::<ClientState>() { + return &state.compositor_state; + } + &client.get_data::<smithay::xwayland::XWaylandClientData>().expect("client is neither ours nor XWayland's").compositor_state + } + + fn commit(&mut self, surface: &WlSurface) { + smithay::backend::renderer::utils::on_commit_buffer_handler::<CompState>(surface); + // XWayland's association of an X11 window with this wl_surface can + // arrive at any point relative to the map request (see + // `xwayland.rs`'s module docs); `surface_associated` handles the + // common ordering, this retries the surfaces still waiting on a + // commit to actually make that association queryable. + self.retry_pending_x11_windows(); + if let Some(&id) = self.surface_to_id.get(surface) { + if let Some(w) = self.id_to_window.get(&id) { + w.on_commit(); + } + } + self.ensure_layer_initial_configure(surface); + } +} + +impl XdgShellHandler for CompState { + fn xdg_shell_state(&mut self) -> &mut XdgShellState { + &mut self.xdg_shell_state + } + + fn new_toplevel(&mut self, surface: ToplevelSurface) { + self.new_managed_window(surface); + } + + fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {} + + fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {} + + fn reposition_request(&mut self, _surface: PopupSurface, _positioner: PositionerState, _token: u32) {} + + fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { + self.remove_window(surface.wl_surface()); + } +} + +impl XdgDecorationHandler for CompState { + fn new_decoration(&mut self, toplevel: ToplevelSurface) { + toplevel.with_pending_state(|state| { + state.decoration_mode = Some(DecorationMode::ServerSide); + }); + } + + fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) { + toplevel.with_pending_state(|state| { + state.decoration_mode = Some(DecorationMode::ServerSide); + }); + toplevel.send_configure(); + } + + fn unset_mode(&mut self, _toplevel: ToplevelSurface) {} +} + +impl ShmHandler for CompState { + fn shm_state(&self) -> &ShmState { + &self.shm_state + } +} + +impl BufferHandler for CompState { + fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} +} + +impl smithay::wayland::output::OutputHandler for CompState {} + +impl SeatHandler for CompState { + type KeyboardFocus = WlSurface; + type PointerFocus = WlSurface; + type TouchFocus = WlSurface; + + fn seat_state(&mut self) -> &mut SeatState<Self> { + &mut self.seat_state + } + + fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {} + fn cursor_image(&mut self, _seat: &Seat<Self>, _image: CursorImageStatus) {} +} + +impl WlrLayerShellHandler for CompState { + fn shell_state(&mut self) -> &mut WlrLayerShellState { + &mut self.layer_shell_state + } + + fn new_layer_surface(&mut self, surface: WlrLayerSurface, wl_output: Option<WlOutput>, _layer: Layer, namespace: String) { + // A client may name the output it wants (a bar on a specific + // monitor); if it doesn't, or names one we don't drive, it lands on + // the primary output. + let output = wl_output + .as_ref() + .and_then(|wl| self.output_for_wl(wl)) + .map(|e| e.output.clone()) + .or_else(|| self.primary_output().cloned()); + let Some(output) = output else { + log::warn!("wayland: layer surface requested but no output exists yet"); + return; + }; + let layer_surface = DesktopLayerSurface::new(surface, namespace); + let result = layer_map_for_output(&output).map_layer(&layer_surface); + if let Err(e) = result { + log::warn!("wayland: failed to map layer surface: {e}"); + } + } + + fn layer_destroyed(&mut self, surface: WlrLayerSurface) { + // The surface belongs to exactly one output's map, but which one is + // the client's choice, so unmap from whichever holds it. + for output in self.outputs().cloned().collect::<Vec<_>>() { + let mut map = layer_map_for_output(&output); + let found = map.layers().find(|l| l.layer_surface() == &surface).cloned(); + if let Some(layer) = found { + map.unmap_layer(&layer); + break; + } + } + // A lock/launcher surface holding exclusive keyboard focus just + // vanished (crash, or a normal close) - don't leave focus dangling + // on a dead surface. + if self.seat.get_keyboard().and_then(|k| k.current_focus()).as_ref() == Some(surface.wl_surface()) { + self.set_keyboard_focus(None); + } + } +} + +/// Clipboard/primary-selection/drag-and-drop. +/// +/// All three selection protocols below (`wl_data_device_manager`, +/// `zwp_primary_selection_v1`, `zwlr_data_control_manager_v1`) share +/// smithay's single `SelectionHandler`. Every transfer here is +/// *client-to-client*: one client owns the selection and writes the bytes +/// itself, and smithay wires the two ends together without the data passing +/// through us. `send_selection` is only ever called for a +/// **compositor-provided** selection (one this WM set itself via +/// `set_data_device_selection`), which srdwm never does - so it is +/// deliberately left unimplemented rather than faked. +impl SelectionHandler for CompState { + type SelectionUserData = (); +} + +impl DataDeviceHandler for CompState { + fn data_device_state(&self) -> &DataDeviceState { + &self.data_device_state + } +} + +// Drag-and-drop: the default trait methods already do the right thing for a +// compositor that doesn't draw its own drag icon or offer server-side drag +// sources - smithay runs the pointer grab and the offer/accept negotiation +// internally. Both are implemented empty (rather than skipped) because +// `DataDeviceHandler` requires them as supertraits. +impl ClientDndGrabHandler for CompState {} +impl ServerDndGrabHandler for CompState {} + +impl PrimarySelectionHandler for CompState { + fn primary_selection_state(&self) -> &PrimarySelectionState { + &self.primary_selection_state + } +} + +/// `zwlr_data_control_manager_v1`: lets a client read/watch the selection +/// without ever holding keyboard focus. This is what `wl-paste --watch` +/// (and thus `cliphist store`, which the user's session autostarts) needs +/// - a focus-following clipboard manager is impossible without it. +impl DataControlHandler for CompState { + fn data_control_state(&self) -> &DataControlState { + &self.data_control_state + } +} + +delegate_compositor!(CompState); +delegate_xdg_shell!(CompState); +delegate_xdg_decoration!(CompState); +delegate_shm!(CompState); +delegate_seat!(CompState); +delegate_output!(CompState); +delegate_layer_shell!(CompState); +delegate_data_device!(CompState); +delegate_primary_selection!(CompState); +delegate_data_control!(CompState); +delegate_session_lock!(CompState); |