This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git


The following commit(s) were added to refs/heads/master by this push:
     new 2b92bedae examples/lvglterm: replace PIPES with PSEUDOTERM
2b92bedae is described below

commit 2b92bedaee0989995255ea4e51a7fc04963a0ae9
Author: Filipe Cavalcanti <[email protected]>
AuthorDate: Thu Aug 20 15:26:24 2026 -0300

    examples/lvglterm: replace PIPES with PSEUDOTERM
    
    The lvglterm terminal now runs the shell on a pseudo-terminal instead of 
three
    plain pipes.
    
    Signed-off-by: Filipe Cavalcanti <[email protected]>
---
 examples/lvglterm/Kconfig          |   6 ++
 examples/lvglterm/lvglterm.c       | 199 +++++++++++++++++++++++++++++--------
 examples/lvglterm/lvglterm.h       |  27 ++---
 examples/lvglterm/lvglterm_kbd.c   |  20 ++--
 examples/lvglterm/lvglterm_touch.c |  17 ++--
 5 files changed, 186 insertions(+), 83 deletions(-)

diff --git a/examples/lvglterm/Kconfig b/examples/lvglterm/Kconfig
index dba596b44..2f4c598f2 100644
--- a/examples/lvglterm/Kconfig
+++ b/examples/lvglterm/Kconfig
@@ -7,9 +7,15 @@ menuconfig EXAMPLES_LVGLTERM
        tristate "LVGL Terminal"
        default n
        depends on GRAPHICS_LVGL
+       select PSEUDOTERM
        ---help---
                Enable LVGL Terminal
 
+               The shell runs on a pseudo-terminal so that it, and the programs
+               it starts, see a console on their standard streams:  isatty() is
+               what tells an interactive program to prompt, echo and flush its
+               output line by line.
+
 if EXAMPLES_LVGLTERM
 
 choice
diff --git a/examples/lvglterm/lvglterm.c b/examples/lvglterm/lvglterm.c
index ad555cc55..b59271db5 100644
--- a/examples/lvglterm/lvglterm.c
+++ b/examples/lvglterm/lvglterm.c
@@ -24,11 +24,10 @@
  * "NuttX RTOS for PinePhone: LVGL Terminal for NSH Shell"
  * https://lupyuen.github.io/articles/terminal
  *
- * Code shared by both input variants: it starts the NSH shell with its
- * standard streams redirected through pipes, renders the shell output in an
- * LVGL text area, and delegates the input source to the selected variant
- * (on-screen keyboard in lvglterm_touch.c, physical keyboard in
- * lvglterm_kbd.c).
+ * Code shared by both input variants: it starts the NSH shell on a
+ * pseudo-terminal, renders the shell output in an LVGL text area, and
+ * delegates the input source to the selected variant (on-screen keyboard in
+ * lvglterm_touch.c, physical keyboard in lvglterm_kbd.c).
  */
 
 /****************************************************************************
@@ -37,14 +36,17 @@
 
 #include <nuttx/config.h>
 #include <sys/boardctl.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <time.h>
+#include <errno.h>
 #include <nuttx/debug.h>
 #include <poll.h>
+#include <pty.h>
 #include <spawn.h>
 #include <lvgl/lvgl.h>
 
@@ -56,10 +58,15 @@
 #  error posix_spawn() should be enabled in the configuration
 #endif
 
-/* NSH Redirection requires Pipes */
+/* The shell runs on a pseudo-terminal rather than on plain pipes because
+ * that is what makes its standard streams look like a console.  Interactive
+ * programs decide from isatty() whether to echo, to prompt and to
+ * line-buffer their output and the terminal driver is what echoes back what
+ * the user types.
+ */
 
-#ifndef CONFIG_DEV_PIPE_SIZE
-#  error FIFO and Named Pipe Drivers should be enabled in the configuration
+#ifndef CONFIG_PSEUDOTERM
+#  error Pseudo-Terminal (PTY) support should be enabled in the configuration
 #endif
 
 /* NSH Output requires a Monospaced Font.  The size is selectable so that
@@ -86,6 +93,16 @@
 
 #define TIMER_PERIOD_MS 20
 
+/* How many reads the output poll may perform on a single timer tick */
+
+#define MAX_READS_PER_TICK 16
+
+/* How many keystrokes may wait for the shell to consume them.  A shell line
+ * is shorter than this, and the shell truncates the over-long ones anyway.
+ */
+
+#define INPUT_QUEUE_SIZE 256
+
 /* Trim the output text area once it grows past this many characters */
 
 #define TERM_MAXCHARS  4096
@@ -108,6 +125,8 @@
  * Private Function Prototypes
  ****************************************************************************/
 
+static bool has_output(void);
+static void flush_input(void);
 static int create_widgets(void);
 static void timer_callback(lv_timer_t *timer);
 
@@ -115,10 +134,6 @@ static void timer_callback(lv_timer_t *timer);
  * Public Data
  ****************************************************************************/
 
-/* Pipe to NSH stdin, written by the selected input variant */
-
-int g_nsh_stdin[2];
-
 /* LVGL Column Container and NSH Output Text Area (shared with the variant) */
 
 lv_obj_t *g_col;
@@ -132,10 +147,16 @@ lv_style_t g_terminal_style;
  * Private Data
  ****************************************************************************/
 
-/* Pipes for NSH stdout and stderr */
+/* PTY master:  the terminal's end of the shell's console.  Keystrokes are
+ * written to it and the shell output is read from it.
+ */
+
+static int g_nsh_fd = -1;
 
-static int g_nsh_stdout[2];
-static int g_nsh_stderr[2];
+/* Keystrokes waiting for room in the terminal (see lvglterm_send_input) */
+
+static char g_input_queue[INPUT_QUEUE_SIZE];
+static int g_input_queued;
 
 /* LVGL Timer for polling NSH Output */
 
@@ -149,26 +170,104 @@ static char * const g_nsh_argv[] =
 };
 
 /****************************************************************************
- * Public Functions
+ * Private Functions
  ****************************************************************************/
 
 /****************************************************************************
- * Name: lvglterm_has_input
+ * Name: has_output
  *
  * Description:
- *   Return true if the file descriptor has data to be read.
+ *   Return true if the shell has produced output that can be read without
+ *   blocking.
  *
  ****************************************************************************/
 
-bool lvglterm_has_input(int fd)
+static bool has_output(void)
 {
   struct pollfd fdp;
 
-  fdp.fd     = fd;
+  fdp.fd     = g_nsh_fd;
   fdp.events = POLLIN;
   return poll(&fdp, 1, 0) > 0 && (fdp.revents & POLLIN) != 0;
 }
 
+/****************************************************************************
+ * Name: flush_input
+ *
+ * Description:
+ *   Hand the queued keystrokes to the terminal, but no more of them than it
+ *   can take right now:  a write that has to wait for room blocks the LVGL
+ *   thread, and that deadlocks the terminal, since the shell stops reading
+ *   as soon as the echo it produces has filled the output buffer that only
+ *   this thread drains.  Runs in the LVGL thread.
+ *
+ ****************************************************************************/
+
+static void flush_input(void)
+{
+  int nwritten;
+  int space;
+
+  if (g_input_queued == 0)
+    {
+      return;
+    }
+
+  if (ioctl(g_nsh_fd, FIONSPACE, &space) < 0 || space <= 0)
+    {
+      return;
+    }
+
+  if (space > g_input_queued)
+    {
+      space = g_input_queued;
+    }
+
+  nwritten = write(g_nsh_fd, g_input_queue, space);
+  if (nwritten <= 0)
+    {
+      return;
+    }
+
+  g_input_queued -= nwritten;
+  memmove(g_input_queue, g_input_queue + nwritten, g_input_queued);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: lvglterm_send_input
+ *
+ * Description:
+ *   Send keystrokes to the shell.  Whatever the terminal cannot take yet is
+ *   queued and handed over by the terminal's periodic timer.  Must run in
+ *   the LVGL thread.
+ *
+ ****************************************************************************/
+
+void lvglterm_send_input(FAR const char *buf, int len)
+{
+  int room = INPUT_QUEUE_SIZE - g_input_queued;
+
+  if (len > room)
+    {
+      /* The queue drains no faster than the shell reads.  Dropping what does
+       * not fit keeps the terminal responsive, and a line that long would be
+       * truncated by the shell in any case.
+       */
+
+      gwarn("WARNING: dropping %d input bytes\n", len - room);
+      len = room;
+    }
+
+  memcpy(g_input_queue + g_input_queued, buf, len);
+  g_input_queued += len;
+
+  flush_input();
+}
+
 /****************************************************************************
  * Name: lvglterm_add_output
  *
@@ -297,9 +396,8 @@ static int create_widgets(void)
  * Name: create_terminal
  *
  * Description:
- *   Start the NSH shell with its streams redirected to pipes, create the
- *   shared widgets and the output-polling timer, and set up the input
- *   variant.
+ *   Start the NSH shell on a pseudo-terminal, create the shared widgets and
+ *   the output-polling timer, and set up the input variant.
  *
  ****************************************************************************/
 
@@ -307,25 +405,31 @@ static int create_terminal(int argc, FAR char *argv[])
 {
   int ret;
   pid_t pid;
+  int slave;
 
-  /* Create the pipes for NSH Shell: stdin, stdout and stderr */
+  /* Create the pseudo-terminal.  The slave keeps the driver defaults, ECHO
+   * and the \n -> \r\n output translation among them, which is what a
+   * console provides and what the shell's line editor expects.
+   */
 
-  if (pipe(g_nsh_stdin) < 0 || pipe(g_nsh_stdout) < 0 ||
-      pipe(g_nsh_stderr) < 0)
+  if (openpty(&g_nsh_fd, &slave, NULL, NULL, NULL) < 0)
     {
-      fprintf(stderr, "pipe failed: %d\n", errno);
+      fprintf(stderr, "openpty failed: %d\n", errno);
       return ERROR;
     }
 
-  /* Close default stdin, stdout and stderr and assign the new pipes */
+  /* Close default stdin, stdout and stderr and assign the terminal slave,
+   * which is the shell's console once it is spawned below.
+   */
 
   close(0);
   close(1);
   close(2);
 
-  dup2(g_nsh_stdin[READ_PIPE], 0);
-  dup2(g_nsh_stdout[WRITE_PIPE], 1);
-  dup2(g_nsh_stderr[WRITE_PIPE], 2);
+  dup2(slave, 0);
+  dup2(slave, 1);
+  dup2(slave, 2);
+  close(slave);
 
   /* Start the NSH Shell and inherit stdin, stdout and stderr */
 
@@ -358,7 +462,7 @@ static int create_terminal(int argc, FAR char *argv[])
  * Name: timer_callback
  *
  * Description:
- *   Poll NSH stdout and stderr for output and render it, then let the input
+ *   Poll the terminal for shell output and render it, then let the input
  *   variant perform its periodic work.  Runs in the LVGL thread.
  *
  ****************************************************************************/
@@ -366,30 +470,37 @@ static int create_terminal(int argc, FAR char *argv[])
 static void timer_callback(lv_timer_t *timer)
 {
   static char buf[64];
+  int reads;
   int ret;
 
-  /* Drain the input variant first (local echo, scroll) so that what the user
-   * typed is rendered before the resulting shell output.
+  /* Collect the keystrokes and hand them over first, so that what the user
+   * typed is rendered (the terminal echoes it back to us) before the
+   * resulting shell output.
    */
 
   lvglterm_input_poll();
+  flush_input();
+
+  /* Drain what the shell has produced.  A writer blocks once it has filled
+   * the terminal buffer, so read as long as there is something to read, but
+   * keep a cap:  a program producing output faster than the screen can take
+   * it must not keep the LVGL thread out of lv_timer_handler().
+   */
 
-  if (lvglterm_has_input(g_nsh_stdout[READ_PIPE]))
+  for (reads = 0; reads < MAX_READS_PER_TICK; reads++)
     {
-      ret = read(g_nsh_stdout[READ_PIPE], buf, sizeof(buf));
-      if (ret > 0)
+      if (!has_output())
         {
-          lvglterm_add_output(buf, ret);
+          break;
         }
-    }
 
-  if (lvglterm_has_input(g_nsh_stderr[READ_PIPE]))
-    {
-      ret = read(g_nsh_stderr[READ_PIPE], buf, sizeof(buf));
-      if (ret > 0)
+      ret = read(g_nsh_fd, buf, sizeof(buf));
+      if (ret <= 0)
         {
-          lvglterm_add_output(buf, ret);
+          break;
         }
+
+      lvglterm_add_output(buf, ret);
     }
 }
 
diff --git a/examples/lvglterm/lvglterm.h b/examples/lvglterm/lvglterm.h
index 627919cdd..684e73327 100644
--- a/examples/lvglterm/lvglterm.h
+++ b/examples/lvglterm/lvglterm.h
@@ -31,25 +31,15 @@
 #include <stdbool.h>
 #include <lvgl/lvgl.h>
 
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
-
-/* Read and Write ends of the NSH pipes */
-
-#define READ_PIPE  0
-#define WRITE_PIPE 1
-
 /****************************************************************************
  * Public Data
  ****************************************************************************/
 
-/* Shared state owned by the core (lvglterm.c).  The input variant writes the
- * shell input to g_nsh_stdin, adds its widgets under g_col (styled with
- * g_terminal_style) and may render into g_output.
+/* Shared state owned by the core (lvglterm.c).  The input variant adds its
+ * widgets under g_col (styled with g_terminal_style) and may render into
+ * g_output.
  */
 
-extern int g_nsh_stdin[2];          /* Input variant -> NSH stdin */
 extern lv_obj_t *g_col;             /* Column container (widget parent) */
 extern lv_obj_t *g_output;          /* NSH output text area */
 extern lv_style_t g_terminal_style; /* Monospaced font style */
@@ -58,15 +48,18 @@ extern lv_style_t g_terminal_style; /* Monospaced font 
style */
  * Public Function Prototypes
  ****************************************************************************/
 
-/* Shared helpers provided by the core */
+/* Shared helpers provided by the core.  Note that the terminal echoes the
+ * keystrokes back as the shell reads them, so an input variant must not echo
+ * what it sends a second time.
+ */
 
-bool lvglterm_has_input(int fd);
+void lvglterm_send_input(FAR const char *buf, int len);
 void lvglterm_add_output(FAR const char *buf, int len);
 
 /* Provided by the selected input variant (lvglterm_touch.c or
  * lvglterm_kbd.c).  lvglterm_input_create() sets up the input source under
- * g_col and wires it to g_nsh_stdin.  lvglterm_input_poll() runs on every
- * LVGL timer tick (LVGL thread) for periodic work; it may be a no-op.
+ * g_col.  lvglterm_input_poll() runs on every LVGL timer tick (LVGL thread)
+ * for periodic work; it may be a no-op.
  */
 
 void lvglterm_input_create(int argc, FAR char *argv[]);
diff --git a/examples/lvglterm/lvglterm_kbd.c b/examples/lvglterm/lvglterm_kbd.c
index daea4c50b..9ebc65c62 100644
--- a/examples/lvglterm/lvglterm_kbd.c
+++ b/examples/lvglterm/lvglterm_kbd.c
@@ -80,11 +80,10 @@ static FAR const char *g_kbddev;   /* Keyboard device path 
(/dev/kbdN) */
  * Name: feed_char
  *
  * Description:
- *   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.
+ *   Forward one character to the shell.  Nothing is echoed here:  the
+ *   terminal echoes the printable characters as the shell reads them and the
+ *   shell's own line editor deals with the control keys, so echoing again
+ *   would show everything twice.  Carriage return is normalised to newline.
  *
  ****************************************************************************/
 
@@ -95,12 +94,7 @@ static void feed_char(char ch)
       ch = '\n';
     }
 
-  write(g_nsh_stdin[WRITE_PIPE], &ch, 1);
-
-  if (ch == '\n' || ((uint8_t)ch >= 0x20 && (uint8_t)ch < 0x7f))
-    {
-      lvglterm_add_output(&ch, 1);
-    }
+  lvglterm_send_input(&ch, 1);
 }
 
 /****************************************************************************
@@ -184,8 +178,8 @@ void lvglterm_input_create(int argc, FAR char *argv[])
  *
  * Description:
  *   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.
+ *   to the shell.  Runs in the LVGL thread from the terminal's periodic
+ *   timer, so the read must not block.
  *
  ****************************************************************************/
 
diff --git a/examples/lvglterm/lvglterm_touch.c 
b/examples/lvglterm/lvglterm_touch.c
index 4285c97e6..8610d4010 100644
--- a/examples/lvglterm/lvglterm_touch.c
+++ b/examples/lvglterm/lvglterm_touch.c
@@ -67,7 +67,6 @@ static lv_obj_t *g_kb;
 
 static void input_callback(lv_event_t *e)
 {
-  int ret;
   const lv_event_code_t code = lv_event_get_code(e);
 
   if (code == LV_EVENT_VALUE_CHANGED)
@@ -94,21 +93,21 @@ static void input_callback(lv_event_t *e)
               return;
             }
 
-          /* Echo the command on the output so the prompt line reads
-           * "nsh> <command>", then send it to the shell.
+          /* Send the command to the shell.  The terminal echoes it back as
+           * the shell reads it, which is what puts it on the prompt line as
+           * "nsh> <command>", so nothing is echoed here.
            */
 
           len = strlen(cmd);
-          lvglterm_add_output(cmd, len);
+          lvglterm_send_input(cmd, len);
+
+          /* The shell acts on the line only once it is terminated */
+
           if (cmd[len - 1] != '\n')
             {
-              lvglterm_add_output("\n", 1);
+              lvglterm_send_input("\n", 1);
             }
 
-          DEBUGASSERT(g_nsh_stdin[WRITE_PIPE] != 0);
-          ret = write(g_nsh_stdin[WRITE_PIPE], cmd, len);
-          DEBUGASSERT(ret == len);
-
           lv_textarea_set_text(g_input, "");
         }
     }

Reply via email to