File indexing completed on 2025-03-16 08:11:25
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 }