From 958292f3a4024c54115035cb3379e301168fbfde Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Thu, 1 Dec 2011 12:06:51 +0100 Subject: 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. --- src/host/layer23/configure.ac | 2 + src/host/layer23/include/osmocom/bb/Makefile.am | 2 +- src/host/layer23/include/osmocom/bb/ui/Makefile.am | 2 + .../include/osmocom/bb/ui/telnet_interface.h | 17 + src/host/layer23/include/osmocom/bb/ui/ui.h | 96 +++ src/host/layer23/src/Makefile.am | 2 +- src/host/layer23/src/ui/Makefile.am | 7 + src/host/layer23/src/ui/telnet_interface.c | 353 ++++++++++ src/host/layer23/src/ui/ui.c | 728 +++++++++++++++++++++ 9 files changed, 1207 insertions(+), 2 deletions(-) create mode 100644 src/host/layer23/include/osmocom/bb/ui/Makefile.am create mode 100644 src/host/layer23/include/osmocom/bb/ui/telnet_interface.h create mode 100644 src/host/layer23/include/osmocom/bb/ui/ui.h create mode 100644 src/host/layer23/src/ui/Makefile.am create mode 100644 src/host/layer23/src/ui/telnet_interface.c create mode 100644 src/host/layer23/src/ui/ui.c 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 + * (C) 2011 by Andreas Eversberg + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +/* 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 + * + * 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 +#include +#include + +#include +#include +#include + +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; +} + -- cgit v1.2.3