// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:critical reason:network-protocol

#include "qwaylandpointer.h"
#include "qwaylandpointer_p.h"
#include <QtWaylandCompositor/QWaylandClient>
#include <QtWaylandCompositor/QWaylandCompositor>

QT_BEGIN_NAMESPACE

QWaylandSurfaceRole QWaylandPointerPrivate::s_role("wl_pointer");

QWaylandPointerPrivate::QWaylandPointerPrivate(QWaylandPointer *pointer, QWaylandSeat *seat)
    : seat(seat)
{
    Q_UNUSED(pointer);
}

uint QWaylandPointerPrivate::sendButton(Qt::MouseButton button, uint32_t state)
{
    Q_Q(QWaylandPointer);
    if (!q->mouseFocus() || !q->mouseFocus()->surface())
        return 0;

    wl_client *client = q->mouseFocus()->surface()->waylandClient();
    uint32_t time = compositor()->currentTimeMsecs();
    uint32_t serial = compositor()->nextSerial();
    const auto resources = resourceMap().values(client);
    for (auto resource : resources)
        send_button(resource->handle, serial, time, q->toWaylandButton(button), state);
    return serial;
}

void QWaylandPointerPrivate::sendMotion()
{
    Q_ASSERT(enteredSurface);
    uint32_t time = compositor()->currentTimeMsecs();
    wl_fixed_t x = wl_fixed_from_double(localPosition.x());
    wl_fixed_t y = wl_fixed_from_double(localPosition.y());
    const auto resources = resourceMap().values(enteredSurface->waylandClient());
    for (auto resource : resources)
        wl_pointer_send_motion(resource->handle, time, x, y);
}

void QWaylandPointerPrivate::sendEnter(QWaylandSurface *surface)
{
    Q_ASSERT(surface && !enteredSurface);
    enterSerial = compositor()->nextSerial();

    QWaylandKeyboard *keyboard = seat->keyboard();
    if (keyboard)
        keyboard->sendKeyModifiers(surface->client(), enterSerial);

    wl_fixed_t x = wl_fixed_from_double(localPosition.x());
    wl_fixed_t y = wl_fixed_from_double(localPosition.y());
    const auto resources = resourceMap().values(surface->waylandClient());
    for (auto resource : resources)
        send_enter(resource->handle, enterSerial, surface->resource(), x, y);

    enteredSurface = surface;
    enteredSurfaceDestroyListener.listenForDestruction(surface->resource());
}

void QWaylandPointerPrivate::sendLeave()
{
    Q_ASSERT(enteredSurface);
    uint32_t serial = compositor()->nextSerial();
    const auto resources = resourceMap().values(enteredSurface->waylandClient());
    for (auto resource : resources)
        send_leave(resource->handle, serial, enteredSurface->resource());
    localPosition = QPointF();
    enteredSurfaceDestroyListener.reset();
    enteredSurface = nullptr;
}

void QWaylandPointerPrivate::ensureEntered(QWaylandSurface *surface)
{
    if (enteredSurface == surface)
        return;

    if (enteredSurface)
        sendLeave();

    if (surface)
        sendEnter(surface);
}

void QWaylandPointerPrivate::pointer_release(wl_pointer::Resource *resource)
{
    wl_resource_destroy(resource->handle);
}

void QWaylandPointerPrivate::pointer_set_cursor(wl_pointer::Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
    Q_UNUSED(serial);

    if (!surface) {
        seat->cursorSurfaceRequested(nullptr, 0, 0, QWaylandClient::fromWlClient(compositor(), resource->client()));
        return;
    }

    QWaylandSurface *s = QWaylandSurface::fromResource(surface);
    // XXX FIXME
    // The role concept was formalized in wayland 1.7, so that release adds one error
    // code for each interface that implements a role, and we are supposed to pass here
    // the newly constructed resource and the correct error code so that if setting the
    // role fails, a proper error can be sent to the client.
    // However we're still using wayland 1.4, which doesn't have interface specific role
    // errors, so the best we can do is to use wl_display's object_id error.
    wl_resource *displayRes = wl_client_get_object(resource->client(), 1);
    if (s->setRole(&QWaylandPointerPrivate::s_role, displayRes, WL_DISPLAY_ERROR_INVALID_OBJECT)) {
        s->markAsCursorSurface(true);
        seat->cursorSurfaceRequested(s, hotspot_x, hotspot_y, QWaylandClient::fromWlClient(compositor(), resource->client()));
    }
}

/*!
 * \class QWaylandPointer
 * \inmodule QtWaylandCompositor
 * \since 5.8
 * \brief The QWaylandPointer class represents a pointer device.
 *
 * This class provides access to the pointer device in a QWaylandSeat. It corresponds to
 * the Wayland interface wl_pointer.
 */

/*!
 * Constructs a QWaylandPointer for the given \a seat and with the given \a parent.
 */
QWaylandPointer::QWaylandPointer(QWaylandSeat *seat, QObject *parent)
    : QWaylandObject(* new QWaylandPointerPrivate(this, seat), parent)
{
    connect(&d_func()->enteredSurfaceDestroyListener, &QWaylandDestroyListener::fired, this, &QWaylandPointer::enteredSurfaceDestroyed);
    connect(seat, &QWaylandSeat::mouseFocusChanged, this, &QWaylandPointer::pointerFocusChanged);
}

/*!
 * Returns the input device for this QWaylandPointer.
 */
QWaylandSeat *QWaylandPointer::seat() const
{
    Q_D(const QWaylandPointer);
    return d->seat;
}

/*!
 * Returns the compositor for this QWaylandPointer.
 */
QWaylandCompositor *QWaylandPointer::compositor() const
{
    Q_D(const QWaylandPointer);
    return d->compositor();
}

/*!
 * Returns the output for this QWaylandPointer.
 */
QWaylandOutput *QWaylandPointer::output() const
{
    Q_D(const QWaylandPointer);
    return d->output;
}

/*!
 * Sets the output for this QWaylandPointer to \a output.
 */
void QWaylandPointer::setOutput(QWaylandOutput *output)
{
    Q_D(QWaylandPointer);
    if (d->output == output) return;
    d->output = output;
    outputChanged();
}

/*!
 * Sends a mouse press event for \a button to the view currently holding mouse focus.
 *
 * Returns the serial number of the press event.
 */
uint QWaylandPointer::sendMousePressEvent(Qt::MouseButton button)
{
    Q_D(QWaylandPointer);
    d->buttonCount++;

    if (d->buttonCount == 1)
        emit buttonPressedChanged();

    return d->sendButton(button, WL_POINTER_BUTTON_STATE_PRESSED);
}

/*!
 * Sends a mouse release event for \a button to the view currently holding mouse focus.
 *
 * Returns the serial number of the release event.
 */
uint QWaylandPointer::sendMouseReleaseEvent(Qt::MouseButton button)
{
    Q_D(QWaylandPointer);
    d->buttonCount--;

    if (d->buttonCount == 0)
        emit buttonPressedChanged();

    return d->sendButton(button, WL_POINTER_BUTTON_STATE_RELEASED);
}

/*!
 * Sets the current mouse focus to \a view and sends a mouse move event to it with the
 * local position \a localPos in surface coordinates and output space position \a outputSpacePos.
 */
void QWaylandPointer::sendMouseMoveEvent(QWaylandView *view, const QPointF &localPos, const QPointF &outputSpacePos)
{
    Q_D(QWaylandPointer);
    if (view && (!view->surface() || view->surface()->isCursorSurface()))
        view = nullptr;
    d->seat->setMouseFocus(view);
    d->localPosition = localPos;
    d->spacePosition = outputSpacePos;

    if (view) {
        // We adjust if the mouse position is on the edge
        // to work around Qt's event propagation
        QSizeF size(view->surface()->destinationSize());
        if (d->localPosition.x() == size.width())
            d->localPosition.rx() -= 0.01;
        if (d->localPosition.y() == size.height())
            d->localPosition.ry() -= 0.01;

        d->ensureEntered(view->surface());
        d->sendMotion();

        if (view->output())
            setOutput(view->output());
    }
}

/*!
 * Sends a mouse wheel event with the given \a orientation and \a delta to the view that currently holds mouse focus.
 */
void QWaylandPointer::sendMouseWheelEvent(Qt::Orientation orientation, int delta)
{
    Q_D(QWaylandPointer);
    if (!d->enteredSurface)
        return;

    uint32_t time = d->compositor()->currentTimeMsecs();
    uint32_t axis = orientation == Qt::Horizontal ? WL_POINTER_AXIS_HORIZONTAL_SCROLL
                                                  : WL_POINTER_AXIS_VERTICAL_SCROLL;

    const auto resources = d->resourceMap().values(d->enteredSurface->waylandClient());
    for (auto resource : resources)
        d->send_axis(resource->handle, time, axis, wl_fixed_from_int(-delta / 12));
}

/*!
 * Returns the view that currently holds mouse focus.
 */
QWaylandView *QWaylandPointer::mouseFocus() const
{
    Q_D(const QWaylandPointer);
    return d->seat->mouseFocus();
}

/*!
 * Returns the current local position of the QWaylandPointer in surface coordinates.
 */
QPointF QWaylandPointer::currentLocalPosition() const
{
    Q_D(const QWaylandPointer);
    return d->localPosition;
}

/*!
 * Returns the current output space position of the QWaylandPointer.
 */
QPointF QWaylandPointer::currentSpacePosition() const
{
    Q_D(const QWaylandPointer);
    return d->spacePosition;
}

/*!
 * \property QWaylandPointer::isButtonPressed
 *
 * This property holds whether any mouse button is currently pressed.
 *
 * The value is \c true if at least one mouse button is pressed; otherwise
 * \c false.
 */

/*!
 * Returns true if any button is currently pressed. Otherwise returns false.
 */
bool QWaylandPointer::isButtonPressed() const
{
    Q_D(const QWaylandPointer);
    return d->buttonCount > 0;
}

/*!
 * \internal
 */
void QWaylandPointer::addClient(QWaylandClient *client, uint32_t id, uint32_t version)
{
    Q_D(QWaylandPointer);
    wl_resource *resource = d->add(client->client(), id, qMin<uint32_t>(QtWaylandServer::wl_pointer::interfaceVersion(), version))->handle;
    if (d->enteredSurface && client == d->enteredSurface->client()) {
        d->send_enter(resource, d->enterSerial, d->enteredSurface->resource(),
                      wl_fixed_from_double(d->localPosition.x()),
                      wl_fixed_from_double(d->localPosition.y()));
    }
}

/*!
 * Returns a Wayland resource for this QWaylandPointer.
 *
 * This API doesn't actually make sense, since there may be many pointer resources per client
 * It's here for compatibility reasons.
 */
struct wl_resource *QWaylandPointer::focusResource() const
{
    Q_D(const QWaylandPointer);
    QWaylandView *focus = d->seat->mouseFocus();
    if (!focus)
        return nullptr;

    // Just return the first resource we can find.
    return d->resourceMap().value(focus->surface()->waylandClient())->handle;
}

/*!
 * \internal
 */
uint QWaylandPointer::sendButton(struct wl_resource *resource, uint32_t time, Qt::MouseButton button, uint32_t state)
{
    // This method is here for compatibility reasons only, since it usually doesn't make sense to
    // send button events to just one of the pointer resources for a client.
    Q_D(QWaylandPointer);
    uint32_t serial = d->compositor()->nextSerial();
    d->send_button(resource, serial, time, toWaylandButton(button), state);
    return serial;
}

/*!
 * \internal
 */
uint32_t QWaylandPointer::toWaylandButton(Qt::MouseButton button)
{
#ifndef BTN_LEFT
    uint32_t BTN_LEFT = 0x110;
#endif
    // the range of valid buttons (evdev module) is from 0x110
    // through 0x11f. 0x120 is the first 'Joystick' button.
    switch (button) {
    case Qt::LeftButton: return BTN_LEFT;
    case Qt::RightButton: return uint32_t(0x111);
    case Qt::MiddleButton: return uint32_t(0x112);
    case Qt::ExtraButton1: return uint32_t(0x113);  // AKA Qt::BackButton, Qt::XButton1
    case Qt::ExtraButton2: return uint32_t(0x114);  // AKA Qt::ForwardButton, Qt::XButton2
    case Qt::ExtraButton3: return uint32_t(0x115);
    case Qt::ExtraButton4: return uint32_t(0x116);
    case Qt::ExtraButton5: return uint32_t(0x117);
    case Qt::ExtraButton6: return uint32_t(0x118);
    case Qt::ExtraButton7: return uint32_t(0x119);
    case Qt::ExtraButton8: return uint32_t(0x11a);
    case Qt::ExtraButton9: return uint32_t(0x11b);
    case Qt::ExtraButton10: return uint32_t(0x11c);
    case Qt::ExtraButton11: return uint32_t(0x11d);
    case Qt::ExtraButton12: return uint32_t(0x11e);
    case Qt::ExtraButton13: return uint32_t(0x11f);
        // default should not occur; but if it does, then return Wayland's highest possible button number.
    default: return uint32_t(0x11f);
    }
}

/*!
 * \internal
 */
void QWaylandPointer::enteredSurfaceDestroyed(void *data)
{
    Q_D(QWaylandPointer);
    Q_UNUSED(data);
    d->enteredSurfaceDestroyListener.reset();
    d->enteredSurface = nullptr;

    d->seat->setMouseFocus(nullptr);

    if (d->buttonCount != 0) {
        d->buttonCount = 0;
        emit buttonPressedChanged();
    }
}

/*!
 * \internal
 */
void QWaylandPointer::pointerFocusChanged(QWaylandView *newFocus, QWaylandView *oldFocus)
{
    Q_D(QWaylandPointer);
    Q_UNUSED(oldFocus);
    bool wasSameSurface = newFocus && newFocus->surface() == d->enteredSurface;
    if (d->enteredSurface && !wasSameSurface)
        d->sendLeave();
}

QT_END_NAMESPACE

#include "moc_qwaylandpointer.cpp"
