From 2f7123e818d6ef6752e67dcd62998e340c32a3ae Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Sat, 22 Aug 2026 07:42:22 +0200 Subject: [PATCH 1/2] examples/nxterm: add PTY-backed keyboard console Convert event-mode keyboard records into the TTY input and signals NSH expects. Signed-off-by: raiden00pl Assisted-by: Claude Code --- examples/nxterm/CMakeLists.txt | 3 + examples/nxterm/Kconfig | 19 ++ examples/nxterm/Makefile | 5 + examples/nxterm/nxterm_internal.h | 4 + examples/nxterm/nxterm_main.c | 10 + examples/nxterm/nxterm_pty.c | 329 ++++++++++++++++++++++++++++++ 6 files changed, 370 insertions(+) create mode 100644 examples/nxterm/nxterm_pty.c diff --git a/examples/nxterm/CMakeLists.txt b/examples/nxterm/CMakeLists.txt index 816d90c365a..19aa117570f 100644 --- a/examples/nxterm/CMakeLists.txt +++ b/examples/nxterm/CMakeLists.txt @@ -33,4 +33,7 @@ if(CONFIG_EXAMPLES_NXTERM) SRCS nxterm_main.c) target_sources(apps PRIVATE nxterm_toolbar.c nxterm_wndo.c nxterm_listener.c) + if(CONFIG_EXAMPLES_NXTERM_PTYCONSOLE) + target_sources(apps PRIVATE nxterm_pty.c) + endif() endif() diff --git a/examples/nxterm/Kconfig b/examples/nxterm/Kconfig index 489942442e4..0892f21891e 100644 --- a/examples/nxterm/Kconfig +++ b/examples/nxterm/Kconfig @@ -64,6 +64,25 @@ config EXAMPLES_NXTERM_FULLSCREEN Size the NXTerm window to cover the complete display instead of centering it at three quarters of the display size. +config EXAMPLES_NXTERM_PTYCONSOLE + bool "PTY-backed keyboard console" + default n + depends on PSEUDOTERM && INPUT_KEYBOARD + depends on !INPUT_KEYBOARD_BYTESTREAM + depends on !NSH_ALTCONDEV && !NSH_USBKBD + ---help--- + Route NSH through a pseudo-terminal and bridge an event-mode keyboard + to the terminal master. This gives NXTerm a real TTY, translates + NuttX keyboard special-key codes to VT100 input, and permits terminal + generated signals such as Ctrl-C and Ctrl-Z. + +config EXAMPLES_NXTERM_KBDDEV + string "Keyboard device" + default "/dev/kbda" + depends on EXAMPLES_NXTERM_PTYCONSOLE + ---help--- + Path of the keyboard event device bridged to the pseudo-terminal. + config EXAMPLES_NXTERM_TOOLBAR_HEIGHT int "Toolbar height" default 16 diff --git a/examples/nxterm/Makefile b/examples/nxterm/Makefile index 58b7037ad8a..07eb89c7fcb 100644 --- a/examples/nxterm/Makefile +++ b/examples/nxterm/Makefile @@ -25,6 +25,11 @@ include $(APPDIR)/Make.defs # NuttX NX Console Example. CSRCS = nxterm_toolbar.c nxterm_wndo.c nxterm_listener.c + +ifeq ($(CONFIG_EXAMPLES_NXTERM_PTYCONSOLE),y) +CSRCS += nxterm_pty.c +endif + MAINSRC = nxterm_main.c # NX built-in application info diff --git a/examples/nxterm/nxterm_internal.h b/examples/nxterm/nxterm_internal.h index d72aa0159a1..181156e342d 100644 --- a/examples/nxterm/nxterm_internal.h +++ b/examples/nxterm/nxterm_internal.h @@ -254,4 +254,8 @@ extern const struct nx_callback_s g_nxtoolcb; FAR void *nxterm_listener(FAR void *arg); +#ifdef CONFIG_EXAMPLES_NXTERM_PTYCONSOLE +int nxterm_pty_redirect(int nxtermfd); +#endif + #endif /* __APPS_EXAMPLES_NXTERM_NXTERM_INTERNAL_H */ diff --git a/examples/nxterm/nxterm_main.c b/examples/nxterm/nxterm_main.c index d42735533e1..7d2e9767589 100644 --- a/examples/nxterm/nxterm_main.c +++ b/examples/nxterm/nxterm_main.c @@ -376,12 +376,22 @@ int main(int argc, FAR char *argv[]) fflush(stdout); fflush(stderr); +#ifdef CONFIG_EXAMPLES_NXTERM_PTYCONSOLE + ret = nxterm_pty_redirect(fd); + if (ret < 0) + { + printf("nxterm_main: PTY console setup failed: %d\n", -ret); + close(fd); + goto errout_with_driver; + } +#else dup2(fd, 1); dup2(fd, 2); /* And we can close our original driver file descriptor */ close(fd); +#endif /* And start the console task. It will inherit stdin, stdout, and stderr * from this task. diff --git a/examples/nxterm/nxterm_pty.c b/examples/nxterm/nxterm_pty.c new file mode 100644 index 00000000000..1e445304fec --- /dev/null +++ b/examples/nxterm/nxterm_pty.c @@ -0,0 +1,329 @@ +/**************************************************************************** + * apps/examples/nxterm/nxterm_pty.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nxterm_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct nxterm_pty_s +{ + int masterfd; + int nxtermfd; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct nxterm_pty_s g_pty; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nxterm_writeall + ****************************************************************************/ + +static int nxterm_writeall(int fd, FAR const void *buffer, size_t buflen) +{ + FAR const uint8_t *src = buffer; + + while (buflen > 0) + { + ssize_t nwritten = write(fd, src, buflen); + if (nwritten < 0) + { + if (errno == EINTR) + { + continue; + } + + return -errno; + } + + src += nwritten; + buflen -= nwritten; + } + + return OK; +} + +/**************************************************************************** + * Name: nxterm_pty_key + ****************************************************************************/ + +static void nxterm_pty_key(FAR struct nxterm_pty_s *priv, uint32_t code, + bool special) +{ + FAR const char *sequence = NULL; + size_t seqlen = 0; + char normal; + + if (!special) + { + normal = (char)code; + nxterm_writeall(priv->masterfd, &normal, 1); + return; + } + + switch (code) + { + case KEYCODE_FWDDEL: + sequence = "\033[3~"; + seqlen = 4; + break; + + case KEYCODE_BACKDEL: + sequence = "\177"; + seqlen = 1; + break; + + case KEYCODE_HOME: + sequence = "\033[H"; + seqlen = 3; + break; + + case KEYCODE_END: + sequence = "\033[F"; + seqlen = 3; + break; + + case KEYCODE_LEFT: + sequence = "\033[D"; + seqlen = 3; + break; + + case KEYCODE_RIGHT: + sequence = "\033[C"; + seqlen = 3; + break; + + case KEYCODE_UP: + sequence = "\033[A"; + seqlen = 3; + break; + + case KEYCODE_DOWN: + sequence = "\033[B"; + seqlen = 3; + break; + + case KEYCODE_PAGEUP: + sequence = "\033[5~"; + seqlen = 4; + break; + + case KEYCODE_PAGEDOWN: + sequence = "\033[6~"; + seqlen = 4; + break; + + case KEYCODE_INSERT: + sequence = "\033[2~"; + seqlen = 4; + break; + + case KEYCODE_ENTER: + sequence = "\n"; + seqlen = 1; + break; + + default: + break; + } + + if (sequence != NULL) + { + nxterm_writeall(priv->masterfd, sequence, seqlen); + } +} + +/**************************************************************************** + * Name: nxterm_pty_bridge + ****************************************************************************/ + +static int nxterm_pty_bridge(int argc, FAR char *argv[]) +{ + FAR struct nxterm_pty_s *priv = &g_pty; + struct keyboard_event_s events[8]; + struct pollfd fds[2]; + uint8_t buffer[64]; + int keyboardfd = -1; + bool waiting = false; + + UNUSED(argc); + UNUSED(argv); + + for (; ; ) + { + if (keyboardfd < 0) + { + keyboardfd = open(CONFIG_EXAMPLES_NXTERM_KBDDEV, + O_RDONLY | O_NONBLOCK); + if (keyboardfd < 0 && !waiting) + { + static const char message[] = "Waiting for a keyboard...\r\n"; + + nxterm_writeall(priv->nxtermfd, message, sizeof(message) - 1); + waiting = true; + } + } + + fds[0].fd = priv->masterfd; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = keyboardfd; + fds[1].events = POLLIN; + fds[1].revents = 0; + + if (poll(fds, keyboardfd < 0 ? 1 : 2, + keyboardfd < 0 ? 1000 : -1) < 0) + { + if (errno != EINTR) + { + break; + } + + continue; + } + + if ((fds[0].revents & POLLIN) != 0) + { + ssize_t nread = read(priv->masterfd, buffer, sizeof(buffer)); + if (nread <= 0 || + nxterm_writeall(priv->nxtermfd, buffer, nread) < 0) + { + break; + } + } + + if (keyboardfd >= 0 && (fds[1].revents & POLLIN) != 0) + { + ssize_t nread = read(keyboardfd, events, sizeof(events)); + size_t i; + + if (nread <= 0) + { + close(keyboardfd); + keyboardfd = -1; + waiting = false; + continue; + } + + waiting = false; + for (i = 0; + i < (size_t)nread / sizeof(struct keyboard_event_s); + i++) + { + if (events[i].type == KEYBOARD_PRESS) + { + nxterm_pty_key(priv, events[i].code, false); + } + else if (events[i].type == KEYBOARD_SPECPRESS) + { + nxterm_pty_key(priv, events[i].code, true); + } + } + } + + if (keyboardfd >= 0 && + (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) + { + close(keyboardfd); + keyboardfd = -1; + waiting = false; + } + } + + if (keyboardfd >= 0) + { + close(keyboardfd); + } + + return EXIT_FAILURE; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nxterm_pty_redirect + ****************************************************************************/ + +int nxterm_pty_redirect(int nxtermfd) +{ + int slavefd; + int ret; + + ret = openpty(&g_pty.masterfd, &slavefd, NULL, NULL, NULL); + if (ret < 0) + { + return -errno; + } + + g_pty.nxtermfd = nxtermfd; + + ret = task_create("NxTermPTY", CONFIG_EXAMPLES_NXTERM_CLIENTPRIO, + CONFIG_EXAMPLES_NXTERM_STACKSIZE, + nxterm_pty_bridge, NULL); + if (ret < 0) + { + close(slavefd); + close(g_pty.masterfd); + return -errno; + } + + if (dup2(slavefd, STDIN_FILENO) < 0 || + dup2(slavefd, STDOUT_FILENO) < 0 || + dup2(slavefd, STDERR_FILENO) < 0) + { + ret = -errno; + close(slavefd); + return ret; + } + + close(slavefd); + return OK; +} From 5dcb5754d164a1012c504a70009750476eef15c9 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Sat, 22 Aug 2026 07:42:22 +0200 Subject: [PATCH 2/2] examples/nxterm: add serial console fallback Prevent framebuffer startup failures from leaving the system without NSH. Signed-off-by: raiden00pl Assisted-by: Claude Code --- examples/nxterm/Kconfig | 18 ++++++++++ examples/nxterm/nxterm_internal.h | 3 ++ examples/nxterm/nxterm_listener.c | 8 ++++- examples/nxterm/nxterm_main.c | 55 +++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/examples/nxterm/Kconfig b/examples/nxterm/Kconfig index 0892f21891e..0b56c4dc193 100644 --- a/examples/nxterm/Kconfig +++ b/examples/nxterm/Kconfig @@ -76,6 +76,24 @@ config EXAMPLES_NXTERM_PTYCONSOLE NuttX keyboard special-key codes to VT100 input, and permits terminal generated signals such as Ctrl-C and Ctrl-Z. +config EXAMPLES_NXTERM_NSH_FALLBACK + bool "Fall back to serial NSH" + default n + depends on NSH_CONSOLE + ---help--- + Run NSH on the original system console if NX or NXTerm initialization + fails. This is useful on framebuffer systems where display startup + failures would otherwise leave no interactive console. + +config EXAMPLES_NXTERM_STARTUP_TIMEOUT + int "NX startup timeout" + default 5 + range 1 60 + depends on EXAMPLES_NXTERM_NSH_FALLBACK + ---help--- + Number of seconds to wait for the NX server to accept the client + connection before falling back to NSH on the system console. + config EXAMPLES_NXTERM_KBDDEV string "Keyboard device" default "/dev/kbda" diff --git a/examples/nxterm/nxterm_internal.h b/examples/nxterm/nxterm_internal.h index 181156e342d..0752a6b6d0e 100644 --- a/examples/nxterm/nxterm_internal.h +++ b/examples/nxterm/nxterm_internal.h @@ -219,6 +219,9 @@ struct nxterm_state_s { volatile bool haveres; /* True: Have screen resolution */ volatile bool connected; /* True: Connected to server */ +#ifdef CONFIG_EXAMPLES_NXTERM_NSH_FALLBACK + volatile bool servererr; /* True: Server connection failed */ +#endif sem_t eventsem; /* Control waiting for display events */ pid_t pid; /* Console task ID */ NXHANDLE hnx; /* The connection handler */ diff --git a/examples/nxterm/nxterm_listener.c b/examples/nxterm/nxterm_listener.c index b01328ab2b8..1fae70e8db1 100644 --- a/examples/nxterm/nxterm_listener.c +++ b/examples/nxterm/nxterm_listener.c @@ -48,7 +48,7 @@ FAR void *nxterm_listener(FAR void *arg) /* Process events forever */ - for (;;) + for (; ; ) { /* Handle the next event. If we were configured blocking, then * we will stay right here until the next event is received. Since @@ -66,7 +66,13 @@ FAR void *nxterm_listener(FAR void *arg) */ printf("nxterm_listener: Lost server connection: %d\n", errno); +#ifdef CONFIG_EXAMPLES_NXTERM_NSH_FALLBACK + g_nxterm_vars.servererr = true; + sem_post(&g_nxterm_vars.eventsem); + return NULL; +#else exit(EXIT_FAILURE); +#endif } /* If we received a message, we must be connected */ diff --git a/examples/nxterm/nxterm_main.c b/examples/nxterm/nxterm_main.c index 7d2e9767589..a2250eb4e81 100644 --- a/examples/nxterm/nxterm_main.c +++ b/examples/nxterm/nxterm_main.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,9 @@ struct nxterm_state_s g_nxterm_vars; static int nxterm_initialize(void) { struct sched_param param; +#ifdef CONFIG_EXAMPLES_NXTERM_NSH_FALLBACK + struct timespec abstime; +#endif pthread_t thread; int ret; @@ -142,6 +146,51 @@ static int nxterm_initialize(void) /* Don't return until we are connected to the server */ +#ifdef CONFIG_EXAMPLES_NXTERM_NSH_FALLBACK + pthread_detach(thread); + + ret = clock_gettime(CLOCK_REALTIME, &abstime); + if (ret < 0) + { + printf("nxterm_initialize: clock_gettime failed: %d\n", errno); + nx_disconnect(g_nxterm_vars.hnx); + g_nxterm_vars.hnx = NULL; + return ERROR; + } + + abstime.tv_sec += CONFIG_EXAMPLES_NXTERM_STARTUP_TIMEOUT; + while (!g_nxterm_vars.connected && !g_nxterm_vars.servererr) + { + /* Wait for the listener thread to report either a connection or + * a server error. The server starts asynchronously, so a timeout + * is also needed when display initialization fails before it can + * send the first event. + */ + + ret = sem_timedwait(&g_nxterm_vars.eventsem, &abstime); + if (ret < 0 && errno != EINTR) + { + if (errno == ETIMEDOUT) + { + printf("nxterm_initialize: NX startup timed out\n"); + } + else + { + printf("nxterm_initialize: sem_timedwait failed: %d\n", + errno); + } + + g_nxterm_vars.servererr = true; + } + } + + if (!g_nxterm_vars.connected) + { + nx_disconnect(g_nxterm_vars.hnx); + g_nxterm_vars.hnx = NULL; + return ERROR; + } +#else while (!g_nxterm_vars.connected) { /* Wait for the listener thread to wake us up when we really @@ -150,6 +199,7 @@ static int nxterm_initialize(void) sem_wait(&g_nxterm_vars.eventsem); } +#endif } else { @@ -418,5 +468,10 @@ int main(int argc, FAR char *argv[]) nx_disconnect(g_nxterm_vars.hnx); errout: +#ifdef CONFIG_EXAMPLES_NXTERM_NSH_FALLBACK + printf("nxterm_main: Falling back to the system console\n"); + return nsh_consolemain(argc, argv); +#else return EXIT_FAILURE; +#endif }