summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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 102d2344..a9068668 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 58a5f7fb..a8791fe4 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 00000000..b010e53c
--- /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 00000000..b35b47f9
--- /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 00000000..75e2fad7
--- /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 58a5f7fb..a8791fe4 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 00000000..62357cc3
--- /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 00000000..d7e9bfb0
--- /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 00000000..5545a5e7
--- /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;
+}
+