Дата и время публикации :
Проблема и решение
1. Суть проблемы
В преобразовании сканкодов клавиш клавиатуры к кодам нажатия в Gtk3 кажется много таинственного и необычного, на первый взгляд. Почему? Потому что, например заявленные, как физический код клавиш, значение возврашаемое в hardware_keycode не совпадает ни с одним из трех наборов сканкодов, которые может генерить клавиатура и пречисленных найти на www.win.tue.nl. Ниже приводится листинг 1.1 получения сканкодов для Vte.Terminal
Листинг 1.1
...
162 class GVTerminal(Vte.Terminal) :
...
166 def __init__(self, *args, **keywords) :
…
169 self.connect('key-press-event', self.on_key_press_event )
…
183 def on_key_press_event(self, widget, event):
184 print("Key press on widget: ", widget )
185 print(" Modifiers : ", event.state )
186 print(" Hardware keycode: %x" % event.hardware_keycode )
187 print(" Key val : %x" % event.keyval )
188 print(" Key name : %s" % Gdk.keyval_name(event.keyval) )
189 print(" Key unicode : %x" % Gdk.keyval_to_unicode(event.keyval) )
190
191 return
...
В строке 183 которого, определена функция-обработчик нажатия клавиш по событию 'key-press-event', как показано в строке 169 того же листинга.
В результате реализации которого получим следующие волшебные числа, например для Bacspace, Escape, Return, он же Enter и Space, как показано в дампе 1.2, ниже.
Листинг 1.2
...
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 24
Key val: ff0d
Key name: Return
Key unicode: d
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 33
Key val: 5c
Key name: backslash
Key unicode: 5c
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 9
Key val: ff1b
Key name: Escape
Key unicode: 1b
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 41
Key val: 20
Key name: space
Key unicode: 20
...
Убежден, что значение event.hardware_keycode напечатанное на против Hardware keycode для четырех часто используемых клавиш может свести с ума. При этом,
- как видно из дампа keyval соответствует принятым значениям в кодировки ascii/utf-8;
- если взять клавиши Стрелка Вверх (ArrowUp), Страница Вверх(PageUp), то увидим следующее показанное в дампе ниже, со всем не клеится со строением мира, как показано в дампе 1.3
Дамп 1.3
...
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 6f
Key val: ff52
Key name: Up
Key unicode: 0
Key press on widget: <GTerminal.GVTerminal object at 0x7f09aef44d00 (GTerminal+GVTerminal at 0x2b55580)>
Modifiers: <flags 0 of type Gdk.ModifierType>
Hardware keycode: 51
Key val: ff9a
Key name: KP_Page_Up
Key unicode: 0
Из которого видно, что значение обоих клавиш в юникоде равно нулю, а физический код не совпадает с ни одним из наборов сканкодов, которые можно найти на www.win.tue.nl
2. Решение
Поэтому взглянем на проблему, вооружившись некоторыми знаниями. Первое получим название клавиатурной раскладки – обычно pc(pc105) или pc(pc104), которую можно получить следующим образом:
$ setxkbmap -print | grep geometry
xkb_geometry { include "pc(pc105)" };
Что указывает на один из набор клавиш IBM/PC, который перекочевал в современные десктопы и ноутбуки.
Далее определяем набор сканкода клавиш IBM с помощью команды showkey -s и определяем сканкод
- клавиши Esc : 0x01 0x81
- клавиши Enter : 0x1c 0x9c
- клавиши Space : 0x39 0xb9
- клавиши ArrowUp: 0xe0 0xc8
- клавиши PageUp : 0x49 0xc9
При этом, как написано на www.win.tue.nl первое значение для клавиши Esc (0x01) , второе (0x81) – отпусканию клавиши, что между обеими значениями составляет разницу 0x80 или логическую единицу в старшем разряде.
Соответственно, как следует из ранее полученных значений нажатия клавиш Gtk, приведем соотношения сканкодов нажатия клавиш утилиты showkey и физический код, полученный после наступления события нажатия клавиши "key-press-event", как показано в таблице 2.1
| Клавиша | Утилита showkey, сканкод нажатия | Gdk.event, физический код клавиши | Разница |
|---|---|---|---|
| клавиши Esc | 0x01 | 0x09 | 0x08 |
| клавиши Enter | 0x1c | 0x24 | 0x08 |
| клавиши Space | 0x39 | 0x41 | 0x08 |
| клавиши ArrowUp | 0xe0 | 0x6f | 0x08 |
| клавиши PageUp | 0x49 | 0x51 | 0x08 |
из которой видно, что физический код клавиши Gdk.event и реальный сканкод нажатия имеют разницу 0x08, о которой просто нужно помнить. Соответственно для выдачи команд можно пользоваться этими кодами, но для лучшей читаемости кода и траты времени на документирование, предлагаю использовать имена клавиш, пример которых приведен в таблицe 2.2
| Клавиша | Физический код клавиши Gdk.event | Уникальное значение клавиши Gdk.event, GDK_KEY_* | Символьное значение ascii, принимаемое терминалом xterm(-256color) |
|---|---|---|---|
| клавиша Esc | 0x09 | Escape | '\e' |
| клавиша Enter | 0x24 | Enter | '\n' |
| клавиша Space | 0x41 | space | '\x20' или ' ' |
| Клавиша Home | 0x6e | Home | '\e[H' |
| клавиша Arrow Up | 0x6f | Up | '\e[A' |
| клавиша Arrow Down | 0x74 | Down | '\e[B' |
| клавиша Page Up | 0x51 | KP_Page_Up | '\e[5~' |
| клавиша Page Down | 0x59 | KP_Next | '\e[6~' |
| Клавиша End | 0x73 | End | '\e[F' |
| Клавиша Del | 0x77 | Delete | '\e[3~' |
| Клавиша Ins | 0x76 | Insert | '\e[2~' |
На основе приведенной таблице 2.2, в реализуемом коде терминала декларируем список JSON, как показано в дампе 2.3
Дамп 2.3
...
jsonKeynames = {
"Insert" : '\033[2~',
"Delete" : '\033[3~',
"KP_Next" : '\033[6~',
"KP_Page_Up" : '\033[5~',
"End" : '\033[F',
"Home" : '\033[H',
"Left" : '\033[D',
"Right" : '\033[C',
"Down" : '\033[B',
"Up" : '\033[A',
"Escape" : '\033',
"Tab" : '\t',
"BackSpace" : '\b',
"Enter" : '\n',
"space" : '\x20',
...
"question" : '?',
"at" : '@',
}
...
def __init__(self, *args, **keywords) :
Vte.Terminal.__init__(self, *args, **keywords)
self.set_input_enabled(True)
self.connect('key-press-event', self.on_key_press_event )
...
def on_key_press_event(self, widget, event):
...
keyname = Gdk.keyval_name(event.keyval)
...
if keyname in self.keynames :
print(" Terminal command: %s" % self.keynames[keyname] )
...
В котором показано, что если клавиша из JSON-списка была нажата, она считается терминальной командой или транслируемым символом из ряда '!','@','#','$','%','$','%','^','&','*','(',')' и т.д.
Но, этого оказалось мало, потому что нужно еще определять к какому типу относится нажатая клавиша, а именно к :
- Алфавитно-цифровому – видимые символы;
- Управляющие клавиши, такие как Ctrl и Alt ;
- Функциональные клавиши, такие как F1, F2, F3 и F12 ;
- Клавиши навигации, такие как Home, End, Page Up, Page Down, Delete и Insert;
- Клавиши на дополнительной цифровой клавиатуре, имена которых Gtk.Gdk маркирует с префиксом "KP_", в т.ч. клавиши навигации Home, End, Page Up, Page Down, Delete и Insert.
Для этого, казалось бы можно было использовать так называемые модификаторы поглощения (consumed modifiers), которые относятся к модификаторы клавиш, маскирующим состояние события Gdk.event.state для определения нажатия комбинаций клавиш и определения используемого набора.
К таким модификаторам клавиш относятся Shift, Control, Meta, Super (клавиша со со значком Windows), Hyper, Alt, Apple и т.д. насколько хватит фантазии у разработчиков раскладок клавиатур.
При этом, для распознавания клавиш по вышеперечисленным типам нам понадобятся при нажатой:
- клавиши Ctrl — модификатор Gdk.ModifierType.CONTROL_MASK,
- клавиши Caps Lock — модификатор Gdk.ModifierType.LOCK_MASK,
- клавиши Shift — модификатор Gdk.ModifierType.SHIFT_MASK,
- клавиши Alt — модификатор Gdk.ModifierType.MOD1_MASK,
- клавиша Super — модификатор Gdk.ModifierType.MOD4_MASK,
- клавиша Super + Shift — модификатор Gdk.ModifierType.MOD5_MASK,
- и т.д.
При этом нужно учитывать, что
- модификаторы не указывают на клавиши, имеющие в системе различные функциональное назначение, как и те, что отвечают за навигацию и перемещение курсора;
- определять к каким группам эти клавиши относятся лучше производить самим, например, группировать их с использованием списка JSON, как это было сделано выше в листинге 2.3
3. Библиография
3.1 AskUbuntu.How do I change keyboard geometry from 104 to 105 keys?
3.2 Keyboard scancodes in linux
3.3 StackOverflow.Map keycode to physical location
3.4 Map table of the scancode/hardware key code
3.5 StackOverflow. Casing arrow keys in bash
3.6 Unix&Linux. Where do I find a list of terminal key codes to remap shortcuts in bash?
3.7 Microsoft.Using your keyboard