1/**************************************************************************** 2** 3** Copyright (C) 2018 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the plugins of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40// This file is included from qnsview.mm, and only used to organize the code 41 42@implementation QNSView (KeysAPI) 43 44+ (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags 45{ 46 const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); 47 Qt::KeyboardModifiers qtMods =Qt::NoModifier; 48 if (modifierFlags & NSEventModifierFlagShift) 49 qtMods |= Qt::ShiftModifier; 50 if (modifierFlags & NSEventModifierFlagControl) 51 qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; 52 if (modifierFlags & NSEventModifierFlagOption) 53 qtMods |= Qt::AltModifier; 54 if (modifierFlags & NSEventModifierFlagCommand) 55 qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; 56 if (modifierFlags & NSEventModifierFlagNumericPad) 57 qtMods |= Qt::KeypadModifier; 58 return qtMods; 59} 60 61@end 62 63@implementation QNSView (Keys) 64 65- (int)convertKeyCode:(QChar)keyChar 66{ 67 return qt_mac_cocoaKey2QtKey(keyChar); 68} 69 70- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType 71{ 72 ulong timestamp = [nsevent timestamp] * 1000; 73 ulong nativeModifiers = [nsevent modifierFlags]; 74 Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; 75 NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; 76 NSString *characters = [nsevent characters]; 77 if (m_inputSource != characters) { 78 [m_inputSource release]; 79 m_inputSource = [characters retain]; 80 } 81 82 // There is no way to get the scan code from carbon/cocoa. But we cannot 83 // use the value 0, since it indicates that the event originates from somewhere 84 // else than the keyboard. 85 quint32 nativeScanCode = 1; 86 quint32 nativeVirtualKey = [nsevent keyCode]; 87 88 QChar ch = QChar::ReplacementCharacter; 89 int keyCode = Qt::Key_unknown; 90 91 // If a dead key occurs as a result of pressing a key combination then 92 // characters will have 0 length, but charactersIgnoringModifiers will 93 // have a valid character in it. This enables key combinations such as 94 // ALT+E to be used as a shortcut with an English keyboard even though 95 // pressing ALT+E will give a dead key while doing normal text input. 96 if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { 97 auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; 98 if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) 99 ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); 100 else if ([characters length] != 0) 101 ch = QChar([characters characterAtIndex:0]); 102 keyCode = [self convertKeyCode:ch]; 103 } 104 105 // we will send a key event unless the input method sets m_sendKeyEvent to false 106 m_sendKeyEvent = true; 107 QString text; 108 // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when 109 // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) 110 if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) 111 text = QString::fromNSString(characters); 112 113 QWindow *window = [self topLevelWindow]; 114 115 // Popups implicitly grab key events; forward to the active popup if there is one. 116 // This allows popups to e.g. intercept shortcuts and close the popup in response. 117 if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { 118 if (!popup->window()->flags().testFlag(Qt::ToolTip)) 119 window = popup->window(); 120 } 121 122 if (eventType == QEvent::KeyPress) { 123 124 if (m_composingText.isEmpty()) { 125 m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, 126 modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); 127 128 // Handling a shortcut may result in closing the window 129 if (!m_platformWindow) 130 return true; 131 } 132 133 QObject *fo = m_platformWindow->window()->focusObject(); 134 if (m_sendKeyEvent && fo) { 135 QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); 136 if (QCoreApplication::sendEvent(fo, &queryEvent)) { 137 bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); 138 Qt::InputMethodHints hints = static_cast<Qt::InputMethodHints>(queryEvent.value(Qt::ImHints).toUInt()); 139 if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { 140 // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call 141 m_currentlyInterpretedKeyEvent = nsevent; 142 [self interpretKeyEvents:@[nsevent]]; 143 m_currentlyInterpretedKeyEvent = 0; 144 } 145 } 146 } 147 if (m_resendKeyEvent) 148 m_sendKeyEvent = true; 149 } 150 151 bool accepted = true; 152 if (m_sendKeyEvent && m_composingText.isEmpty()) { 153 QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, 154 nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); 155 accepted = QWindowSystemInterface::flushWindowSystemEvents(); 156 } 157 m_sendKeyEvent = false; 158 m_resendKeyEvent = false; 159 return accepted; 160} 161 162- (void)keyDown:(NSEvent *)nsevent 163{ 164 if ([self isTransparentForUserInput]) 165 return [super keyDown:nsevent]; 166 167 const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; 168 169 // When Qt is used to implement a plugin for a native application we 170 // want to propagate unhandled events to other native views. However, 171 // Qt does not always set the accepted state correctly (in particular 172 // for return key events), so do this for plugin applications only 173 // to prevent incorrect forwarding in the general case. 174 const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; 175 176 // Track keyDown acceptance/forward state for later acceptance of the keyUp. 177 if (!shouldPropagate) 178 m_acceptedKeyDowns.insert([nsevent keyCode]); 179 180 if (shouldPropagate) 181 [super keyDown:nsevent]; 182} 183 184- (void)keyUp:(NSEvent *)nsevent 185{ 186 if ([self isTransparentForUserInput]) 187 return [super keyUp:nsevent]; 188 189 const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; 190 191 // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was 192 // accepted. Qt text controls wil often not use and ignore keyUp events, but we 193 // want to avoid propagating unmatched keyUps. 194 const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); 195 if (!keyUpAccepted && !keyDownAccepted) 196 [super keyUp:nsevent]; 197} 198 199- (void)cancelOperation:(id)sender 200{ 201 Q_UNUSED(sender); 202 203 NSEvent *currentEvent = [NSApp currentEvent]; 204 if (!currentEvent || currentEvent.type != NSEventTypeKeyDown) 205 return; 206 207 // Handling the key event may recurse back here through interpretKeyEvents 208 // (when IM is enabled), so we need to guard against that. 209 if (currentEvent == m_currentlyInterpretedKeyEvent) 210 return; 211 212 // Send Command+Key_Period and Escape as normal keypresses so that 213 // the key sequence is delivered through Qt. That way clients can 214 // intercept the shortcut and override its effect. 215 [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; 216} 217 218- (void)flagsChanged:(NSEvent *)nsevent 219{ 220 ulong timestamp = [nsevent timestamp] * 1000; 221 ulong nativeModifiers = [nsevent modifierFlags]; 222 Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers:nativeModifiers]; 223 224 // There is no way to get the scan code from carbon/cocoa. But we cannot 225 // use the value 0, since it indicates that the event originates from somewhere 226 // else than the keyboard. 227 quint32 nativeScanCode = 1; 228 quint32 nativeVirtualKey = [nsevent keyCode]; 229 230 // calculate the delta and remember the current modifiers for next time 231 static ulong m_lastKnownModifiers; 232 ulong lastKnownModifiers = m_lastKnownModifiers; 233 ulong delta = lastKnownModifiers ^ nativeModifiers; 234 m_lastKnownModifiers = nativeModifiers; 235 236 struct qt_mac_enum_mapper 237 { 238 ulong mac_mask; 239 Qt::Key qt_code; 240 }; 241 static qt_mac_enum_mapper modifier_key_symbols[] = { 242 { NSEventModifierFlagShift, Qt::Key_Shift }, 243 { NSEventModifierFlagControl, Qt::Key_Meta }, 244 { NSEventModifierFlagCommand, Qt::Key_Control }, 245 { NSEventModifierFlagOption, Qt::Key_Alt }, 246 { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }, 247 { 0ul, Qt::Key_unknown } }; 248 for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { 249 uint mac_mask = modifier_key_symbols[i].mac_mask; 250 if ((delta & mac_mask) == 0u) 251 continue; 252 253 Qt::Key qtCode = modifier_key_symbols[i].qt_code; 254 if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { 255 if (qtCode == Qt::Key_Meta) 256 qtCode = Qt::Key_Control; 257 else if (qtCode == Qt::Key_Control) 258 qtCode = Qt::Key_Meta; 259 } 260 QWindowSystemInterface::handleExtendedKeyEvent(m_platformWindow->window(), 261 timestamp, 262 (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease 263 : QEvent::KeyPress, 264 qtCode, 265 modifiers ^ [QNSView convertKeyModifiers:mac_mask], 266 nativeScanCode, nativeVirtualKey, 267 nativeModifiers ^ mac_mask); 268 } 269} 270 271@end 272