/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
    SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
    SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "config-kwin.h"

#include "core/inputdevice.h"
#include <QObject>
#include <QPoint>
#include <QPointer>

#include <KConfigWatcher>
#include <KSharedConfig>
#include <QSet>

#include <functional>

class KGlobalAccelInterface;
class QAction;
class QKeySequence;
class QMouseEvent;
class QKeyEvent;
class QWheelEvent;

namespace KWin
{
class IdleDetector;
class Window;
class GlobalShortcutsManager;
class InputEventFilter;
class InputEventSpy;
class KeyboardInputRedirection;
class PointerInputRedirection;
class SeatInterface;
class TabletInputRedirection;
class TouchInputRedirection;
class WindowSelectorFilter;
struct SwitchEvent;
struct TabletToolTipEvent;
struct TabletToolAxisEvent;
struct PointerAxisEvent;
struct PointerButtonEvent;
struct PointerMotionEvent;
struct PointerSwipeGestureBeginEvent;
struct PointerSwipeGestureUpdateEvent;
struct PointerSwipeGestureEndEvent;
struct PointerSwipeGestureCancelEvent;
struct PointerPinchGestureBeginEvent;
struct PointerPinchGestureUpdateEvent;
struct PointerPinchGestureEndEvent;
struct PointerPinchGestureCancelEvent;
struct PointerHoldGestureBeginEvent;
struct PointerHoldGestureEndEvent;
struct PointerHoldGestureCancelEvent;
struct KeyboardKeyEvent;
struct TabletToolProximityEvent;
struct TabletToolTipEvent;
struct TabletToolButtonEvent;
struct TabletPadButtonEvent;
struct TabletPadStripEvent;
struct TabletPadRingEvent;
struct TabletPadDialEvent;
struct TouchDownEvent;
struct TouchMotionEvent;
struct TouchUpEvent;

namespace Decoration
{
class DecoratedWindowImpl;
}

class InputBackend;
class InputDevice;

/**
 * @brief This class is responsible for redirecting incoming input to the surface which currently
 * has input or send enter/leave events.
 *
 * In addition input is intercepted before passed to the surfaces to have KWin internal areas
 * getting input first (e.g. screen edges) and filter the input event out if we currently have
 * a full input grab.
 */
class KWIN_EXPORT InputRedirection : public QObject
{
    Q_OBJECT
public:
    ~InputRedirection() override;
    void init();

    /**
     * @return const QPointF& The current global pointer position
     */
    QPointF globalPointer() const;
    Qt::MouseButtons qtButtonStates() const;
    Qt::KeyboardModifiers keyboardModifiers() const;
    Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const;

    void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action);
    void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action);
    void registerTouchpadSwipeShortcut(SwipeDirection direction, uint32_t fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback = {});
    void registerTouchpadPinchShortcut(PinchDirection direction, uint32_t fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback = {});
    void registerTouchscreenSwipeShortcut(SwipeDirection direction, uint32_t fingerCount, QAction *action, std::function<void(qreal)> progressCallback = {});
    void forceRegisterTouchscreenSwipeShortcut(SwipeDirection direction, uint32_t fingerCount, QAction *action, std::function<void(qreal)> progressCallback = {});

    bool supportsPointerWarping() const;
    void warpPointer(const QPointF &pos);

    std::optional<QPointF> implicitGrabPositionBySerial(SeatInterface *seat, uint32_t serial) const;

    void installInputEventFilter(InputEventFilter *filter);
    void uninstallInputEventFilter(InputEventFilter *filter);

    /**
     * Installs the @p spy for spying on events.
     */
    void installInputEventSpy(InputEventSpy *spy);

    /**
     * Uninstalls the @p spy. This happens automatically when deleting an InputEventSpy.
     */
    void uninstallInputEventSpy(InputEventSpy *spy);

    void simulateUserActivity();

    void addIdleDetector(IdleDetector *detector);
    void removeIdleDetector(IdleDetector *detector);

    QList<Window *> idleInhibitors() const;
    void addIdleInhibitor(Window *inhibitor);
    void removeIdleInhibitor(Window *inhibitor);

    Window *findToplevel(const QPointF &pos);
    GlobalShortcutsManager *shortcuts() const
    {
        return m_shortcuts;
    }

    /**
     * Sends an event through all InputFilters.
     * The method is invoked on each input filter. Processing is stopped if
     * a filter returns @c true for it
     */
    void processFilters(auto method, const auto &...args)
    {
        for (const auto filter : std::as_const(m_filters)) {
            if ((filter->*method)(args...)) {
                return;
            }
        }
    }

    /**
     * Sends an event through all input event spies.
     * The method is invoked on each InputEventSpy.
     */
    void processSpies(auto method, const auto &...args)
    {
        for (const auto spy : std::as_const(m_spies)) {
            (spy->*method)(args...);
        }
    }

    KeyboardInputRedirection *keyboard() const
    {
        return m_keyboard;
    }
    PointerInputRedirection *pointer() const
    {
        return m_pointer;
    }
    TabletInputRedirection *tablet() const
    {
        return m_tablet;
    }
    TouchInputRedirection *touch() const
    {
        return m_touch;
    }

    /**
     * Specifies which was the device that triggered the last input event
     */
    void setLastInputHandler(QObject *device);
    QObject *lastInputHandler() const;

    void setLastInteractionSerial(uint32_t serial);
    uint32_t lastInteractionSerial() const;

    QList<InputDevice *> devices() const;

    bool hasAlphaNumericKeyboard();
    bool hasPointer() const;
    bool hasTouch() const;
    bool hasTabletModeSwitch();

    void startInteractiveWindowSelection(std::function<void(KWin::Window *)> callback, const QByteArray &cursorName);
    void startInteractivePositionSelection(std::function<void(const QPoint &)> callback);
    bool isSelectingWindow() const;

    void addInputDevice(InputDevice *device);
    void removeInputDevice(InputDevice *device);
    void addInputBackend(std::unique_ptr<InputBackend> &&inputBackend);

Q_SIGNALS:
    void deviceAdded(InputDevice *device);
    void deviceRemoved(InputDevice *device);
    /**
     * @brief Emitted when the global pointer position changed
     *
     * @param pos The new global pointer position.
     */
    void globalPointerChanged(const QPointF &pos);
    /**
     * @brief Emitted when the state of a pointer button changed.
     *
     * @param button The button which changed
     * @param state The new button state
     */
    void pointerButtonStateChanged(uint32_t button, PointerButtonState state);
    /**
     * @brief Emitted when a pointer axis changed
     *
     * @param axis The axis on which the even occurred
     * @param delta The delta of the event.
     */
    void pointerAxisChanged(PointerAxis axis, qreal delta);
    /**
     * @brief Emitted when the modifiers changes.
     *
     * Only emitted for the mask which is provided by Qt::KeyboardModifiers, if other modifiers
     * change signal is not emitted
     *
     * @param newMods The new modifiers state
     * @param oldMods The previous modifiers state
     */
    void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods);
    /**
     * @brief Emitted when the state of a key changed.
     *
     * @param keyCode The keycode of the key which changed
     * @param state The new key state
     */
    void keyStateChanged(quint32 keyCode, KeyboardKeyState state);

    void hasKeyboardChanged(bool set);
    void hasAlphaNumericKeyboardChanged(bool set);
    void hasPointerChanged(bool set);
    void hasTouchChanged(bool set);
    void hasTabletModeSwitchChanged(bool set);

private Q_SLOTS:
    void handleInputConfigChanged(const KConfigGroup &group);
    void updateScreens();

private:
    void setupInputBackends();
    void setupWorkspace();
    void setupInputFilters();
    void updateLeds(LEDs leds);
    void updateAvailableInputDevices();
    KeyboardInputRedirection *m_keyboard;
    PointerInputRedirection *m_pointer;
    TabletInputRedirection *m_tablet;
    TouchInputRedirection *m_touch;
    QObject *m_lastInputDevice = nullptr;
    uint32_t m_lastInteractionSerial = 0;

    GlobalShortcutsManager *m_shortcuts;

    std::vector<std::unique_ptr<InputBackend>> m_inputBackends;
    QList<InputDevice *> m_inputDevices;

    QList<IdleDetector *> m_idleDetectors;
    QList<Window *> m_idleInhibitors;
    std::unique_ptr<WindowSelectorFilter> m_windowSelector;

    QList<InputEventFilter *> m_filters;
    QList<InputEventSpy *> m_spies;
    KConfigWatcher::Ptr m_inputConfigWatcher;

    std::unique_ptr<InputEventFilter> m_virtualTerminalFilter;
    std::unique_ptr<InputEventFilter> m_dragAndDropFilter;
#if KWIN_BUILD_SCREENLOCKER
    std::unique_ptr<InputEventFilter> m_lockscreenFilter;
#endif
    std::unique_ptr<InputEventFilter> m_screenEdgeFilter;
    std::unique_ptr<InputEventFilter> m_tabboxFilter;
    std::unique_ptr<InputEventFilter> m_globalShortcutFilter;
    std::unique_ptr<InputEventFilter> m_effectsFilter;
    std::unique_ptr<InputEventFilter> m_interactiveMoveResizeFilter;
    std::unique_ptr<InputEventFilter> m_popupFilter;
    std::unique_ptr<InputEventFilter> m_decorationFilter;
    std::unique_ptr<InputEventFilter> m_windowActionFilter;
    std::unique_ptr<InputEventFilter> m_internalWindowFilter;
    std::unique_ptr<InputEventFilter> m_inputKeyboardFilter;
    std::unique_ptr<InputEventFilter> m_forwardFilter;

    std::unique_ptr<InputEventSpy> m_hideCursorSpy;
    std::unique_ptr<InputEventSpy> m_userActivitySpy;

    LEDs m_leds;
    bool m_hasKeyboard = false;
    bool m_hasPointer = false;
    bool m_hasTouch = false;
    bool m_hasTabletModeSwitch = false;

    KWIN_SINGLETON(InputRedirection)
    friend InputRedirection *input();
    friend class DecorationEventFilter;
    friend class InternalWindowEventFilter;
    friend class ForwardInputFilter;
};

namespace InputFilterOrder
{
enum Order {
    PlaceholderOutput,
    Dpms,
    ButtonRebind,
    SlowKeys,
    BounceKeys,
    StickyKeys,
    MouseKeys,
    EisInput,

    VirtualTerminal,
    LockScreen,
    ScreenEdge,
    DragAndDrop,
    WindowSelector,
    TabBox,
    GlobalShortcut,
    Effects,
    InteractiveMoveResize,
    Popup,
    Decoration,
    WindowAction,
    XWayland,
    InternalWindow,
    InputMethod,
    Forward,
};
}

/**
 * Base class for filtering input events inside InputRedirection.
 *
 * The idea behind the InputEventFilter is to have task oriented
 * filters. E.g. there is one filter taking care of a locked screen,
 * one to take care of interacting with window decorations, etc.
 *
 * A concrete subclass can reimplement the virtual methods and decide
 * whether an event should be filtered out or not by returning either
 * @c true or @c false. E.g. the lock screen filter can easily ensure
 * that all events are filtered out.
 *
 * As soon as a filter returns @c true the processing is stopped. If
 * a filter returns @c false the next one is invoked. This means a filter
 * installed early gets to see more events than a filter installed later on.
 *
 * Deleting an instance of InputEventFilter automatically uninstalls it from
 * InputRedirection.
 */
class KWIN_EXPORT InputEventFilter
{
public:
    /**
     * Construct and install the InputEventFilter
     * @param weight The position in the input chain, lower values come first.
     * @note the filter is not installed automatically
     */
    InputEventFilter(InputFilterOrder::Order weight);
    /**
     * @brief ~InputEventFilter
     * This will uninstall the event filter if needed
     */
    virtual ~InputEventFilter();

    /**
     * The position in the input chain, lower values come first.
     */
    int weight() const;

    virtual bool pointerMotion(PointerMotionEvent *event);
    virtual bool pointerButton(PointerButtonEvent *event);
    virtual bool pointerFrame();
    /**
     * Event filter for pointer axis events.
     *
     * @param event The event information about the axis event
     * @return @c true to stop further event processing, @c false to pass to next filter
     */
    virtual bool pointerAxis(PointerAxisEvent *event);
    /**
     * Event filter for keyboard events.
     *
     * @param event The event information about the key event
     * @return @c true to stop further event processing, @c false to pass to next filter.
     */
    virtual bool keyboardKey(KeyboardKeyEvent *event);
    virtual bool touchDown(TouchDownEvent *event);
    virtual bool touchMotion(TouchMotionEvent *event);
    virtual bool touchUp(TouchUpEvent *event);
    virtual bool touchCancel();
    virtual bool touchFrame();

    virtual bool pinchGestureBegin(PointerPinchGestureBeginEvent *event);
    virtual bool pinchGestureUpdate(PointerPinchGestureUpdateEvent *event);
    virtual bool pinchGestureEnd(PointerPinchGestureEndEvent *event);
    virtual bool pinchGestureCancelled(PointerPinchGestureCancelEvent *event);

    virtual bool swipeGestureBegin(PointerSwipeGestureBeginEvent *event);
    virtual bool swipeGestureUpdate(PointerSwipeGestureUpdateEvent *event);
    virtual bool swipeGestureEnd(PointerSwipeGestureEndEvent *event);
    virtual bool swipeGestureCancelled(PointerSwipeGestureCancelEvent *event);

    virtual bool holdGestureBegin(PointerHoldGestureBeginEvent *event);
    virtual bool holdGestureEnd(PointerHoldGestureEndEvent *event);
    virtual bool holdGestureCancelled(PointerHoldGestureCancelEvent *event);

    virtual bool switchEvent(SwitchEvent *event);

    virtual bool tabletToolProximityEvent(TabletToolProximityEvent *event);
    virtual bool tabletToolAxisEvent(TabletToolAxisEvent *event);
    virtual bool tabletToolTipEvent(TabletToolTipEvent *event);
    virtual bool tabletToolButtonEvent(TabletToolButtonEvent *event);
    virtual bool tabletPadButtonEvent(TabletPadButtonEvent *event);
    virtual bool tabletPadStripEvent(TabletPadStripEvent *event);
    virtual bool tabletPadRingEvent(TabletPadRingEvent *event);
    virtual bool tabletPadDialEvent(TabletPadDialEvent *event);

protected:
    bool passToInputMethod(KeyboardKeyEvent *event);

private:
    int m_weight = 0;
};

class KWIN_EXPORT InputDeviceHandler : public QObject
{
    Q_OBJECT
public:
    ~InputDeviceHandler() override;
    virtual void init();

    void update();

    /**
     * @brief First Window currently at the position of the input device
     * according to the stacking order.
     * @return Window* at device position.
     *
     * This will be null if no window is at the position
     */
    Window *hover() const;
    /**
     * @brief Window currently having pointer input focus (this might
     * be different from the Window at the position of the pointer).
     * @return Window* with pointer focus.
     *
     * This will be null if no window has focus
     */
    Window *focus() const;

    /**
     * @brief The Decoration currently receiving events.
     * @return decoration with pointer focus.
     */
    Decoration::DecoratedWindowImpl *decoration() const;

    virtual QPointF position() const = 0;

    void setFocus(Window *window);
    void setDecoration(Decoration::DecoratedWindowImpl *decoration);

Q_SIGNALS:
    void decorationChanged();

protected:
    explicit InputDeviceHandler(InputRedirection *parent);

    virtual void cleanupDecoration(Decoration::DecoratedWindowImpl *old, Decoration::DecoratedWindowImpl *now) = 0;

    virtual void focusUpdate(Window *old, Window *now) = 0;

    /**
     * Certain input devices can be in a state of having no valid
     * position. An example are touch screens when no finger/pen
     * is resting on the surface (no touch point).
     */
    virtual bool positionValid() const
    {
        return true;
    }
    virtual bool focusUpdatesBlocked()
    {
        return false;
    }

    inline bool inited() const
    {
        return m_inited;
    }
    inline void setInited(bool set)
    {
        m_inited = set;
    }

private:
    bool setHover(Window *window);
    void updateFocus();
    void updateDecoration();

    struct
    {
        QPointer<Window> window;
        QMetaObject::Connection surfaceCreatedConnection;
    } m_hover;

    struct
    {
        QPointer<Window> window;
        QPointer<Decoration::DecoratedWindowImpl> decoration;
    } m_focus;

    bool m_inited = false;
};

inline InputRedirection *input()
{
    return InputRedirection::s_self;
}

inline QList<InputDevice *> InputRedirection::devices() const
{
    return m_inputDevices;
}

} // namespace KWin
