system/kbd: add a keyboard dump that reads any keyboard

The hidkbd and keyboard examples do the same thing for one kind of
keyboard each:  hidkbd reads a USB HID keyboard as a byte stream, and
keyboard reads an upper half keyboard as events.  Neither works with the
other, so bringing up a new keyboard means picking the right example
first, and there is no answer for somebody whose keyboard is neither.

Every keyboard registered with keyboard_register() is read the same way,
so one tool covers them all:  USB HID, matrix, simulator, virtio, VNC.

The payload follows INPUT_KEYBOARD_BYTESTREAM rather than a switch of
its own.  An application has no business knowing what hardware is behind
the device, and a build cannot mix the two formats anyway.

The two examples stay for now.  They are what the in-tree configurations
still name, and removing them has to wait until those configurations
have been moved over.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
This commit is contained in:
Jorge Guzman 2026-07-30 15:15:54 -03:00
parent 37a1f0e068
commit ad2242d812
5 changed files with 395 additions and 0 deletions

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

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

@ -0,0 +1,265 @@
/****************************************************************************
* 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.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.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
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main
****************************************************************************/
int main(int argc, FAR char *argv[])
{
FAR const char *devpath = CONFIG_SYSTEM_KBD_DEVPATH;
char buffer[KBD_READ_SIZE];
size_t nkeys = 0;
size_t limit = 0;
ssize_t nbytes;
int fd;
if (argc > 1)
{
devpath = argv[1];
}
if (argc > 2)
{
limit = strtoul(argv[2], 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, O_RDONLY);
if (fd >= 0)
{
break;
}
printf("kbd: waiting for %s: %d\n", devpath, errno);
fflush(stdout);
sleep(3);
}
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;
}
}
close(fd);
printf("kbd: %zu keys\n", nkeys);
return EXIT_SUCCESS;
}