2 This source file is part of Konsole, a terminal emulator.
4 Copyright (C) 2007 by Robert Knight <robertknight@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 #include "KeyboardTranslator.h"
30 #include <QtCore/QBuffer>
32 #include <QtCore/QFile>
33 #include <QtCore/QFileInfo>
34 #include <QtCore/QTextStream>
35 #include <QtGui/QKeySequence>
40 #include <KStandardDirs>
42 using namespace Konsole
;
45 const char* KeyboardTranslatorManager::defaultTranslatorText
=
46 #include <DefaultTranslatorText.h>
49 KeyboardTranslatorManager::KeyboardTranslatorManager()
50 : _haveLoadedAll(false)
53 KeyboardTranslatorManager::~KeyboardTranslatorManager()
55 qDeleteAll(_translators
.values());
57 QString
KeyboardTranslatorManager::findTranslatorPath(const QString
& name
)
59 return KGlobal::dirs()->findResource("data","konsole/"+name
+".keytab");
61 void KeyboardTranslatorManager::findTranslators()
63 QStringList list
= KGlobal::dirs()->findAllResources("data",
65 KStandardDirs::NoDuplicates
);
67 // add the name of each translator to the list and associated
68 // the name with a null pointer to indicate that the translator
69 // has not yet been loaded from disk
70 QStringListIterator
listIter(list
);
71 while (listIter
.hasNext())
73 QString translatorPath
= listIter
.next();
75 QString name
= QFileInfo(translatorPath
).baseName();
77 if ( !_translators
.contains(name
) )
78 _translators
.insert(name
,0);
81 _haveLoadedAll
= true;
84 const KeyboardTranslator
* KeyboardTranslatorManager::findTranslator(const QString
& name
)
87 return defaultTranslator();
89 if ( _translators
.contains(name
) && _translators
[name
] != 0 )
90 return _translators
[name
];
92 KeyboardTranslator
* translator
= loadTranslator(name
);
94 if ( translator
!= 0 )
95 _translators
[name
] = translator
;
96 else if ( !name
.isEmpty() )
97 kWarning() << "Unable to load translator" << name
;
102 bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator
* translator
)
104 const QString path
= KGlobal::dirs()->saveLocation("data","konsole/")+translator
->name()
107 kDebug() << "Saving translator to" << path
;
109 QFile
destination(path
);
111 if (!destination
.open(QIODevice::WriteOnly
| QIODevice::Text
))
113 kWarning() << "Unable to save keyboard translation:"
114 << destination
.errorString();
120 KeyboardTranslatorWriter
writer(&destination
);
121 writer
.writeHeader(translator
->description());
123 QListIterator
<KeyboardTranslator::Entry
> iter(translator
->entries());
124 while ( iter
.hasNext() )
125 writer
.writeEntry(iter
.next());
133 KeyboardTranslator
* KeyboardTranslatorManager::loadTranslator(const QString
& name
)
135 const QString
& path
= findTranslatorPath(name
);
138 if (name
.isEmpty() || !source
.open(QIODevice::ReadOnly
| QIODevice::Text
))
141 return loadTranslator(&source
,name
);
144 const KeyboardTranslator
* KeyboardTranslatorManager::defaultTranslator()
146 kDebug() << "Loading default translator from text" << defaultTranslatorText
;
148 textBuffer
.setData(defaultTranslatorText
,strlen(defaultTranslatorText
));
149 return loadTranslator(&textBuffer
,"fallback");
152 KeyboardTranslator
* KeyboardTranslatorManager::loadTranslator(QIODevice
* source
,const QString
& name
)
154 KeyboardTranslator
* translator
= new KeyboardTranslator(name
);
155 KeyboardTranslatorReader
reader(source
);
156 translator
->setDescription( reader
.description() );
157 while ( reader
.hasNextEntry() )
158 translator
->addEntry(reader
.nextEntry());
162 if ( !reader
.parseError() )
173 KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice
* destination
)
174 : _destination(destination
)
176 Q_ASSERT( destination
&& destination
->isWritable() );
178 _writer
= new QTextStream(_destination
);
180 KeyboardTranslatorWriter::~KeyboardTranslatorWriter()
184 void KeyboardTranslatorWriter::writeHeader( const QString
& description
)
186 *_writer
<< "keyboard \"" << description
<< '\"' << '\n';
188 void KeyboardTranslatorWriter::writeEntry( const KeyboardTranslator::Entry
& entry
)
192 if ( entry
.command() != KeyboardTranslator::NoCommand
)
193 result
= entry
.resultToString();
195 result
= '\"' + entry
.resultToString() + '\"';
197 *_writer
<< "key " << entry
.conditionToString() << " : " << result
<< '\n';
201 // each line of the keyboard translation file is one of:
204 // - key KeySequence : "characters"
205 // - key KeySequence : CommandName
207 // KeySequence begins with the name of the key ( taken from the Qt::Key enum )
208 // and is followed by the keyboard modifiers and state flags ( with + or - in front
209 // of each modifier or flag to indicate whether it is required ). All keyboard modifiers
210 // and flags are optional, if a particular modifier or state is not specified it is
211 // assumed not to be a part of the sequence. The key sequence may contain whitespace
213 // eg: "key Up+Shift : scrollLineUp"
214 // "key Next-Shift : "\E[6~"
216 // (lines containing only whitespace are ignored, parseLine assumes that comments have
217 // already been removed)
220 KeyboardTranslatorReader::KeyboardTranslatorReader( QIODevice
* source
)
224 // read input until we find the description
225 while ( _description
.isEmpty() && !source
->atEnd() )
227 const QList
<Token
>& tokens
= tokenize( QString(source
->readLine()) );
229 if ( !tokens
.isEmpty() && tokens
.first().type
== Token::TitleKeyword
)
231 _description
= i18n(tokens
[1].text
.toUtf8());
237 void KeyboardTranslatorReader::readNext()
240 while ( !_source
->atEnd() )
242 const QList
<Token
>& tokens
= tokenize( QString(_source
->readLine()) );
243 if ( !tokens
.isEmpty() && tokens
.first().type
== Token::KeyKeyword
)
245 KeyboardTranslator::States flags
= KeyboardTranslator::NoState
;
246 KeyboardTranslator::States flagMask
= KeyboardTranslator::NoState
;
247 Qt::KeyboardModifiers modifiers
= Qt::NoModifier
;
248 Qt::KeyboardModifiers modifierMask
= Qt::NoModifier
;
250 int keyCode
= Qt::Key_unknown
;
252 decodeSequence(tokens
[1].text
.toLower(),
259 KeyboardTranslator::Command command
= KeyboardTranslator::NoCommand
;
262 // get text or command
263 if ( tokens
[2].type
== Token::OutputText
)
265 text
= tokens
[2].text
.toLocal8Bit();
267 else if ( tokens
[2].type
== Token::Command
)
270 if (!parseAsCommand(tokens
[2].text
,command
))
271 kWarning() << "Command" << tokens
[2].text
<< "not understood.";
274 KeyboardTranslator::Entry newEntry
;
275 newEntry
.setKeyCode( keyCode
);
276 newEntry
.setState( flags
);
277 newEntry
.setStateMask( flagMask
);
278 newEntry
.setModifiers( modifiers
);
279 newEntry
.setModifierMask( modifierMask
);
280 newEntry
.setText( text
);
281 newEntry
.setCommand( command
);
283 _nextEntry
= newEntry
;
294 bool KeyboardTranslatorReader::parseAsCommand(const QString
& text
,KeyboardTranslator::Command
& command
)
296 if ( text
.compare("erase",Qt::CaseInsensitive
) == 0 )
297 command
= KeyboardTranslator::EraseCommand
;
298 else if ( text
.compare("scrollpageup",Qt::CaseInsensitive
) == 0 )
299 command
= KeyboardTranslator::ScrollPageUpCommand
;
300 else if ( text
.compare("scrollpagedown",Qt::CaseInsensitive
) == 0 )
301 command
= KeyboardTranslator::ScrollPageDownCommand
;
302 else if ( text
.compare("scrolllineup",Qt::CaseInsensitive
) == 0 )
303 command
= KeyboardTranslator::ScrollLineUpCommand
;
304 else if ( text
.compare("scrolllinedown",Qt::CaseInsensitive
) == 0 )
305 command
= KeyboardTranslator::ScrollLineDownCommand
;
306 else if ( text
.compare("scrolllock",Qt::CaseInsensitive
) == 0 )
307 command
= KeyboardTranslator::ScrollLockCommand
;
314 bool KeyboardTranslatorReader::decodeSequence(const QString
& text
,
316 Qt::KeyboardModifiers
& modifiers
,
317 Qt::KeyboardModifiers
& modifierMask
,
318 KeyboardTranslator::States
& flags
,
319 KeyboardTranslator::States
& flagMask
)
321 bool isWanted
= true;
322 bool endOfItem
= false;
325 Qt::KeyboardModifiers tempModifiers
= modifiers
;
326 Qt::KeyboardModifiers tempModifierMask
= modifierMask
;
327 KeyboardTranslator::States tempFlags
= flags
;
328 KeyboardTranslator::States tempFlagMask
= flagMask
;
330 for ( int i
= 0 ; i
< text
.count() ; i
++ )
332 const QChar
& ch
= text
[i
];
333 bool isLastLetter
= ( i
== text
.count()-1 );
336 if ( ch
.isLetterOrNumber() )
342 if ( (endOfItem
|| isLastLetter
) && !buffer
.isEmpty() )
344 Qt::KeyboardModifier itemModifier
= Qt::NoModifier
;
346 KeyboardTranslator::State itemFlag
= KeyboardTranslator::NoState
;
348 if ( parseAsModifier(buffer
,itemModifier
) )
350 tempModifierMask
|= itemModifier
;
353 tempModifiers
|= itemModifier
;
355 else if ( parseAsStateFlag(buffer
,itemFlag
) )
357 tempFlagMask
|= itemFlag
;
360 tempFlags
|= itemFlag
;
362 else if ( parseAsKeyCode(buffer
,itemKeyCode
) )
363 keyCode
= itemKeyCode
;
365 kDebug() << "Unable to parse key binding item:" << buffer
;
370 // check if this is a wanted / not-wanted flag and update the
371 // state ready for the next item
374 else if ( ch
== '-' )
378 modifiers
= tempModifiers
;
379 modifierMask
= tempModifierMask
;
381 flagMask
= tempFlagMask
;
386 bool KeyboardTranslatorReader::parseAsModifier(const QString
& item
, Qt::KeyboardModifier
& modifier
)
388 if ( item
== "shift" )
389 modifier
= Qt::ShiftModifier
;
390 else if ( item
== "ctrl" || item
== "control" )
391 modifier
= Qt::ControlModifier
;
392 else if ( item
== "alt" )
393 modifier
= Qt::AltModifier
;
394 else if ( item
== "meta" )
395 modifier
= Qt::MetaModifier
;
396 else if ( item
== "keypad" )
397 modifier
= Qt::KeypadModifier
;
403 bool KeyboardTranslatorReader::parseAsStateFlag(const QString
& item
, KeyboardTranslator::State
& flag
)
405 if ( item
== "appcukeys" )
406 flag
= KeyboardTranslator::CursorKeysState
;
407 else if ( item
== "ansi" )
408 flag
= KeyboardTranslator::AnsiState
;
409 else if ( item
== "newline" )
410 flag
= KeyboardTranslator::NewLineState
;
411 else if ( item
== "appscreen" )
412 flag
= KeyboardTranslator::AlternateScreenState
;
413 else if ( item
== "anymod" )
414 flag
= KeyboardTranslator::AnyModifierState
;
420 bool KeyboardTranslatorReader::parseAsKeyCode(const QString
& item
, int& keyCode
)
422 QKeySequence sequence
= QKeySequence::fromString(item
);
423 if ( !sequence
.isEmpty() )
425 keyCode
= sequence
[0];
427 if ( sequence
.count() > 1 )
429 kDebug() << "Unhandled key codes in sequence: " << item
;
432 // additional cases implemented for backwards compatibility with KDE 3
433 else if ( item
== "prior" )
434 keyCode
= Qt::Key_PageUp
;
435 else if ( item
== "next" )
436 keyCode
= Qt::Key_PageDown
;
443 QString
KeyboardTranslatorReader::description() const
447 bool KeyboardTranslatorReader::hasNextEntry()
451 KeyboardTranslator::Entry
KeyboardTranslatorReader::createEntry( const QString
& condition
,
452 const QString
& result
)
454 QString
entryString("keyboard \"temporary\"\nkey ");
455 entryString
.append(condition
);
456 entryString
.append(" : ");
458 // if 'result' is the name of a command then the entry result will be that command,
459 // otherwise the result will be treated as a string to echo when the key sequence
460 // specified by 'condition' is pressed
461 KeyboardTranslator::Command command
;
462 if (parseAsCommand(result
,command
))
463 entryString
.append(result
);
465 entryString
.append('\"' + result
+ '\"');
467 QByteArray array
= entryString
.toUtf8();
469 KeyboardTranslator::Entry entry
;
471 QBuffer
buffer(&array
);
472 buffer
.open(QIODevice::ReadOnly
);
473 KeyboardTranslatorReader
reader(&buffer
);
475 if ( reader
.hasNextEntry() )
476 entry
= reader
.nextEntry();
481 KeyboardTranslator::Entry
KeyboardTranslatorReader::nextEntry()
483 Q_ASSERT( _hasNext
);
486 KeyboardTranslator::Entry entry
= _nextEntry
;
492 bool KeyboardTranslatorReader::parseError()
496 QList
<KeyboardTranslatorReader::Token
> KeyboardTranslatorReader::tokenize(const QString
& line
)
498 QString text
= line
.simplified();
500 // comment line: # comment
501 static QRegExp
comment("\\#.*");
502 // title line: keyboard "title"
503 static QRegExp
title("keyboard\\s+\"(.*)\"");
504 // key line: key KeySequence : "output"
505 // key line: key KeySequence : command
506 static QRegExp
key("key\\s+([\\w\\+\\s\\-]+)\\s*:\\s*(\"(.*)\"|\\w+)");
510 if ( text
.isEmpty() || comment
.exactMatch(text
) )
515 if ( title
.exactMatch(text
) )
517 Token titleToken
= { Token::TitleKeyword
, QString() };
518 Token textToken
= { Token::TitleText
, title
.capturedTexts()[1] };
520 list
<< titleToken
<< textToken
;
522 else if ( key
.exactMatch(text
) )
524 Token keyToken
= { Token::KeyKeyword
, QString() };
525 Token sequenceToken
= { Token::KeySequence
, key
.capturedTexts()[1].remove(' ') };
527 list
<< keyToken
<< sequenceToken
;
529 if ( key
.capturedTexts()[3].isEmpty() )
531 // capturedTexts()[2] is a command
532 Token commandToken
= { Token::Command
, key
.capturedTexts()[2] };
533 list
<< commandToken
;
537 // capturedTexts()[3] is the output string
538 Token outputToken
= { Token::OutputText
, key
.capturedTexts()[3] };
544 kWarning() << "Line in keyboard translator file could not be understood:" << text
;
550 QList
<QString
> KeyboardTranslatorManager::allTranslators()
552 if ( !_haveLoadedAll
)
557 return _translators
.keys();
560 KeyboardTranslator::Entry::Entry()
562 , _modifiers(Qt::NoModifier
)
563 , _modifierMask(Qt::NoModifier
)
565 , _stateMask(NoState
)
566 , _command(NoCommand
)
570 bool KeyboardTranslator::Entry::operator==(const Entry
& rhs
) const
572 return _keyCode
== rhs
._keyCode
&&
573 _modifiers
== rhs
._modifiers
&&
574 _modifierMask
== rhs
._modifierMask
&&
575 _state
== rhs
._state
&&
576 _stateMask
== rhs
._stateMask
&&
577 _command
== rhs
._command
&&
581 bool KeyboardTranslator::Entry::matches(int keyCode
,
582 Qt::KeyboardModifiers modifiers
,
585 if ( _keyCode
!= keyCode
)
588 if ( (modifiers
& _modifierMask
) != (_modifiers
& _modifierMask
) )
591 // if modifiers is non-zero, the 'any modifier' state is implicit
592 if ( modifiers
!= 0 )
593 state
|= AnyModifierState
;
595 if ( (state
& _stateMask
) != (_state
& _stateMask
) )
598 // special handling for the 'Any Modifier' state, which checks for the presence of
599 // any or no modifiers. In this context, the 'keypad' modifier does not count.
600 bool anyModifiersSet
= modifiers
!= 0 && modifiers
!= Qt::KeypadModifier
;
601 if ( _stateMask
& KeyboardTranslator::AnyModifierState
)
603 // test fails if any modifier is required but none are set
604 if ( (_state
& KeyboardTranslator::AnyModifierState
) && !anyModifiersSet
)
607 // test fails if no modifier is allowed but one or more are set
608 if ( !(_state
& KeyboardTranslator::AnyModifierState
) && anyModifiersSet
)
614 QByteArray
KeyboardTranslator::Entry::escapedText(bool expandWildCards
,Qt::KeyboardModifiers modifiers
) const
616 QByteArray
result(text(expandWildCards
,modifiers
));
618 for ( int i
= 0 ; i
< result
.count() ; i
++ )
621 char replacement
= 0;
625 case 27 : replacement
= 'E'; break;
626 case 8 : replacement
= 'b'; break;
627 case 12 : replacement
= 'f'; break;
628 case 9 : replacement
= 't'; break;
629 case 13 : replacement
= 'r'; break;
630 case 10 : replacement
= 'n'; break;
632 // any character which is not printable is replaced by an equivalent
633 // \xhh escape sequence (where 'hh' are the corresponding hex digits)
634 if ( !QChar(ch
).isPrint() )
638 if ( replacement
== 'x' )
640 result
.replace(i
,1,"\\x"+QByteArray(1,ch
).toHex());
641 } else if ( replacement
!= 0 )
644 result
.insert(i
,'\\');
645 result
.insert(i
+1,replacement
);
651 QByteArray
KeyboardTranslator::Entry::unescape(const QByteArray
& input
) const
653 QByteArray
result(input
);
655 for ( int i
= 0 ; i
< result
.count()-1 ; i
++ )
658 QByteRef ch
= result
[i
];
661 char replacement
[2] = {0,0};
662 int charsToRemove
= 2;
663 bool escapedChar
= true;
665 switch ( result
[i
+1] )
667 case 'E' : replacement
[0] = 27; break;
668 case 'b' : replacement
[0] = 8 ; break;
669 case 'f' : replacement
[0] = 12; break;
670 case 't' : replacement
[0] = 9 ; break;
671 case 'r' : replacement
[0] = 13; break;
672 case 'n' : replacement
[0] = 10; break;
675 // format is \xh or \xhh where 'h' is a hexadecimal
676 // digit from 0-9 or A-F which should be replaced
677 // with the corresponding character value
678 char hexDigits
[3] = {0};
680 if ( (i
< result
.count()-2) && isxdigit(result
[i
+2]) )
681 hexDigits
[0] = result
[i
+2];
682 if ( (i
< result
.count()-3) && isxdigit(result
[i
+3]) )
683 hexDigits
[1] = result
[i
+3];
686 sscanf(hexDigits
,"%x",&charValue
);
688 replacement
[0] = (char)charValue
;
690 charsToRemove
= 2 + strlen(hexDigits
);
698 result
.replace(i
,charsToRemove
,replacement
);
705 void KeyboardTranslator::Entry::insertModifier( QString
& item
, int modifier
) const
707 if ( !(modifier
& _modifierMask
) )
710 if ( modifier
& _modifiers
)
715 if ( modifier
== Qt::ShiftModifier
)
717 else if ( modifier
== Qt::ControlModifier
)
719 else if ( modifier
== Qt::AltModifier
)
721 else if ( modifier
== Qt::MetaModifier
)
723 else if ( modifier
== Qt::KeypadModifier
)
726 void KeyboardTranslator::Entry::insertState( QString
& item
, int state
) const
728 if ( !(state
& _stateMask
) )
731 if ( state
& _state
)
736 if ( state
== KeyboardTranslator::AlternateScreenState
)
738 else if ( state
== KeyboardTranslator::NewLineState
)
740 else if ( state
== KeyboardTranslator::AnsiState
)
742 else if ( state
== KeyboardTranslator::CursorKeysState
)
744 else if ( state
== KeyboardTranslator::AnyModifierState
)
747 QString
KeyboardTranslator::Entry::resultToString(bool expandWildCards
,Qt::KeyboardModifiers modifiers
) const
749 if ( !_text
.isEmpty() )
750 return escapedText(expandWildCards
,modifiers
);
751 else if ( _command
== EraseCommand
)
753 else if ( _command
== ScrollPageUpCommand
)
754 return "ScrollPageUp";
755 else if ( _command
== ScrollPageDownCommand
)
756 return "ScrollPageDown";
757 else if ( _command
== ScrollLineUpCommand
)
758 return "ScrollLineUp";
759 else if ( _command
== ScrollLineDownCommand
)
760 return "ScrollLineDown";
761 else if ( _command
== ScrollLockCommand
)
766 QString
KeyboardTranslator::Entry::conditionToString() const
768 QString result
= QKeySequence(_keyCode
).toString();
771 insertModifier( result
, Qt::ShiftModifier
);
772 insertModifier( result
, Qt::ControlModifier
);
773 insertModifier( result
, Qt::AltModifier
);
774 insertModifier( result
, Qt::MetaModifier
);
777 insertState( result
, KeyboardTranslator::AlternateScreenState
);
778 insertState( result
, KeyboardTranslator::NewLineState
);
779 insertState( result
, KeyboardTranslator::AnsiState
);
780 insertState( result
, KeyboardTranslator::CursorKeysState
);
781 insertState( result
, KeyboardTranslator::AnyModifierState
);
786 KeyboardTranslator::KeyboardTranslator(const QString
& name
)
791 void KeyboardTranslator::setDescription(const QString
& description
)
793 _description
= description
;
795 QString
KeyboardTranslator::description() const
799 void KeyboardTranslator::setName(const QString
& name
)
803 QString
KeyboardTranslator::name() const
808 QList
<KeyboardTranslator::Entry
> KeyboardTranslator::entries() const
810 return _entries
.values();
813 void KeyboardTranslator::addEntry(const Entry
& entry
)
815 const int keyCode
= entry
.keyCode();
816 _entries
.insert(keyCode
,entry
);
818 void KeyboardTranslator::replaceEntry(const Entry
& existing
, const Entry
& replacement
)
820 if ( !existing
.isNull() )
821 _entries
.remove(existing
.keyCode(),existing
);
822 _entries
.insert(replacement
.keyCode(),replacement
);
824 void KeyboardTranslator::removeEntry(const Entry
& entry
)
826 _entries
.remove(entry
.keyCode(),entry
);
828 KeyboardTranslator::Entry
KeyboardTranslator::findEntry(int keyCode
, Qt::KeyboardModifiers modifiers
, States state
) const
830 if ( _entries
.contains(keyCode
) )
832 QList
<Entry
> entriesForKey
= _entries
.values(keyCode
);
834 QListIterator
<Entry
> iter(entriesForKey
);
836 while (iter
.hasNext())
838 const Entry
& next
= iter
.next();
839 if ( next
.matches(keyCode
,modifiers
,state
) )
843 return Entry(); // entry not found
850 void KeyboardTranslatorManager::addTranslator(KeyboardTranslator
* translator
)
852 _translators
.insert(translator
->name(),translator
);
854 if ( !saveTranslator(translator
) )
855 kWarning() << "Unable to save translator" << translator
->name()
858 bool KeyboardTranslatorManager::deleteTranslator(const QString
& name
)
860 Q_ASSERT( _translators
.contains(name
) );
863 QString path
= findTranslatorPath(name
);
864 if ( QFile::remove(path
) )
866 _translators
.remove(name
);
871 kWarning() << "Failed to remove translator - " << path
;
875 K_GLOBAL_STATIC( KeyboardTranslatorManager
, theKeyboardTranslatorManager
)
876 KeyboardTranslatorManager
* KeyboardTranslatorManager::instance()
878 return theKeyboardTranslatorManager
;