mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-25 07:27:16 +00:00
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 <filipe.cavalcanti@espressif.com>
This commit is contained in:
parent
d570175cc9
commit
2b92bedaee
5 changed files with 190 additions and 87 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_stdout[2];
|
||||
static int g_nsh_stderr[2];
|
||||
static int g_nsh_fd = -1;
|
||||
|
||||
/* 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 */
|
||||
|
||||
|
|
@ -148,25 +169,103 @@ static char * const g_nsh_argv[] =
|
|||
NSH_TASK, NULL
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: has_output
|
||||
*
|
||||
* Description:
|
||||
* Return true if the shell has produced output that can be read without
|
||||
* blocking.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static bool has_output(void)
|
||||
{
|
||||
struct pollfd fdp;
|
||||
|
||||
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_has_input
|
||||
* Name: lvglterm_send_input
|
||||
*
|
||||
* Description:
|
||||
* Return true if the file descriptor has data to be read.
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
bool lvglterm_has_input(int fd)
|
||||
void lvglterm_send_input(FAR const char *buf, int len)
|
||||
{
|
||||
struct pollfd fdp;
|
||||
int room = INPUT_QUEUE_SIZE - g_input_queued;
|
||||
|
||||
fdp.fd = fd;
|
||||
fdp.events = POLLIN;
|
||||
return poll(&fdp, 1, 0) > 0 && (fdp.revents & POLLIN) != 0;
|
||||
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();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
|
@ -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();
|
||||
|
||||
if (lvglterm_has_input(g_nsh_stdout[READ_PIPE]))
|
||||
{
|
||||
ret = read(g_nsh_stdout[READ_PIPE], buf, sizeof(buf));
|
||||
if (ret > 0)
|
||||
{
|
||||
lvglterm_add_output(buf, ret);
|
||||
}
|
||||
}
|
||||
/* 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_stderr[READ_PIPE]))
|
||||
for (reads = 0; reads < MAX_READS_PER_TICK; reads++)
|
||||
{
|
||||
ret = read(g_nsh_stderr[READ_PIPE], buf, sizeof(buf));
|
||||
if (ret > 0)
|
||||
if (!has_output())
|
||||
{
|
||||
lvglterm_add_output(buf, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = read(g_nsh_fd, buf, sizeof(buf));
|
||||
if (ret <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lvglterm_add_output(buf, ret);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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[]);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
|
|
|
|||
|
|
@ -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, "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue