use std::cell::RefCell;
use std::rc::Rc;
use std::time::{Duration, Instant};

use forge_ipc::{GestureSector, KeyTrigger, Keysym, Modifiers};
use smithay::backend::input::{
    AbsolutePositionEvent, ButtonState, Event, InputBackend, InputEvent, KeyState,
    KeyboardKeyEvent, MouseButton, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
    TouchEvent as _, TouchSlot,
};
use smithay::input::keyboard::{
    keysyms, FilterResult, Keycode, KeysymHandle, ModifiersState, XkbConfig,
};
use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent as PointerMotionData};
use smithay::input::touch::{DownEvent, MotionEvent, UpEvent};
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Point, Rectangle, SERIAL_COUNTER};
use smithay::wayland::compositor;
use tracing::{error, info};

use crate::config::{GestureAction, GestureBinding};
use crate::daemon;
use crate::drawing::ForgeSurfaceData;
use crate::forge::Forge;
use crate::intent::UiIntent;
use crate::orientation::Orientation;
use crate::output::{Canvas, GESTURE_HANDLE_HEIGHT};
use crate::ssd::SsdAction;
use crate::windows::focus::FocusTarget;
use crate::windows::surface::{InputSurface, InputSurfaceKind};
use crate::windows::window::Window;

pub const HOLD_DURATION: Duration = Duration::from_secs(1);
pub const REPEAT_DELAY: Duration = Duration::from_millis(500);
pub const REPEAT_RATE: Duration = Duration::from_millis(25);

const DOUBLE_TAP_TIMEOUT: Duration = Duration::from_millis(300);
const DOUBLE_TAP_DISTANCE_SQ: f64 = 2000.0;
const TAP_DISTANCE_SQ: f64 = 400.0;
const VELOCITY_FRICTION: f64 = 0.1;
const POINTER_SLOT: Option<u32> = Some(0);
const TOP_GESTURE_HEIGHT: f64 = 100.0;
/// Bottom band (logical px) within which a swipe-up arms over an app, and
/// the fallback switcher zone when the dock height is unknown.
const BOTTOM_ARM_BAND: f64 = 60.0;
/// How long the finger must dwell (settled past the arm fraction) before the
/// bottom swipe-up commits the app switcher.
const DWELL_MS: u64 = 150;
/// Progress drift (fraction of screen height) the finger may wander without
/// restarting the dwell countdown — a margin so it need not be perfectly
/// still, only roughly settled.
const DWELL_TOLERANCE: f32 = 0.04;

mod state;
mod touch;
mod keyboard;
mod swipes;
mod pointer;
mod launch;

pub use state::{ArmedSsdButton, ArmedSsdTarget, HandleGesture, TouchState};
use state::{TouchAction, TouchEvent, TouchEventType};

impl Forge {
    pub fn handle_orientation(&mut self, orientation: Orientation) {
        self.windows.update_orientation(orientation);
        self.force_redraw(false);
    }

    pub fn handle_input<I: InputBackend>(&mut self, event: InputEvent<I>) {
        if !self.session.display_on && self.handle_display_off_input(&event) {
            return;
        }

        self.notify_idle_activity();
        crate::idle_monitor_dbus::touch_activity();
        crate::powermanager_dbus::activity();

        match event {
            InputEvent::Keyboard { event, .. } => {
                self.on_keyboard_input(event.key_code(), event.state(), event.time_msec());
            },
            InputEvent::PointerMotion { event, .. } => {
                self.input.draw_cursor = true;
                let delta = (event.delta().x, event.delta().y);
                let prev = self.input.touch_state.position;
                let pos = prev + Point::from(delta);
                let size_i = self.windows.canvas().size();
                let rects: Vec<Rectangle<f64, Logical>> = self
                    .windows
                    .output_rects()
                    .iter()
                    .map(|r| r.to_f64())
                    .collect();
                let clamped = TouchState::clamp_point_impl(pos, prev, &rects);
                self.input.touch_state.position = clamped;
                if self.input.touch_state.touching() {
                    let slot = TouchSlot::from(POINTER_SLOT);
                    let ev = TouchEvent::from_pointer(TouchEventType::Motion, slot, event.time_msec(), clamped);
                    self.on_touch_motion(ev);
                } else if self.windows.is_resize_dragging() {
                    self.windows.update_resize_drag(clamped);
                    self.unstall();
                } else if self.windows.is_move_dragging() {
                    self.windows.update_move_drag(clamped);
                    self.unstall();
                }

                // Active corner detection (raw deltas so stuck-cursor still
                // progresses), against the screen under the pointer so every
                // monitor's corners are live.
                let corner_rect = self.windows.output_at(clamped).logical_rect();
                let corner_local = clamped - corner_rect.loc.to_f64();
                if let Some((corner, action)) = self.input.corner_recognizer.on_motion(
                    corner_local, delta.0, delta.1, corner_rect.size, std::time::Instant::now(),
                ) {
                    self.trigger_corner_action(corner, action);
                }
                let fb = self.input.corner_recognizer.progress();
                self.windows.set_corner_feedback(fb);

                if let Some(event) = self.input.edge_recognizer.on_motion(clamped, size_i.h) {
                    let msg = match event {
                        crate::edge_action::EdgeEvent::Enter => r#"{"action":"reveal_dock"}"#,
                        crate::edge_action::EdgeEvent::Leave => r#"{"action":"hide_dock"}"#,
                    };
                    Self::send_to_crustace(msg);
                }

                // Suppress forwarding pointer motion to clients while forge
                // owns the pointer session — window move/resize drag or a
                // topbar swipe. Otherwise motion leaks to whatever surface
                // sits under the cursor (usually a crustace layer surface)
                // and its gesture recogniser picks it up as a swipe.
                if !self.pointer_captured() {
                    self.on_pointer_motion(clamped, event.time_msec());
                }
            },
            InputEvent::PointerMotionAbsolute { event } => {
                self.input.draw_cursor = true;
                let pos = self.transform_position(&event);
                let prev = self.input.touch_state.position;
                let dx = pos.x - prev.x;
                let dy = pos.y - prev.y;
                self.input.touch_state.position = pos;
                let size_i = self.windows.canvas().size();
                if self.input.touch_state.touching() {
                    let slot = TouchSlot::from(POINTER_SLOT);
                    let ev = TouchEvent::from_pointer(TouchEventType::Motion, slot, event.time_msec(), pos);
                    self.on_touch_motion(ev);
                } else if self.windows.is_resize_dragging() {
                    self.windows.update_resize_drag(pos);
                    self.unstall();
                } else if self.windows.is_move_dragging() {
                    self.windows.update_move_drag(pos);
                    self.unstall();
                }

                if let Some((corner, action)) = self.input.corner_recognizer.on_motion(
                    pos, dx, dy, size_i, std::time::Instant::now(),
                ) {
                    self.trigger_corner_action(corner, action);
                }
                let fb = self.input.corner_recognizer.progress();
                self.windows.set_corner_feedback(fb);

                if let Some(event) = self.input.edge_recognizer.on_motion(pos, size_i.h) {
                    let msg = match event {
                        crate::edge_action::EdgeEvent::Enter => r#"{"action":"reveal_dock"}"#,
                        crate::edge_action::EdgeEvent::Leave => r#"{"action":"hide_dock"}"#,
                    };
                    Self::send_to_crustace(msg);
                }

                if !self.pointer_captured() {
                    self.on_pointer_motion(pos, event.time_msec());
                }
            },
            InputEvent::PointerButton { event } => {
                if self.shell.screenshot.is_active() {
                    if event.button() == Some(MouseButton::Left) {
                        let pos = self.input.touch_state.position;
                        match event.state() {
                            ButtonState::Pressed => self.shell.screenshot.on_press(pos),
                            ButtonState::Released => self.shell.screenshot.on_release(),
                        }
                    }
                    self.request_redraw();
                    self.unstall();
                    return;
                }
                let serial = SERIAL_COUNTER.next_serial();
                let time = event.time_msec();
                let button = event.button_code();
                let state = event.state();

                let mut forward_to_client = true;

                // Forge-owned modal dialogs (portal consent, polkit auth) own
                // the pointer outright: like the app switcher, a client popup
                // grab — stale or live — must not swallow their clicks, and
                // neither press nor release may leak to the windows beneath.
                if self.windows.confirm.visible || self.auth_active() {
                    if event.button() == Some(MouseButton::Left)
                        && state == ButtonState::Pressed
                    {
                        let pos = self.input.touch_state.position;
                        let canvas = *self.windows.canvas();
                        if self.windows.confirm.visible {
                            if let Some(allow) = self.windows.confirm.handle_tap(&canvas, pos) {
                                self.dispatch(crate::intent::UiIntent::PortalConsent(allow));
                            }
                        } else {
                            use crate::polkit::PolkitAction;
                            let intent = match self.shell.polkit.handle_tap(&canvas, pos) {
                                Some(PolkitAction::Authenticate) => {
                                    Some(crate::intent::UiIntent::Authenticate)
                                }
                                Some(PolkitAction::Cancel) => {
                                    Some(crate::intent::UiIntent::CancelAuthentication)
                                }
                                _ => None,
                            };
                            if let Some(intent) = intent {
                                self.dispatch(intent);
                            }
                        }
                        self.windows.set_dirty();
                        self.request_redraw();
                    }
                    return;
                }

                // A pointer grab (Wayland popup, drag-and-drop, or an explicit
                // client grab) is modal: a *press* under an active grab must
                // go to the grab (via the forward below), not start a forge
                // interaction — this keeps a click from leaking to a window
                // underneath a grabbing popup (e.g. an X11 toolbar behind a
                // GTK menu), and lets a click outside the popup dismiss it.
                //
                // Only the PRESS is blocked. A release is never blocked, so an
                // in-flight move/resize drag or topbar swipe always reaches
                // its end handler below — otherwise it would stay glued to the
                // cursor forever if a grab appeared mid-gesture.
                //
                // Exception: the app switcher is a forge-owned modal overlay,
                // so a stale client pointer grab must not block clicks on it.
                // On winit the bottom swipe-up establishes the default pointer
                // grab on press and swallows the release, leaving the grab
                // live — which otherwise blocks every subsequent tile press.
                let block_routing = state == ButtonState::Pressed
                    && !self.shell.app_switcher.is_active()
                    && self.seat.get_pointer().is_some_and(|p| p.is_grabbed());

                if event.button() == Some(MouseButton::Left) && !block_routing {
                    let pos = self.input.touch_state.position;

                    let switcher_active = self.shell.app_switcher.is_active();
                    // An in-flight carousel drag (e.g. swipe-up-to-close) must
                    // always end through on_touch_up, even when the release
                    // lands on the topbar or dock band — otherwise the topbar /
                    // dock branch swallows the release and the tile stays glued
                    // to the cursor.
                    let dragging = self.shell.app_switcher.is_carousel_dragging();
                    let on_topbar = switcher_active && !dragging && self.shell.topbar.hit_test(pos).is_some();
                    let on_dock   = switcher_active && !dragging && self.windows.foreground_layer_at(pos);
                    if switcher_active && on_dock {
                        self.shell.app_switcher.cancel();
                        self.windows.set_dirty();
                        self.on_pointer_motion(pos, time);
                    }
                    if switcher_active && on_topbar {
                        if state == ButtonState::Released {
                            if let Some(action) = self.shell.topbar.hit_test(pos) {
                                let msg = match action {
                                    crate::topbar::TopbarAction::OpenNotifications => r#"{"action":"open_notifications"}"#,
                                    crate::topbar::TopbarAction::OpenQuickSettings => r#"{"action":"open_quick_settings"}"#,
                                    crate::topbar::TopbarAction::ToggleCalendar    => r#"{"action":"toggle_calendar"}"#,
                                };
                                Self::send_to_crustace(msg);
                                self.request_redraw();
                            }
                        }
                        forward_to_client = false;
                    } else if switcher_active && !on_dock {
                        // Both shells need the natural press→motion→release
                        // sequence: the mobile carousel for drag/inertia, the
                        // desktop panel for the tile drag that carries a
                        // window to another screen. Mapping the physical
                        // press to Down is what lets a drag start where the
                        // button actually went down.
                        let slot = TouchSlot::from(POINTER_SLOT);
                        let ev_type = match state {
                            ButtonState::Pressed => TouchEventType::Down,
                            ButtonState::Released => TouchEventType::Up,
                        };
                        let ev = TouchEvent::from_pointer(ev_type, slot, 0, pos);
                        match ev_type {
                            TouchEventType::Down => self.on_touch_down(ev),
                            _ => self.on_touch_up(ev),
                        }
                        forward_to_client = false;
                    } else {

                    // An SSD title bar lives ABOVE its window's bounds, so
                    // `surface_at` falls through to whatever client body
                    // sits under that strip (typically a window one layer
                    // below in the stack). Pre-empt that: when the point
                    // lands on any visible SSD, treat the click as a
                    // non-client hit so `on_touch_down` can route it to
                    // `apply_ssd_button` / start a title-bar drag for the
                    // correct (topmost) window, and suppress the pointer
                    // button forwarding so no stale client receives it.
                    let desktop = !self.windows.canvas().is_mobile();
                    // A top/overlay layer surface (dock, calendar, any
                    // crustace panel) stacks above windows and their SSD
                    // chrome; input must match the visual order, so layer
                    // coverage pre-empts the SSD / resize-band pre-emption.
                    let on_layer = self.windows.foreground_layer_at(pos);

                    // Ctrl+Alt (or Super) + left-press on a window body moves
                    // it from anywhere: the rescue for a window whose bar is
                    // unreachable. Ctrl+Alt is the compositor's modifier
                    // chord, kept for keyboards without a Super key.
                    if state == ButtonState::Pressed
                        && desktop
                        && !on_layer
                        && self.seat.get_keyboard().is_some_and(|k| {
                            let mods = k.modifier_state();
                            (mods.ctrl && mods.alt) || mods.logo
                        })
                        && self.windows.start_move_drag_anywhere(pos)
                    {
                        self.request_redraw();
                        return;
                    }

                    let on_ssd = desktop
                        && !on_layer
                        && (self.windows.ssd_window_hit_test(pos).is_some()
                            || self.windows.ssd_x11_hit_test(pos).is_some());
                    let on_resize_band = desktop
                        && !on_layer
                        && self.windows.resize_cursor_edge(pos).is_some();

                    let hit = if on_ssd || on_resize_band {
                        None
                    } else {
                        self.windows.surface_at(pos)
                    };
                    let is_toplevel_surface = hit
                        .as_ref()
                        .is_some_and(|s| matches!(s.toplevel, Some(InputSurfaceKind::Layout(_))));

                    // When the click lands directly on a toplevel's content
                    // surface (not on SSD / layer), smithay's pointer path
                    // forwards the click but does not raise the window or
                    // move keyboard focus to it. Do that here on press so
                    // clicking a background window both raises and focuses
                    // it, matching desktop conventions.
                    if is_toplevel_surface && state == ButtonState::Pressed {
                        if let Some(mut s) = hit {
                            if let Some(InputSurfaceKind::Layout((weak, _))) =
                                s.toplevel.take()
                            {
                                self.windows.set_focus(Some(FocusTarget::Layout(weak)));
                            }
                        }
                    }

                    let releasing_armed_ssd =
                        state == ButtonState::Released && self.input.armed_ssd.is_some();
                    let synthesize_touch = !is_toplevel_surface || releasing_armed_ssd;
                    if releasing_armed_ssd {
                        forward_to_client = false;
                    }

                    if synthesize_touch {
                        // Mirror of the CSD-drag fix in the `else if` branch
                        // below: when this synthesised touch-up is about to
                        // end an in-flight drag (release landed on an SSD
                        // strip or a layer surface like the dock), forward
                        // the matching button-up to the surface that started
                        // the drag BEFORE on_touch_up refreshes pointer
                        // focus. Skipping leaves the drag-originating client
                        // (Firefox / GTK4) stuck "button held" and the next
                        // pointer motion looks like a continuation of the
                        // previous drag.
                        if state == ButtonState::Released
                            && (self.windows.is_move_dragging()
                                || self.windows.is_resize_dragging())
                        {
                            let pointer = self.seat.get_pointer().unwrap();
                            pointer.button(
                                self,
                                &ButtonEvent { serial, time, button, state: state.into() },
                            );
                            pointer.frame(self);
                            forward_to_client = false;
                        }
                        let slot = TouchSlot::from(POINTER_SLOT);
                        let ty = if state == ButtonState::Pressed {
                            TouchEventType::Down
                        } else {
                            TouchEventType::Up
                        };
                        let ev = TouchEvent::from_pointer(ty, slot, 0, pos);
                        match ty {
                            TouchEventType::Down => self.on_touch_down(ev),
                            TouchEventType::Up => self.on_touch_up(ev),
                            TouchEventType::Motion => {}
                        }
                    } else if state == ButtonState::Released {
                        // CSD-initiated drags bypass the synthesised touch-up.
                        let drag_ended = if self.windows.is_resize_dragging() {
                            self.windows.end_resize_drag();
                            true
                        } else if self.windows.is_move_dragging() {
                            self.windows.end_move_drag();
                            true
                        } else {
                            false
                        };
                        if drag_ended {
                            self.request_redraw();
                            // Order matters: smithay's pointer is still
                            // focused on whatever surface received the press
                            // that initiated the drag (typically the CSD
                            // client's title bar, since SSD-initiated drags
                            // go through `on_touch_up` instead). Send the
                            // matching button-up to THAT focus first so the
                            // client gets a balanced press/release pair —
                            // without it Firefox / GTK4 stay stuck "button
                            // held" and the next pointer motion is
                            // interpreted as a continuation of the drag,
                            // which surfaces as "the previous window keeps
                            // moving when you grab a different one"
                            // (eydos issue #8).
                            let pointer = self.seat.get_pointer().unwrap();
                            pointer.button(
                                self,
                                &ButtonEvent { serial, time, button, state: state.into() },
                            );
                            pointer.frame(self);
                            forward_to_client = false; // already delivered
                            // Now replay a motion at the current cursor
                            // position so smithay flips pointer focus from
                            // the dragged surface to whatever is under the
                            // cursor — that unblocks the dock hover and
                            // routes the next click to the intended window.
                            self.on_pointer_motion(pos, time);
                        }
                    }

                    // SSD clicks are fully handled compositor-side — don't
                    // leak the button press to whatever client surface
                    // smithay's pointer was last focused on.
                    if on_ssd {
                        forward_to_client = false;
                    }
                    }
                }

                // Right-click on a window's SSD title bar opens the
                // window context menu. Forge owns the SSD geometry so
                // it does the hit-test; rendering the menu surface is
                // crustace's job (it already owns every other shell
                // overlay), so we ship a JSON IPC with the window id +
                // the cursor position and let crustace anchor its
                // menu there. The button press is swallowed (forge
                // doesn't fall through to the client) so the right-
                // click never confuses the application below the bar.
                if event.button() == Some(MouseButton::Right)
                    && state == ButtonState::Pressed
                    && !block_routing
                    && !self.windows.canvas().is_mobile()
                    && !self.windows.foreground_layer_at(self.input.touch_state.position)
                {
                    let pos = self.input.touch_state.position;
                    if let Some((window_rc, action)) = self.windows.ssd_window_hit_test(pos) {
                        if matches!(action, crate::ssd::SsdAction::Move) {
                            let surface_id = {
                                use smithay::reexports::wayland_server::Resource as _;
                                use crate::windows::surface::Surface as _;
                                window_rc.borrow().surface.surface().id().protocol_id()
                            };
                            let msg = format!(
                                r#"{{"action":"window_context_menu","window_id":{},"x":{:.1},"y":{:.1}}}"#,
                                surface_id, pos.x, pos.y,
                            );
                            Self::send_to_crustace(&msg);
                            forward_to_client = false;
                        }
                    }
                }

                if forward_to_client {
                    let pointer = self.seat.get_pointer().unwrap();
                    pointer.button(
                        self,
                        &ButtonEvent {
                            serial,
                            time,
                            button,
                            state: state.into(),
                        },
                    );
                    pointer.frame(self);
                }
            },
            InputEvent::PointerAxis { event, .. } => {
                let source = event.source();
                let horizontal_amount = event
                    .amount(smithay::backend::input::Axis::Horizontal)
                    .unwrap_or_else(|| event.amount_v120(smithay::backend::input::Axis::Horizontal).unwrap_or(0.0) * 3.0 / 120.0);
                let vertical_amount = event
                    .amount(smithay::backend::input::Axis::Vertical)
                    .unwrap_or_else(|| event.amount_v120(smithay::backend::input::Axis::Vertical).unwrap_or(0.0) * 3.0 / 120.0);

                {
                    let mut frame = AxisFrame::new(event.time_msec()).source(source);

                    if horizontal_amount != 0.0 {
                        frame = frame.value(smithay::backend::input::Axis::Horizontal, horizontal_amount);
                        if let Some(v120) = event.amount_v120(smithay::backend::input::Axis::Horizontal) {
                            frame = frame.v120(smithay::backend::input::Axis::Horizontal, v120 as i32);
                        }
                    }
                    if vertical_amount != 0.0 {
                        frame = frame.value(smithay::backend::input::Axis::Vertical, vertical_amount);
                        if let Some(v120) = event.amount_v120(smithay::backend::input::Axis::Vertical) {
                            frame = frame.v120(smithay::backend::input::Axis::Vertical, v120 as i32);
                        }
                    }

                    let pointer = self.seat.get_pointer().unwrap();
                    pointer.axis(self, frame);
                    pointer.frame(self);
                }
            },
            InputEvent::TouchDown { event } => {
                let pos = self.transform_position(&event);
                self.input.touch_state.events.push(TouchEvent::new(
                    TouchEventType::Down,
                    event.slot(),
                    event.time_msec(),
                    pos,
                ));
            },
            InputEvent::TouchUp { event } => {
                self.input.touch_state.events.push(TouchEvent::new(
                    TouchEventType::Up,
                    event.slot(),
                    event.time_msec(),
                    self.input.touch_state.position,
                ));
            },
            InputEvent::TouchMotion { event } => {
                let pos = self.transform_position(&event);
                self.input.touch_state.events.push(TouchEvent::new(
                    TouchEventType::Motion,
                    event.slot(),
                    event.time_msec(),
                    pos,
                ));
            },
            InputEvent::TouchFrame { .. } => {
                for ev in self.input.touch_state.events.drain(..).collect::<Vec<_>>() {
                    match ev.ty {
                        TouchEventType::Down => self.on_touch_down(ev),
                        TouchEventType::Up => self.on_touch_up(ev),
                        TouchEventType::Motion => self.on_touch_motion(ev),
                    }
                }
                self.seat.get_touch().unwrap().frame(self);
            },
            InputEvent::TouchCancel { event } => {
                self.input.touch_state.events.retain(|e| e.slot != event.slot());
                if self.input.armed_ssd.as_ref().is_some_and(|a| a.slot == event.slot()) {
                    self.input.armed_ssd = None;
                    self.windows.set_dirty();
                }
            },
            InputEvent::DeviceAdded { .. } | InputEvent::DeviceRemoved { .. } => {
                self.refresh_keyboard_presence();
            },
            _ => (),
        }

        self.unstall();
    }

    /// Without powermanagerd (which owns the lock delay) forge locks on wake.
    fn wake_display(&mut self) {
        if !crate::powermanager_dbus::available() {
            self.lock_session();
        }
        self.set_display_status(true);
    }

    fn handle_display_off_input<I: InputBackend>(&mut self, event: &InputEvent<I>) -> bool {
        match event {
            InputEvent::Keyboard { .. } => self.wake_display(),
            InputEvent::TouchDown { event } => {
                let pos = self.transform_position(event);
                self.input.touch_state.events.push(TouchEvent::new(
                    TouchEventType::Down,
                    event.slot(),
                    event.time_msec(),
                    pos,
                ));
            },
            InputEvent::TouchUp { event } => {
                self.input.touch_state.events.push(TouchEvent::new(
                    TouchEventType::Up,
                    event.slot(),
                    event.time_msec(),
                    self.input.touch_state.position,
                ));
            },
            InputEvent::TouchFrame { .. } => {
                for ev in self.input.touch_state.events.drain(..).collect::<Vec<_>>() {
                    match ev.ty {
                        TouchEventType::Down => {
                            self.input.touch_state.start(self.windows.canvas(), ev.slot, ev.position);
                        },
                        TouchEventType::Up if self.input.touch_state.slot.take() == Some(ev.slot) => {
                            match self.input.touch_state.action(self.windows.canvas()) {
                                Some(TouchAction::Tap) => {
                                    self.input.touch_state.last_tap = Some((Instant::now(), ev.position));
                                },
                                Some(TouchAction::DoubleTap) => self.wake_display(),
                                _ => (),
                            }
                        },
                        _ => (),
                    }
                }
                self.seat.get_touch().unwrap().frame(self);
            },
            InputEvent::PointerMotion { .. }
            | InputEvent::PointerMotionAbsolute { .. }
            | InputEvent::PointerButton { .. }
            | InputEvent::PointerAxis { .. } => self.wake_display(),
            _ => (),
        }

        true
    }

    fn transform_position<I, E>(&self, event: &E) -> Point<f64, Logical>
    where
        E: AbsolutePositionEvent<I>,
        I: InputBackend,
    {
        let canvas = self.windows.canvas();
        let res = canvas.resolution();
        let (mut x, mut y) = event.position_transformed(res).into();
        let (w, h) = res.to_f64().into();

        (x, y) = match canvas.orientation() {
            Orientation::Portrait => (x, y),
            Orientation::Landscape => (y, w - x),
            Orientation::InversePortrait => (w - x, h - y),
            Orientation::InverseLandscape => (h - y, x),
        };

        (x, y).into()
    }

}

enum InputAction {
    ChangeVt(i32),
    None,
}

impl From<InputAction> for FilterResult<InputAction> {
    fn from(a: InputAction) -> Self {
        FilterResult::Intercept(a)
    }
}

