Skip to main content

pixelflux/wayland/
frontend.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! Headless Smithay compositor that stands in for a real display server so ordinary Wayland
8//! clients have somewhere to render — their composited output is exactly what the capture pipeline
9//! reads back and H.264-encodes. There is no monitor, KMS, or libinput in this process, so
10//! everything a desktop session normally receives from hardware — an output to map windows onto, a
11//! seat to deliver input to, a clipboard to share — this frontend has to synthesize itself.
12//!
13//! This module owns `AppState`, the single context threaded through every Smithay protocol
14//! handler, and implements those handlers: `wl_compositor` commit handling with the window
15//! map/configure/focus state machine, seat/keyboard/pointer/touch routing through `FocusTarget`,
16//! clipboard and primary-selection bridging to Python, and the xdg-shell / layer-shell /
17//! xdg-activation / decoration / fractional-scale / dmabuf wiring. It also resolves cursor images
18//! to PNG for the Python callback and provides the serial and monotonic-time helpers the input
19//! path stamps onto events.
20
21use std::borrow::Cow;
22use std::fs::File;
23use std::time::{Duration, Instant};
24
25use gbm::{BufferObject, Device as RawGbmDevice};
26use std::sync::Mutex;
27use std::collections::hash_map::DefaultHasher;
28use std::hash::{Hash, Hasher};
29use smithay::backend::renderer::utils::RendererSurfaceState;
30use smithay::backend::input::InputTime;
31use smithay::backend::allocator::dmabuf::Dmabuf;
32use smithay::backend::allocator::{ Buffer, Fourcc};
33use smithay::backend::renderer::damage::OutputDamageTracker;
34use smithay::backend::renderer::{
35    Bind, ExportMem, gles::GlesRenderer, pixman::PixmanRenderer, ImportDma,
36};
37use smithay::backend::allocator::Modifier;
38use smithay::backend::drm::DrmNode;
39use smithay::output::WeakOutput;
40use smithay::wayland::image_capture_source::{
41    ImageCaptureSource, ImageCaptureSourceHandler, ImageCaptureSourceState,
42    OutputCaptureSourceHandler, OutputCaptureSourceState,
43};
44use smithay::wayland::image_copy_capture::{
45    BufferConstraints, CaptureFailureReason, DmabufConstraints, Frame as CopyFrame,
46    FrameRef as CopyFrameRef, ImageCopyCaptureHandler, ImageCopyCaptureState,
47    Session as CopySession, SessionRef as CopySessionRef,
48};
49use smithay::input::dnd::{DnDGrab, DndFocus, GrabType, Source};
50use std::sync::Arc;
51use crate::wayland::cursor::{Cursor, CursorJob};
52use crate::wayland::keymap::KeymapPolicy;
53use smithay::reexports::wayland_protocols_misc::zwp_virtual_keyboard_v1::server::{
54    zwp_virtual_keyboard_manager_v1::{self, ZwpVirtualKeyboardManagerV1},
55    zwp_virtual_keyboard_v1::{self, ZwpVirtualKeyboardV1},
56};
57use smithay::reexports::wayland_server::{DataInit, Dispatch, GlobalDispatch, New};
58use smithay::wayland::viewporter::ViewporterState;
59use smithay::wayland::pointer_warp::{PointerWarpHandler, PointerWarpManager};
60use smithay::reexports::wayland_server::protocol::wl_pointer::WlPointer;
61use smithay::reexports::wayland_server::protocol::wl_shm;
62use smithay::wayland::relative_pointer::RelativePointerManagerState;
63use smithay::wayland::pointer_constraints::{
64    with_pointer_constraint, PointerConstraint, PointerConstraintsHandler, PointerConstraintsState,
65};
66use smithay::input::pointer::PointerHandle;
67use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
68use smithay::desktop::utils::under_from_surface_tree;
69use smithay::desktop::{PopupKind, PopupManager, WindowSurfaceType};
70use smithay::wayland::presentation::PresentationState;
71use smithay::wayland::foreign_toplevel_list::{
72    ForeignToplevelHandle, ForeignToplevelListHandler, ForeignToplevelListState,
73};
74use smithay::wayland::shell::xdg::decoration::{
75    XdgDecorationHandler, XdgDecorationState,
76};
77use smithay::desktop::{layer_map_for_output, LayerSurface as DesktopLayerSurface};
78use smithay::wayland::shell::wlr_layer::{
79    WlrLayerShellHandler, WlrLayerShellState, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
80};
81use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
82use smithay::wayland::selection::wlr_data_control::{DataControlHandler, DataControlState};
83use smithay::wayland::selection::ext_data_control::{
84    DataControlHandler as ExtDataControlHandler, DataControlState as ExtDataControlState,
85};
86use smithay::wayland::cursor_shape::CursorShapeManagerState;
87use smithay::wayland::xdg_activation::{
88    XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
89};
90use smithay::wayland::selection::primary_selection::{
91    set_primary_focus, PrimarySelectionHandler, PrimarySelectionState,
92};
93use smithay::wayland::keyboard_shortcuts_inhibit::{
94    KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
95};
96
97use smithay::{
98    delegate_dispatch2,
99    desktop::{Space, Window},
100    input::{
101        Seat, SeatHandler, SeatState,
102        keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
103        pointer::{
104            AxisFrame, ButtonEvent, CursorIcon, CursorImageAttributes, CursorImageStatus, Focus,
105            GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
106            GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
107            GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent,
108            PointerTarget, RelativeMotionEvent,
109        },
110        touch::{DownEvent, FrameMarker, OrientationEvent, ShapeEvent, TouchTarget, UpEvent},
111    },
112    output::Output,
113    reexports::{
114        wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
115        wayland_server::{
116            Client, DisplayHandle, Resource,
117            backend::{ClientData, ClientId, DisconnectReason, GlobalId, ObjectId},
118            protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface},
119        },
120    },
121    utils::{Clock, IsAlive, Logical, Monotonic, Point, Rectangle, Serial},
122    wayland::{
123        buffer::BufferHandler,
124        compositor::{
125            BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
126            SurfaceAttributes, with_states,
127        },
128        dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf},
129        fractional_scale::{FractionalScaleHandler, FractionalScaleManagerState},
130        output::{OutputHandler, OutputManagerState},
131        seat::WaylandFocus,
132        selection::{
133            SelectionHandler, SelectionSource, SelectionTarget,
134            data_device::{
135                DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler,
136                request_data_device_client_selection, set_data_device_focus,
137            },
138        },
139        shell::xdg::{
140            PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
141            XdgToplevelSurfaceData,
142        },
143        shm::{BufferAccessError, ShmHandler, ShmState, with_buffer_contents},
144    },
145};
146
147use crate::CaptureSettings;
148use crate::encoders::FrameEncoder;
149use crate::encoders::overlay::OverlayState;
150
151use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering, fence};
152
153static SERIAL_COUNTER: AtomicU32 = AtomicU32::new(1);
154
155/// Hand out the next unique, monotonically increasing Wayland event serial.
156///
157/// Wayland tags each event with a serial so clients and Smithay can prove a request was caused
158/// by a specific event (popup grab, selection change). Every injected input event draws a fresh
159/// value from this process-wide atomic counter.
160///
161/// # Returns
162///
163/// A new [`Serial`] value.
164pub fn next_serial() -> Serial {
165    Serial::from(SERIAL_COUNTER.fetch_add(1, Ordering::SeqCst))
166}
167
168/// Timestamp for injected input events, from `CLOCK_MONOTONIC` at microsecond granularity.
169///
170/// The seat hands clients the wrapping millisecond count the core protocol carries and the
171/// relative-pointer protocol the full microseconds, both read from this one sample.
172pub fn wayland_time() -> InputTime {
173    let mut ts = libc::timespec {
174        tv_sec: 0,
175        tv_nsec: 0,
176    };
177    unsafe {
178        libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
179    }
180    InputTime::from_micros(
181        (ts.tv_sec as u64)
182            .wrapping_mul(1_000_000)
183            .wrapping_add((ts.tv_nsec as u64) / 1_000),
184    )
185}
186
187use crate::pace::FramePace;
188
189/// One capture pipeline bound to one output (display id): its settings, encoder set,
190/// frame pools, delivery thread, and per-stream bookkeeping. Exactly one capture may run per
191/// output; all fields mirror the pipeline strategy documented on [`AppState`], instantiated
192/// per display.
193pub struct WlCapture {
194    pub settings: CaptureSettings,
195    /// The frame sink, shared with the delivery thread. Kept here so a reconfigure the
196    /// calloop performs on its own (a host that kept a different mode) can respawn delivery
197    /// with the same consumer; `None` for a recorder-owned capture.
198    pub callback: Option<Arc<dyn crate::sink::FrameSink>>,
199    /// Zero-copy GPU session (GLES render + same-GPU dmabuf encode), driven by the encode
200    /// thread on `gpu_pool`'s targets and taken back by a restart (`crate::ZeroCopySession`);
201    /// `None` whenever a readback path is active for this display.
202    pub zero_copy: Option<Arc<Mutex<Option<FrameEncoder>>>>,
203    /// The zero-copy session's render targets, handed between the calloop, which composites
204    /// into one, and the encode thread, which encodes it in place; `gpu_ring` owns them.
205    pub gpu_pool: Option<Arc<crate::WlFramePool<Dmabuf>>>,
206    pub gpu_ring: Vec<(BufferObject<()>, Dmabuf)>,
207    /// A quiet host's GPU frame copied out of the stream's buffer (`detach_retained_gpu`).
208    pub host_still: Option<(BufferObject<()>, Dmabuf)>,
209    /// Host frames replaced while a frame in flight to the encode thread still reads them,
210    /// each with the count of recycled frames (`WlEncodeStats::recycled`) after which it is
211    /// free to go back to the host; `published` counts the frames handed over.
212    pub host_inflight: Vec<(u64, crate::wayland::host::HostFrame)>,
213    /// Client buffers encoded as they stood (`crate::scanout_candidate`), each held, so its
214    /// client is not told to draw into it again, until the recycled count passes the frame
215    /// that reads it, as `host_inflight` holds host frames.
216    pub scanout_inflight: Vec<(u64, smithay::backend::renderer::utils::Buffer)>,
217    /// Whether the last frame was encoded from a client's buffer, else why it was composited,
218    /// so only a change is logged; `None` before the first frame.
219    pub direct: Option<Result<(), &'static str>>,
220    pub published: u64,
221    /// The frame count of the last publish that encodes the retained host frame.
222    pub host_retained_use: u64,
223    pub recording_sink: Option<Arc<crate::recording_sink::RecordingSink>>,
224    pub deliver_tx:
225        Option<std::sync::mpsc::SyncSender<Vec<crate::encoders::software::EncodedStripe>>>,
226    pub deliver_join: Option<std::thread::JoinHandle<()>>,
227    /// Raised at teardown so the deliver thread stops calling the sink and
228    /// only drains: its exit is then bounded by the one in-flight call.
229    pub deliver_discard: Option<Arc<AtomicBool>>,
230    pub encode_pool: Option<Arc<crate::WlFramePool>>,
231    pub encode_join: Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
232    pub encode_controls: Arc<crate::WlEncodeControls>,
233    pub encode_stats: Arc<crate::WlEncodeStats>,
234    /// What this capture streams and how, for `ScreenCapture::stream_info`.
235    pub report: Arc<crate::report::StreamReport>,
236    pub pool_last_render: Vec<u64>,
237    pub render_seq: u64,
238    pub pool_content_gen: Vec<u64>,
239    pub content_gen: u64,
240    pub frame_counter: u16,
241    pub pending_force_idr: bool,
242    pub needs_full_render: bool,
243    /// One frame a period, a change taking its period's frame as it lands, and the extra frames
244    /// a change in a taken period may spend (`FramePace::windowed`), as the X11 captures pace.
245    pub pace: FramePace,
246    /// A change (a client's commit, input, a host frame) found its period's frame taken and
247    /// waits for the next period to open (`serve_changes`); any frame rendered clears it.
248    pub change_pending: bool,
249    /// The window boundary whose frame callbacks went out (`answer_frame_callbacks`).
250    pub callbacks_at: Option<Instant>,
251    /// A boundary's frame callbacks wait for the render of a change still pending at it.
252    pub callbacks_owed: bool,
253}
254
255impl WlCapture {
256    /// Arm a keyframe on whichever path is live, plus a full render so a static
257    /// screen still produces the frame. Exactly ONE flag is set: the pool encode
258    /// loop consumes the atomic, every other path consumes `pending_force_idr` —
259    /// setting both would leave one armed forever (the host-mode idle gate polls
260    /// them every tick, so a stuck flag disables idle skipping for the session).
261    pub fn request_idr(&mut self) {
262        if self.pooled() {
263            self.encode_controls
264                .force_idr
265                .store(true, Ordering::Relaxed);
266        } else {
267            self.pending_force_idr = true;
268        }
269        self.needs_full_render = true;
270    }
271
272    /// Leave frame `frame_id` and every frame after it out of the predictions, for a client that
273    /// lost it or a sender that withheld it (`withheld`): handed to the encode thread, which
274    /// applies it ahead of its next encode. An encoder that cannot codes a keyframe instead.
275    pub fn invalidate_reference(&mut self, frame_id: u16, withheld: bool) {
276        use crate::encoders::reference::ReferenceReport;
277        self.take_report(if withheld {
278            ReferenceReport::Withheld(frame_id)
279        } else {
280            ReferenceReport::Lost(frame_id)
281        });
282    }
283
284    /// Note that every consumer holds frame `frame_id`, or where not `held` was sent it
285    /// (`ReferenceWindow::acknowledge`), handed on as `invalidate_reference` hands a loss.
286    pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
287        use crate::encoders::reference::ReferenceReport;
288        self.take_report(if held {
289            ReferenceReport::Held(frame_id)
290        } else {
291            ReferenceReport::Sent(frame_id)
292        });
293    }
294
295    fn take_report(&mut self, report: crate::encoders::reference::ReferenceReport) {
296        if self.pooled() {
297            self.encode_controls
298                .reference_reports
299                .lock()
300                .unwrap()
301                .push(report);
302        }
303    }
304
305    /// Whether an encode thread runs this capture's encodes (a readback pool or the
306    /// zero-copy session's): every one does once its delivery is up.
307    pub fn pooled(&self) -> bool {
308        self.encode_pool.is_some() || self.gpu_pool.is_some()
309    }
310}
311
312/// One virtual output and everything sized to it: the Smithay `Output` + its advertised
313/// global, its layout position, the damage tracker, render targets, and the (at most one)
314/// capture bound to it. `id` is the Python-facing display key; id 0 is the primary
315/// HEADLESS-1 output, which is never destroyed.
316pub struct OutputNode {
317    pub id: u32,
318    pub output: Output,
319    /// The wl_output global this node published, and `None` for a view: a view
320    /// captures a rectangle of another node's output rather than a screen of its
321    /// own, so it publishes nothing and the session sees one screen for all the
322    /// views over it. That is what keeps a pointer grab -- a window drag -- inside
323    /// one surface as it crosses from one display to the next.
324    pub global: Option<GlobalId>,
325    /// The node whose output this one is a view of; `None` when it owns its output.
326    pub owner: Option<u32>,
327    /// The rectangle size a view's damage tracker was built for, so a display that
328    /// changes resolution rebuilds it rather than tracking damage for the old one.
329    pub view_size: (i32, i32),
330    /// The scale the view's tracker maps windows with; a screen's tracker follows its output.
331    pub view_scale: f64,
332    /// Layout offset: the output's logical position in the Space AND its physical offset
333    /// for absolute input injection. The two coincide at scale 1; with mixed scales the
334    /// caller must place outputs so neither the logical nor the physical rectangles
335    /// overlap.
336    pub pos: (i32, i32),
337    pub damage_tracker: OutputDamageTracker,
338    /// What changed on the output between one software-rendered frame and the next, apart
339    /// from `damage_tracker`'s answer, which is what the buffer rendered into has to have
340    /// repainted: that render goes straight into a pool buffer, and one the encoder held
341    /// through a slow frame is older than the tracker's history, so repainting it damages
342    /// the whole output though nothing on it changed. Built on the first such frame, and
343    /// again after the output changes size.
344    pub content_tracker: Option<OutputDamageTracker>,
345    /// Host-side scratch target: pixman throttle path renders here, GLES screenshots read
346    /// back here.
347    pub frame_buffer: Vec<u8>,
348    /// GPU render target for this output (GLES mode): the GBM BO and its dmabuf export.
349    pub offscreen_buffer: Option<(BufferObject<()>, Dmabuf)>,
350    /// Render ticks in a row that drew nothing on the target (`UNDRAWN_TICKS_PER_FRAME`).
351    pub undrawn_ticks: u32,
352    /// Watermark overlay for THIS output: loaded at the output's scale, positioned (and,
353    /// for the bouncing anchor, animated) against the output's own frame dimensions.
354    pub overlay_state: OverlayState,
355    pub capture: Option<WlCapture>,
356    /// Monotonic count of frames reported presented through wp_presentation.
357    pub frame_seq: u64,
358    /// Whether the render target already holds a fully composited frame, so a
359    /// capture-less render (screenshot, copy-capture client) may use buffer age
360    /// instead of redrawing everything. Cleared whenever the target is replaced.
361    pub target_seeded: bool,
362    /// Deadline for this output's post-reconfigure content hold. While it is set, the
363    /// display composites but does not publish: a resized or freshly created output paints
364    /// its clear color wherever a client has not yet answered the new size, and that gray
365    /// must not reach the stream. Cleared by the first tick a client covers the output, and
366    /// by the deadline for one that never does.
367    pub content_hold_until: Option<Instant>,
368}
369
370/// A host-capture layout request in flight for one display: the epoch of the apply it
371/// rode on, the size the capture was configured for, and the geometry readers parked
372/// behind it, answered once the host has decided so they report the size actually
373/// captured (the realized-geometry barrier).
374pub struct PendingHostLayout {
375    pub epoch: u64,
376    pub want: (i32, i32),
377    pub geometry_waiters: Vec<std::sync::mpsc::Sender<(i32, i32, f64)>>,
378}
379
380/// One ext-image-copy-capture session: an external client capturing one of this
381/// compositor's outputs. A requested frame parks in `pending` until the render loop
382/// has content for it, so delivery happens at most once per composited frame and an
383/// unchanged screen holds the frame instead of duplicating it.
384pub struct OutputCopySession {
385    pub session: CopySession,
386    pub output: WeakOutput,
387    pub pending: Option<CopyFrame>,
388    /// The first frame of a session ships without waiting for damage; only
389    /// afterwards does an unchanged screen hold the frame.
390    pub delivered_once: bool,
391}
392
393/// Every window placed on `display_id` and actually composited there. Keyed off the window's
394/// own output tag rather than the Space's output map, which only catches up on the next
395/// `Space::refresh`; a parked window carries the tag but is mapped clear of every output.
396pub fn windows_on_output(space: &Space<Window>, display_id: u32) -> impl Iterator<Item = &Window> {
397    space.elements().filter(move |w| {
398        window_output_id(w) == display_id
399            && !window_meta(w)
400                .map(|m| m.parked.load(Ordering::Relaxed))
401                .unwrap_or(false)
402    })
403}
404
405/// Whether a client on this display has COMMITTED content spanning the whole output. Under
406/// forced fullscreen a window answers its configure at the output's logical size, so one
407/// still carrying its pre-configure size reads as not covering and holds the display's frames.
408pub fn output_content_covers(
409    space: &Space<Window>,
410    display_id: u32,
411    logical_w: f64,
412    logical_h: f64,
413) -> bool {
414    windows_on_output(space, display_id).any(|w| {
415        let size = w.geometry().size;
416        f64::from(size.w) >= logical_w && f64::from(size.h) >= logical_h
417    })
418}
419
420impl OutputNode {
421    /// The output's logical geometry in layout coordinates: origin plus mode/scale-derived
422    /// logical size. A view's size is its own rectangle -- the mode belongs to the
423    /// screen it is cut from.
424    pub fn logical_geometry(&self) -> Option<Rectangle<i32, Logical>> {
425        let scale = self.output.current_scale().fractional_scale();
426        let (w, h) = if self.owner.is_some() {
427            self.capture
428                .as_ref()
429                .map(|c| (c.settings.width, c.settings.height))
430                .unwrap_or(self.view_size)
431        } else {
432            let mode = self.output.current_mode()?;
433            (mode.size.w, mode.size.h)
434        };
435        Some(Rectangle::new(
436            Point::from(self.pos),
437            (
438                (f64::from(w) / scale).round() as i32,
439                (f64::from(h) / scale).round() as i32,
440            )
441                .into(),
442        ))
443    }
444}
445
446pub(crate) static NEXT_WINDOW_ID: AtomicU32 = AtomicU32::new(1);
447
448/// Per-window bookkeeping carried in the window's user-data map: a stable numeric id the
449/// Python side addresses the window by, the display id of the output it is placed on, and
450/// whether that output has been chosen yet.
451pub struct WindowMeta {
452    pub id: u32,
453    pub output: AtomicU32,
454    /// Set once the first commit has picked the window's output. The choice cannot key off
455    /// xdg's `initial_configure_sent`: a client that negotiates xdg-decoration (every
456    /// wlroots-based nested compositor, GTK, and Qt) is answered with a configure before it
457    /// ever commits, which would leave every window on the pointer's output.
458    pub placed: AtomicBool,
459    /// Set while the window is mapped outside every output, tagged to the output it will
460    /// take once one exists. See `park_window`.
461    pub parked: AtomicBool,
462}
463
464/// Where a parked window is mapped: clear of every output, so it is composited into none of
465/// them. Layout offsets are non-negative — the union layout Selkies computes re-anchors at
466/// the origin — so far negative coordinates can never collide with a real output.
467pub const PARKED_POS: (i32, i32) = (-(1 << 20), -(1 << 20));
468
469/// The logical size a parked screen is held at. A nested session lays its desktop out across
470/// every screen it has, including one waiting here: at the size of a real screen it would
471/// double the session's coordinate space, sending anything a client centers on the desktop
472/// (X11 applications place themselves) onto the screen nobody is watching. Small enough not
473/// to move that center, large enough to lay out on; `place_window_on_output` configures the
474/// real size the moment the screen is given an output.
475pub const PARKED_LOGICAL_SIZE: (i32, i32) = (320, 240);
476
477/// Whether a window is an X11 override-redirect one, which places and stacks itself.
478pub fn is_override_redirect(window: &Window) -> bool {
479    window
480        .x11_surface()
481        .is_some_and(|s| s.is_override_redirect())
482}
483
484/// The window's meta, inserted at `new_toplevel`; windows created before that (none in
485/// practice) read as id 0 / primary output.
486pub fn window_meta(window: &Window) -> Option<&WindowMeta> {
487    window.user_data().get::<WindowMeta>()
488}
489
490/// The display id of the output this window is placed on (primary when untagged).
491pub fn window_output_id(window: &Window) -> u32 {
492    window_meta(window)
493        .map(|m| m.output.load(Ordering::Relaxed))
494        .unwrap_or(0)
495}
496
497/// A screenshot waiting for its output to render; pixels are owned by the receiver.
498pub(crate) struct ScreenshotRequest {
499    pub display_id: u32,
500    pub resp: std::sync::mpsc::Sender<Result<crate::computer_use::ScreenshotFrame, String>>,
501    pub canceled: Arc<AtomicBool>,
502    pub deadline: Instant,
503}
504
505impl ScreenshotRequest {
506    fn inactive_reason(&self, now: Instant) -> Option<&'static str> {
507        if self.canceled.load(Ordering::Acquire) {
508            Some("Screenshot canceled")
509        } else if now >= self.deadline {
510            Some("Screenshot timed out")
511        } else {
512            None
513        }
514    }
515}
516
517/// Bounded admission on the compositor: one pending readback per output.
518#[derive(Default)]
519pub(crate) struct ScreenshotQueue {
520    requests: Vec<ScreenshotRequest>,
521}
522
523impl ScreenshotQueue {
524    pub fn enqueue(&mut self, request: ScreenshotRequest, now: Instant) {
525        self.prune(now);
526        let error = request.inactive_reason(now).or_else(|| {
527            if self.requests.len() >= crate::computer_use::MAX_SCREENSHOT_REQUESTS
528                || self.contains(request.display_id)
529            {
530                Some("Screenshot busy: a readback is already pending or the queue is full")
531            } else {
532                None
533            }
534        });
535        if let Some(error) = error {
536            let _ = request.resp.send(Err(error.to_string()));
537        } else {
538            self.requests.push(request);
539        }
540    }
541
542    pub fn prune(&mut self, now: Instant) {
543        self.requests.retain(|request| {
544            if let Some(error) = request.inactive_reason(now) {
545                let _ = request.resp.send(Err(error.to_string()));
546                false
547            } else {
548                true
549            }
550        });
551    }
552
553    pub fn contains(&self, display_id: u32) -> bool {
554        self.requests.iter().any(|r| r.display_id == display_id)
555    }
556
557    pub fn is_empty(&self) -> bool {
558        self.requests.is_empty()
559    }
560
561    pub fn take(&mut self, display_id: u32, now: Instant) -> Option<ScreenshotRequest> {
562        self.prune(now);
563        let index = self
564            .requests
565            .iter()
566            .position(|r| r.display_id == display_id)?;
567        Some(self.requests.remove(index))
568    }
569
570    pub fn fail_display(&mut self, display_id: u32, reason: &str) {
571        if let Some(index) = self
572            .requests
573            .iter()
574            .position(|r| r.display_id == display_id)
575        {
576            let request = self.requests.remove(index);
577            let _ = request.resp.send(Err(reason.to_string()));
578        }
579    }
580
581    pub fn fail_all(&mut self, reason: &str) {
582        for request in self.requests.drain(..) {
583            let _ = request.resp.send(Err(reason.to_string()));
584        }
585    }
586}
587
588#[cfg(test)]
589mod screenshot_queue_tests {
590    use super::{ScreenshotQueue, ScreenshotRequest};
591    use std::sync::{
592        Arc,
593        atomic::{AtomicBool, Ordering},
594        mpsc,
595    };
596    use std::time::{Duration, Instant};
597
598    fn request(
599        display_id: u32,
600        deadline: Instant,
601    ) -> (
602        ScreenshotRequest,
603        mpsc::Receiver<Result<crate::computer_use::ScreenshotFrame, String>>,
604    ) {
605        let (resp, rx) = mpsc::channel();
606        (
607            ScreenshotRequest {
608                display_id,
609                resp,
610                canceled: Arc::new(AtomicBool::new(false)),
611                deadline,
612            },
613            rx,
614        )
615    }
616
617    #[test]
618    fn screenshot_duplicate_does_not_replace_the_waiter() {
619        let now = Instant::now();
620        let mut queue = ScreenshotQueue::default();
621        let (first, first_rx) = request(7, now + Duration::from_secs(1));
622        let (second, second_rx) = request(7, now + Duration::from_secs(1));
623        queue.enqueue(first, now);
624        queue.enqueue(second, now);
625        assert!(
626            second_rx
627                .recv_timeout(Duration::from_secs(1))
628                .unwrap()
629                .err()
630                .unwrap()
631                .contains("busy")
632        );
633        assert!(matches!(
634            first_rx.try_recv(),
635            Err(mpsc::TryRecvError::Empty)
636        ));
637        assert!(
638            queue
639                .take(7, now)
640                .unwrap()
641                .resp
642                .send(Err("first reply".to_string()))
643                .is_ok()
644        );
645        assert_eq!(
646            first_rx
647                .recv_timeout(Duration::from_secs(1))
648                .unwrap()
649                .err()
650                .unwrap(),
651            "first reply"
652        );
653    }
654
655    #[test]
656    fn screenshot_outputs_have_independent_bounded_slots() {
657        let now = Instant::now();
658        let mut queue = ScreenshotQueue::default();
659        let mut receivers = Vec::new();
660        for display in 0..crate::computer_use::MAX_SCREENSHOT_REQUESTS as u32 {
661            let (request, rx) = request(display, now + Duration::from_secs(1));
662            queue.enqueue(request, now);
663            receivers.push(rx);
664        }
665        let (overflow, rx) = request(99, now + Duration::from_secs(1));
666        queue.enqueue(overflow, now);
667        assert!(
668            rx.recv_timeout(Duration::from_secs(1))
669                .unwrap()
670                .err()
671                .unwrap()
672                .contains("busy")
673        );
674        assert_eq!(queue.take(2, now).unwrap().display_id, 2);
675        assert!(queue.contains(0));
676        assert!(queue.contains(1));
677        assert!(queue.contains(3));
678        let (replacement, _) = request(99, now + Duration::from_secs(1));
679        queue.enqueue(replacement, now);
680        assert!(queue.contains(99));
681    }
682
683    #[test]
684    fn screenshot_canceled_and_expired_requests_release_slots() {
685        let now = Instant::now();
686        let mut queue = ScreenshotQueue::default();
687        let (canceled, canceled_rx) = request(0, now + Duration::from_secs(2));
688        let flag = canceled.canceled.clone();
689        let (expired, expired_rx) = request(1, now + Duration::from_secs(1));
690        queue.enqueue(canceled, now);
691        queue.enqueue(expired, now);
692        flag.store(true, Ordering::Release);
693        queue.prune(now + Duration::from_secs(1));
694        assert!(queue.is_empty());
695        assert_eq!(
696            canceled_rx
697                .recv_timeout(Duration::from_secs(1))
698                .unwrap()
699                .err()
700                .unwrap(),
701            "Screenshot canceled"
702        );
703        assert_eq!(
704            expired_rx
705                .recv_timeout(Duration::from_secs(1))
706                .unwrap()
707                .err()
708                .unwrap(),
709            "Screenshot timed out"
710        );
711        let (fresh, _) = request(0, now + Duration::from_secs(3));
712        queue.enqueue(fresh, now + Duration::from_secs(1));
713        assert!(queue.contains(0));
714    }
715
716    #[test]
717    fn screenshot_inactive_requests_never_enter_the_queue() {
718        let now = Instant::now();
719        let mut queue = ScreenshotQueue::default();
720        let (expired, rx) = request(0, now);
721        queue.enqueue(expired, now);
722        assert_eq!(
723            rx.recv_timeout(Duration::from_secs(1))
724                .unwrap()
725                .err()
726                .unwrap(),
727            "Screenshot timed out"
728        );
729        let (canceled, rx) = request(0, now + Duration::from_secs(1));
730        canceled.canceled.store(true, Ordering::Release);
731        queue.enqueue(canceled, now);
732        assert_eq!(
733            rx.recv_timeout(Duration::from_secs(1))
734                .unwrap()
735                .err()
736                .unwrap(),
737            "Screenshot canceled"
738        );
739        assert!(queue.is_empty());
740    }
741
742    #[test]
743    fn screenshot_timeout_is_checked_again_before_handoff() {
744        let now = Instant::now();
745        let mut queue = ScreenshotQueue::default();
746        let (pending, rx) = request(4, now + Duration::from_secs(1));
747        queue.enqueue(pending, now);
748        assert!(queue.take(4, now + Duration::from_secs(1)).is_none());
749        assert_eq!(
750            rx.recv_timeout(Duration::from_secs(1))
751                .unwrap()
752                .err()
753                .unwrap(),
754            "Screenshot timed out"
755        );
756        assert!(queue.is_empty());
757    }
758
759    #[test]
760    fn screenshot_output_removal_does_not_cancel_other_outputs() {
761        let now = Instant::now();
762        let mut queue = ScreenshotQueue::default();
763        let (removed, removed_rx) = request(2, now + Duration::from_secs(1));
764        let (other, other_rx) = request(3, now + Duration::from_secs(1));
765        queue.enqueue(removed, now);
766        queue.enqueue(other, now);
767        queue.fail_display(2, "removed");
768        assert_eq!(
769            removed_rx
770                .recv_timeout(Duration::from_secs(1))
771                .unwrap()
772                .err()
773                .unwrap(),
774            "removed"
775        );
776        assert!(queue.contains(3));
777        assert!(matches!(
778            other_rx.try_recv(),
779            Err(mpsc::TryRecvError::Empty)
780        ));
781        let (reused, _) = request(2, now + Duration::from_secs(1));
782        queue.enqueue(reused, now);
783        assert!(queue.contains(2));
784        queue.fail_all("disconnected");
785        assert!(queue.is_empty());
786        assert_eq!(
787            other_rx
788                .recv_timeout(Duration::from_secs(1))
789                .unwrap()
790                .err()
791                .unwrap(),
792            "disconnected"
793        );
794    }
795}
796
797/// Where the seat's pointer is, as `PointerShare::read` returns it.
798#[derive(Clone, Copy, Debug, PartialEq)]
799pub struct PointerAt {
800    /// Physical union-layout coordinates, the space absolute motion is injected in.
801    pub x: f64,
802    pub y: f64,
803    /// The fractional scale of the output the pointer is on: a relative move, applied in
804    /// logical pixels, travels this many physical pixels per pixel.
805    pub scale: f64,
806    /// The last numbered motion command the compositor has applied (`WaylandBackend`).
807    pub motion: u64,
808}
809
810/// The seat's pointer for Python to read without a round trip to the compositor thread:
811/// written once per event-loop pass from wherever it moved (an injected move, a client's
812/// warp, an output going away) under a sequence count, so a read never pairs one pass's
813/// position with another's motion number and the compositor never waits on a reader.
814#[derive(Default)]
815pub struct PointerShare {
816    /// Odd while a pass writes, zero before the first.
817    version: AtomicU64,
818    /// x and y as two `f32`s, x in the low half.
819    position: AtomicU64,
820    scale: AtomicU64,
821    motion: AtomicU64,
822}
823
824impl PointerShare {
825    /// Called from the compositor thread alone.
826    pub fn publish(&self, at: PointerAt) {
827        let position =
828            u64::from((at.x as f32).to_bits()) | u64::from((at.y as f32).to_bits()) << 32;
829        let scale = at.scale.to_bits();
830        let version = self.version.load(Ordering::Relaxed);
831        if version != 0
832            && self.position.load(Ordering::Relaxed) == position
833            && self.scale.load(Ordering::Relaxed) == scale
834            && self.motion.load(Ordering::Relaxed) == at.motion
835        {
836            return;
837        }
838        self.version.store(version + 1, Ordering::Relaxed);
839        fence(Ordering::Release);
840        self.position.store(position, Ordering::Relaxed);
841        self.scale.store(scale, Ordering::Relaxed);
842        self.motion.store(at.motion, Ordering::Relaxed);
843        self.version.store(version + 2, Ordering::Release);
844    }
845
846    pub fn read(&self) -> Option<PointerAt> {
847        loop {
848            let version = self.version.load(Ordering::Acquire);
849            if version == 0 {
850                return None;
851            }
852            if version & 1 == 0 {
853                let position = self.position.load(Ordering::Relaxed);
854                let scale = self.scale.load(Ordering::Relaxed);
855                let motion = self.motion.load(Ordering::Relaxed);
856                fence(Ordering::Acquire);
857                if self.version.load(Ordering::Relaxed) == version {
858                    return Some(PointerAt {
859                        x: f64::from(f32::from_bits(position as u32)),
860                        y: f64::from(f32::from_bits((position >> 32) as u32)),
861                        scale: f64::from_bits(scale),
862                        motion,
863                    });
864                }
865            }
866            std::hint::spin_loop();
867        }
868    }
869}
870
871/// Central context threaded through every Smithay handler; owns the Wayland globals, the
872/// GBM/EGL (or pixman) renderer state, and the capture/encode pipeline state.
873///
874/// A single `AppState` lives for the whole compositor thread and is mutated in place by the
875/// calloop event sources (client dispatch, input injection, the capture timer). The frame
876/// pipeline is instantiated PER DISPLAY: each `OutputNode` in `output_nodes` owns its output,
877/// damage tracker, and render targets, and its optional `WlCapture` owns that display's
878/// encoder set and delivery. The non-obvious `WlCapture`/`OutputNode` fields encode the
879/// pipeline's threading and buffer strategy:
880///
881/// 1. **Render / encode targets** (per display):
882///    - **`video_encoder`**: the zero-copy GPU session only (GLES render + same-GPU dmabuf
883///      encode). Its EGL/dmabuf handles are calloop-affine, so this encoder runs inline on the
884///      calloop thread; it is `None` whenever a readback path is active.
885///    - **`encode_pool` / `encode_join`**: the readback capture||encode split. The calloop renders
886///      and reads back into a pooled host buffer and publishes it, while a separate encode thread
887///      owns the CPU / cross-GPU / pixman-HW encoders. `encode_join` returns that thread's hardware
888///      session on shutdown so a restart can reconfigure it in place rather than rebuild it. Both
889///      are `None` while a zero-copy GPU session runs.
890///    - **`frame_buffer`**: scratch render target used only by the pixman memory-throttle path; the
891///      normal readback path renders and reads back into the pooled buffers instead.
892///    - **`pool_last_render` / `render_seq`**: buffer-age bookkeeping for the pixman path, which
893///      renders directly into the pooled buffers (an age is "renders since this slot was last the
894///      target"). The GLES path renders into one fixed offscreen buffer and never consults these.
895///    - **`pool_content_gen` / `content_gen`**: staleness bookkeeping for the GLES readback path,
896///      which skips the GPU readback on no-damage ticks. `content_gen` advances whenever a render
897///      reports damage; a pooled buffer whose stamp lags it holds pre-damage pixels and must be
898///      read back once before publishing, so the encoder's paint-over / burst / recovery sends
899///      never ship stale content.
900///
901/// 2. **Delivery** (`deliver_tx` / `deliver_join`): encoded frames go to a dedicated delivery
902///    thread over a capacity-1 rendezvous channel, mirroring the X11 single-slot FramePool
903///    (non-dropping, ordered, at most one frame of blocking backpressure), so a slow GIL-holding
904///    Python callback can never stall input/control dispatch on the calloop thread.
905///
906/// 3. **Keyframes** (`pending_force_idr`): set by an IDR request (client reconnect / decoder reset)
907///    and consumed once on the next captured frame to force an immediate keyframe.
908///
909/// 4. **Clipboard** (`pending_clipboard_read`): stages the mimes chosen in `new_selection`, or a
910///    cleared selection; the loop drains them only after the dispatch that stores the new client
911///    source, so the read targets the new selection rather than the previous one.
912///
913/// 5. **GPU selection** (`auto_gpu_selected`): records that automatic (not explicit) selection
914///    picked `render_node_path`, so `StartCapture` aims the encoder at that same node unless a
915///    device was chosen explicitly.
916pub struct AppState {
917    pub compositor_state: CompositorState,
918    pub fractional_scale_state: FractionalScaleManagerState,
919    pub viewporter_state: ViewporterState,
920    pub presentation_state: PresentationState,
921    pub shm_state: ShmState,
922    pub single_pixel_buffer: SinglePixelBufferState,
923    pub dmabuf_state: DmabufState,
924    pub dmabuf_global: Option<DmabufGlobal>,
925    pub ext_data_control_state: ExtDataControlState,
926    pub cursor_shape_state: CursorShapeManagerState,
927    pub image_capture_source_state: ImageCaptureSourceState,
928    pub output_capture_source_state: OutputCaptureSourceState,
929    pub image_copy_capture_state: ImageCopyCaptureState,
930    pub copy_sessions: Vec<OutputCopySession>,
931    pub output_state: OutputManagerState,
932    pub seat_state: SeatState<AppState>,
933    pub shell_state: XdgShellState,
934    pub layer_shell_state: WlrLayerShellState,
935    pub space: Space<Window>,
936    pub data_device_state: DataDeviceState,
937    pub data_control_state: DataControlState,
938    pub dh: DisplayHandle,
939    pub seat: Seat<AppState>,
940    /// Every live output with its per-display render/capture state; index 0 is the
941    /// primary (display id 0), which is never destroyed.
942    pub output_nodes: Vec<OutputNode>,
943    pub pending_windows: Vec<Window>,
944
945    pub foreign_toplevel_list: ForeignToplevelListState,
946    /// The shortcuts-inhibit protocol: a client whose window has the keyboard may ask that the
947    /// window policy's binds stand aside while it reads a chord (the desktop shell's shortcut
948    /// editor); the policy's key filter honors an active inhibitor on the focused window.
949    pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
950    pub xdg_decoration_state: XdgDecorationState,
951    pub xdg_activation_state: XdgActivationState,
952    pub primary_selection_state: PrimarySelectionState,
953    pub popups: PopupManager,
954
955    /// Screenshot readback buffers, ahead of the renderer: dropped first, they forget their
956    /// mappings while its GL context still holds them.
957    pub(crate) screenshot_readbacks: crate::ScreenshotReadbacks,
958    pub gles_renderer: Option<GlesRenderer>,
959    pub pixman_renderer: Option<PixmanRenderer>,
960
961    pub gbm_device: Option<RawGbmDevice<File>>,
962
963    /// Mirror of the PRIMARY display's capture settings (geometry fallbacks, computer-use
964    /// info); per-display settings live on each capture.
965    pub settings: CaptureSettings,
966    /// A cursor sink is registered on the `wl-cursor` worker (which owns it); tracked here
967    /// so sprite resolution is skipped while nobody listens.
968    pub cursor_callback_set: bool,
969    /// Cursor delivery jobs to the `wl-cursor` worker (PNG encode + sink call off-thread).
970    pub cursor_tx: std::sync::mpsc::Sender<CursorJob>,
971    pub clipboard_callback: Option<Arc<dyn crate::sink::ClipboardSink>>,
972    /// The mimes of the current client selection staged for one read, delivered together; an
973    /// empty list stages the report of a selection a client cleared.
974    pub pending_clipboard_read: Option<Vec<String>>,
975    /// Bumped by every staged delivery. A reader thread hands its entries over only while its
976    /// delivery is still the newest, so a slow source cannot land after a later copy or clear.
977    pub clipboard_generation: Arc<AtomicU64>,
978    /// The mimes chosen from the current CLIENT-owned clipboard selection, recorded even while
979    /// no callback is registered so `SetClipboardCallback` can re-stage a read of a copy made
980    /// in the gap; empty when the selection is cleared or compositor-owned.
981    pub current_selection_mimes: Vec<String>,
982
983    pub last_log_time: Instant,
984    pub start_time: Instant,
985    pub clock: Clock<Monotonic>,
986
987    pub use_gpu: bool,
988
989    pub cursor_helper: Cursor,
990
991    /// Seat keymap owner: base layout plus batched overlay binds. Every seat keymap swap
992    /// flows through this policy, so keymap identity has exactly one writer.
993    pub keymap_policy: KeymapPolicy,
994    /// Host-capture session when pixelflux captures an EXTERNAL compositor:
995    /// frames arrive by screencopy and input routes to its virtual devices.
996    pub host: Option<crate::wayland::host::HostSession>,
997    /// Per display, the layout request the host has not answered yet. A capture start
998    /// submits its mode and carries on at that size; the render tick waits for the host to
999    /// answer, then re-sizes the capture to the mode the host announces. Emptied with the
1000    /// host session.
1001    pub host_layout_pending: std::collections::HashMap<u32, PendingHostLayout>,
1002    /// Wakes the calloop when a host capture thread has queued a frame, so its display
1003    /// renders that frame at once (at frame pace, see `FramePace`) rather than on the next
1004    /// timer tick.
1005    pub host_frame_tx: smithay::reexports::calloop::channel::Sender<usize>,
1006    /// Per display, how many mode requests in a row the host has answered with a different
1007    /// mode; cleared when it applies one. Past a few, the log names the remedy.
1008    pub host_mode_refusals: std::collections::HashMap<u32, u32>,
1009
1010    pub current_cursor_icon: Option<CursorImageStatus>,
1011    /// A surface-backed cursor set during the dispatch in progress and not delivered yet:
1012    /// clients commit the sprite's buffer after `set_cursor` in the same batch, so delivery
1013    /// waits for that commit, or for the end of the dispatch when none comes, rather than
1014    /// shipping the surface's previous sprite under the new hotspot.
1015    pub cursor_surface_pending: bool,
1016    pub cursor_buffer: Option<WlBuffer>,
1017    /// The icon a client's drag carries, drawn at the pointer into every frame while the drag
1018    /// lasts (the cursor itself is the browser's to draw, a drag's icon is not), with the offset
1019    /// from the pointer its commits moved it to.
1020    pub dnd_icon: Option<(WlSurface, Point<i32, Logical>)>,
1021    pub render_cursor_on_framebuffer: bool,
1022    pub pointer_warp_state: PointerWarpManager,
1023    pub relative_pointer_state: RelativePointerManagerState,
1024    pub pointer_constraints_state: PointerConstraintsState,
1025    pub render_node_path: String,
1026    pub auto_gpu_selected: bool,
1027    /// Screenshot readbacks waiting for their output's next render.
1028    pub(crate) pending_screenshots: ScreenshotQueue,
1029    /// The command channel, drained in place (wakeups arrive on a separate ping channel) so
1030    /// the render tick can apply every queued command BEFORE starting a long render/encode —
1031    /// queued input is never starved behind the tick it arrived during.
1032    pub command_rx: Option<smithay::reexports::calloop::channel::Channel<crate::ThreadCommand>>,
1033    /// When the last input command landed: the no-capture tick speeds up right
1034    /// after input so apps pacing on frame callbacks respond promptly (they
1035    /// are the ones noticing the idle rate while nobody encodes).
1036    pub last_input_at: Option<Instant>,
1037    /// When the last pointer move landed, and the smoothed spacing between moves: what
1038    /// tells a client pacing its moves at the frame cadence from a faster or slower one
1039    /// (`FramePace::input_paced`). A gap longer than a few periods is a pause rather than a
1040    /// spacing and is clamped before it enters the average.
1041    pub pointer_motion_at: Option<Instant>,
1042    pub input_interval: Option<Duration>,
1043    /// Where the pointer is, published each event-loop pass for the backend's `pointer_location`.
1044    pub pointer_share: Arc<PointerShare>,
1045    /// The highest motion number among the motion commands applied (`PointerAt::motion`).
1046    pub motion_applied: u64,
1047    /// Whether the frame timer is armed at the long idle interval: input
1048    /// arriving then must not wait that deadline out, so the wake handler
1049    /// sends the idle frame callbacks itself (frame-paced via
1050    /// `last_idle_service_at`) instead of waiting for the timer.
1051    pub frame_idle_long: bool,
1052    /// When idle frame callbacks were last serviced, bounding the wake
1053    /// handler's servicing to frame pace under an input storm.
1054    pub last_idle_service_at: Option<Instant>,
1055    /// Deliver threads of stopped captures, joined without blocking the event
1056    /// loop: the timer tick reaps the finished ones, and the shutdown Barrier
1057    /// drains the rest so nothing that can attach to Python outlives it.
1058    pub deliver_reaper: Vec<std::thread::JoinHandle<()>>,
1059    /// Encode threads whose teardown-time harvest expired (wedged behind a
1060    /// consumer inside Python); reaped like the deliver threads, with the
1061    /// unclaimed encoder dropped here on the event loop — its usual home.
1062    pub encode_reaper: Vec<std::thread::JoinHandle<Option<crate::FrameEncoder>>>,
1063    /// The installed window policy and what the compositor keeps for it; `None` leaves every
1064    /// window fullscreen on its output (`crate::wayland::wm`).
1065    pub wm: Option<crate::wayland::wm::WmState>,
1066    /// The window list taskbars read (`zwlr_foreign_toplevel_manager_v1`).
1067    pub foreign_toplevel: crate::wayland::foreign_toplevel::ForeignToplevelState,
1068    /// The virtual desktops pagers read and switch (`ext_workspace_manager_v1`).
1069    pub workspace: crate::wayland::workspace::WorkspaceState,
1070    /// Xwayland and its window manager, started under a window policy.
1071    pub xwm: crate::wayland::xwm::XwmState,
1072    /// The compositor's event loop, for sources started after it is up (Xwayland).
1073    pub loop_handle: smithay::reexports::calloop::LoopHandle<'static, AppState>,
1074    /// The window holding the keyboard, as the seat last reported it: read where the seat's
1075    /// own lock is held (`focus_changed` runs under it), where asking the keyboard would wait
1076    /// on itself.
1077    pub keyboard_focus: Option<u32>,
1078}
1079
1080/// Pointer-constraints protocol. A lock is activated for the surface the pointer already holds,
1081/// and `activate_constraint_under` picks up one asked for ahead of the pointer when the motion
1082/// that enters it arrives: SDL asks as relative mode goes on, which is before it has the pointer.
1083/// Confinement regions and cursor-position hints are still accepted as no-ops.
1084impl PointerConstraintsHandler for AppState {
1085    fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
1086        let Some(focus) = pointer.current_focus() else {
1087            return;
1088        };
1089        if focus.wl_surface().as_deref() != Some(surface) {
1090            return;
1091        }
1092        with_pointer_constraint(surface, pointer, |constraint| {
1093            if let Some(constraint) = constraint {
1094                constraint.activate();
1095            }
1096        });
1097    }
1098}
1099
1100/// Foreign-toplevel-list protocol: exposes the managed state so Smithay can advertise each
1101/// toplevel (title / app-id) to listing clients such as taskbars.
1102impl ForeignToplevelListHandler for AppState {
1103    fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
1104        &mut self.foreign_toplevel_list
1105    }
1106}
1107
1108/// xdg-activation protocol: hands out activation tokens and, on redemption, raises the
1109/// target window.
1110///
1111/// `token_created` accepts every token. `request_activation` honors a token only while it is fresh
1112/// (issued less than 10 seconds ago) so a stale or replayed token cannot steal focus, then raises
1113/// the window whose surface matches to the top of the space.
1114impl KeyboardShortcutsInhibitHandler for AppState {
1115    fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
1116        &mut self.keyboard_shortcuts_inhibit_state
1117    }
1118
1119    /// An inhibitor is granted only to the window that holds the keyboard, so a client cannot
1120    /// take the session's shortcuts away from behind another window.
1121    fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
1122        let focused = crate::wayland::wm::focused_surface(self);
1123        if focused.as_ref() == Some(inhibitor.wl_surface()) {
1124            inhibitor.activate();
1125        }
1126    }
1127}
1128
1129impl XdgActivationHandler for AppState {
1130    fn activation_state(&mut self) -> &mut XdgActivationState {
1131        &mut self.xdg_activation_state
1132    }
1133
1134    fn token_created(&mut self, _token: XdgActivationToken, _data: XdgActivationTokenData) -> bool {
1135        true
1136    }
1137
1138    fn request_activation(
1139        &mut self,
1140        _token: XdgActivationToken,
1141        token_data: XdgActivationTokenData,
1142        surface: WlSurface,
1143    ) {
1144        if token_data.timestamp.elapsed().as_secs() < 10 {
1145            let window = self
1146                .space
1147                .elements()
1148                .find(|w| w.wl_surface().as_deref() == Some(&surface))
1149                .cloned();
1150            if let Some(window) = window {
1151                let id = window_meta(&window).map(|m| m.id).unwrap_or(0);
1152                if !crate::wayland::wm::request(self, id, crate::wm::Request::Activate) {
1153                    self.space.raise_element(&window, true);
1154                }
1155            }
1156        }
1157    }
1158}
1159
1160/// Carry the X11-style primary selection so middle-click paste works between clients.
1161/// Exposing the managed state lets Smithay record which client currently owns the primary selection;
1162/// `focus_changed` then keeps that ownership tracking keyboard focus, so a middle-click pastes from
1163/// whichever client the user is actually working in.
1164impl PrimarySelectionHandler for AppState {
1165    fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
1166        &mut self.primary_selection_state
1167    }
1168}
1169
1170/// Default every toplevel to server-side decorations so clients don't bake their own title
1171/// bars and borders into the captured image.
1172///
1173/// The frontend shows fullscreen application content with no window-manager chrome, so a client
1174/// left to draw client-side decorations would paint a stray title bar straight into the encoded
1175/// frame. Pinning the negotiation to `Mode::ServerSide` (in `new_decoration`, and again when a
1176/// client unsets its preference in `unset_mode`) hands decoration duty to the compositor — which
1177/// draws none — leaving clean, borderless output; `request_mode` still grants a mode a client
1178/// explicitly insists on. Each path acknowledges with a configure.
1179impl XdgDecorationHandler for AppState {
1180    fn new_decoration(&mut self, toplevel: ToplevelSurface) {
1181        toplevel.with_pending_state(|state| {
1182            state.decoration_mode = Some(Mode::ServerSide);
1183        });
1184        toplevel.send_configure();
1185        // A client that asks and states no preference leaves the choice to the policy; one
1186        // that never asks draws its own frame.
1187        if let Some(window) = self.window_of_toplevel(&toplevel) {
1188            crate::wayland::wm::set_decoration(self, &window, crate::wm::Decoration::Unspecified);
1189        }
1190    }
1191
1192    fn request_mode(&mut self, toplevel: ToplevelSurface, mode: Mode) {
1193        toplevel.with_pending_state(|state| {
1194            state.decoration_mode = Some(mode);
1195        });
1196        toplevel.send_configure();
1197        if let Some(window) = self.window_of_toplevel(&toplevel) {
1198            let preference = match mode {
1199                Mode::ClientSide => crate::wm::Decoration::Client,
1200                _ => crate::wm::Decoration::Server,
1201            };
1202            crate::wayland::wm::set_decoration(self, &window, preference);
1203        }
1204    }
1205
1206    fn unset_mode(&mut self, toplevel: ToplevelSurface) {
1207        toplevel.with_pending_state(|state| {
1208            state.decoration_mode = Some(Mode::ServerSide);
1209        });
1210        toplevel.send_configure();
1211        if let Some(window) = self.window_of_toplevel(&toplevel) {
1212            crate::wayland::wm::set_decoration(self, &window, crate::wm::Decoration::Unspecified);
1213        }
1214    }
1215}
1216
1217/// wlr-layer-shell protocol: places panels / overlays / backgrounds (layer surfaces).
1218///
1219/// `new_layer_surface` resolves the requested output (or the first output), configures the surface
1220/// to that output's full pixel size, and maps it into the output's layer map so the render loop
1221/// composites it in the correct z-order. `layer_destroyed` drops the surface from every layer map
1222/// and arranges them again, so the work areas give back what it reserved.
1223impl WlrLayerShellHandler for AppState {
1224    fn shell_state(&mut self) -> &mut WlrLayerShellState {
1225        &mut self.layer_shell_state
1226    }
1227
1228    fn new_layer_surface(
1229        &mut self,
1230        surface: WlrLayerSurface,
1231        output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
1232        _layer: WlrLayer,
1233        namespace: String,
1234    ) {
1235        let smithay_output = if let Some(wlo) = output.as_ref() {
1236            self.output_nodes
1237                .iter()
1238                .map(|n| &n.output)
1239                .find(|o| o.owns(wlo))
1240        } else {
1241            self.primary_output()
1242        };
1243
1244        if let Some(output) = smithay_output {
1245            let mode = output.current_mode().unwrap();
1246
1247            surface.with_pending_state(|state| {
1248                state.size =
1249                    Some((f64::from(mode.size.w) as i32, f64::from(mode.size.h) as i32).into());
1250            });
1251            surface.send_configure();
1252
1253            let layer = DesktopLayerSurface::new(surface, namespace);
1254            let _ = layer_map_for_output(output).map_layer(&layer);
1255        }
1256    }
1257
1258    fn layer_destroyed(&mut self, _surface: WlrLayerSurface) {
1259        for node in &self.output_nodes {
1260            let mut map = layer_map_for_output(&node.output);
1261            map.cleanup();
1262            map.arrange();
1263        }
1264        crate::wayland::wm::outputs_changed(self);
1265        crate::wayland::wm::sync_layer_keyboard(self);
1266    }
1267}
1268
1269/// Core `wl_compositor` protocol: per-surface commit handling plus the window
1270/// map/configure/focus state machine.
1271impl CompositorHandler for AppState {
1272    fn compositor_state(&mut self) -> &mut CompositorState {
1273        &mut self.compositor_state
1274    }
1275    fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
1276        if let Some(data) = client.get_data::<smithay::xwayland::XWaylandClientData>() {
1277            return &data.compositor_state;
1278        }
1279        &client.get_data::<ClientState>().unwrap().compositor_state
1280    }
1281
1282    /// Fold a client's `wl_surface.commit` into compositor state and — the reason the window
1283    /// logic lives in this handler — walk a brand-new toplevel through the xdg-shell handshake it
1284    /// must finish before it may be shown.
1285    ///
1286    /// A window cannot just appear on its first commit: xdg-shell requires the compositor to send an
1287    /// initial configure (telling the client the size and state to draw at) and the client to ack it
1288    /// and commit a buffer matching that size before the surface counts as mapped. Mapping earlier
1289    /// would flash an unconfigured, wrongly-sized window into the captured frame. So a pending
1290    /// toplevel is carried across two commits instead of one, while everything else here is
1291    /// per-commit housekeeping that must run whether or not a map is in flight. On each commit, in
1292    /// order:
1293    ///
1294    /// 1. **Buffer intake**: `on_commit_buffer_handler` ingests the newly attached buffer.
1295    /// 2. **Layer relayout**: if the surface is a mapped layer surface, re-arrange the output's
1296    ///    layer map so geometry tracks the new content.
1297    /// 3. **Cursor refresh**: if the surface backs the current cursor, re-send its image so a client
1298    ///    animating its own cursor surface is reflected downstream.
1299    /// 4. **Foreign-toplevel metadata**: push the current title / app-id to the foreign-toplevel
1300    ///    handle for taskbar-style clients.
1301    /// 5. **Window on-commit**: forward the commit to the matching mapped `Window`.
1302    /// 6. **Two-phase map of a pending toplevel**:
1303    ///    - **First commit — no initial configure sent yet**: this commit is the client announcing
1304    ///      it wants to be shown, so compute the logical size from the output mode/scale (falling
1305    ///      back to the settings resolution), send a fullscreen + activated configure, and re-queue
1306    ///      the window to wait for the client's acked commit — nothing is mapped yet.
1307    ///    - **Acked commit — the client has drawn to that configure**: map the element at the origin,
1308    ///      refresh its cached bounding box *before* reading geometry (so the drift check sees
1309    ///      current geometry and doesn't fire a redundant configure), enter the output, and, only if
1310    ///      the client's geometry still drifts more than a pixel from the expected fullscreen size,
1311    ///      send one corrective configure. Finally give the new window keyboard focus so input lands
1312    ///      on it at once.
1313    fn commit(&mut self, surface: &WlSurface) {
1314        // New content (a buffer or damage) on any surface but the cursor's, read before the
1315        // renderer state takes it: the captures render it as it lands (`serve_changes`).
1316        let new_content = with_states(surface, |states| {
1317            states.role != Some("cursor_image") && {
1318                let mut attrs = states.cached_state.get::<SurfaceAttributes>();
1319                let current = attrs.current();
1320                current.buffer.is_some() || !current.damage.is_empty()
1321            }
1322        });
1323        // Under host capture the host's frames are what is captured, and they mark their own.
1324        if new_content && self.host.is_none() {
1325            for node in &mut self.output_nodes {
1326                if let Some(cap) = node.capture.as_mut() {
1327                    cap.change_pending = true;
1328                }
1329            }
1330        }
1331        smithay::backend::renderer::utils::on_commit_buffer_handler::<Self>(surface);
1332        if let Some((icon, offset)) = self.dnd_icon.as_mut()
1333            && icon == surface
1334        {
1335            let delta = with_states(surface, |states| {
1336                states
1337                    .cached_state
1338                    .get::<SurfaceAttributes>()
1339                    .current()
1340                    .buffer_delta
1341                    .take()
1342            });
1343            *offset += delta.unwrap_or_default();
1344        }
1345        // A popup created without a parent, as a layer surface's always is, joins its parent's
1346        // popups when it maps.
1347        self.popups.commit(surface);
1348
1349        for node in &self.output_nodes {
1350            let mut layer_map = layer_map_for_output(&node.output);
1351            let found = layer_map
1352                .layers()
1353                .any(|layer| layer.wl_surface() == surface);
1354            if found {
1355                layer_map.arrange();
1356                drop(layer_map);
1357                crate::wayland::wm::outputs_changed(self);
1358                crate::wayland::wm::sync_layer_keyboard(self);
1359                break;
1360            }
1361        }
1362
1363        if let Some(CursorImageStatus::Surface(ref cursor_surface)) = self.current_cursor_icon
1364            && cursor_surface == surface
1365        {
1366            let status = CursorImageStatus::Surface(surface.clone());
1367            self.cursor_surface_pending = false;
1368            self.send_cursor_image(&status);
1369        }
1370
1371        let mut foreign_update = None;
1372        if let Some(handle) = with_states(surface, |states| {
1373            states.data_map.get::<ForeignToplevelHandle>().cloned()
1374        }) && let Some(window) = self
1375            .space
1376            .elements()
1377            .find(|w| w.wl_surface().as_deref() == Some(surface))
1378            && let Some(_toplevel) = window.toplevel()
1379        {
1380            let (title, app_id) = with_states(surface, |states| {
1381                let attributes = states
1382                    .data_map
1383                    .get::<XdgToplevelSurfaceData>()
1384                    .unwrap()
1385                    .lock()
1386                    .unwrap();
1387                (attributes.title.clone(), attributes.app_id.clone())
1388            });
1389
1390            handle.send_title(&title.unwrap_or_default());
1391            handle.send_app_id(&app_id.unwrap_or_default());
1392            handle.send_done();
1393            foreign_update = window_meta(window).map(|m| m.id);
1394        }
1395        if let Some(id) = foreign_update {
1396            crate::wayland::foreign_toplevel::update(self, id);
1397        }
1398
1399        let mapped = self
1400            .space
1401            .elements()
1402            .find(|w| {
1403                w.toplevel()
1404                    .map(|tl| tl.wl_surface() == surface)
1405                    .unwrap_or(false)
1406            })
1407            .cloned();
1408        if let Some(window) = mapped {
1409            window.on_commit();
1410            // A null-buffer commit unmaps the toplevel (xdg-shell): purge it from the
1411            // space so it no longer lists or renders, and re-queue it so a client that
1412            // maps again goes back through the configure handshake.
1413            let has_buffer =
1414                smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
1415                    s.buffer().is_some()
1416                })
1417                .unwrap_or(false);
1418            if !has_buffer {
1419                self.space.unmap_elem(&window);
1420                if let Some(id) = window_meta(&window).map(|m| m.id) {
1421                    crate::wayland::foreign_toplevel::remove(self, id);
1422                }
1423                crate::wayland::wm::window_unmapped(self, &window);
1424                self.pending_windows.push(window);
1425                self.refocus_pointer();
1426                return;
1427            }
1428            let size = window.geometry().size;
1429            crate::wayland::wm::window_resized(
1430                self,
1431                &window,
1432                crate::wm::Size {
1433                    w: size.w,
1434                    h: size.h,
1435                },
1436            );
1437        }
1438
1439        if let Some(idx) = self.pending_windows.iter().position(|w| {
1440            w.toplevel()
1441                .map(|tl| tl.wl_surface() == surface)
1442                .unwrap_or(false)
1443        }) {
1444            let window = self.pending_windows.remove(idx);
1445            let toplevel = window.toplevel().unwrap();
1446
1447            // Whether this window still needs an output. Deliberately not xdg's
1448            // `initial_configure_sent`: the decoration handshake answers with a configure
1449            // before the client's first commit, which would skip the choice below for
1450            // every client that negotiates decorations.
1451            let needs_placement = window_meta(&window)
1452                .map(|meta| !meta.placed.swap(true, Ordering::Relaxed))
1453                .unwrap_or(false);
1454
1455            if needs_placement && crate::wayland::wm::active(self) {
1456                // Under a window policy the policy configures the window; the output under
1457                // the pointer is where it opens unless the policy says otherwise.
1458                let target_id = self.pointer_display();
1459                if let Some(meta) = window_meta(&window) {
1460                    meta.output.store(target_id, Ordering::Relaxed);
1461                }
1462                self.pending_windows.push(window.clone());
1463                crate::wayland::wm::window_added(self, &window);
1464                if !toplevel.is_initial_configure_sent() {
1465                    toplevel.send_configure();
1466                }
1467            } else if needs_placement {
1468                // A new toplevel opens fullscreened on the output the pointer is on
1469                // (primary when indeterminate); the choice is pinned on the window's
1470                // meta so the acked commit maps to the same output.
1471                let mut target_id = self.pointer_display();
1472                // A second fullscreen surface from a client that already owns one on
1473                // the target output is screen-like (a nested compositor opens one
1474                // host toplevel per screen): give it an empty output when one exists,
1475                // and park it otherwise rather than let it cover the screen already
1476                // there. `create_output` hands it the output it is waiting for.
1477                let mut park = false;
1478                if self.would_cover_screen(&window, target_id) {
1479                    // Screens only: a view is a capture over someone else's
1480                    // screen, and no window is ever tagged to one, so a view
1481                    // always reads as empty and would swallow the screen-like
1482                    // toplevel that `create_output` later needs parked.
1483                    let empty = self
1484                        .output_nodes
1485                        .iter()
1486                        .filter(|n| n.owner.is_none())
1487                        .map(|n| n.id)
1488                        .find(|oid| {
1489                            !self
1490                                .space
1491                                .elements()
1492                                .chain(self.pending_windows.iter())
1493                                .any(|w| window_output_id(w) == *oid)
1494                        });
1495                    match empty {
1496                        Some(oid) => target_id = oid,
1497                        None => park = true,
1498                    }
1499                }
1500                if let Some(meta) = window_meta(&window) {
1501                    meta.output.store(target_id, Ordering::Relaxed);
1502                    meta.parked.store(park, Ordering::Relaxed);
1503                }
1504                let (logical_width, logical_height) = if park {
1505                    PARKED_LOGICAL_SIZE
1506                } else if let Some(size) = self.logical_size_of(target_id) {
1507                    size
1508                } else {
1509                    let scale = self.settings.scale.max(0.1);
1510                    (
1511                        (f64::from(self.settings.width) / scale).round() as i32,
1512                        (f64::from(self.settings.height) / scale).round() as i32,
1513                    )
1514                };
1515
1516                toplevel.with_pending_state(|state| {
1517                    state.states.set(XdgState::Activated);
1518                    state.states.set(XdgState::Fullscreen);
1519                    state.size = Some((logical_width, logical_height).into());
1520                });
1521                toplevel.send_configure();
1522
1523                self.pending_windows.push(window);
1524            } else if smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
1525                s.buffer().is_none()
1526            })
1527            .unwrap_or(true)
1528            {
1529                // Configured but still buffer-less (a decoration-triggered configure, an
1530                // ack-only commit, or a remap after a null-buffer unmap — xdg
1531                // initial_configure_sent stays true there): keep waiting; mapping now would
1532                // list and hit-test a phantom window. Answer with the forced-fullscreen
1533                // configure so the client draws its first buffer at the right geometry;
1534                // under a policy its configure stands.
1535                let tl = toplevel.clone();
1536                self.pending_windows.push(window);
1537                if !crate::wayland::wm::active(self) {
1538                    self.send_forced_fullscreen_configure(&tl);
1539                }
1540            } else if crate::wayland::wm::active(self) {
1541                let id = window_meta(&window).map(|m| m.id).unwrap_or(0);
1542                let rect = crate::wayland::wm::rect_of(self, id);
1543                let node_idx = self.node_idx_for_id(window_output_id(&window)).unwrap_or(0);
1544                let (target_output, pos) = {
1545                    let node = &self.output_nodes[node_idx];
1546                    (node.output.clone(), node.pos)
1547                };
1548                let at = rect.map(|r| (r.x, r.y)).unwrap_or(pos);
1549                self.space.map_element(window.clone(), at, false);
1550                window.on_commit();
1551                target_output.enter(surface);
1552                let scale = target_output.current_scale().fractional_scale();
1553                with_states(surface, |states| {
1554                    smithay::wayland::compositor::send_surface_state(
1555                        surface,
1556                        states,
1557                        scale.ceil() as i32,
1558                        smithay::utils::Transform::Normal,
1559                    );
1560                    smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1561                        fs.set_preferred_scale(scale);
1562                    });
1563                });
1564                let size = window.geometry().size;
1565                crate::wayland::wm::window_mapped(
1566                    self,
1567                    &window,
1568                    crate::wm::Size {
1569                        w: size.w,
1570                        h: size.h,
1571                    },
1572                );
1573                crate::wayland::foreign_toplevel::add(self, &window);
1574                self.refocus_pointer();
1575            } else {
1576                let target_id = window_output_id(&window);
1577                let parked = window_meta(&window)
1578                    .map(|meta| meta.parked.load(Ordering::Relaxed))
1579                    .unwrap_or(false);
1580                let node_idx = self.node_idx_for_id(target_id).unwrap_or(0);
1581                let (target_output, pos) = {
1582                    let node = &self.output_nodes[node_idx];
1583                    (node.output.clone(), node.pos)
1584                };
1585                // A parked window enters no output and takes no focus: it is waiting for
1586                // an output of its own, and holds its size until one arrives.
1587                self.space.map_element(
1588                    window.clone(),
1589                    if parked { PARKED_POS } else { pos },
1590                    !parked,
1591                );
1592                window.on_commit();
1593
1594                if !parked {
1595                    target_output.enter(surface);
1596                    let scale = target_output.current_scale().fractional_scale();
1597                    with_states(surface, |states| {
1598                        smithay::wayland::compositor::send_surface_state(
1599                            surface,
1600                            states,
1601                            scale.ceil() as i32,
1602                            smithay::utils::Transform::Normal,
1603                        );
1604                        smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1605                            fs.set_preferred_scale(scale);
1606                        });
1607                    });
1608
1609                    if let Some(geo_out) = self.output_nodes[node_idx].logical_geometry() {
1610                        let (expected_w, expected_h) = (geo_out.size.w, geo_out.size.h);
1611                        let geo = window.geometry();
1612                        if (geo.size.w - expected_w).abs() > 1
1613                            || (geo.size.h - expected_h).abs() > 1
1614                        {
1615                            toplevel.with_pending_state(|state| {
1616                                state.states.set(XdgState::Activated);
1617                                state.states.set(XdgState::Fullscreen);
1618                                state.size = Some((expected_w, expected_h).into());
1619                            });
1620                            toplevel.send_configure();
1621                        }
1622                    }
1623
1624                    let serial = next_serial();
1625                    let target = FocusTarget::Window(window.clone());
1626                    if let Some(keyboard) = self.seat.get_keyboard() {
1627                        keyboard.set_focus(self, Some(target), serial);
1628                    }
1629                    crate::wayland::foreign_toplevel::add(self, &window);
1630                    self.refocus_pointer();
1631                } else {
1632                    // A client that drew before answering the parked size (a nested
1633                    // compositor's spare screen starts at its backend's own default)
1634                    // is told again now that it has mapped, so the session lays out
1635                    // on a screen the size of the one it is waiting for.
1636                    let geo = window.geometry();
1637                    if (geo.size.w - PARKED_LOGICAL_SIZE.0).abs() > 1
1638                        || (geo.size.h - PARKED_LOGICAL_SIZE.1).abs() > 1
1639                    {
1640                        let tl = toplevel.clone();
1641                        self.send_forced_fullscreen_configure(&tl);
1642                    }
1643                }
1644            }
1645        }
1646    }
1647}
1648
1649impl AppState {
1650    /// The window (shown, pending, or hidden by the policy) a toplevel belongs to.
1651    pub(crate) fn window_of_toplevel(&self, toplevel: &ToplevelSurface) -> Option<Window> {
1652        self.space
1653            .elements()
1654            .chain(self.pending_windows.iter())
1655            .chain(self.wm.iter().flat_map(|wm| wm.hidden.values()))
1656            .find(|w| w.toplevel().is_some_and(|t| t == toplevel))
1657            .cloned()
1658    }
1659
1660    /// The policy's id of a toplevel's window.
1661    pub(crate) fn id_of_toplevel(&self, toplevel: &ToplevelSurface) -> Option<u32> {
1662        self.window_of_toplevel(toplevel)
1663            .and_then(|w| window_meta(&w).map(|m| m.id))
1664    }
1665
1666    /// The primary (display id 0) output.
1667    pub(crate) fn primary_output(&self) -> Option<&Output> {
1668        self.output_nodes.first().map(|n| &n.output)
1669    }
1670
1671    pub(crate) fn node_idx_for_id(&self, id: u32) -> Option<usize> {
1672        self.output_nodes.iter().position(|n| n.id == id)
1673    }
1674
1675    /// The ids of the views onto `id`'s output.
1676    pub(crate) fn view_ids_of(&self, id: u32) -> Vec<u32> {
1677        self.output_nodes
1678            .iter()
1679            .filter(|n| n.owner == Some(id))
1680            .map(|n| n.id)
1681            .collect()
1682    }
1683
1684    /// The node whose output `id` draws from: itself, or the node it views.
1685    pub(crate) fn screen_id_of(&self, id: u32) -> u32 {
1686        self.node_idx_for_id(id)
1687            .and_then(|i| self.output_nodes[i].owner)
1688            .unwrap_or(id)
1689    }
1690
1691    /// Index of the node whose LOGICAL rect contains `p`.
1692    pub(crate) fn node_idx_under(&self, p: Point<f64, Logical>) -> Option<usize> {
1693        self.output_nodes.iter().position(|n| {
1694            n.logical_geometry()
1695                .map(|g| g.to_f64().contains(p))
1696                .unwrap_or(false)
1697        })
1698    }
1699
1700    /// Map absolute PHYSICAL union-layout coordinates to a logical layout point: each
1701    /// output occupies the physical rectangle at its layout offset sized by its mode; the
1702    /// point is clamped into the nearest output when it falls outside all of them, so the
1703    /// pointer can never leave the layout.
1704    pub(crate) fn layout_physical_to_logical(&self, x: f64, y: f64) -> Point<f64, Logical> {
1705        let mut best: Option<(f64, Point<f64, Logical>)> = None;
1706        for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1707            let Some(mode) = node.output.current_mode() else {
1708                continue;
1709            };
1710            let scale = node.output.current_scale().fractional_scale();
1711            let (px, py) = (f64::from(node.pos.0), f64::from(node.pos.1));
1712            let cx = x.max(px).min(px + f64::from(mode.size.w) - 1.0);
1713            let cy = y.max(py).min(py + f64::from(mode.size.h) - 1.0);
1714            let d2 = (x - cx).powi(2) + (y - cy).powi(2);
1715            let logical = Point::from((
1716                f64::from(node.pos.0) + (cx - px) / scale,
1717                f64::from(node.pos.1) + (cy - py) / scale,
1718            ));
1719            if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1720                best = Some((d2, logical));
1721            }
1722        }
1723        best.map(|(_, p)| p).unwrap_or_else(|| (0.0, 0.0).into())
1724    }
1725
1726    /// Whether an active lock holds the pointer where it is, so a motion must carry the delta
1727    /// alone. Only the surface the pointer has entered can hold it, and only inside the region
1728    /// the lock names.
1729    pub(crate) fn pointer_locked(
1730        &self,
1731        pointer: &PointerHandle<Self>,
1732        under: &Option<(FocusTarget, Point<f64, Logical>)>,
1733        location: Point<f64, Logical>,
1734    ) -> bool {
1735        let Some((target, origin)) = under.as_ref() else {
1736            return false;
1737        };
1738        if pointer.current_focus().as_ref() != Some(target) {
1739            return false;
1740        }
1741        let Some(surface) = target.wl_surface() else {
1742            return false;
1743        };
1744        with_pointer_constraint(&surface, pointer, |constraint| match constraint {
1745            Some(constraint) if constraint.is_active() => {
1746                let local = (location - *origin).to_i32_round();
1747                constraint
1748                    .region()
1749                    .is_none_or(|region| region.contains(local))
1750                    && matches!(&*constraint, PointerConstraint::Locked(_))
1751            }
1752            _ => false,
1753        })
1754    }
1755
1756    /// What the pointer targets at a logical layout point: an overlay layer surface, an X11
1757    /// override-redirect window on top under the point, a top layer surface, then a window, then
1758    /// a bottom or background layer surface, each through its popup where one lies under the
1759    /// point, since a popup takes pointer events on its own surface. That is the order they are
1760    /// drawn in. Layer geometry is output-local, so layers are hit-tested with the point local to
1761    /// the output under it. Whatever is found is then resolved to the subsurface of its tree that
1762    /// takes input at the point ([`FocusTarget::under_point`]): a subsurface takes pointer events
1763    /// on its own surface too, and handed to the surface below it, a click on a browser's bubble
1764    /// lands on the page under the bubble.
1765    ///
1766    /// While an X11 popup menu is open, the pointer is that menu's wherever it is, outside the
1767    /// menu too: an X11 client grabs the pointer for its menu and closes the menu on a click
1768    /// elsewhere, but rootless Xwayland hears of the pointer only over its own windows, so a
1769    /// click on a Wayland surface would leave the menu open and holding the pointer.
1770    /// [`x11_menu_open`]'s window gets the events, at points Xwayland turns into the root
1771    /// coordinates the grab is delivered with.
1772    pub(crate) fn pointer_target_under(
1773        &self,
1774        p: Point<f64, Logical>,
1775    ) -> Option<(FocusTarget, Point<f64, Logical>)> {
1776        use smithay::wayland::shell::wlr_layer::Layer;
1777        let layer_hit = |layers: &[Layer]| {
1778            let node = &self.output_nodes[self.node_idx_under(p)?];
1779            let origin = Point::<i32, Logical>::from(node.pos);
1780            let local = (p - origin.to_f64()).to_i32_round();
1781            let layer_map = layer_map_for_output(&node.output);
1782            layer_map.layers().rev().find_map(|layer| {
1783                if !layers.contains(&layer.layer()) {
1784                    return None;
1785                }
1786                let bbox = layer_map.layer_geometry(layer)?;
1787                popup_under(layer.wl_surface(), bbox.loc + origin, p).or_else(|| {
1788                    // The surface's input region decides, so a wallpaper that takes input on
1789                    // its icons alone leaves the rest of the screen to what lies under it.
1790                    layer
1791                        .surface_under((local - bbox.loc).to_f64(), WindowSurfaceType::ALL)
1792                        .map(|_| {
1793                            (
1794                                FocusTarget::LayerSurface(layer.clone()),
1795                                (bbox.loc + origin).to_f64(),
1796                            )
1797                        })
1798                })
1799            })
1800        };
1801        if let Some(menu) = self.x11_menu_open() {
1802            return Some(menu);
1803        }
1804        let found = layer_hit(&[Layer::Overlay])
1805            .or_else(|| {
1806                self.space
1807                    .element_under(p)
1808                    .filter(|(window, _)| is_override_redirect(window))
1809                    .map(|(window, loc)| (FocusTarget::Window(window.clone()), loc.to_f64()))
1810            })
1811            .or_else(|| layer_hit(&[Layer::Top]))
1812            .or_else(|| {
1813                self.space.element_under(p).map(|(window, loc)| {
1814                    window
1815                        .wl_surface()
1816                        .and_then(|root| popup_under(&root, loc + window.geometry().loc, p))
1817                        .unwrap_or_else(|| (FocusTarget::Window(window.clone()), loc.to_f64()))
1818                })
1819            })
1820            .or_else(|| layer_hit(&[Layer::Bottom, Layer::Background]));
1821        found.map(|(target, origin)| target.under_point(origin, p))
1822    }
1823
1824    /// The topmost X11 popup menu that is open, with where it lies.
1825    fn x11_menu_open(&self) -> Option<(FocusTarget, Point<f64, Logical>)> {
1826        use smithay::xwayland::xwm::WmWindowType;
1827        let menu = self.space.elements().rev().find(|window| {
1828            window.x11_surface().is_some_and(|surface| {
1829                surface.is_override_redirect()
1830                    && matches!(
1831                        surface.window_type(),
1832                        Some(
1833                            WmWindowType::PopupMenu
1834                                | WmWindowType::DropdownMenu
1835                                | WmWindowType::Menu
1836                        )
1837                    )
1838            })
1839        })?;
1840        let loc = self.space.element_location(menu)?;
1841        Some((FocusTarget::Window(menu.clone()), loc.to_f64()))
1842    }
1843
1844    /// Hands pointer focus to what now lies under a still pointer once a window maps, unmaps,
1845    /// or moves, and ahead of a button or scroll that comes with no motion (a pointer-locked
1846    /// one), so input reaches what is shown there. A held grab keeps its focus until it ends.
1847    /// An enter reaches only the wl_pointers a client already holds and is not repeated to one
1848    /// bound later, so a client yet to bind one is not entered here: the window no longer shown
1849    /// is left, and the client's first input after it binds enters it.
1850    pub(crate) fn refocus_pointer(&mut self) {
1851        if self.host.is_some() || self.wm.as_ref().is_some_and(|wm| wm.pointer_taken) {
1852            return;
1853        }
1854        let Some(pointer) = self.seat.get_pointer() else {
1855            return;
1856        };
1857        if pointer.is_grabbed() {
1858            return;
1859        }
1860        let location = pointer.current_location();
1861        let under = self.pointer_target_under(location).filter(|(target, _)| {
1862            target
1863                .wl_surface()
1864                .and_then(|surface| surface.client())
1865                .is_none_or(|client| pointer.client_pointers(&client).next().is_some())
1866        });
1867        if pointer.current_focus() == under.as_ref().map(|(t, _)| t.clone()) {
1868            return;
1869        }
1870        let time = wayland_time();
1871        pointer.motion(
1872            self,
1873            under.clone(),
1874            &MotionEvent {
1875                location,
1876                serial: next_serial(),
1877                time,
1878            },
1879        );
1880        if under.is_some() {
1881            pointer.motion(
1882                self,
1883                under.clone(),
1884                &MotionEvent {
1885                    location,
1886                    serial: next_serial(),
1887                    time,
1888                },
1889            );
1890        }
1891        pointer.frame(self);
1892        self.activate_constraint_under(&pointer, &under, location);
1893    }
1894
1895    /// Activates a constraint waiting on the surface the pointer has arrived over.
1896    pub(crate) fn activate_constraint_under(
1897        &self,
1898        pointer: &PointerHandle<Self>,
1899        under: &Option<(FocusTarget, Point<f64, Logical>)>,
1900        location: Point<f64, Logical>,
1901    ) {
1902        let Some((target, origin)) = under.as_ref() else {
1903            return;
1904        };
1905        let Some(surface) = target.wl_surface() else {
1906            return;
1907        };
1908        with_pointer_constraint(&surface, pointer, |constraint| {
1909            if let Some(constraint) = constraint
1910                && !constraint.is_active()
1911                && constraint
1912                    .region()
1913                    .is_none_or(|region| region.contains((location - *origin).to_i32_round()))
1914            {
1915                constraint.activate();
1916            }
1917        });
1918    }
1919
1920    /// Clamp a logical layout point into the nearest output's logical rectangle.
1921    pub(crate) fn clamp_logical(&self, p: Point<f64, Logical>) -> Point<f64, Logical> {
1922        let mut best: Option<(f64, Point<f64, Logical>)> = None;
1923        for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1924            let Some(geo) = node.logical_geometry() else {
1925                continue;
1926            };
1927            let scale = node.output.current_scale().fractional_scale();
1928            let g = geo.to_f64();
1929            let margin = 1.0 / scale.max(0.1);
1930            let cx = p.x.max(g.loc.x).min(g.loc.x + g.size.w - margin);
1931            let cy = p.y.max(g.loc.y).min(g.loc.y + g.size.h - margin);
1932            let d2 = (p.x - cx).powi(2) + (p.y - cy).powi(2);
1933            if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1934                best = Some((d2, (cx, cy).into()));
1935            }
1936        }
1937        best.map(|(_, p)| p).unwrap_or(p)
1938    }
1939
1940    /// Logical layout point -> physical union-layout coordinates (inverse of
1941    /// `layout_physical_to_logical` for in-bounds points; primary-relative otherwise).
1942    pub(crate) fn layout_logical_to_physical(&self, p: Point<f64, Logical>) -> (f64, f64) {
1943        let (x, y, _) = self.layout_logical_to_physical_scaled(p);
1944        (x, y)
1945    }
1946
1947    /// `layout_logical_to_physical`, with the scale of the output it mapped through.
1948    pub(crate) fn layout_logical_to_physical_scaled(
1949        &self,
1950        p: Point<f64, Logical>,
1951    ) -> (f64, f64, f64) {
1952        let idx = self.node_idx_under(p).unwrap_or(0);
1953        let Some(node) = self.output_nodes.get(idx) else {
1954            return (p.x, p.y, 1.0);
1955        };
1956        let scale = node.output.current_scale().fractional_scale();
1957        (
1958            f64::from(node.pos.0) + (p.x - f64::from(node.pos.0)) * scale,
1959            f64::from(node.pos.1) + (p.y - f64::from(node.pos.1)) * scale,
1960            scale,
1961        )
1962    }
1963
1964    /// Logical size of the given display's output.
1965    pub(crate) fn logical_size_of(&self, id: u32) -> Option<(i32, i32)> {
1966        let idx = self.node_idx_for_id(id)?;
1967        let geo = self.output_nodes[idx].logical_geometry()?;
1968        Some((geo.size.w, geo.size.h))
1969    }
1970
1971    /// A nested session opens one host toplevel per screen, and a drag it carries from
1972    /// one screen to the next is its own to continue: the implicit grab that would keep
1973    /// handing the first window motion past its edge is released at the crossing, so
1974    /// the pointer enters the next screen's window and the session places its cursor
1975    /// through that window, at that screen's scale. Only a click grab still holding the
1976    /// window it started on is released, and only for another window of the same client.
1977    pub(crate) fn release_grab_across_screens(
1978        &mut self,
1979        pointer: &PointerHandle<Self>,
1980        under: &Option<(FocusTarget, Point<f64, Logical>)>,
1981        serial: Serial,
1982        time: InputTime,
1983    ) {
1984        let Some(FocusTarget::Window(next)) = under.as_ref().map(|(t, _)| t.owner()) else {
1985            return;
1986        };
1987        let Some(GrabStartData {
1988            focus: Some((start, _)),
1989            ..
1990        }) = pointer.grab_start_data()
1991        else {
1992            return;
1993        };
1994        let FocusTarget::Window(held) = start.owner().clone() else {
1995            return;
1996        };
1997        let focused = pointer.current_focus();
1998        if held == *next
1999            || focused.as_ref().map(FocusTarget::owner) != Some(&FocusTarget::Window(held.clone()))
2000        {
2001            return;
2002        }
2003        let client_of = |w: &Window| w.wl_surface().and_then(|s| s.client()).map(|c| c.id());
2004        if client_of(&held).is_some() && client_of(&held) == client_of(next) {
2005            pointer.unset_grab(self, serial, time);
2006        }
2007    }
2008
2009    /// Where the pointer is shown: where the window policy took it while it takes the pointer's
2010    /// motion, else the seat's pointer ([`crate::wayland::wm::judged_location`]). A frame drag
2011    /// whose press grabbed for the client moves the window and not the seat's pointer, so the
2012    /// cursor drawn into the stream, the position the page is told, and the display the pointer
2013    /// is on would otherwise stay where the drag began until it ends.
2014    pub(crate) fn pointer_shown_at(&self) -> Option<Point<f64, Logical>> {
2015        self.seat
2016            .get_pointer()
2017            .map(|p| crate::wayland::wm::judged_location(self, p.current_location()))
2018    }
2019
2020    /// The display id under the pointer (primary when indeterminate).
2021    pub(crate) fn pointer_display(&self) -> u32 {
2022        self.pointer_shown_at()
2023            .and_then(|pos| self.node_idx_under(pos))
2024            .map(|idx| self.output_nodes[idx].id)
2025            .unwrap_or(0)
2026    }
2027
2028    /// Whether `window` would land on top of a screen another window from the same client
2029    /// already occupies. A nested session opens one host toplevel per screen, and a second
2030    /// one covering the first would replace what the display shows rather than add to it.
2031    pub(crate) fn would_cover_screen(&self, window: &Window, id: u32) -> bool {
2032        let Some(client) = window.wl_surface().and_then(|s| s.client()) else {
2033            return false;
2034        };
2035        self.space
2036            .elements()
2037            .chain(self.pending_windows.iter())
2038            .filter(|w| !std::ptr::eq(*w, window) && w.wl_surface() != window.wl_surface())
2039            .any(|w| {
2040                window_output_id(w) == id
2041                    && !window_meta(w)
2042                        .map(|m| m.parked.load(Ordering::Relaxed))
2043                        .unwrap_or(false)
2044                    && w.wl_surface()
2045                        .and_then(|s| s.client())
2046                        .is_some_and(|c| c.id() == client.id())
2047            })
2048    }
2049
2050    /// Map `window` clear of every output while keeping it tagged for `id`, so it holds its
2051    /// size and its place in the window list without being composited anywhere. A nested
2052    /// session's spare screens wait here until `create_output` gives them one.
2053    pub(crate) fn park_window(&mut self, window: &Window, id: u32) {
2054        let old_id = window_output_id(window);
2055        if let Some(meta) = window_meta(window) {
2056            meta.output.store(id, Ordering::Relaxed);
2057            meta.parked.store(true, Ordering::Relaxed);
2058        }
2059        if let (Some(surface), Some(idx)) = (window.wl_surface(), self.node_idx_for_id(old_id)) {
2060            self.output_nodes[idx].output.leave(&surface);
2061        }
2062        self.space.map_element(window.clone(), PARKED_POS, false);
2063        self.refocus_pointer();
2064        if let Some(toplevel) = window.toplevel() {
2065            let toplevel = toplevel.clone();
2066            self.send_forced_fullscreen_configure(&toplevel);
2067        }
2068    }
2069
2070    /// Place `window` on output `id`: retag its meta, remap it at the output's layout
2071    /// origin, move output enter/leave, push the output's fractional scale, and send the
2072    /// forced-fullscreen configure at that output's logical size.
2073    pub(crate) fn place_window_on_output(&mut self, window: &Window, id: u32) -> bool {
2074        let Some(idx) = self.node_idx_for_id(id) else {
2075            return false;
2076        };
2077        let old_id = window_output_id(window);
2078        let (new_output, pos) = {
2079            let node = &self.output_nodes[idx];
2080            (node.output.clone(), node.pos)
2081        };
2082        let old_output = self
2083            .node_idx_for_id(old_id)
2084            .map(|i| self.output_nodes[i].output.clone());
2085        if let Some(meta) = window_meta(window) {
2086            meta.output.store(id, Ordering::Relaxed);
2087            meta.parked.store(false, Ordering::Relaxed);
2088        }
2089        self.space.map_element(window.clone(), pos, true);
2090        self.refocus_pointer();
2091        if let Some(surface) = window.wl_surface() {
2092            if let Some(old) = old_output
2093                && old_id != id
2094            {
2095                old.leave(&surface);
2096            }
2097            new_output.enter(&surface);
2098            let scale = new_output.current_scale().fractional_scale();
2099            with_states(&surface, |states| {
2100                smithay::wayland::compositor::send_surface_state(
2101                    &surface,
2102                    states,
2103                    scale.ceil() as i32,
2104                    smithay::utils::Transform::Normal,
2105                );
2106                smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
2107                    fs.set_preferred_scale(scale);
2108                });
2109            });
2110        }
2111        if let Some(toplevel) = window.toplevel() {
2112            let toplevel = toplevel.clone();
2113            self.send_forced_fullscreen_configure(&toplevel);
2114        }
2115        true
2116    }
2117
2118    /// Drain the clipboard delivery staged by `new_selection` and hand its `(mime, bytes)`
2119    /// entries, the flavors of one copy, to the clipboard sink off-thread; a cleared selection
2120    /// is handed over as no entries.
2121    ///
2122    /// Runs from the loop *after* the dispatch that stored the new client source, so the requests
2123    /// target the current selection rather than the previous one. It clones the callback, opens a
2124    /// pipe per mime, and asks the owning client source to write each into its pipe's writer. A
2125    /// spawned reader thread then reads the responses in turn. The overall bound is by SIZE (64
2126    /// MiB per flavor, then delivered truncated) so a hostile client cannot balloon memory; time
2127    /// only bounds INACTIVITY — a producer that keeps bytes flowing may take as long as it needs
2128    /// (a large transfer from a slow source still delivers), while one that goes silent for 10 s
2129    /// without closing its fd is dropped so each clipboard change cannot leak a pinned thread +
2130    /// pipe. Deliveries leave in the order they were staged: one that a later copy or clear
2131    /// overtook is dropped (`clipboard_generation`). The `SHUTDOWN` checks keep this off a
2132    /// consumer that is gone.
2133    pub(crate) fn process_pending_clipboard_read(&mut self) {
2134        let Some(mimes) = self.pending_clipboard_read.take() else {
2135            return;
2136        };
2137        if crate::SHUTDOWN.load(Ordering::Relaxed) {
2138            return;
2139        }
2140        let Some(cb) = self.clipboard_callback.clone() else {
2141            return;
2142        };
2143        let cleared = mimes.is_empty();
2144        let mut pipes = Vec::new();
2145        for mime in mimes {
2146            let Ok((reader, writer)) = std::io::pipe() else {
2147                continue;
2148            };
2149            let writer: std::os::fd::OwnedFd = writer.into();
2150            let served = match request_data_device_client_selection::<AppState>(
2151                &self.seat,
2152                mime.clone(),
2153                writer,
2154            ) {
2155                Ok(()) => true,
2156                Err(_) => match (std::io::pipe(), self.xwm.wm.as_mut()) {
2157                    (Ok((again, writer)), Some(xwm)) => {
2158                        drop(reader);
2159                        if xwm
2160                            .send_selection(SelectionTarget::Clipboard, mime.clone(), writer.into())
2161                            .is_ok()
2162                        {
2163                            pipes.push((mime, again));
2164                        }
2165                        continue;
2166                    }
2167                    _ => false,
2168                },
2169            };
2170            if served {
2171                pipes.push((mime, reader));
2172            }
2173        }
2174        if pipes.is_empty() && !cleared {
2175            return;
2176        }
2177        let generations = self.clipboard_generation.clone();
2178        let generation = generations.fetch_add(1, Ordering::Relaxed) + 1;
2179        std::thread::spawn(move || {
2180            let entries: Vec<(String, Vec<u8>)> = pipes
2181                .into_iter()
2182                .filter_map(|(mime, reader)| read_selection(reader).map(|bytes| (mime, bytes)))
2183                .collect();
2184            if (entries.is_empty() && !cleared) || crate::SHUTDOWN.load(Ordering::Relaxed) {
2185                return;
2186            }
2187            if generations.load(Ordering::Relaxed) != generation {
2188                return;
2189            }
2190            cb.selection(entries);
2191        });
2192    }
2193
2194    /// Resolve a `CursorImageStatus` into a job for the `wl-cursor` worker, which does the
2195    /// PNG encode, caching, and the GIL-bound Python call off the calloop thread. Also re-invoked
2196    /// from the calloop command handlers to replay the retained cursor when a callback
2197    /// (re)registers or a capture restarts.
2198    ///
2199    /// Only the renderer/surface-affine work happens here:
2200    ///
2201    /// 1. **Named** / **Hidden**: forwarded as-is (the worker owns its own theme handle).
2202    /// 2. **Surface** (a client-supplied cursor sprite): ignore any surface without the
2203    ///    `cursor_image` role, read the hotspot, then read the backing buffer by one of two paths:
2204    ///    - **SHM**: hash only the sprite's sub-region — width/height/stride/offset/format plus the
2205    ///      pixel span — because many sprites share one pool and differ only by `offset`, so hashing
2206    ///      the whole pool would collide; ship the raw pool bytes plus descriptor to the worker.
2207    ///    - **dmabuf** (`NotManaged`): bind it to the GLES renderer, copy the framebuffer to
2208    ///      `Abgr8888`, map it back (calloop-affine), and ship the raw RGBA readback.
2209    ///
2210    ///    A sprite whose buffer could not be read is dropped by the worker, preserving the
2211    ///    consumer's last cursor instead of blanking it (only "hide" carries empty data).
2212    pub(crate) fn send_cursor_image(&mut self, image: &CursorImageStatus) {
2213        if !self.cursor_callback_set {
2214            return;
2215        }
2216        let job = match image {
2217            CursorImageStatus::Named(icon) => {
2218                self.cursor_buffer = None;
2219                CursorJob::Named {
2220                    name: cursor_icon_to_str(icon),
2221                }
2222            }
2223            CursorImageStatus::Hidden => {
2224                self.cursor_buffer = None;
2225                CursorJob::Hide
2226            }
2227            CursorImageStatus::Surface(surface) => {
2228                let mut hot_x = 0;
2229                let mut hot_y = 0;
2230                let mut is_cursor_role = false;
2231
2232                // The hotspot is set in logical surface coordinates; the consumer gets the
2233                // sprite in buffer pixels, so the hotspot ships in the same units.
2234                with_states(surface, |states| {
2235                    if states.role == Some("cursor_image") {
2236                        is_cursor_role = true;
2237                    }
2238                    let buffer_scale = states
2239                        .cached_state
2240                        .get::<SurfaceAttributes>()
2241                        .current()
2242                        .buffer_scale
2243                        .max(1);
2244                    if let Some(attributes) = states.data_map.get::<Mutex<CursorImageAttributes>>()
2245                        && let Ok(guard) = attributes.lock()
2246                    {
2247                        hot_x = guard.hotspot.x * buffer_scale;
2248                        hot_y = guard.hotspot.y * buffer_scale;
2249                    }
2250                });
2251
2252                if !is_cursor_role {
2253                    return;
2254                }
2255
2256                let buffer_found = with_states(surface, |states| {
2257                    let mut attrs = states.cached_state.get::<SurfaceAttributes>();
2258
2259                    if let Some(BufferAssignment::NewBuffer(b)) = &attrs.current().buffer {
2260                        return Some(b.clone());
2261                    }
2262
2263                    if let Some(mutex) = states.data_map.get::<Mutex<RendererSurfaceState>>()
2264                        && let Ok(renderer_state) = mutex.try_lock()
2265                        && let Some(b) = renderer_state.buffer()
2266                    {
2267                        let wl_buffer: &wayland_server::protocol::wl_buffer::WlBuffer = b;
2268                        return Some(wl_buffer.clone());
2269                    }
2270                    None
2271                });
2272
2273                let Some(buffer) = buffer_found else { return };
2274
2275                let shm_result = with_buffer_contents(&buffer, |ptr, len, spec| {
2276                    let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
2277                    let mut hasher = DefaultHasher::new();
2278                    spec.width.hash(&mut hasher);
2279                    spec.height.hash(&mut hasher);
2280                    spec.stride.hash(&mut hasher);
2281                    spec.offset.hash(&mut hasher);
2282                    spec.format.hash(&mut hasher);
2283                    let start = (spec.offset.max(0) as usize).min(len);
2284                    let span =
2285                        (spec.stride.max(0) as usize).saturating_mul(spec.height.max(0) as usize);
2286                    let end = start.saturating_add(span).min(len);
2287                    slice[start..end].hash(&mut hasher);
2288                    let hash = hasher.finish();
2289                    (
2290                        hash,
2291                        spec.width,
2292                        spec.height,
2293                        spec.stride,
2294                        spec.format,
2295                        spec.offset,
2296                        slice.to_vec(),
2297                    )
2298                });
2299
2300                let job = match shm_result {
2301                    Ok((hash, width, height, stride, format, buf_offset, raw_bytes)) => {
2302                        Some(CursorJob::Shm {
2303                            hash,
2304                            width,
2305                            height,
2306                            stride,
2307                            offset: buf_offset,
2308                            opaque: format == wl_shm::Format::Xrgb8888,
2309                            bytes: raw_bytes,
2310                            hot_x,
2311                            hot_y,
2312                        })
2313                    }
2314                    Err(BufferAccessError::NotManaged) => {
2315                        let mut gles_job = None;
2316                        let dmabuf_opt = get_dmabuf(&buffer).ok().cloned();
2317                        if let Some(mut dmabuf) = dmabuf_opt
2318                            && let Some(renderer) = self.gles_renderer.as_mut()
2319                        {
2320                            let width = dmabuf.width() as i32;
2321                            let height = dmabuf.height() as i32;
2322
2323                            match renderer.bind(&mut dmabuf) {
2324                                Ok(frame) => {
2325                                    let rect =
2326                                        Rectangle::new((0, 0).into(), (width, height).into());
2327                                    match renderer.copy_framebuffer(&frame, rect, Fourcc::Abgr8888)
2328                                    {
2329                                        Ok(mapping) => match renderer.map_texture(&mapping) {
2330                                            Ok(data) => {
2331                                                let mut hasher = DefaultHasher::new();
2332                                                data.hash(&mut hasher);
2333                                                gles_job = Some(CursorJob::Gles {
2334                                                    hash: hasher.finish(),
2335                                                    width,
2336                                                    height,
2337                                                    bytes: data.to_vec(),
2338                                                    hot_x,
2339                                                    hot_y,
2340                                                });
2341                                            }
2342                                            Err(e) => eprintln!("Failed to map texture: {:?}", e),
2343                                        },
2344                                        Err(e) => eprintln!("Failed to copy framebuffer: {:?}", e),
2345                                    }
2346                                }
2347                                Err(e) => eprintln!("Failed to bind dmabuf to renderer: {:?}", e),
2348                            }
2349                        }
2350                        gles_job
2351                    }
2352                    Err(_) => None,
2353                };
2354
2355                self.cursor_buffer = Some(buffer);
2356                let Some(job) = job else { return };
2357                job
2358            }
2359        };
2360        let _ = self.cursor_tx.send(job);
2361    }
2362
2363    /// Deliver a surface-backed cursor whose dispatch ended without a commit of its surface
2364    /// (a client re-showing a sprite it attached earlier).
2365    pub(crate) fn flush_pending_cursor(&mut self) {
2366        if !self.cursor_surface_pending {
2367            return;
2368        }
2369        self.cursor_surface_pending = false;
2370        if let Some(icon) = self.current_cursor_icon.clone() {
2371            self.send_cursor_image(&icon);
2372        }
2373    }
2374
2375    /// Re-apply the policy's keymap (base + overlays) to the seat keyboard, broadcasting to
2376    /// clients only when the content actually changed (smithay dedupes by content hash).
2377    pub(crate) fn apply_keymap_policy(&mut self) {
2378        let text = self.keymap_policy.keymap_text();
2379        self.apply_keymap_text(text);
2380    }
2381
2382    /// Deliver `text` as the seat keymap (and the host virtual keyboard's, in
2383    /// host-capture mode). Empty text is ignored.
2384    pub(crate) fn apply_keymap_text(&mut self, text: String) {
2385        if text.is_empty() {
2386            return;
2387        }
2388        // Host-capture mode: the same managed keymap rides on the virtual
2389        // keyboard, so the host compositor translates injected keycodes with
2390        // selkies' keymap (overlay binds included) instead of its own.
2391        if let Some(host) = &self.host {
2392            host.set_keymap(&text);
2393        }
2394        if let Some(keyboard) = self.seat.get_keyboard()
2395            && let Err(e) = keyboard.set_keymap_from_string(self, text)
2396        {
2397            eprintln!("[Wayland] keymap swap failed: {e:?}");
2398        }
2399    }
2400
2401    /// Resolve `keysyms` to `(keycode, level)` pairs, overlay-binding whatever the base
2402    /// cannot produce — at most ONE keymap swap for the whole batch, and never rebinding a
2403    /// keycode that is currently held down. Serves computer-use; the interactive input path
2404    /// resolves keysyms in selkies and injects plain keycodes.
2405    pub(crate) fn bind_keysyms(&mut self, keysyms: &[u32]) -> Vec<(u32, u32)> {
2406        let pressed: std::collections::HashSet<u32> = self
2407            .seat
2408            .get_keyboard()
2409            .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
2410            .unwrap_or_default();
2411        let (out, changed) = self.keymap_policy.bind_many(keysyms, &pressed);
2412        if changed {
2413            self.apply_keymap_policy();
2414        }
2415        out
2416    }
2417
2418    /// `bind_keysyms` restricted to level-0 resolutions (see
2419    /// [`KeymapPolicy::bind_many_plain`]); used by the virtual-keyboard translation path, which
2420    /// cannot synthesize modifiers.
2421    pub(crate) fn bind_keysyms_plain(&mut self, keysyms: &[u32]) -> Vec<u32> {
2422        let pressed: std::collections::HashSet<u32> = self
2423            .seat
2424            .get_keyboard()
2425            .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
2426            .unwrap_or_default();
2427        let (out, changed) = self.keymap_policy.bind_many_plain(keysyms, &pressed);
2428        if changed {
2429            self.apply_keymap_policy();
2430        }
2431        out
2432    }
2433
2434    /// Answer any client fullscreen/maximize (un)set request with the compositor's forced
2435    /// policy: every toplevel is Fullscreen+Activated at the CURRENT logical size of the
2436    /// output the window is placed on. The configure is always sent, so an app toggling
2437    /// fullscreen gets an explicit, current-geometry answer instead of silence or stale
2438    /// pending state.
2439    pub(crate) fn send_forced_fullscreen_configure(&mut self, toplevel: &ToplevelSurface) {
2440        let output_id = self
2441            .space
2442            .elements()
2443            .chain(self.pending_windows.iter())
2444            .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
2445            .map(window_output_id)
2446            .unwrap_or(0);
2447        let parked = self
2448            .space
2449            .elements()
2450            .chain(self.pending_windows.iter())
2451            .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
2452            .and_then(window_meta)
2453            .map(|meta| meta.parked.load(Ordering::Relaxed))
2454            .unwrap_or(false);
2455        let (logical_width, logical_height) = if parked {
2456            PARKED_LOGICAL_SIZE
2457        } else if let Some(size) = self.logical_size_of(output_id) {
2458            size
2459        } else {
2460            let scale = self.settings.scale.max(0.1);
2461            (
2462                (f64::from(self.settings.width) / scale).round() as i32,
2463                (f64::from(self.settings.height) / scale).round() as i32,
2464            )
2465        };
2466        toplevel.with_pending_state(|state| {
2467            state.states.set(XdgState::Fullscreen);
2468            state.states.set(XdgState::Activated);
2469            state.size = Some((logical_width, logical_height).into());
2470        });
2471        toplevel.send_configure();
2472    }
2473}
2474
2475/// Clipboard mime types the bridge hands to Python. A picture is read as the first of the
2476/// image types the client's source offers; text as its markup, when it offers any, and the
2477/// first of the plain-text names beneath it, so a paste gets the text the source itself wrote.
2478const CLIPBOARD_IMAGE_MIMES: &[&str] = &[
2479    "image/png",
2480    "image/jpeg",
2481    "image/webp",
2482    "image/bmp",
2483    "image/svg+xml",
2484    "image/svg",
2485];
2486const CLIPBOARD_TEXT_MIMES: &[&str] = &[
2487    "text/plain;charset=utf-8",
2488    "UTF8_STRING",
2489    "text/plain",
2490    "STRING",
2491    "TEXT",
2492];
2493/// The flavor a password manager offers beside a secret it copies (KeePassXC and KDE set it to
2494/// `secret`), in KDE's spelling and the prefixed ones a toolkit that validates mime types can
2495/// see. It is read with the copy it marks, so the consumer can keep the secret out of every
2496/// history it would otherwise enter.
2497const CLIPBOARD_SECRET_HINTS: &[&str] = &[
2498    "x-kde-passwordManagerHint",
2499    "text/x-kde-passwordManagerHint",
2500    "application/x-kde-passwordManagerHint",
2501];
2502
2503/// The flavors of a client's copy the bridge reads, from the mimes its source offers: the
2504/// picture, else the markup and the plain text, and a password manager's hint beside either.
2505/// None for a copy offering nothing the bridge reads, which then reads like a cleared one.
2506pub(crate) fn clipboard_flavors(offered: &[String]) -> Vec<String> {
2507    let has = |want: &str| offered.iter().any(|m| m == want);
2508    match CLIPBOARD_IMAGE_MIMES.iter().find(|m| has(m)) {
2509        Some(image) => vec![*image],
2510        None => ["text/html"]
2511            .iter()
2512            .filter(|m| has(m))
2513            .chain(CLIPBOARD_TEXT_MIMES.iter().find(|m| has(m)))
2514            .copied()
2515            .collect(),
2516    }
2517    .into_iter()
2518    .chain(CLIPBOARD_SECRET_HINTS.iter().copied().filter(|m| has(m)))
2519    .map(str::to_string)
2520    .collect()
2521}
2522
2523/// One staged clipboard flavor read to its end: the source's bytes, or nothing for a source
2524/// that wrote none or went silent.
2525fn read_selection(reader: std::io::PipeReader) -> Option<Vec<u8>> {
2526    use std::io::Read;
2527    use std::os::fd::AsRawFd;
2528    const CAP: usize = 64 * 1024 * 1024;
2529    const IDLE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(10);
2530    let mut last_data = Instant::now();
2531    let mut buf = Vec::new();
2532    let mut chunk = [0u8; 65536];
2533    loop {
2534        let remaining = IDLE_DEADLINE.checked_sub(last_data.elapsed())?;
2535        let mut pfd = libc::pollfd {
2536            fd: reader.as_raw_fd(),
2537            events: libc::POLLIN,
2538            revents: 0,
2539        };
2540        let timeout_ms = remaining.as_millis().min(i32::MAX as u128).max(1) as i32;
2541        let ready = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
2542        if ready < 0 {
2543            if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
2544                continue;
2545            }
2546            return None;
2547        }
2548        if ready == 0 {
2549            continue;
2550        }
2551        match (&reader).read(&mut chunk) {
2552            Ok(0) => break,
2553            Ok(n) => {
2554                last_data = Instant::now();
2555                let take_n = n.min(CAP - buf.len());
2556                buf.extend_from_slice(&chunk[..take_n]);
2557                if buf.len() == CAP {
2558                    break;
2559                }
2560            }
2561            Err(e)
2562                if e.kind() == std::io::ErrorKind::Interrupted
2563                    || e.kind() == std::io::ErrorKind::WouldBlock =>
2564            {
2565                continue;
2566            }
2567            Err(_) => return None,
2568        }
2569    }
2570    (!buf.is_empty()).then_some(buf)
2571}
2572
2573/// Selection (clipboard) bridge between Wayland clients and Python. `SelectionUserData` is
2574/// the Python-owned payload, one `(mime, bytes)` entry per offered flavor, served to
2575/// pasting clients when Python holds the selection.
2576impl SelectionHandler for AppState {
2577    type SelectionUserData = std::sync::Arc<Vec<(String, Vec<u8>)>>;
2578
2579    /// A client took the clipboard, or cleared it: pick the flavors to read and stage them for
2580    /// the loop.
2581    ///
2582    /// Only client-owned clipboard (not primary) selections are relayed to Python. The source's
2583    /// flavors (`clipboard_flavors`) are recorded in `pending_clipboard_read`, and a cleared
2584    /// selection as no flavors. The read itself is deferred: the new source is stored only after
2585    /// this handler returns, so `process_pending_clipboard_read` runs post-dispatch and reads the
2586    /// new selection rather than the previous one.
2587    fn new_selection(
2588        &mut self,
2589        ty: SelectionTarget,
2590        source: Option<SelectionSource>,
2591        seat: Seat<Self>,
2592    ) {
2593        if ty != SelectionTarget::Clipboard {
2594            return;
2595        }
2596        let chosen = source.map_or_else(Vec::new, |source| clipboard_flavors(&source.mime_types()));
2597        // Recorded even with no callback armed, so SetClipboardCallback can re-stage a
2598        // read of a copy made while nobody was listening.
2599        self.current_selection_mimes.clone_from(&chosen);
2600        if crate::SHUTDOWN.load(Ordering::Relaxed) {
2601            return;
2602        }
2603        if self.clipboard_callback.is_none() {
2604            return;
2605        }
2606        self.pending_clipboard_read = Some(chosen);
2607        let _ = seat;
2608    }
2609
2610    /// A client pastes the Python-owned selection: stream the stored bytes into the client's
2611    /// fd on a spawned thread, since the receiving pipe may backpressure. Bounded by an
2612    /// idle deadline: a client that never reads its paste fd must not pin the thread.
2613    fn send_selection(
2614        &mut self,
2615        ty: SelectionTarget,
2616        mime_type: String,
2617        fd: std::os::fd::OwnedFd,
2618        _seat: Seat<Self>,
2619        user_data: &Self::SelectionUserData,
2620    ) {
2621        if ty != SelectionTarget::Clipboard && ty != SelectionTarget::Primary {
2622            return;
2623        }
2624        // The text aliases are all advertised for the first text entry, so a request for
2625        // any of them takes it; every other advertised mime is an entry of its own.
2626        let entries = user_data.clone();
2627        if entries.is_empty()
2628            && let Some(xwm) = self.xwm.wm.as_mut()
2629        {
2630            if let Err(e) = xwm.send_selection(ty, mime_type, fd) {
2631                eprintln!("[Wayland] X11 selection not read: {e:?}");
2632            }
2633            return;
2634        }
2635        let Some(index) = entries
2636            .iter()
2637            .position(|(mime, _)| *mime == mime_type)
2638            .or_else(|| {
2639                entries
2640                    .iter()
2641                    .position(|(mime, _)| mime.starts_with("text/plain"))
2642                    .filter(|_| !mime_type.contains('/') || mime_type.starts_with("text/plain"))
2643            })
2644        else {
2645            return;
2646        };
2647        std::thread::spawn(move || {
2648            let _ = crate::wayland::wlclient::write_fd_all(
2649                &fd,
2650                &entries[index].1,
2651                crate::wayland::wlclient::IO_TIMEOUT,
2652            );
2653        });
2654    }
2655}
2656
2657/// `wl_data_device` protocol: exposes the managed state for drag-and-drop and clipboard
2658/// data transfers.
2659impl DataDeviceHandler for AppState {
2660    fn data_device_state(&mut self) -> &mut DataDeviceState {
2661        &mut self.data_device_state
2662    }
2663}
2664/// wlr-data-control protocol: exposes the managed state so privileged clients can read and
2665/// set selections.
2666impl DataControlHandler for AppState {
2667    fn data_control_state(&mut self) -> &mut DataControlState {
2668        &mut self.data_control_state
2669    }
2670}
2671/// No graphics tablets exist here (input is injected); the default no-op tool-image
2672/// callback is all cursor-shape's tablet half needs, and a tool would focus plain surfaces.
2673impl smithay::input::tablet::TabletSeatHandler for AppState {
2674    type ToolFocus = WlSurface;
2675}
2676
2677/// ext-data-control: the standardized successor of wlr-data-control. Both globals
2678/// stay advertised, sharing the same selection state, so older clipboard managers
2679/// keep working while current ones use the ext protocol.
2680impl ExtDataControlHandler for AppState {
2681    fn data_control_state(&mut self) -> &mut ExtDataControlState {
2682        &mut self.ext_data_control_state
2683    }
2684}
2685
2686/// `wl_data_device.start_drag`: a client's drag becomes Smithay's drag-and-drop grab on the
2687/// pointer or the touch point that started it, which offers the source to the surface under it,
2688/// Wayland or X11 through the X11 window manager's bridge, and drops it on release; its icon is
2689/// drawn at the pointer until then (`AppState::dnd_icon`). The trait's own default cancels every
2690/// drag, which left a drag between two windows, or out of one, ending as it began.
2691impl WaylandDndGrabHandler for AppState {
2692    fn dnd_requested<S: Source>(
2693        &mut self,
2694        source: S,
2695        icon: Option<WlSurface>,
2696        seat: Seat<Self>,
2697        serial: Serial,
2698        type_: GrabType,
2699    ) {
2700        self.dnd_icon = icon.map(|surface| (surface, (0, 0).into()));
2701        let display = self.dh.clone();
2702        match type_ {
2703            GrabType::Pointer => {
2704                let Some(pointer) = seat.get_pointer() else {
2705                    source.cancel();
2706                    return;
2707                };
2708                let Some(start) = pointer.grab_start_data() else {
2709                    source.cancel();
2710                    return;
2711                };
2712                let grab = DnDGrab::new_pointer(&display, start, source, seat);
2713                pointer.set_grab(self, grab, serial, Focus::Keep);
2714            }
2715            GrabType::Touch => {
2716                let Some(touch) = seat.get_touch() else {
2717                    source.cancel();
2718                    return;
2719                };
2720                let Some(start) = touch.grab_start_data() else {
2721                    source.cancel();
2722                    return;
2723                };
2724                let grab = DnDGrab::new_touch(&display, start, source, seat);
2725                touch.set_grab(self, grab, serial);
2726            }
2727        }
2728    }
2729}
2730/// Buffer lifecycle hook: buffer destruction needs no bookkeeping here.
2731impl BufferHandler for AppState {
2732    fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
2733}
2734/// `wl_shm` protocol: exposes the shared-memory buffer state.
2735impl ShmHandler for AppState {
2736    fn shm_state(&self) -> &ShmState {
2737        &self.shm_state
2738    }
2739}
2740/// Output protocol: a `wl_output` a client binds after the globals that name outputs enters
2741/// the workspace group and the window handles on it, since those enters reach only the
2742/// objects the client held when they were sent.
2743impl OutputHandler for AppState {
2744    fn output_bound(
2745        &mut self,
2746        output: Output,
2747        wl_output: smithay::reexports::wayland_server::protocol::wl_output::WlOutput,
2748    ) {
2749        crate::wayland::workspace::output_bound(self, &output, &wl_output);
2750        crate::wayland::foreign_toplevel::output_bound(self, &output, &wl_output);
2751    }
2752}
2753
2754/// Linux-dmabuf protocol: imports client dmabufs into the GLES renderer.
2755///
2756/// `dmabuf_imported` attempts the import into the GLES renderer and signals the client through the
2757/// notifier — success only when a GLES renderer exists and the import succeeds, otherwise failure
2758/// (the software / pixman path advertises no dmabuf global, so it always fails here).
2759impl DmabufHandler for AppState {
2760    fn dmabuf_state(&mut self) -> &mut DmabufState {
2761        &mut self.dmabuf_state
2762    }
2763
2764    fn dmabuf_imported(
2765        &mut self,
2766        _global: &DmabufGlobal,
2767        dmabuf: Dmabuf,
2768        notifier: ImportNotifier,
2769    ) {
2770        if let Some(renderer) = self.gles_renderer.as_mut() {
2771            if renderer.import_dmabuf(&dmabuf, None).is_ok() {
2772                let _ = notifier.successful::<AppState>();
2773            } else {
2774                notifier.failed();
2775            }
2776        } else {
2777            notifier.failed();
2778        }
2779    }
2780}
2781
2782impl ImageCaptureSourceHandler for AppState {
2783    fn source_destroyed(&mut self, _source: ImageCaptureSource) {}
2784}
2785
2786impl OutputCaptureSourceHandler for AppState {
2787    fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState {
2788        &mut self.output_capture_source_state
2789    }
2790
2791    fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) {
2792        source.user_data().insert_if_missing(|| output.downgrade());
2793    }
2794}
2795
2796/// ext-image-copy-capture: external clients capturing this compositor's outputs.
2797///
2798/// A dmabuf-capable client gets the render formats of the GLES renderer and its frames
2799/// are filled with one GPU blit from the output's composited buffer; an shm client gets
2800/// a readback. Frames complete from the render loop, never from a stale backbuffer.
2801impl ImageCopyCaptureHandler for AppState {
2802    fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
2803        &mut self.image_copy_capture_state
2804    }
2805
2806    fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {
2807        let output = source.user_data().get::<WeakOutput>()?.upgrade()?;
2808        output_capture_constraints(&output, self.gles_renderer.as_ref(), &self.render_node_path)
2809    }
2810
2811    fn new_session(&mut self, session: CopySession) {
2812        let Some(output) = session.source().user_data().get::<WeakOutput>().cloned() else {
2813            session.stop();
2814            return;
2815        };
2816        match self.capture_constraints(&session.source()) {
2817            Some(constraints) => {
2818                session.update_constraints(constraints);
2819                self.copy_sessions.push(OutputCopySession {
2820                    session,
2821                    output,
2822                    pending: None,
2823                    delivered_once: false,
2824                });
2825            }
2826            None => session.stop(),
2827        }
2828    }
2829
2830    fn frame(&mut self, session: &CopySessionRef, frame: CopyFrame) {
2831        match self
2832            .copy_sessions
2833            .iter_mut()
2834            .find(|cs| cs.session == *session)
2835        {
2836            Some(cs) => cs.pending = Some(frame),
2837            None => frame.fail(CaptureFailureReason::Unknown),
2838        }
2839    }
2840
2841    fn frame_aborted(&mut self, frame: CopyFrameRef) {
2842        for cs in self.copy_sessions.iter_mut() {
2843            if cs.pending.as_ref().is_some_and(|f| *f == frame) {
2844                cs.pending = None;
2845            }
2846        }
2847    }
2848
2849    fn session_destroyed(&mut self, session: CopySessionRef) {
2850        self.copy_sessions.retain(|cs| cs.session != session);
2851    }
2852}
2853
2854impl AppState {
2855    pub fn copy_frame_pending_for(&self, output: &Output) -> bool {
2856        self.copy_sessions
2857            .iter()
2858            .any(|cs| cs.pending.is_some() && cs.output.upgrade().as_ref() == Some(output))
2859    }
2860}
2861
2862/// Buffer constraints for capturing `output`: its current mode's size, the shm formats
2863/// both render paths can serve, and — with a GLES renderer — the renderer's dmabuf
2864/// render formats so a client buffer can be blitted to on the GPU.
2865pub fn output_capture_constraints(
2866    output: &Output,
2867    renderer: Option<&GlesRenderer>,
2868    render_node_path: &str,
2869) -> Option<BufferConstraints> {
2870    let mode = output.current_mode()?;
2871    let dma = renderer.and_then(|renderer| {
2872        let node = DrmNode::from_path(render_node_path).ok()?;
2873        let mut formats: Vec<(Fourcc, Vec<Modifier>)> = Vec::new();
2874        for f in Bind::<Dmabuf>::supported_formats(renderer)? {
2875            match formats.iter_mut().find(|(code, _)| *code == f.code) {
2876                Some((_, mods)) => mods.push(f.modifier),
2877                None => formats.push((f.code, vec![f.modifier])),
2878            }
2879        }
2880        (!formats.is_empty()).then_some(DmabufConstraints { node, formats })
2881    });
2882    Some(BufferConstraints {
2883        size: (mode.size.w, mode.size.h).into(),
2884        shm: vec![wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888],
2885        dma,
2886    })
2887}
2888
2889/// Fractional-scale protocol: tells a newly-bound surface the output's current fractional
2890/// scale so it renders at the right pixel density.
2891impl FractionalScaleHandler for AppState {
2892    fn new_fractional_scale(
2893        &mut self,
2894        surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
2895    ) {
2896        if let Some(output) = self.primary_output() {
2897            let scale = output.current_scale().fractional_scale();
2898            with_states(&surface, |states| {
2899                smithay::wayland::compositor::send_surface_state(
2900                    &surface,
2901                    states,
2902                    scale.ceil() as i32,
2903                    smithay::utils::Transform::Normal,
2904                );
2905                smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
2906                    fs.set_preferred_scale(scale);
2907                });
2908            });
2909        }
2910    }
2911}
2912
2913/// Input-event target for Smithay's seat handlers. Smithay requires a concrete type as the
2914/// "target" of a keyboard / pointer / touch event; `FocusTarget` bridges that to the concrete
2915/// Wayland surface behind a window, popup, or layer surface.
2916#[derive(Debug, Clone, PartialEq)]
2917#[allow(clippy::large_enum_variant)]
2918pub enum FocusTarget {
2919    Window(Window),
2920    Popup(PopupKind),
2921    LayerSurface(DesktopLayerSurface),
2922    /// A subsurface under the pointer, which takes the pointer's events on its own surface
2923    /// (a browser draws its permission and menu bubbles so), with the window, popup, or layer
2924    /// surface it belongs to, which everything but those events goes by: the keyboard, the
2925    /// window policy, a grab's window.
2926    Subsurface(WlSurface, Box<FocusTarget>),
2927}
2928
2929impl FocusTarget {
2930    /// The window, popup, or layer surface this target is, or a subsurface belongs to.
2931    pub(crate) fn owner(&self) -> &FocusTarget {
2932        match self {
2933            FocusTarget::Subsurface(_, owner) => owner.owner(),
2934            target => target,
2935        }
2936    }
2937
2938    /// This target, or the subsurface of its surface tree that takes input at the logical
2939    /// point `p` where one does, with the origin pointer events there are local to; `origin`
2940    /// is where this target's own surface lies. A point the tree takes no input at leaves the
2941    /// target as it is.
2942    fn under_point(
2943        self,
2944        origin: Point<f64, Logical>,
2945        p: Point<f64, Logical>,
2946    ) -> (FocusTarget, Point<f64, Logical>) {
2947        let Some(root) = self.wl_surface().map(Cow::into_owned) else {
2948            return (self, origin);
2949        };
2950        match under_from_surface_tree(&root, p, origin.to_i32_round(), WindowSurfaceType::ALL) {
2951            Some((surface, at)) if surface != root => (
2952                FocusTarget::Subsurface(surface, Box::new(self)),
2953                at.to_f64(),
2954            ),
2955            _ => (self, origin),
2956        }
2957    }
2958}
2959
2960/// The popup of `root`, whose popups are placed from `origin`, that lies under the logical
2961/// point `p`, as a focus target with the popup's own origin.
2962fn popup_under(
2963    root: &WlSurface,
2964    origin: Point<i32, Logical>,
2965    p: Point<f64, Logical>,
2966) -> Option<(FocusTarget, Point<f64, Logical>)> {
2967    PopupManager::popups_for_surface(root).find_map(|(popup, location)| {
2968        let at = origin + location - popup.geometry().loc;
2969        under_from_surface_tree(popup.wl_surface(), p, at, WindowSurfaceType::ALL)
2970            .map(|_| (FocusTarget::Popup(popup), at.to_f64()))
2971    })
2972}
2973
2974/// Wrap a `Window` as a focus target.
2975impl From<Window> for FocusTarget {
2976    fn from(w: Window) -> Self {
2977        FocusTarget::Window(w)
2978    }
2979}
2980
2981/// Wrap a popup as a focus target.
2982impl From<PopupKind> for FocusTarget {
2983    fn from(p: PopupKind) -> Self {
2984        FocusTarget::Popup(p)
2985    }
2986}
2987
2988/// Wrap a layer surface as a focus target.
2989impl From<DesktopLayerSurface> for FocusTarget {
2990    fn from(l: DesktopLayerSurface) -> Self {
2991        FocusTarget::LayerSurface(l)
2992    }
2993}
2994
2995/// Liveness of a focus target: true while its underlying window / popup / layer surface is
2996/// still alive, so dead targets are dropped from focus.
2997impl IsAlive for FocusTarget {
2998    fn alive(&self) -> bool {
2999        match self {
3000            FocusTarget::Window(w) => w.alive(),
3001            FocusTarget::Popup(p) => p.alive(),
3002            FocusTarget::LayerSurface(l) => l.alive(),
3003            FocusTarget::Subsurface(surface, owner) => surface.alive() && owner.alive(),
3004        }
3005    }
3006}
3007
3008/// Expose the wrapped target's underlying `wl_surface` and same-client checks so Smithay
3009/// can route focus and selection ownership by client.
3010impl WaylandFocus for FocusTarget {
3011    fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
3012        match self {
3013            FocusTarget::Window(w) => w.wl_surface(),
3014            FocusTarget::Popup(p) => Some(Cow::Borrowed(p.wl_surface())),
3015            FocusTarget::LayerSurface(l) => Some(Cow::Borrowed(l.wl_surface())),
3016            FocusTarget::Subsurface(surface, _) => Some(Cow::Borrowed(surface)),
3017        }
3018    }
3019    fn same_client_as(&self, object_id: &ObjectId) -> bool {
3020        match self {
3021            FocusTarget::Window(w) => w.same_client_as(object_id),
3022            FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
3023            FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
3024            FocusTarget::Subsurface(surface, _) => surface.id().same_client_as(object_id),
3025        }
3026    }
3027}
3028
3029/// Forward every keyboard event (enter / leave / key / modifiers) to the wrapped target's
3030/// underlying `wl_surface`, which carries Smithay's real keyboard-target implementation; a
3031/// subsurface's events go to the surface it belongs to, since keyboard focus is a whole
3032/// window's.
3033impl KeyboardTarget<AppState> for FocusTarget {
3034    fn enter(
3035        &self,
3036        seat: &Seat<AppState>,
3037        data: &mut AppState,
3038        keys: Vec<KeysymHandle<'_>>,
3039        serial: Serial,
3040    ) {
3041        if let Some(surface) = self.owner().wl_surface() {
3042            smithay::input::keyboard::KeyboardTarget::enter(
3043                surface.as_ref(),
3044                seat,
3045                data,
3046                keys,
3047                serial,
3048            );
3049        }
3050    }
3051    fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial) {
3052        if let Some(surface) = self.owner().wl_surface() {
3053            smithay::input::keyboard::KeyboardTarget::leave(surface.as_ref(), seat, data, serial);
3054        }
3055    }
3056    fn key(
3057        &self,
3058        seat: &Seat<AppState>,
3059        data: &mut AppState,
3060        key: KeysymHandle<'_>,
3061        state: smithay::backend::input::KeyState,
3062        serial: Serial,
3063        time: InputTime,
3064    ) {
3065        if let Some(surface) = self.owner().wl_surface() {
3066            smithay::input::keyboard::KeyboardTarget::key(
3067                surface.as_ref(),
3068                seat,
3069                data,
3070                key,
3071                state,
3072                serial,
3073                time,
3074            );
3075        }
3076    }
3077    fn modifiers(
3078        &self,
3079        seat: &Seat<AppState>,
3080        data: &mut AppState,
3081        modifiers: ModifiersState,
3082        serial: Serial,
3083    ) {
3084        if let Some(surface) = self.owner().wl_surface() {
3085            smithay::input::keyboard::KeyboardTarget::modifiers(
3086                surface.as_ref(),
3087                seat,
3088                data,
3089                modifiers,
3090                serial,
3091            );
3092        }
3093    }
3094}
3095
3096/// Forward drag-and-drop focus events (enter / motion / leave / drop) to the wrapped
3097/// target's underlying `wl_surface`, reusing the `WlSurface` offer-data type.
3098impl DndFocus<AppState> for FocusTarget {
3099    type OfferData<S: Source> = <WlSurface as DndFocus<AppState>>::OfferData<S>;
3100
3101    fn enter<S: Source>(
3102        &self,
3103        data: &mut AppState,
3104        dh: &DisplayHandle,
3105        source: Arc<S>,
3106        seat: &Seat<AppState>,
3107        location: Point<f64, Logical>,
3108        serial: &Serial,
3109    ) -> Option<Self::OfferData<S>> {
3110        if let Some(surface) = self.wl_surface() {
3111            <WlSurface as DndFocus<AppState>>::enter(
3112                surface.as_ref(),
3113                data,
3114                dh,
3115                source,
3116                seat,
3117                location,
3118                serial,
3119            )
3120        } else {
3121            None
3122        }
3123    }
3124
3125    fn motion<S: Source>(
3126        &self,
3127        data: &mut AppState,
3128        offer: Option<&mut Self::OfferData<S>>,
3129        seat: &Seat<AppState>,
3130        location: Point<f64, Logical>,
3131        time: InputTime,
3132    ) {
3133        if let Some(surface) = self.wl_surface() {
3134            <WlSurface as DndFocus<AppState>>::motion(
3135                surface.as_ref(),
3136                data,
3137                offer,
3138                seat,
3139                location,
3140                time,
3141            )
3142        }
3143    }
3144
3145    fn leave<S: Source>(
3146        &self,
3147        data: &mut AppState,
3148        offer: Option<&mut Self::OfferData<S>>,
3149        seat: &Seat<AppState>,
3150    ) {
3151        if let Some(surface) = self.wl_surface() {
3152            <WlSurface as DndFocus<AppState>>::leave(surface.as_ref(), data, offer, seat)
3153        }
3154    }
3155
3156    fn drop<S: Source>(
3157        &self,
3158        data: &mut AppState,
3159        offer: Option<&mut Self::OfferData<S>>,
3160        seat: &Seat<AppState>,
3161    ) {
3162        if let Some(surface) = self.wl_surface() {
3163            <WlSurface as DndFocus<AppState>>::drop(surface.as_ref(), data, offer, seat)
3164        }
3165    }
3166}
3167
3168/// Forward every pointer event (motion, buttons, axis, and all swipe / pinch / hold gesture
3169/// phases) to the wrapped target's underlying `wl_surface`.
3170impl PointerTarget<AppState> for FocusTarget {
3171    fn enter(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
3172        if let Some(surface) = self.wl_surface() {
3173            smithay::input::pointer::PointerTarget::enter(surface.as_ref(), seat, data, event);
3174        }
3175    }
3176    fn motion(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
3177        if let Some(surface) = self.wl_surface() {
3178            smithay::input::pointer::PointerTarget::motion(surface.as_ref(), seat, data, event);
3179        }
3180    }
3181    fn relative_motion(
3182        &self,
3183        seat: &Seat<AppState>,
3184        data: &mut AppState,
3185        event: &RelativeMotionEvent,
3186    ) {
3187        if let Some(surface) = self.wl_surface() {
3188            smithay::input::pointer::PointerTarget::relative_motion(
3189                surface.as_ref(),
3190                seat,
3191                data,
3192                event,
3193            );
3194        }
3195    }
3196    fn button(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ButtonEvent) {
3197        if let Some(surface) = self.wl_surface() {
3198            smithay::input::pointer::PointerTarget::button(surface.as_ref(), seat, data, event);
3199        }
3200    }
3201    fn axis(&self, seat: &Seat<AppState>, data: &mut AppState, frame: AxisFrame) {
3202        if let Some(surface) = self.wl_surface() {
3203            smithay::input::pointer::PointerTarget::axis(surface.as_ref(), seat, data, frame);
3204        }
3205    }
3206    fn frame(&self, seat: &Seat<AppState>, data: &mut AppState) {
3207        if let Some(surface) = self.wl_surface() {
3208            smithay::input::pointer::PointerTarget::frame(surface.as_ref(), seat, data);
3209        }
3210    }
3211    fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial, time: InputTime) {
3212        if let Some(surface) = self.wl_surface() {
3213            smithay::input::pointer::PointerTarget::leave(
3214                surface.as_ref(),
3215                seat,
3216                data,
3217                serial,
3218                time,
3219            );
3220        }
3221    }
3222    fn gesture_swipe_begin(
3223        &self,
3224        seat: &Seat<AppState>,
3225        data: &mut AppState,
3226        event: &GestureSwipeBeginEvent,
3227    ) {
3228        if let Some(surface) = self.wl_surface() {
3229            smithay::input::pointer::PointerTarget::gesture_swipe_begin(
3230                surface.as_ref(),
3231                seat,
3232                data,
3233                event,
3234            );
3235        }
3236    }
3237    fn gesture_swipe_update(
3238        &self,
3239        seat: &Seat<AppState>,
3240        data: &mut AppState,
3241        event: &GestureSwipeUpdateEvent,
3242    ) {
3243        if let Some(surface) = self.wl_surface() {
3244            smithay::input::pointer::PointerTarget::gesture_swipe_update(
3245                surface.as_ref(),
3246                seat,
3247                data,
3248                event,
3249            );
3250        }
3251    }
3252    fn gesture_swipe_end(
3253        &self,
3254        seat: &Seat<AppState>,
3255        data: &mut AppState,
3256        event: &GestureSwipeEndEvent,
3257    ) {
3258        if let Some(surface) = self.wl_surface() {
3259            smithay::input::pointer::PointerTarget::gesture_swipe_end(
3260                surface.as_ref(),
3261                seat,
3262                data,
3263                event,
3264            );
3265        }
3266    }
3267    fn gesture_pinch_begin(
3268        &self,
3269        seat: &Seat<AppState>,
3270        data: &mut AppState,
3271        event: &GesturePinchBeginEvent,
3272    ) {
3273        if let Some(surface) = self.wl_surface() {
3274            smithay::input::pointer::PointerTarget::gesture_pinch_begin(
3275                surface.as_ref(),
3276                seat,
3277                data,
3278                event,
3279            );
3280        }
3281    }
3282    fn gesture_pinch_update(
3283        &self,
3284        seat: &Seat<AppState>,
3285        data: &mut AppState,
3286        event: &GesturePinchUpdateEvent,
3287    ) {
3288        if let Some(surface) = self.wl_surface() {
3289            smithay::input::pointer::PointerTarget::gesture_pinch_update(
3290                surface.as_ref(),
3291                seat,
3292                data,
3293                event,
3294            );
3295        }
3296    }
3297    fn gesture_pinch_end(
3298        &self,
3299        seat: &Seat<AppState>,
3300        data: &mut AppState,
3301        event: &GesturePinchEndEvent,
3302    ) {
3303        if let Some(surface) = self.wl_surface() {
3304            smithay::input::pointer::PointerTarget::gesture_pinch_end(
3305                surface.as_ref(),
3306                seat,
3307                data,
3308                event,
3309            );
3310        }
3311    }
3312    fn gesture_hold_begin(
3313        &self,
3314        seat: &Seat<AppState>,
3315        data: &mut AppState,
3316        event: &GestureHoldBeginEvent,
3317    ) {
3318        if let Some(surface) = self.wl_surface() {
3319            smithay::input::pointer::PointerTarget::gesture_hold_begin(
3320                surface.as_ref(),
3321                seat,
3322                data,
3323                event,
3324            );
3325        }
3326    }
3327    fn gesture_hold_end(
3328        &self,
3329        seat: &Seat<AppState>,
3330        data: &mut AppState,
3331        event: &GestureHoldEndEvent,
3332    ) {
3333        if let Some(surface) = self.wl_surface() {
3334            smithay::input::pointer::PointerTarget::gesture_hold_end(
3335                surface.as_ref(),
3336                seat,
3337                data,
3338                event,
3339            );
3340        }
3341    }
3342}
3343
3344/// Forward every touch event (down / up / motion / frame / cancel / shape / orientation) to
3345/// the wrapped target's underlying `wl_surface`.
3346impl TouchTarget<AppState> for FocusTarget {
3347    fn down(&self, seat: &Seat<AppState>, data: &mut AppState, event: &DownEvent) {
3348        if let Some(surface) = self.wl_surface() {
3349            smithay::input::touch::TouchTarget::down(surface.as_ref(), seat, data, event);
3350        }
3351    }
3352    fn up(&self, seat: &Seat<AppState>, data: &mut AppState, event: &UpEvent) {
3353        if let Some(surface) = self.wl_surface() {
3354            smithay::input::touch::TouchTarget::up(surface.as_ref(), seat, data, event);
3355        }
3356    }
3357    fn motion(
3358        &self,
3359        seat: &Seat<AppState>,
3360        data: &mut AppState,
3361        event: &smithay::input::touch::MotionEvent,
3362    ) {
3363        if let Some(surface) = self.wl_surface() {
3364            smithay::input::touch::TouchTarget::motion(surface.as_ref(), seat, data, event);
3365        }
3366    }
3367    fn frame(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
3368        if let Some(surface) = self.wl_surface() {
3369            smithay::input::touch::TouchTarget::frame(surface.as_ref(), seat, data, marker);
3370        }
3371    }
3372    fn cancel(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
3373        if let Some(surface) = self.wl_surface() {
3374            smithay::input::touch::TouchTarget::cancel(surface.as_ref(), seat, data, marker);
3375        }
3376    }
3377    fn shape(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ShapeEvent) {
3378        if let Some(surface) = self.wl_surface() {
3379            smithay::input::touch::TouchTarget::shape(surface.as_ref(), seat, data, event);
3380        }
3381    }
3382    fn orientation(&self, seat: &Seat<AppState>, data: &mut AppState, event: &OrientationEvent) {
3383        if let Some(surface) = self.wl_surface() {
3384            smithay::input::touch::TouchTarget::orientation(surface.as_ref(), seat, data, event);
3385        }
3386    }
3387    fn last_frame(&self, seat: &Seat<AppState>, data: &mut AppState) -> Option<FrameMarker> {
3388        self.wl_surface().and_then(|surface| {
3389            smithay::input::touch::TouchTarget::last_frame(surface.as_ref(), seat, data)
3390        })
3391    }
3392}
3393
3394/// Map a Smithay `CursorIcon` to its CSS cursor-name string, used both for themed-cursor
3395/// lookup and for the name handed to the Python cursor callback; unknown icons fall back to
3396/// `"default"`.
3397pub fn cursor_icon_to_str(icon: &CursorIcon) -> &'static str {
3398    match icon {
3399        CursorIcon::Default => "default",
3400        CursorIcon::ContextMenu => "context-menu",
3401        CursorIcon::Help => "help",
3402        CursorIcon::Pointer => "pointer",
3403        CursorIcon::Progress => "progress",
3404        CursorIcon::Wait => "wait",
3405        CursorIcon::Cell => "cell",
3406        CursorIcon::Crosshair => "crosshair",
3407        CursorIcon::Text => "text",
3408        CursorIcon::VerticalText => "vertical-text",
3409        CursorIcon::Alias => "alias",
3410        CursorIcon::Copy => "copy",
3411        CursorIcon::Move => "move",
3412        CursorIcon::NoDrop => "no-drop",
3413        CursorIcon::NotAllowed => "not-allowed",
3414        CursorIcon::Grab => "grab",
3415        CursorIcon::Grabbing => "grabbing",
3416        CursorIcon::AllScroll => "all-scroll",
3417        CursorIcon::ColResize => "col-resize",
3418        CursorIcon::RowResize => "row-resize",
3419        CursorIcon::NResize => "n-resize",
3420        CursorIcon::EResize => "e-resize",
3421        CursorIcon::SResize => "s-resize",
3422        CursorIcon::WResize => "w-resize",
3423        CursorIcon::NeResize => "ne-resize",
3424        CursorIcon::NwResize => "nw-resize",
3425        CursorIcon::SeResize => "se-resize",
3426        CursorIcon::SwResize => "sw-resize",
3427        CursorIcon::EwResize => "ew-resize",
3428        CursorIcon::NsResize => "ns-resize",
3429        CursorIcon::NeswResize => "nesw-resize",
3430        CursorIcon::NwseResize => "nwse-resize",
3431        CursorIcon::ZoomIn => "zoom-in",
3432        CursorIcon::ZoomOut => "zoom-out",
3433        _ => "default",
3434    }
3435}
3436
3437/// Seat wiring: declares the keyboard / pointer / touch focus types and reacts to cursor
3438/// changes and keyboard-focus moves.
3439impl SeatHandler for AppState {
3440    type KeyboardFocus = FocusTarget;
3441    type PointerFocus = FocusTarget;
3442    type TouchFocus = FocusTarget;
3443    fn seat_state(&mut self) -> &mut SeatState<AppState> {
3444        &mut self.seat_state
3445    }
3446
3447    /// A client requested a cursor change (named, hidden, or surface-backed): retain it as
3448    /// the current icon and forward it to the Python cursor callback. A surface-backed one
3449    /// is delivered by the surface's commit or by `flush_pending_cursor`, whichever is first.
3450    fn cursor_image(&mut self, _seat: &Seat<AppState>, image: CursorImageStatus) {
3451        self.current_cursor_icon = Some(image.clone());
3452        self.cursor_surface_pending = matches!(image, CursorImageStatus::Surface(_));
3453        if !self.cursor_surface_pending {
3454            self.send_cursor_image(&image);
3455        }
3456    }
3457
3458    /// Keep BOTH selections' focus following keyboard focus: without the data-device half,
3459    /// the focused client never receives wl_data_offer events and Ctrl+V paste is a silent
3460    /// no-op even while the compositor-side selection is correct (primary covers only
3461    /// middle-click paste).
3462    fn focus_changed(&mut self, seat: &Seat<AppState>, focus: Option<&Self::KeyboardFocus>) {
3463        let dh = &self.dh;
3464        let client = focus
3465            .and_then(|t| t.wl_surface())
3466            .and_then(|s| dh.get_client(s.id()).ok());
3467        set_data_device_focus(dh, seat, client.clone());
3468        set_primary_focus(dh, seat, client);
3469        self.keyboard_focus = match focus {
3470            Some(FocusTarget::Window(window)) => window_meta(window).map(|m| m.id),
3471            _ => None,
3472        };
3473        crate::wayland::foreign_toplevel::update_all(self);
3474        crate::wayland::xwm::publish_stacking(self);
3475    }
3476}
3477
3478/// Pointer-warp protocol: lets a client teleport the pointer to a surface-local position
3479/// (games / remote-desktop style warps).
3480///
3481/// `warp_pointer` locates the requesting surface's origin in the global space, adds the requested
3482/// surface-local offset to get a global position, recomputes what element lies under it, and emits
3483/// a synthetic motion event so focus and enter/leave follow the warp.
3484impl PointerWarpHandler for AppState {
3485    fn warp_pointer(
3486        &mut self,
3487        surface: WlSurface,
3488        _pointer: WlPointer,
3489        pos: Point<f64, Logical>,
3490        serial: Serial,
3491    ) {
3492        let surface_origin = self.space.elements().find_map(|window| {
3493            if window.wl_surface().as_deref() == Some(&surface) {
3494                self.space.element_location(window)
3495            } else {
3496                None
3497            }
3498        });
3499
3500        if let Some(origin) = surface_origin {
3501            let global_pos = origin.to_f64() + pos;
3502            let time = wayland_time();
3503
3504            if let Some(pointer) = self.seat.get_pointer() {
3505                let under = self
3506                    .space
3507                    .element_under(global_pos)
3508                    .map(|(w, loc)| (FocusTarget::Window(w.clone()), loc.to_f64()));
3509
3510                pointer.motion(
3511                    self,
3512                    under,
3513                    &MotionEvent {
3514                        location: global_pos,
3515                        serial,
3516                        time,
3517                    },
3518                );
3519            }
3520        }
3521    }
3522}
3523
3524/// xdg-shell protocol: toplevel and popup lifecycle.
3525impl XdgShellHandler for AppState {
3526    fn xdg_shell_state(&mut self) -> &mut XdgShellState {
3527        &mut self.shell_state
3528    }
3529    /// A new toplevel appears: wrap it in a `Window`, queue it for mapping, and register a
3530    /// foreign-toplevel handle (seeded with title / app-id) stored on the surface for later updates.
3531    /// The window is pinned to the pointer's output HERE — the first commit can't be relied on
3532    /// for that, because a decoration-negotiating client (foot) has its initial configure sent
3533    /// by `new_decoration` before it ever commits, skipping the pre-configure commit branch.
3534    fn new_toplevel(&mut self, surface: ToplevelSurface) {
3535        let target_id = self.pointer_display();
3536        let window = Window::new_wayland_window(surface.clone());
3537        window
3538            .user_data()
3539            .insert_if_missing_threadsafe(|| WindowMeta {
3540                id: NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed),
3541                output: AtomicU32::new(target_id),
3542                placed: AtomicBool::new(false),
3543                parked: AtomicBool::new(false),
3544            });
3545        self.pending_windows.push(window);
3546        // A null buffer about to be committed is the last chance to keep the window's picture
3547        // for the policy's close effect; the hook reads the pending state before it applies.
3548        smithay::wayland::compositor::add_pre_commit_hook::<AppState, _>(
3549            surface.wl_surface(),
3550            |state, _, surface| {
3551                let removing = with_states(surface, |states| {
3552                    matches!(
3553                        states
3554                            .cached_state
3555                            .get::<smithay::wayland::compositor::SurfaceAttributes>()
3556                            .pending()
3557                            .buffer,
3558                        Some(smithay::wayland::compositor::BufferAssignment::Removed)
3559                    )
3560                });
3561                if !removing {
3562                    return;
3563                }
3564                let window = state
3565                    .space
3566                    .elements()
3567                    .find(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface))
3568                    .cloned();
3569                if let Some(window) = window {
3570                    crate::wayland::wm::snapshot(state, &window);
3571                }
3572            },
3573        );
3574        let (title, app_id) = with_states(surface.wl_surface(), |states| {
3575            let attributes = states
3576                .data_map
3577                .get::<XdgToplevelSurfaceData>()
3578                .unwrap()
3579                .lock()
3580                .unwrap();
3581            (attributes.title.clone(), attributes.app_id.clone())
3582        });
3583
3584        let handle = self
3585            .foreign_toplevel_list
3586            .new_toplevel::<AppState>(title.unwrap_or_default(), app_id.unwrap_or_default());
3587
3588        with_states(surface.wl_surface(), |states| {
3589            states.data_map.insert_if_missing(|| handle)
3590        });
3591    }
3592    /// Register a new popup (menu, tooltip, combo-box list) with the `PopupManager` so it
3593    /// takes part in grab and dismissal handling, then send the initial configure xdg-shell requires
3594    /// before the client is allowed to draw it.
3595    fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
3596        if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
3597            eprintln!("Failed to track popup: {:?}", err);
3598        }
3599        let _ = surface.send_configure();
3600    }
3601    /// Popup grab: find the popup's root surface and its window or layer surface, then install a
3602    /// popup grab so dismissal and pointer routing behave correctly.
3603    fn grab(
3604        &mut self,
3605        surface: PopupSurface,
3606        _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3607        serial: Serial,
3608    ) {
3609        let kind = PopupKind::Xdg(surface);
3610        let Ok(root_surface) = smithay::desktop::find_popup_root_surface(&kind) else {
3611            return;
3612        };
3613        let root = self
3614            .space
3615            .elements()
3616            .find(|w| w.wl_surface().as_deref() == Some(&root_surface))
3617            .cloned()
3618            .map(FocusTarget::Window)
3619            .or_else(|| {
3620                self.output_nodes.iter().find_map(|node| {
3621                    layer_map_for_output(&node.output)
3622                        .layer_for_surface(&root_surface, WindowSurfaceType::TOPLEVEL)
3623                        .cloned()
3624                        .map(FocusTarget::LayerSurface)
3625                })
3626            });
3627        if let Some(root) = root {
3628            let _ = self.popups.grab_popup(root, kind, &self.seat, serial);
3629        }
3630    }
3631    /// Re-track a popup whose position changed (e.g. a submenu flipping sides to stay
3632    /// on-screen) so the `PopupManager` follows its new geometry, then echo the client's reposition
3633    /// token back to confirm the move took effect.
3634    fn reposition_request(
3635        &mut self,
3636        surface: PopupSurface,
3637        _positioner: PositionerState,
3638        token: u32,
3639    ) {
3640        if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
3641            eprintln!("Failed to track popup: {:?}", err);
3642        }
3643        let _ = surface.send_repositioned(token);
3644    }
3645    /// Client fullscreen request: always granted at the compositor's forced-fullscreen
3646    /// geometry (the CURRENT logical size), so the toggle gets a definite answer. A request
3647    /// naming an output moves the window there, which is how a nested compositor puts each
3648    /// of its screens on a monitor of its choosing.
3649    fn fullscreen_request(
3650        &mut self,
3651        surface: ToplevelSurface,
3652        output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
3653    ) {
3654        if crate::wayland::wm::active(self) {
3655            let output_id = output.as_ref().and_then(|wlo| {
3656                self.output_nodes
3657                    .iter()
3658                    .find(|n| n.output.owns(wlo))
3659                    .map(|n| n.id)
3660            });
3661            if let Some(id) = self.id_of_toplevel(&surface) {
3662                crate::wayland::wm::request(self, id, crate::wm::Request::Fullscreen(output_id));
3663            }
3664            return;
3665        }
3666        if let Some(id) = output.as_ref().and_then(|wlo| {
3667            self.output_nodes
3668                .iter()
3669                .find(|n| n.output.owns(wlo))
3670                .map(|n| n.id)
3671        }) {
3672            let mapped = self
3673                .space
3674                .elements()
3675                .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
3676                .cloned();
3677            if let Some(window) = mapped {
3678                if self.place_window_on_output(&window, id) {
3679                    return;
3680                }
3681            } else if let Some(meta) = self
3682                .pending_windows
3683                .iter()
3684                .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
3685                .and_then(window_meta)
3686            {
3687                // Still waiting for its first buffer: record the output instead of mapping
3688                // it, so nothing renders or hit-tests before the client has drawn. The
3689                // configure below then carries that output's geometry.
3690                meta.output.store(id, Ordering::Relaxed);
3691                meta.placed.store(true, Ordering::Relaxed);
3692            }
3693        }
3694        self.send_forced_fullscreen_configure(&surface);
3695    }
3696    /// Client unfullscreen request: the forced-fullscreen policy stands, but the client
3697    /// still receives an explicit configure at the current geometry (the Smithay default sends
3698    /// NOTHING here, leaving the app waiting on a toggle that never answers).
3699    fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
3700        if let Some(id) = self.id_of_toplevel(&surface)
3701            && crate::wayland::wm::request(self, id, crate::wm::Request::Unfullscreen)
3702        {
3703            return;
3704        }
3705        self.send_forced_fullscreen_configure(&surface);
3706    }
3707    /// Maximize request: the policy's where one is installed, else the forced-fullscreen
3708    /// configure (same geometry).
3709    fn maximize_request(&mut self, surface: ToplevelSurface) {
3710        if let Some(id) = self.id_of_toplevel(&surface)
3711            && crate::wayland::wm::request(self, id, crate::wm::Request::Maximize)
3712        {
3713            return;
3714        }
3715        self.send_forced_fullscreen_configure(&surface);
3716    }
3717    /// Unmaximize request: the policy's, else an explicit current-geometry configure.
3718    fn unmaximize_request(&mut self, surface: ToplevelSurface) {
3719        if let Some(id) = self.id_of_toplevel(&surface)
3720            && crate::wayland::wm::request(self, id, crate::wm::Request::Unmaximize)
3721        {
3722            return;
3723        }
3724        self.send_forced_fullscreen_configure(&surface);
3725    }
3726    /// Minimize request: the policy's; without one every window stays as it is.
3727    fn minimize_request(&mut self, surface: ToplevelSurface) {
3728        if let Some(id) = self.id_of_toplevel(&surface) {
3729            crate::wayland::wm::request(self, id, crate::wm::Request::Minimize);
3730        }
3731    }
3732    /// An interactive move the client started from a button it holds: the policy's drag.
3733    fn move_request(
3734        &mut self,
3735        surface: ToplevelSurface,
3736        _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3737        _serial: Serial,
3738    ) {
3739        if let Some(id) = self.id_of_toplevel(&surface) {
3740            crate::wayland::wm::request(self, id, crate::wm::Request::Move);
3741        }
3742    }
3743    /// An interactive resize the client started: the policy's drag on the named edge.
3744    fn resize_request(
3745        &mut self,
3746        surface: ToplevelSurface,
3747        _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3748        _serial: Serial,
3749        edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
3750    ) {
3751        if let Some(id) = self.id_of_toplevel(&surface) {
3752            let edge = crate::wm::ResizeEdge::from_xdg(edges as u32);
3753            crate::wayland::wm::request(self, id, crate::wm::Request::Resize(edge));
3754        }
3755    }
3756    /// The client asks for its window menu: the policy's, at the point in the window.
3757    fn show_window_menu(
3758        &mut self,
3759        surface: ToplevelSurface,
3760        _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3761        _serial: Serial,
3762        location: Point<i32, Logical>,
3763    ) {
3764        if let Some(id) = self.id_of_toplevel(&surface) {
3765            crate::wayland::wm::request(
3766                self,
3767                id,
3768                crate::wm::Request::ShowMenu {
3769                    x: location.x,
3770                    y: location.y,
3771                },
3772            );
3773        }
3774    }
3775    /// The title changed: the policy's frame and the taskbars follow.
3776    fn title_changed(&mut self, surface: ToplevelSurface) {
3777        if let Some(window) = self.window_of_toplevel(&surface) {
3778            crate::wayland::wm::window_changed(self, &window);
3779            if let Some(id) = window_meta(&window).map(|m| m.id) {
3780                crate::wayland::foreign_toplevel::update(self, id);
3781            }
3782        }
3783    }
3784    /// The app id changed: the policy's frame icon and the taskbars follow.
3785    fn app_id_changed(&mut self, surface: ToplevelSurface) {
3786        if let Some(window) = self.window_of_toplevel(&surface) {
3787            crate::wayland::wm::window_changed(self, &window);
3788            if let Some(id) = window_meta(&window).map(|m| m.id) {
3789                crate::wayland::foreign_toplevel::update(self, id);
3790            }
3791        }
3792    }
3793    /// The parent changed: the policy stacks and places the dialog over it.
3794    fn parent_changed(&mut self, surface: ToplevelSurface) {
3795        if let Some(window) = self.window_of_toplevel(&surface) {
3796            crate::wayland::wm::window_changed(self, &window);
3797        }
3798    }
3799    /// A toplevel closed: drop it from the pending-window queue so a window that never
3800    /// finished mapping can't linger there, unmap it from the space at once so `list_windows`
3801    /// and hit-testing never see a husk (the periodic `space.refresh` would only reap it
3802    /// later), and remove its foreign-toplevel handle so taskbar-style clients stop listing a
3803    /// window that is gone.
3804    fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
3805        // The window's id is read before it leaves the space and the pending list, which is
3806        // where it is found.
3807        let id = self
3808            .wm
3809            .as_ref()
3810            .and_then(|wm| {
3811                wm.hidden
3812                    .iter()
3813                    .find(|(_, w)| w.toplevel().is_some_and(|t| *t == surface))
3814                    .map(|(id, _)| *id)
3815            })
3816            .or_else(|| {
3817                self.space
3818                    .elements()
3819                    .chain(self.pending_windows.iter())
3820                    .find(|w| w.toplevel().is_some_and(|t| *t == surface))
3821                    .and_then(|w| window_meta(w).map(|m| m.id))
3822            });
3823        if let Some(idx) = self
3824            .pending_windows
3825            .iter()
3826            .position(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
3827        {
3828            self.pending_windows.remove(idx);
3829        }
3830        let mapped = self
3831            .space
3832            .elements()
3833            .find(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
3834            .cloned();
3835        if let Some(window) = mapped {
3836            crate::wayland::wm::snapshot(self, &window);
3837            self.space.unmap_elem(&window);
3838            self.refocus_pointer();
3839        }
3840        if let Some(handle) = with_states(surface.wl_surface(), |states| {
3841            states.data_map.get::<ForeignToplevelHandle>().cloned()
3842        }) {
3843            self.foreign_toplevel_list.remove_toplevel(&handle);
3844        }
3845        if let Some(id) = id {
3846            crate::wayland::foreign_toplevel::remove(self, id);
3847            crate::wayland::wm::window_removed(self, id);
3848        }
3849    }
3850}
3851
3852/// In-house `zwp_virtual_keyboard_v1` implementation. Smithay's manager swaps the
3853/// client-visible seat keymap to the virtual keyboard's keymap on every VK event and never
3854/// restores it, leaving every client holding a foreign keymap (and killing the compositor's
3855/// overlay keycodes) after any VK use. Here VK key events are TRANSLATED instead: each keycode
3856/// resolves to its level-0 keysym under the VK client's own uploaded keymap, maps onto the seat
3857/// keymap (overlay-binding on demand, batched at keymap upload), and injects through the seat's
3858/// regular input path — the seat keymap identity never changes and modifier/pressed-key state
3859/// stays coherent with server-side injection. VK `modifiers` requests are ignored: applying a
3860/// foreign modifier mask would corrupt the seat's own tracked state, and the supported VK
3861/// client (selkies' wayland_typer) binds every keysym at level 0 and never sends them.
3862pub struct PfVirtualKeyboard {
3863    inner: Mutex<PfVkState>,
3864}
3865
3866#[derive(Default)]
3867struct PfVkState {
3868    /// Level-0 keysym per xkb keycode of the client's uploaded keymap.
3869    syms: Option<std::collections::HashMap<u32, u32>>,
3870    /// VK xkb keycode -> injected seat keycode, so a release always matches its press even
3871    /// across policy rebinds.
3872    pressed: std::collections::HashMap<u32, u32>,
3873}
3874
3875impl GlobalDispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3876    fn bind(
3877        _state: &mut Self,
3878        _dh: &DisplayHandle,
3879        _client: &Client,
3880        resource: New<ZwpVirtualKeyboardManagerV1>,
3881        _global_data: &(),
3882        data_init: &mut DataInit<'_, Self>,
3883    ) {
3884        data_init.init(resource, ());
3885    }
3886}
3887
3888impl Dispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3889    fn request(
3890        _state: &mut Self,
3891        _client: &Client,
3892        _resource: &ZwpVirtualKeyboardManagerV1,
3893        request: zwp_virtual_keyboard_manager_v1::Request,
3894        _data: &(),
3895        _dh: &DisplayHandle,
3896        data_init: &mut DataInit<'_, Self>,
3897    ) {
3898        if let zwp_virtual_keyboard_manager_v1::Request::CreateVirtualKeyboard { seat: _, id } =
3899            request
3900        {
3901            data_init.init(
3902                id,
3903                PfVirtualKeyboard {
3904                    inner: Mutex::new(PfVkState::default()),
3905                },
3906            );
3907        }
3908    }
3909}
3910
3911impl Dispatch<ZwpVirtualKeyboardV1, PfVirtualKeyboard> for AppState {
3912    fn request(
3913        state: &mut Self,
3914        _client: &Client,
3915        resource: &ZwpVirtualKeyboardV1,
3916        request: zwp_virtual_keyboard_v1::Request,
3917        data: &PfVirtualKeyboard,
3918        _dh: &DisplayHandle,
3919        _data_init: &mut DataInit<'_, Self>,
3920    ) {
3921        use smithay::input::keyboard::xkb;
3922        match request {
3923            zwp_virtual_keyboard_v1::Request::Keymap { format, fd, size } => {
3924                if format != 1 {
3925                    return;
3926                }
3927                // The size is the client's word: mapping past the file's end faults the
3928                // compositor thread. A keymap is tens of kilobytes.
3929                const MAX_KEYMAP_BYTES: u64 = 4 << 20;
3930                let file = std::fs::File::from(fd);
3931                let length = file.metadata().map(|m| m.len()).unwrap_or(0);
3932                if size == 0 || u64::from(size) > length || u64::from(size) > MAX_KEYMAP_BYTES {
3933                    eprintln!(
3934                        "[Wayland] virtual-keyboard keymap of {size} bytes in a {length}-byte file; ignoring."
3935                    );
3936                    return;
3937                }
3938                let fd = std::os::fd::OwnedFd::from(file);
3939                let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
3940                let keymap = unsafe {
3941                    xkb::Keymap::new_from_fd(
3942                        &ctx,
3943                        fd,
3944                        size as usize,
3945                        xkb::KEYMAP_FORMAT_TEXT_V1,
3946                        xkb::KEYMAP_COMPILE_NO_FLAGS,
3947                    )
3948                };
3949                let Ok(Some(keymap)) = keymap else {
3950                    eprintln!("[Wayland] virtual-keyboard keymap failed to compile; ignoring.");
3951                    return;
3952                };
3953                let syms = crate::wayland::keymap::level0_syms(&keymap);
3954                // Pre-bind everything this keymap can type that the seat cannot, in ONE
3955                // seat keymap swap, so the following key events bind nothing.
3956                let missing: Vec<u32> = syms
3957                    .values()
3958                    .copied()
3959                    .filter(|&s| !state.keymap_policy.resolves_plain(s))
3960                    .collect();
3961                if !missing.is_empty() {
3962                    let _ = state.bind_keysyms_plain(&missing);
3963                }
3964                data.inner.lock().unwrap().syms = Some(syms);
3965            }
3966            zwp_virtual_keyboard_v1::Request::Key {
3967                time: _,
3968                key,
3969                state: key_state,
3970            } => {
3971                let mut vk = data.inner.lock().unwrap();
3972                if vk.syms.is_none() {
3973                    drop(vk);
3974                    resource.post_error(
3975                        zwp_virtual_keyboard_v1::Error::NoKeymap,
3976                        "`key` sent before keymap.",
3977                    );
3978                    return;
3979                }
3980                let vk_kc = key.wrapping_add(8);
3981                let seat_kc = if key_state == 1 {
3982                    let sym = vk.syms.as_ref().and_then(|m| m.get(&vk_kc)).copied();
3983                    // Translate through the seat keymap; an untranslatable keycode
3984                    // passes through raw (base sections of both keymaps agree for
3985                    // ordinary pc keycodes).
3986                    let kc = match sym {
3987                        Some(sym) => {
3988                            let bound = state.bind_keysyms_plain(&[sym])[0];
3989                            if bound != 0 { bound } else { vk_kc }
3990                        }
3991                        None => vk_kc,
3992                    };
3993                    vk.pressed.insert(vk_kc, kc);
3994                    kc
3995                } else {
3996                    vk.pressed.remove(&vk_kc).unwrap_or(vk_kc)
3997                };
3998                drop(vk);
3999                let pressed = key_state == 1;
4000                if let Some(keyboard) = state.seat.get_keyboard() {
4001                    let serial = next_serial();
4002                    let time = wayland_time();
4003                    keyboard.input(
4004                        state,
4005                        smithay::backend::input::Keycode::new(seat_kc),
4006                        if pressed {
4007                            smithay::backend::input::KeyState::Pressed
4008                        } else {
4009                            smithay::backend::input::KeyState::Released
4010                        },
4011                        serial,
4012                        time,
4013                        |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
4014                    );
4015                }
4016            }
4017            zwp_virtual_keyboard_v1::Request::Modifiers { .. } => {}
4018            zwp_virtual_keyboard_v1::Request::Destroy => {}
4019            _ => {}
4020        }
4021    }
4022
4023    /// Release every seat key this virtual keyboard still holds, so a VK client that
4024    /// disconnects mid-press cannot leave keys logically stuck.
4025    fn destroyed(
4026        state: &mut Self,
4027        _client: ClientId,
4028        _resource: &ZwpVirtualKeyboardV1,
4029        data: &PfVirtualKeyboard,
4030    ) {
4031        let held: Vec<u32> = data
4032            .inner
4033            .lock()
4034            .unwrap()
4035            .pressed
4036            .drain()
4037            .map(|(_, kc)| kc)
4038            .collect();
4039        if held.is_empty() {
4040            return;
4041        }
4042        if let Some(keyboard) = state.seat.get_keyboard() {
4043            for kc in held {
4044                let serial = next_serial();
4045                let time = wayland_time();
4046                keyboard.input(
4047                    state,
4048                    smithay::backend::input::Keycode::new(kc),
4049                    smithay::backend::input::KeyState::Released,
4050                    serial,
4051                    time,
4052                    |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
4053                );
4054            }
4055        }
4056    }
4057}
4058
4059/// Per-client data attached to every Wayland client connection; holds the compositor's
4060/// per-client surface state.
4061#[derive(Default)]
4062pub struct ClientState {
4063    pub compositor_state: CompositorClientState,
4064}
4065/// Client lifecycle hooks; connect and disconnect need no bookkeeping here.
4066impl ClientData for ClientState {
4067    fn initialized(&self, _client_id: ClientId) {}
4068    fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
4069}
4070
4071delegate_dispatch2!(AppState);
4072
4073/// Row stride (bytes) of a tightly-mapped RGBA8 GPU readback, derived from the mapping
4074/// length rather than assuming `width*4`.
4075///
4076/// Dividing the buffer length by the height recovers a padded stride, so a padded readback cannot
4077/// skew the cursor image; the result never drops below one full `width*4` row, and a zero height
4078/// short-circuits to one row to avoid dividing by zero.
4079pub(crate) fn rgba_readback_stride(buf_len: usize, height: usize, width: usize) -> usize {
4080    let row = width.saturating_mul(4);
4081    if height == 0 {
4082        return row;
4083    }
4084    (buf_len / height).max(row)
4085}
4086
4087#[cfg(test)]
4088mod stride_tests {
4089    use super::rgba_readback_stride;
4090
4091    /// A tightly-packed readback yields exactly `width*4` stride.
4092    #[test]
4093    fn packed_readback_is_width_times_four() {
4094        assert_eq!(rgba_readback_stride(64 * 4 * 48, 48, 64), 64 * 4);
4095        assert_eq!(rgba_readback_stride(3 * 4 * 2, 2, 3), 12);
4096    }
4097
4098    /// A padded readback (extra bytes per row) recovers the true padded stride from the
4099    /// buffer length.
4100    #[test]
4101    fn padded_readback_recovers_real_stride() {
4102        let (w, h, pad) = (3usize, 2usize, 4usize);
4103        let stride = w * 4 + pad;
4104        assert_eq!(rgba_readback_stride(stride * h, h, w), stride);
4105    }
4106
4107    /// Extracting pixels with the recovered stride from a padded buffer reproduces the
4108    /// written values with no row-to-row skew.
4109    #[test]
4110    fn padded_extraction_has_no_skew() {
4111        let (w, h) = (3usize, 2usize);
4112        let stride = 16usize;
4113        let mut buf = vec![0u8; stride * h];
4114        for y in 0..h {
4115            for x in 0..w {
4116                let o = y * stride + x * 4;
4117                buf[o] = x as u8 + 1;
4118                buf[o + 1] = y as u8 + 1;
4119            }
4120        }
4121        let s = rgba_readback_stride(buf.len(), h, w);
4122        assert_eq!(s, stride);
4123        for y in 0..h {
4124            for x in 0..w {
4125                let o = y * s + x * 4;
4126                assert_eq!(buf[o], x as u8 + 1);
4127                assert_eq!(buf[o + 1], y as u8 + 1);
4128            }
4129        }
4130    }
4131
4132    /// Zero height returns one full row instead of dividing by zero.
4133    #[test]
4134    fn zero_height_no_divide_by_zero() {
4135        assert_eq!(rgba_readback_stride(0, 0, 10), 40);
4136    }
4137
4138    /// A buffer shorter than a single row still reports a full `width*4` row.
4139    #[test]
4140    fn truncated_buffer_keeps_full_row() {
4141        assert_eq!(rgba_readback_stride(10, 5, 64), 64 * 4);
4142    }
4143}
4144
4145#[cfg(test)]
4146mod clipboard_flavor_tests {
4147    use super::{CLIPBOARD_SECRET_HINTS, clipboard_flavors};
4148
4149    fn offer(mimes: &[&str]) -> Vec<String> {
4150        clipboard_flavors(&mimes.iter().map(|m| m.to_string()).collect::<Vec<_>>())
4151    }
4152
4153    /// A password manager's copy is read with its hint, whichever spelling it offers.
4154    #[test]
4155    fn a_secret_copy_is_read_with_its_hint() {
4156        for hint in CLIPBOARD_SECRET_HINTS {
4157            assert_eq!(
4158                offer(&["TEXT", "text/plain", "text/plain;charset=utf-8", hint]),
4159                vec!["text/plain;charset=utf-8".to_string(), hint.to_string()]
4160            );
4161        }
4162    }
4163
4164    /// An ordinary copy reads as before: the picture alone, or the markup and the text.
4165    #[test]
4166    fn an_ordinary_copy_reads_as_before() {
4167        assert_eq!(offer(&["text/plain", "image/png"]), vec!["image/png"]);
4168        assert_eq!(
4169            offer(&["UTF8_STRING", "text/html"]),
4170            vec!["text/html", "UTF8_STRING"]
4171        );
4172    }
4173
4174    /// A copy offering nothing the bridge reads stages no flavors, like a cleared selection.
4175    #[test]
4176    fn an_unreadable_copy_stages_nothing() {
4177        assert!(offer(&["application/x-private"]).is_empty());
4178    }
4179}
4180
4181#[cfg(test)]
4182mod pointer_share_tests {
4183    use super::{PointerAt, PointerShare};
4184    use std::sync::Arc;
4185
4186    fn at(x: f64, y: f64, motion: u64) -> PointerAt {
4187        PointerAt {
4188            x,
4189            y,
4190            scale: 1.5,
4191            motion,
4192        }
4193    }
4194
4195    /// Nothing reads as a position before the compositor's first pass, and a pass's fields
4196    /// read back together, at the precision a pixel position needs.
4197    #[test]
4198    fn a_published_position_reads_back_whole() {
4199        let share = PointerShare::default();
4200        assert_eq!(share.read(), None);
4201        share.publish(at(1920.5, 33.25, 7));
4202        assert_eq!(share.read(), Some(at(1920.5, 33.25, 7)));
4203        share.publish(at(-12.0, 7679.75, 9));
4204        assert_eq!(share.read(), Some(at(-12.0, 7679.75, 9)));
4205    }
4206
4207    /// A reader racing the writer never sees one pass's position with another's motion
4208    /// number: every pass writes a position derived from its number.
4209    #[test]
4210    fn a_read_racing_the_writer_is_one_pass() {
4211        let share = Arc::new(PointerShare::default());
4212        let writer = {
4213            let share = share.clone();
4214            std::thread::spawn(move || {
4215                for n in 1..200_000u64 {
4216                    share.publish(at(n as f64, (n % 4096) as f64, n));
4217                }
4218            })
4219        };
4220        let mut seen = 0;
4221        while !writer.is_finished() || seen == 0 {
4222            if let Some(p) = share.read() {
4223                assert_eq!(p.x, f64::from(p.motion as f32));
4224                assert_eq!(p.y, (p.motion % 4096) as f64);
4225                seen += 1;
4226            }
4227        }
4228        writer.join().unwrap();
4229    }
4230}