File indexing completed on 2024-05-19 05:28:17

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     This program is distributed in the hope that it will be useful,
0009     but WITHOUT ANY WARRANTY; without even the implied warranty of
0010     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0011     GNU General Public License for more details.
0012 
0013     You should have received a copy of the GNU General Public License
0014     along with this program; if not, write to the Free Software
0015     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0016     02110-1301  USA.
0017 */
0018 
0019 // Own
0020 #include "KeyboardTranslator.h"
0021 
0022 // System
0023 #include <cctype>
0024 #include <cstdio>
0025 
0026 // Qt
0027 #include <QBuffer>
0028 #include <QDir>
0029 #include <QFile>
0030 #include <QFileInfo>
0031 #include <QKeySequence>
0032 #include <QRegExp>
0033 #include <QTextStream>
0034 #include <QtDebug>
0035 
0036 #include "tools.h"
0037 
0038 // KDE
0039 // #include <KDebug>
0040 // #include <KLocale>
0041 // #include <KStandardDirs>
0042 
0043 using namespace Konsole;
0044 
0045 const QByteArray KeyboardTranslatorManager::defaultTranslatorText(
0046     "keyboard \"Fallback Key Translator\"\n"
0047     "key Tab : \"\\t\"");
0048 
0049 #ifdef Q_OS_MAC
0050 // On Mac, Qt::ControlModifier means Cmd, and MetaModifier means Ctrl
0051 const Qt::KeyboardModifier KeyboardTranslator::CTRL_MOD = Qt::MetaModifier;
0052 #else
0053 const Qt::KeyboardModifier KeyboardTranslator::CTRL_MOD = Qt::ControlModifier;
0054 #endif
0055 
0056 KeyboardTranslatorManager::KeyboardTranslatorManager()
0057     : _haveLoadedAll(false)
0058 {
0059 }
0060 
0061 KeyboardTranslatorManager::~KeyboardTranslatorManager()
0062 {
0063     qDeleteAll(_translators);
0064 }
0065 
0066 QString KeyboardTranslatorManager::findTranslatorPath(const QString &name)
0067 {
0068     return QString(kbLayoutDir() + name + QLatin1String(".keytab"));
0069     // return KGlobal::dirs()->findResource("data","konsole/"+name+".keytab");
0070 }
0071 
0072 void KeyboardTranslatorManager::findTranslators()
0073 {
0074     QDir dir(kbLayoutDir());
0075     QStringList filters;
0076     filters << QLatin1String("*.keytab");
0077     dir.setNameFilters(filters);
0078     QStringList list = dir.entryList(filters);
0079     //    QStringList list = KGlobal::dirs()->findAllResources("data",
0080     //                                                         "konsole/*.keytab",
0081     //                                                        KStandardDirs::NoDuplicates);
0082 
0083     // add the name of each translator to the list and associated
0084     // the name with a null pointer to indicate that the translator
0085     // has not yet been loaded from disk
0086     QStringListIterator listIter(list);
0087     while (listIter.hasNext()) {
0088         QString translatorPath = listIter.next();
0089 
0090         QString name = QFileInfo(translatorPath).baseName();
0091 
0092         if (!_translators.contains(name))
0093             _translators.insert(name, nullptr);
0094     }
0095 
0096     _haveLoadedAll = true;
0097 }
0098 
0099 const KeyboardTranslator *KeyboardTranslatorManager::findTranslator(const QString &name)
0100 {
0101     if (name.isEmpty())
0102         return defaultTranslator();
0103 
0104     if (_translators.contains(name) && _translators[name] != nullptr)
0105         return _translators[name];
0106 
0107     KeyboardTranslator *translator = loadTranslator(name);
0108 
0109     if (translator != nullptr)
0110         _translators[name] = translator;
0111     else if (!name.isEmpty())
0112         qDebug() << "Unable to load translator" << name;
0113 
0114     return translator;
0115 }
0116 
0117 bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator *translator)
0118 {
0119     qDebug() << "KeyboardTranslatorManager::saveTranslator"
0120              << "unimplemented";
0121     Q_UNUSED(translator);
0122 #if 0
0123     const QString path = KGlobal::dirs()->saveLocation("data","konsole/")+translator->name()
0124            +".keytab";
0125 
0126     //kDebug() << "Saving translator to" << path;
0127 
0128     QFile destination(path);
0129     if (!destination.open(QIODevice::WriteOnly | QIODevice::Text))
0130     {
0131         qDebug() << "Unable to save keyboard translation:"
0132                    << destination.errorString();
0133         return false;
0134     }
0135 
0136     {
0137         KeyboardTranslatorWriter writer(&destination);
0138         writer.writeHeader(translator->description());
0139 
0140         QListIterator<KeyboardTranslator::Entry> iter(translator->entries());
0141         while ( iter.hasNext() )
0142             writer.writeEntry(iter.next());
0143     }
0144 
0145     destination.close();
0146 #endif
0147     return true;
0148 }
0149 
0150 KeyboardTranslator *KeyboardTranslatorManager::loadTranslator(const QString &name)
0151 {
0152     const QString &path = findTranslatorPath(name);
0153 
0154     QFile source(path);
0155     if (name.isEmpty() || !source.open(QIODevice::ReadOnly | QIODevice::Text))
0156         return nullptr;
0157 
0158     return loadTranslator(&source, name);
0159 }
0160 
0161 const KeyboardTranslator *KeyboardTranslatorManager::defaultTranslator()
0162 {
0163     // Try to find the default.keytab file if it exists, otherwise
0164     // fall back to the hard-coded one
0165     const KeyboardTranslator *translator = findTranslator(QLatin1String("default"));
0166     if (!translator) {
0167         QBuffer textBuffer;
0168         textBuffer.setData(defaultTranslatorText);
0169         textBuffer.open(QIODevice::ReadOnly);
0170         translator = loadTranslator(&textBuffer, QLatin1String("fallback"));
0171     }
0172     return translator;
0173 }
0174 
0175 KeyboardTranslator *KeyboardTranslatorManager::loadTranslator(QIODevice *source, const QString &name)
0176 {
0177     KeyboardTranslator *translator = new KeyboardTranslator(name);
0178     KeyboardTranslatorReader reader(source);
0179     translator->setDescription(reader.description());
0180     while (reader.hasNextEntry())
0181         translator->addEntry(reader.nextEntry());
0182 
0183     source->close();
0184 
0185     if (!reader.parseError()) {
0186         return translator;
0187     } else {
0188         delete translator;
0189         return nullptr;
0190     }
0191 }
0192 
0193 KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice *destination)
0194     : _destination(destination)
0195 {
0196     Q_ASSERT(destination && destination->isWritable());
0197 
0198     _writer = new QTextStream(_destination);
0199 }
0200 KeyboardTranslatorWriter::~KeyboardTranslatorWriter()
0201 {
0202     delete _writer;
0203 }
0204 void KeyboardTranslatorWriter::writeHeader(const QString &description)
0205 {
0206     *_writer << "keyboard \"" << description << '\"' << '\n';
0207 }
0208 void KeyboardTranslatorWriter::writeEntry(const KeyboardTranslator::Entry &entry)
0209 {
0210     QString result;
0211     if (entry.command() != KeyboardTranslator::NoCommand)
0212         result = entry.resultToString();
0213     else
0214         result = QLatin1Char('\"') + entry.resultToString() + QLatin1Char('\"');
0215 
0216     *_writer << QLatin1String("key ") << entry.conditionToString() << QLatin1String(" : ") << result << QLatin1Char('\n');
0217 }
0218 
0219 // each line of the keyboard translation file is one of:
0220 //
0221 // - keyboard "name"
0222 // - key KeySequence : "characters"
0223 // - key KeySequence : CommandName
0224 //
0225 // KeySequence begins with the name of the key ( taken from the Qt::Key enum )
0226 // and is followed by the keyboard modifiers and state flags ( with + or - in front
0227 // of each modifier or flag to indicate whether it is required ).  All keyboard modifiers
0228 // and flags are optional, if a particular modifier or state is not specified it is
0229 // assumed not to be a part of the sequence.  The key sequence may contain whitespace
0230 //
0231 // eg:  "key Up+Shift : scrollLineUp"
0232 //      "key Next-Shift : "\E[6~"
0233 //
0234 // (lines containing only whitespace are ignored, parseLine assumes that comments have
0235 // already been removed)
0236 //
0237 
0238 KeyboardTranslatorReader::KeyboardTranslatorReader(QIODevice *source)
0239     : _source(source)
0240     , _hasNext(false)
0241 {
0242     // read input until we find the description
0243     while (_description.isEmpty() && !source->atEnd()) {
0244         QList<Token> tokens = tokenize(QString::fromUtf8(source->readLine()));
0245         if (!tokens.isEmpty() && tokens.first().type == Token::TitleKeyword)
0246             _description = tokens[1].text;
0247     }
0248     // read first entry (if any)
0249     readNext();
0250 }
0251 void KeyboardTranslatorReader::readNext()
0252 {
0253     // find next entry
0254     while (!_source->atEnd()) {
0255         const QList<Token> &tokens = tokenize(QString::fromUtf8(_source->readLine()));
0256         if (!tokens.isEmpty() && tokens.first().type == Token::KeyKeyword) {
0257             KeyboardTranslator::States flags = KeyboardTranslator::NoState;
0258             KeyboardTranslator::States flagMask = KeyboardTranslator::NoState;
0259             Qt::KeyboardModifiers modifiers = Qt::NoModifier;
0260             Qt::KeyboardModifiers modifierMask = Qt::NoModifier;
0261 
0262             int keyCode = Qt::Key_unknown;
0263 
0264             decodeSequence(tokens[1].text.toLower(), keyCode, modifiers, modifierMask, flags, flagMask);
0265 
0266             KeyboardTranslator::Command command = KeyboardTranslator::NoCommand;
0267             QByteArray text;
0268 
0269             // get text or command
0270             if (tokens[2].type == Token::OutputText) {
0271                 text = tokens[2].text.toLocal8Bit();
0272             } else if (tokens[2].type == Token::Command) {
0273                 // identify command
0274                 if (!parseAsCommand(tokens[2].text, command))
0275                     qDebug() << "Command" << tokens[2].text << "not understood.";
0276             }
0277 
0278             KeyboardTranslator::Entry newEntry;
0279             newEntry.setKeyCode(keyCode);
0280             newEntry.setState(flags);
0281             newEntry.setStateMask(flagMask);
0282             newEntry.setModifiers(modifiers);
0283             newEntry.setModifierMask(modifierMask);
0284             newEntry.setText(text);
0285             newEntry.setCommand(command);
0286 
0287             _nextEntry = newEntry;
0288 
0289             _hasNext = true;
0290 
0291             return;
0292         }
0293     }
0294 
0295     _hasNext = false;
0296 }
0297 
0298 bool KeyboardTranslatorReader::parseAsCommand(const QString &text, KeyboardTranslator::Command &command)
0299 {
0300     if (text.compare(QLatin1String("erase"), Qt::CaseInsensitive) == 0)
0301         command = KeyboardTranslator::EraseCommand;
0302     else if (text.compare(QLatin1String("scrollpageup"), Qt::CaseInsensitive) == 0)
0303         command = KeyboardTranslator::ScrollPageUpCommand;
0304     else if (text.compare(QLatin1String("scrollpagedown"), Qt::CaseInsensitive) == 0)
0305         command = KeyboardTranslator::ScrollPageDownCommand;
0306     else if (text.compare(QLatin1String("scrolllineup"), Qt::CaseInsensitive) == 0)
0307         command = KeyboardTranslator::ScrollLineUpCommand;
0308     else if (text.compare(QLatin1String("scrolllinedown"), Qt::CaseInsensitive) == 0)
0309         command = KeyboardTranslator::ScrollLineDownCommand;
0310     else if (text.compare(QLatin1String("scrolllock"), Qt::CaseInsensitive) == 0)
0311         command = KeyboardTranslator::ScrollLockCommand;
0312     else if (text.compare(QLatin1String("scrolluptotop"), Qt::CaseInsensitive) == 0)
0313         command = KeyboardTranslator::ScrollUpToTopCommand;
0314     else if (text.compare(QLatin1String("scrolldowntobottom"), Qt::CaseInsensitive) == 0)
0315         command = KeyboardTranslator::ScrollDownToBottomCommand;
0316     else
0317         return false;
0318 
0319     return true;
0320 }
0321 
0322 bool KeyboardTranslatorReader::decodeSequence(const QString &text,
0323                                               int &keyCode,
0324                                               Qt::KeyboardModifiers &modifiers,
0325                                               Qt::KeyboardModifiers &modifierMask,
0326                                               KeyboardTranslator::States &flags,
0327                                               KeyboardTranslator::States &flagMask)
0328 {
0329     bool isWanted = true;
0330     bool endOfItem = false;
0331     QString buffer;
0332 
0333     Qt::KeyboardModifiers tempModifiers = modifiers;
0334     Qt::KeyboardModifiers tempModifierMask = modifierMask;
0335     KeyboardTranslator::States tempFlags = flags;
0336     KeyboardTranslator::States tempFlagMask = flagMask;
0337 
0338     for (int i = 0; i < text.size(); i++) {
0339         const QChar &ch = text[i];
0340         bool isFirstLetter = i == 0;
0341         bool isLastLetter = (i == text.size() - 1);
0342         endOfItem = true;
0343         if (ch.isLetterOrNumber()) {
0344             endOfItem = false;
0345             buffer.append(ch);
0346         } else if (isFirstLetter) {
0347             buffer.append(ch);
0348         }
0349 
0350         if ((endOfItem || isLastLetter) && !buffer.isEmpty()) {
0351             Qt::KeyboardModifier itemModifier = Qt::NoModifier;
0352             int itemKeyCode = 0;
0353             KeyboardTranslator::State itemFlag = KeyboardTranslator::NoState;
0354 
0355             if (parseAsModifier(buffer, itemModifier)) {
0356                 tempModifierMask |= itemModifier;
0357 
0358                 if (isWanted)
0359                     tempModifiers |= itemModifier;
0360             } else if (parseAsStateFlag(buffer, itemFlag)) {
0361                 tempFlagMask |= itemFlag;
0362 
0363                 if (isWanted)
0364                     tempFlags |= itemFlag;
0365             } else if (parseAsKeyCode(buffer, itemKeyCode))
0366                 keyCode = itemKeyCode;
0367             else
0368                 qDebug() << "Unable to parse key binding item:" << buffer;
0369 
0370             buffer.clear();
0371         }
0372 
0373         // check if this is a wanted / not-wanted flag and update the
0374         // state ready for the next item
0375         if (ch == QLatin1Char('+'))
0376             isWanted = true;
0377         else if (ch == QLatin1Char('-'))
0378             isWanted = false;
0379     }
0380 
0381     modifiers = tempModifiers;
0382     modifierMask = tempModifierMask;
0383     flags = tempFlags;
0384     flagMask = tempFlagMask;
0385 
0386     return true;
0387 }
0388 
0389 bool KeyboardTranslatorReader::parseAsModifier(const QString &item, Qt::KeyboardModifier &modifier)
0390 {
0391     if (item == QLatin1String("shift"))
0392         modifier = Qt::ShiftModifier;
0393     else if (item == QLatin1String("ctrl") || item == QLatin1String("control"))
0394         modifier = Qt::ControlModifier;
0395     else if (item == QLatin1String("alt"))
0396         modifier = Qt::AltModifier;
0397     else if (item == QLatin1String("meta"))
0398         modifier = Qt::MetaModifier;
0399     else if (item == QLatin1String("keypad"))
0400         modifier = Qt::KeypadModifier;
0401     else
0402         return false;
0403 
0404     return true;
0405 }
0406 bool KeyboardTranslatorReader::parseAsStateFlag(const QString &item, KeyboardTranslator::State &flag)
0407 {
0408     if (item == QLatin1String("appcukeys") || item == QLatin1String("appcursorkeys"))
0409         flag = KeyboardTranslator::CursorKeysState;
0410     else if (item == QLatin1String("ansi"))
0411         flag = KeyboardTranslator::AnsiState;
0412     else if (item == QLatin1String("newline"))
0413         flag = KeyboardTranslator::NewLineState;
0414     else if (item == QLatin1String("appscreen"))
0415         flag = KeyboardTranslator::AlternateScreenState;
0416     else if (item == QLatin1String("anymod") || item == QLatin1String("anymodifier"))
0417         flag = KeyboardTranslator::AnyModifierState;
0418     else if (item == QLatin1String("appkeypad"))
0419         flag = KeyboardTranslator::ApplicationKeypadState;
0420     else
0421         return false;
0422 
0423     return true;
0424 }
0425 bool KeyboardTranslatorReader::parseAsKeyCode(const QString &item, int &keyCode)
0426 {
0427     QKeySequence sequence = QKeySequence::fromString(item);
0428     if (!sequence.isEmpty()) {
0429 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0430         keyCode = sequence[0];
0431 #else
0432         keyCode = sequence[0].toCombined();
0433 #endif
0434 
0435         if (sequence.count() > 1) {
0436             qDebug() << "Unhandled key codes in sequence: " << item;
0437         }
0438     }
0439     // additional cases implemented for backwards compatibility with KDE 3
0440     else if (item == QLatin1String("prior"))
0441         keyCode = Qt::Key_PageUp;
0442     else if (item == QLatin1String("next"))
0443         keyCode = Qt::Key_PageDown;
0444     else
0445         return false;
0446 
0447     return true;
0448 }
0449 
0450 QString KeyboardTranslatorReader::description() const
0451 {
0452     return _description;
0453 }
0454 bool KeyboardTranslatorReader::hasNextEntry() const
0455 {
0456     return _hasNext;
0457 }
0458 KeyboardTranslator::Entry KeyboardTranslatorReader::createEntry(const QString &condition, const QString &result)
0459 {
0460     QString entryString = QString::fromLatin1("keyboard \"temporary\"\nkey ");
0461     entryString.append(condition);
0462     entryString.append(QLatin1String(" : "));
0463 
0464     // if 'result' is the name of a command then the entry result will be that command,
0465     // otherwise the result will be treated as a string to echo when the key sequence
0466     // specified by 'condition' is pressed
0467     KeyboardTranslator::Command command;
0468     if (parseAsCommand(result, command))
0469         entryString.append(result);
0470     else
0471         entryString.append(QLatin1Char('\"') + result + QLatin1Char('\"'));
0472 
0473     QByteArray array = entryString.toUtf8();
0474     QBuffer buffer(&array);
0475     buffer.open(QIODevice::ReadOnly);
0476     KeyboardTranslatorReader reader(&buffer);
0477 
0478     KeyboardTranslator::Entry entry;
0479     if (reader.hasNextEntry())
0480         entry = reader.nextEntry();
0481 
0482     return entry;
0483 }
0484 
0485 KeyboardTranslator::Entry KeyboardTranslatorReader::nextEntry()
0486 {
0487     Q_ASSERT(_hasNext);
0488     KeyboardTranslator::Entry entry = _nextEntry;
0489     readNext();
0490     return entry;
0491 }
0492 bool KeyboardTranslatorReader::parseError()
0493 {
0494     return false;
0495 }
0496 QList<KeyboardTranslatorReader::Token> KeyboardTranslatorReader::tokenize(const QString &line)
0497 {
0498     QString text = line;
0499 
0500     // remove comments
0501     bool inQuotes = false;
0502     int commentPos = -1;
0503     for (int i = text.length() - 1; i >= 0; i--) {
0504         QChar ch = text[i];
0505         if (ch == QLatin1Char('\"'))
0506             inQuotes = !inQuotes;
0507         else if (ch == QLatin1Char('#') && !inQuotes)
0508             commentPos = i;
0509     }
0510     if (commentPos != -1)
0511         text.remove(commentPos, text.length());
0512 
0513     text = text.simplified();
0514 
0515     // title line: keyboard "title"
0516     static QRegExp title(QLatin1String("keyboard\\s+\"(.*)\""));
0517     // key line: key KeySequence : "output"
0518     // key line: key KeySequence : command
0519     static QRegExp key(QLatin1String("key\\s+([\\w\\+\\s\\-\\*\\.]+)\\s*:\\s*(\"(.*)\"|\\w+)"));
0520 
0521     QList<Token> list;
0522     if (text.isEmpty()) {
0523         return list;
0524     }
0525 
0526     if (title.exactMatch(text)) {
0527         Token titleToken = {Token::TitleKeyword, QString()};
0528         Token textToken = {Token::TitleText, title.capturedTexts().at(1)};
0529 
0530         list << titleToken << textToken;
0531     } else if (key.exactMatch(text)) {
0532         Token keyToken = {Token::KeyKeyword, QString()};
0533         Token sequenceToken = {Token::KeySequence, key.capturedTexts().value(1).remove(QLatin1Char(' '))};
0534 
0535         list << keyToken << sequenceToken;
0536 
0537         if (key.capturedTexts().at(3).isEmpty()) {
0538             // capturedTexts()[2] is a command
0539             Token commandToken = {Token::Command, key.capturedTexts().at(2)};
0540             list << commandToken;
0541         } else {
0542             // capturedTexts()[3] is the output string
0543             Token outputToken = {Token::OutputText, key.capturedTexts().at(3)};
0544             list << outputToken;
0545         }
0546     } else {
0547         qDebug() << "Line in keyboard translator file could not be understood:" << text;
0548     }
0549 
0550     return list;
0551 }
0552 
0553 QList<QString> KeyboardTranslatorManager::allTranslators()
0554 {
0555     if (!_haveLoadedAll) {
0556         findTranslators();
0557     }
0558 
0559     return _translators.keys();
0560 }
0561 
0562 KeyboardTranslator::Entry::Entry()
0563     : _keyCode(0)
0564     , _modifiers(Qt::NoModifier)
0565     , _modifierMask(Qt::NoModifier)
0566     , _state(NoState)
0567     , _stateMask(NoState)
0568     , _command(NoCommand)
0569 {
0570 }
0571 
0572 bool KeyboardTranslator::Entry::operator==(const Entry &rhs) const
0573 {
0574     return _keyCode == rhs._keyCode && _modifiers == rhs._modifiers && _modifierMask == rhs._modifierMask && _state == rhs._state
0575         && _stateMask == rhs._stateMask && _command == rhs._command && _text == rhs._text;
0576 }
0577 
0578 bool KeyboardTranslator::Entry::matches(int keyCode, Qt::KeyboardModifiers modifiers, States testState) const
0579 {
0580 #ifdef Q_OS_MAC
0581     // On Mac, arrow keys are considered part of keypad. Ignore that.
0582     modifiers &= ~Qt::KeypadModifier;
0583 #endif
0584 
0585     if (_keyCode != keyCode)
0586         return false;
0587 
0588     if ((modifiers & _modifierMask) != (_modifiers & _modifierMask))
0589         return false;
0590 
0591     // if modifiers is non-zero, the 'any modifier' state is implicit
0592     if ((modifiers & ~Qt::KeypadModifier) != 0)
0593         testState |= AnyModifierState;
0594 
0595     if ((testState & _stateMask) != (_state & _stateMask))
0596         return false;
0597 
0598     // special handling for the 'Any Modifier' state, which checks for the presence of
0599     // any or no modifiers.  In this context, the 'keypad' modifier does not count.
0600     bool anyModifiersSet = modifiers != 0 && modifiers != Qt::KeypadModifier;
0601     bool wantAnyModifier = _state & KeyboardTranslator::AnyModifierState;
0602     if (_stateMask & KeyboardTranslator::AnyModifierState) {
0603         if (wantAnyModifier != anyModifiersSet)
0604             return false;
0605     }
0606 
0607     return true;
0608 }
0609 QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards, Qt::KeyboardModifiers modifiers) const
0610 {
0611     QByteArray result(text(expandWildCards, modifiers));
0612 
0613     for (int i = 0; i < result.size(); i++) {
0614         char ch = result[i];
0615         char replacement = 0;
0616 
0617         switch (ch) {
0618         case 27:
0619             replacement = 'E';
0620             break;
0621         case 8:
0622             replacement = 'b';
0623             break;
0624         case 12:
0625             replacement = 'f';
0626             break;
0627         case 9:
0628             replacement = 't';
0629             break;
0630         case 13:
0631             replacement = 'r';
0632             break;
0633         case 10:
0634             replacement = 'n';
0635             break;
0636         default:
0637             // any character which is not printable is replaced by an equivalent
0638             // \xhh escape sequence (where 'hh' are the corresponding hex digits)
0639             if (!QChar(QLatin1Char(ch)).isPrint())
0640                 replacement = 'x';
0641         }
0642 
0643         if (replacement == 'x') {
0644             result.replace(i, 1, QByteArray("\\x" + QByteArray(1, ch).toHex()));
0645         } else if (replacement != 0) {
0646             result.remove(i, 1);
0647             result.insert(i, '\\');
0648             result.insert(i + 1, replacement);
0649         }
0650     }
0651 
0652     return result;
0653 }
0654 QByteArray KeyboardTranslator::Entry::unescape(const QByteArray &input) const
0655 {
0656     QByteArray result(input);
0657 
0658     for (int i = 0; i < result.size() - 1; i++) {
0659         auto ch = result[i];
0660         if (ch == '\\') {
0661             char replacement[2] = {0, 0};
0662             int charsToRemove = 2;
0663             bool escapedChar = true;
0664 
0665             switch (result[i + 1]) {
0666             case 'E':
0667                 replacement[0] = 27;
0668                 break;
0669             case 'b':
0670                 replacement[0] = 8;
0671                 break;
0672             case 'f':
0673                 replacement[0] = 12;
0674                 break;
0675             case 't':
0676                 replacement[0] = 9;
0677                 break;
0678             case 'r':
0679                 replacement[0] = 13;
0680                 break;
0681             case 'n':
0682                 replacement[0] = 10;
0683                 break;
0684             case 'x': {
0685                 // format is \xh or \xhh where 'h' is a hexadecimal
0686                 // digit from 0-9 or A-F which should be replaced
0687                 // with the corresponding character value
0688                 char hexDigits[3] = {0};
0689 
0690                 if ((i < result.size() - 2) && isxdigit(result[i + 2]))
0691                     hexDigits[0] = result[i + 2];
0692                 if ((i < result.size() - 3) && isxdigit(result[i + 3]))
0693                     hexDigits[1] = result[i + 3];
0694 
0695                 unsigned charValue = 0;
0696                 sscanf(hexDigits, "%x", &charValue);
0697 
0698                 replacement[0] = (char)charValue;
0699                 charsToRemove = 2 + strlen(hexDigits);
0700             } break;
0701             default:
0702                 escapedChar = false;
0703             }
0704 
0705             if (escapedChar)
0706                 result.replace(i, charsToRemove, replacement, 1);
0707         }
0708     }
0709 
0710     return result;
0711 }
0712 
0713 void KeyboardTranslator::Entry::insertModifier(QString &item, int modifier) const
0714 {
0715     if (!(modifier & _modifierMask))
0716         return;
0717 
0718     if (modifier & _modifiers)
0719         item += QLatin1Char('+');
0720     else
0721         item += QLatin1Char('-');
0722 
0723     if (modifier == Qt::ShiftModifier)
0724         item += QLatin1String("Shift");
0725     else if (modifier == Qt::ControlModifier)
0726         item += QLatin1String("Ctrl");
0727     else if (modifier == Qt::AltModifier)
0728         item += QLatin1String("Alt");
0729     else if (modifier == Qt::MetaModifier)
0730         item += QLatin1String("Meta");
0731     else if (modifier == Qt::KeypadModifier)
0732         item += QLatin1String("KeyPad");
0733 }
0734 void KeyboardTranslator::Entry::insertState(QString &item, int state) const
0735 {
0736     if (!(state & _stateMask))
0737         return;
0738 
0739     if (state & _state)
0740         item += QLatin1Char('+');
0741     else
0742         item += QLatin1Char('-');
0743 
0744     if (state == KeyboardTranslator::AlternateScreenState)
0745         item += QLatin1String("AppScreen");
0746     else if (state == KeyboardTranslator::NewLineState)
0747         item += QLatin1String("NewLine");
0748     else if (state == KeyboardTranslator::AnsiState)
0749         item += QLatin1String("Ansi");
0750     else if (state == KeyboardTranslator::CursorKeysState)
0751         item += QLatin1String("AppCursorKeys");
0752     else if (state == KeyboardTranslator::AnyModifierState)
0753         item += QLatin1String("AnyModifier");
0754     else if (state == KeyboardTranslator::ApplicationKeypadState)
0755         item += QLatin1String("AppKeypad");
0756 }
0757 QString KeyboardTranslator::Entry::resultToString(bool expandWildCards, Qt::KeyboardModifiers modifiers) const
0758 {
0759     if (!_text.isEmpty())
0760         return QString::fromLatin1(escapedText(expandWildCards, modifiers));
0761     else if (_command == EraseCommand)
0762         return QLatin1String("Erase");
0763     else if (_command == ScrollPageUpCommand)
0764         return QLatin1String("ScrollPageUp");
0765     else if (_command == ScrollPageDownCommand)
0766         return QLatin1String("ScrollPageDown");
0767     else if (_command == ScrollLineUpCommand)
0768         return QLatin1String("ScrollLineUp");
0769     else if (_command == ScrollLineDownCommand)
0770         return QLatin1String("ScrollLineDown");
0771     else if (_command == ScrollLockCommand)
0772         return QLatin1String("ScrollLock");
0773     else if (_command == ScrollUpToTopCommand)
0774         return QLatin1String("ScrollUpToTop");
0775     else if (_command == ScrollDownToBottomCommand)
0776         return QLatin1String("ScrollDownToBottom");
0777 
0778     return QString();
0779 }
0780 QString KeyboardTranslator::Entry::conditionToString() const
0781 {
0782     QString result = QKeySequence(_keyCode).toString();
0783 
0784     insertModifier(result, Qt::ShiftModifier);
0785     insertModifier(result, Qt::ControlModifier);
0786     insertModifier(result, Qt::AltModifier);
0787     insertModifier(result, Qt::MetaModifier);
0788     insertModifier(result, Qt::KeypadModifier);
0789 
0790     insertState(result, KeyboardTranslator::AlternateScreenState);
0791     insertState(result, KeyboardTranslator::NewLineState);
0792     insertState(result, KeyboardTranslator::AnsiState);
0793     insertState(result, KeyboardTranslator::CursorKeysState);
0794     insertState(result, KeyboardTranslator::AnyModifierState);
0795     insertState(result, KeyboardTranslator::ApplicationKeypadState);
0796 
0797     return result;
0798 }
0799 
0800 KeyboardTranslator::KeyboardTranslator(const QString &name)
0801     : _name(name)
0802 {
0803 }
0804 
0805 void KeyboardTranslator::setDescription(const QString &description)
0806 {
0807     _description = description;
0808 }
0809 QString KeyboardTranslator::description() const
0810 {
0811     return _description;
0812 }
0813 void KeyboardTranslator::setName(const QString &name)
0814 {
0815     _name = name;
0816 }
0817 QString KeyboardTranslator::name() const
0818 {
0819     return _name;
0820 }
0821 
0822 QList<KeyboardTranslator::Entry> KeyboardTranslator::entries() const
0823 {
0824     return _entries.values();
0825 }
0826 
0827 void KeyboardTranslator::addEntry(const Entry &entry)
0828 {
0829     const int keyCode = entry.keyCode();
0830     _entries.insert(keyCode, entry);
0831 }
0832 void KeyboardTranslator::replaceEntry(const Entry &existing, const Entry &replacement)
0833 {
0834     if (!existing.isNull())
0835         _entries.remove(existing.keyCode(), existing);
0836     _entries.insert(replacement.keyCode(), replacement);
0837 }
0838 void KeyboardTranslator::removeEntry(const Entry &entry)
0839 {
0840     _entries.remove(entry.keyCode(), entry);
0841 }
0842 KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const
0843 {
0844     for (auto it = _entries.cbegin(), end = _entries.cend(); it != end; ++it) {
0845         if (it.key() == keyCode)
0846             if (it.value().matches(keyCode, modifiers, state))
0847                 return *it;
0848     }
0849     return Entry(); // entry not found
0850 }
0851 void KeyboardTranslatorManager::addTranslator(KeyboardTranslator *translator)
0852 {
0853     _translators.insert(translator->name(), translator);
0854 
0855     if (!saveTranslator(translator))
0856         qDebug() << "Unable to save translator" << translator->name() << "to disk.";
0857 }
0858 bool KeyboardTranslatorManager::deleteTranslator(const QString &name)
0859 {
0860     Q_ASSERT(_translators.contains(name));
0861 
0862     // locate and delete
0863     QString path = findTranslatorPath(name);
0864     if (QFile::remove(path)) {
0865         _translators.remove(name);
0866         return true;
0867     } else {
0868         qDebug() << "Failed to remove translator - " << path;
0869         return false;
0870     }
0871 }
0872 Q_GLOBAL_STATIC(KeyboardTranslatorManager, theKeyboardTranslatorManager)
0873 KeyboardTranslatorManager *KeyboardTranslatorManager::instance()
0874 {
0875     return theKeyboardTranslatorManager;
0876 }