File indexing completed on 2024-04-28 05:50:42
0001 /* 0002 This source file is part of Konsole, a terminal emulator. 0003 0004 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 // Own 0010 #include "KeyboardTranslator.h" 0011 0012 // System 0013 #include <cctype> 0014 #include <cstdio> 0015 0016 // Qt 0017 #include <QKeySequence> 0018 0019 // KDE 0020 #include <KLocalizedString> 0021 0022 Q_LOGGING_CATEGORY(KonsoleKeyTrDebug, "org.kde.konsole.keytranslator", QtDebugMsg) 0023 0024 using namespace Konsole; 0025 0026 KeyboardTranslator::Entry::Entry() 0027 : _keyCode(0) 0028 , _modifiers(Qt::NoModifier) 0029 , _modifierMask(Qt::NoModifier) 0030 , _state(NoState) 0031 , _stateMask(NoState) 0032 , _command(NoCommand) 0033 , _text(QByteArray()) 0034 { 0035 } 0036 0037 bool KeyboardTranslator::Entry::operator==(const Entry &rhs) const 0038 { 0039 /* clang-format off */ 0040 return _keyCode == rhs._keyCode 0041 && _modifiers == rhs._modifiers 0042 && _modifierMask == rhs._modifierMask 0043 && _state == rhs._state 0044 && _stateMask == rhs._stateMask 0045 && _command == rhs._command 0046 && _text == rhs._text; 0047 /* clang-format on */ 0048 } 0049 0050 bool KeyboardTranslator::Entry::matches(int testKeyCode, Qt::KeyboardModifiers testKeyboardModifiers, States testState) const 0051 { 0052 if (_keyCode != testKeyCode) { 0053 return false; 0054 } 0055 0056 if ((testKeyboardModifiers & _modifierMask) != (_modifiers & _modifierMask)) { 0057 return false; 0058 } 0059 0060 // if testKeyboardModifiers is non-zero, the 'any modifier' state is implicit 0061 if (testKeyboardModifiers != 0) { 0062 testState |= AnyModifierState; 0063 } 0064 0065 if ((testState & _stateMask) != (_state & _stateMask)) { 0066 return false; 0067 } 0068 0069 // special handling for the 'Any Modifier' state, which checks for the presence of 0070 // any or no modifiers. In this context, the 'keypad' modifier does not count. 0071 bool anyModifiersSet = (testKeyboardModifiers != 0) && (testKeyboardModifiers != Qt::KeypadModifier); 0072 bool wantAnyModifier = (_state & KeyboardTranslator::AnyModifierState) != 0; 0073 if ((_stateMask & KeyboardTranslator::AnyModifierState) != 0) { 0074 if (wantAnyModifier != anyModifiersSet) { 0075 return false; 0076 } 0077 } 0078 0079 return true; 0080 } 0081 0082 QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards, Qt::KeyboardModifiers keyboardModifiers) const 0083 { 0084 QByteArray result(text(expandWildCards, keyboardModifiers)); 0085 0086 for (int i = 0; i < result.length(); i++) { 0087 const char ch = result[i]; 0088 char replacement = 0; 0089 0090 switch (ch) { 0091 case 27: 0092 replacement = 'E'; 0093 break; 0094 case 8: 0095 replacement = 'b'; 0096 break; 0097 case 12: 0098 replacement = 'f'; 0099 break; 0100 case 9: 0101 replacement = 't'; 0102 break; 0103 case 13: 0104 replacement = 'r'; 0105 break; 0106 case 10: 0107 replacement = 'n'; 0108 break; 0109 default: 0110 // any character which is not printable is replaced by an equivalent 0111 // \xhh escape sequence (where 'hh' are the corresponding hex digits) 0112 if (!QChar(QLatin1Char(ch)).isPrint()) { 0113 replacement = 'x'; 0114 } 0115 } 0116 0117 if (replacement == 'x') { 0118 result.replace(i, 1, QByteArray{"\\x" + QByteArray(1, ch).toHex()}); 0119 } else if (replacement != 0) { 0120 result.remove(i, 1); 0121 result.insert(i, '\\'); 0122 result.insert(i + 1, replacement); 0123 } 0124 } 0125 0126 return result; 0127 } 0128 0129 QByteArray KeyboardTranslator::Entry::unescape(const QByteArray &text) const 0130 { 0131 QByteArray result(text); 0132 0133 for (int i = 0; i < result.length() - 1; i++) { 0134 auto ch = result[i]; 0135 if (ch == '\\') { 0136 char replacement[2] = {0, 0}; 0137 int charsToRemove = 2; 0138 bool escapedChar = true; 0139 0140 switch (result[i + 1]) { 0141 case 'E': 0142 replacement[0] = 27; 0143 break; 0144 case 'b': 0145 replacement[0] = 8; 0146 break; 0147 case 'f': 0148 replacement[0] = 12; 0149 break; 0150 case 't': 0151 replacement[0] = 9; 0152 break; 0153 case 'r': 0154 replacement[0] = 13; 0155 break; 0156 case 'n': 0157 replacement[0] = 10; 0158 break; 0159 case 'x': { 0160 // format is \xh or \xhh where 'h' is a hexadecimal 0161 // digit from 0-9 or A-F which should be replaced 0162 // with the corresponding character value 0163 char hexDigits[3] = {0}; 0164 0165 if ((i < result.length() - 2) && (isxdigit(result[i + 2]) != 0)) { 0166 hexDigits[0] = result[i + 2]; 0167 } 0168 if ((i < result.length() - 3) && (isxdigit(result[i + 3]) != 0)) { 0169 hexDigits[1] = result[i + 3]; 0170 } 0171 0172 unsigned charValue = 0; 0173 sscanf(hexDigits, "%2x", &charValue); 0174 0175 replacement[0] = static_cast<char>(charValue); 0176 charsToRemove = 2 + qstrlen(hexDigits); 0177 break; 0178 } 0179 default: 0180 escapedChar = false; 0181 } 0182 0183 if (escapedChar) { 0184 result.replace(i, charsToRemove, replacement, 1); 0185 } 0186 } 0187 } 0188 0189 return result; 0190 } 0191 0192 void KeyboardTranslator::Entry::insertModifier(QString &item, int modifier) const 0193 { 0194 if ((modifier & _modifierMask) == 0U) { 0195 return; 0196 } 0197 0198 if ((modifier & _modifiers) != 0U) { 0199 item += QLatin1Char('+'); 0200 } else { 0201 item += QLatin1Char('-'); 0202 } 0203 0204 if (modifier == Qt::ShiftModifier) { 0205 item += QLatin1String("Shift"); 0206 } else if (modifier == Qt::ControlModifier) { 0207 item += QLatin1String("Ctrl"); 0208 } else if (modifier == Qt::AltModifier) { 0209 item += QLatin1String("Alt"); 0210 } else if (modifier == Qt::MetaModifier) { 0211 item += QLatin1String("Meta"); 0212 } else if (modifier == Qt::KeypadModifier) { 0213 item += QLatin1String("KeyPad"); 0214 } 0215 } 0216 0217 void KeyboardTranslator::Entry::insertState(QString &item, int state) const 0218 { 0219 if ((state & _stateMask) == 0) { 0220 return; 0221 } 0222 0223 if ((state & _state) != 0) { 0224 item += QLatin1Char('+'); 0225 } else { 0226 item += QLatin1Char('-'); 0227 } 0228 0229 if (state == KeyboardTranslator::AlternateScreenState) { 0230 item += QLatin1String("AppScreen"); 0231 } else if (state == KeyboardTranslator::NewLineState) { 0232 item += QLatin1String("NewLine"); 0233 } else if (state == KeyboardTranslator::AnsiState) { 0234 item += QLatin1String("Ansi"); 0235 } else if (state == KeyboardTranslator::CursorKeysState) { 0236 item += QLatin1String("AppCursorKeys"); 0237 } else if (state == KeyboardTranslator::AnyModifierState) { 0238 item += QLatin1String("AnyModifier"); 0239 } else if (state == KeyboardTranslator::ApplicationKeypadState) { 0240 item += QLatin1String("AppKeypad"); 0241 } 0242 } 0243 0244 QString KeyboardTranslator::Entry::resultToString(bool expandWildCards, Qt::KeyboardModifiers keyboardModifiers) const 0245 { 0246 if (!_text.isEmpty()) { 0247 return QString::fromLatin1(escapedText(expandWildCards, keyboardModifiers)); 0248 } else if (_command == EraseCommand) { 0249 return QStringLiteral("Erase"); 0250 } else if (_command == ScrollPageUpCommand) { 0251 return QStringLiteral("ScrollPageUp"); 0252 } else if (_command == ScrollPageDownCommand) { 0253 return QStringLiteral("ScrollPageDown"); 0254 } else if (_command == ScrollLineUpCommand) { 0255 return QStringLiteral("ScrollLineUp"); 0256 } else if (_command == ScrollLineDownCommand) { 0257 return QStringLiteral("ScrollLineDown"); 0258 } else if (_command == ScrollUpToTopCommand) { 0259 return QStringLiteral("ScrollUpToTop"); 0260 } else if (_command == ScrollDownToBottomCommand) { 0261 return QStringLiteral("ScrollDownToBottom"); 0262 } else if (_command == ScrollPromptUpCommand) { 0263 return QStringLiteral("ScrollPromptUp"); 0264 } else if (_command == ScrollPromptDownCommand) { 0265 return QStringLiteral("ScrollPromptDown"); 0266 } 0267 0268 return QString(); 0269 } 0270 0271 QString KeyboardTranslator::Entry::conditionToString() const 0272 { 0273 QString result = QKeySequence(_keyCode).toString(); 0274 0275 insertModifier(result, Qt::ShiftModifier); 0276 insertModifier(result, Qt::ControlModifier); 0277 insertModifier(result, Qt::AltModifier); 0278 insertModifier(result, Qt::MetaModifier); 0279 insertModifier(result, Qt::KeypadModifier); 0280 0281 insertState(result, KeyboardTranslator::AlternateScreenState); 0282 insertState(result, KeyboardTranslator::NewLineState); 0283 insertState(result, KeyboardTranslator::AnsiState); 0284 insertState(result, KeyboardTranslator::CursorKeysState); 0285 insertState(result, KeyboardTranslator::AnyModifierState); 0286 insertState(result, KeyboardTranslator::ApplicationKeypadState); 0287 0288 return result; 0289 } 0290 0291 KeyboardTranslator::KeyboardTranslator(const QString &name) 0292 : _entries(QMultiHash<int, Entry>()) 0293 , _name(name) 0294 , _description(QString()) 0295 { 0296 } 0297 0298 void KeyboardTranslator::setDescription(const QString &description) 0299 { 0300 _description = description; 0301 } 0302 0303 QString KeyboardTranslator::description() const 0304 { 0305 return _description; 0306 } 0307 0308 void KeyboardTranslator::setName(const QString &name) 0309 { 0310 _name = name; 0311 } 0312 0313 QString KeyboardTranslator::name() const 0314 { 0315 return _name; 0316 } 0317 0318 QList<KeyboardTranslator::Entry> KeyboardTranslator::entries() const 0319 { 0320 return _entries.values(); 0321 } 0322 0323 void KeyboardTranslator::addEntry(const Entry &entry) 0324 { 0325 const int keyCode = entry.keyCode(); 0326 _entries.insert(keyCode, entry); 0327 } 0328 0329 void KeyboardTranslator::replaceEntry(const Entry &existing, const Entry &replacement) 0330 { 0331 if (!existing.isNull()) { 0332 _entries.remove(existing.keyCode(), existing); 0333 } 0334 0335 _entries.insert(replacement.keyCode(), replacement); 0336 } 0337 0338 void KeyboardTranslator::removeEntry(const Entry &entry) 0339 { 0340 _entries.remove(entry.keyCode(), entry); 0341 } 0342 0343 KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const 0344 { 0345 auto it = _entries.find(keyCode); 0346 while (it != _entries.constEnd() && it.key() == keyCode) { 0347 if (it.value().matches(keyCode, modifiers, state)) { 0348 return it.value(); 0349 } 0350 ++it; 0351 } 0352 0353 return Entry(); // No matching entry 0354 }