* [RFC PATCH v5] Input/Output Terminal Abstraction
@ 2024-03-31 23:47 Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
To: ell; +Cc: Marcel Holtmann
This expands on Marcel Holtman's 2023-12-22 RFCv4 patch for an
input/output terminal abstraction.
Substantive changes from the v4 version:
* Return status is 'int' rather than 'bool' with 0 as success and <
0 as failure where the failures are negated POSIX error
numbers. This removes the need for system integrators to intuit
their own errors on a previously-false Boolean return status.
* Makes 'l_term_set_{input,output}' public functions.
* Adds observer functions for the terminal rows and columns.
* Adds a modifier function for the terminal bounds.
* Adds:
- l_term_io_func_t
- l_term_set_io_handler
- l_term_process
- l_term_io_callback
Collectively, these make the abstraction run loop adaptable with a
default callback that can be easily used with ELL's run loop,
'l_term_io_callback'.
* Adds 'l_term_[v]print' which complete the output interface.
Of course, its always possible to perform this outside of an atop
the existing functions; however, this eliminates integrators from
having to manage the buffers necessary to do this.
This has proven to work well with the RFCv4 l_edit interfaces and the
corresponding demo-{cli,edit} applications.
Grant Erickson (4):
term: Initial revision.
ell: Add include directive for 'ell/term.h'.
ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES.
term: Added 'l_term_*' symbols.
Makefile.am | 2 +
ell/ell.h | 1 +
ell/ell.sym | 21 +++
ell/term.c | 487 ++++++++++++++++++++++++++++++++++++++++++++++++++++
ell/term.h | 69 ++++++++
5 files changed, 580 insertions(+)
create mode 100644 ell/term.c
create mode 100644 ell/term.h
--
2.42.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH v5] term: Initial revision.
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
To: ell; +Cc: Marcel Holtmann
This is the intial revision of 'term.[hc]' an input/output terminal
abstraction.
---
ell/term.c | 487 +++++++++++++++++++++++++++++++++++++++++++++++++++++
ell/term.h | 69 ++++++++
2 files changed, 556 insertions(+)
create mode 100644 ell/term.c
create mode 100644 ell/term.h
diff --git a/ell/term.c b/ell/term.c
new file mode 100644
index 000000000000..65ddbcbb7600
--- /dev/null
+++ b/ell/term.c
@@ -0,0 +1,487 @@
+/*
+ * Embedded Linux library
+ * Copyright (C) 2023-2024 Intel Corporation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#include "private.h"
+#include "signal.h"
+#include "io.h"
+#include "term.h"
+
+// MARK: Preprocessor Definitions
+
+#define IO_HANDLER(term, fd, readable, writable) \
+ do { \
+ if (term && term->io_handler) { \
+ term->io_handler(term, \
+ fd, \
+ readable, \
+ writable, \
+ term->io_data); \
+ } \
+ } while (0);
+
+// MARK: Type Declarations
+
+struct term_ops {
+ bool color_support;
+ bool use_sigwinch;
+ int (*get_winsize) (int fd, unsigned short *row, unsigned short *col);
+ int (*get_attr) (int fd, struct termios *c);
+ int (*set_attr) (int fd, const struct termios *c);
+};
+
+static int null_get_winsize(int fd, unsigned short *row, unsigned short *col)
+{
+ if (row) *row = 24;
+ if (col) *col = 80;
+ return 0;
+}
+
+static int null_get_attr(int fd, struct termios *c)
+{
+ return 0;
+}
+
+static int null_set_attr(int fd, const struct termios *c)
+{
+ return 0;
+}
+
+static const struct term_ops default_null_ops = {
+ .color_support = false,
+ .use_sigwinch = false,
+ .get_winsize = null_get_winsize,
+ .get_attr = null_get_attr,
+ .set_attr = null_set_attr,
+};
+
+static int tty_get_winsize(int fd, unsigned short *row, unsigned short *col)
+{
+ struct winsize ws;
+ int res;
+
+ res = ioctl(fd, TIOCGWINSZ, &ws);
+ if (!res) {
+ if (row) *row = ws.ws_row;
+ if (col) *col = ws.ws_col;
+ }
+ else
+ res = -errno;
+
+ return res;
+}
+
+static int tty_get_attr(int fd, struct termios *c)
+{
+ int retval = 0;
+
+ if (tcgetattr(fd, c) != 0)
+ retval = -errno;
+
+ return retval;
+}
+
+static int tty_set_attr(int fd, const struct termios *c)
+{
+ int retval = 0;
+
+ if (tcsetattr(fd, TCSANOW, c) != 0)
+ retval = -errno;
+
+ return retval;
+}
+
+static const struct term_ops default_tty_ops = {
+ .color_support = true,
+ .use_sigwinch = true,
+ .get_winsize = tty_get_winsize,
+ .get_attr = tty_get_attr,
+ .set_attr = tty_set_attr,
+};
+
+struct l_term {
+ int in_fd;
+ int out_fd;
+ l_term_io_func_t io_handler;
+ void *io_data;
+ const struct term_ops *in_ops;
+ const struct term_ops *out_ops;
+ struct termios in_termios;
+ struct termios out_termios;
+ unsigned short num_row;
+ unsigned short num_col;
+ struct l_signal *sigwinch;
+ bool is_running;
+ char key_buf[8];
+ size_t key_len;
+ l_term_key_func_t key_handler;
+ void *key_data;
+};
+
+LIB_EXPORT struct l_term *l_term_new(void)
+{
+ struct l_term *term;
+
+ term = l_new(struct l_term, 1);
+
+ term->in_fd = -1;
+ term->in_ops = NULL;
+
+ term->out_fd = -1;
+ term->out_ops = NULL;
+
+ term->is_running = false;
+
+ return term;
+}
+
+LIB_EXPORT void l_term_free(struct l_term *term)
+{
+ if (!term)
+ return;
+
+ l_free(term);
+}
+
+LIB_EXPORT int l_term_set_io_handler(struct l_term *term,
+ l_term_io_func_t handler, void *user_data)
+{
+ if (!term)
+ return -EINVAL;
+
+ term->io_handler = handler;
+ term->io_data = user_data;
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_set_input(struct l_term *term, int fd)
+{
+ if (!term)
+ return -EINVAL;
+
+ if (fd < 0)
+ return -EBADF;
+
+ term->in_fd = fd;
+ term->in_ops = NULL;
+
+ IO_HANDLER(term, fd, 1, 0);
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_set_output(struct l_term *term, int fd)
+{
+ if (!term)
+ return -EINVAL;
+
+ if (fd < 0)
+ return -EBADF;
+
+ term->out_fd = fd;
+ term->out_ops = NULL;
+
+ IO_HANDLER(term, fd, 0, 1);
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_set_input_stdin(struct l_term *term)
+{
+ return l_term_set_input(term, STDIN_FILENO);
+}
+
+LIB_EXPORT int l_term_set_output_stdout(struct l_term *term)
+{
+ return l_term_set_output(term, STDOUT_FILENO);
+}
+
+LIB_EXPORT int l_term_set_key_handler(struct l_term *term,
+ l_term_key_func_t handler, void *user_data)
+{
+ if (!term)
+ return -EINVAL;
+
+ term->key_handler = handler;
+ term->key_data = user_data;
+
+ return 0;
+}
+
+LIB_EXPORT bool l_term_io_callback(struct l_io *io, void *user_data)
+{
+ struct l_term *term = user_data;
+
+ l_term_process(term);
+
+ return true;
+}
+
+static void sigwinch_handler(void *user_data)
+{
+ struct l_term *term = user_data;
+
+ term->out_ops->get_winsize(term->out_fd,
+ &term->num_row, &term->num_col);
+}
+
+LIB_EXPORT int l_term_open(struct l_term *term)
+{
+ struct termios termios;
+ int retval = 0;
+
+ if (!term)
+ return -EINVAL;
+
+ /* Missing input or output file descriptor is a non-recoverable
+ * situation at this point.
+ */
+ if (term->in_fd < 0 || term->out_fd < 0)
+ return -EBADF;
+
+ /* If no input operations are provided, fallback to use TTY
+ * defaults or null setting.
+ */
+ if (!term->in_ops) {
+ if (isatty(term->in_fd))
+ term->in_ops = &default_tty_ops;
+ else
+ term->in_ops = &default_null_ops;
+ }
+
+ /* If no output operations are provided, fallback to use TTY
+ * defaults or null setting.
+ */
+ if (!term->out_ops) {
+ if (isatty(term->out_fd))
+ term->out_ops = &default_tty_ops;
+ else
+ term->out_ops = &default_null_ops;
+ }
+
+ /* Save current termios setting of input */
+ memset(&term->in_termios, 0, sizeof(term->in_termios));
+ retval = term->in_ops->get_attr(term->in_fd, &term->in_termios);
+ if (retval < 0)
+ return retval;
+
+ /* Save current termios setting of output */
+ memset(&term->out_termios, 0, sizeof(term->out_termios));
+ retval = term->out_ops->get_attr(term->out_fd, &term->out_termios);
+ if (retval < 0)
+ return retval;
+
+ /* Disable canonical mode (ICANON), disable echoing of input
+ * characters (ECHO) and disable generating signals.
+ *
+ * In noncanonical mode input is available immediately (without
+ * the user having to type a line-delimiter character), no input
+ * processing is performed, and line editing is disabled.
+ *
+ * When any of the characters INTR, QUIT, SUSP, or DSUSP are
+ * received, don't generate the corresponding signal.
+ */
+ memcpy(&termios, &term->in_termios, sizeof(termios));
+ termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG);
+ retval = term->in_ops->set_attr(term->in_fd, &termios);
+ if (retval < 0)
+ return retval;
+
+ /* Send TIOCGWINSZ ioctl to retrieve col and row number */
+ retval = term->out_ops->get_winsize(term->out_fd,
+ &term->num_row, &term->num_col);
+ if (retval < 0)
+ return retval;
+
+ /* Setup SIGWINCH window resize signal handler if supported */
+ if (term->out_ops->use_sigwinch)
+ term->sigwinch = l_signal_create(SIGWINCH, sigwinch_handler,
+ term, NULL);
+
+ IO_HANDLER(term, term->in_fd, 1, 0);
+ IO_HANDLER(term, term->out_fd, 0, 1);
+
+ term->is_running = true;
+
+ return retval;
+}
+
+LIB_EXPORT int l_term_close(struct l_term *term)
+{
+ int retval = 0;
+
+ if (!term)
+ return -EINVAL;
+
+ term->is_running = false;
+
+ IO_HANDLER(term, term->in_fd, 0, 0);
+ IO_HANDLER(term, term->out_fd, 0, 0);
+
+ /* Remove SIGWINCH window resize signal handler */
+ if (term->out_ops->use_sigwinch)
+ l_signal_remove(term->sigwinch);
+
+ /* Restore previous termios setting from input and output */
+ retval = term->in_ops->set_attr(term->in_fd, &term->in_termios);
+ if (retval < 0)
+ return retval;
+
+ retval = term->out_ops->set_attr(term->out_fd, &term->out_termios);
+ if (retval < 0)
+ return retval;
+
+ return retval;
+}
+
+LIB_EXPORT void l_term_process(struct l_term *term)
+{
+ wchar_t wstr[2];
+ ssize_t len;
+ mbstate_t ps;
+
+ if (!term)
+ return;
+
+ len = read(term->in_fd, term->key_buf + term->key_len,
+ sizeof(term->key_buf) - term->key_len);
+ if (len < 0)
+ return;
+
+ term->key_len += len;
+
+ while (term->key_len > 0) {
+ memset(&ps, 0, sizeof(ps));
+
+ len = mbrtowc(wstr, term->key_buf, term->key_len, &ps);
+ if (len < 0)
+ break;
+
+ memmove(term->key_buf, term->key_buf + len,
+ term->key_len - len);
+ term->key_len -= len;
+
+ if (term->key_handler) {
+ wint_t wch = wstr[0];
+ term->key_handler(term, wch, term->key_data);
+ }
+ }
+}
+
+LIB_EXPORT int l_term_putnstr(struct l_term *term, const char *str, size_t n)
+{
+ ssize_t res;
+
+ if (!term)
+ return -EINVAL;
+
+ if (!term->is_running)
+ return -EPERM;
+
+ res = write(term->out_fd, str, n);
+ if (res < 0)
+ return -errno;
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_putstr(struct l_term *term, const char *str)
+{
+ if (!str)
+ return -EINVAL;
+
+ return l_term_putnstr(term, str, strlen(str));
+}
+
+LIB_EXPORT int l_term_putchar(struct l_term *term, int ch)
+{
+ char c = ch;
+
+ return l_term_putnstr(term, &c, 1);
+}
+
+LIB_EXPORT int l_term_print(struct l_term *term, const char *str, ...)
+{
+ va_list ap;
+ int retval;
+
+ va_start(ap, str);
+
+ retval = l_term_vprint(term, str, ap);
+
+ va_end(ap);
+
+ return retval;
+}
+
+LIB_EXPORT int l_term_vprint(struct l_term *term, const char *str, va_list ap)
+{
+ if (!term || !str)
+ return -EINVAL;
+
+ if (!term->is_running)
+ return -EPERM;
+
+ if (vdprintf(term->out_fd, str, ap) < 0)
+ return -errno;
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_set_bounds(struct l_term *term, uint16_t rows,
+ uint16_t columns)
+{
+ if (!term)
+ return -EINVAL;
+
+ term->num_row = rows;
+ term->num_col = columns;
+
+ return 0;
+}
+
+LIB_EXPORT int l_term_get_rows(struct l_term *term, uint16_t *rows)
+{
+ int retval = 0;
+
+ if (!term)
+ return -EINVAL;
+
+ if (!term->out_ops)
+ return -ENOSYS;
+
+ retval = term->out_ops->get_winsize(term->out_fd, rows, NULL);
+
+ return retval;
+}
+
+LIB_EXPORT int l_term_get_columns(struct l_term *term, uint16_t *columns)
+{
+ int retval = 0;
+
+ if (!term)
+ return -EINVAL;
+
+ if (!term->out_ops)
+ return -ENOSYS;
+
+ retval = term->out_ops->get_winsize(term->out_fd, NULL, columns);
+
+ return retval;
+}
diff --git a/ell/term.h b/ell/term.h
new file mode 100644
index 000000000000..89ed6c97c7ce
--- /dev/null
+++ b/ell/term.h
@@ -0,0 +1,69 @@
+/*
+ * Embedded Linux library
+ * Copyright (C) 2023-2024 Intel Corporation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __ELL_TERM_H
+#define __ELL_TERM_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <wchar.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct l_term;
+
+struct l_term *l_term_new(void);
+void l_term_free(struct l_term *term);
+
+typedef void (*l_term_io_func_t)(struct l_term *term,
+ int fd,
+ bool readable,
+ bool writable,
+ void *user_data);
+
+int l_term_set_io_handler(struct l_term *term,
+ l_term_io_func_t handler,
+ void *user_data);
+
+int l_term_set_input(struct l_term *term, int fd);
+int l_term_set_output(struct l_term *term, int fd);
+
+int l_term_set_input_stdin(struct l_term *term);
+int l_term_set_output_stdout(struct l_term *term);
+
+typedef void (*l_term_key_func_t) (struct l_term *term, wint_t wch, void *user_data);
+
+int l_term_set_key_handler(struct l_term *term,
+ l_term_key_func_t handler, void *user_data);
+
+int l_term_open(struct l_term *term);
+int l_term_close(struct l_term *term);
+
+bool l_term_io_callback(struct l_io *io, void *user_data);
+
+void l_term_process(struct l_term *term);
+
+int l_term_putnstr(struct l_term *term, const char *str, size_t n);
+int l_term_putstr(struct l_term *term, const char *str);
+int l_term_putchar(struct l_term *term, int ch);
+int l_term_print(struct l_term *term, const char *str, ...);
+int l_term_vprint(struct l_term *term, const char *str, va_list ap);
+
+int l_term_set_bounds(struct l_term *term, uint16_t rows, uint16_t columns);
+
+int l_term_get_rows(struct l_term *term, uint16_t *rows);
+int l_term_get_columns(struct l_term *term, uint16_t *columns);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ELL_TERM_H */
--
2.42.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC PATCH v5] ell: Add include directive for 'ell/term.h'.
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson
3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
To: ell; +Cc: Marcel Holtmann
---
ell/ell.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/ell/ell.h b/ell/ell.h
index f67339105e8f..c648715e9bce 100644
--- a/ell/ell.h
+++ b/ell/ell.h
@@ -46,6 +46,7 @@
#include <ell/ecc.h>
#include <ell/ecdh.h>
#include <ell/time.h>
+#include <ell/term.h>
#include <ell/gpio.h>
#include <ell/path.h>
#include <ell/acd.h>
--
2.42.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES.
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson
3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
To: ell; +Cc: Marcel Holtmann
---
Makefile.am | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index 6c86e94e963e..5059682eec5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -55,6 +55,7 @@ pkginclude_HEADERS = ell/ell.h \
ell/ecc.h \
ell/ecdh.h \
ell/time.h \
+ ell/term.h \
ell/gpio.h \
ell/path.h \
ell/icmp6.h \
@@ -145,6 +146,7 @@ ell_libell_la_SOURCES = $(linux_headers) \
ell/ecdh.c \
ell/time.c \
ell/time-private.h \
+ ell/term.c \
ell/gpio.c \
ell/path.c \
ell/icmp6.c \
--
2.42.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC PATCH v5] term: Added 'l_term_*' symbols.
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
` (2 preceding siblings ...)
2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
To: ell; +Cc: Marcel Holtmann
---
ell/ell.sym | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/ell/ell.sym b/ell/ell.sym
index 28b55d36719b..2767bf55e079 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -623,6 +623,27 @@ global:
l_ecdh_generate_shared_secret;
/* time */
l_time_now;
+ /* term */
+ l_term_new;
+ l_term_free;
+ l_term_set_io_handler;
+ l_term_set_input;
+ l_term_set_input_stdin;
+ l_term_set_output;
+ l_term_set_output_stdout;
+ l_term_set_key_handler;
+ l_term_open;
+ l_term_close;
+ l_term_io_callback;
+ l_term_process;
+ l_term_putnstr;
+ l_term_putstr;
+ l_term_putchar;
+ l_term_print;
+ l_term_vprint;
+ l_term_set_bounds;
+ l_term_get_rows;
+ l_term_get_columns;
/* gpio */
l_gpio_chips_with_line_label;
l_gpio_chip_new;
--
2.42.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2024-03-31 23:47 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).