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

acassis 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 59d3a0d0a examples/lvglterm: support physical keyboard input
59d3a0d0a is described below

commit 59d3a0d0ae74f7c2cc7e2877158c0ffa55129d09
Author: Jorge Guzman <[email protected]>
AuthorDate: Thu Jul 2 12:40:58 2026 -0300

    examples/lvglterm: support physical keyboard input
    
    Refactor the LVGL terminal into a shared core (lvglterm.c) plus two
    selectable input variants: the existing on-screen touch keyboard
    (lvglterm_touch.c) and a new physical keyboard variant (lvglterm_kbd.c) that
    reads key events from a /dev/kbdN device and streams them to the shell.
    
    The input source is chosen with a Kconfig choice (touch by default, keeping
    the previous behaviour).  The keyboard variant reads its device from
    CONFIG_EXAMPLES_LVGLTERM_KBD_DEV (default /dev/kbd0), overridable by the 
first
    command-line argument, and scrolls the output with the Fn cursor keys.  A 
font
    choice (UNSCII 8 or 16) is added for low-resolution displays.
    
    Signed-off-by: Jorge Guzman <[email protected]>
---
 examples/lvglterm/CMakeLists.txt   |   9 +-
 examples/lvglterm/Kconfig          |  50 ++++
 examples/lvglterm/Makefile         |   9 +-
 examples/lvglterm/lvglterm.c       | 509 ++++++++++++++-----------------------
 examples/lvglterm/lvglterm.h       |  75 ++++++
 examples/lvglterm/lvglterm_kbd.c   | 228 +++++++++++++++++
 examples/lvglterm/lvglterm_touch.c | 164 ++++++++++++
 7 files changed, 719 insertions(+), 325 deletions(-)

diff --git a/examples/lvglterm/CMakeLists.txt b/examples/lvglterm/CMakeLists.txt
index fe6d3c48c..22650f82f 100644
--- a/examples/lvglterm/CMakeLists.txt
+++ b/examples/lvglterm/CMakeLists.txt
@@ -21,6 +21,12 @@
 # 
##############################################################################
 
 if(CONFIG_EXAMPLES_LVGLTERM)
+  if(CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD)
+    set(lvglterm_input lvglterm_kbd.c)
+  else()
+    set(lvglterm_input lvglterm_touch.c)
+  endif()
+
   nuttx_add_application(
     NAME
     lvglterm
@@ -31,5 +37,6 @@ if(CONFIG_EXAMPLES_LVGLTERM)
     MODULE
     ${CONFIG_EXAMPLES_LVGLTERM}
     SRCS
-    lvglterm.c)
+    lvglterm.c
+    ${lvglterm_input})
 endif()
diff --git a/examples/lvglterm/Kconfig b/examples/lvglterm/Kconfig
index dec9d9b24..718af98b6 100644
--- a/examples/lvglterm/Kconfig
+++ b/examples/lvglterm/Kconfig
@@ -12,6 +12,56 @@ menuconfig EXAMPLES_LVGLTERM
 
 if EXAMPLES_LVGLTERM
 
+choice
+       prompt "LVGL Terminal input source"
+       default EXAMPLES_LVGLTERM_INPUT_TOUCH
+       ---help---
+               Select how the terminal receives keystrokes.  Only one input 
source
+               is built at a time.
+
+config EXAMPLES_LVGLTERM_INPUT_TOUCH
+       bool "On-screen keyboard (touch)"
+       ---help---
+               Input comes from an on-screen LVGL keyboard operated by touch; a
+               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)"
+       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.
+
+endchoice
+
+config EXAMPLES_LVGLTERM_KBD_DEV
+       string "Keyboard device path"
+       default "/dev/kbd0"
+       depends on EXAMPLES_LVGLTERM_INPUT_KBD
+       ---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).
+
+choice
+       prompt "LVGL Terminal font"
+       default EXAMPLES_LVGLTERM_FONT_UNSCII_16
+       ---help---
+               Monospaced font used to render the terminal.  Use the smaller 
UNSCII 8
+               on low-resolution displays where UNSCII 16 shows too few 
columns.
+
+config EXAMPLES_LVGLTERM_FONT_UNSCII_8
+       bool "UNSCII 8 (small)"
+       select LV_FONT_UNSCII_8
+
+config EXAMPLES_LVGLTERM_FONT_UNSCII_16
+       bool "UNSCII 16"
+       select LV_FONT_UNSCII_16
+
+endchoice
+
 config EXAMPLES_LVGLTERM_PRIORITY
        int "lvglterm task priority"
        default 100
diff --git a/examples/lvglterm/Makefile b/examples/lvglterm/Makefile
index 531d8c405..314351f9a 100644
--- a/examples/lvglterm/Makefile
+++ b/examples/lvglterm/Makefile
@@ -27,8 +27,15 @@ PRIORITY = $(CONFIG_EXAMPLES_LVGLTERM_PRIORITY)
 STACKSIZE = $(CONFIG_EXAMPLES_LVGLTERM_STACKSIZE)
 MODULE = $(CONFIG_EXAMPLES_LVGLTERM)
 
-# LVGL Terminal
+# LVGL Terminal.  lvglterm.c holds the shared core; the input source is
+# selected at build time (on-screen keyboard for touch, /dev/kbdN otherwise).
 
 MAINSRC = lvglterm.c
 
+ifeq ($(CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD),y)
+CSRCS = lvglterm_kbd.c
+else
+CSRCS = lvglterm_touch.c
+endif
+
 include $(APPDIR)/Application.mk
diff --git a/examples/lvglterm/lvglterm.c b/examples/lvglterm/lvglterm.c
index f1ef2259f..0814e9afe 100644
--- a/examples/lvglterm/lvglterm.c
+++ b/examples/lvglterm/lvglterm.c
@@ -23,6 +23,12 @@
 /* Reference:
  * "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).
  */
 
 /****************************************************************************
@@ -35,12 +41,15 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
 #include <time.h>
 #include <nuttx/debug.h>
 #include <poll.h>
 #include <spawn.h>
 #include <lvgl/lvgl.h>
 
+#include "lvglterm.h"
+
 /* NSH Task requires posix_spawn() */
 
 #ifndef CONFIG_LIBC_EXECFUNCS
@@ -53,10 +62,20 @@
 #  error FIFO and Named Pipe Drivers should be enabled in the configuration
 #endif
 
-/* NSH Output requires a Monospaced Font */
+/* NSH Output requires a Monospaced Font.  The size is selectable so that
+ * low-resolution displays can use the smaller UNSCII 8.
+ */
 
-#ifndef CONFIG_LV_FONT_UNSCII_16
-#  error LVGL Font UNSCII 16 should be enabled in the configuration
+#if defined(CONFIG_EXAMPLES_LVGLTERM_FONT_UNSCII_8)
+#  ifndef CONFIG_LV_FONT_UNSCII_8
+#    error LVGL Font UNSCII 8 should be enabled in the configuration
+#  endif
+#  define LVGLTERM_FONT lv_font_unscii_8
+#else
+#  ifndef CONFIG_LV_FONT_UNSCII_16
+#    error LVGL Font UNSCII 16 should be enabled in the configuration
+#  endif
+#  define LVGLTERM_FONT lv_font_unscii_16
 #endif
 
 /****************************************************************************
@@ -65,12 +84,12 @@
 
 /* How often to poll for output from NSH Shell (milliseconds) */
 
-#define TIMER_PERIOD_MS 100
+#define TIMER_PERIOD_MS 20
 
-/* Read and Write Pipes for NSH stdin, stdout and stderr */
+/* Trim the output text area once it grows past this many characters */
 
-#define READ_PIPE  0
-#define WRITE_PIPE 1
+#define TERM_MAXCHARS  4096
+#define TERM_KEEPCHARS 3072
 
 /* NSH Task to be started */
 
@@ -81,37 +100,33 @@
  ****************************************************************************/
 
 static int create_widgets(void);
-static void timer_callback(lv_timer_t * timer);
-static void input_callback(lv_event_t * e);
-static bool has_input(int fd);
-static void remove_escape_codes(char *buf, int len);
+static void timer_callback(lv_timer_t *timer);
 
 /****************************************************************************
- * Private Data
+ * Public Data
  ****************************************************************************/
 
-/* Pipes for NSH Shell: stdin, stdout, stderr */
-
-static int g_nsh_stdin[2];
-static int g_nsh_stdout[2];
-static int g_nsh_stderr[2];
+/* Pipe to NSH stdin, written by the selected input variant */
 
-/* LVGL Column Container for NSH Widgets */
+int g_nsh_stdin[2];
 
-static lv_obj_t *g_col;
+/* LVGL Column Container and NSH Output Text Area (shared with the variant) */
 
-/* LVGL Text Area Widgets for NSH Input and Output */
+lv_obj_t *g_col;
+lv_obj_t *g_output;
 
-static lv_obj_t *g_input;
-static lv_obj_t *g_output;
+/* LVGL Font Style for NSH Input and Output */
 
-/* LVGL Keyboard Widget for NSH Terminal */
+lv_style_t g_terminal_style;
 
-static lv_obj_t *g_kb;
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
 
-/* LVGL Font Style for NSH Input and Output */
+/* Pipes for NSH stdout and stderr */
 
-static lv_style_t g_terminal_style;
+static int g_nsh_stdout[2];
+static int g_nsh_stderr[2];
 
 /* LVGL Timer for polling NSH Output */
 
@@ -125,108 +140,121 @@ static char * const g_nsh_argv[] =
 };
 
 /****************************************************************************
- * Private Functions
+ * Public Functions
  ****************************************************************************/
 
 /****************************************************************************
- * Name: create_terminal
+ * Name: lvglterm_has_input
  *
  * Description:
- *   Create the LVGL Terminal. Start the NSH Shell and redirect the NSH
- *   stdin, stdout and stderr to the LVGL Widgets.
- *
- * Input Parameters:
- *   None
- *
- * Returned Value:
- *   Zero (OK) on success; a negated errno value is returned on any failure.
+ *   Return true if the file descriptor has data to be read.
  *
  ****************************************************************************/
 
-static int create_terminal(void)
+bool lvglterm_has_input(int fd)
 {
-  int ret;
-  pid_t pid;
+  struct pollfd fdp;
 
-  /* Create the pipes for NSH Shell: stdin, stdout and stderr */
+  fdp.fd     = fd;
+  fdp.events = POLLIN;
+  return poll(&fdp, 1, 0) > 0 && (fdp.revents & POLLIN) != 0;
+}
 
-  ret = pipe(g_nsh_stdin);
-  if (ret < 0)
-    {
-      _err("stdin pipe failed: %d\n", errno);
-      return ERROR;
-    }
+/****************************************************************************
+ * Name: lvglterm_add_output
+ *
+ * Description:
+ *   Append text to the NSH output text area, dropping VT100 escape sequences
+ *   and carriage returns, honouring backspace, and trimming the buffer once
+ *   it grows too large.  Must run in the LVGL thread.
+ *
+ ****************************************************************************/
 
-  ret = pipe(g_nsh_stdout);
-  if (ret < 0)
-    {
-      _err("stdout pipe failed: %d\n", errno);
-      return ERROR;
-    }
+void lvglterm_add_output(FAR const char *buf, int len)
+{
+  char clean[64];
+  int  ci = 0;
+  int  i;
 
-  ret = pipe(g_nsh_stderr);
-  if (ret < 0)
+  for (i = 0; i < len; i++)
     {
-      _err("stderr pipe failed: %d\n", errno);
-      return ERROR;
-    }
+      char c = buf[i];
 
-  /* Close default stdin, stdout and stderr */
+      if (c == 0x1b)                /* ESC: skip the escape sequence */
+        {
+          i++;
+          if (i < len && buf[i] == '[')
+            {
+              for (i++; i < len; i++)
+                {
+                  if ((buf[i] >= 'A' && buf[i] <= 'Z') ||
+                      (buf[i] >= 'a' && buf[i] <= 'z'))
+                    {
+                      break;
+                    }
+                }
+            }
 
-  close(0);
-  close(1);
-  close(2);
+          continue;
+        }
 
-  /* Assign the new pipes as stdin, stdout and stderr */
+      if (c == '\r')                /* Drop carriage return */
+        {
+          continue;
+        }
 
-  dup2(g_nsh_stdin[READ_PIPE], 0);
-  dup2(g_nsh_stdout[WRITE_PIPE], 1);
-  dup2(g_nsh_stderr[WRITE_PIPE], 2);
+      if (c == 0x08 || c == 0x7f)   /* Backspace/DEL: erase last character */
+        {
+          if (ci > 0)
+            {
+              clean[ci] = '\0';
+              lv_textarea_add_text(g_output, clean);
+              ci = 0;
+            }
 
-  /* Start the NSH Shell and inherit stdin, stdout and stderr */
+          lv_textarea_delete_char(g_output);
+          continue;
+        }
 
-  ret = posix_spawn(&pid,        /* Returned Task ID */
-                    NSH_TASK,    /* NSH Path */
-                    NULL,        /* Inherit stdin, stdout and stderr */
-                    NULL,        /* Default spawn attributes */
-                    g_nsh_argv,  /* Arguments */
-                    NULL);       /* No environment */
-  if (ret < 0)
-    {
-      int errcode = errno;
-      _err("posix_spawn failed: %d\n", errcode);
-      return -errcode;
+      if (c == '\n' || c == '\t' || (c >= 0x20 && c < 0x7f))
+        {
+          clean[ci++] = c;
+          if (ci >= (int)sizeof(clean) - 1)
+            {
+              clean[ci] = '\0';
+              lv_textarea_add_text(g_output, clean);
+              ci = 0;
+            }
+        }
     }
 
-  /* Create an LVGL Timer to poll for output from NSH Shell */
-
-  g_timer = lv_timer_create(timer_callback,  /* Callback Function */
-                            TIMER_PERIOD_MS, /* Timer Period (millisec) */
-                            NULL);           /* Callback Argument */
-  DEBUGASSERT(g_timer != NULL);
+  if (ci > 0)
+    {
+      clean[ci] = '\0';
+      lv_textarea_add_text(g_output, clean);
+    }
 
-  /* Create the LVGL Terminal Widgets */
+  /* Trim the text area if it has grown too large */
 
-  ret = create_widgets();
-  if (ret < 0)
+  if ((int)strlen(lv_textarea_get_text(g_output)) > TERM_MAXCHARS)
     {
-      return ret;
+      FAR const char *txt = lv_textarea_get_text(g_output);
+      int total = strlen(txt);
+      lv_textarea_set_text(g_output, txt + (total - TERM_KEEPCHARS));
+      lv_textarea_set_cursor_pos(g_output, LV_TEXTAREA_CURSOR_LAST);
     }
-
-  return OK;
 }
 
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
 /****************************************************************************
  * Name: create_widgets
  *
  * Description:
- *   Create the LVGL Widgets for LVGL Terminal.
- *
- * Input Parameters:
- *   None
- *
- * Returned Value:
- *   Zero (OK) on success; a negated errno value is returned on any failure.
+ *   Create the shared LVGL widgets: a column container and the NSH output
+ *   text area.  The input source is added by the selected variant.
  *
  ****************************************************************************/
 
@@ -235,7 +263,7 @@ static int create_widgets(void)
   /* Set the Font Style for NSH Input and Output to a Monospaced Font */
 
   lv_style_init(&g_terminal_style);
-  lv_style_set_text_font(&g_terminal_style, &lv_font_unscii_16);
+  lv_style_set_text_font(&g_terminal_style, &LVGLTERM_FONT);
 
   /* Create an LVGL Container with Column Flex Direction */
 
@@ -243,7 +271,7 @@ static int create_widgets(void)
   DEBUGASSERT(g_col != NULL);
   lv_obj_set_size(g_col, LV_PCT(100), LV_PCT(100));
   lv_obj_set_flex_flow(g_col, LV_FLEX_FLOW_COLUMN);
-  lv_obj_set_style_pad_all(g_col, 0, 0);  /* No padding */
+  lv_obj_set_style_pad_all(g_col, 0, 0);
 
   /* Create an LVGL Text Area Widget for NSH Output */
 
@@ -251,254 +279,107 @@ static int create_widgets(void)
   DEBUGASSERT(g_output != NULL);
   lv_obj_add_style(g_output, &g_terminal_style, 0);
   lv_obj_set_width(g_output, LV_PCT(100));
-  lv_obj_set_flex_grow(g_output, 1);  /* Fill the column */
-
-  /* Create an LVGL Text Area Widget for NSH Input */
-
-  g_input = lv_textarea_create(g_col);
-  DEBUGASSERT(g_input != NULL);
-  lv_obj_add_style(g_input, &g_terminal_style, 0);
-  lv_obj_set_size(g_input, LV_PCT(100), LV_SIZE_CONTENT);
-
-  /* Create an LVGL Keyboard Widget */
-
-  g_kb = lv_keyboard_create(g_col);
-  DEBUGASSERT(g_kb != NULL);
-  lv_obj_set_style_pad_all(g_kb, 0, 0);  /* No padding */
-
-  /* Register the Callback Function for NSH Input */
-
-  lv_obj_add_event_cb(g_input, input_callback, LV_EVENT_ALL, NULL);
-
-  /* Set the Keyboard to populate the NSH Input Text Area */
-
-  lv_keyboard_set_textarea(g_kb, g_input);
+  lv_obj_set_flex_grow(g_output, 1);
 
   return OK;
 }
 
 /****************************************************************************
- * Name: timer_callback
+ * Name: create_terminal
  *
  * Description:
- *   Callback Function for LVGL Timer. Poll NSH stdout and stderr for output
- *   and display the output.
- *
- * Input Parameters:
- *   timer - LVGL Timer for the callback
- *
- * Returned Value:
- *   None
+ *   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.
  *
  ****************************************************************************/
 
-static void timer_callback(lv_timer_t *timer)
+static int create_terminal(int argc, FAR char *argv[])
 {
   int ret;
-  static char buf[64];
-
-  DEBUGASSERT(g_nsh_stdout[READ_PIPE] != 0);
-  DEBUGASSERT(g_nsh_stderr[READ_PIPE] != 0);
+  pid_t pid;
 
-  /* Poll NSH stdout to check if there's output to be processed */
+  /* Create the pipes for NSH Shell: stdin, stdout and stderr */
 
-  if (has_input(g_nsh_stdout[READ_PIPE]))
+  if (pipe(g_nsh_stdin) < 0 || pipe(g_nsh_stdout) < 0 ||
+      pipe(g_nsh_stderr) < 0)
     {
-      /* Read the output from NSH stdout */
-
-      ret = read(g_nsh_stdout[READ_PIPE], buf, sizeof(buf) - 1);
-      if (ret > 0)
-        {
-          /* Add to NSH Output Text Area */
-
-          buf[ret] = 0;
-          remove_escape_codes(buf, ret);
-
-          DEBUGASSERT(g_output != NULL);
-          lv_textarea_add_text(g_output, buf);
-        }
+      _err("pipe failed: %d\n", errno);
+      return ERROR;
     }
 
-  /* Poll NSH stderr to check if there's output to be processed */
+  /* Close default stdin, stdout and stderr and assign the new pipes */
 
-  if (has_input(g_nsh_stderr[READ_PIPE]))
-    {
-      /* Read the output from NSH stderr */
+  close(0);
+  close(1);
+  close(2);
 
-      ret = read(g_nsh_stderr[READ_PIPE], buf, sizeof(buf) - 1);
-      if (ret > 0)
-        {
-          /* Add to NSH Output Text Area */
+  dup2(g_nsh_stdin[READ_PIPE], 0);
+  dup2(g_nsh_stdout[WRITE_PIPE], 1);
+  dup2(g_nsh_stderr[WRITE_PIPE], 2);
 
-          buf[ret] = 0;
-          remove_escape_codes(buf, ret);
+  /* Start the NSH Shell and inherit stdin, stdout and stderr */
 
-          DEBUGASSERT(g_output != NULL);
-          lv_textarea_add_text(g_output, buf);
-        }
+  ret = posix_spawn(&pid, NSH_TASK, NULL, NULL, g_nsh_argv, NULL);
+  if (ret < 0)
+    {
+      int errcode = errno;
+      _err("posix_spawn failed: %d\n", errcode);
+      return -errcode;
     }
-}
 
-/****************************************************************************
- * Name: input_callback
- *
- * Description:
- *   Callback Function for NSH Input Text Area. If Enter Key was pressed,
- *   send the NSH Input Command to NSH stdin.
- *
- * Input Parameters:
- *   e - LVGL Event for the callback
- *
- * Returned Value:
- *   None
- *
- ****************************************************************************/
-
-static void input_callback(lv_event_t *e)
-{
-  int ret;
-
-  /* Decode the LVGL Event */
+  /* Create an LVGL Timer to poll for output from NSH Shell */
 
-  const lv_event_code_t code = lv_event_get_code(e);
+  g_timer = lv_timer_create(timer_callback, TIMER_PERIOD_MS, NULL);
+  DEBUGASSERT(g_timer != NULL);
 
-  /* If NSH Input Text Area has changed, get the Key Pressed */
+  /* Create the shared widgets and the input source for the variant */
 
-  if (code == LV_EVENT_VALUE_CHANGED)
+  ret = create_widgets();
+  if (ret < 0)
     {
-      /* Get the Button Index of the Keyboard Button Pressed */
-
-      const uint16_t id = lv_keyboard_get_selected_button(g_kb);
-
-      /* Get the Text of the Keyboard Button */
-
-      const char *key = lv_keyboard_get_button_text(g_kb, id);
-      if (key == NULL)
-        {
-          return;
-        }
-
-      /* If Key Pressed is Enter, send the Command to NSH stdin */
-
-      if (key[0] == 0xef && key[1] == 0xa2 && key[2] == 0xa2)
-        {
-          /* Read the NSH Input */
-
-          const char *cmd;
-          DEBUGASSERT(g_input != NULL);
-          cmd = lv_textarea_get_text(g_input);
-          if (cmd == NULL || cmd[0] == 0)
-            {
-              return;
-            }
-
-          /* Send the Command to NSH stdin */
-
-          DEBUGASSERT(g_nsh_stdin[WRITE_PIPE] != 0);
-          ret = write(g_nsh_stdin[WRITE_PIPE], cmd, strlen(cmd));
-          DEBUGASSERT(ret == strlen(cmd));
-
-          /* Erase the NSH Input */
-
-          lv_textarea_set_text(g_input, "");
-        }
+      return ret;
     }
+
+  lvglterm_input_create(argc, argv);
+  return OK;
 }
 
 /****************************************************************************
- * Name: has_input
+ * Name: timer_callback
  *
  * Description:
- *   Return true if the File Descriptor has data to be read.
- *
- * Input Parameters:
- *   fd - File Descriptor to be checked
- *
- * Returned Value:
- *   True if File Descriptor has data to be read; False otherwise
+ *   Poll NSH stdout and stderr for output and render it, then let the input
+ *   variant perform its periodic work.  Runs in the LVGL thread.
  *
  ****************************************************************************/
 
-static bool has_input(int fd)
+static void timer_callback(lv_timer_t *timer)
 {
+  static char buf[64];
   int ret;
 
-  /* Poll the File Descriptor for input */
+  /* Drain the input variant first (local echo, scroll) so that what the user
+   * typed is rendered before the resulting shell output.
+   */
 
-  struct pollfd fdp;
-  fdp.fd = fd;
-  fdp.events = POLLIN;
-  ret = poll(&fdp,  /* File Descriptors */
-             1,     /* Number of File Descriptors */
-             0);    /* Poll Timeout (Milliseconds) */
+  lvglterm_input_poll();
 
-  if (ret > 0)
+  if (lvglterm_has_input(g_nsh_stdout[READ_PIPE]))
     {
-      /* If poll is OK and there is input */
-
-      if ((fdp.revents & POLLIN) != 0)
+      ret = read(g_nsh_stdout[READ_PIPE], buf, sizeof(buf));
+      if (ret > 0)
         {
-          /* Report that there is input */
-
-          return true;
+          lvglterm_add_output(buf, ret);
         }
-
-      /* Else report no input */
-
-      return false;
     }
-  else if (ret == 0)
-    {
-      /* If timeout, report no input */
-
-      return false;
-    }
-  else if (ret < 0)
-    {
-      /* Handle error */
-
-      _err("poll failed: %d, fd=%d\n", ret, fd);
-      return false;
-    }
-
-  /* Never comes here */
-
-  DEBUGPANIC();
-  return false;
-}
-
-/****************************************************************************
- * Name: remove_escape_codes
- *
- * Description:
- *   Remove ANSI Escape Codes from the string. Assumes that buf[len] is 0.
- *
- * Input Parameters:
- *   buf - String to be processed
- *   len - Length of string
- *
- * Returned Value:
- *   None
- *
- ****************************************************************************/
 
-static void remove_escape_codes(char *buf, int len)
-{
-  int i;
-  int j;
-
-  for (i = 0; i < len; i++)
+  if (lvglterm_has_input(g_nsh_stderr[READ_PIPE]))
     {
-      /* Escape Code looks like 0x1b 0x5b 0x4b */
-
-      if (buf[i] == 0x1b)
+      ret = read(g_nsh_stderr[READ_PIPE], buf, sizeof(buf));
+      if (ret > 0)
         {
-          /* Remove 3 bytes */
-
-          for (j = i; j + 2 < len; j++)
-            {
-              buf[j] = buf[j + 3];
-            }
+          lvglterm_add_output(buf, ret);
         }
     }
 }
@@ -511,15 +392,10 @@ static void remove_escape_codes(char *buf, int len)
  * Name: main or lvglterm_main
  *
  * Description:
- *   Start an LVGL Terminal that runs interactive commands with NSH Shell.
- *   NSH Commands are entered through an LVGL Keyboard. NSH Output is
- *   rendered in an LVGL Widget.
- *
- * Input Parameters:
- *   Standard argc and argv
- *
- * Returned Value:
- *   Zero on success; a positive, non-zero value on failure.
+ *   Start an LVGL Terminal that runs interactive commands with NSH.
+ *   NSH output is rendered in an LVGL text area; the input comes from an
+ *   on-screen keyboard (touch) or a physical keyboard, selected at build
+ *   time.
  *
  ****************************************************************************/
 
@@ -534,15 +410,10 @@ int main(int argc, FAR char *argv[])
 #endif
 
 #ifdef CONFIG_BOARDCTL_FINALINIT
-  /* Perform architecture-specific final-initialization (if configured) */
-
   boardctl(BOARDIOC_FINALINIT, 0);
 #endif
 
-  /* LVGL initialization */
-
   lv_init();
-
   lv_nuttx_dsc_init(&info);
 
 #ifdef CONFIG_LV_USE_NUTTX_LCD
@@ -550,23 +421,18 @@ int main(int argc, FAR char *argv[])
 #endif
 
   lv_nuttx_init(&info, &result);
-
   if (result.disp == NULL)
     {
-      LV_LOG_ERROR("lv_demos initialization failure!");
+      LV_LOG_ERROR("lv_nuttx_init failure!");
       return 1;
     }
 
-  /* Create the LVGL Widgets */
-
-  ret = create_terminal();
+  ret = create_terminal(argc, argv);
   if (ret < 0)
     {
       return EXIT_FAILURE;
     }
 
-  /* Handle LVGL tasks */
-
 #ifdef CONFIG_LV_USE_NUTTX_LIBUV
   lv_nuttx_uv_loop(&ui_loop, &result);
 #else
@@ -574,13 +440,10 @@ int main(int argc, FAR char *argv[])
     {
       uint32_t idle;
       idle = lv_timer_handler();
-
-      /* Minimum sleep of 1ms */
-
       idle = idle ? idle : 1;
       usleep(idle * 1000);
     }
-
 #endif
+
   return EXIT_SUCCESS;
 }
diff --git a/examples/lvglterm/lvglterm.h b/examples/lvglterm/lvglterm.h
new file mode 100644
index 000000000..627919cdd
--- /dev/null
+++ b/examples/lvglterm/lvglterm.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+ * apps/examples/lvglterm/lvglterm.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_EXAMPLES_LVGLTERM_LVGLTERM_H
+#define __APPS_EXAMPLES_LVGLTERM_LVGLTERM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#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.
+ */
+
+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 */
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/* Shared helpers provided by the core */
+
+bool lvglterm_has_input(int fd);
+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.
+ */
+
+void lvglterm_input_create(int argc, FAR char *argv[]);
+void lvglterm_input_poll(void);
+
+#endif /* __APPS_EXAMPLES_LVGLTERM_LVGLTERM_H */
diff --git a/examples/lvglterm/lvglterm_kbd.c b/examples/lvglterm/lvglterm_kbd.c
new file mode 100644
index 000000000..4c18aed7d
--- /dev/null
+++ b/examples/lvglterm/lvglterm_kbd.c
@@ -0,0 +1,228 @@
+/****************************************************************************
+ * apps/examples/lvglterm/lvglterm_kbd.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.
+ *
+ ****************************************************************************/
+
+/* 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.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/input/keyboard.h>
+
+#include <lvgl/lvgl.h>
+
+#include "lvglterm.h"
+
+/****************************************************************************
+ * 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 */
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static int g_echo[2];              /* Keyboard task -> screen (local echo) */
+static volatile int g_scroll;      /* Pending scroll (lines), applied by LVGL 
*/
+static FAR const char *g_kbddev;   /* Keyboard device path (/dev/kbdN) */
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: kbd_task
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+static int kbd_task(int argc, FAR char *argv[])
+{
+  struct keyboard_event_s evt;
+  ssize_t nread;
+  int  kfd;
+  char ch;
+
+  kfd = open(g_kbddev, O_RDONLY);
+  if (kfd < 0)
+    {
+      gerr("ERROR: open %s failed: %d\n", g_kbddev, errno);
+      return EXIT_FAILURE;
+    }
+
+  for (; ; )
+    {
+      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);
+        }
+    }
+
+  close(kfd);
+  return EXIT_SUCCESS;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: lvglterm_input_create
+ *
+ * Description:
+ *   Resolve the keyboard device (first command-line argument, else the
+ *   configured default) and start the task that streams key presses to NSH.
+ *
+ ****************************************************************************/
+
+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)
+    {
+      gerr("ERROR: echo pipe failed: %d\n", errno);
+      return;
+    }
+
+  /* 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.
+ *
+ ****************************************************************************/
+
+void lvglterm_input_poll(void)
+{
+  char buf[64];
+  int  n;
+  int  s;
+
+  /* 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.
+   */
+
+  s = g_scroll;
+  if (s != 0)
+    {
+      g_scroll -= s;
+      lv_obj_scroll_by(g_output, 0, -s * SCROLL_STEP, LV_ANIM_OFF);
+    }
+
+  if (lvglterm_has_input(g_echo[READ_PIPE]))
+    {
+      n = read(g_echo[READ_PIPE], buf, sizeof(buf));
+      if (n > 0)
+        {
+          lvglterm_add_output(buf, n);
+        }
+    }
+}
diff --git a/examples/lvglterm/lvglterm_touch.c 
b/examples/lvglterm/lvglterm_touch.c
new file mode 100644
index 000000000..4285c97e6
--- /dev/null
+++ b/examples/lvglterm/lvglterm_touch.c
@@ -0,0 +1,164 @@
+/****************************************************************************
+ * apps/examples/lvglterm/lvglterm_touch.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.
+ *
+ ****************************************************************************/
+
+/* Touch input variant of the LVGL terminal: an on-screen LVGL keyboard feeds
+ * an input text area; pressing Enter sends the line to NSH stdin.  This is
+ * the original PinePhone behaviour and suits touchscreen boards.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <unistd.h>
+#include <string.h>
+#include <nuttx/debug.h>
+#include <lvgl/lvgl.h>
+
+#include "lvglterm.h"
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void input_callback(lv_event_t *e);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* LVGL Text Area for NSH Input and the on-screen Keyboard Widget */
+
+static lv_obj_t *g_input;
+static lv_obj_t *g_kb;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: input_callback
+ *
+ * Description:
+ *   Callback for the NSH input text area.  When the on-screen keyboard Enter
+ *   key is pressed, send the input command to NSH stdin.
+ *
+ ****************************************************************************/
+
+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)
+    {
+      const uint16_t id = lv_keyboard_get_selected_button(g_kb);
+      const char *key = lv_keyboard_get_button_text(g_kb, id);
+
+      if (key == NULL)
+        {
+          return;
+        }
+
+      /* If the key pressed is Enter, send the command to NSH stdin */
+
+      if (key[0] == 0xef && key[1] == 0xa2 && key[2] == 0xa2)
+        {
+          const char *cmd;
+          int len;
+
+          DEBUGASSERT(g_input != NULL);
+          cmd = lv_textarea_get_text(g_input);
+          if (cmd == NULL || cmd[0] == 0)
+            {
+              return;
+            }
+
+          /* Echo the command on the output so the prompt line reads
+           * "nsh> <command>", then send it to the shell.
+           */
+
+          len = strlen(cmd);
+          lvglterm_add_output(cmd, len);
+          if (cmd[len - 1] != '\n')
+            {
+              lvglterm_add_output("\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, "");
+        }
+    }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: lvglterm_input_create
+ *
+ * Description:
+ *   Create the on-screen keyboard and input text area under the shared
+ *   column container.
+ *
+ ****************************************************************************/
+
+void lvglterm_input_create(int argc, FAR char *argv[])
+{
+  UNUSED(argc);
+  UNUSED(argv);
+
+  /* Create an LVGL Text Area Widget for NSH Input */
+
+  g_input = lv_textarea_create(g_col);
+  DEBUGASSERT(g_input != NULL);
+  lv_obj_add_style(g_input, &g_terminal_style, 0);
+  lv_obj_set_size(g_input, LV_PCT(100), LV_SIZE_CONTENT);
+
+  /* Create an LVGL Keyboard Widget */
+
+  g_kb = lv_keyboard_create(g_col);
+  DEBUGASSERT(g_kb != NULL);
+  lv_obj_set_style_pad_all(g_kb, 0, 0);
+
+  /* Wire the keyboard to the input text area */
+
+  lv_obj_add_event_cb(g_input, input_callback, LV_EVENT_ALL, NULL);
+  lv_keyboard_set_textarea(g_kb, g_input);
+}
+
+/****************************************************************************
+ * Name: lvglterm_input_poll
+ *
+ * Description:
+ *   No periodic work is needed for the touch variant.
+ *
+ ****************************************************************************/
+
+void lvglterm_input_poll(void)
+{
+}


Reply via email to