blob: 350ae4b9be56ab92d185a6d70a2213524a895aca [file] [log] [blame]
/****************************************************************************
**
** 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 "qcocoakeymapper.h"
#include <QtCore/QDebug>
#include <QtGui/QGuiApplication>
QT_BEGIN_NAMESPACE
// QCocoaKeyMapper debug facilities
//#define DEBUG_KEY_BINDINGS
//#define DEBUG_KEY_BINDINGS_MODIFIERS
//#define DEBUG_KEY_MAPS
// Possible modifier states.
// NOTE: The order of these states match the order in updatePossibleKeyCodes()!
static const Qt::KeyboardModifiers ModsTbl[] = {
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
};
bool qt_mac_eat_unicode_key = false;
/* key maps */
struct qt_mac_enum_mapper
{
int mac_code;
int qt_code;
#if defined(DEBUG_KEY_BINDINGS)
# define QT_MAC_MAP_ENUM(x) x, #x
const char *desc;
#else
# define QT_MAC_MAP_ENUM(x) x
#endif
};
//modifiers
static qt_mac_enum_mapper qt_mac_modifier_symbols[] = {
{ shiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) },
{ rightShiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) },
{ controlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) },
{ rightControlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) },
{ cmdKey, QT_MAC_MAP_ENUM(Qt::ControlModifier) },
{ optionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) },
{ rightOptionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) },
{ kEventKeyModifierNumLockMask, QT_MAC_MAP_ENUM(Qt::KeypadModifier) },
{ 0, QT_MAC_MAP_ENUM(0) }
};
Qt::KeyboardModifiers qt_mac_get_modifiers(int keys)
{
#ifdef DEBUG_KEY_BINDINGS_MODIFIERS
qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", keys, keys);
#endif
Qt::KeyboardModifiers ret = Qt::NoModifier;
for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) {
if (keys & qt_mac_modifier_symbols[i].mac_code) {
#ifdef DEBUG_KEY_BINDINGS_MODIFIERS
qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc);
#endif
ret |= Qt::KeyboardModifier(qt_mac_modifier_symbols[i].qt_code);
}
}
if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) {
Qt::KeyboardModifiers oldModifiers = ret;
ret &= ~(Qt::MetaModifier | Qt::ControlModifier);
if (oldModifiers & Qt::ControlModifier)
ret |= Qt::MetaModifier;
if (oldModifiers & Qt::MetaModifier)
ret |= Qt::ControlModifier;
}
return ret;
}
static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys)
{
#ifdef DEBUG_KEY_BINDINGS_MODIFIERS
qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", (int)keys, (int)keys);
#endif
int ret = 0;
for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) {
if (keys & qt_mac_modifier_symbols[i].qt_code) {
#ifdef DEBUG_KEY_BINDINGS_MODIFIERS
qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc);
#endif
ret |= qt_mac_modifier_symbols[i].mac_code;
}
}
if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) {
int oldModifiers = ret;
ret &= ~(controlKeyBit | cmdKeyBit);
if (oldModifiers & controlKeyBit)
ret |= cmdKeyBit;
if (oldModifiers & cmdKeyBit)
ret |= controlKeyBit;
}
return ret;
}
//keyboard keys (non-modifiers)
static qt_mac_enum_mapper qt_mac_keyboard_symbols[] = {
{ kHomeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Home) },
{ kEnterCharCode, QT_MAC_MAP_ENUM(Qt::Key_Enter) },
{ kEndCharCode, QT_MAC_MAP_ENUM(Qt::Key_End) },
{ kBackspaceCharCode, QT_MAC_MAP_ENUM(Qt::Key_Backspace) },
{ kTabCharCode, QT_MAC_MAP_ENUM(Qt::Key_Tab) },
{ kPageUpCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageUp) },
{ kPageDownCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageDown) },
{ kReturnCharCode, QT_MAC_MAP_ENUM(Qt::Key_Return) },
{ kEscapeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Escape) },
{ kLeftArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Left) },
{ kRightArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Right) },
{ kUpArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Up) },
{ kDownArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Down) },
{ kHelpCharCode, QT_MAC_MAP_ENUM(Qt::Key_Help) },
{ kDeleteCharCode, QT_MAC_MAP_ENUM(Qt::Key_Delete) },
//ascii maps, for debug
{ ':', QT_MAC_MAP_ENUM(Qt::Key_Colon) },
{ ';', QT_MAC_MAP_ENUM(Qt::Key_Semicolon) },
{ '<', QT_MAC_MAP_ENUM(Qt::Key_Less) },
{ '=', QT_MAC_MAP_ENUM(Qt::Key_Equal) },
{ '>', QT_MAC_MAP_ENUM(Qt::Key_Greater) },
{ '?', QT_MAC_MAP_ENUM(Qt::Key_Question) },
{ '@', QT_MAC_MAP_ENUM(Qt::Key_At) },
{ ' ', QT_MAC_MAP_ENUM(Qt::Key_Space) },
{ '!', QT_MAC_MAP_ENUM(Qt::Key_Exclam) },
{ '"', QT_MAC_MAP_ENUM(Qt::Key_QuoteDbl) },
{ '#', QT_MAC_MAP_ENUM(Qt::Key_NumberSign) },
{ '$', QT_MAC_MAP_ENUM(Qt::Key_Dollar) },
{ '%', QT_MAC_MAP_ENUM(Qt::Key_Percent) },
{ '&', QT_MAC_MAP_ENUM(Qt::Key_Ampersand) },
{ '\'', QT_MAC_MAP_ENUM(Qt::Key_Apostrophe) },
{ '(', QT_MAC_MAP_ENUM(Qt::Key_ParenLeft) },
{ ')', QT_MAC_MAP_ENUM(Qt::Key_ParenRight) },
{ '*', QT_MAC_MAP_ENUM(Qt::Key_Asterisk) },
{ '+', QT_MAC_MAP_ENUM(Qt::Key_Plus) },
{ ',', QT_MAC_MAP_ENUM(Qt::Key_Comma) },
{ '-', QT_MAC_MAP_ENUM(Qt::Key_Minus) },
{ '.', QT_MAC_MAP_ENUM(Qt::Key_Period) },
{ '/', QT_MAC_MAP_ENUM(Qt::Key_Slash) },
{ '[', QT_MAC_MAP_ENUM(Qt::Key_BracketLeft) },
{ ']', QT_MAC_MAP_ENUM(Qt::Key_BracketRight) },
{ '\\', QT_MAC_MAP_ENUM(Qt::Key_Backslash) },
{ '_', QT_MAC_MAP_ENUM(Qt::Key_Underscore) },
{ '`', QT_MAC_MAP_ENUM(Qt::Key_QuoteLeft) },
{ '{', QT_MAC_MAP_ENUM(Qt::Key_BraceLeft) },
{ '}', QT_MAC_MAP_ENUM(Qt::Key_BraceRight) },
{ '|', QT_MAC_MAP_ENUM(Qt::Key_Bar) },
{ '~', QT_MAC_MAP_ENUM(Qt::Key_AsciiTilde) },
{ '^', QT_MAC_MAP_ENUM(Qt::Key_AsciiCircum) },
{ 0, QT_MAC_MAP_ENUM(0) }
};
static qt_mac_enum_mapper qt_mac_keyvkey_symbols[] = { //real scan codes
{ kVK_F1, QT_MAC_MAP_ENUM(Qt::Key_F1) },
{ kVK_F2, QT_MAC_MAP_ENUM(Qt::Key_F2) },
{ kVK_F3, QT_MAC_MAP_ENUM(Qt::Key_F3) },
{ kVK_F4, QT_MAC_MAP_ENUM(Qt::Key_F4) },
{ kVK_F5, QT_MAC_MAP_ENUM(Qt::Key_F5) },
{ kVK_F6, QT_MAC_MAP_ENUM(Qt::Key_F6) },
{ kVK_F7, QT_MAC_MAP_ENUM(Qt::Key_F7) },
{ kVK_F8, QT_MAC_MAP_ENUM(Qt::Key_F8) },
{ kVK_F9, QT_MAC_MAP_ENUM(Qt::Key_F9) },
{ kVK_F10, QT_MAC_MAP_ENUM(Qt::Key_F10) },
{ kVK_F11, QT_MAC_MAP_ENUM(Qt::Key_F11) },
{ kVK_F12, QT_MAC_MAP_ENUM(Qt::Key_F12) },
{ kVK_F13, QT_MAC_MAP_ENUM(Qt::Key_F13) },
{ kVK_F14, QT_MAC_MAP_ENUM(Qt::Key_F14) },
{ kVK_F15, QT_MAC_MAP_ENUM(Qt::Key_F15) },
{ kVK_F16, QT_MAC_MAP_ENUM(Qt::Key_F16) },
{ kVK_Return, QT_MAC_MAP_ENUM(Qt::Key_Return) },
{ kVK_Tab, QT_MAC_MAP_ENUM(Qt::Key_Tab) },
{ kVK_Escape, QT_MAC_MAP_ENUM(Qt::Key_Escape) },
{ kVK_Help, QT_MAC_MAP_ENUM(Qt::Key_Help) },
{ kVK_UpArrow, QT_MAC_MAP_ENUM(Qt::Key_Up) },
{ kVK_DownArrow, QT_MAC_MAP_ENUM(Qt::Key_Down) },
{ kVK_LeftArrow, QT_MAC_MAP_ENUM(Qt::Key_Left) },
{ kVK_RightArrow, QT_MAC_MAP_ENUM(Qt::Key_Right) },
{ kVK_PageUp, QT_MAC_MAP_ENUM(Qt::Key_PageUp) },
{ kVK_PageDown, QT_MAC_MAP_ENUM(Qt::Key_PageDown) },
{ 0, QT_MAC_MAP_ENUM(0) }
};
static qt_mac_enum_mapper qt_mac_private_unicode[] = {
{ 0xF700, QT_MAC_MAP_ENUM(Qt::Key_Up) }, //NSUpArrowFunctionKey
{ 0xF701, QT_MAC_MAP_ENUM(Qt::Key_Down) }, //NSDownArrowFunctionKey
{ 0xF702, QT_MAC_MAP_ENUM(Qt::Key_Left) }, //NSLeftArrowFunctionKey
{ 0xF703, QT_MAC_MAP_ENUM(Qt::Key_Right) }, //NSRightArrowFunctionKey
{ 0xF727, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertFunctionKey
{ 0xF728, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteFunctionKey
{ 0xF729, QT_MAC_MAP_ENUM(Qt::Key_Home) }, //NSHomeFunctionKey
{ 0xF72B, QT_MAC_MAP_ENUM(Qt::Key_End) }, //NSEndFunctionKey
{ 0xF72C, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, //NSPageUpFunctionKey
{ 0xF72D, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, //NSPageDownFunctionKey
{ 0xF72E, QT_MAC_MAP_ENUM(Qt::Key_Print) }, //NSPrintScreenFunctionKey
{ 0xF72F, QT_MAC_MAP_ENUM(Qt::Key_ScrollLock) }, //NSScrollLockFunctionKey
{ 0xF730, QT_MAC_MAP_ENUM(Qt::Key_Pause) }, //NSPauseFunctionKey
{ 0xF731, QT_MAC_MAP_ENUM(Qt::Key_SysReq) }, //NSSysReqFunctionKey
{ 0xF735, QT_MAC_MAP_ENUM(Qt::Key_Menu) }, //NSMenuFunctionKey
{ 0xF738, QT_MAC_MAP_ENUM(Qt::Key_Printer) }, //NSPrintFunctionKey
{ 0xF73A, QT_MAC_MAP_ENUM(Qt::Key_Clear) }, //NSClearDisplayFunctionKey
{ 0xF73D, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertCharFunctionKey
{ 0xF73E, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteCharFunctionKey
{ 0xF741, QT_MAC_MAP_ENUM(Qt::Key_Select) }, //NSSelectFunctionKey
{ 0xF742, QT_MAC_MAP_ENUM(Qt::Key_Execute) }, //NSExecuteFunctionKey
{ 0xF743, QT_MAC_MAP_ENUM(Qt::Key_Undo) }, //NSUndoFunctionKey
{ 0xF744, QT_MAC_MAP_ENUM(Qt::Key_Redo) }, //NSRedoFunctionKey
{ 0xF745, QT_MAC_MAP_ENUM(Qt::Key_Find) }, //NSFindFunctionKey
{ 0xF746, QT_MAC_MAP_ENUM(Qt::Key_Help) }, //NSHelpFunctionKey
{ 0xF747, QT_MAC_MAP_ENUM(Qt::Key_Mode_switch) }, //NSModeSwitchFunctionKey
{ 0, QT_MAC_MAP_ENUM(0) }
};
static int qt_mac_get_key(int modif, const QChar &key, int virtualKey)
{
#ifdef DEBUG_KEY_BINDINGS
qDebug("**Mapping key: %d (0x%04x) - %d (0x%04x)", key.unicode(), key.unicode(), virtualKey, virtualKey);
#endif
if (key == kClearCharCode && virtualKey == 0x47)
return Qt::Key_Clear;
if (key.isDigit()) {
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: %d", __LINE__, key.digitValue());
#endif
return key.digitValue() + Qt::Key_0;
}
if (key.isLetter()) {
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: %d", __LINE__, (key.toUpper().unicode() - 'A'));
#endif
return (key.toUpper().unicode() - 'A') + Qt::Key_A;
}
if (key.isSymbol()) {
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: %d", __LINE__, (key.unicode()));
#endif
return key.unicode();
}
for (int i = 0; qt_mac_keyboard_symbols[i].qt_code; i++) {
if (qt_mac_keyboard_symbols[i].mac_code == key) {
/* To work like Qt for X11 we issue Backtab when Shift + Tab are pressed */
if (qt_mac_keyboard_symbols[i].qt_code == Qt::Key_Tab && (modif & Qt::ShiftModifier)) {
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: Qt::Key_Backtab", __LINE__);
#endif
return Qt::Key_Backtab;
}
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: %s", __LINE__, qt_mac_keyboard_symbols[i].desc);
#endif
return qt_mac_keyboard_symbols[i].qt_code;
}
}
//last ditch try to match the scan code
for (int i = 0; qt_mac_keyvkey_symbols[i].qt_code; i++) {
if (qt_mac_keyvkey_symbols[i].mac_code == virtualKey) {
#ifdef DEBUG_KEY_BINDINGS
qDebug("%d: got key: %s", __LINE__, qt_mac_keyvkey_symbols[i].desc);
#endif
return qt_mac_keyvkey_symbols[i].qt_code;
}
}
// check if they belong to key codes in private unicode range
if (key >= 0xf700 && key <= 0xf747) {
if (key >= 0xf704 && key <= 0xf726) {
return Qt::Key_F1 + (key.unicode() - 0xf704) ;
}
for (int i = 0; qt_mac_private_unicode[i].qt_code; i++) {
if (qt_mac_private_unicode[i].mac_code == key) {
return qt_mac_private_unicode[i].qt_code;
}
}
}
//oh well
#ifdef DEBUG_KEY_BINDINGS
qDebug("Unknown case.. %s:%d %d[%d] %d", __FILE__, __LINE__, key.unicode(), key.toLatin1(), virtualKey);
#endif
return Qt::Key_unknown;
}
QCocoaKeyMapper::QCocoaKeyMapper()
{
memset(keyLayout, 0, sizeof(keyLayout));
}
QCocoaKeyMapper::~QCocoaKeyMapper()
{
deleteLayouts();
}
Qt::KeyboardModifiers QCocoaKeyMapper::queryKeyboardModifiers()
{
return qt_mac_get_modifiers(GetCurrentKeyModifiers());
}
bool QCocoaKeyMapper::updateKeyboard()
{
const UCKeyboardLayout *uchrData = nullptr;
QCFType<TISInputSourceRef> source = TISCopyInputMethodKeyboardLayoutOverride();
if (!source)
source = TISCopyCurrentKeyboardInputSource();
if (keyboard_mode != NullMode && source == currentInputSource) {
return false;
}
Q_ASSERT(source);
CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(source,
kTISPropertyUnicodeKeyLayoutData));
uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : nullptr;
keyboard_kind = LMGetKbdType();
if (uchrData) {
keyboard_layout_format = uchrData;
keyboard_mode = UnicodeMode;
} else {
keyboard_layout_format = nullptr;
keyboard_mode = NullMode;
}
currentInputSource = source;
keyboard_dead = 0;
const auto newMode = keyboard_mode;
deleteLayouts();
keyboard_mode = newMode;
return true;
}
void QCocoaKeyMapper::deleteLayouts()
{
keyboard_mode = NullMode;
for (int i = 0; i < 255; ++i) {
if (keyLayout[i]) {
delete keyLayout[i];
keyLayout[i] = nullptr;
}
}
}
void QCocoaKeyMapper::clearMappings()
{
deleteLayouts();
updateKeyboard();
}
void QCocoaKeyMapper::updateKeyMap(unsigned short macVirtualKey, QChar unicodeKey)
{
updateKeyboard();
if (keyLayout[macVirtualKey])
return;
UniCharCount buffer_size = 10;
UniChar buffer[buffer_size];
keyLayout[macVirtualKey] = new KeyboardLayoutItem;
for (int i = 0; i < 16; ++i) {
UniCharCount out_buffer_size = 0;
keyLayout[macVirtualKey]->qtKey[i] = 0;
const UInt32 keyModifier = ((qt_mac_get_mac_modifiers(ModsTbl[i]) >> 8) & 0xFF);
OSStatus err = UCKeyTranslate(keyboard_layout_format, macVirtualKey, kUCKeyActionDown, keyModifier,
keyboard_kind, 0, &keyboard_dead, buffer_size, &out_buffer_size, buffer);
if (err == noErr && out_buffer_size) {
const QChar unicode(buffer[0]);
int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey);
if (qtkey == Qt::Key_unknown)
qtkey = unicode.unicode();
keyLayout[macVirtualKey]->qtKey[i] = qtkey;
} else {
int qtkey = qt_mac_get_key(keyModifier, unicodeKey, macVirtualKey);
if (qtkey == Qt::Key_unknown)
qtkey = unicodeKey.unicode();
keyLayout[macVirtualKey]->qtKey[i] = qtkey;
}
}
#ifdef DEBUG_KEY_MAPS
qDebug("updateKeyMap for virtual key = 0x%02x!", (uint)macVirtualKey);
for (int i = 0; i < 16; ++i) {
qDebug(" [%d] (%d,0x%02x,'%c')", i,
keyLayout[macVirtualKey]->qtKey[i],
keyLayout[macVirtualKey]->qtKey[i],
keyLayout[macVirtualKey]->qtKey[i]);
}
#endif
}
QList<int> QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const
{
QList<int> ret;
const_cast<QCocoaKeyMapper *>(this)->updateKeyMap(event->nativeVirtualKey(), QChar(event->key()));
KeyboardLayoutItem *kbItem = keyLayout[event->nativeVirtualKey()];
if (!kbItem) // Key is not in any keyboard layout (e.g. eisu-key on Japanese keyboard)
return ret;
int baseKey = kbItem->qtKey[0];
Qt::KeyboardModifiers keyMods = event->modifiers();
ret << int(baseKey + keyMods); // The base key is _always_ valid, of course
for (int i = 1; i < 8; ++i) {
Qt::KeyboardModifiers neededMods = ModsTbl[i];
int key = kbItem->qtKey[i];
if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) {
ret << int(key + (keyMods & ~neededMods));
}
}
return ret;
}
QT_END_NAMESPACE