use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_long, c_ulong};
use std::slice;
use std::sync::{Arc, Mutex};

use dpi::{PhysicalPosition, PhysicalSize};
use tracing::warn;
use winit_common::xkb::{self, Context, XkbState};
use winit_core::application::ApplicationHandler;
use winit_core::event::{
    ButtonSource, DeviceEvent, DeviceId, ElementState, FingerId, Ime, MouseButton,
    MouseScrollDelta, PointerKind, PointerSource, RawKeyEvent, SurfaceSizeWriter, TouchPhase,
    WindowEvent,
};
use winit_core::event_loop::DndAction;
use winit_core::keyboard::ModifiersState;
use winit_core::window::WindowId;
use x11_dl::xinput2::{
    self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent,
    XILeaveEvent, XIModifierState, XIRawEvent,
};
use x11_dl::xlib::{
    self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent,
    XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent,
    XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec,
};
use x11rb::protocol::sync::{ConnectionExt, Int64};
use x11rb::protocol::xinput;
use x11rb::protocol::xkb::ID as XkbId;
use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask};
use x11rb::x11_utils::{ExtensionInformation, Serialize};
use xkbcommon_dl::xkb_mod_mask_t;

use crate::atoms::*;
use crate::dnd::{DndState, SelectionType};
use crate::event_loop::{
    ALL_DEVICES, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, DeviceType,
    ScrollOrientation, mkdid, mkwid,
};
use crate::ime::{ImeEvent, ImeEventReceiver, ImeReceiver, ImeRequest};
use crate::util;
use crate::util::cookie::GenericEventCookie;
use crate::window::UnownedWindow;

/// The maximum amount of X modifiers to replay.
pub const MAX_MOD_REPLAY_LEN: usize = 32;

/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
const KEYCODE_OFFSET: u8 = 8;

#[derive(Debug)]
pub struct EventProcessor {
    pub ime_receiver: ImeReceiver,
    pub ime_event_receiver: ImeEventReceiver,
    pub randr_event_offset: u8,
    pub devices: RefCell<HashMap<DeviceId, Device>>,
    pub xi2ext: ExtensionInformation,
    pub xkbext: ExtensionInformation,
    pub target: ActiveEventLoop,
    pub xkb_context: Context,
    // Number of touch events currently in progress
    pub num_touch: u32,
    // This is the last pressed key that is repeatable (if it hasn't been
    // released).
    //
    // Used to detect key repeats.
    pub held_key_press: Option<u32>,
    pub first_touch: Option<u32>,
    // Currently focused window belonging to this process
    pub active_window: Option<xproto::Window>,
    /// Latest modifiers we've sent for the user to trigger change in event.
    pub modifiers: Cell<ModifiersState>,
    // Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking
    // since they are not unique and could be duplicated in case of sequence of key events is
    // delivered at near the same time.
    pub xfiltered_modifiers: VecDeque<u8>,
    pub xmodmap: util::ModifierKeymap,
    pub is_composing: bool,
}

impl EventProcessor {
    pub(crate) fn process_event(&mut self, xev: &mut XEvent, app: &mut dyn ApplicationHandler) {
        self.process_xevent(xev, app);

        // Handle IME requests.
        while let Ok(request) = self.ime_receiver.try_recv() {
            let ime = match self.target.ime.as_mut() {
                Some(ime) => ime,
                None => continue,
            };

            let ime = ime.get_mut();
            match request {
                ImeRequest::Area(window_id, x, y, w, h) => {
                    ime.send_xim_area(window_id, x, y, w, h);
                },
                ImeRequest::Allow(window_id, allowed) => {
                    ime.set_ime_allowed(window_id, allowed);
                },
            }
        }

        // Drain IME events.
        while let Ok((window, event)) = self.ime_event_receiver.try_recv() {
            let window_id = mkwid(window as xproto::Window);
            let event = match event {
                ImeEvent::Enabled => WindowEvent::Ime(Ime::Enabled),
                ImeEvent::Start => {
                    self.is_composing = true;
                    WindowEvent::Ime(Ime::Preedit("".to_owned(), None))
                },
                ImeEvent::Update(text, position) if self.is_composing => {
                    WindowEvent::Ime(Ime::Preedit(text, Some((position, position))))
                },
                ImeEvent::End => {
                    self.is_composing = false;
                    // Issue empty preedit on `Done`.
                    WindowEvent::Ime(Ime::Preedit(String::new(), None))
                },
                ImeEvent::Disabled => {
                    self.is_composing = false;
                    WindowEvent::Ime(Ime::Disabled)
                },
                _ => continue,
            };

            app.window_event(&self.target, window_id, event);
        }
    }

    /// XFilterEvent tells us when an event has been discarded by the input method.
    /// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
    /// along with an extra copy of the KeyRelease events. This also prevents backspace and
    /// arrow keys from being detected twice.
    #[must_use]
    fn filter_event(&mut self, xev: &mut XEvent) -> bool {
        unsafe {
            (self.target.xconn.xlib.XFilterEvent)(xev, {
                let xev: &XAnyEvent = xev.as_ref();
                xev.window
            }) == xlib::True
        }
    }

    fn process_xevent(&mut self, xev: &mut XEvent, app: &mut dyn ApplicationHandler) {
        let event_type = xev.get_type();

        // If we have IME disabled, don't try to `filter_event`, since only IME can consume them
        // and forward back. This is not desired for e.g. games since some IMEs may delay the input
        // and game can toggle IME back when e.g. typing into some field where latency won't really
        // matter.
        let filtered = if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
            let ime = self.target.ime.as_ref();
            let window = self.active_window.map(|window| window as XWindow);
            let forward_to_ime = ime
                .and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window)))
                .unwrap_or(false);

            let filtered = forward_to_ime && self.filter_event(xev);
            if filtered {
                let xev: &XKeyEvent = xev.as_ref();
                if self.xmodmap.is_modifier(xev.keycode as u8) {
                    // Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
                    // when the modifiers are consumed entirely.
                    if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN {
                        self.xfiltered_modifiers.pop_back();
                    }
                    self.xfiltered_modifiers.push_front(xev.keycode as u8);
                }
            }

            filtered
        } else {
            self.filter_event(xev)
        };

        // Don't process event if it was filtered.
        if filtered {
            return;
        }

        match event_type {
            xlib::ClientMessage => self.client_message(xev.as_ref(), app),
            xlib::SelectionNotify => self.selection_notify(xev.as_ref(), app),
            xlib::ConfigureNotify => self.configure_notify(xev.as_ref(), app),
            xlib::ReparentNotify => self.reparent_notify(xev.as_ref()),
            xlib::MapNotify => self.map_notify(xev.as_ref(), app),
            xlib::DestroyNotify => self.destroy_notify(xev.as_ref(), app),
            xlib::PropertyNotify => self.property_notify(xev.as_ref(), app),
            xlib::VisibilityNotify => self.visibility_notify(xev.as_ref(), app),
            xlib::Expose => self.expose(xev.as_ref()),
            // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events.
            ty @ xlib::KeyPress | ty @ xlib::KeyRelease => {
                let state = if ty == xlib::KeyPress {
                    ElementState::Pressed
                } else {
                    ElementState::Released
                };

                self.xinput_key_input(xev.as_mut(), state, app);
            },
            xlib::GenericEvent => {
                let xev: GenericEventCookie =
                    match GenericEventCookie::from_event(self.target.xconn.clone(), *xev) {
                        Some(xev) if xev.extension() == self.xi2ext.major_opcode => xev,
                        _ => return,
                    };

                let evtype = xev.evtype();

                match evtype {
                    ty @ xinput2::XI_ButtonPress | ty @ xinput2::XI_ButtonRelease => {
                        let state = if ty == xinput2::XI_ButtonPress {
                            ElementState::Pressed
                        } else {
                            ElementState::Released
                        };

                        let xev: &XIDeviceEvent = unsafe { xev.as_event() };
                        self.update_mods_from_xinput2_event(&xev.mods, &xev.group, false, app);
                        self.xinput2_button_input(xev, state, app);
                    },
                    xinput2::XI_Motion => {
                        let xev: &XIDeviceEvent = unsafe { xev.as_event() };
                        self.update_mods_from_xinput2_event(&xev.mods, &xev.group, false, app);
                        self.xinput2_mouse_motion(xev, app);
                    },
                    xinput2::XI_Enter => {
                        let xev: &XIEnterEvent = unsafe { xev.as_event() };
                        self.xinput2_mouse_enter(xev, app);
                    },
                    xinput2::XI_Leave => {
                        let xev: &XILeaveEvent = unsafe { xev.as_event() };
                        self.update_mods_from_xinput2_event(&xev.mods, &xev.group, false, app);
                        self.xinput2_mouse_left(xev, app);
                    },
                    xinput2::XI_FocusIn => {
                        let xev: &XIFocusInEvent = unsafe { xev.as_event() };
                        self.xinput2_focused(xev, app);
                    },
                    xinput2::XI_FocusOut => {
                        let xev: &XIFocusOutEvent = unsafe { xev.as_event() };
                        self.xinput2_unfocused(xev, app);
                    },
                    xinput2::XI_TouchBegin | xinput2::XI_TouchUpdate | xinput2::XI_TouchEnd => {
                        let xev: &XIDeviceEvent = unsafe { xev.as_event() };
                        self.xinput2_touch(xev, evtype, app);
                    },
                    xinput2::XI_RawButtonPress | xinput2::XI_RawButtonRelease => {
                        let state = match evtype {
                            xinput2::XI_RawButtonPress => ElementState::Pressed,
                            xinput2::XI_RawButtonRelease => ElementState::Released,
                            _ => unreachable!(),
                        };

                        let xev: &XIRawEvent = unsafe { xev.as_event() };
                        self.xinput2_raw_button_input(xev, state, app);
                    },
                    xinput2::XI_RawMotion => {
                        let xev: &XIRawEvent = unsafe { xev.as_event() };
                        self.xinput2_raw_mouse_motion(xev, app);
                    },
                    xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => {
                        let state = match evtype {
                            xinput2::XI_RawKeyPress => ElementState::Pressed,
                            xinput2::XI_RawKeyRelease => ElementState::Released,
                            _ => unreachable!(),
                        };

                        let xev: &xinput2::XIRawEvent = unsafe { xev.as_event() };
                        self.xinput2_raw_key_input(xev, state, app);
                    },

                    xinput2::XI_HierarchyChanged => {
                        let xev: &XIHierarchyEvent = unsafe { xev.as_event() };
                        self.xinput2_hierarchy_changed(xev);
                    },
                    _ => {},
                }
            },
            _ => {
                if event_type == self.xkbext.first_event as _ {
                    let xev: &XkbAnyEvent = unsafe { &*(xev as *const _ as *const XkbAnyEvent) };
                    self.xkb_event(xev, app);
                }
                if event_type == self.randr_event_offset as c_int {
                    self.process_dpi_change(app);
                }
            },
        }
    }

    pub fn poll(&self) -> bool {
        unsafe { (self.target.xconn.xlib.XPending)(self.target.xconn.display) != 0 }
    }

    pub fn poll_one_event<'a>(
        &mut self,
        event_ptr: &'a mut MaybeUninit<XEvent>,
    ) -> Option<&'a mut XEvent> {
        // This function is used to poll and remove a single event
        // from the Xlib event queue in a non-blocking, atomic way.
        // XCheckIfEvent is non-blocking and removes events from queue.
        // XNextEvent can't be used because it blocks while holding the
        // global Xlib mutex.
        // XPeekEvent does not remove events from the queue.
        unsafe extern "C" fn predicate(
            _display: *mut XDisplay,
            _event: *mut XEvent,
            _filter: *mut c_char,
        ) -> c_int {
            1
        }

        let event_initialized = unsafe {
            (self.target.xconn.xlib.XCheckIfEvent)(
                self.target.xconn.display,
                event_ptr.as_mut_ptr(),
                Some(predicate),
                std::ptr::null_mut(),
            ) != 0
        };

        event_initialized.then(|| unsafe { event_ptr.assume_init_mut() })
    }

    pub fn init_device(&self, device: xinput::DeviceId) {
        let mut devices = self.devices.borrow_mut();
        if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) {
            let atoms = self.target.x_connection().atoms();

            for info in info.iter() {
                devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info, atoms));
            }
        }
    }

    pub fn with_window<F, Ret>(&self, window_id: xproto::Window, callback: F) -> Option<Ret>
    where
        F: Fn(&Arc<UnownedWindow>) -> Ret,
    {
        let mut deleted = false;
        let window_id = WindowId::from_raw(window_id as _);
        let result = self
            .target
            .windows
            .borrow()
            .get(&window_id)
            .and_then(|window| {
                let arc = window.upgrade();
                deleted = arc.is_none();
                arc
            })
            .map(|window| callback(&window));

        if deleted {
            // Garbage collection
            self.target.windows.borrow_mut().remove(&window_id);
        }

        result
    }

    fn client_message(&mut self, xev: &XClientMessageEvent, app: &mut dyn ApplicationHandler) {
        let atoms = self.target.xconn.atoms();

        let window = xev.window as xproto::Window;
        let window_id = mkwid(window);

        if xev.data.get_long(0) as xproto::Atom == self.target.wm_delete_window {
            app.window_event(&self.target, window_id, WindowEvent::CloseRequested);
            return;
        }

        if xev.data.get_long(0) as xproto::Atom == self.target.net_wm_ping {
            let client_msg = xproto::ClientMessageEvent {
                response_type: xproto::CLIENT_MESSAGE_EVENT,
                format: xev.format as _,
                sequence: xev.serial as _,
                window: self.target.root,
                type_: xev.message_type as _,
                data: xproto::ClientMessageData::from({
                    let [a, b, c, d, e]: [c_long; 5] = xev.data.as_longs().try_into().unwrap();
                    [a as u32, b as u32, c as u32, d as u32, e as u32]
                }),
            };

            self.target
                .xconn
                .xcb_connection()
                .send_event(
                    false,
                    self.target.root,
                    xproto::EventMask::SUBSTRUCTURE_NOTIFY
                        | xproto::EventMask::SUBSTRUCTURE_REDIRECT,
                    client_msg.serialize(),
                )
                .expect_then_ignore_error("Failed to send `ClientMessage` event.");
            return;
        }

        if xev.data.get_long(0) as xproto::Atom == self.target.net_wm_sync_request {
            let sync_counter_id = match self
                .with_window(xev.window as xproto::Window, |window| window.sync_counter_id())
            {
                Some(Some(sync_counter_id)) => sync_counter_id.get(),
                _ => return,
            };

            #[cfg(target_pointer_width = "32")]
            let (lo, hi) =
                (bytemuck::cast::<c_long, u32>(xev.data.get_long(2)), xev.data.get_long(3));

            #[cfg(not(target_pointer_width = "32"))]
            let (lo, hi) = (
                (xev.data.get_long(2) & 0xffffffff) as u32,
                bytemuck::cast::<u32, i32>((xev.data.get_long(3) & 0xffffffff) as u32),
            );

            self.target
                .xconn
                .xcb_connection()
                .sync_set_counter(sync_counter_id, Int64 { lo, hi })
                .expect_then_ignore_error("Failed to set XSync counter.");

            return;
        }

        if xev.message_type == atoms[XdndEnter] as c_ulong {
            // Cautiously limit the scope of the `dnd` lock so we don't rely on `app.window_event`
            // never contending the lock.
            let transfer_id = {
                let mut dnd = self.target.dnd.borrow_mut();
                let source_window = xev.data.get_long(0) as xproto::Window;
                let flags = xev.data.get_long(1);

                let version = flags >> 24;

                let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
                let types: Vec<_> = if !has_more_types {
                    [
                        xev.data.get_long(2) as xproto::Atom,
                        xev.data.get_long(3) as xproto::Atom,
                        xev.data.get_long(4) as xproto::Atom,
                    ]
                    .map(|ty_atom| SelectionType::new(atoms, ty_atom))
                    .into_iter()
                    .collect()
                } else if let Ok(more_types) = unsafe { dnd.get_type_list(source_window) } {
                    more_types
                        .into_iter()
                        .map(|ty_atom| SelectionType::new(atoms, ty_atom))
                        .collect()
                } else {
                    Default::default()
                };

                dnd.init_state(version, source_window, window, types.into()).transfer_id
            };

            app.window_event(&self.target, window_id, WindowEvent::DragEntered {
                id: transfer_id,
                position: None,
            });
            return;
        }

        if xev.message_type == atoms[XdndPosition] as c_ulong {
            // This event occurs every time the mouse moves while a file's being dragged
            // over our window. We emit HoveredFile in response; while the macOS backend
            // does that upon a drag entering, XDND doesn't have access to the actual drop
            // data until this event. For parity with other platforms, we only emit
            // `HoveredFile` the first time, though if winit's API is later extended to
            // supply position updates with `HoveredFile` or another event, implementing
            // that here would be trivial.

            let source_window = xev.data.get_long(0) as xproto::Window;

            // https://www.freedesktop.org/wiki/Specifications/XDND/#xdndposition
            // Note that coordinates are in "desktop space", not "window space"
            // (in X11 parlance, they're root window coordinates)
            let packed_coordinates = xev.data.get_long(2);
            let x = (packed_coordinates >> 16) as i16;
            let y = (packed_coordinates & 0xffff) as i16;

            let coords = self
                .target
                .xconn
                .translate_coords(self.target.root, window, x, y)
                .expect("Failed to translate window coordinates");

            // Cautiously limit the scope of the `dnd` lock so we don't rely on `app.window_event`
            // never contending the lock.
            let transfer_id = {
                let dnd = self.target.dnd.borrow();
                let Some(state) = dnd.state() else {
                    return;
                };
                // By our own state flow, `state` should never be `None` at this point.
                let version = state.version;

                let time = if version == 0 {
                    // In version 0, time isn't specified
                    x11rb::CURRENT_TIME
                } else {
                    xev.data.get_long(3) as xproto::Timestamp
                };

                // Log this timestamp.
                self.target.xconn.set_timestamp(time);

                unsafe {
                    dnd.send_status(
                        window,
                        source_window,
                        if state.accepted { DndState::Accepted } else { DndState::Rejected },
                    )
                    .expect("Failed to send `XdndStatus` message.");
                }

                state.transfer_id
            };

            app.window_event(&self.target, window_id, WindowEvent::DragPosition {
                id: transfer_id,
                position: PhysicalPosition::new(coords.dst_x as f64, coords.dst_y as f64),
                // `Copy` is the default. Other actions are possible in X11, but the specification
                // does not properly explain how to implement them (only giving a vague description
                // of `XdndMove`). For simplicity's sake, we simply do not implement non-copy drag
                // on X11.
                // See https://www.freedesktop.org/wiki/Specifications/XDND/
                proposed_action: Some(DndAction::Copy),
            });

            return;
        }

        if xev.message_type == atoms[XdndDrop] as c_ulong {
            let (source_window, transfer_id) = {
                let dnd = self.target.dnd.borrow();
                let Some(state) = dnd.state() else {
                    warn!("Received `XdndDrop` without `XdndEnter`");
                    return;
                };
                let source_window = state.source_window;

                (source_window, state.transfer_id)
            };

            app.window_event(
                &self.target,
                window_id,
                // TODO
                WindowEvent::DragDropped {
                    id: transfer_id,
                    // `Copy` is the default. Other actions are possible in X11, but the
                    // specification does not properly explain how to implement
                    // them (only giving a vague description of `XdndMove`). For
                    // simplicity's sake, we simply do not implement non-copy drag
                    // on X11.
                    // See https://www.freedesktop.org/wiki/Specifications/XDND/
                    proposed_action: Some(DndAction::Copy),
                },
            );

            let mut dnd = self.target.dnd.borrow_mut();

            if let Some(state) =
                dnd.state_mut().filter(|state| !state.pending_fetch_types.is_empty())
            {
                state.finished = Some((window, source_window));
            } else {
                unsafe {
                    dnd.send_finished(window, source_window)
                        .expect("Failed to send `XdndFinished` message.");
                }
            }

            return;
        }

        if xev.message_type == atoms[XdndLeave] as c_ulong {
            let dnd = self.target.dnd.borrow();
            let Some(state) = dnd.state() else {
                return;
            };
            app.window_event(&self.target, window_id, WindowEvent::DragLeft {
                id: state.transfer_id,
            });
        }
    }

    fn selection_notify(&mut self, xev: &XSelectionEvent, app: &mut dyn ApplicationHandler) {
        let atoms = self.target.xconn.atoms();

        let xwindow = xev.requestor as xproto::Window;

        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        // For now, winit only supports selections for drag-and-drop. This should be changed
        // when clipboard support is implemented.
        if xev.property != atoms[XdndSelection] as c_ulong {
            return;
        }

        let (transfer_id, serial, type_) = {
            let Some(state) = self.target.dnd.get_mut().state_mut() else {
                return;
            };

            let Some((serial, type_)) = state.pending_fetch_types.pop_front() else {
                return;
            };

            // Annoyingly, `xproto::Atom` and `x11_dl::Atom` are different on 64-bit
            // but the same on 32-bit, so just casting to `u32` will cause "casting
            // to same type" clippy warnings when compiled as 32-bit.
            #[cfg(target_pointer_width = "32")]
            let target_type = xev.target;
            #[cfg(target_pointer_width = "64")]
            let target_type = xev.target as u32;

            if target_type != type_.atom() {
                let get_name = |atom| {
                    self.target
                        .xconn
                        .xcb_connection()
                        .get_atom_name(atom)
                        .ok()
                        .and_then(|cookie| cookie.reply().ok())
                        .and_then(|reply| String::from_utf8(reply.name).ok())
                };

                let expected_type_name = get_name(type_.atom());
                let found_type_name = get_name(xev.type_ as _);

                let extra_context = match (expected_type_name, found_type_name) {
                    (Some(expected), Some(found)) => {
                        format!(" (expected {expected}, found {found})")
                    },
                    (Some(expected), None) => format!(" (expected {expected})"),
                    (None, Some(found)) => format!(" (found {found})"),
                    (None, None) => "".to_string(),
                };
                warn!(
                    "Received `SelectionNotify` with unexpected type{extra_context}. Continuing, \
                     but this may be a bug."
                );
            }

            (state.transfer_id, serial, type_)
        };

        let value = match self.target.dnd.borrow().read_data(xwindow, type_) {
            Ok(value) => Arc::new(value),
            Err(err) => {
                warn!("Failed to read selection: {err}");
                return;
            },
        };

        let window_id = mkwid(xwindow);

        app.window_event(&self.target, window_id, WindowEvent::DataTransferReceived {
            id: transfer_id,
            serial,
            value,
        });

        let dnd = self.target.dnd.borrow();

        // If we have another fetch pending, request it from the drag source window
        if let Some((window, type_)) = dnd.state().and_then(|state| {
            state
                .pending_fetch_types
                .front()
                .cloned()
                .map(|(_, type_)| (state.target_window, type_))
        }) {
            dnd.convert_selection(window, self.target.xconn.timestamp(), type_.atom());
        } else if let Some((this_window, target_window)) =
            dnd.state().and_then(|state| state.finished)
        {
            unsafe {
                dnd.send_finished(this_window, target_window)
                    .expect("Failed to send `XdndFinished` message.");
            }
        }
    }

    fn configure_notify(&self, xev: &XConfigureEvent, app: &mut dyn ApplicationHandler) {
        let xwindow = xev.window as xproto::Window;
        let window_id = mkwid(xwindow);

        let window = match self.with_window(xwindow, Arc::clone) {
            Some(window) => window,
            None => return,
        };

        // So apparently...
        // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root
        // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent
        // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5
        // We don't want to send `Moved` when this is false, since then every `SurfaceResized`
        // (whether the window moved or not) is accompanied by an extraneous `Moved` event
        // that has a position relative to the parent window.
        let is_synthetic = xev.send_event == xlib::True;

        // These are both in physical space.
        let new_surface_size = (xev.width as u32, xev.height as u32);
        let new_inner_position = (xev.x, xev.y);

        let (mut resized, moved) = {
            let mut shared_state_lock = window.shared_state_lock();

            let resized = util::maybe_change(&mut shared_state_lock.size, new_surface_size);
            let moved = if is_synthetic {
                util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position)
            } else {
                // Detect when frame extents change.
                // Since this isn't synthetic, as per the notes above, this position is relative to
                // the parent window.
                let rel_parent = new_inner_position;
                if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent)
                {
                    // This ensures we process the next `Moved`.
                    shared_state_lock.inner_position = None;
                    // Extra insurance against stale frame extents.
                    shared_state_lock.frame_extents = None;
                }
                false
            };
            (resized, moved)
        };

        let position = window.shared_state_lock().position;

        let new_outer_position = if let (Some(position), false) = (position, moved) {
            position
        } else {
            let mut shared_state_lock = window.shared_state_lock();

            // We need to convert client area position to window position.
            let frame_extents =
                shared_state_lock.frame_extents.as_ref().cloned().unwrap_or_else(|| {
                    let frame_extents =
                        self.target.xconn.get_frame_extents_heuristic(xwindow, self.target.root);
                    shared_state_lock.frame_extents = Some(frame_extents.clone());
                    frame_extents
                });
            let outer =
                frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
            shared_state_lock.position = Some(outer);

            // Unlock shared state to prevent deadlock in callback below
            drop(shared_state_lock);

            if moved {
                app.window_event(&self.target, window_id, WindowEvent::Moved(outer.into()));
            }
            outer
        };

        if is_synthetic {
            let mut shared_state_lock = window.shared_state_lock();
            // If we don't use the existing adjusted value when available, then the user can screw
            // up the resizing by dragging across monitors *without* dropping the
            // window.
            let (width, height) =
                shared_state_lock.dpi_adjusted.unwrap_or((xev.width as u32, xev.height as u32));

            let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
            let new_scale_factor = {
                let window_rect = util::AaRect::new(new_outer_position, new_surface_size);
                let monitor = self
                    .target
                    .xconn
                    .get_monitor_for_window(Some(window_rect))
                    .expect("Failed to find monitor for window");

                if monitor.is_dummy() {
                    // Avoid updating monitor using a dummy monitor handle
                    last_scale_factor
                } else {
                    shared_state_lock.last_monitor = monitor.clone();
                    monitor.scale_factor
                }
            };
            if last_scale_factor != new_scale_factor {
                let (new_width, new_height) = window.adjust_for_dpi(
                    last_scale_factor,
                    new_scale_factor,
                    width,
                    height,
                    &shared_state_lock,
                );

                let old_surface_size = PhysicalSize::new(width, height);
                let new_surface_size = PhysicalSize::new(new_width, new_height);

                // Unlock shared state to prevent deadlock in callback below
                drop(shared_state_lock);

                let surface_size = Arc::new(Mutex::new(new_surface_size));
                app.window_event(&self.target, window_id, WindowEvent::ScaleFactorChanged {
                    scale_factor: new_scale_factor,
                    surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
                });

                let new_surface_size = *surface_size.lock().unwrap();
                drop(surface_size);

                if new_surface_size != old_surface_size {
                    window.request_surface_size_physical(
                        new_surface_size.width,
                        new_surface_size.height,
                    );
                    window.shared_state_lock().dpi_adjusted = Some(new_surface_size.into());
                    // if the DPI factor changed, force a resize event to ensure the logical
                    // size is computed with the right DPI factor
                    resized = true;
                }
            }
        }

        // NOTE: Ensure that the lock is dropped before handling the resized and
        // sending the event back to user.
        let hittest = {
            let mut shared_state_lock = window.shared_state_lock();
            let hittest = shared_state_lock.cursor_hittest;

            // This is a hack to ensure that the DPI adjusted resize is actually
            // applied on all WMs. KWin doesn't need this, but Xfwm does. The hack
            // should not be run on other WMs, since tiling WMs constrain the window
            // size, making the resize fail. This would cause an endless stream of
            // XResizeWindow requests, making Xorg, the winit client, and the WM
            // consume 100% of CPU.
            if let Some(adjusted_size) = shared_state_lock.dpi_adjusted {
                if new_surface_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
                    // When this finally happens, the event will not be synthetic.
                    shared_state_lock.dpi_adjusted = None;
                } else {
                    // Unlock shared state to prevent deadlock in callback below
                    drop(shared_state_lock);
                    window.request_surface_size_physical(adjusted_size.0, adjusted_size.1);
                }
            }

            hittest
        };

        // Reload hittest.
        if hittest.unwrap_or(false) {
            let _ = window.set_cursor_hittest(true);
        }

        if resized {
            let event = WindowEvent::SurfaceResized(new_surface_size.into());
            app.window_event(&self.target, window_id, event);
        }
    }

    /// This is generally a reliable way to detect when the window manager's been
    /// replaced, though this event is only fired by reparenting window managers
    /// (which is almost all of them). Failing to correctly update WM info doesn't
    /// really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only
    /// effect is that we waste some time trying to query unsupported properties.
    fn reparent_notify(&self, xev: &XReparentEvent) {
        self.target.xconn.update_cached_wm_info(self.target.root);

        self.with_window(xev.window as xproto::Window, |window| {
            window.invalidate_cached_frame_extents();
        });
    }

    fn map_notify(&self, xev: &XMapEvent, app: &mut dyn ApplicationHandler) {
        let window = xev.window as xproto::Window;
        let window_id = mkwid(window);

        // NOTE: Re-issue the focus state when mapping the window.
        //
        // The purpose of it is to deliver initial focused state of the newly created
        // window, given that we can't rely on `CreateNotify`, due to it being not
        // sent.
        let focus = self.with_window(window, |window| window.has_focus()).unwrap_or_default();
        app.window_event(&self.target, window_id, WindowEvent::Focused(focus));
    }

    fn destroy_notify(&self, xev: &XDestroyWindowEvent, app: &mut dyn ApplicationHandler) {
        let window = xev.window as xproto::Window;
        let window_id = mkwid(window);

        // In the event that the window's been destroyed without being dropped first, we
        // cleanup again here.
        self.target.windows.borrow_mut().remove(&WindowId::from_raw(window as _));

        // Since all XIM stuff needs to happen from the same thread, we destroy the input
        // context here instead of when dropping the window.
        if let Some(ime) = self.target.ime.as_ref() {
            ime.borrow_mut()
                .remove_context(window as XWindow)
                .expect("Failed to destroy input context");
        }

        app.window_event(&self.target, window_id, WindowEvent::Destroyed);
    }

    fn property_notify(&mut self, xev: &XPropertyEvent, app: &mut dyn ApplicationHandler) {
        let atoms = self.target.x_connection().atoms();
        let atom = xev.atom as xproto::Atom;

        if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER)
            || atom == atoms[_XSETTINGS_SETTINGS]
        {
            self.process_dpi_change(app);
        }
    }

    fn visibility_notify(&self, xev: &XVisibilityEvent, app: &mut dyn ApplicationHandler) {
        let xwindow = xev.window as xproto::Window;

        let window_id = mkwid(xwindow);
        let event = WindowEvent::Occluded(xev.state == xlib::VisibilityFullyObscured);
        app.window_event(&self.target, window_id, event);

        self.with_window(xwindow, |window| {
            window.visibility_notify();
        });
    }

    fn expose(&self, xev: &XExposeEvent) {
        // Multiple Expose events may be received for subareas of a window.
        // We issue `RedrawRequested` only for the last event of such a series.
        if xev.count == 0 {
            let window = xev.window as xproto::Window;
            let window_id = mkwid(window);
            self.target.redraw_sender.send(window_id);
        }
    }

    fn xinput_key_input(
        &mut self,
        xev: &mut XKeyEvent,
        state: ElementState,
        app: &mut dyn ApplicationHandler,
    ) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        let window = match self.active_window {
            Some(window) => window,
            None => return,
        };

        let window_id = mkwid(window);

        let keycode = xev.keycode as _;

        // Update state to track key repeats and determine whether this key was a repeat.
        //
        // Note, when a key is held before focusing on this window the first
        // (non-synthetic) event will not be flagged as a repeat (also note that the
        // synthetic press event that is generated before this when the window gains focus
        // will also not be flagged as a repeat).
        //
        // Only keys that can repeat should change the held_key_press state since a
        // continuously held repeatable key may continue repeating after the press of a
        // non-repeatable key.
        let key_repeats =
            self.xkb_context.keymap_mut().map(|k| k.key_repeats(keycode)).unwrap_or(false);
        let repeat = if key_repeats {
            let is_latest_held = self.held_key_press == Some(keycode);

            if state == ElementState::Pressed {
                self.held_key_press = Some(keycode);
                is_latest_held
            } else {
                // Check that the released key is the latest repeatable key that has been
                // pressed, since repeats will continue for the latest key press if a
                // different previously pressed key is released.
                if is_latest_held {
                    self.held_key_press = None;
                }
                false
            }
        } else {
            false
        };

        // NOTE: When the modifier was captured by the XFilterEvents the modifiers for the modifier
        // itself are out of sync due to XkbState being delivered before XKeyEvent, since it's
        // being replayed by the XIM, thus we should replay ourselves.
        let replay = if let Some(position) =
            self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8)
        {
            // We don't have to replay modifiers pressed before the current event if some events
            // were not forwarded to us, since their state is irrelevant.
            self.xfiltered_modifiers.resize(self.xfiltered_modifiers.len() - 1 - position, 0);
            true
        } else {
            false
        };

        // Always update the modifiers when we're not replaying.
        if !replay {
            self.update_mods_from_core_event(window_id, xev.state as u16, app);
        }

        if keycode != 0 && !self.is_composing {
            // Don't alter the modifiers state from replaying.
            if replay {
                self.send_synthic_modifier_from_core(window_id, xev.state as u16, app);
            }

            if let Some(mut key_processor) = self.xkb_context.key_context() {
                let event = key_processor.process_key_event(keycode, state, repeat);
                let event =
                    WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false };
                app.window_event(&self.target, window_id, event);
            }

            // Restore the client's modifiers state after replay.
            if replay {
                self.send_modifiers(window_id, self.modifiers.get(), true, app);
            }

            return;
        }

        if let Some(ic) =
            self.target.ime.as_ref().and_then(|ime| ime.borrow().get_context(window as XWindow))
        {
            let written = self.target.xconn.lookup_utf8(ic, xev);
            if !written.is_empty() {
                let event = WindowEvent::Ime(Ime::Preedit(String::new(), None));
                app.window_event(&self.target, window_id, event);

                let event = WindowEvent::Ime(Ime::Commit(written));
                self.is_composing = false;
                app.window_event(&self.target, window_id, event);
            }
        }
    }

    fn send_synthic_modifier_from_core(
        &mut self,
        window_id: winit_core::window::WindowId,
        state: u16,
        app: &mut dyn ApplicationHandler,
    ) {
        let keymap = match self.xkb_context.keymap_mut() {
            Some(keymap) => keymap,
            None => return,
        };

        let xcb = self.target.xconn.xcb_connection().get_raw_xcb_connection();

        // Use synthetic state since we're replaying the modifier. The user modifier state
        // will be restored later.
        let mut xkb_state = match XkbState::new_x11(xcb, keymap) {
            Some(xkb_state) => xkb_state,
            None => return,
        };

        let mask = self.xkb_mod_mask_from_core(state);
        xkb_state.update_modifiers(mask, 0, 0, 0, 0, Self::core_keyboard_group(state));
        let mods: ModifiersState = xkb_state.modifiers().into();

        let event = WindowEvent::ModifiersChanged(mods.into());
        app.window_event(&self.target, window_id, event);
    }

    fn xinput2_button_input(
        &self,
        event: &XIDeviceEvent,
        state: ElementState,
        app: &mut dyn ApplicationHandler,
    ) {
        let window_id = mkwid(event.event as xproto::Window);
        let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));

        // Set the timestamp.
        self.target.xconn.set_timestamp(event.time as xproto::Timestamp);

        let Some(DeviceType::Mouse) = self
            .devices
            .borrow()
            .get(&mkdid(event.sourceid as xinput::DeviceId))
            .map(|device| device.r#type)
        else {
            return;
        };

        // Deliver multi-touch events instead of emulated mouse events.
        if (event.flags & xinput2::XIPointerEmulated) != 0 {
            return;
        }

        let position = PhysicalPosition::new(event.event_x, event.event_y);

        let event = match event.detail as u32 {
            xlib::Button1 => WindowEvent::PointerButton {
                device_id,
                primary: true,
                state,
                position,
                button: MouseButton::Left.into(),
                is_macos_activation_click: false,
            },
            xlib::Button2 => WindowEvent::PointerButton {
                device_id,
                primary: true,
                state,
                position,
                button: MouseButton::Middle.into(),
                is_macos_activation_click: false,
            },
            xlib::Button3 => WindowEvent::PointerButton {
                device_id,
                primary: true,
                state,
                position,
                button: MouseButton::Right.into(),
                is_macos_activation_click: false,
            },

            // Suppress emulated scroll wheel clicks, since we handle the real motion events for
            // those. In practice, even clicky scroll wheels appear to be reported by
            // evdev (and XInput2 in turn) as axis motion, so we don't otherwise
            // special-case these button presses.
            4..=7 => match state {
                ElementState::Pressed => WindowEvent::MouseWheel {
                    device_id,
                    delta: match event.detail {
                        4 => MouseScrollDelta::LineDelta(0.0, 1.0),
                        5 => MouseScrollDelta::LineDelta(0.0, -1.0),
                        6 => MouseScrollDelta::LineDelta(1.0, 0.0),
                        7 => MouseScrollDelta::LineDelta(-1.0, 0.0),
                        _ => unreachable!(),
                    },
                    phase: TouchPhase::Moved,
                },
                ElementState::Released => return,
            },

            x @ 8..37 => WindowEvent::PointerButton {
                device_id,
                primary: true,
                state,
                position,
                // Button 8 maps to MouseButton::BACK = 3; 36 maps to MouseButton::Button32.
                // 255 is the largest code yielded on X11 (tested).
                button: MouseButton::try_from_u8((x - 5) as u8).unwrap().into(),
                is_macos_activation_click: false,
            },
            x @ 37..=0xff => WindowEvent::PointerButton {
                device_id,
                primary: true,
                state,
                position,
                // 255 is the largest code yielded on X11 (tested).
                button: ButtonSource::Unknown(x as u16),
                is_macos_activation_click: false,
            },
            _ => return,
        };

        app.window_event(&self.target, window_id, event);
    }

    fn xinput2_mouse_motion(&self, event: &XIDeviceEvent, app: &mut dyn ApplicationHandler) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(event.time as xproto::Timestamp);

        let Some(DeviceType::Mouse) = self
            .devices
            .borrow()
            .get(&mkdid(event.sourceid as xinput::DeviceId))
            .map(|device| device.r#type)
        else {
            return;
        };

        let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));
        let window = event.event as xproto::Window;
        let window_id = mkwid(window);
        let new_cursor_pos = (event.event_x, event.event_y);

        let cursor_moved = self.with_window(window, |window| {
            let mut shared_state_lock = window.shared_state_lock();
            util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
        });

        if cursor_moved == Some(true) {
            let position = PhysicalPosition::new(event.event_x, event.event_y);

            let event = WindowEvent::PointerMoved {
                device_id,
                primary: true,
                position,
                source: PointerSource::Mouse,
            };
            app.window_event(&self.target, window_id, event);
        } else if cursor_moved.is_none() {
            return;
        }

        // More gymnastics, for self.devices
        let mask = unsafe {
            slice::from_raw_parts(event.valuators.mask, event.valuators.mask_len as usize)
        };
        let mut devices = self.devices.borrow_mut();
        let physical_device = match devices.get_mut(&mkdid(event.sourceid as xinput::DeviceId)) {
            Some(device) => device,
            None => return,
        };

        let mut events = Vec::new();
        let mut value = event.valuators.values;
        for i in 0..event.valuators.mask_len * 8 {
            if !xinput2::XIMaskIsSet(mask, i) {
                continue;
            }

            let x = unsafe { *value };

            if let Some(&mut (_, ref mut info)) =
                physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i as _)
            {
                let delta = (x - info.position) / info.increment;
                info.position = x;
                // X11 vertical scroll coordinates are opposite to winit's
                let delta = match info.orientation {
                    ScrollOrientation::Horizontal => {
                        MouseScrollDelta::LineDelta(-delta as f32, 0.0)
                    },
                    ScrollOrientation::Vertical => MouseScrollDelta::LineDelta(0.0, -delta as f32),
                };

                let event = WindowEvent::MouseWheel { device_id, delta, phase: TouchPhase::Moved };
                events.push(event);
            }

            value = unsafe { value.offset(1) };
        }

        for event in events {
            app.window_event(&self.target, window_id, event);
        }
    }

    fn xinput2_mouse_enter(&self, event: &XIEnterEvent, app: &mut dyn ApplicationHandler) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(event.time as xproto::Timestamp);

        let window = event.event as xproto::Window;
        let window_id = mkwid(window);
        let device_id = mkdid(event.deviceid as xinput::DeviceId);

        if let Some(all_info) = DeviceInfo::get(&self.target.xconn, ALL_DEVICES.into()) {
            let mut devices = self.devices.borrow_mut();
            for device_info in all_info.iter() {
                // The second expression is need for resetting to work correctly on i3, and
                // presumably some other WMs. On those, `XI_Enter` doesn't include the physical
                // device ID, so both `sourceid` and `deviceid` are the virtual device.
                if device_info.deviceid == event.sourceid
                    || device_info.attachment == event.sourceid
                {
                    let device_id = mkdid(device_info.deviceid as xinput::DeviceId);
                    if let Some(device) = devices.get_mut(&device_id) {
                        device.reset_scroll_position(device_info);
                    }
                }
            }
        }

        if self.window_exists(window) {
            let device_id = Some(device_id);
            let position = PhysicalPosition::new(event.event_x, event.event_y);

            let event = WindowEvent::PointerEntered {
                device_id,
                primary: true,
                position,
                kind: PointerKind::Mouse,
            };
            app.window_event(&self.target, window_id, event);
        }
    }

    fn xinput2_mouse_left(&self, event: &XILeaveEvent, app: &mut dyn ApplicationHandler) {
        let window = event.event as xproto::Window;

        // Set the timestamp.
        self.target.xconn.set_timestamp(event.time as xproto::Timestamp);

        // Leave, FocusIn, and FocusOut can be received by a window that's already
        // been destroyed, which the user presumably doesn't want to deal with.
        if self.window_exists(window) {
            let window_id = mkwid(window);
            let event = WindowEvent::PointerLeft {
                device_id: Some(mkdid(event.deviceid as xinput::DeviceId)),
                primary: true,
                position: Some(PhysicalPosition::new(event.event_x, event.event_y)),
                kind: PointerKind::Mouse,
            };
            app.window_event(&self.target, window_id, event);
        }
    }

    fn xinput2_focused(&mut self, xev: &XIFocusInEvent, app: &mut dyn ApplicationHandler) {
        let window = xev.event as xproto::Window;

        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        if let Some(ime) = self.target.ime.as_ref() {
            ime.borrow_mut().focus(xev.event).expect("Failed to focus input context");
        }

        if self.active_window == Some(window) {
            return;
        }

        self.active_window = Some(window);

        self.target.update_listen_device_events(true);

        let window_id = mkwid(window);
        let position = PhysicalPosition::new(xev.event_x, xev.event_y);

        if let Some(window) = self.with_window(window, Arc::clone) {
            window.shared_state_lock().has_focus = true;
        }

        app.window_event(&self.target, window_id, WindowEvent::Focused(true));

        // Issue key press events for all pressed keys
        Self::handle_pressed_keys(
            &self.target,
            window_id,
            ElementState::Pressed,
            &mut self.xkb_context,
            app,
        );

        self.update_mods_from_query(window_id, app);

        // The deviceid for this event is for a keyboard instead of a pointer,
        // so we have to do a little extra work.
        let device_id = self
            .devices
            .borrow()
            .get(&mkdid(xev.deviceid as xinput::DeviceId))
            .map(|device| mkdid(device.attachment as xinput::DeviceId));

        let event = WindowEvent::PointerMoved {
            device_id,
            primary: true,
            position,
            source: PointerSource::Mouse,
        };
        app.window_event(&self.target, window_id, event);
    }

    fn xinput2_unfocused(&mut self, xev: &XIFocusOutEvent, app: &mut dyn ApplicationHandler) {
        let window = xev.event as xproto::Window;

        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        if !self.window_exists(window) {
            return;
        }

        if let Some(ime) = self.target.ime.as_ref() {
            ime.borrow_mut().unfocus(xev.event).expect("Failed to unfocus input context");
        }

        if self.active_window.take() == Some(window) {
            let window_id = mkwid(window);

            self.target.update_listen_device_events(false);

            // Clear the modifiers when unfocusing the window.
            if let Some(xkb_state) = self.xkb_context.state_mut() {
                xkb_state.update_modifiers(0, 0, 0, 0, 0, 0);
                let mods = xkb_state.modifiers();
                self.send_modifiers(window_id, mods.into(), true, app);
            }

            // Issue key release events for all pressed keys
            Self::handle_pressed_keys(
                &self.target,
                window_id,
                ElementState::Released,
                &mut self.xkb_context,
                app,
            );

            // Clear this so detecting key repeats is consistently handled when the
            // window regains focus.
            self.held_key_press = None;

            if let Some(window) = self.with_window(window, Arc::clone) {
                window.shared_state_lock().has_focus = false;
            }

            app.window_event(&self.target, window_id, WindowEvent::Focused(false));
        }
    }

    fn xinput2_touch(&mut self, xev: &XIDeviceEvent, phase: i32, app: &mut dyn ApplicationHandler) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        let window = xev.event as xproto::Window;
        if self.window_exists(window) {
            let window_id = mkwid(window);
            let id = xev.detail as u32;
            let position = PhysicalPosition::new(xev.event_x, xev.event_y);

            // Mouse cursor position changes when touch events are received.
            // Only the first concurrently active touch ID moves the mouse cursor.
            let is_first_touch =
                is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase);
            if is_first_touch {
                let event = WindowEvent::PointerMoved {
                    device_id: None,
                    primary: true,
                    position: position.cast(),
                    source: PointerSource::Mouse,
                };
                app.window_event(&self.target, window_id, event);
            }

            let device_id = Some(mkdid(xev.deviceid as xinput::DeviceId));
            let finger_id = FingerId::from_raw(id as usize);

            match phase {
                xinput2::XI_TouchBegin => {
                    let event = WindowEvent::PointerEntered {
                        device_id,
                        primary: is_first_touch,
                        position,
                        kind: PointerKind::Touch(finger_id),
                    };
                    app.window_event(&self.target, window_id, event);
                    let event = WindowEvent::PointerButton {
                        device_id,
                        primary: is_first_touch,
                        state: ElementState::Pressed,
                        position,
                        button: ButtonSource::Touch { finger_id, force: None },
                        is_macos_activation_click: false,
                    };
                    app.window_event(&self.target, window_id, event);
                },
                xinput2::XI_TouchUpdate => {
                    let event = WindowEvent::PointerMoved {
                        device_id,
                        primary: is_first_touch,
                        position,
                        source: PointerSource::Touch { finger_id, force: None },
                    };
                    app.window_event(&self.target, window_id, event);
                },
                xinput2::XI_TouchEnd => {
                    let event = WindowEvent::PointerButton {
                        device_id,
                        primary: is_first_touch,
                        state: ElementState::Released,
                        position,
                        button: ButtonSource::Touch { finger_id, force: None },
                        is_macos_activation_click: false,
                    };
                    app.window_event(&self.target, window_id, event);
                    let event = WindowEvent::PointerLeft {
                        device_id,
                        primary: is_first_touch,
                        position: Some(position),
                        kind: PointerKind::Touch(finger_id),
                    };
                    app.window_event(&self.target, window_id, event);
                },
                _ => unreachable!(),
            }
        }
    }

    fn xinput2_raw_button_input(
        &self,
        xev: &XIRawEvent,
        state: ElementState,
        app: &mut dyn ApplicationHandler,
    ) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        if xev.flags & xinput2::XIPointerEmulated == 0 {
            let event = DeviceEvent::Button { state, button: xev.detail as u32 };
            app.device_event(&self.target, Some(mkdid(xev.deviceid as xinput::DeviceId)), event);
        }
    }

    fn xinput2_raw_mouse_motion(&self, xev: &XIRawEvent, app: &mut dyn ApplicationHandler) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        let did = Some(mkdid(xev.deviceid as xinput::DeviceId));
        let mask =
            unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
        let mut value = xev.raw_values;
        let mut mouse_delta = util::Delta::default();
        let mut scroll_delta = util::Delta::default();
        for i in 0..xev.valuators.mask_len * 8 {
            if !xinput2::XIMaskIsSet(mask, i) {
                continue;
            }
            let x = unsafe { value.read_unaligned() };

            // We assume that every XInput2 device with analog axes is a pointing device emitting
            // relative coordinates.
            match i {
                0 => mouse_delta.set_x(x),
                1 => mouse_delta.set_y(x),
                2 => scroll_delta.set_x(x as f32),
                3 => scroll_delta.set_y(x as f32),
                _ => {},
            }

            value = unsafe { value.offset(1) };
        }

        let Some(DeviceType::Mouse) = self
            .devices
            .borrow()
            .get(&mkdid(xev.sourceid as xinput::DeviceId))
            .map(|device| device.r#type)
        else {
            return;
        };

        if let Some(mouse_delta) = mouse_delta.consume() {
            app.device_event(&self.target, did, DeviceEvent::PointerMotion { delta: mouse_delta });
        }

        if let Some(scroll_delta) = scroll_delta.consume() {
            let event = DeviceEvent::MouseWheel {
                delta: MouseScrollDelta::LineDelta(scroll_delta.0, scroll_delta.1),
            };
            app.device_event(&self.target, did, event);
        }
    }

    fn xinput2_raw_key_input(
        &mut self,
        xev: &XIRawEvent,
        state: ElementState,
        app: &mut dyn ApplicationHandler,
    ) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

        let device_id = Some(mkdid(xev.sourceid as xinput::DeviceId));
        let keycode = xev.detail as u32;
        if keycode < KEYCODE_OFFSET as u32 {
            return;
        }
        let physical_key = xkb::raw_keycode_to_physicalkey(keycode);

        let event = DeviceEvent::Key(RawKeyEvent { physical_key, state });
        app.device_event(&self.target, device_id, event);
    }

    fn xinput2_hierarchy_changed(&mut self, xev: &XIHierarchyEvent) {
        // Set the timestamp.
        self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
        let infos = unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) };
        for info in infos {
            if 0 != info.flags & (xinput2::XISlaveAdded | xinput2::XIMasterAdded) {
                self.init_device(info.deviceid as xinput::DeviceId);
            } else if 0 != info.flags & (xinput2::XISlaveRemoved | xinput2::XIMasterRemoved) {
                let mut devices = self.devices.borrow_mut();
                devices.remove(&mkdid(info.deviceid as xinput::DeviceId));
            }
        }
    }

    fn xkb_event(&mut self, xev: &XkbAnyEvent, app: &mut dyn ApplicationHandler) {
        match xev.xkb_type {
            xlib::XkbNewKeyboardNotify => {
                let xev = unsafe { &*(xev as *const _ as *const xlib::XkbNewKeyboardNotifyEvent) };

                // Set the timestamp.
                self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

                let keycodes_changed_flag = 0x1;
                let geometry_changed_flag = 0x1 << 1;

                let keycodes_changed = util::has_flag(xev.changed, keycodes_changed_flag);
                let geometry_changed = util::has_flag(xev.changed, geometry_changed_flag);

                if xev.device == self.xkb_context.core_keyboard_id
                    && (keycodes_changed || geometry_changed)
                {
                    let xcb = self.target.xconn.xcb_connection().get_raw_xcb_connection();
                    self.xkb_context.set_keymap_from_x11(xcb);
                    self.xmodmap.reload_from_x_connection(&self.target.xconn);

                    let window_id = match self.active_window.map(mkwid) {
                        Some(window_id) => window_id,
                        None => return,
                    };

                    if let Some(state) = self.xkb_context.state_mut() {
                        let mods = state.modifiers().into();
                        self.send_modifiers(window_id, mods, true, app);
                    }
                }
            },
            xlib::XkbMapNotify => {
                let xcb = self.target.xconn.xcb_connection().get_raw_xcb_connection();
                self.xkb_context.set_keymap_from_x11(xcb);
                self.xmodmap.reload_from_x_connection(&self.target.xconn);
                let window_id = match self.active_window.map(mkwid) {
                    Some(window_id) => window_id,
                    None => return,
                };

                if let Some(state) = self.xkb_context.state_mut() {
                    let mods = state.modifiers().into();
                    self.send_modifiers(window_id, mods, true, app);
                }
            },
            xlib::XkbStateNotify => {
                let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) };

                // Set the timestamp.
                self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);

                if let Some(state) = self.xkb_context.state_mut() {
                    state.update_modifiers(
                        xev.base_mods,
                        xev.latched_mods,
                        xev.locked_mods,
                        xev.base_group as u32,
                        xev.latched_group as u32,
                        xev.locked_group as u32,
                    );

                    let window_id = match self.active_window.map(mkwid) {
                        Some(window_id) => window_id,
                        None => return,
                    };

                    let mods = state.modifiers().into();
                    self.send_modifiers(window_id, mods, true, app);
                }
            },
            _ => {},
        }
    }

    pub(crate) fn update_mods_from_xinput2_event(
        &mut self,
        mods: &XIModifierState,
        group: &XIModifierState,
        force: bool,
        app: &mut dyn ApplicationHandler,
    ) {
        if let Some(state) = self.xkb_context.state_mut() {
            state.update_modifiers(
                mods.base as u32,
                mods.latched as u32,
                mods.locked as u32,
                group.base as u32,
                group.latched as u32,
                group.locked as u32,
            );

            // NOTE: we use active window since generally sub windows don't have keyboard input,
            // and winit assumes that unfocused window doesn't have modifiers.
            let window_id = match self.active_window.map(mkwid) {
                Some(window_id) => window_id,
                None => return,
            };

            let mods = state.modifiers();
            self.send_modifiers(window_id, mods.into(), force, app);
        }
    }

    fn update_mods_from_query(
        &mut self,
        window_id: winit_core::window::WindowId,
        app: &mut dyn ApplicationHandler,
    ) {
        let xkb_state = match self.xkb_context.state_mut() {
            Some(xkb_state) => xkb_state,
            None => return,
        };

        unsafe {
            let mut state: XkbStateRec = std::mem::zeroed();
            if (self.target.xconn.xlib.XkbGetState)(
                self.target.xconn.display,
                XkbId::USE_CORE_KBD.into(),
                &mut state,
            ) == xlib::True
            {
                xkb_state.update_modifiers(
                    state.base_mods as u32,
                    state.latched_mods as u32,
                    state.locked_mods as u32,
                    state.base_group as u32,
                    state.latched_group as u32,
                    state.locked_group as u32,
                );
            }
        }

        let mods = xkb_state.modifiers();
        self.send_modifiers(window_id, mods.into(), true, app)
    }

    pub(crate) fn update_mods_from_core_event(
        &mut self,
        window_id: winit_core::window::WindowId,
        state: u16,
        app: &mut dyn ApplicationHandler,
    ) {
        let xkb_mask = self.xkb_mod_mask_from_core(state);
        let xkb_state = match self.xkb_context.state_mut() {
            Some(xkb_state) => xkb_state,
            None => return,
        };

        // NOTE: this is inspired by Qt impl.
        let mut depressed = xkb_state.depressed_modifiers() & xkb_mask;
        let latched = xkb_state.latched_modifiers() & xkb_mask;
        let locked = xkb_state.locked_modifiers() & xkb_mask;
        // Set modifiers in depressed if they don't appear in any of the final masks.
        depressed |= !(depressed | latched | locked) & xkb_mask;

        xkb_state.update_modifiers(
            depressed,
            latched,
            locked,
            0,
            0,
            Self::core_keyboard_group(state),
        );

        let mods = xkb_state.modifiers();
        self.send_modifiers(window_id, mods.into(), false, app);
    }

    // Bits 13 and 14 report the state keyboard group.
    pub fn core_keyboard_group(state: u16) -> u32 {
        ((state >> 13) & 3) as u32
    }

    pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t {
        let mods_indices = match self.xkb_context.keymap_mut() {
            Some(keymap) => keymap.mods_indices(),
            None => return 0,
        };

        // Build the XKB modifiers from the regular state.
        let mut depressed = 0u32;
        if let Some(shift) = mods_indices.shift.filter(|_| ModMask::SHIFT.intersects(state)) {
            depressed |= 1 << shift;
        }
        if let Some(caps) = mods_indices.caps.filter(|_| ModMask::LOCK.intersects(state)) {
            depressed |= 1 << caps;
        }
        if let Some(ctrl) = mods_indices.ctrl.filter(|_| ModMask::CONTROL.intersects(state)) {
            depressed |= 1 << ctrl;
        }
        if let Some(alt) = mods_indices.alt.filter(|_| ModMask::M1.intersects(state)) {
            depressed |= 1 << alt;
        }
        if let Some(num) = mods_indices.num.filter(|_| ModMask::M2.intersects(state)) {
            depressed |= 1 << num;
        }
        if let Some(mod3) = mods_indices.mod3.filter(|_| ModMask::M3.intersects(state)) {
            depressed |= 1 << mod3;
        }
        if let Some(logo) = mods_indices.logo.filter(|_| ModMask::M4.intersects(state)) {
            depressed |= 1 << logo;
        }
        if let Some(mod5) = mods_indices.mod5.filter(|_| ModMask::M5.intersects(state)) {
            depressed |= 1 << mod5;
        }

        depressed
    }

    /// Send modifiers for the active window.
    ///
    /// The event won't be sent when the `modifiers` match the previously `sent` modifiers value,
    /// unless `force` is passed. The `force` should be passed when the active window changes.
    fn send_modifiers(
        &self,
        window_id: winit_core::window::WindowId,
        modifiers: ModifiersState,
        force: bool,
        app: &mut dyn ApplicationHandler,
    ) {
        // NOTE: Always update the modifiers to account for case when they've changed
        // and forced was `true`.
        if self.modifiers.replace(modifiers) != modifiers || force {
            let event = WindowEvent::ModifiersChanged(self.modifiers.get().into());
            app.window_event(&self.target, window_id, event);
        }
    }

    fn handle_pressed_keys(
        target: &ActiveEventLoop,
        window_id: winit_core::window::WindowId,
        state: ElementState,
        xkb_context: &mut Context,
        app: &mut dyn ApplicationHandler,
    ) {
        // Update modifiers state and emit key events based on which keys are currently pressed.
        let xcb = target.xconn.xcb_connection().get_raw_xcb_connection();

        let keymap = match xkb_context.keymap_mut() {
            Some(keymap) => keymap,
            None => return,
        };

        // Send the keys using the synthetic state to not alter the main state.
        let mut xkb_state = match XkbState::new_x11(xcb, keymap) {
            Some(xkb_state) => xkb_state,
            None => return,
        };
        let mut key_processor = match xkb_context.key_context_with_state(&mut xkb_state) {
            Some(key_processor) => key_processor,
            None => return,
        };

        for keycode in target.xconn.query_keymap().into_iter().filter(|k| *k >= KEYCODE_OFFSET) {
            let event = key_processor.process_key_event(keycode as u32, state, false);
            let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: true };
            app.window_event(target, window_id, event);
        }
    }

    fn process_dpi_change(&self, app: &mut dyn ApplicationHandler) {
        self.target.xconn.reload_database().expect("failed to reload Xft database");

        // In the future, it would be quite easy to emit monitor hotplug events.
        let prev_list = {
            let prev_list = self.target.xconn.invalidate_cached_monitor_list();
            match prev_list {
                Some(prev_list) => prev_list,
                None => return,
            }
        };

        let new_list = self.target.xconn.available_monitors().expect("Failed to get monitor list");
        for new_monitor in new_list {
            // Previous list may be empty, in case of disconnecting and
            // reconnecting the only one monitor. We still need to emit events in
            // this case.
            let maybe_prev_scale_factor = prev_list
                .iter()
                .find(|prev_monitor| prev_monitor.name == new_monitor.name)
                .map(|prev_monitor| prev_monitor.scale_factor);
            if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
                for window in self.target.windows.borrow().values().filter_map(|w| w.upgrade()) {
                    window.refresh_dpi_for_monitor(
                        &new_monitor,
                        maybe_prev_scale_factor,
                        app,
                        &self.target,
                    )
                }
            }
        }
    }

    fn window_exists(&self, window_id: xproto::Window) -> bool {
        self.with_window(window_id, |_| ()).is_some()
    }
}

fn is_first_touch(first: &mut Option<u32>, num: &mut u32, id: u32, phase: i32) -> bool {
    match phase {
        xinput2::XI_TouchBegin => {
            if *num == 0 {
                *first = Some(id);
            }
            *num += 1;
        },
        xinput2::XI_TouchEnd => {
            if *first == Some(id) {
                *first = None;
            }
            *num = num.saturating_sub(1);
        },
        _ => (),
    }

    *first == Some(id)
}
