// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include <qglobal.h>

#ifdef Q_OS_MACOS
#include <AppKit/AppKit.h>
#endif

#if defined(QT_PLATFORM_UIKIT)
#include <UIKit/UIKit.h>
#endif

#include "qapplekeymapper_p.h"

#include <QtCore/qloggingcategory.h>
#include <QtGui/QGuiApplication>

QT_BEGIN_NAMESPACE

#ifdef Q_OS_MACOS
Q_STATIC_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys");
#endif

static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers)
{
    if (QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta))
        return modifiers;

    Qt::KeyboardModifiers swappedModifiers = modifiers;
    swappedModifiers &= ~(Qt::MetaModifier | Qt::ControlModifier);

    if (modifiers & Qt::ControlModifier)
        swappedModifiers |= Qt::MetaModifier;
    if (modifiers & Qt::MetaModifier)
        swappedModifiers |= Qt::ControlModifier;

    return swappedModifiers;
}

[[maybe_unused]] static Qt::Key swapModifierIfNeeded(const Qt::Key key)
{
    if (QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta))
        return key;

    if (key == Qt::Key_Meta)
        return Qt::Key_Control;
    else if (key == Qt::Key_Control)
        return Qt::Key_Meta;

    return key;
}

#ifdef Q_OS_MACOS
static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = {
    { NSEventModifierFlagShift, Qt::ShiftModifier },
    { NSEventModifierFlagControl, Qt::ControlModifier },
    { NSEventModifierFlagCommand, Qt::MetaModifier },
    { NSEventModifierFlagOption, Qt::AltModifier },
    { NSEventModifierFlagNumericPad, Qt::KeypadModifier }
};

Qt::KeyboardModifiers QAppleKeyMapper::fromCocoaModifiers(NSEventModifierFlags cocoaModifiers)
{
    Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
    for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) {
        if (cocoaModifiers & cocoaModifier)
            qtModifiers |= qtModifier;
    }

    return swapModifiersIfNeeded(qtModifiers);
}

NSEventModifierFlags QAppleKeyMapper::toCocoaModifiers(Qt::KeyboardModifiers qtModifiers)
{
    qtModifiers = swapModifiersIfNeeded(qtModifiers);

    NSEventModifierFlags cocoaModifiers = 0;
    for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) {
        if (qtModifiers & qtModifier)
            cocoaModifiers |= cocoaModifier;
    }

    return cocoaModifiers;
}

using CarbonModifiers = UInt32; // As opposed to EventModifiers which is UInt16

static CarbonModifiers toCarbonModifiers(Qt::KeyboardModifiers qtModifiers)
{
    qtModifiers = swapModifiersIfNeeded(qtModifiers);

    static constexpr std::tuple<int, Qt::KeyboardModifier> carbonModifierMap[] = {
        { shiftKey, Qt::ShiftModifier },
        { controlKey, Qt::ControlModifier },
        { cmdKey, Qt::MetaModifier },
        { optionKey, Qt::AltModifier },
        { kEventKeyModifierNumLockMask, Qt::KeypadModifier }
    };

    CarbonModifiers carbonModifiers = 0;
    for (const auto &[carbonModifier, qtModifier] : carbonModifierMap) {
        if (qtModifiers & qtModifier)
            carbonModifiers |= carbonModifier;
    }

    return carbonModifiers;
}

// NSEvent.keyCode codes for keys that are independent of keyboard layout.
// Some of these are technically possible to add custom key maps for, but
// doing so would be unexpected.
static QHash<char16_t, Qt::Key> layoutIndependentKeyCodes = {
    { kVK_F1, Qt::Key_F1 },
    { kVK_F2, Qt::Key_F2 },
    { kVK_F3, Qt::Key_F3 },
    { kVK_F4, Qt::Key_F4 },
    { kVK_F5, Qt::Key_F5 },
    { kVK_F6, Qt::Key_F6 },
    { kVK_F7, Qt::Key_F7 },
    { kVK_F8, Qt::Key_F8 },
    { kVK_F9, Qt::Key_F9 },
    { kVK_F10, Qt::Key_F10 },
    { kVK_F11, Qt::Key_F11 },
    { kVK_F12, Qt::Key_F12 },
    { kVK_F13, Qt::Key_F13 },
    { kVK_F14, Qt::Key_F14 },
    { kVK_F15, Qt::Key_F15 },
    { kVK_F16, Qt::Key_F16 },
    { kVK_F17, Qt::Key_F17 },
    { kVK_F18, Qt::Key_F18 },
    { kVK_F19, Qt::Key_F19 },
    { kVK_F20, Qt::Key_F20 },

    { kVK_Return, Qt::Key_Return },
    { kVK_Tab, Qt::Key_Tab },
    { kVK_Space, Qt::Key_Space },
    { kVK_Escape, Qt::Key_Escape },
    { kVK_Delete, Qt::Key_Backspace },
    { kVK_ForwardDelete, Qt::Key_Delete },

    { kVK_Home, Qt::Key_Home },
    { kVK_End, Qt::Key_End },
    { kVK_PageUp, Qt::Key_PageUp },
    { kVK_PageDown, Qt::Key_PageDown },

    { kVK_UpArrow, Qt::Key_Up },
    { kVK_DownArrow, Qt::Key_Down },
    { kVK_LeftArrow, Qt::Key_Left },
    { kVK_RightArrow, Qt::Key_Right },

    { kVK_CapsLock, Qt::Key_CapsLock },
    { kVK_Shift, Qt::Key_Shift },
    { kVK_RightShift, Qt::Key_Shift },

#if 0
    // FIXME: Map these here instead of relying on
    // custom logic in [QNSView flagsChanged:]

    { kVK_Command, Qt::Key_unknown },
    { kVK_RightCommand, Qt::Key_unknown },
    { kVK_Option, Qt::Key_unknown },
    { kVK_RightOption, Qt::Key_unknown },
    { kVK_Control, Qt::Key_unknown },
    { kVK_RightControl, Qt::Key_unknown },
    { kVK_Function, Qt::Key_unknown },
#endif

    { kVK_VolumeUp, Qt::Key_VolumeUp },
    { kVK_VolumeDown, Qt::Key_VolumeDown },
    { kVK_Mute, Qt::Key_VolumeMute },

#if 0
    // FIXME: Figure out which Qt::Key this maps to
    { kVK_ContextualMenu, Qt::Key_unknown },
#endif
    { kVK_Help, Qt::Key_Help },

    { kVK_ANSI_KeypadClear, Qt::Key_Clear },
    { kVK_ANSI_KeypadEnter, Qt::Key_Enter },
};

static QHash<char16_t, Qt::Key> functionKeys = {
    { NSUpArrowFunctionKey, Qt::Key_Up },
    { NSDownArrowFunctionKey, Qt::Key_Down },
    { NSLeftArrowFunctionKey, Qt::Key_Left },
    { NSRightArrowFunctionKey, Qt::Key_Right },
    // F1-35 function keys handled manually below
    { NSInsertFunctionKey, Qt::Key_Insert },
    { NSDeleteFunctionKey, Qt::Key_Delete },
    { NSHomeFunctionKey, Qt::Key_Home },
    { NSEndFunctionKey, Qt::Key_End },
    { NSPageUpFunctionKey, Qt::Key_PageUp },
    { NSPageDownFunctionKey, Qt::Key_PageDown },
    { NSPrintScreenFunctionKey, Qt::Key_Print },
    { NSScrollLockFunctionKey, Qt::Key_ScrollLock },
    { NSPauseFunctionKey, Qt::Key_Pause },
    { NSSysReqFunctionKey, Qt::Key_SysReq },
    { NSMenuFunctionKey, Qt::Key_Menu },
    { NSPrintFunctionKey, Qt::Key_Printer },
    { NSClearDisplayFunctionKey, Qt::Key_Clear },
    { NSInsertCharFunctionKey, Qt::Key_Insert },
    { NSDeleteCharFunctionKey, Qt::Key_Delete },
    { NSSelectFunctionKey, Qt::Key_Select },
    { NSExecuteFunctionKey, Qt::Key_Execute },
    { NSUndoFunctionKey, Qt::Key_Undo },
    { NSRedoFunctionKey, Qt::Key_Redo },
    { NSFindFunctionKey, Qt::Key_Find },
    { NSHelpFunctionKey, Qt::Key_Help },
    { NSModeSwitchFunctionKey, Qt::Key_Mode_switch }
};

static int toKeyCode(const QChar &key, int virtualKey, int modifiers)
{
    qCDebug(lcQpaKeyMapperKeys, "Mapping key: %d (0x%04x) / vk %d (0x%04x)",
        key.unicode(), key.unicode(), virtualKey, virtualKey);

    // Check first if we have a virtual key that should be treated as layout
    // independent. If so, we want to return early without inspecting the key.
    if (auto qtKey = layoutIndependentKeyCodes.value(virtualKey)) {
        qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey << "based on layout independent virtual key";
        // To work like Qt for X11 we issue Backtab when Shift + Tab are pressed
        if (qtKey == Qt::Key_Tab && (modifiers & Qt::ShiftModifier)) {
            qCDebug(lcQpaKeyMapperKeys, "Transformed into Qt::Key_Backtab");
            return Qt::Key_Backtab;
        }
        return qtKey;
    }

    // Then check if the key is one of the functions keys in the private Unicode range
    if (key >= char16_t(NSUpArrowFunctionKey) && key <= char16_t(NSModeSwitchFunctionKey)) {
        if (auto qtKey = functionKeys.value(key.unicode())) {
            qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey;
            return qtKey;
        } else if (key >= char16_t(NSF1FunctionKey) && key <= char16_t(NSF35FunctionKey)) {
            auto functionKey = Qt::Key_F1 + (key.unicode() - NSF1FunctionKey) ;
            qCDebug(lcQpaKeyMapperKeys) << "Got" << functionKey;
            return functionKey;
        }
    }

    if (key.isDigit()) {
        qCDebug(lcQpaKeyMapperKeys, "Got digit key: %d", key.digitValue());
        return key.digitValue() + Qt::Key_0;
    }

    if (key.isLetter()) {
        qCDebug(lcQpaKeyMapperKeys, "Got letter key: %d", (key.toUpper().unicode() - 'A'));
        return (key.toUpper().unicode() - 'A') + Qt::Key_A;
    }
    if (key.isSymbol()) {
        qCDebug(lcQpaKeyMapperKeys, "Got symbol key: %d", (key.unicode()));
        return key.unicode();
    }

    qCDebug(lcQpaKeyMapperKeys, "Unknown case.. %d[%d] %d", key.unicode(), key.toLatin1(), virtualKey);
    return Qt::Key_unknown;
}

// --------- Cocoa key mapping moved from Qt Core ---------

static const int NSEscapeCharacter = 27; // not defined by Cocoa headers

static const QHash<char16_t, Qt::Key> cocoaKeys = {
    { NSEnterCharacter, Qt::Key_Enter },
    { NSBackspaceCharacter, Qt::Key_Backspace },
    { NSTabCharacter, Qt::Key_Tab },
    { NSNewlineCharacter, Qt::Key_Return },
    { NSCarriageReturnCharacter, Qt::Key_Return },
    { NSBackTabCharacter, Qt::Key_Backtab },
    { NSEscapeCharacter, Qt::Key_Escape },
    { NSDeleteCharacter, Qt::Key_Backspace },
    { NSUpArrowFunctionKey, Qt::Key_Up },
    { NSDownArrowFunctionKey, Qt::Key_Down },
    { NSLeftArrowFunctionKey, Qt::Key_Left },
    { NSRightArrowFunctionKey, Qt::Key_Right },
    { NSF1FunctionKey, Qt::Key_F1 },
    { NSF2FunctionKey, Qt::Key_F2 },
    { NSF3FunctionKey, Qt::Key_F3 },
    { NSF4FunctionKey, Qt::Key_F4 },
    { NSF5FunctionKey, Qt::Key_F5 },
    { NSF6FunctionKey, Qt::Key_F6 },
    { NSF7FunctionKey, Qt::Key_F7 },
    { NSF8FunctionKey, Qt::Key_F8 },
    { NSF9FunctionKey, Qt::Key_F9 },
    { NSF10FunctionKey, Qt::Key_F10 },
    { NSF11FunctionKey, Qt::Key_F11 },
    { NSF12FunctionKey, Qt::Key_F12 },
    { NSF13FunctionKey, Qt::Key_F13 },
    { NSF14FunctionKey, Qt::Key_F14 },
    { NSF15FunctionKey, Qt::Key_F15 },
    { NSF16FunctionKey, Qt::Key_F16 },
    { NSF17FunctionKey, Qt::Key_F17 },
    { NSF18FunctionKey, Qt::Key_F18 },
    { NSF19FunctionKey, Qt::Key_F19 },
    { NSF20FunctionKey, Qt::Key_F20 },
    { NSF21FunctionKey, Qt::Key_F21 },
    { NSF22FunctionKey, Qt::Key_F22 },
    { NSF23FunctionKey, Qt::Key_F23 },
    { NSF24FunctionKey, Qt::Key_F24 },
    { NSF25FunctionKey, Qt::Key_F25 },
    { NSF26FunctionKey, Qt::Key_F26 },
    { NSF27FunctionKey, Qt::Key_F27 },
    { NSF28FunctionKey, Qt::Key_F28 },
    { NSF29FunctionKey, Qt::Key_F29 },
    { NSF30FunctionKey, Qt::Key_F30 },
    { NSF31FunctionKey, Qt::Key_F31 },
    { NSF32FunctionKey, Qt::Key_F32 },
    { NSF33FunctionKey, Qt::Key_F33 },
    { NSF34FunctionKey, Qt::Key_F34 },
    { NSF35FunctionKey, Qt::Key_F35 },
    { NSInsertFunctionKey, Qt::Key_Insert },
    { NSDeleteFunctionKey, Qt::Key_Delete },
    { NSHomeFunctionKey, Qt::Key_Home },
    { NSEndFunctionKey, Qt::Key_End },
    { NSPageUpFunctionKey, Qt::Key_PageUp },
    { NSPageDownFunctionKey, Qt::Key_PageDown },
    { NSPrintScreenFunctionKey, Qt::Key_Print },
    { NSScrollLockFunctionKey, Qt::Key_ScrollLock },
    { NSPauseFunctionKey, Qt::Key_Pause },
    { NSSysReqFunctionKey, Qt::Key_SysReq },
    { NSMenuFunctionKey, Qt::Key_Menu },
    { NSHelpFunctionKey, Qt::Key_Help },
};

QChar QAppleKeyMapper::toCocoaKey(Qt::Key key)
{
    // Prioritize overloaded keys
    if (key == Qt::Key_Return)
        return char16_t(NSCarriageReturnCharacter);
    if (key == Qt::Key_Backspace)
        return char16_t(NSBackspaceCharacter);

    Q_CONSTINIT static QHash<Qt::Key, char16_t> reverseCocoaKeys;
    if (reverseCocoaKeys.isEmpty()) {
        reverseCocoaKeys.reserve(cocoaKeys.size());
        for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it)
            reverseCocoaKeys.insert(it.value(), it.key());
    }

    return reverseCocoaKeys.value(key);
}

Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode)
{
    if (auto key = cocoaKeys.value(keyCode.unicode()))
        return key;

    return Qt::Key(keyCode.toUpper().unicode());
}

// ------------------------------------------------

Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const
{
    return fromCocoaModifiers(NSEvent.modifierFlags);
}

bool QAppleKeyMapper::updateKeyboard()
{
    QCFType<TISInputSourceRef> source = TISCopyInputMethodKeyboardLayoutOverride();
    if (!source)
        source = TISCopyCurrentKeyboardInputSource();

    if (m_keyboardMode != NullMode && source == m_currentInputSource)
        return false;

    Q_ASSERT(source);
    m_currentInputSource = source;
    m_keyboardKind = LMGetKbdType();

    m_keyMap.clear();

    if (auto data = CFDataRef(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))) {
        const UCKeyboardLayout *uchrData = reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data));
        Q_ASSERT(uchrData);
        m_keyboardLayoutFormat = uchrData;
        m_keyboardMode = UnicodeMode;
    } else {
        m_keyboardLayoutFormat = nullptr;
        m_keyboardMode = NullMode;
    }

    qCDebug(lcQpaKeyMapper) << "Updated keyboard to"
        << QString::fromCFString(CFStringRef(TISGetInputSourceProperty(
            m_currentInputSource, kTISPropertyLocalizedName)));

    return true;
}

static constexpr Qt::KeyboardModifiers modifierCombinations[] = {
    Qt::NoModifier,                                             // 0
    Qt::ShiftModifier,                                          // 1
    Qt::ControlModifier,                                        // 2
    Qt::ControlModifier | Qt::ShiftModifier,                    // 3
    Qt::AltModifier,                                            // 4
    Qt::AltModifier | Qt::ShiftModifier,                        // 5
    Qt::AltModifier | Qt::ControlModifier,                      // 6
    Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier,  // 7
    Qt::MetaModifier,                                           // 8
    Qt::MetaModifier | Qt::ShiftModifier,                       // 9
    Qt::MetaModifier | Qt::ControlModifier,                     // 10
    Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier, // 11
    Qt::MetaModifier | Qt::AltModifier,                         // 12
    Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier,     // 13
    Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier,   // 14
    Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier,  // 15
};

/*
    Returns a key map for the given \virtualKey based on all
    possible modifier combinations.
*/
const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virtualKey) const
{
    static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations);

    const_cast<QAppleKeyMapper *>(this)->updateKeyboard();

    auto &keyMap = m_keyMap[virtualKey];
    if (keyMap[Qt::NoModifier] != Qt::Key_unknown)
        return keyMap; // Already filled

    qCDebug(lcQpaKeyMapper, "Updating key map for virtual key 0x%02x", (uint)virtualKey);

    // Key mapping via [NSEvent charactersByApplyingModifiers:] only works for key down
    // events, but we might (wrongly) get into this code path for other key events such
    // as NSEventTypeFlagsChanged.
    const bool canMapCocoaEvent = NSApp.currentEvent.type == NSEventTypeKeyDown;

    if (!canMapCocoaEvent)
        qCWarning(lcQpaKeyMapper) << "Could not map key to character for event" << NSApp.currentEvent;

    for (int i = 0; i < kNumModifierCombinations; ++i) {
        Q_ASSERT(!i || keyMap[i] == 0);

        auto qtModifiers = modifierCombinations[i];
        auto carbonModifiers = toCarbonModifiers(qtModifiers);
        const UInt32 modifierKeyState = (carbonModifiers >> 8) & 0xFF;

        UInt32 deadKeyState = 0;
        static const UniCharCount maxStringLength = 10;
        static UniChar unicodeString[maxStringLength];
        UniCharCount actualStringLength = 0;
        OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey,
            kUCKeyActionDown, modifierKeyState, m_keyboardKind,
            kUCKeyTranslateNoDeadKeysMask, &deadKeyState,
            maxStringLength, &actualStringLength,
            unicodeString);

        // Use translated Unicode key if valid
        QChar carbonUnicodeKey;
        if (err == noErr && actualStringLength)
            carbonUnicodeKey = QChar(unicodeString[0]);

        if (canMapCocoaEvent) {
            // Until we've verified that the Cocoa API works as expected
            // we first run the event through the Carbon APIs and then
            // compare the results to Cocoa.
            auto cocoaModifiers = toCocoaModifiers(qtModifiers);
            auto *charactersWithModifiers = [NSApp.currentEvent charactersByApplyingModifiers:cocoaModifiers];

            QChar cocoaUnicodeKey;
            if (charactersWithModifiers.length > 0)
                cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]);

            if (cocoaUnicodeKey != carbonUnicodeKey) {
                qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey
                    << "and Carbon" << carbonUnicodeKey << "for virtual key" << virtualKey
                    << "with" << qtModifiers;
            }
        }

        int qtKey = toKeyCode(carbonUnicodeKey, virtualKey, qtModifiers);
        if (qtKey == Qt::Key_unknown)
            qtKey = carbonUnicodeKey.unicode();

        keyMap[i] = qtKey;

        qCDebug(lcQpaKeyMapper).verbosity(0) << "\t" << qtModifiers
            << "+" << qUtf8Printable(QString::asprintf("0x%02x", virtualKey))
            << "=" << qUtf8Printable(QString::asprintf("%d / 0x%02x /", qtKey, qtKey))
                   << QKeySequence(qtKey).toString();
    }

    return keyMap;
}

/*
    Compute the possible key combinations that can map to the event's
    virtual key and modifiers, in the current keyboard layout.

    For example, given a normal US keyboard layout, the virtual key
    23 combined with the Alt (⌥) and Shift (⇧) modifiers, can map
    to the following key combinations:

        - Alt+Shift+5
        - Alt+%
        - Shift+∞
        - ﬁ

    The function builds on a key map produced by keyMapForKey(),
    where each modifier-key combination has been mapped to the
    key it will produce.
*/
QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
{
    QList<QKeyCombination> ret;

    const auto nativeVirtualKey = event->nativeVirtualKey();
    if (!nativeVirtualKey)
        return ret;

    auto keyMap = keyMapForKey(nativeVirtualKey);

    auto unmodifiedKey = keyMap[Qt::NoModifier];
    Q_ASSERT(unmodifiedKey != Qt::Key_unknown);

    auto eventModifiers = event->modifiers();

    int startingModifierLayer = 0;
    if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
        // When the Command key is pressed AppKit seems to do key equivalent
        // matching using a Latin/Roman interpretation of the current keyboard
        // layout. For example, for a Greek layout, pressing Option+Command+C
        // produces a key event with chars="ç" and unmodchars="ψ", but AppKit
        // still treats this as a match for a key equivalent of Option+Command+C.
        // We can't do the same by just applying the modifiers to our key map,
        // as that too contains "ψ" for the Option+Command combination. What we
        // can do instead is take advantage of the fact that the Command
        // modifier layer in all/most keyboard layouts contains a Latin
        // layer. We then combine that with the modifiers of the event
        // to produce the resulting "Latin" key combination.

        // Depending on whether Qt::AA_MacDontSwapCtrlAndMeta is set or not
        // the index of the Command layer in modifierCombinations will differ.
        const int commandLayer = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? 8 : 2;
        // FIXME: We don't clear the key map when Qt::AA_MacDontSwapCtrlAndMeta
        // is set, so changing Qt::AA_MacDontSwapCtrlAndMeta at runtime will
        // fail once we've built the key map.
        ret << QKeyCombination::fromCombined(int(eventModifiers) + int(keyMap[commandLayer]));

        // If the unmodified key is outside of Latin1, we also treat
        // that as a valid key combination, even if AppKit natively
        // does not. For example, for a Greek layout, we still want
        // to support Option+Command+ψ as a key combination, as it's
        // unlikely to clash with the Latin key combination we added
        // above.

        // However, if the unmodified key is within Latin1, we skip
        // it, to avoid these types of conflicts. For example, in
        // the same Greek layout, pressing the key next to Tab will
        // produce a Latin ';' symbol, but we've already treated that
        // as 'q' above, thanks to the Command modifier, so we skip
        // the potential Command+; key combination. This is also in
        // line with what AppKit natively does.

        // Skipping Latin1 unmodified keys also handles the case of
        // a Latin layout, where the unmodified and modified keys
        // are the same.

        if (unmodifiedKey <= 0xff)
            startingModifierLayer = 1;
    }

    // FIXME: We only compute the first 8 combinations. Why?
    for (int i = startingModifierLayer; i < 15; ++i) {
        auto keyAfterApplyingModifiers = keyMap[i];
        if (!keyAfterApplyingModifiers)
             continue;

        // Include key if the event modifiers match exactly,
        // or are a superset of the current candidate modifiers.
        auto candidateModifiers = modifierCombinations[i];
        if ((eventModifiers & candidateModifiers) == candidateModifiers) {
            // If the event includes more modifiers than the candidate they
            // will need to be included in the resulting key combination.
            auto additionalModifiers = eventModifiers & ~candidateModifiers;

            auto keyCombination = QKeyCombination::fromCombined(
                int(additionalModifiers) + int(keyAfterApplyingModifiers));

            // If there's an existing key combination with the same key,
            // but a different set of modifiers, we want to choose only
            // one of them, by priority (see below).
            const auto existingCombination = std::find_if(
                ret.begin(), ret.end(), [&](auto existingCombination) {
                    return existingCombination.key() == keyAfterApplyingModifiers;
                });

            if (existingCombination != ret.end()) {
                // We prioritize the combination with the more specific
                // modifiers. In the case where the number of modifiers
                // are the same, we want to prioritize Command over Option
                // over Control over Shift. Unfortunately the order (and
                // hence value) of the modifiers in Qt::KeyboardModifier
                // does not match our preferred order when Control and
                // Meta is switched, but we can work around that by
                // explicitly swapping the modifiers and using that
                // for the comparison. This also works when the
                // Qt::AA_MacDontSwapCtrlAndMeta application attribute
                // is set, as the incoming modifiers are then left
                // as is, and we can still trust the order.
                auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers());
                auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers);
                if (replacementModifiers > existingModifiers)
                    *existingCombination = keyCombination;
            } else {
                // All is good, no existing combination has this key
                ret << keyCombination;
            }
        }
    }

    return ret;
}



#else // iOS

Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
                                      NSString *charactersIgnoringModifiers, QString &text)
{
    if ([characters isEqualToString:@"\t"]) {
        if (qtModifiers & Qt::ShiftModifier)
            return Qt::Key_Backtab;
        return Qt::Key_Tab;
    } else if ([characters isEqualToString:@"\r"]) {
        if (qtModifiers & Qt::KeypadModifier)
            return Qt::Key_Enter;
        return Qt::Key_Return;
    }
    if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
        QChar ch;
        if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
            ([charactersIgnoringModifiers length] != 0)) {
            ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
        } else if ([characters length] != 0) {
            ch = QChar([characters characterAtIndex:0]);
        }
        if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
            (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
            text = QString::fromNSString(characters);
        }
        if (!ch.isNull())
            return Qt::Key(ch.toUpper().unicode());
    }
    return Qt::Key_unknown;
}

// Keyboard keys (non-modifiers)
API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode)
{
    static QHash<NSString *, Qt::Key> uiKitKeys = {
        { UIKeyInputF1, Qt::Key_F1 },
        { UIKeyInputF2, Qt::Key_F2 },
        { UIKeyInputF3, Qt::Key_F3 },
        { UIKeyInputF4, Qt::Key_F4 },
        { UIKeyInputF5, Qt::Key_F5 },
        { UIKeyInputF6, Qt::Key_F6 },
        { UIKeyInputF7, Qt::Key_F7 },
        { UIKeyInputF8, Qt::Key_F8 },
        { UIKeyInputF9, Qt::Key_F9 },
        { UIKeyInputF10, Qt::Key_F10 },
        { UIKeyInputF11, Qt::Key_F11 },
        { UIKeyInputF12, Qt::Key_F12 },
        { UIKeyInputHome, Qt::Key_Home },
        { UIKeyInputEnd, Qt::Key_End },
        { UIKeyInputPageUp, Qt::Key_PageUp },
        { UIKeyInputPageDown, Qt::Key_PageDown },
        { UIKeyInputEscape, Qt::Key_Escape },
        { UIKeyInputUpArrow, Qt::Key_Up },
        { UIKeyInputDownArrow, Qt::Key_Down },
        { UIKeyInputLeftArrow, Qt::Key_Left },
        { UIKeyInputRightArrow, Qt::Key_Right }
    };

    if (auto key = uiKitKeys.value(keyCode))
        return key;

    return Qt::Key_unknown;
}

Qt::Key QAppleKeyMapper::fromUIKitKey(UIKeyboardHIDUsage usage)
{
    static QHash<UIKeyboardHIDUsage, Qt::Key> modifierKeys = {
        { UIKeyboardHIDUsageKeyboardCapsLock, Qt::Key_CapsLock },
        { UIKeyboardHIDUsageKeyboardLeftAlt, Qt::Key_Alt },
        { UIKeyboardHIDUsageKeyboardRightAlt, Qt::Key_Alt },
        { UIKeyboardHIDUsageKeyboardLeftControl, Qt::Key_Control },
        { UIKeyboardHIDUsageKeyboardRightControl, Qt::Key_Control },
        { UIKeyboardHIDUsageKeyboardLeftShift, Qt::Key_Shift },
        { UIKeyboardHIDUsageKeyboardRightShift, Qt::Key_Shift },
        { UIKeyboardHIDUsageKeyboardLeftGUI, Qt::Key_Meta },
        { UIKeyboardHIDUsageKeyboardRightGUI, Qt::Key_Meta },
    };

    if (auto key = modifierKeys.value(usage))
        return swapModifierIfNeeded(key);

    return Qt::Key_unknown;
}

static constexpr std::tuple<ulong, Qt::KeyboardModifier> uiKitModifierMap[] = {
    { UIKeyModifierShift, Qt::ShiftModifier },
    { UIKeyModifierControl, Qt::ControlModifier },
    { UIKeyModifierCommand, Qt::MetaModifier },
    { UIKeyModifierAlternate, Qt::AltModifier },
    { UIKeyModifierNumericPad, Qt::KeypadModifier }
};

ulong QAppleKeyMapper::toUIKitModifiers(Qt::KeyboardModifiers qtModifiers)
{
    qtModifiers = swapModifiersIfNeeded(qtModifiers);

    ulong nativeModifiers = 0;
    for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) {
        if (qtModifiers & qtModifier)
            nativeModifiers |= nativeModifier;
    }

    return nativeModifiers;
}

Qt::KeyboardModifiers QAppleKeyMapper::fromUIKitModifiers(ulong nativeModifiers)
{
    Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
    for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) {
        if (nativeModifiers & nativeModifier)
            qtModifiers |= qtModifier;
    }

    return swapModifiersIfNeeded(qtModifiers);
}
#endif

QT_END_NAMESPACE
