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