diff --git a/examples/lvglterm/CMakeLists.txt b/examples/lvglterm/CMakeLists.txt index 22650f82f..c3f84b26e 100644 --- a/examples/lvglterm/CMakeLists.txt +++ b/examples/lvglterm/CMakeLists.txt @@ -21,10 +21,10 @@ # ############################################################################## if(CONFIG_EXAMPLES_LVGLTERM) - if(CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD) - set(lvglterm_input lvglterm_kbd.c) - else() + if(CONFIG_EXAMPLES_LVGLTERM_INPUT_TOUCH) set(lvglterm_input lvglterm_touch.c) + else() + set(lvglterm_input lvglterm_kbd.c) endif() nuttx_add_application( diff --git a/examples/lvglterm/Kconfig b/examples/lvglterm/Kconfig index 718af98b6..ca393e845 100644 --- a/examples/lvglterm/Kconfig +++ b/examples/lvglterm/Kconfig @@ -17,7 +17,9 @@ choice default EXAMPLES_LVGLTERM_INPUT_TOUCH ---help--- Select how the terminal receives keystrokes. Only one input source - is built at a time. + 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. config EXAMPLES_LVGLTERM_INPUT_TOUCH bool "On-screen keyboard (touch)" @@ -26,24 +28,36 @@ 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 - bool "Physical keyboard (/dev/kbdN)" +config EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX + bool "Matrix / upper-half keyboard (keyboard events)" depends on INPUT_KEYBOARD ---help--- - Input comes from a physical keyboard registered as a /dev/kbdN - device; keystrokes are streamed to the shell and the output fills - the whole screen. + 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. 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 + depends on EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX || EXAMPLES_LVGLTERM_INPUT_KBD_USB ---help--- - Keyboard device the terminal reads key events 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). + 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). choice prompt "LVGL Terminal font" diff --git a/examples/lvglterm/Makefile b/examples/lvglterm/Makefile index 314351f9a..0d4c5d0c2 100644 --- a/examples/lvglterm/Makefile +++ b/examples/lvglterm/Makefile @@ -32,7 +32,7 @@ MODULE = $(CONFIG_EXAMPLES_LVGLTERM) MAINSRC = lvglterm.c -ifeq ($(CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD),y) +ifneq ($(CONFIG_EXAMPLES_LVGLTERM_INPUT_TOUCH),y) CSRCS = lvglterm_kbd.c else CSRCS = lvglterm_touch.c diff --git a/examples/lvglterm/lvglterm.c b/examples/lvglterm/lvglterm.c index 0814e9afe..f2dcb1f10 100644 --- a/examples/lvglterm/lvglterm.c +++ b/examples/lvglterm/lvglterm.c @@ -304,7 +304,7 @@ static int create_terminal(int argc, FAR char *argv[]) if (pipe(g_nsh_stdin) < 0 || pipe(g_nsh_stdout) < 0 || pipe(g_nsh_stderr) < 0) { - _err("pipe failed: %d\n", errno); + fprintf(stderr, "pipe failed: %d\n", errno); return ERROR; } @@ -324,7 +324,7 @@ static int create_terminal(int argc, FAR char *argv[]) if (ret < 0) { int errcode = errno; - _err("posix_spawn failed: %d\n", errcode); + fprintf(stderr, "posix_spawn failed: %d\n", errcode); return -errcode; } diff --git a/examples/lvglterm/lvglterm_kbd.c b/examples/lvglterm/lvglterm_kbd.c index 4c18aed7d..08ae83612 100644 --- a/examples/lvglterm/lvglterm_kbd.c +++ b/examples/lvglterm/lvglterm_kbd.c @@ -20,11 +20,21 @@ * ****************************************************************************/ -/* Physical-keyboard input variant of the LVGL terminal: a task reads key - * events from a /dev/kbdN keyboard device and streams them to NSH; the shell - * output fills the whole screen. Works on any board with a keyboard driver; - * the device defaults to CONFIG_EXAMPLES_LVGLTERM_KBD_DEV and can be - * overridden by the first command-line argument. +/* Physical-keyboard input variant of the LVGL terminal: the keyboard device + * is polled (non-blocking) from the LVGL thread and key presses are streamed + * to NSH; the shell output fills the whole screen. Works on any board with + * 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: + * + * - 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. */ /**************************************************************************** @@ -34,13 +44,20 @@ #include #include #include +#include #include #include -#include #include #include -#include +#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_MATRIX +# include +#endif + +#ifdef CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD_USB +# include +# include +#endif #include @@ -67,8 +84,7 @@ * Private Data ****************************************************************************/ -static int g_echo[2]; /* Keyboard task -> screen (local echo) */ -static volatile int g_scroll; /* Pending scroll (lines), applied by LVGL */ +static int g_kfd = -1; /* Keyboard device fd (opened O_NONBLOCK) */ static FAR const char *g_kbddev; /* Keyboard device path (/dev/kbdN) */ /**************************************************************************** @@ -76,87 +92,46 @@ static FAR const char *g_kbddev; /* Keyboard device path (/dev/kbdN) */ ****************************************************************************/ /**************************************************************************** - * Name: kbd_task + * Name: feed_char * * Description: - * Read key presses from the keyboard device and forward them to NSH stdin - * (and to the echo pipe so the user sees what is typed). The Fn Up/Down - * keys are handled locally as scroll requests. + * Forward one character to NSH stdin and, for printable characters and + * newline, echo it on screen. Control keys such as backspace are echoed + * by the shell's own line editor (via its stdout) to avoid handling them + * twice. Carriage return is normalised to newline. Runs in the LVGL + * thread, so it may touch the LVGL widgets directly. * ****************************************************************************/ -static int kbd_task(int argc, FAR char *argv[]) +static void feed_char(char ch) { - struct keyboard_event_s evt; - ssize_t nread; - int kfd; - char ch; - - kfd = open(g_kbddev, O_RDONLY); - if (kfd < 0) + if (ch == '\r') { - gerr("ERROR: open %s failed: %d\n", g_kbddev, errno); - return EXIT_FAILURE; + ch = '\n'; } - for (; ; ) + write(g_nsh_stdin[WRITE_PIPE], &ch, 1); + + if (ch == '\n' || ((uint8_t)ch >= 0x20 && (uint8_t)ch < 0x7f)) { - nread = read(kfd, &evt, sizeof(evt)); - if (nread != (ssize_t)sizeof(evt)) - { - if (nread <= 0) - { - usleep(10000); - } - - continue; - } - - if (evt.type != KEYBOARD_PRESS) - { - continue; - } - - /* Fn navigation keys are handled locally: Up/Down scroll the terminal - * (applied by the LVGL thread) and are not sent to the shell. - * Left/Right are reserved and simply swallowed for now. - */ - - if (evt.code >= KEY_UP && evt.code <= KEY_RIGHT) - { - if (evt.code == KEY_UP) - { - g_scroll -= 1; - } - else if (evt.code == KEY_DOWN) - { - g_scroll += 1; - } - - continue; - } - - ch = (char)evt.code; - if (ch == '\r') - { - ch = '\n'; - } - - /* Always feed the shell. Echo only printable characters and newline - * locally; control keys such as backspace are echoed by the shell's - * own line editor (via its stdout) to avoid handling them twice. - */ - - write(g_nsh_stdin[WRITE_PIPE], &ch, 1); - - if (ch == '\n' || ((uint8_t)ch >= 0x20 && (uint8_t)ch < 0x7f)) - { - write(g_echo[WRITE_PIPE], &ch, 1); - } + lvglterm_add_output(&ch, 1); } +} - close(kfd); - return EXIT_SUCCESS; +/**************************************************************************** + * Name: scroll_terminal + * + * Description: + * Scroll the output text area up (older output) or down by one step. Runs + * in the LVGL thread. + * + ****************************************************************************/ + +static void scroll_terminal(bool up) +{ + int32_t dy = up ? SCROLL_STEP : -SCROLL_STEP; + + lv_obj_scroll_by(g_output, 0, dy, LV_ANIM_OFF); } /**************************************************************************** @@ -168,7 +143,8 @@ static int kbd_task(int argc, FAR char *argv[]) * * Description: * Resolve the keyboard device (first command-line argument, else the - * configured default) and start the task that streams key presses to NSH. + * configured default) and open it non-blocking so that it can be polled + * from the LVGL thread. * ****************************************************************************/ @@ -176,53 +152,109 @@ void lvglterm_input_create(int argc, FAR char *argv[]) { g_kbddev = (argc > 1) ? argv[1] : CONFIG_EXAMPLES_LVGLTERM_KBD_DEV; - if (pipe(g_echo) < 0) + g_kfd = open(g_kbddev, O_RDONLY | O_NONBLOCK); + if (g_kfd < 0) { - gerr("ERROR: echo pipe failed: %d\n", errno); - return; + gerr("ERROR: open %s failed: %d\n", g_kbddev, errno); } - - /* The read end is polled by the LVGL timer and must not block */ - - fcntl(g_echo[READ_PIPE], F_SETFL, - fcntl(g_echo[READ_PIPE], F_GETFL) | O_NONBLOCK); - - task_create("lvgltermkbd", SCHED_PRIORITY_DEFAULT, 2048, kbd_task, NULL); } /**************************************************************************** * Name: lvglterm_input_poll * * Description: - * Apply any pending scroll and drain the local echo pipe onto the screen. - * Runs in the LVGL thread. + * Drain any pending key presses from the keyboard device and forward them + * to NSH (echoing what is typed on screen). Runs in the LVGL thread from + * the terminal's periodic timer, so the read must not block. * ****************************************************************************/ void lvglterm_input_poll(void) { - char buf[64]; - int n; - int s; + if (g_kfd < 0) + { + return; + } - /* Apply any pending scroll requested by the keyboard task (Up/Down). - * A negative value scrolls up (towards older output), positive scrolls - * down. Consume only what we read so concurrent keypresses are not lost. +#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. */ - s = g_scroll; - if (s != 0) + 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) { - g_scroll -= s; - lv_obj_scroll_by(g_output, 0, -s * SCROLL_STEP, LV_ANIM_OFF); + return; } - if (lvglterm_has_input(g_echo[READ_PIPE])) + memset(&state, 0, sizeof(state)); + lib_meminstream(&stream, buf, nread); + + for (; ; ) { - n = read(g_echo[READ_PIPE], buf, sizeof(buf)); - if (n > 0) + ret = kbd_decode((FAR struct lib_instream_s *)&stream, &state, &ch); + if (ret == KBD_ERROR) { - lvglterm_add_output(buf, n); + break; + } + + if (ret == KBD_PRESS) + { + feed_char((char)ch); + } + else if (ret == KBD_SPECPRESS) + { + if (ch == KEYCODE_UP) + { + scroll_terminal(true); + } + else if (ch == KEYCODE_DOWN) + { + scroll_terminal(false); + } } } +#else + /* Upper-half keyboard: read() returns keyboard_event_s events */ + + struct keyboard_event_s evt; + + while (read(g_kfd, &evt, sizeof(evt)) == (ssize_t)sizeof(evt)) + { + if (evt.type != KEYBOARD_PRESS) + { + continue; + } + + /* 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); + } +#endif }