Дата и время публикации :
Проблема и решение
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