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 }