Merge pull request #8161 from jepler/usb_host_keyboard
usb host: add keyboard map control in usb workflow
This commit is contained in:
commit
2a83657b6f
@ -30,11 +30,13 @@
|
||||
|
||||
#include "shared-bindings/usb/__init__.h"
|
||||
#include "shared-bindings/usb/core/__init__.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
//| """PyUSB-compatible USB host API
|
||||
//|
|
||||
//| The `usb` is a subset of PyUSB that allows you to communicate to USB devices.
|
||||
//| """
|
||||
//|
|
||||
|
||||
STATIC mp_rom_map_elem_t usb_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb) },
|
||||
|
@ -30,16 +30,49 @@
|
||||
|
||||
#include "shared-bindings/usb_host/__init__.h"
|
||||
#include "shared-bindings/usb_host/Port.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
//| """USB Host
|
||||
//|
|
||||
//| The `usb_host` module allows you to manage USB host ports. To communicate
|
||||
//| with devices use the `usb` module that is a subset of PyUSB's API.
|
||||
//| """
|
||||
//|
|
||||
//| def set_user_keymap(keymap: ReadableBuffer, /) -> None:
|
||||
//| """Set the keymap used by a USB HID keyboard in kernel mode
|
||||
//|
|
||||
//| The keymap consists of 256 or 384 1-byte entries that map from USB keycodes
|
||||
//| to ASCII codes. The first 128 entries are for unmodified keys,
|
||||
//| the next 128 entries are for shifted keys,and the next optional 128 entries are
|
||||
//| for altgr-modified keys.
|
||||
//|
|
||||
//| The values must all be ASCII (32 through 126 inclusive); other values are not valid.
|
||||
//|
|
||||
//| The values at index 0, 128, and 256 (if the keymap has 384 entries) must be
|
||||
//| 0; other values are reserved for future expansion to indicate alternate
|
||||
//| keymap formats.
|
||||
//|
|
||||
//| At other indices, the value 0 is used to indicate that the normal
|
||||
//| definition is still used. For instance, the entry for HID_KEY_ARROW_UP
|
||||
//| (0x52) is usually 0 so that the default behavior of sending an escape code
|
||||
//| is preserved.
|
||||
//|
|
||||
//| This function is a CircuitPython extension not present in PyUSB
|
||||
//| """
|
||||
//|
|
||||
STATIC mp_obj_t usb_set_user_keymap(mp_obj_t buf_in) {
|
||||
mp_buffer_info_t bufinfo;
|
||||
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
|
||||
usb_keymap_set(bufinfo.buf, bufinfo.len);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(usb_set_user_keymap_obj, usb_set_user_keymap);
|
||||
|
||||
STATIC mp_map_elem_t usb_host_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb_host) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_Port), MP_OBJ_FROM_PTR(&usb_host_port_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_user_keymap), MP_OBJ_FROM_PTR(&usb_set_user_keymap_obj) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(usb_host_module_globals, usb_host_module_globals_table);
|
||||
|
@ -26,60 +26,49 @@
|
||||
|
||||
#include "shared-module/usb/utf16le.h"
|
||||
|
||||
STATIC void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) {
|
||||
// TODO: Check for runover.
|
||||
(void)utf8_len;
|
||||
typedef struct {
|
||||
const uint16_t *buf;
|
||||
size_t len;
|
||||
} utf16_str;
|
||||
|
||||
for (size_t i = 0; i < utf16_len; i++) {
|
||||
uint16_t chr = utf16[i];
|
||||
if (chr < 0x80) {
|
||||
*utf8++ = chr & 0xff;
|
||||
} else if (chr < 0x800) {
|
||||
*utf8++ = (uint8_t)(0xC0 | (chr >> 6 & 0x1F));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 0 & 0x3F));
|
||||
} else if (chr < 0x10000) {
|
||||
// TODO: Verify surrogate.
|
||||
*utf8++ = (uint8_t)(0xE0 | (chr >> 12 & 0x0F));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 6 & 0x3F));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 0 & 0x3F));
|
||||
} else {
|
||||
// TODO: Handle UTF-16 code points that take two entries.
|
||||
uint32_t hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */
|
||||
chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */
|
||||
chr = (hc | chr) + 0x10000;
|
||||
*utf8++ = (uint8_t)(0xF0 | (chr >> 18 & 0x07));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 12 & 0x3F));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 6 & 0x3F));
|
||||
*utf8++ = (uint8_t)(0x80 | (chr >> 0 & 0x3F));
|
||||
}
|
||||
STATIC uint32_t utf16str_peek_unit(utf16_str *utf) {
|
||||
if (!utf->len) {
|
||||
return 0;
|
||||
}
|
||||
return *utf->buf;
|
||||
}
|
||||
|
||||
// Count how many bytes a utf-16-le encoded string will take in utf-8.
|
||||
STATIC mp_int_t _count_utf8_bytes(const uint16_t *buf, size_t len) {
|
||||
size_t total_bytes = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint16_t chr = buf[i];
|
||||
if (chr < 0x80) {
|
||||
total_bytes += 1;
|
||||
} else if (chr < 0x800) {
|
||||
total_bytes += 2;
|
||||
} else if (chr < 0x10000) {
|
||||
total_bytes += 3;
|
||||
} else {
|
||||
total_bytes += 4;
|
||||
STATIC uint32_t utf16str_next_unit(utf16_str *utf) {
|
||||
uint32_t result = utf16str_peek_unit(utf);
|
||||
if (utf->len) {
|
||||
utf->len--;
|
||||
utf->buf++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
STATIC uint32_t utf16str_next_codepoint(utf16_str *utf) {
|
||||
uint32_t unichr = utf16str_next_unit(utf);
|
||||
if (unichr >= 0xd800 && unichr < 0xdc00) {
|
||||
uint32_t low_surrogate = utf16str_peek_unit(utf);
|
||||
if (low_surrogate >= 0xdc00 && low_surrogate < 0xe000) {
|
||||
(void)utf16str_next_unit(utf);
|
||||
unichr = (unichr - 0xd800) * 0x400 + low_surrogate - 0xdc00 + 0x10000;
|
||||
}
|
||||
}
|
||||
return total_bytes;
|
||||
return unichr;
|
||||
}
|
||||
|
||||
STATIC void _convert_utf16le_to_utf8(vstr_t *vstr, utf16_str *utf) {
|
||||
while (utf->len) {
|
||||
vstr_add_char(vstr, utf16str_next_codepoint(utf));
|
||||
}
|
||||
}
|
||||
|
||||
mp_obj_t utf16le_to_string(const uint16_t *buf, size_t utf16_len) {
|
||||
size_t size = _count_utf8_bytes(buf, utf16_len);
|
||||
// will grow if necessary, but will never grow for an all-ASCII descriptor
|
||||
vstr_t vstr;
|
||||
vstr_init_len(&vstr, size + 1);
|
||||
byte *p = (byte *)vstr.buf;
|
||||
// Null terminate.
|
||||
p[size] = '\0';
|
||||
_convert_utf16le_to_utf8(buf, utf16_len, p, size);
|
||||
vstr_init(&vstr, utf16_len);
|
||||
utf16_str utf = {buf, utf16_len};
|
||||
_convert_utf16le_to_utf8(&vstr, &utf);
|
||||
return mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
|
||||
}
|
||||
|
@ -31,50 +31,73 @@
|
||||
#include "py/runtime.h"
|
||||
#include "shared/runtime/interrupt_char.h"
|
||||
#include "supervisor/usb.h"
|
||||
#include "supervisor/background_callback.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#ifndef DEBUG
|
||||
#define DEBUG (0)
|
||||
#endif
|
||||
|
||||
// Buffer the incoming serial data in the background so that we can look for the
|
||||
// interrupt character.
|
||||
STATIC ringbuf_t _incoming_ringbuf;
|
||||
STATIC uint8_t _buf[16];
|
||||
|
||||
uint8_t _dev_addr;
|
||||
uint8_t _interface;
|
||||
STATIC uint8_t _dev_addr;
|
||||
STATIC uint8_t _interface;
|
||||
|
||||
#define FLAG_ALPHABETIC (1)
|
||||
#define FLAG_SHIFT (2)
|
||||
#define FLAG_NUMLOCK (4)
|
||||
#define FLAG_CTRL (8)
|
||||
#define FLAG_LUT (16)
|
||||
#define FLAG_SHIFT (1)
|
||||
#define FLAG_NUMLOCK (2)
|
||||
#define FLAG_CTRL (4)
|
||||
#define FLAG_STRING (8)
|
||||
|
||||
const char *const lut[] = {
|
||||
"!@#$%^&*()", /* 0 - shifted numeric keys */
|
||||
"\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */
|
||||
"\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */
|
||||
"\12\13\10\22", /* 3 - arrow keys RLDU */
|
||||
"/*-+\n1234567890.", /* 4 - keypad w/numlock */
|
||||
"/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */
|
||||
};
|
||||
STATIC uint8_t user_keymap[384];
|
||||
STATIC size_t user_keymap_len = 0;
|
||||
|
||||
void usb_keymap_set(const uint8_t *buf, size_t len) {
|
||||
user_keymap_len = len = MIN(len, sizeof(user_keymap));
|
||||
memcpy(user_keymap, buf, len);
|
||||
memset(user_keymap + len, 0, sizeof(user_keymap) - len);
|
||||
}
|
||||
|
||||
struct keycode_mapper {
|
||||
uint8_t first, last, code, flags;
|
||||
} keycode_to_ascii[] = {
|
||||
{ HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, },
|
||||
const char *data;
|
||||
};
|
||||
|
||||
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, },
|
||||
#define SEP "\0" // separator in FLAG_STRING sequences
|
||||
#define NOTHING "" // in FLAG_STRING sequences
|
||||
#define CURSOR_UP "\e[A"
|
||||
#define CURSOR_DOWN "\e[B"
|
||||
#define CURSOR_LEFT "\e[D"
|
||||
#define CURSOR_RIGHT "\e[C"
|
||||
#define CURSOR_PGUP "\e[5~"
|
||||
#define CURSOR_PGDN "\e[6~"
|
||||
#define CURSOR_HOME "\e[H"
|
||||
#define CURSOR_END "\e[F"
|
||||
#define CURSOR_INS "\e[2~"
|
||||
#define CURSOR_DEL "\e[3~"
|
||||
|
||||
STATIC struct keycode_mapper keycode_to_ascii[] = {
|
||||
{ HID_KEY_A, HID_KEY_Z, 'a', 0, NULL},
|
||||
|
||||
{ HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT, "!@#$%^&*()" },
|
||||
{ HID_KEY_1, HID_KEY_9, '1', 0, },
|
||||
{ HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, },
|
||||
{ HID_KEY_0, HID_KEY_0, '0', 0, },
|
||||
|
||||
{ HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL },
|
||||
{ HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, },
|
||||
{ HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, },
|
||||
{ HID_KEY_ENTER, HID_KEY_SLASH, 0, FLAG_SHIFT, "\n\x1b\177\t _+{}|~:\"~<>?" },
|
||||
{ HID_KEY_ENTER, HID_KEY_SLASH, 0, 0, "\r\x1b\10\t -=[]\\#;'`,./" },
|
||||
|
||||
{ HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd
|
||||
|
||||
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT },
|
||||
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 0, FLAG_NUMLOCK | FLAG_STRING,
|
||||
"/\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},
|
||||
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 0, 0, "/*-+\n1234567890." },
|
||||
|
||||
{ HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 0, FLAG_STRING, CURSOR_RIGHT SEP CURSOR_LEFT SEP CURSOR_DOWN SEP CURSOR_UP },
|
||||
|
||||
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT },
|
||||
{ HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT },
|
||||
};
|
||||
|
||||
STATIC bool report_contains(const hid_keyboard_report_t *report, uint8_t key) {
|
||||
@ -86,30 +109,81 @@ STATIC bool report_contains(const hid_keyboard_report_t *report, uint8_t key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int old_ascii = -1;
|
||||
uint32_t repeat_timeout;
|
||||
STATIC const char *old_buf = NULL;
|
||||
STATIC size_t buf_size = 0;
|
||||
// this matches Linux default of 500ms to first repeat, 1/20s thereafter
|
||||
const uint32_t default_repeat_time = 50;
|
||||
const uint32_t initial_repeat_time = 500;
|
||||
enum { initial_repeat_time = 500, default_repeat_time = 50 };
|
||||
STATIC uint64_t repeat_deadline;
|
||||
STATIC void repeat_f(void *unused);
|
||||
background_callback_t repeat_cb = {repeat_f, NULL, NULL, NULL};
|
||||
|
||||
STATIC void send_ascii(uint8_t code, uint32_t repeat_time) {
|
||||
old_ascii = code;
|
||||
STATIC void set_repeat_deadline(uint64_t new_deadline) {
|
||||
repeat_deadline = new_deadline;
|
||||
background_callback_add_core(&repeat_cb);
|
||||
}
|
||||
|
||||
STATIC void send_bufn_core(const char *buf, size_t n) {
|
||||
old_buf = buf;
|
||||
buf_size = n;
|
||||
// repeat_timeout = millis() + repeat_time;
|
||||
if (code == mp_interrupt_char) {
|
||||
mp_sched_keyboard_interrupt();
|
||||
return;
|
||||
for (; n--; buf++) {
|
||||
int code = *buf;
|
||||
if (code == mp_interrupt_char) {
|
||||
mp_sched_keyboard_interrupt();
|
||||
return;
|
||||
}
|
||||
if (ringbuf_num_empty(&_incoming_ringbuf) == 0) {
|
||||
// Drop on the floor
|
||||
return;
|
||||
}
|
||||
ringbuf_put(&_incoming_ringbuf, code);
|
||||
}
|
||||
if (ringbuf_num_empty(&_incoming_ringbuf) == 0) {
|
||||
// Drop on the floor
|
||||
return;
|
||||
}
|
||||
|
||||
STATIC void send_bufn(const char *buf, size_t n) {
|
||||
send_bufn_core(buf, n);
|
||||
set_repeat_deadline(supervisor_ticks_ms64() + initial_repeat_time);
|
||||
}
|
||||
|
||||
STATIC void send_bufz(const char *buf) {
|
||||
send_bufn(buf, strlen(buf));
|
||||
}
|
||||
|
||||
STATIC void send_byte(uint8_t code) {
|
||||
static char buf[1];
|
||||
buf[0] = code;
|
||||
send_bufn(buf, 1);
|
||||
}
|
||||
|
||||
STATIC void send_repeat(void) {
|
||||
if (old_buf) {
|
||||
uint64_t now = supervisor_ticks_ms64();
|
||||
if (now >= repeat_deadline) {
|
||||
send_bufn_core(old_buf, buf_size);
|
||||
set_repeat_deadline(now + default_repeat_time);
|
||||
} else {
|
||||
background_callback_add_core(&repeat_cb);
|
||||
}
|
||||
}
|
||||
ringbuf_put(&_incoming_ringbuf, code);
|
||||
}
|
||||
|
||||
STATIC void repeat_f(void *unused) {
|
||||
send_repeat();
|
||||
}
|
||||
|
||||
hid_keyboard_report_t old_report;
|
||||
|
||||
STATIC const char *skip_nuls(const char *buf, size_t n) {
|
||||
while (n--) {
|
||||
buf += strlen(buf) + 1;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t *report) {
|
||||
bool alt = report->modifier & 0x44;
|
||||
bool has_altgr = (user_keymap_len > 256);
|
||||
bool altgr = has_altgr && report->modifier & 0x40;
|
||||
bool alt = has_altgr ? report->modifier & 0x4 : report->modifier & 0x44;
|
||||
bool shift = report->modifier & 0x22;
|
||||
bool ctrl = report->modifier & 0x11;
|
||||
bool caps = old_report.reserved & 1;
|
||||
@ -121,8 +195,8 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
|
||||
return;
|
||||
}
|
||||
|
||||
// something was pressed or release, so cancel any key repeat
|
||||
old_ascii = -1;
|
||||
// something was pressed or released, so cancel any key repeat
|
||||
old_buf = NULL;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8_t keycode = report->keycode[i];
|
||||
@ -139,6 +213,22 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
|
||||
} else if (keycode == HID_KEY_CAPS_LOCK) {
|
||||
caps = !caps;
|
||||
} else {
|
||||
size_t idx = keycode + (altgr ? 256 : shift ? 128 : 0);
|
||||
uint8_t ascii = user_keymap[idx];
|
||||
#if DEBUG
|
||||
mp_printf(&mp_plat_print, "lookup HID keycode %d mod %x at idx %d -> ascii %d (%c)\n",
|
||||
keycode, report->modifier, idx, ascii, ascii >= 32 && ascii <= 126 ? ascii : '.');
|
||||
#endif
|
||||
if (ascii != 0) {
|
||||
if (ctrl) {
|
||||
ascii &= 0x1f;
|
||||
} else if (ascii >= 'a' && ascii <= 'z' && caps) {
|
||||
ascii ^= ('a' ^ 'A');
|
||||
}
|
||||
send_byte(ascii);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < MP_ARRAY_SIZE(keycode_to_ascii); j++) {
|
||||
struct keycode_mapper *mapper = &keycode_to_ascii[j];
|
||||
if (!(keycode >= mapper->first && keycode <= mapper->last)) {
|
||||
@ -153,15 +243,17 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
|
||||
if (mapper->flags & FLAG_CTRL && !ctrl) {
|
||||
continue;
|
||||
}
|
||||
if (mapper->flags & FLAG_LUT) {
|
||||
code = lut[mapper->code][keycode - mapper->first];
|
||||
if (mapper->flags & FLAG_STRING) {
|
||||
const char *msg = skip_nuls(mapper->data, keycode - mapper->first);
|
||||
send_bufz(msg);
|
||||
break;
|
||||
} else if (mapper->data) {
|
||||
code = mapper->data[keycode - mapper->first];
|
||||
} else {
|
||||
code = keycode - mapper->first + mapper->code;
|
||||
}
|
||||
if (mapper->flags & FLAG_ALPHABETIC) {
|
||||
if (shift ^ caps) {
|
||||
code ^= ('a' ^ 'A');
|
||||
}
|
||||
if (code >= 'a' && code <= 'z' && (shift ^ caps)) {
|
||||
code ^= ('a' ^ 'A');
|
||||
}
|
||||
if (ctrl) {
|
||||
code &= 0x1f;
|
||||
@ -169,7 +261,7 @@ STATIC void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard
|
||||
if (alt) {
|
||||
code ^= 0x80;
|
||||
}
|
||||
send_ascii(code, initial_repeat_time);
|
||||
send_byte(code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ char usb_keyboard_read_char(void);
|
||||
bool usb_keyboard_in_use(uint8_t dev_addr, uint8_t interface);
|
||||
void usb_keyboard_detach(uint8_t dev_addr, uint8_t interface);
|
||||
void usb_keyboard_attach(uint8_t dev_addr, uint8_t interface);
|
||||
void usb_keymap_set(const uint8_t *buf, size_t len);
|
||||
#endif
|
||||
|
||||
#endif // MICROPY_INCLUDED_SUPERVISOR_USB_H
|
||||
|
Loading…
Reference in New Issue
Block a user