This commit is contained in:
Jorge Guzman 2026-08-01 11:13:43 -03:00 committed by GitHub
commit 8366ced063
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 839 additions and 129 deletions

View file

@ -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

View file

@ -35,6 +35,8 @@
#include <nuttx/usb/usbhost.h>
#ifdef CONFIG_EXAMPLES_HIDKBD_ENCODED
# include <ctype.h>
# include <nuttx/streams.h>
# include <nuttx/input/kbd_codec.h>
#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

View file

@ -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

View file

@ -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 <errno.h>
#include <debug.h>
#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX
# include <nuttx/input/keyboard.h>
#endif
#include <nuttx/input/keyboard.h>
#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_USB
#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM
# include <nuttx/streams.h>
# include <nuttx/input/kbd_codec.h>
#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,38 +191,35 @@ void lvglterm_input_create(int argc, FAR char *argv[])
void lvglterm_input_poll(void)
{
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;
}
#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;
uint8_t ch;
int ret;
nread = read(g_kfd, buf, sizeof(buf));
if (nread <= 0)
{
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 */
evt = (FAR struct keyboard_event_s *)buf;
struct keyboard_event_s evt;
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);
}
else if (evt->type == KEYBOARD_SPECPRESS)
{
handle_key(evt->code, true);
}
/* 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)
{
if (evt.code == KEY_UP)
{
scroll_terminal(true);
}
else if (evt.code == KEY_DOWN)
{
scroll_terminal(false);
}
continue;
}
feed_char((char)evt.code);
nread -= sizeof(struct keyboard_event_s);
evt++;
}
#endif
}

View file

@ -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);
if (event.data1 != 0)
{
d_post_event(&event);
}
break;
case KEYBOARD_RELEASE:
case KEYBOARD_SPECPRESS:
case KEYBOARD_SPECREL:
press = (kevent->type == KEYBOARD_SPECPRESS);
event.data1 = translate_speckey(kevent->code);
break;
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)

View file

@ -505,19 +505,11 @@ static void i_get_event(void)
while ((err = get_kbd_event(&kbdevent)) == 0)
{
switch (kbdevent.type)
{
case KEYBOARD_PRESS:
/* All four event types are handled: dropping the special ones here
* would silently lose the arrow keys.
*/
/* deliberate fall-though */
case KEYBOARD_RELEASE:
i_handle_keyboard_event(&kbdevent);
break;
default:
break;
}
i_handle_keyboard_event(&kbdevent);
}
#endif
}

View file

@ -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

View file

@ -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

35
system/kbd/CMakeLists.txt Normal file
View file

@ -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()

38
system/kbd/Kconfig Normal file
View file

@ -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

25
system/kbd/Make.defs Normal file
View file

@ -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

32
system/kbd/Makefile Normal file
View file

@ -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

476
system/kbd/kbd_main.c Normal file
View file

@ -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 <nuttx/config.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <nuttx/input/keyboard.h>
#ifdef CONFIG_INPUT_KEYBOARD_BYTESTREAM
# include <nuttx/streams.h>
# include <nuttx/input/kbd_codec.h>
#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;
}