diff --git a/examples/hidkbd/Kconfig b/examples/hidkbd/Kconfig index c80d4094372..8b938de37ab 100644 --- a/examples/hidkbd/Kconfig +++ b/examples/hidkbd/Kconfig @@ -36,7 +36,7 @@ config EXAMPLES_HIDKBD_DEVNAME config EXAMPLES_HIDKBD_ENCODED bool "Encode Special Keys" default y - depends on HIDKBD_ENCODED && LIBC_KBDCODEC + depends on INPUT_KEYBOARD_BYTESTREAM ---help--- Decode special key press events in the user buffer. In this case, the example coded will use the interfaces defined in diff --git a/examples/hidkbd/hidkbd_main.c b/examples/hidkbd/hidkbd_main.c index 1515d65261b..d2608213652 100644 --- a/examples/hidkbd/hidkbd_main.c +++ b/examples/hidkbd/hidkbd_main.c @@ -35,6 +35,8 @@ #include #ifdef CONFIG_EXAMPLES_HIDKBD_ENCODED +# include + # include # include #endif @@ -55,7 +57,8 @@ # define CONFIG_EXAMPLES_HIDKBD_DEVNAME "/dev/kbda" #endif -#if !defined(CONFIG_HIDKBD_ENCODED) || !defined(CONFIG_LIBC_KBDCODEC) +#if !defined(CONFIG_INPUT_KEYBOARD_BYTESTREAM) || \ + !defined(CONFIG_LIBC_KBDCODEC) # undef CONFIG_EXAMPLES_HIDKBD_ENCODED #endif diff --git a/examples/lvglterm/Kconfig b/examples/lvglterm/Kconfig index ca393e845b1..7c4a706834a 100644 --- a/examples/lvglterm/Kconfig +++ b/examples/lvglterm/Kconfig @@ -17,9 +17,7 @@ choice default EXAMPLES_LVGLTERM_INPUT_TOUCH ---help--- Select how the terminal receives keystrokes. Only one input source - is built at a time. The physical-keyboard options differ in the data - the keyboard device delivers on read(), so pick the one that matches - the hardware. + is built at a time. config EXAMPLES_LVGLTERM_INPUT_TOUCH bool "On-screen keyboard (touch)" @@ -28,37 +26,29 @@ config EXAMPLES_LVGLTERM_INPUT_TOUCH command line is typed and submitted with Enter. This is the default and matches the original behaviour. -config EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX - bool "Matrix / upper-half keyboard (keyboard events)" +config EXAMPLES_LVGLTERM_INPUT_KBD + bool "Physical keyboard" depends on INPUT_KEYBOARD ---help--- - Physical keyboard registered through the INPUT_KEYBOARD upper half, - whose read() returns struct keyboard_event_s events (for example the - M5Stack Cardputer matrix keyboard on /dev/kbd0). Fn Up/Down scroll - the terminal. - -config EXAMPLES_LVGLTERM_INPUT_KBD_USB - bool "USB HID keyboard (byte stream)" - depends on USBHOST_HIDKBD - select LIBC_KBDCODEC - ---help--- - USB HID keyboard (for example on /dev/kbda with CONFIG_USBHOST_HIDKBD). - read() returns a byte stream that is decoded with the keyboard codec: - normal keys go to the shell and, when the driver is built with - CONFIG_HIDKBD_ENCODED, the Up/Down cursor keys scroll the terminal. + Any keyboard registered with keyboard_register(): USB HID, a matrix, + the simulator, virtio. The terminal does not need to know which one + it is. Cursor Up and Down scroll the terminal, everything else goes + to the shell. endchoice config EXAMPLES_LVGLTERM_KBD_DEV string "Keyboard device path" - default "/dev/kbda" if EXAMPLES_LVGLTERM_INPUT_KBD_USB default "/dev/kbd0" - depends on EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX || EXAMPLES_LVGLTERM_INPUT_KBD_USB + depends on EXAMPLES_LVGLTERM_INPUT_KBD ---help--- Keyboard device the terminal reads from. Can also be overridden at run time by passing the path as the first command-line argument (useful when more than one keyboard is present). + Note that the USB HID driver names its devices /dev/kbda onwards, + since they come and go as keyboards are plugged in. + choice prompt "LVGL Terminal font" default EXAMPLES_LVGLTERM_FONT_UNSCII_16 diff --git a/examples/lvglterm/lvglterm_kbd.c b/examples/lvglterm/lvglterm_kbd.c index 08ae83612d8..daea4c50bcf 100644 --- a/examples/lvglterm/lvglterm_kbd.c +++ b/examples/lvglterm/lvglterm_kbd.c @@ -26,15 +26,13 @@ * a keyboard driver; the device defaults to CONFIG_EXAMPLES_LVGLTERM_KBD_DEV * and can be overridden by the first command-line argument. * - * Two device flavours are supported, selected at build time: + * Any keyboard registered with keyboard_register() works: USB HID, a + * matrix, the simulator, virtio. Which one it is makes no difference here. * - * - Upper-half keyboards (INPUT_KEYBOARD, e.g. the M5Stack Cardputer - * matrix on /dev/kbd0) deliver struct keyboard_event_s events; the Fn - * Up/Down keys are handled locally as scroll requests. - * - USB HID keyboards (EXAMPLES_LVGLTERM_INPUT_KBD_USB, e.g. /dev/kbda) - * deliver a byte stream decoded with the NuttX keyboard codec: normal - * keys are forwarded to the shell and, when the driver is built with - * CONFIG_HIDKBD_ENCODED, the Up/Down cursor keys scroll the terminal. + * The device delivers struct keyboard_event_s events unless the kernel was + * built with INPUT_KEYBOARD_BYTESTREAM, in which case it delivers the byte + * stream that the keyboard codec defines. That is a property of the build + * rather than of the hardware, so it is what selects the reader below. */ /**************************************************************************** @@ -50,11 +48,9 @@ #include #include -#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX -# include -#endif +#include -#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_USB +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM # include # include #endif @@ -67,17 +63,6 @@ * Pre-processor Definitions ****************************************************************************/ -/* Out-of-band key codes for the Fn + navigation cluster (cursor keys), - * reported by keyboard drivers that follow this convention (for example the - * M5Stack Cardputer). Up/Down scroll the terminal instead of going to the - * shell; drivers that do not emit these codes keep normal behaviour. - */ - -#define KEY_UP 0x80 -#define KEY_DOWN 0x81 -#define KEY_LEFT 0x82 -#define KEY_RIGHT 0x83 - #define SCROLL_STEP 24 /* Pixels scrolled per Up/Down keypress */ /**************************************************************************** @@ -134,6 +119,41 @@ static void scroll_terminal(bool up) lv_obj_scroll_by(g_output, 0, dy, LV_ANIM_OFF); } +/**************************************************************************** + * Name: handle_key + * + * Description: + * Act on one key press. Cursor Up and Down scroll the terminal, anything + * else goes to the shell. + * + * Input Parameters: + * code - The character, or a value from enum kbd_keycode_e + * special - True if the code is a keycode rather than a character. The + * two ranges overlap, so this is what tells them apart. + * + ****************************************************************************/ + +static void handle_key(uint32_t code, bool special) +{ + if (special) + { + if (code == KEYCODE_UP) + { + scroll_terminal(true); + } + else if (code == KEYCODE_DOWN) + { + scroll_terminal(false); + } + + /* Any other special key has no meaning to a terminal */ + + return; + } + + feed_char(code); +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -171,25 +191,21 @@ void lvglterm_input_create(int argc, FAR char *argv[]) void lvglterm_input_poll(void) { - if (g_kfd < 0) - { - return; - } - -#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_USB - /* USB HID keyboard: read() returns a byte stream that is decoded with the - * keyboard codec. Normal keys are fed to the shell; the Up/Down cursor - * keys (only emitted when the driver is built with CONFIG_HIDKBD_ENCODED) - * scroll the terminal. On a plain-ASCII stream every byte simply decodes - * to a normal key press, so this also works without encoding. - */ - - struct lib_meminstream_s stream; - struct kbd_getstate_s state; char buf[64]; ssize_t nread; +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM + struct lib_meminstream_s stream; + struct kbd_getstate_s state; uint8_t ch; int ret; +#else + FAR struct keyboard_event_s *evt; +#endif + + if (g_kfd < 0) + { + return; + } nread = read(g_kfd, buf, sizeof(buf)); if (nread <= 0) @@ -197,12 +213,13 @@ void lvglterm_input_poll(void) return; } +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM memset(&state, 0, sizeof(state)); lib_meminstream(&stream, buf, nread); for (; ; ) { - ret = kbd_decode((FAR struct lib_instream_s *)&stream, &state, &ch); + ret = kbd_decode(&stream.common, &state, &ch); if (ret == KBD_ERROR) { break; @@ -210,51 +227,29 @@ void lvglterm_input_poll(void) if (ret == KBD_PRESS) { - feed_char((char)ch); + handle_key(ch, false); } else if (ret == KBD_SPECPRESS) { - if (ch == KEYCODE_UP) - { - scroll_terminal(true); - } - else if (ch == KEYCODE_DOWN) - { - scroll_terminal(false); - } + handle_key(ch, true); } } #else - /* Upper-half keyboard: read() returns keyboard_event_s events */ - - struct keyboard_event_s evt; + evt = (FAR struct keyboard_event_s *)buf; - while (read(g_kfd, &evt, sizeof(evt)) == (ssize_t)sizeof(evt)) + while (nread >= (ssize_t)sizeof(struct keyboard_event_s)) { - if (evt.type != KEYBOARD_PRESS) + if (evt->type == KEYBOARD_PRESS) { - continue; + handle_key(evt->code, false); } - - /* Fn navigation keys are handled locally: Up/Down scroll the terminal; - * Left/Right are reserved and simply swallowed for now. - */ - - if (evt.code >= KEY_UP && evt.code <= KEY_RIGHT) + else if (evt->type == KEYBOARD_SPECPRESS) { - if (evt.code == KEY_UP) - { - scroll_terminal(true); - } - else if (evt.code == KEY_DOWN) - { - scroll_terminal(false); - } - - continue; + handle_key(evt->code, true); } - feed_char((char)evt.code); + nread -= sizeof(struct keyboard_event_s); + evt++; } #endif } diff --git a/games/NXDoom/src/i_input.c b/games/NXDoom/src/i_input.c index 8cb3a863019..a7e61b8f8b7 100644 --- a/games/NXDoom/src/i_input.c +++ b/games/NXDoom/src/i_input.c @@ -192,6 +192,38 @@ static int init_kbd_dev(struct keyboard_dev *dev) ****************************************************************************/ static int translate_key(uint32_t keycode) +{ + /* A keyboard driver reports Enter as the character that it produces, so + * it never arrives as KEYCODE_ENTER. The game binds the menu to + * KEY_ENTER, which is the carriage return, so the line feed that a + * driver actually sends has to be folded onto it. + */ + + if (keycode == '\n') + { + return KEY_ENTER; + } + + return keycode; +} + +/**************************************************************************** + * Name: translate_speckey + * + * Description: + * Translates a value from enum kbd_keycode_e into one from doomkeys.h. + * + * A special key arrives with a KEYBOARD_SPECPRESS or KEYBOARD_SPECREL + * event rather than as a character: the keycodes overlap the character + * range, so the event type is what tells an arrow key from the character + * that shares its value. + * + * Return: + * The translated key code, or zero if the key has no meaning to the game. + * + ****************************************************************************/ + +static int translate_speckey(uint32_t keycode) { switch (keycode) { @@ -203,10 +235,76 @@ static int translate_key(uint32_t keycode) return KEY_UPARROW; case KEYCODE_DOWN: return KEY_DOWNARROW; + + /* Drivers disagree on Enter: a USB HID keyboard reports the line feed + * that the key produces, while the simulator reports KEYCODE_ENTER. + * Both have to land on KEY_ENTER, so both are handled, here and in + * translate_key(). + */ + case KEYCODE_ENTER: return KEY_ENTER; + + case KEYCODE_BACKDEL: + return KEY_BACKSPACE; + case KEYCODE_FWDDEL: + return KEY_DEL; + case KEYCODE_INSERT: + return KEY_INS; + case KEYCODE_HOME: + return KEY_HOME; + case KEYCODE_END: + return KEY_END; + case KEYCODE_PAGEUP: + return KEY_PGUP; + case KEYCODE_PAGEDOWN: + return KEY_PGDN; + case KEYCODE_PAUSE: + return KEY_PAUSE; + case KEYCODE_CAPSLOCK: + return KEY_CAPSLOCK; + + /* Fire, run and strafe. The game binds these by default, so a + * keyboard that does not report its modifiers cannot play it. + */ + + case KEYCODE_LCTRL: + case KEYCODE_RCTRL: + return KEY_RCTRL; + case KEYCODE_LSHIFT: + case KEYCODE_RSHIFT: + return KEY_RSHIFT; + case KEYCODE_LALT: + case KEYCODE_RALT: + return KEY_RALT; + + case KEYCODE_F1: + return KEY_F1; + case KEYCODE_F2: + return KEY_F2; + case KEYCODE_F3: + return KEY_F3; + case KEYCODE_F4: + return KEY_F4; + case KEYCODE_F5: + return KEY_F5; + case KEYCODE_F6: + return KEY_F6; + case KEYCODE_F7: + return KEY_F7; + case KEYCODE_F8: + return KEY_F8; + case KEYCODE_F9: + return KEY_F9; + case KEYCODE_F10: + return KEY_F10; + case KEYCODE_F11: + return KEY_F11; + case KEYCODE_F12: + return KEY_F12; + default: - return keycode; + return 0; } } @@ -319,24 +417,53 @@ void i_handle_keyboard_event(struct keyboard_event_s *kevent) */ event_t event; + bool press; switch (kevent->type) { case KEYBOARD_PRESS: - event.type = ev_keydown; + case KEYBOARD_RELEASE: + press = (kevent->type == KEYBOARD_PRESS); event.data1 = translate_key(kevent->code); - event.data2 = get_localized_key(kevent->code); - event.data3 = get_typed_char(kevent->code); + break; - if (event.data1 != 0) - { - d_post_event(&event); - } + case KEYBOARD_SPECPRESS: + case KEYBOARD_SPECREL: + press = (kevent->type == KEYBOARD_SPECPRESS); + event.data1 = translate_speckey(kevent->code); break; - case KEYBOARD_RELEASE: + default: + return; + } + + if (event.data1 == 0) + { + return; + } + + if (press) + { + event.type = ev_keydown; + + /* A special key has no printable character, so it contributes + * nothing to data2 and data3. + */ + + if (kevent->type == KEYBOARD_PRESS) + { + event.data2 = get_localized_key(kevent->code); + event.data3 = get_typed_char(kevent->code); + } + else + { + event.data2 = 0; + event.data3 = 0; + } + } + else + { event.type = ev_keyup; - event.data1 = translate_key(kevent->code); /* data2/data3 are initialized to zero for ev_keyup. * For ev_keydown it's the shifted Unicode character @@ -347,16 +474,9 @@ void i_handle_keyboard_event(struct keyboard_event_s *kevent) event.data2 = 0; event.data3 = 0; - - if (event.data1 != 0) - { - d_post_event(&event); - } - break; - - default: - break; } + + d_post_event(&event); } void i_start_text_input(int x1, int y1, int x2, int y2) diff --git a/games/NXDoom/src/i_video.c b/games/NXDoom/src/i_video.c index cf3c837802a..d9a76a65144 100644 --- a/games/NXDoom/src/i_video.c +++ b/games/NXDoom/src/i_video.c @@ -505,19 +505,11 @@ static void i_get_event(void) while ((err = get_kbd_event(&kbdevent)) == 0) { - switch (kbdevent.type) - { - case KEYBOARD_PRESS: - - /* deliberate fall-though */ - - case KEYBOARD_RELEASE: - i_handle_keyboard_event(&kbdevent); - break; + /* All four event types are handled: dropping the special ones here + * would silently lose the arrow keys. + */ - default: - break; - } + i_handle_keyboard_event(&kbdevent); } #endif } diff --git a/graphics/microwindows/Kconfig b/graphics/microwindows/Kconfig index 8463d791fb0..9d5f0cb8c43 100644 --- a/graphics/microwindows/Kconfig +++ b/graphics/microwindows/Kconfig @@ -33,7 +33,7 @@ config MICROWINDOWS_KBD_EVENT config MICROWINDOWS_KBD_RAW bool "Raw-mode keyboard driver" - depends on LIBC_KBDCODEC + depends on INPUT_KEYBOARD_BYTESTREAM ---help--- Reads raw byte stream from a character device (e.g., /dev/kbda) and decodes escape sequences via the kbd_codec library. Suitable diff --git a/nshlib/Kconfig b/nshlib/Kconfig index 8f0cd36ee67..fbbb08d3b39 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -1090,6 +1090,7 @@ config NSH_USBKBD bool "Use USB keyboard input" default n depends on NSH_CONSOLE && USBHOST_HIDKBD && !NSH_USBCONSOLE + depends on INPUT_KEYBOARD_BYTESTREAM ---help--- Normally NSH uses the same device for stdin, stdout, and stderr. By default, that device is /dev/console. If this option is selected, @@ -1098,6 +1099,9 @@ config NSH_USBKBD interface) and the data from the keyboard will drive NSH. NSH output (stdout and stderr) will still go to /dev/console. + NSH reads the keyboard as a stream of characters, which is what + INPUT_KEYBOARD_BYTESTREAM makes a keyboard device deliver. + if NSH_USBKBD config NSH_USBKBD_DEVNAME diff --git a/system/kbd/CMakeLists.txt b/system/kbd/CMakeLists.txt new file mode 100644 index 00000000000..8ee13e65b0b --- /dev/null +++ b/system/kbd/CMakeLists.txt @@ -0,0 +1,35 @@ +# ############################################################################## +# apps/system/kbd/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_SYSTEM_KBD) + nuttx_add_application( + NAME + ${CONFIG_SYSTEM_KBD_PROGNAME} + PRIORITY + ${CONFIG_SYSTEM_KBD_PRIORITY} + STACKSIZE + ${CONFIG_SYSTEM_KBD_STACKSIZE} + MODULE + ${CONFIG_SYSTEM_KBD} + SRCS + kbd_main.c) +endif() diff --git a/system/kbd/Kconfig b/system/kbd/Kconfig new file mode 100644 index 00000000000..d1030ab0915 --- /dev/null +++ b/system/kbd/Kconfig @@ -0,0 +1,38 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_KBD + tristate "Keyboard dump" + default n + depends on INPUT_KEYBOARD + ---help--- + Dump what a keyboard reports. This reads any keyboard registered + with keyboard_register(), whatever the hardware behind it is, so it + is the tool to reach for when bringing up a new keyboard. + + Replaces the hidkbd and keyboard examples, which did the same thing + for one kind of keyboard each. + +if SYSTEM_KBD + +config SYSTEM_KBD_PROGNAME + string "Program name" + default "kbd" + +config SYSTEM_KBD_PRIORITY + int "Task priority" + default 100 + +config SYSTEM_KBD_STACKSIZE + int "Task stack size" + default DEFAULT_TASK_STACKSIZE + +config SYSTEM_KBD_DEVPATH + string "Keyboard device path" + default "/dev/kbd0" + ---help--- + The keyboard to read when none is named on the command line. + +endif diff --git a/system/kbd/Make.defs b/system/kbd/Make.defs new file mode 100644 index 00000000000..f80780936d9 --- /dev/null +++ b/system/kbd/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/system/kbd/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_KBD),) +CONFIGURED_APPS += $(APPDIR)/system/kbd +endif diff --git a/system/kbd/Makefile b/system/kbd/Makefile new file mode 100644 index 00000000000..97c5dad1969 --- /dev/null +++ b/system/kbd/Makefile @@ -0,0 +1,32 @@ +############################################################################ +# apps/system/kbd/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_SYSTEM_KBD_PROGNAME) +PRIORITY = $(CONFIG_SYSTEM_KBD_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_KBD_STACKSIZE) +MODULE = $(CONFIG_SYSTEM_KBD) + +MAINSRC = kbd_main.c + +include $(APPDIR)/Application.mk diff --git a/system/kbd/kbd_main.c b/system/kbd/kbd_main.c new file mode 100644 index 00000000000..20851077bb4 --- /dev/null +++ b/system/kbd/kbd_main.c @@ -0,0 +1,476 @@ +/**************************************************************************** + * apps/system/kbd/kbd_main.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* Dump what a keyboard reports. + * + * Every keyboard registered with keyboard_register() is read the same way, + * whatever the hardware behind it is: a USB HID keyboard, a matrix, the + * simulator, virtio. So this works with all of them, and it is the place + * to look when bringing up a new one. + * + * The device delivers struct keyboard_event_s events unless the kernel was + * built with INPUT_KEYBOARD_BYTESTREAM, in which case it delivers the byte + * stream that the keyboard codec defines. This follows that setting rather + * than having a switch of its own. + * + * With -i it goes the other way and injects into a uinput keyboard, either + * what it reads from its own stdin or every key of another keyboard. So an + * application can be driven from a serial console with no keyboard hardware + * at all, and a real keyboard and an injected one can drive it at the same + * time, which neither can do on its own since an application opens a single + * device. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM +# include +# include +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define KBD_READ_SIZE 64 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: kbd_typename + ****************************************************************************/ + +static FAR const char *kbd_typename(uint32_t type) +{ + switch (type) + { + case KEYBOARD_PRESS: + return "press"; + + case KEYBOARD_RELEASE: + return "release"; + + case KEYBOARD_SPECPRESS: + return "specpress"; + + case KEYBOARD_SPECREL: + return "specrel"; + + default: + return "unknown"; + } +} + +/**************************************************************************** + * Name: kbd_show + * + * Description: + * Print one key. A normal key carries a character, a special key carries + * a value from enum kbd_keycode_e, and the type is what says which. + * + ****************************************************************************/ + +static void kbd_show(uint32_t code, uint32_t type) +{ + if (type == KEYBOARD_PRESS || type == KEYBOARD_RELEASE) + { + printf("%-9s code %3" PRIu32 " '%c'\n", kbd_typename(type), code, + isprint(code) ? (int)code : '.'); + } + else + { + printf("%-9s keycode %" PRIu32 "\n", kbd_typename(type), code); + } + + fflush(stdout); +} + +/**************************************************************************** + * Name: kbd_dump + * + * Description: + * Print everything in one read. + * + * Returned Value: + * The number of keys printed. + * + ****************************************************************************/ + +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM +static size_t kbd_dump(FAR char *buffer, size_t nbytes) +{ + struct lib_meminstream_s stream; + struct kbd_getstate_s state; + uint8_t ch; + size_t nkeys = 0; + int ret; + + lib_meminstream(&stream, buffer, nbytes); + memset(&state, 0, sizeof(state)); + + for (; ; ) + { + ret = kbd_decode(&stream.common, &state, &ch); + if (ret == KBD_ERROR) + { + break; + } + + kbd_show(ch, ret); + nkeys++; + } + + return nkeys; +} +#else +static size_t kbd_dump(FAR char *buffer, size_t nbytes) +{ + FAR struct keyboard_event_s *key = (FAR struct keyboard_event_s *)buffer; + size_t nkeys = 0; + + if ((nbytes % sizeof(struct keyboard_event_s)) != 0) + { + fprintf(stderr, "kbd: short read of %zu bytes, ignoring\n", nbytes); + return 0; + } + + while (nbytes >= sizeof(struct keyboard_event_s)) + { + kbd_show(key->code, key->type); + + nbytes -= sizeof(struct keyboard_event_s); + key++; + nkeys++; + } + + return nkeys; +} +#endif + +/**************************************************************************** + * Name: kbd_inject + * + * Description: + * Turn each byte read from stdin into a key press and release on the + * given device. The device has to be a uinput keyboard: a real one has + * nothing to inject into. + * + * Note that this always writes struct keyboard_event_s. Injection goes + * through the lower half write method, which is not affected by + * INPUT_KEYBOARD_BYTESTREAM. + * + * Returned Value: + * The number of keys injected. + * + ****************************************************************************/ + +static bool kbd_emit(int fd, uint32_t code, uint32_t type) +{ + struct keyboard_event_s key; + + key.code = code; + key.type = type; + + if (write(fd, &key, sizeof(key)) != (ssize_t)sizeof(key)) + { + fprintf(stderr, "kbd: inject failed: %d\n", errno); + return false; + } + + return true; +} + +static size_t kbd_inject(int fd) +{ + unsigned char buf[KBD_READ_SIZE]; + uint32_t code; + ssize_t nread; + ssize_t i; + size_t nkeys = 0; + + for (; ; ) + { + nread = read(STDIN_FILENO, buf, sizeof(buf)); + if (nread <= 0) + { + break; + } + + for (i = 0; i < nread; i++) + { + /* A terminal sends a carriage return where a keyboard would + * send a line feed. + */ + + code = (buf[i] == '\r') ? '\n' : buf[i]; + + if (!kbd_emit(fd, code, KEYBOARD_PRESS)) + { + return nkeys; + } + + kbd_emit(fd, code, KEYBOARD_RELEASE); + nkeys++; + } + } + + return nkeys; +} + +/**************************************************************************** + * Name: kbd_forward + * + * Description: + * Copy every key from one keyboard onto another. Pointing an + * application at the destination lets a real keyboard and an injected one + * drive it at the same time, which neither can do on its own since an + * application opens a single device. + * + * Returned Value: + * The number of keys forwarded. + * + ****************************************************************************/ + +static size_t kbd_forward(int fd, int srcfd) +{ + char buf[KBD_READ_SIZE]; + ssize_t nread; + size_t nkeys = 0; +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM + struct lib_meminstream_s stream; + struct kbd_getstate_s state; + uint8_t ch; + int ret; +#else + FAR struct keyboard_event_s *key; +#endif + + for (; ; ) + { + nread = read(srcfd, buf, sizeof(buf)); + if (nread <= 0) + { + break; + } + +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM + + /* The source speaks the byte stream and the destination only takes + * events, so this decodes rather than copies. The decoder returns + * the event type directly. + */ + + memset(&state, 0, sizeof(state)); + lib_meminstream(&stream, buf, nread); + + for (; ; ) + { + ret = kbd_decode(&stream.common, &state, &ch); + if (ret == KBD_ERROR) + { + break; + } + + if (!kbd_emit(fd, ch, ret)) + { + return nkeys; + } + + nkeys++; + } +#else + key = (FAR struct keyboard_event_s *)buf; + + while (nread >= (ssize_t)sizeof(struct keyboard_event_s)) + { + if (!kbd_emit(fd, key->code, key->type)) + { + return nkeys; + } + + nread -= sizeof(struct keyboard_event_s); + key++; + nkeys++; + } +#endif + } + + return nkeys; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + FAR const char *devpath = CONFIG_SYSTEM_KBD_DEVPATH; + FAR const char *srcpath = NULL; + char buffer[KBD_READ_SIZE]; + bool inject = false; + size_t nkeys = 0; + size_t limit = 0; + ssize_t nbytes; + int argi = 1; + int srcfd; + int fd; + + if (argi < argc && strcmp(argv[argi], "-i") == 0) + { + inject = true; + argi++; + } + + if (argi < argc) + { + devpath = argv[argi++]; + } + + if (argi < argc) + { + /* When injecting, the extra argument is the keyboard to forward from + * rather than a count: there is nothing to count. + */ + + if (inject) + { + srcpath = argv[argi]; + } + else + { + limit = strtoul(argv[argi], NULL, 10); + } + } + + /* Wait for the device. A USB keyboard appears when it is plugged in, so + * this is the normal way to start rather than an error path. + */ + + for (; ; ) + { + fd = open(devpath, (inject ? O_WRONLY : O_RDONLY) | O_CLOEXEC); + if (fd >= 0) + { + break; + } + + printf("kbd: waiting for %s: %d\n", devpath, errno); + fflush(stdout); + sleep(3); + } + + if (inject) + { + if (srcpath == NULL) + { + printf("kbd: injecting into %s, type to send, Ctrl-D to stop\n", + devpath); + fflush(stdout); + + nkeys = kbd_inject(fd); + } + else + { + srcfd = open(srcpath, O_RDONLY | O_CLOEXEC); + if (srcfd < 0) + { + fprintf(stderr, "kbd: open %s failed: %d\n", srcpath, errno); + close(fd); + return EXIT_FAILURE; + } + + printf("kbd: forwarding %s into %s\n", srcpath, devpath); + fflush(stdout); + + nkeys = kbd_forward(fd, srcfd); + close(srcfd); + } + + goto done; + } + + printf("kbd: reading %s, %s\n", devpath, +#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM + "byte stream" +#else + "keyboard events" +#endif + ); + fflush(stdout); + + for (; ; ) + { + nbytes = read(fd, buffer, sizeof(buffer)); + if (nbytes < 0) + { + if (errno == EINTR) + { + continue; + } + + fprintf(stderr, "kbd: read %s failed: %d\n", devpath, errno); + break; + } + else if (nbytes == 0) + { + /* The keyboard is gone */ + + break; + } + + nkeys += kbd_dump(buffer, nbytes); + + if (limit > 0 && nkeys >= limit) + { + break; + } + } + +done: + close(fd); + printf("kbd: %zu keys\n", nkeys); + return EXIT_SUCCESS; +}