use std::cell::RefCell;
use std::collections::HashSet;
use std::os::fd::OwnedFd;
use std::path::PathBuf;
use std::process::Stdio;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use std::{cmp, env, mem};

use _decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
use _server_decoration::server::org_kde_kwin_server_decoration::{
    Mode as KdeMode, OrgKdeKwinServerDecoration,
};
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as ManagerMode;
use calloop::Mode;
use forge_ipc::{Keysym, Orientation};
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::input::KeyState;
use smithay::backend::renderer::ImportDma;
use smithay::desktop::{
    PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy,
};
use smithay::input::keyboard::xkb::ModMask;
use smithay::input::keyboard::{FilterResult, KeyboardHandle, Keycode, LedState, XkbConfig};
use smithay::input::pointer::{CursorImageStatus, CursorImageSurfaceData, Focus};
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::reexports::calloop::generic::{Generic, NoIoDrop};
use smithay::reexports::calloop::signals::{Signal, Signals};
use smithay::reexports::calloop::{
    Interest, LoopHandle, Mode as TriggerMode, PostAction, RegistrationToken,
};
use smithay::reexports::wayland_protocols::xdg::decoration as _decoration;
use smithay::reexports::wayland_protocols::ext::session_lock::v1::server::ext_session_lock_v1::ExtSessionLockV1;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::reexports::wayland_server::{Client, Display, DisplayHandle, Resource, WEnum};
use smithay::utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER};
use smithay::wayland::alpha_modifier::AlphaModifierState;
use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::compositor;
use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState};
use smithay::wayland::cursor_shape::CursorShapeManagerState;
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
use smithay::wayland::foreign_toplevel_list::{
    ForeignToplevelListHandler, ForeignToplevelListState,
};
use smithay::wayland::fractional_scale::{
    self, FractionalScaleHandler, FractionalScaleManagerState,
};
use smithay::wayland::idle_inhibit::{IdleInhibitHandler, IdleInhibitManagerState};
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
use smithay::wayland::input_method::{
    InputMethodHandler, InputMethodManagerState, PopupSurface as ImeSurface,
};
use smithay::wayland::keyboard_shortcuts_inhibit::{
    KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
};
use smithay::wayland::output::{OutputHandler, OutputManagerState};
use smithay::wayland::pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState};
use smithay::wayland::pointer_gestures::PointerGesturesState;
use smithay::wayland::presentation::PresentationState;
use smithay::wayland::relative_pointer::RelativePointerManagerState;
use smithay::wayland::security_context::{
    SecurityContext, SecurityContextHandler, SecurityContextListenerSource, SecurityContextState,
};
use smithay::wayland::selection::data_device::{
    self, DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler,
};
use smithay::wayland::selection::primary_selection::{
    self, PrimarySelectionHandler, PrimarySelectionState,
};
use smithay::wayland::selection::wlr_data_control::{DataControlHandler, DataControlState};
use smithay::wayland::selection::{SelectionHandler, SelectionSource, SelectionTarget};
use smithay::wayland::session_lock::{
    LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker,
};
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
use smithay::wayland::shell::wlr_layer::{
    Layer, LayerSurface, WlrLayerShellHandler, WlrLayerShellState,
};
use smithay::wayland::shell::xdg::decoration::{XdgDecorationHandler, XdgDecorationState};
use smithay::wayland::shell::xdg::{
    Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
    XdgToplevelSurfaceRoleAttributes,
};
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
use smithay::wayland::socket::ListeningSocketSource;
use smithay::wayland::tablet_manager::TabletManagerState;
use smithay::wayland::text_input::{TextInputManagerState, TextInputSeat};
use smithay::wayland::viewporter::ViewporterState;
use smithay::wayland::virtual_keyboard::{VirtualKeyboardHandler, VirtualKeyboardManagerState};
use smithay::wayland::xdg_activation::{
    XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
};
use tracing::{error, info, warn};

use crate::autobrightness::AutoBrightness;
use crate::backend::Backend;
use crate::cursor::CursorImage;
use crate::config::ForgeConfig;
use crate::config::KeyBinding;
use crate::drawing::ForgeSurfaceData;
use crate::input::{TouchState, REPEAT_DELAY, REPEAT_RATE};
use crate::mpris::MprisState;
use crate::orientation::{Accelerometer, AccelerometerSource};
use crate::osd::Osd;
use crate::output::Canvas;
use crate::polkit::PolkitAgent;
use crate::portal::PortalState;
use crate::protocols::export_dmabuf::{ExportDmabufCapture, ExportDmabufHandler};
use crate::protocols::foreign_toplevel::{ForeignToplevelHandler, ForeignToplevelState};
use crate::protocols::image_copy_capture::{ImageCopyCapture, ImageCopyCaptureHandler};
use crate::protocols::output_power::{OutputPowerHandler, OutputPowerManagerState};
use crate::protocols::screencopy::frame::Screencopy;
use crate::protocols::screencopy::{ScreencopyHandler, ScreencopyManagerState};
use crate::sysmon::SystemState;
use crate::text::TextRenderer;
use crate::windows::surface::Surface;
use crate::windows::window::DecorationPolicy;
use crate::windows::Windows;
use crate::{daemon, ipc_server, trace_error};

const ACTIVATION_TIMEOUT: Duration = Duration::from_secs(10);
const INIT_SCRIPT: &str = "initrc";
const FRAME_HISTORY_SIZE: usize = 16;
const PREDICTION_MARGIN_PCT: u64 = 150;

mod handlers;
mod session;
mod capture;
mod swipes;
mod osk;
mod frame;
mod init;
use frame::FramePacer;
pub use swipes::{BottomSwipe, SwipeContext, TopbarSwipe, TopbarSwipeMsg};

pub struct Forge {
    pub event_loop: LoopHandle<'static, Self>,
    pub display_handle: DisplayHandle,
    pub seat_name: String,
    pub windows: Windows,
    pub seat: Seat<Self>,
    pub terminated: bool,
    pub init_done: bool,
    pub backend: Backend,
    /// Output snapshot served over org.gnome.Mutter.DisplayConfig;
    /// refreshed via [`Forge::update_display_state`].
    pub display_state: std::sync::Arc<crate::display_config_dbus::SharedDisplayState>,

    screencast: Option<std::process::Child>,
    /// PipeWire connection for portal screencast streams. `None` when no user
    /// daemon is reachable; the portal then degrades to denying ScreenCast.
    pub pipewire: Option<crate::pw::PipeWire>,
    pub exec_override: Option<PathBuf>,

    pub popups: PopupManager,

    protocol: ProtocolState,
    pub session: SessionState,
    render: RenderState,
    pub input: InputState,
    pub shell: ShellState,

    accel_token: RegistrationToken,

    last_focus: Option<WlSurface>,

    pub xwm: Option<smithay::xwayland::xwm::X11Wm>,
}

/// Wayland protocol globals grouped together, out of the main `Forge` fields.
pub struct ProtocolState {
    pub compositor_state: CompositorState,
    pub xdg_shell_state: XdgShellState,
    pub shm_state: ShmState,
    pub seat_state: SeatState<Forge>,
    pub data_device_state: DataDeviceState,
    pub primary_selection_state: PrimarySelectionState,
    pub data_control_state: DataControlState,
    pub layer_shell_state: WlrLayerShellState,
    pub xdg_activation_state: XdgActivationState,
    pub kde_decoration_state: KdeDecorationState,
    pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
    pub lock_state: SessionLockManagerState,
    pub idle_notifier_state: IdleNotifierState<Forge>,
    pub dmabuf_state: DmabufState,
    pub foreign_toplevel_state: ForeignToplevelState,
    pub ext_foreign_toplevel_list_state: ForeignToplevelListState,
    pub output_power_state: OutputPowerManagerState,
    pub session_manager_state: crate::protocols::session_management::SessionManagerState,
    pub xwayland_shell_state: smithay::wayland::xwayland_shell::XWaylandShellState,
}

/// Session lifecycle: lock, greeter, idle inhibition, power and OSK state.
pub struct SessionState {
    pub display_on: bool,
    /// Whether the display was on when the machine went to sleep; a
    /// display that was already off stays off on resume.
    pub wake_display: bool,
    pub greeter_mode: bool,
    pub kiosk: bool,
    pub locker: Option<SessionLocker>,
    pub active_lock: Option<ExtSessionLockV1>,
    pub idle_inhibitors: Vec<WlSurface>,
    pub osk_visible: bool,
    pub last_ti_active: bool,
    pub has_keyboard: bool,
    pub power_key_pressed_at: Option<Instant>,
    pub power_key_inhibitor: Option<OwnedFd>,
    pub auto_brightness: AutoBrightness,
}

/// Rendering-side state: text rasteriser, frame pacer, stall flag, clipboard.
pub struct RenderState {
    pub text_renderer: TextRenderer,
    pub frame_pacer: FramePacer,
    pub stalled: bool,
    pub clipboard_png: Option<Arc<[u8]>>,
}

/// Input plumbing: keys, pointer/touch, gesture recognisers and swipe state.
pub struct InputState {
    pub key_bindings: Vec<KeyBinding>,
    pub active_keys: HashSet<Keysym>,
    pub touch_state: TouchState,
    pub draw_cursor: bool,
    pub cursor_position: Option<Point<f64, Logical>>,
    pub cursor_status: CursorImageStatus,
    pub gesture_recognizer: crate::gesture_recognizer::GestureRecognizer,
    pub corner_recognizer: crate::corner_action::CornerRecognizer,
    pub edge_recognizer: crate::edge_action::BottomEdgeRecognizer,
    pub ssd_last_press: Option<(Instant, Point<f64, Logical>)>,
    pub armed_ssd: Option<crate::input::ArmedSsdButton>,
    pub topbar_swipe: Option<TopbarSwipe>,
    pub bottom_swipe: Option<BottomSwipe>,
    pub dwell_timer: Option<RegistrationToken>,
    pub dwell_anchor: Option<f32>,
    pub xkb_layouts: Vec<String>,
    pub current_layout: usize,
}

/// Shell surfaces and their backing state: panels, overlays, media, auth,
/// system snapshot and the launcher query bridge.
pub struct ShellState {
    pub osd: Osd,
    pub topbar: crate::topbar::Topbar,
    pub app_switcher: crate::app_switcher::AppSwitcher,
    pub mpris: MprisState,
    pub polkit: PolkitAgent,
    pub portal: PortalState,
    pub screenshot: crate::screenshot::ScreenshotSelector,
    pub sys_state: SystemState,
    pub sys_state_rx: Option<mpsc::Receiver<SystemState>>,
    pub pending_launcher_query: Option<(String, Instant)>,
}

impl Forge {
    /// Push the primary output's current mode / scale / connector into the
    /// shared snapshot served over org.gnome.Mutter.DisplayConfig.
    pub fn update_display_state(&self) {
        let canvas = self.windows.canvas();
        let res = canvas.physical_resolution();
        self.display_state.update(
            &self.windows.output_name(),
            res.w.max(0) as u32,
            res.h.max(0) as u32,
            canvas.refresh_mhz().max(0) as u32,
            canvas.scale(),
        );
    }

    pub(crate) fn set_focus(&mut self, surface: Option<WlSurface>) {
        // Focus moving to anything but a layer surface means the user left
        // the launcher flow (clicked a toplevel, launcher closed); the
        // launcher itself grabbing the keyboard must not disarm the pending
        // query — during its slide-in it cannot receive keys yet.
        if !surface.as_ref().is_some_and(|s| self.windows.surface_is_layer(s)) {
            self.shell.pending_launcher_query = None;
        }
        if let Some(kb) = self.seat.get_keyboard() {
            kb.set_focus(self, surface, SERIAL_COUNTER.next_serial());
        }
    }

    pub fn last_focus(&self) -> Option<&WlSurface> {
        self.last_focus.as_ref()
    }

    /// Keyboard focus follows modality: while a modal dialog (polkit auth or
    /// portal consent) is up, the focused client loses the keyboard — the
    /// leave resets its pressed-key state, so a key held across the dialog's
    /// appearance cannot autorepeat forever — and gets it back once the last
    /// dialog resolves.
    pub(crate) fn sync_modal_focus(&mut self) {
        if self.shell.polkit.visible || self.windows.confirm.visible {
            self.set_focus(None);
        } else {
            let focus = self.windows.focus().map(|(surface, _)| surface);
            self.set_focus(focus);
        }
    }

    /// Whether the modal polkit auth dialog is up.
    pub fn auth_active(&self) -> bool {
        self.shell.polkit.visible
    }

    /// Enter or leave single-app kiosk mode (silences the active corners).
    pub fn set_kiosk(&mut self, enabled: bool) {
        self.session.kiosk = enabled;
    }

    pub fn dmabuf_global_target(&mut self) -> (&mut DmabufState, &DisplayHandle) {
        (&mut self.protocol.dmabuf_state, &self.display_handle)
    }

    /// Read-only access to the frame pacer for presentation scheduling.
    pub fn frame_pacer(&self) -> &FramePacer {
        &self.render.frame_pacer
    }

    /// Route a semantic UI intent, centralising the redraw bookkeeping the
    /// input handlers would otherwise each repeat.
    pub fn dispatch(&mut self, intent: crate::intent::UiIntent) {
        use crate::intent::UiIntent;
        match intent {
            UiIntent::Authenticate => self.shell.polkit.begin_authenticate(),
            UiIntent::CancelAuthentication => {
                self.shell.polkit.cancel();
                self.sync_modal_focus();
            }
            UiIntent::AuthTextInput(c) => self.shell.polkit.push_char(c),
            UiIntent::AuthBackspace => self.shell.polkit.pop_char(),
            UiIntent::PortalConsent(allow) => {
                self.resolve_confirm(allow);
                return;
            }
        }
        self.windows.set_dirty();
        self.request_redraw();
    }

}


#[derive(Default)]
struct ClientState {
    compositor_state: CompositorClientState,
}

impl ClientData for ClientState {
    fn initialized(&self, _: ClientId) {}
    fn disconnected(&self, _: ClientId, _: DisconnectReason) {}
}

/// Check if a command exists in PATH.
fn which_exists(name: &str) -> bool {
    env::var_os("PATH")
        .map(|paths| env::split_paths(&paths).any(|dir| dir.join(name).is_file()))
        .unwrap_or(false)
}

/// Write compositor-owned selection bytes (a screenshot PNG) to a requesting
/// client's pipe on a detached thread. A multi-megabyte PNG overruns the pipe
/// buffer, so a blocking write on the render loop would stall the compositor
/// until the client drains it.
fn serve_clipboard_bytes(fd: OwnedFd, bytes: Arc<[u8]>) {
    std::thread::spawn(move || {
        use std::io::Write;
        use std::os::fd::AsRawFd;
        // The receiver may hand us a write end with O_NONBLOCK set; write_all
        // would then fail with WouldBlock once the 64 KiB pipe buffer fills and
        // silently truncate the payload. Blocking is fine on this thread.
        unsafe {
            let flags = libc::fcntl(fd.as_raw_fd(), libc::F_GETFL);
            if flags >= 0 {
                libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, flags & !libc::O_NONBLOCK);
            }
        }
        let mut file = std::fs::File::from(fd);
        if let Err(err) = file.write_all(&bytes) {
            warn!("clipboard write failed: {err}");
        }
    });
}

// --- XWayland support ---

mod xwayland_impl {
    use smithay::utils::{Logical, Rectangle};
    use smithay::wayland::selection::data_device::{
        clear_data_device_selection, current_data_device_selection_userdata,
        request_data_device_client_selection, set_data_device_selection,
    };
    use smithay::wayland::selection::primary_selection::{
        clear_primary_selection, current_primary_selection_userdata,
        request_primary_client_selection, set_primary_selection,
    };
    use smithay::wayland::selection::SelectionTarget;
    use smithay::xwayland::xwm::X11Window;
    use smithay::xwayland::xwm::{Reorder, ResizeEdge, X11Surface, X11Wm, XwmHandler, XwmId};
    use std::os::fd::OwnedFd;
    use tracing::{debug, error, warn};

    use super::Forge;

    impl XwmHandler for Forge {
        fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
            self.xwm.as_mut().expect("xwm not started")
        }

        fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}

        fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}

        fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
            window
                .set_mapped(true)
                .unwrap_or_else(|e| warn!("X11 map failed: {e}"));
            let mut geo = window.geometry();
            if crate::x11_window::wants_ssd(&window) {
                let bar_h = crate::ssd::SsdDecoration::bar_height();
                let min_y = self.windows.available_top_y() + bar_h;
                if geo.loc.y < min_y {
                    geo.loc.y = min_y;
                }
            }
            window
                .configure(geo)
                .unwrap_or_else(|e| warn!("X11 configure failed: {e}"));
            debug!("X11 window mapped: {:?}", window.title());
            let wl = window.wl_surface();
            let title = window.title();
            let app_id = {
                let c = window.class();
                if c.is_empty() {
                    window.instance()
                } else {
                    c
                }
            };
            let handle = self
                .protocol
                .ext_foreign_toplevel_list_state
                .new_toplevel::<Self>(&title, &app_id);
            handle.send_done();
            self.windows.x11_windows.map(window, Some(handle));
            if let Some(wl) = wl {
                self.windows.z_promote(&wl);
            }
            self.request_redraw();
        }

        fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
            debug!("X11 override-redirect mapped: {:?}", window.title());
            let wl = window.wl_surface();
            self.windows.x11_windows.map(window, None);
            if let Some(wl) = wl {
                self.windows.z_promote(&wl);
            }
            self.request_redraw();
        }

        fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
            debug!("X11 window unmapped: {:?}", window.title());
            if let Some(wl) = window.wl_surface() {
                self.windows.z_remove(&wl);
            }
            self.windows.x11_windows.unmap(&window);
            window
                .set_mapped(false)
                .unwrap_or_else(|e| warn!("X11 unmap failed: {e}"));
            self.windows.set_dirty();
        }

        fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
            if let Some(wl) = window.wl_surface() {
                self.windows.z_remove(&wl);
            }
            self.windows.x11_windows.unmap(&window);
            self.windows.set_dirty();
        }

        fn configure_request(
            &mut self,
            _xwm: XwmId,
            window: X11Surface,
            x: Option<i32>,
            y: Option<i32>,
            w: Option<u32>,
            h: Option<u32>,
            _reorder: Option<Reorder>,
        ) {
            let mut geo = window.geometry();
            if let Some(x) = x {
                geo.loc.x = x;
            }
            if let Some(y) = y {
                geo.loc.y = y;
            }
            if let Some(w) = w {
                geo.size.w = w as i32;
            }
            if let Some(h) = h {
                geo.size.h = h as i32;
            }
            if crate::x11_window::wants_ssd(&window) {
                let bar_h = crate::ssd::SsdDecoration::bar_height();
                let min_y = self.windows.available_top_y() + bar_h;
                if geo.loc.y < min_y {
                    geo.loc.y = min_y;
                }
            }
            window
                .configure(geo)
                .unwrap_or_else(|e| warn!("X11 configure failed: {e}"));
        }

        fn configure_notify(
            &mut self,
            _xwm: XwmId,
            _window: X11Surface,
            _geometry: Rectangle<i32, Logical>,
            _above: Option<X11Window>,
        ) {
        }

        fn resize_request(
            &mut self,
            _xwm: XwmId,
            window: X11Surface,
            _button: u32,
            resize_edge: ResizeEdge,
        ) {
            use crate::ssd::ResizeEdge as Edge;
            let edge = match resize_edge {
                ResizeEdge::Top => Edge::N,
                ResizeEdge::Bottom => Edge::S,
                ResizeEdge::Left => Edge::W,
                ResizeEdge::Right => Edge::E,
                ResizeEdge::TopLeft => Edge::NW,
                ResizeEdge::TopRight => Edge::NE,
                ResizeEdge::BottomLeft => Edge::SW,
                ResizeEdge::BottomRight => Edge::SE,
            };
            let pointer = self.input.touch_state.cursor_pos();
            self.windows.start_resize_drag_for_x11(&window, edge, pointer);
        }

        fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
            let pointer = self.input.touch_state.cursor_pos();
            self.windows.start_move_drag_for_x11(&window, pointer);
        }

        fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
            self.windows.set_x11_maximized(&window, true);
            self.request_redraw();
        }

        fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
            let Some(wl) = window.wl_surface() else { return };
            self.windows.x11_windows.set_minimized(&wl, true);
            self.windows.set_dirty();
            self.request_redraw();
        }

        fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
            let Some(wl) = window.wl_surface() else { return };
            self.windows.x11_windows.set_minimized(&wl, false);
            self.windows.set_dirty();
            self.request_redraw();
        }

        fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
            self.windows.set_x11_maximized(&window, false);
            self.request_redraw();
        }

        fn property_notify(
            &mut self,
            _xwm: XwmId,
            window: X11Surface,
            property: smithay::xwayland::xwm::WmWindowProperty,
        ) {
            use smithay::xwayland::xwm::WmWindowProperty;
            match property {
                WmWindowProperty::Title => {
                    self.windows
                        .x11_windows
                        .update_title(&window, &window.title());
                }
                WmWindowProperty::Class => {
                    let c = window.class();
                    let id = if c.is_empty() { window.instance() } else { c };
                    self.windows.x11_windows.update_app_id(&window, &id);
                }
                _ => {}
            }
        }

        fn allow_selection_access(&mut self, _xwm: XwmId, _selection: SelectionTarget) -> bool {
            // Not focus-gated like anvil: clipboard bridges (xfreerdp) read on owner-change, unfocused.
            true
        }

        fn send_selection(
            &mut self,
            _xwm: XwmId,
            selection: SelectionTarget,
            mime_type: String,
            fd: OwnedFd,
        ) {
            if selection == SelectionTarget::Clipboard && mime_type == "image/png" {
                if let Some(bytes) = self.render.clipboard_png.clone() {
                    super::serve_clipboard_bytes(fd, bytes);
                    return;
                }
            }
            match selection {
                SelectionTarget::Clipboard => {
                    if let Err(err) =
                        request_data_device_client_selection(&self.seat, mime_type, fd)
                    {
                        error!("failed to request Wayland clipboard for Xwayland: {err}");
                    }
                }
                SelectionTarget::Primary => {
                    if let Err(err) = request_primary_client_selection(&self.seat, mime_type, fd) {
                        error!("failed to request Wayland primary selection for Xwayland: {err}");
                    }
                }
            }
        }

        fn new_selection(
            &mut self,
            _xwm: XwmId,
            selection: SelectionTarget,
            mime_types: Vec<String>,
        ) {
            // An X11 client took the clipboard, so drop any screenshot we held.
            if selection == SelectionTarget::Clipboard {
                self.render.clipboard_png = None;
            }
            match selection {
                SelectionTarget::Clipboard => {
                    set_data_device_selection(&self.display_handle, &self.seat, mime_types, ())
                }
                SelectionTarget::Primary => {
                    set_primary_selection(&self.display_handle, &self.seat, mime_types, ())
                }
            }
        }

        fn cleared_selection(&mut self, _xwm: XwmId, selection: SelectionTarget) {
            match selection {
                SelectionTarget::Clipboard => {
                    if current_data_device_selection_userdata(&self.seat).is_some() {
                        clear_data_device_selection(&self.display_handle, &self.seat)
                    }
                }
                SelectionTarget::Primary => {
                    if current_primary_selection_userdata(&self.seat).is_some() {
                        clear_primary_selection(&self.display_handle, &self.seat)
                    }
                }
            }
        }
    }

    impl smithay::wayland::xwayland_shell::XWaylandShellHandler for Forge {
        fn xwayland_shell_state(
            &mut self,
        ) -> &mut smithay::wayland::xwayland_shell::XWaylandShellState {
            &mut self.protocol.xwayland_shell_state
        }

        fn surface_associated(
            &mut self,
            _xwm: smithay::xwayland::xwm::XwmId,
            wl_surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
            _surface: smithay::xwayland::X11Surface,
        ) {
            self.windows.z_promote(&wl_surface);
            self.request_redraw();
        }
    }

    impl smithay::input::dnd::DndGrabHandler for Forge {}

    smithay::delegate_xwayland_shell!(Forge);
}
