Skip to content

Commit 910fcf1

Browse files
committed
usb host keyboard: add keymap support
This is confined to pure ASCII, but does support Alt Gr. This is enough to make a French keyboard layout work: * home row is "qsdfghjklm" * numerals are all shifted * keys like {} require Alt Gr While not tested, this should also support layouts like Colemak with the necssary keymap file. Layouts can be produced from Neradoc's keyboard layout modules on the device & loaded at runtime. Code to do so is at https://gist.github.com/jepler/597c0a00e0eb014a433e03c0ae363a08 as well as a sketch of how an extended format could support dead keys & the full Unicode BMP instead of just ASCII.
1 parent 79d18c5 commit 910fcf1

File tree

3 files changed

+152
-47
lines changed

3 files changed

+152
-47
lines changed

shared-bindings/usb/__init__.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,48 @@
3030

3131
#include "shared-bindings/usb/__init__.h"
3232
#include "shared-bindings/usb/core/__init__.h"
33+
#include "supervisor/usb.h"
3334

3435
//| """PyUSB-compatible USB host API
3536
//|
3637
//| The `usb` is a subset of PyUSB that allows you to communicate to USB devices.
3738
//| """
39+
//|
40+
//| def set_user_keymap(keymap: ReadableBuffer, /) -> None:
41+
//| """Set the keymap used by a USB HID keyboard in kernel mode
42+
//|
43+
//| The keymap consists of 256 or 384 1-byte entries that map from USB keycodes
44+
//| to ASCII codes. The first 128 entries are for unmodified keys,
45+
//| the next 128 entries are for shifted keys,and the next optional 128 entries are
46+
//| for altgr-modified keys.
47+
//|
48+
//| The values must all be ASCII (32 through 126 inclusive); other values are not valid.
49+
//|
50+
//| The values at index 0, 128, and 256 (if the keymap has 384 entries) must be
51+
//| 0; other values are reserved for future expansion to indicate alternate
52+
//| keymap formats.
53+
//|
54+
//| At other indices, the value 0 is used to indicate that the normal
55+
//| definition is still used. For instance, the entry for HID_KEY_ARROW_UP
56+
//| (0x52) is usually 0 so that the default behavior of sending an escape code
57+
//| is preserved.
58+
//|
59+
//| This function is a CircuitPython extension not present in PyUSB
60+
//| """
61+
//|
62+
STATIC mp_obj_t usb_set_user_keymap(mp_obj_t buf_in) {
63+
mp_buffer_info_t bufinfo;
64+
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
65+
usb_keymap_set(bufinfo.buf, bufinfo.len);
66+
return mp_const_none;
67+
}
68+
69+
MP_DEFINE_CONST_FUN_OBJ_1(usb_set_user_keymap_obj, usb_set_user_keymap);
3870

3971
STATIC mp_rom_map_elem_t usb_module_globals_table[] = {
4072
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb) },
4173
{ MP_ROM_QSTR(MP_QSTR_core), MP_OBJ_FROM_PTR(&usb_core_module) },
74+
{ MP_ROM_QSTR(MP_QSTR_set_user_keymap), MP_OBJ_FROM_PTR(&usb_set_user_keymap_obj) },
4275
};
4376

4477
STATIC MP_DEFINE_CONST_DICT(usb_module_globals, usb_module_globals_table);

supervisor/shared/usb/host_keyboard.c

Lines changed: 118 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,49 +32,70 @@
3232
#include "shared/runtime/interrupt_char.h"
3333
#include "supervisor/usb.h"
3434

35+
#ifndef DEBUG
36+
#define DEBUG (0)
37+
#endif
38+
3539
// Buffer the incoming serial data in the background so that we can look for the
3640
// interrupt character.
3741
STATIC ringbuf_t _incoming_ringbuf;
3842
STATIC uint8_t _buf[16];
3943

40-
uint8_t _dev_addr;
41-
uint8_t _interface;
42-
43-
#define FLAG_ALPHABETIC (1)
44-
#define FLAG_SHIFT (2)
45-
#define FLAG_NUMLOCK (4)
46-
#define FLAG_CTRL (8)
47-
#define FLAG_LUT (16)
48-
49-
const char *const lut[] = {
50-
"!@#$%^&*()", /* 0 - shifted numeric keys */
51-
"\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */
52-
"\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */
53-
"\12\13\10\22", /* 3 - arrow keys RLDU */
54-
"/*-+\n1234567890.", /* 4 - keypad w/numlock */
55-
"/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */
56-
};
44+
STATIC uint8_t _dev_addr;
45+
STATIC uint8_t _interface;
46+
47+
#define FLAG_SHIFT (1)
48+
#define FLAG_NUMLOCK (2)
49+
#define FLAG_CTRL (4)
50+
#define FLAG_STRING (8)
51+
52+
STATIC uint8_t user_keymap[384];
53+
STATIC size_t user_keymap_len = 0;
54+
55+
void usb_keymap_set(const uint8_t *buf, size_t len) {
56+
user_keymap_len = len = MIN(len, sizeof(user_keymap));
57+
memcpy(user_keymap, buf, len);
58+
memset(user_keymap + len, 0, sizeof(user_keymap) - len);
59+
}
5760

5861
struct keycode_mapper {
5962
uint8_t first, last, code, flags;
60-
} keycode_to_ascii[] = {
61-
{ HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, },
63+
const char *data;
64+
};
6265

63-
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, },
66+
#define SEP "\0" // separator in FLAG_STRING sequences
67+
#define NOTHING "" // in FLAG_STRING sequences
68+
#define CURSOR_UP "\e[A"
69+
#define CURSOR_DOWN "\e[B"
70+
#define CURSOR_LEFT "\e[D"
71+
#define CURSOR_RIGHT "\e[C"
72+
#define CURSOR_PGUP "\e[5~"
73+
#define CURSOR_PGDN "\e[6~"
74+
#define CURSOR_HOME "\e[H"
75+
#define CURSOR_END "\e[F"
76+
#define CURSOR_INS "\e[2~"
77+
#define CURSOR_DEL "\e[3~"
78+
79+
STATIC struct keycode_mapper keycode_to_ascii[] = {
80+
{ HID_KEY_A, HID_KEY_Z, 'a', 0, NULL},
81+
82+
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT, "!@#$%^&*()" },
6483
{ HID_KEY_1, HID_KEY_9, '1', 0, },
6584
{ HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, },
6685
{ HID_KEY_0, HID_KEY_0, '0', 0, },
6786

6887
{ HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL },
69-
{ HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, },
70-
{ HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, },
88+
{ HID_KEY_ENTER, HID_KEY_SLASH, 0, FLAG_SHIFT, "\n\x1b\177\t _+{}|~:\"~<>?" },
89+
{ HID_KEY_ENTER, HID_KEY_SLASH, 0, 0, "\r\x1b\10\t -=[]\\#;'`,./" },
7190

7291
{ HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd
7392

74-
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT },
93+
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 0, FLAG_NUMLOCK | FLAG_STRING,
94+
"/\0" "*\0" "-\0" "+\0" "\n\0" CURSOR_END SEP CURSOR_DOWN SEP CURSOR_PGDN SEP CURSOR_LEFT SEP NOTHING SEP CURSOR_RIGHT SEP CURSOR_HOME SEP CURSOR_UP SEP CURSOR_PGDN SEP CURSOR_INS SEP CURSOR_DEL},
95+
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 0, 0, "/*-+\n1234567890." },
96+
97+
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 0, FLAG_STRING, CURSOR_RIGHT SEP CURSOR_LEFT SEP CURSOR_DOWN SEP CURSOR_UP },
7598

76-
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT },
77-
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT },
7899
};
79100

80101
STATIC bool report_contains(const hid_keyboard_report_t *report, uint8_t key) {
@@ -86,30 +107,63 @@ STATIC bool report_contains(const hid_keyboard_report_t *report, uint8_t key) {
86107
return false;
87108
}
88109

89-
int old_ascii = -1;
90-
uint32_t repeat_timeout;
110+
STATIC const char *old_buf = NULL;
111+
STATIC size_t buf_size = 0;
91112
// this matches Linux default of 500ms to first repeat, 1/20s thereafter
92-
const uint32_t default_repeat_time = 50;
93-
const uint32_t initial_repeat_time = 500;
113+
STATIC const uint32_t initial_repeat_time = 500;
94114

95-
STATIC void send_ascii(uint8_t code, uint32_t repeat_time) {
96-
old_ascii = code;
115+
STATIC void send_bufn(const char *buf, size_t n, uint32_t repeat_time) {
116+
old_buf = buf;
117+
buf_size = n;
97118
// repeat_timeout = millis() + repeat_time;
98-
if (code == mp_interrupt_char) {
99-
mp_sched_keyboard_interrupt();
100-
return;
119+
for (; n--; buf++) {
120+
int code = *buf;
121+
if (code == mp_interrupt_char) {
122+
mp_sched_keyboard_interrupt();
123+
return;
124+
}
125+
if (ringbuf_num_empty(&_incoming_ringbuf) == 0) {
126+
// Drop on the floor
127+
return;
128+
}
129+
ringbuf_put(&_incoming_ringbuf, code);
101130
}
102-
if (ringbuf_num_empty(&_incoming_ringbuf) == 0) {
103-
// Drop on the floor
104-
return;
131+
}
132+
133+
STATIC void send_bufz(const char *buf, uint32_t repeat_time) {
134+
send_bufn(buf, strlen(buf), repeat_time);
135+
}
136+
137+
STATIC void send_byte(uint8_t code, uint32_t repeat_time) {
138+
static char buf[1];
139+
buf[0] = code;
140+
send_bufn(buf, 1, repeat_time);
141+
}
142+
143+
#if 0
144+
STATIC uint32_t repeat_timeout;
145+
STATIC const uint32_t default_repeat_time = 50;
146+
// TODO: nothing actually SENDS the repetitions...
147+
STATIC void send_repeat() {
148+
if (old_buf) {
149+
send_bufn(old_buf, old_buf_size, default_repeat_time);
105150
}
106-
ringbuf_put(&_incoming_ringbuf, code);
107151
}
152+
#endif
108153

109154
hid_keyboard_report_t old_report;
110155

156+
STATIC const char *skip_nuls(const char *buf, size_t n) {
157+
while (n--) {
158+
buf += strlen(buf) + 1;
159+
}
160+
return buf;
161+
}
162+
111163
STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t *report) {
112-
bool alt = report->modifier & 0x44;
164+
bool has_altgr = (user_keymap_len > 256);
165+
bool altgr = has_altgr && report->modifier & 0x40;
166+
bool alt = has_altgr ? report->modifier & 0x4 : report->modifier & 0x44;
113167
bool shift = report->modifier & 0x22;
114168
bool ctrl = report->modifier & 0x11;
115169
bool caps = old_report.reserved & 1;
@@ -122,7 +176,7 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
122176
}
123177

124178
// something was pressed or release, so cancel any key repeat
125-
old_ascii = -1;
179+
old_buf = NULL;
126180

127181
for (int i = 0; i < 6; i++) {
128182
uint8_t keycode = report->keycode[i];
@@ -139,6 +193,22 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
139193
} else if (keycode == HID_KEY_CAPS_LOCK) {
140194
caps = !caps;
141195
} else {
196+
size_t idx = keycode + (altgr ? 256 : shift ? 128 : 0);
197+
uint8_t ascii = user_keymap[idx];
198+
#if DEBUG
199+
mp_printf(&mp_plat_print, "lookup HID keycode %d mod %x at idx %d -> ascii %d (%c)\n",
200+
keycode, report->modifier, idx, ascii, ascii >= 32 && ascii <= 126 ? ascii : '.');
201+
#endif
202+
if (ascii != 0) {
203+
if (ctrl) {
204+
ascii &= 0x1f;
205+
} else if (ascii >= 'a' && ascii <= 'z' && caps) {
206+
ascii ^= ('a' ^ 'A');
207+
}
208+
send_byte(ascii, initial_repeat_time);
209+
continue;
210+
}
211+
142212
for (size_t j = 0; j < MP_ARRAY_SIZE(keycode_to_ascii); j++) {
143213
struct keycode_mapper *mapper = &keycode_to_ascii[j];
144214
if (!(keycode >= mapper->first && keycode <= mapper->last)) {
@@ -153,23 +223,24 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
153223
if (mapper->flags & FLAG_CTRL && !ctrl) {
154224
continue;
155225
}
156-
if (mapper->flags & FLAG_LUT) {
157-
code = lut[mapper->code][keycode - mapper->first];
226+
if (mapper->flags & FLAG_STRING) {
227+
const char *msg = skip_nuls(mapper->data, keycode - mapper->first);
228+
send_bufz(msg, initial_repeat_time);
229+
} else if (mapper->data) {
230+
code = mapper->data[keycode - mapper->first];
158231
} else {
159232
code = keycode - mapper->first + mapper->code;
160233
}
161-
if (mapper->flags & FLAG_ALPHABETIC) {
162-
if (shift ^ caps) {
163-
code ^= ('a' ^ 'A');
164-
}
234+
if (code >= 'a' && code <= 'z' && (shift ^ caps)) {
235+
code ^= ('a' ^ 'A');
165236
}
166237
if (ctrl) {
167238
code &= 0x1f;
168239
}
169240
if (alt) {
170241
code ^= 0x80;
171242
}
172-
send_ascii(code, initial_repeat_time);
243+
send_byte(code, initial_repeat_time);
173244
break;
174245
}
175246
}

supervisor/usb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ char usb_keyboard_read_char(void);
104104
bool usb_keyboard_in_use(uint8_t dev_addr, uint8_t interface);
105105
void usb_keyboard_detach(uint8_t dev_addr, uint8_t interface);
106106
void usb_keyboard_attach(uint8_t dev_addr, uint8_t interface);
107+
void usb_keymap_set(const uint8_t *buf, size_t len);
107108
#endif
108109

109110
#endif // MICROPY_INCLUDED_SUPERVISOR_USB_H

0 commit comments

Comments
 (0)