summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2011-12-01 12:06:51 +0100
committerAndreas Eversberg <jolly@eversberg.eu>2016-09-25 08:11:47 +0200
commit958292f3a4024c54115035cb3379e301168fbfde (patch)
tree7f8225d258ae7ec27d145ec05a8a6e01c51a19d6
parenta5a5cd1eff13e8079c6f0058d5e5d94e8dd99ba5 (diff)
layer23/mobile: Added generic user interface
The user interface instance is currently accessed via telnet. It supports different views to display or enter something. It is still not complete.
-rw-r--r--src/host/layer23/configure.ac2
-rw-r--r--src/host/layer23/include/osmocom/bb/Makefile.am2
-rw-r--r--src/host/layer23/include/osmocom/bb/ui/Makefile.am2
-rw-r--r--src/host/layer23/include/osmocom/bb/ui/telnet_interface.h17
-rw-r--r--src/host/layer23/include/osmocom/bb/ui/ui.h96
-rw-r--r--src/host/layer23/src/Makefile.am2
-rw-r--r--src/host/layer23/src/ui/Makefile.am7
-rw-r--r--src/host/layer23/src/ui/telnet_interface.c353
-rw-r--r--src/host/layer23/src/ui/ui.c728
9 files changed, 1207 insertions, 2 deletions
diff --git a/src/host/layer23/configure.ac b/src/host/layer23/configure.ac
index 102d234..a906866 100644
--- a/src/host/layer23/configure.ac
+++ b/src/host/layer23/configure.ac
@@ -30,11 +30,13 @@ AC_OUTPUT(
src/Makefile
src/common/Makefile
src/misc/Makefile
+ src/ui/Makefile
src/mobile/Makefile
include/Makefile
include/osmocom/Makefile
include/osmocom/bb/Makefile
include/osmocom/bb/common/Makefile
include/osmocom/bb/misc/Makefile
+ include/osmocom/bb/ui/Makefile
include/osmocom/bb/mobile/Makefile
Makefile)
diff --git a/src/host/layer23/include/osmocom/bb/Makefile.am b/src/host/layer23/include/osmocom/bb/Makefile.am
index 58a5f7f..a8791fe 100644
--- a/src/host/layer23/include/osmocom/bb/Makefile.am
+++ b/src/host/layer23/include/osmocom/bb/Makefile.am
@@ -1 +1 @@
-SUBDIRS = common misc mobile
+SUBDIRS = common misc ui mobile
diff --git a/src/host/layer23/include/osmocom/bb/ui/Makefile.am b/src/host/layer23/include/osmocom/bb/ui/Makefile.am
new file mode 100644
index 0000000..b010e53
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/ui/Makefile.am
@@ -0,0 +1,2 @@
+noinst_HEADERS = ui.h telnet_interface.h
+
diff --git a/src/host/layer23/include/osmocom/bb/ui/telnet_interface.h b/src/host/layer23/include/osmocom/bb/ui/telnet_interface.h
new file mode 100644
index 0000000..b35b47f
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/ui/telnet_interface.h
@@ -0,0 +1,17 @@
+#ifndef _LIBUI_TELNET_IF_H
+#define _LIBUI_TELNET_IF_H
+
+struct ui_telnet_connection {
+ struct llist_head entry;
+ void *priv;
+ struct osmo_fd fd;
+ struct ui_inst *ui;
+ struct buffer *obuf;
+ int iac, sb, esc;
+};
+
+int ui_telnet_init(struct ui_inst *ui, void *tall_ctx, int port);
+int ui_telnet_puts(struct ui_inst *ui, const char *text);
+void ui_telnet_exit(struct ui_inst *ui);
+
+#endif /* _LIBUI_TELNET_IF_H */
diff --git a/src/host/layer23/include/osmocom/bb/ui/ui.h b/src/host/layer23/include/osmocom/bb/ui/ui.h
new file mode 100644
index 0000000..75e2fad
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/ui/ui.h
@@ -0,0 +1,96 @@
+#ifndef _libui_h
+#define _libui_h
+
+#define UI_ROWS 8
+#define UI_COLS 12
+#define UI_TARGET 0
+
+enum ui_key {
+ UI_KEY_0 = '0',
+ UI_KEY_1 = '1',
+ UI_KEY_2 = '2',
+ UI_KEY_3 = '3',
+ UI_KEY_4 = '4',
+ UI_KEY_5 = '5',
+ UI_KEY_6 = '6',
+ UI_KEY_7 = '7',
+ UI_KEY_8 = '8',
+ UI_KEY_9 = '9',
+ UI_KEY_STAR = '*',
+ UI_KEY_HASH = '#',
+ UI_KEY_F1 = 1,
+ UI_KEY_F2 = 2,
+ UI_KEY_PICKUP = 26,
+ UI_KEY_HANGUP = 27,
+ UI_KEY_UP = 28,
+ UI_KEY_DOWN = 29,
+ UI_KEY_LEFT = 30,
+ UI_KEY_RIGHT = 31,
+};
+
+union ui_view_data {
+ struct {
+ int lines;
+ const char **text;
+ int vpos;
+ } listview;
+ struct {
+ int lines;
+ const char **text;
+ int vpos;
+ int cursor;
+ } selectview;
+ struct {
+ char *number;
+ int num_len;
+ int pos;
+ int options;
+ int options_pos;
+ } stringview;
+ struct {
+ unsigned int value;
+ int sign;
+ int min, max;
+ } intview;
+};
+
+struct ui_inst;
+
+struct ui_view {
+ const char *name;
+ int (*init)(struct ui_view *uv, union ui_view_data *ud);
+ int (*keypad)(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp);
+ int (*display)(struct ui_inst *ui, union ui_view_data *ud);
+};
+
+struct ui_inst {
+ struct ui_view *uv;
+ const char *title;
+ const char *bottom_line;
+ union ui_view_data ud;
+ int (*key_cb)(struct ui_inst *ui, enum ui_key kp);
+ int (*beep_cb)(struct ui_inst *ui);
+ /* display */
+ char buffer[(UI_COLS + 1) * UI_ROWS];
+ int cursor_on, cursor_x, cursor_y;
+ /* telnet */
+ void *tall_telnet_ctx;
+ struct osmo_fd server_socket;
+ struct llist_head active_connections;
+ int (*telnet_cb)(struct ui_inst *ui);
+};
+
+extern struct ui_view ui_listview;
+extern struct ui_view ui_selectview;
+extern struct ui_view ui_stringview;
+extern struct ui_view ui_intview;
+
+int ui_inst_init(struct ui_inst *ui, struct ui_view *uv,
+ int (*key_cb)(struct ui_inst *ui, enum ui_key kp),
+ int (*beep_cb)(struct ui_inst *ui),
+ int (*telnet_cb)(struct ui_inst *ui));
+int ui_inst_keypad(struct ui_inst *ui, enum ui_key kp);
+int ui_inst_refresh(struct ui_inst *ui);
+
+#endif /* _libui_h */
diff --git a/src/host/layer23/src/Makefile.am b/src/host/layer23/src/Makefile.am
index 58a5f7f..a8791fe 100644
--- a/src/host/layer23/src/Makefile.am
+++ b/src/host/layer23/src/Makefile.am
@@ -1 +1 @@
-SUBDIRS = common misc mobile
+SUBDIRS = common misc ui mobile
diff --git a/src/host/layer23/src/ui/Makefile.am b/src/host/layer23/src/ui/Makefile.am
new file mode 100644
index 0000000..62357cc
--- /dev/null
+++ b/src/host/layer23/src/ui/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS)
+
+noinst_LIBRARIES = libui.a
+libui_a_SOURCES = ui.c telnet_interface.c
+
+
diff --git a/src/host/layer23/src/ui/telnet_interface.c b/src/host/layer23/src/ui/telnet_interface.c
new file mode 100644
index 0000000..d7e9bfb
--- /dev/null
+++ b/src/host/layer23/src/ui/telnet_interface.c
@@ -0,0 +1,353 @@
+/* minimalistic telnet/network interface it might turn into a wire interface */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/telnet.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/vty/buffer.h>
+
+#include <osmocom/bb/ui/ui.h>
+#include <osmocom/bb/ui/telnet_interface.h>
+
+/* Send WILL TELOPT_ECHO to remote server. */
+static void vty_will_echo(struct ui_inst *ui)
+{
+ unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
+ ui_telnet_puts(ui, (char *)cmd);
+}
+
+/* Make suppress Go-Ahead telnet option. */
+static void vty_will_suppress_go_ahead(struct ui_inst *ui)
+{
+ unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
+ ui_telnet_puts(ui, (char *)cmd);
+}
+
+/* Make don't use linemode over telnet. */
+static void vty_dont_linemode(struct ui_inst *ui)
+{
+ unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' };
+ ui_telnet_puts(ui, (char *)cmd);
+}
+
+/* Use window size. */
+static void vty_do_window_size(struct ui_inst *ui)
+{
+ unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' };
+ ui_telnet_puts(ui, (char *)cmd);
+}
+
+static int telnet_new_connection(struct osmo_fd *fd, unsigned int what);
+
+int ui_telnet_init(struct ui_inst *ui, void *tall_ctx, int port)
+{
+ struct sockaddr_in sock_addr;
+ int fd, rc, on = 1;
+
+ ui->tall_telnet_ctx = talloc_named_const(tall_ctx, 1,
+ "ui_telnet_connection");
+
+ INIT_LLIST_HEAD(&ui->active_connections);
+
+ /* FIXME: use new socket.c code of libosmocore */
+ fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+ if (fd < 0) {
+ LOGP(0, LOGL_ERROR, "Telnet interface socket creation "
+ "failed\n");
+ talloc_free(ui->tall_telnet_ctx);
+ return fd;
+ }
+
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_port = htons(port);
+ sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ rc = bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
+ if (rc < 0) {
+ LOGP(0, LOGL_ERROR, "Telnet interface failed to bind\n");
+ close(fd);
+ talloc_free(ui->tall_telnet_ctx);
+ return rc;
+ }
+
+ rc = listen(fd, 0);
+ if (rc < 0) {
+ LOGP(0, LOGL_ERROR, "Telnet interface failed to listen\n");
+ close(fd);
+ talloc_free(ui->tall_telnet_ctx);
+ return rc;
+ }
+
+ ui->server_socket.when = BSC_FD_READ;
+ ui->server_socket.cb = telnet_new_connection;
+ ui->server_socket.priv_nr = 0;
+ ui->server_socket.data = ui;
+ ui->server_socket.fd = fd;
+ osmo_fd_register(&ui->server_socket);
+
+ return 0;
+}
+
+int ui_telnet_puts(struct ui_inst *ui, const char *text)
+{
+ struct ui_telnet_connection *conn;
+
+ llist_for_each_entry(conn, &ui->active_connections, entry) {
+ buffer_put(conn->obuf, text, strlen(text));
+ conn->fd.when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int telnet_close_client(struct osmo_fd *fd)
+{
+ struct ui_telnet_connection *conn =
+ (struct ui_telnet_connection*)fd->data;
+ struct ui_inst *ui = conn->ui;
+
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+
+ buffer_free(conn->obuf);
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+
+ /* notify about connection */
+ ui->telnet_cb(ui);
+
+ return 0;
+}
+
+//#define DEBUG_SEQEUENCES
+static int telnet_getc(struct ui_telnet_connection *conn, unsigned char p)
+{
+#ifdef DEBUG_SEQEUENCES
+ printf("k: %d\n", p);
+#endif
+
+ if (conn->sb) {
+ if (p == SE) {
+#ifdef DEBUG_SEQEUENCES
+ puts("se");
+#endif
+ conn->sb = 0;
+ conn->iac = 0;
+ }
+ return 0;
+ }
+ if (conn->iac) {
+ if (conn->iac == 1) {
+ if (p == SB) {
+ conn->sb = 1;
+#ifdef DEBUG_SEQEUENCES
+ puts("sb");
+#endif
+ return 0;
+ }
+ if (p == IAC) {
+ conn->iac = 0;
+#ifdef DEBUG_SEQEUENCES
+ puts("iac iac (ende)");
+#endif
+ return 0;
+ }
+ conn->iac = 2;
+ return 0;
+ }
+ conn->iac = 0;
+#ifdef DEBUG_SEQEUENCES
+ puts("iac ende");
+#endif
+ return 0;
+ }
+ if (p == IAC) {
+ conn->iac = 1;
+#ifdef DEBUG_SEQEUENCES
+ puts("iac");
+#endif
+ return 0;
+ }
+
+ if (conn->esc) {
+ if (conn->esc == 1) {
+ if (p == 91) {
+ conn->esc = 2;
+ return 0;
+ }
+ if (p == 79) {
+ conn->esc = 3;
+ return 0;
+ }
+ conn->esc = 0;
+#ifdef DEBUG_SEQEUENCES
+ puts("esc abort");
+#endif
+ return 0;
+ }
+ if (conn->esc == 2) {
+ if (p == 65)
+ ui_inst_keypad(conn->ui, UI_KEY_UP);
+ if (p == 66)
+ ui_inst_keypad(conn->ui, UI_KEY_DOWN);
+ if (p == 67)
+ ui_inst_keypad(conn->ui, UI_KEY_RIGHT);
+ if (p == 68)
+ ui_inst_keypad(conn->ui, UI_KEY_LEFT);
+ if (p == 72)
+ ui_inst_keypad(conn->ui, UI_KEY_PICKUP);
+ if (p == 70)
+ ui_inst_keypad(conn->ui, UI_KEY_HANGUP);
+ }
+ if (conn->esc == 3) {
+ if (p == 80)
+ ui_inst_keypad(conn->ui, UI_KEY_F1);
+ if (p == 81)
+ ui_inst_keypad(conn->ui, UI_KEY_F2);
+ }
+ conn->esc = 0;
+#ifdef DEBUG_SEQEUENCES
+ puts("esc ende");
+#endif
+ return 0;
+ }
+ if (p == 27) {
+ conn->esc = 1;
+#ifdef DEBUG_SEQEUENCES
+ puts("esc");
+#endif
+ return 0;
+ }
+
+ if (p == 3 || p == 4)
+ return -1;
+
+ /* refresh */
+ if (p == 12) {
+ ui_inst_refresh(conn->ui);
+ return 0;
+ }
+
+ ui_inst_keypad(conn->ui, p);
+
+ return 0;
+}
+
+static int client_data(struct osmo_fd *fd, unsigned int what)
+{
+ struct ui_telnet_connection *conn = fd->data;
+ int rc = 0;
+
+ if (what & BSC_FD_READ) {
+ char buffer[16], *p = buffer;
+ int nbytes;
+ nbytes = read(fd->fd, buffer, sizeof(buffer));
+ if (nbytes == 0) {
+ conn->ui = NULL;
+ telnet_close_client(fd);
+ return rc;
+ }
+ if (nbytes > 0) {
+ while (nbytes--) {
+ rc = telnet_getc(conn, *p++);
+ if (rc < 0) {
+ telnet_close_client(&conn->fd);
+ /* telnet conn is gone, must exit! */
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (what & BSC_FD_WRITE) {
+ rc = buffer_flush_all(conn->obuf, fd->fd);
+ if (rc == BUFFER_EMPTY)
+ conn->fd.when &= ~BSC_FD_WRITE;
+ }
+
+ return rc;
+}
+
+static int telnet_new_connection(struct osmo_fd *fd, unsigned int what)
+{
+ struct ui_telnet_connection *conn;
+ struct sockaddr_in sockaddr;
+ socklen_t len = sizeof(sockaddr);
+ int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
+ struct ui_inst *ui = (struct ui_inst *) fd->data;
+
+ if (new_connection < 0) {
+ LOGP(0, LOGL_ERROR, "telnet accept failed\n");
+ return new_connection;
+ }
+
+ conn = talloc_zero(ui->tall_telnet_ctx, struct ui_telnet_connection);
+ conn->ui = ui;
+ conn->fd.data = conn;
+ conn->fd.fd = new_connection;
+ conn->fd.when = BSC_FD_READ;
+ conn->fd.cb = client_data;
+ osmo_fd_register(&conn->fd);
+ llist_add_tail(&conn->entry, &ui->active_connections);
+
+ conn->obuf = buffer_new(ui->tall_telnet_ctx, 0);
+
+ vty_will_echo(ui);
+ vty_will_suppress_go_ahead(ui);
+ vty_dont_linemode(ui);
+ vty_do_window_size(ui);
+
+ /* notify about connection */
+ ui->telnet_cb(ui);
+
+ return 0;
+}
+
+void ui_telnet_exit(struct ui_inst *ui)
+{
+ struct ui_telnet_connection *tc, *tc2;
+
+ if (ui->server_socket.fd <= 0)
+ return;
+
+ llist_for_each_entry_safe(tc, tc2, &ui->active_connections, entry)
+ telnet_close_client(&tc->fd);
+
+ osmo_fd_unregister(&ui->server_socket);
+ close(ui->server_socket.fd);
+ ui->server_socket.fd = -1;
+ talloc_free(ui->tall_telnet_ctx);
+}
+
diff --git a/src/host/layer23/src/ui/ui.c b/src/host/layer23/src/ui/ui.c
new file mode 100644
index 0000000..5545a5e
--- /dev/null
+++ b/src/host/layer23/src/ui/ui.c
@@ -0,0 +1,728 @@
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/bb/ui/ui.h>
+#include <osmocom/bb/ui/telnet_interface.h>
+
+static char *ui_center(const char *text)
+{
+ static char line[UI_COLS + 1];
+ int len, shift;
+
+ strncpy(line, text, UI_COLS);
+ line[UI_COLS] = '\0';
+ len = strlen(line);
+ if (len + 1 < UI_COLS) {
+ shift = (UI_COLS - len) / 2;
+ memcpy(line + shift, line, len + 1);
+ memset(line, ' ', shift);
+ }
+
+ return line;
+}
+
+/*
+ * io functions
+ */
+
+int ui_clearhome(struct ui_inst *ui)
+{
+ int i;
+
+ /* initialize with spaces */
+ memset(ui->buffer, ' ', sizeof(ui->buffer));
+ /* terminate with EOL */
+ for (i = 0; i < UI_ROWS; i++)
+ ui->buffer[(UI_COLS + 1) * (i + 1) - 1] = '\0';
+
+ ui->cursor_x = ui->cursor_y = 0;
+ ui->cursor_on = 0;
+
+ return 0;
+}
+
+int ui_puts(struct ui_inst *ui, int ln, const char *text)
+{
+ int len = strlen(text);
+
+ /* out of range */
+ if (ln < 0 || ln >= UI_ROWS)
+ return -EINVAL;
+
+ /* clip */
+ if (len > UI_COLS)
+ len = UI_COLS;
+
+ /* copy */
+ if (len)
+ memcpy(ui->buffer + (UI_COLS + 1) * ln, text, len);
+
+ return 0;
+}
+
+int ui_flush(struct ui_inst *ui)
+{
+ int i;
+ char frame[UI_COLS + 5];
+ char line[UI_COLS + 5];
+ char cursor[16];
+
+ /* clear */
+ ui_telnet_puts(ui, "\033c");
+
+ /* display */
+ memset(frame + 1, '-', UI_COLS);
+ frame[0] = frame[UI_COLS + 1] = '+';
+ frame[UI_COLS + 2] = '\r';
+ frame[UI_COLS + 3] = '\n';
+ frame[UI_COLS + 4] = '\0';
+ ui_telnet_puts(ui, frame);
+ for (i = 0; i < UI_ROWS; i++) {
+ sprintf(line, "|%s|\r\n", ui->buffer + (UI_COLS + 1) * i);
+ ui_telnet_puts(ui, line);
+ }
+ ui_telnet_puts(ui, frame);
+
+ ui_telnet_puts(ui, "\r\n"
+ "1 2 3 4 5 6 7 8 9 * 0 # = digits\r\n"
+ "Pos1 = pickup, End = hangup\r\n"
+ "F1 = left button, F2 = right button\r\n"
+ "arrow keys = navigation buttons\r\n");
+
+ /* set cursor */
+ if (ui->cursor_on) {
+ sprintf(cursor, "\033[%d;%dH", ui->cursor_y + 2,
+ ui->cursor_x + 2);
+ ui_telnet_puts(ui, cursor);
+ }
+
+ return 0;
+}
+
+static int bottom_puts(struct ui_inst *ui, const char *text)
+{
+ char bottom_line[UI_COLS + 1], *p;
+ int space;
+
+ strncpy(bottom_line, text, UI_COLS);
+ bottom_line[UI_COLS] = '\0';
+ if ((p = strchr(bottom_line, ' '))
+ && (space = UI_COLS - strlen(bottom_line))) {
+ p++;
+ memcpy(p + space, p, strlen(p));
+ memset(p, ' ', space);
+ }
+
+ return ui_puts(ui, UI_ROWS - 1, bottom_line);
+}
+
+/*
+ * listview
+ */
+
+static int init_listview(struct ui_view *uv, union ui_view_data *ud)
+{
+ ud->listview.vpos = 0;
+ ud->listview.lines = 0;
+
+ return 0;
+}
+
+static int keypad_listview(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp)
+{
+ int rows = UI_ROWS;
+
+ if (ui->title)
+ rows--;
+ if (ui->bottom_line)
+ rows--;
+
+ switch (kp) {
+ case UI_KEY_UP:
+ if (ud->listview.vpos == 0)
+ return -1;
+ ud->listview.vpos--;
+ break;
+ case UI_KEY_DOWN:
+ if (rows + ud->listview.vpos >= ud->listview.lines)
+ return -1;
+ ud->listview.vpos++;
+ break;
+ default:
+ return 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+}
+
+static int display_listview(struct ui_inst *ui, union ui_view_data *ud)
+{
+ const char **text = ud->listview.text;
+ int lines = ud->listview.lines;
+ int i, j = 0;
+ int rows = UI_ROWS;
+
+ if (ui->bottom_line)
+ rows--;
+
+ /* vpos will skip lines */
+ for (i = 0; i < ud->listview.vpos; i++) {
+ /* if we reached end of test, we leave the pointer there */
+ if (*text == NULL)
+ break;
+ text++;
+ j++;
+ }
+
+ ui_clearhome(ui);
+ /* title */
+ i = 0;
+ if (ui->title)
+ ui_puts(ui, i++, ui_center(ui->title));
+ for (; i < rows; i++) {
+ if (*text && j < lines) {
+ ui_puts(ui, i, *text);
+ text++;
+ j++;
+ } else
+ break;
+// ui_puts(ui, i, "~");
+ }
+ if (ui->bottom_line)
+ bottom_puts(ui, ui->bottom_line);
+ ui_flush(ui);
+
+ return 0;
+}
+
+/*
+ * selectview
+ */
+
+static int init_selectview(struct ui_view *uv, union ui_view_data *ud)
+{
+ ud->selectview.vpos = 0;
+ ud->selectview.cursor = 0;
+ ud->selectview.lines = 0;
+
+ return 0;
+}
+
+static int keypad_selectview(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp)
+{
+ int rows = UI_ROWS;
+
+ if (ui->title)
+ rows--;
+ if (ui->bottom_line)
+ rows--;
+
+ switch (kp) {
+ case UI_KEY_UP:
+ if (ud->selectview.cursor == 0)
+ return -1;
+ ud->selectview.cursor--;
+ /* follow cursor */
+ if (ud->selectview.cursor < ud->selectview.vpos)
+ ud->selectview.vpos = ud->selectview.cursor;
+ break;
+ case UI_KEY_DOWN:
+ if (ud->selectview.cursor >= ud->selectview.lines - 1)
+ return -1;
+ ud->selectview.cursor++;
+ /* follow cursor */
+ if (ud->selectview.cursor > ud->selectview.vpos + rows - 1)
+ ud->selectview.vpos = ud->selectview.cursor -
+ (rows - 1);
+ break;
+ default:
+ return 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+}
+
+static int display_selectview(struct ui_inst *ui, union ui_view_data *ud)
+{
+ const char **text = ud->selectview.text;
+ int lines = ud->selectview.lines;
+ int i, j = 0, y = 0;
+ int rows = UI_ROWS;
+ char line[UI_COLS + 1];
+
+ if (ui->bottom_line)
+ rows--;
+
+ /* vpos will skip lines */
+ for (i = 0; i < ud->selectview.vpos; i++) {
+ /* if we reached end of test, we leave the pointer there */
+ if (*text == NULL)
+ break;
+ text++;
+ j++;
+ }
+
+ ui_clearhome(ui);
+ /* title */
+ i = 0;
+ if (ui->title)
+ ui_puts(ui, i++, ui_center(ui->title));
+ for (; i < rows; i++) {
+ if (*text && j < lines) {
+ if (ud->selectview.cursor == j)
+ y = i;
+ strncpy(line, *text, UI_COLS);
+ line[UI_COLS] = '\0';
+ ui_puts(ui, i, line);
+ text++;
+ j++;
+ } else
+ break;
+// ui_puts(ui, i, "~");
+ }
+ if (ui->bottom_line)
+ bottom_puts(ui, ui->bottom_line);
+ ui->cursor_on = 1;
+ ui->cursor_x = 0;
+ ui->cursor_y = y;
+ ui_flush(ui);
+
+ return 0;
+}
+
+/*
+ * stringview
+ */
+
+static int init_stringview(struct ui_view *uv, union ui_view_data *ud)
+{
+ ud->stringview.options = 0;
+ ud->stringview.pos = 0;
+
+ return 0;
+}
+
+static int keypad_stringview(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp);
+
+static int keypad_stringview_options(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp)
+{
+ switch (kp) {
+ case UI_KEY_F1: /* back */
+ ud->stringview.options = 0;
+ break;
+ case UI_KEY_1:
+ ud->stringview.options = 0;
+ return keypad_stringview(ui, uv, ud, 'a');
+ case UI_KEY_2:
+ ud->stringview.options = 0;
+ return keypad_stringview(ui, uv, ud, 'b');
+ case UI_KEY_3:
+ ud->stringview.options = 0;
+ return keypad_stringview(ui, uv, ud, 'c');
+ case UI_KEY_0:
+ ud->stringview.options = 0;
+ return keypad_stringview(ui, uv, ud, '+');
+ default:
+ return 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+}
+
+static int keypad_stringview(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp)
+{
+ if (ud->stringview.options)
+ return keypad_stringview_options(ui, uv, ud, kp);
+
+ switch (kp) {
+ case UI_KEY_STAR:
+ case UI_KEY_HASH:
+ case UI_KEY_1:
+ case UI_KEY_2:
+ case UI_KEY_3:
+ case UI_KEY_4:
+ case UI_KEY_5:
+ case UI_KEY_6:
+ case UI_KEY_7:
+ case UI_KEY_8:
+ case UI_KEY_9:
+ case UI_KEY_0:
+ case 'a':
+ case 'b':
+ case 'c':
+ case '+':
+ /* check if number is full */
+ if (strlen(ud->stringview.number) + 1 == ud->stringview.num_len)
+ return -1;
+ /* add digit */
+ if (ud->stringview.number[ud->stringview.pos] == '\0') {
+ /* add to the end */
+ ud->stringview.number[ud->stringview.pos] = kp;
+ ud->stringview.pos++;
+ ud->stringview.number[ud->stringview.pos] = '\0';
+ } else {
+ /* insert digit */
+ memcpy(ud->stringview.number + ud->stringview.pos + 1,
+ ud->stringview.number + ud->stringview.pos,
+ strlen(ud->stringview.number +
+ ud->stringview.pos)
+ + 1);
+ ud->stringview.number[ud->stringview.pos] = kp;
+ ud->stringview.pos++;
+ }
+ break;
+ case UI_KEY_LEFT:
+ if (ud->stringview.pos == 0)
+ return -1;
+ ud->stringview.pos--;
+ break;
+ case UI_KEY_RIGHT:
+ if (ud->stringview.pos == strlen(ud->stringview.number))
+ return -1;
+ ud->stringview.pos++;
+ break;
+ case UI_KEY_UP: /* select options */
+ ud->stringview.options = 1;
+ ud->stringview.options_pos = 0;
+ break;
+ case UI_KEY_F1: /* clear */
+ ud->stringview.pos = 0;
+ ud->stringview.number[0] = '\0';
+ break;
+ case UI_KEY_F2: /* delete */
+ if (ud->stringview.pos == 0)
+ return -1;
+ /* del digit */
+ if (ud->stringview.number[ud->stringview.pos] == '\0') {
+ /* del digit from the end */
+ ud->stringview.pos--;
+ ud->stringview.number[ud->stringview.pos] = '\0';
+ } else {
+ /* remove digit */
+ memcpy(ud->stringview.number + ud->stringview.pos - 1,
+ ud->stringview.number + ud->stringview.pos,
+ strlen(ud->stringview.number +
+ ud->stringview.pos)
+ + 1);
+ ud->stringview.pos--;
+ }
+ break;
+ default:
+ return 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+}
+
+static int display_stringview(struct ui_inst *ui, union ui_view_data *ud)
+{
+ char line[UI_COLS + 1];
+ char *p = ud->stringview.number;
+ int len = strlen(p);
+ int i = 1, y;
+
+ /* options screen */
+ if (ud->stringview.options) {
+ ui_clearhome(ui);
+ ui_puts(ui, 0, ui_center("Extra Keys"));
+ ui_puts(ui, 2, "1:a 2:b 3:c");
+ ui_puts(ui, 3, "4: 5: 6: ");
+ ui_puts(ui, 4, "7: 8: 9: ");
+ ui_puts(ui, 5, "*: 0:+ #: ");
+ bottom_puts(ui, "back ");
+ ui_flush(ui);
+ return 0;
+ }
+
+ /* if number shrunk */
+ if (ud->stringview.pos > len)
+ ud->stringview.pos = len;
+
+ ui_clearhome(ui);
+ /* title */
+ if (ui->title) {
+ ui_puts(ui, i++, ui_center(ui->title));
+ i++;
+ }
+ y = i;
+ /* if line exceeds display width */
+ while (len > UI_COLS) {
+ memcpy(line, p, UI_COLS);
+ line[UI_COLS] = '\0';
+ ui_puts(ui, i++, line);
+ p += UI_COLS;
+ len -= UI_COLS;
+ }
+ /* last line */
+ if (len)
+ ui_puts(ui, i, p);
+ /* cursor */
+ ui->cursor_on = 1;
+ ui->cursor_x = ud->stringview.pos % UI_COLS;
+ ui->cursor_y = y + (ud->stringview.pos / UI_COLS);
+ /* F-keys info */
+ bottom_puts(ui, "clear del");
+ ui_flush(ui);
+
+ return 0;
+}
+
+/*
+ * integer view
+ */
+
+static int init_intview(struct ui_view *uv, union ui_view_data *ud)
+{
+ ud->intview.min = -128;
+ ud->intview.max = 127;
+ ud->intview.value = 0;
+
+ return 0;
+}
+
+static int keypad_intview(struct ui_inst *ui, struct ui_view *uv,
+ union ui_view_data *ud, enum ui_key kp)
+{
+ int value;
+
+ switch (kp) {
+ case UI_KEY_1:
+ case UI_KEY_2:
+ case UI_KEY_3:
+ case UI_KEY_4:
+ case UI_KEY_5:
+ case UI_KEY_6:
+ case UI_KEY_7:
+ case UI_KEY_8:
+ case UI_KEY_9:
+ case UI_KEY_0:
+ value = ud->intview.value;
+ value = value * 10 + kp - UI_KEY_0;
+ /* if additional digit would cause overflow (or no change) */
+ if (value <= ud->intview.value)
+ return - 1;
+ if (value > 0x7fffffff)
+ return - 1;
+ ud->intview.value = value;
+ break;
+ case UI_KEY_STAR:
+ ud->intview.sign = 1 - ud->intview.sign;
+ break;
+ case UI_KEY_UP:
+ case UI_KEY_DOWN:
+ if (ud->intview.sign)
+ value = 0 - ud->intview.value;
+ else
+ value = ud->intview.value;
+ /* check if limit is already reached */
+ if (kp == UI_KEY_UP && value == ud->intview.max)
+ return -1;
+ if (kp == UI_KEY_DOWN && value == ud->intview.min)
+ return -1;
+ /* if value out of range, put it in range */
+ if (value > ud->intview.max) {
+ value = ud->intview.max;
+ goto store_value;
+ }
+ if (value < ud->intview.min) {
+ value = ud->intview.min;
+ goto store_value;
+ }
+ if (kp == UI_KEY_UP)
+ value++;
+ else
+ value--;
+ goto store_value;
+ case UI_KEY_LEFT: /* delete */
+ /* already 0 */
+ if (ud->intview.value == 0)
+ return -1;
+ ud->intview.value /= 10;
+ break;
+ default:
+ /* if other key is pressed, make value fit in range */
+ if (ud->intview.sign)
+ value = 0 - ud->intview.value;
+ else
+ value = ud->intview.value;
+ if (value < ud->intview.min) {
+ value = ud->intview.min;
+ goto store_value;
+ }
+ if (value > ud->intview.max) {
+ value = ud->intview.max;
+ goto store_value;
+ }
+ return 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+
+store_value:
+ /* store new value */
+ if (value < 0) {
+ ud->intview.value = 0 - value;
+ ud->intview.sign = 1;
+ } else {
+ ud->intview.value = value;
+ ud->intview.sign = 0;
+ }
+ /* refresh display */
+ uv->display(ui, ud);
+
+ return 0;
+}
+
+static int display_intview(struct ui_inst *ui, union ui_view_data *ud)
+{
+ char line[UI_COLS + 2];
+ int i = 1, y, x = 1;
+ int value;
+
+ ui_clearhome(ui);
+ /* title */
+ if (ui->title) {
+ ui_puts(ui, i++, ui_center(ui->title));
+ i++;
+ }
+ y = i;
+ /* value */
+ if (ud->intview.sign)
+ line[0] = '-';
+ else
+ line[0] = ' ';
+ sprintf(line + 1, "%d", ud->intview.value);
+ ui_puts(ui, i++, line);
+ /* range */
+ i++;
+ snprintf(line, UI_COLS + 1, "(%d..%d)", ud->intview.min,
+ ud->intview.max);
+ line[UI_COLS] = '\0';
+ ui_puts(ui, i++, line);
+ /* cursor */
+ value = ud->intview.value;
+ while (value) {
+ x++;
+ value /= 10;
+ }
+ ui->cursor_on = 1;
+ ui->cursor_x = x;
+ ui->cursor_y = y;
+ /* F-keys info */
+ if (ui->bottom_line)
+ bottom_puts(ui, ui->bottom_line);
+ ui_flush(ui);
+
+ return 0;
+}
+
+/*
+ * structure of all views
+ */
+
+struct ui_view ui_listview = {
+ .init = init_listview,
+ .keypad = keypad_listview,
+ .display = display_listview,
+};
+
+struct ui_view ui_selectview = {
+ .init = init_selectview,
+ .keypad = keypad_selectview,
+ .display = display_selectview,
+};
+
+struct ui_view ui_stringview = {
+ .init = init_stringview,
+ .keypad = keypad_stringview,
+ .display = display_stringview,
+};
+
+struct ui_view ui_intview = {
+ .init = init_intview,
+ .keypad = keypad_intview,
+ .display = display_intview,
+};
+
+/*
+ * instance handling
+ */
+
+int ui_inst_init(struct ui_inst *ui, struct ui_view *uv,
+ int (*key_cb)(struct ui_inst *ui, enum ui_key kp),
+ int (*beep_cb)(struct ui_inst *ui),
+ int (*telnet_cb)(struct ui_inst *ui))
+{
+ ui->uv = uv;
+ ui->key_cb = key_cb;
+ ui->beep_cb = beep_cb;
+ ui->telnet_cb = telnet_cb;
+
+ ui_clearhome(ui);
+
+ /* initialize view */
+ uv->init(uv, &ui->ud);
+
+ return 0;
+}
+
+int ui_inst_refresh(struct ui_inst *ui)
+{
+ /* refresh display */
+ return ui->uv->display(ui, &ui->ud);
+}
+
+/* process keypress at user interface */
+int ui_inst_keypad(struct ui_inst *ui, enum ui_key kp)
+{
+ int rc;
+
+ /* first check if key is handled by callback */
+ rc = ui->key_cb(ui, kp);
+ if (rc)
+ return rc; /* must exit, since key_cb() may reconfigure UI */
+
+ rc = ui->uv->keypad(ui, ui->uv, &ui->ud, kp);
+ if (rc < 0)
+ ui->beep_cb(ui);
+
+ return rc;
+}
+