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