/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qwindowstabletsupport.h"

#include "qwindowscontext.h"
#include "qwindowskeymapper.h"
#include "qwindowswindow.h"
#include "qwindowsscreen.h"

#include <qpa/qwindowsysteminterface.h>

#include <QtGui/qevent.h>
#include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
#include <QtCore/qdebug.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qmath.h>

#include <private/qguiapplication_p.h>
#include <QtCore/private/qsystemlibrary_p.h>

// Note: The definition of the PACKET structure in pktdef.h depends on this define.
#define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_Z | PK_TIME)
#include <pktdef.h>

QT_BEGIN_NAMESPACE

enum {
    PacketMode = 0,
    TabletPacketQSize = 128,
    DeviceIdMask = 0xFF6, // device type mask && device color mask
    CursorTypeBitMask = 0x0F06 // bitmask to find the specific cursor type (see Wacom FAQ)
};

extern "C" LRESULT QT_WIN_CALLBACK qWindowsTabletSupportWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WT_PROXIMITY:
        if (QWindowsContext::instance()->tabletSupport()->translateTabletProximityEvent(wParam, lParam))
            return 0;
        break;
    case WT_PACKET:
        if (QWindowsContext::instance()->tabletSupport()->translateTabletPacketEvent())
            return 0;
        break;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}


// Scale tablet coordinates to screen coordinates.

static inline int sign(int x)
{
    return x >= 0 ? 1 : -1;
}

inline QPointF QWindowsTabletDeviceData::scaleCoordinates(int coordX, int coordY, const QRect &targetArea) const
{
    const int targetX = targetArea.x();
    const int targetY = targetArea.y();
    const int targetWidth = targetArea.width();
    const int targetHeight = targetArea.height();

    const qreal x = sign(targetWidth) == sign(maxX) ?
        ((coordX - minX) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX :
        ((qAbs(maxX) - (coordX - minX)) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX;

    const qreal y = sign(targetHeight) == sign(maxY) ?
        ((coordY - minY) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY :
        ((qAbs(maxY) - (coordY - minY)) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY;

    return {x, y};
}

template <class Stream>
static void formatOptions(Stream &str, unsigned options)
{
    if (options & CXO_SYSTEM)
        str << " CXO_SYSTEM";
    if (options & CXO_PEN)
        str << " CXO_PEN";
    if (options & CXO_MESSAGES)
        str << " CXO_MESSAGES";
    if (options & CXO_MARGIN)
        str << " CXO_MARGIN";
    if (options & CXO_MGNINSIDE)
        str << " CXO_MGNINSIDE";
    if (options & CXO_CSRMESSAGES)
        str << " CXO_CSRMESSAGES";
}

#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t)
{
    QDebugStateSaver saver(d);
    d.nospace();
    d << "TabletDevice id:" << t.uniqueId << " pressure: " << t.minPressure
      << ".." << t.maxPressure << " tan pressure: " << t.minTanPressure << ".."
      << t.maxTanPressure << " area: (" << t.minX << ',' << t.minY << ',' << t.minZ
      << ")..(" << t.maxX << ',' << t.maxY << ',' << t.maxZ << ") device "
      << t.currentDevice << " pointer " << t.currentPointerType;
    return d;
}

QDebug operator<<(QDebug d, const LOGCONTEXT &lc)
{
    QDebugStateSaver saver(d);
    d.nospace();
    d << "LOGCONTEXT(\"" << QString::fromWCharArray(lc.lcName) << "\", options=0x"
        << hex << lc.lcOptions << dec;
    formatOptions(d, lc.lcOptions);
    d << ", status=0x" << hex << lc.lcStatus << ", device=0x" << lc.lcDevice
        << dec << ", PktRate=" << lc.lcPktRate
        << ", PktData=" << lc.lcPktData << ", PktMode=" << lc.lcPktMode
        << ", MoveMask=0x" << hex << lc.lcMoveMask << ", BtnDnMask=0x" << lc.lcBtnDnMask
        << ", BtnUpMask=0x" << lc.lcBtnUpMask << dec << ", SysMode=" << lc.lcSysMode
        << ", InOrg=(" << lc.lcInOrgX << ", " << lc.lcInOrgY << ", " << lc.lcInOrgZ
        <<  "), InExt=(" << lc.lcInExtX << ", " << lc.lcInExtY << ", " << lc.lcInExtZ
        << ") OutOrg=(" << lc.lcOutOrgX << ", " << lc.lcOutOrgY << ", "
        << lc.lcOutOrgZ <<  "), OutExt=(" << lc.lcOutExtX << ", " << lc.lcOutExtY
        << ", " << lc.lcOutExtZ
        << "), Sens=(" << lc.lcSensX << ", " << lc.lcSensX << ", " << lc.lcSensZ
        << ") SysOrg=(" << lc.lcSysOrgX << ", " << lc.lcSysOrgY
        << "), SysExt=(" << lc.lcSysExtX << ", " << lc.lcSysExtY
        << "), SysSens=(" << lc.lcSysSensX << ", " << lc.lcSysSensY << "))";
    return d;
}
#endif // !QT_NO_DEBUG_STREAM

QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL;

/*!
    \class QWindowsWinTab32DLL QWindowsTabletSupport
    \brief Functions from wintabl32.dll shipped with WACOM tablets used by QWindowsTabletSupport.

    \internal
    \ingroup qt-lighthouse-win
*/

bool QWindowsWinTab32DLL::init()
{
    if (wTInfo)
        return true;
    QSystemLibrary library(QStringLiteral("wintab32"));
    if (!library.load())
        return false;
    wTOpen = (PtrWTOpen)library.resolve("WTOpenW");
    wTClose = (PtrWTClose)library.resolve("WTClose");
    wTInfo = (PtrWTInfo)library.resolve("WTInfoW");
    wTEnable = (PtrWTEnable)library.resolve("WTEnable");
    wTOverlap = (PtrWTEnable)library.resolve("WTOverlap");
    wTPacketsGet = (PtrWTPacketsGet)library.resolve("WTPacketsGet");
    wTGet = (PtrWTGet)library.resolve("WTGetW");
    wTQueueSizeGet = (PtrWTQueueSizeGet)library.resolve("WTQueueSizeGet");
    wTQueueSizeSet = (PtrWTQueueSizeSet)library.resolve("WTQueueSizeSet");
    return wTOpen && wTClose && wTInfo && wTEnable && wTOverlap && wTPacketsGet && wTQueueSizeGet && wTQueueSizeSet;
}

/*!
    \class QWindowsTabletSupport
    \brief Tablet support for Windows.

    Support for WACOM tablets.

    \sa http://www.wacomeng.com/windows/docs/Wintab_v140.htm

    \internal
    \since 5.2
    \ingroup qt-lighthouse-win
*/

QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context)
    : m_window(window)
    , m_context(context)
{
     AXIS orientation[3];
    // Some tablets don't support tilt, check if it is possible,
    if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation))
        m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution;
}

QWindowsTabletSupport::~QWindowsTabletSupport()
{
    QWindowsTabletSupport::m_winTab32DLL.wTClose(m_context);
    DestroyWindow(m_window);
}

QWindowsTabletSupport *QWindowsTabletSupport::create()
{
    if (!m_winTab32DLL.init())
        return nullptr;
    const HWND window = QWindowsContext::instance()->createDummyWindow(QStringLiteral("TabletDummyWindow"),
                                                                       L"TabletDummyWindow",
                                                                       qWindowsTabletSupportWndProc);
    if (!window) {
        qCWarning(lcQpaTablet) << __FUNCTION__ << "Unable to create window for tablet.";
        return nullptr;
    }
    LOGCONTEXT lcMine;
    // build our context from the default context
    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine);
    qCDebug(lcQpaTablet) << "Default: " << lcMine;
    // Go for the raw coordinates, the tablet event will return good stuff
    lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES;
    lcMine.lcPktData = lcMine.lcMoveMask = PACKETDATA;
    lcMine.lcPktMode = PacketMode;
    lcMine.lcOutOrgX = 0;
    lcMine.lcOutExtX = lcMine.lcInExtX;
    lcMine.lcOutOrgY = 0;
    lcMine.lcOutExtY = -lcMine.lcInExtY;
    qCDebug(lcQpaTablet) << "Requesting: " << lcMine;
    const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true);
    if (!context) {
        qCDebug(lcQpaTablet) << __FUNCTION__ << "Unable to open tablet.";
        DestroyWindow(window);
        return nullptr;

    }
    // Set the size of the Packet Queue to the correct size
    const int currentQueueSize = QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeGet(context);
    if (currentQueueSize != TabletPacketQSize) {
        if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, TabletPacketQSize)) {
            if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, currentQueueSize))  {
                qWarning("Unable to set queue size on tablet. The tablet will not work.");
                QWindowsTabletSupport::m_winTab32DLL.wTClose(context);
                DestroyWindow(window);
                return nullptr;
            } // cannot restore old size
        } // cannot set
    } // mismatch
    qCDebug(lcQpaTablet) << "Opened tablet context " << context << " on window "
        <<  window << "changed packet queue size " << currentQueueSize
        << "->" <<  TabletPacketQSize << "\nobtained: " << lcMine;
    return new QWindowsTabletSupport(window, context);
}

unsigned QWindowsTabletSupport::options() const
{
    UINT result = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_CTXOPTIONS, &result);
    return result;
}

QString QWindowsTabletSupport::description() const
{
    const unsigned size = m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, nullptr);
    if (!size)
        return QString();
    QVarLengthArray<TCHAR> winTabId(size + 1);
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, winTabId.data());
    WORD implementationVersion = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_IMPLVERSION, &implementationVersion);
    WORD specificationVersion = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_SPECVERSION, &specificationVersion);
    const unsigned opts = options();
    WORD devices = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NDEVICES, &devices);
    WORD cursors = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NCURSORS, &cursors);
    WORD extensions = 0;
    m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_NEXTENSIONS, &extensions);
    QString result;
    QTextStream str(&result);
    str << '"' << QString::fromWCharArray(winTabId.data())
        << "\" specification: v" << (specificationVersion >> 8)
        << '.' << (specificationVersion & 0xFF) << " implementation: v"
        << (implementationVersion >> 8) << '.' << (implementationVersion & 0xFF)
        << ' ' << devices << " device(s), " << cursors << " cursor(s), "
        << extensions << " extensions" << ", options: 0x" << hex << opts << dec;
    formatOptions(str, opts);
    if (m_tiltSupport)
        str << " tilt";
    return result;
}

void QWindowsTabletSupport::notifyActivate()
{
    // Cooperate with other tablet applications, but when we get focus, I want to use the tablet.
    const bool result = QWindowsTabletSupport::m_winTab32DLL.wTEnable(m_context, true)
        && QWindowsTabletSupport::m_winTab32DLL.wTOverlap(m_context, true);
   qCDebug(lcQpaTablet) << __FUNCTION__ << result;
}

static inline int indexOfDevice(const QVector<QWindowsTabletDeviceData> &devices, qint64 uniqueId)
{
    for (int i = 0; i < devices.size(); ++i)
        if (devices.at(i).uniqueId == uniqueId)
            return i;
    return -1;
}

static inline QTabletEvent::TabletDevice deviceType(const UINT cursorType)
{
    if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902))
        return QTabletEvent::Stylus;
    if (cursorType == 0x4020) // Surface Pro 2 tablet device
        return QTabletEvent::Stylus;
    switch (cursorType & CursorTypeBitMask) {
    case 0x0802:
        return QTabletEvent::Stylus;
    case 0x0902:
        return QTabletEvent::Airbrush;
    case 0x0004:
        return QTabletEvent::FourDMouse;
    case 0x0006:
        return QTabletEvent::Puck;
    case 0x0804:
        return QTabletEvent::RotationStylus;
    default:
        break;
    }
    return QTabletEvent::NoDevice;
}

static inline QTabletEvent::PointerType pointerType(unsigned currentCursor)
{
    switch (currentCursor % 3) { // %3 for dual track
    case 0:
        return QTabletEvent::Cursor;
    case 1:
        return QTabletEvent::Pen;
    case 2:
        return QTabletEvent::Eraser;
    default:
        break;
    }
    return QTabletEvent::UnknownPointer;
}

QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT cursorType) const
{
    QWindowsTabletDeviceData result;
    result.uniqueId = uniqueId;
    /* browse WinTab's many info items to discover pressure handling. */
    AXIS axis;
    LOGCONTEXT lc;
    /* get the current context for its device variable. */
    QWindowsTabletSupport::m_winTab32DLL.wTGet(m_context, &lc);
    /* get the size of the pressure axis. */
    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &axis);
    result.minPressure = int(axis.axMin);
    result.maxPressure = int(axis.axMax);

    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis);
    result.minTanPressure = int(axis.axMin);
    result.maxTanPressure = int(axis.axMax);

    LOGCONTEXT defaultLc;
    /* get default region */
    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFCONTEXT, 0, &defaultLc);
    result.maxX = int(defaultLc.lcInExtX) - int(defaultLc.lcInOrgX);
    result.maxY = int(defaultLc.lcInExtY) - int(defaultLc.lcInOrgY);
    result.maxZ = int(defaultLc.lcInExtZ) - int(defaultLc.lcInOrgZ);
    result.currentDevice = deviceType(cursorType);
    return result;
}

bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam)
{
    PACKET proximityBuffer[1]; // we are only interested in the first packet in this case
    const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer);

    if (!LOWORD(lParam)) {
        qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice;
        if (m_currentDevice < 0 || m_currentDevice >= m_devices.size()) // QTBUG-65120, spurious leave observed
            return false;
        m_state = PenUp;
        if (totalPacks > 0) {
            QWindowSystemInterface::handleTabletLeaveProximityEvent(proximityBuffer[0].pkTime,
                                                                    m_devices.at(m_currentDevice).currentDevice,
                                                                    m_devices.at(m_currentDevice).currentPointerType,
                                                                    m_devices.at(m_currentDevice).uniqueId);
        } else {
            QWindowSystemInterface::handleTabletLeaveProximityEvent(m_devices.at(m_currentDevice).currentDevice,
                                                                    m_devices.at(m_currentDevice).currentPointerType,
                                                                    m_devices.at(m_currentDevice).uniqueId);

        }
        return true;
    }

    if (!totalPacks)
        return false;

    const UINT currentCursor = proximityBuffer[0].pkCursor;
    UINT physicalCursorId;
    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId);
    UINT cursorType;
    QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_TYPE, &cursorType);
    const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId);
    // initializing and updating the cursor should be done in response to
    // WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send
    // the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES
    m_currentDevice = indexOfDevice(m_devices, uniqueId);
    if (m_currentDevice < 0) {
        m_currentDevice = m_devices.size();
        m_devices.push_back(tabletInit(uniqueId, cursorType));
    }
    m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor);
    m_state = PenProximity;
    qCDebug(lcQpaTablet) << "enter proximity for device #"
        << m_currentDevice << m_devices.at(m_currentDevice);
    QWindowSystemInterface::handleTabletEnterProximityEvent(proximityBuffer[0].pkTime,
                                                            m_devices.at(m_currentDevice).currentDevice,
                                                            m_devices.at(m_currentDevice).currentPointerType,
                                                            m_devices.at(m_currentDevice).uniqueId);
    return true;
}

bool QWindowsTabletSupport::translateTabletPacketEvent()
{
    static PACKET localPacketBuf[TabletPacketQSize];  // our own tablet packet queue.
    const int packetCount = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, TabletPacketQSize, &localPacketBuf);
    if (!packetCount || m_currentDevice < 0)
        return false;

    const int currentDevice = m_devices.at(m_currentDevice).currentDevice;
    const int currentPointer = m_devices.at(m_currentDevice).currentPointerType;
    const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId;

    // The tablet can be used in 2 different modes (reflected in enum Mode),
    // depending on its settings:
    // 1) Absolute (pen) mode:
    //    The coordinates are scaled to the virtual desktop (by default). The user
    //    can also choose to scale to the monitor or a region of the screen.
    //    When entering proximity, the tablet driver snaps the mouse pointer to the
    //    tablet position scaled to that area and keeps it in sync.
    // 2) Relative (mouse) mode:
    //    The pen follows the mouse. The constant 'absoluteRange' specifies the
    //    manhattanLength difference for detecting if a tablet input device is in this mode,
    //    in which case we snap the position to the mouse position.
    // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext
    // area is always the virtual desktop.
    const QRect virtualDesktopArea =
        QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle());

    if (QWindowsContext::verbose > 1)  {
        qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount
            << "mode=" << m_mode << "target:"
            << QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target;
    }

    const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();

    for (int i = 0; i < packetCount ; ++i) {
        const PACKET &packet = localPacketBuf[i];

        const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0;

        QPointF globalPosF =
            m_devices.at(m_currentDevice).scaleCoordinates(packet.pkX, packet.pkY, virtualDesktopArea);

        QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.

        // Get Mouse Position and compare to tablet info
        const QPoint mouseLocation = QWindowsCursor::mousePosition();
        if (m_state == PenProximity) {
            m_state = PenDown;
            m_mode = (mouseLocation - globalPosF).manhattanLength() > m_absoluteRange
                ? MouseMode : PenMode;
            qCDebug(lcQpaTablet) << __FUNCTION__ << "mode=" << m_mode << "pen:"
                << globalPosF << "mouse:" << mouseLocation;
        }
        if (m_mode == MouseMode)
            globalPosF = mouseLocation;
        const QPoint globalPos = globalPosF.toPoint();

        if (!target)
            target = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT);
        if (!target)
            continue;

        const QPlatformWindow *platformWindow = target->handle();
        Q_ASSERT(platformWindow);
        const QPoint localPos = platformWindow->mapFromGlobal(globalPos);

        const qreal pressureNew = packet.pkButtons && (currentPointer == QTabletEvent::Pen || currentPointer == QTabletEvent::Eraser) ?
            m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) :
            qreal(0);
        const qreal tangentialPressure = currentDevice == QTabletEvent::Airbrush ?
            m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) :
            qreal(0);

        int tiltX = 0;
        int tiltY = 0;
        qreal rotation = 0;
        if (m_tiltSupport) {
            // Convert from azimuth and altitude to x tilt and y tilt. What
            // follows is the optimized version. Here are the equations used:
            // X = sin(azimuth) * cos(altitude)
            // Y = cos(azimuth) * cos(altitude)
            // Z = sin(altitude)
            // X Tilt = arctan(X / Z)
            // Y Tilt = arctan(Y / Z)
            const double radAzim = qDegreesToRadians(packet.pkOrientation.orAzimuth / 10.0);
            const double tanAlt = std::tan(qDegreesToRadians(std::abs(packet.pkOrientation.orAltitude / 10.0)));

            const double radX = std::atan(std::sin(radAzim) / tanAlt);
            const double radY = std::atan(std::cos(radAzim) / tanAlt);
            tiltX = int(qRadiansToDegrees(radX));
            tiltY = int(qRadiansToDegrees(-radY));
            rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0);
            if (rotation > 180.0)
                rotation -= 360.0;
        }

        if (QWindowsContext::verbose > 1)  {
            qCDebug(lcQpaTablet)
                << "Packet #" << i << '/' << packetCount << "button:" << packet.pkButtons
                << globalPosF << z << "to:" << target << localPos << "(packet" << packet.pkX
                << packet.pkY << ") dev:" << currentDevice << "pointer:"
                << currentPointer << "P:" << pressureNew << "tilt:" << tiltX << ','
                << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation;
        }

        QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
                                                  currentDevice, currentPointer,
                                                  static_cast<Qt::MouseButtons>(packet.pkButtons),
                                                  pressureNew, tiltX, tiltY,
                                                  tangentialPressure, rotation, z,
                                                  uniqueId,
                                                  keyboardModifiers);
    }
    return true;
}

QT_END_NAMESPACE
