summaryrefslogtreecommitdiff
path: root/extras/ezio/ezio-term.c
diff options
context:
space:
mode:
Diffstat (limited to 'extras/ezio/ezio-term.c')
-rw-r--r--extras/ezio/ezio-term.c1060
1 files changed, 1060 insertions, 0 deletions
diff --git a/extras/ezio/ezio-term.c b/extras/ezio/ezio-term.c
new file mode 100644
index 000000000..c2177addf
--- /dev/null
+++ b/extras/ezio/ezio-term.c
@@ -0,0 +1,1060 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+
+#include <config.h>
+#include <assert.h>
+#include <curses.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <term.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "extras/ezio/byteq.h"
+#include "extras/ezio/tty.h"
+#include "extras/ezio/vt.h"
+#include "daemon.h"
+#include "ezio.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "terminal.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ezio_term
+#include "vlog.h"
+
+/* EZIO button status. */
+enum btn_status {
+ BTN_UP = 1 << 0,
+ BTN_DOWN = 1 << 1,
+ BTN_ENTER = 1 << 2,
+ BTN_ESC = 1 << 3
+};
+
+/* -e, --ezio: EZIO3 serial device file. */
+static char *ezio_dev = "/dev/ttyS1";
+
+/* -i, --input: Terminal from which to accept additional keyboard input. */
+static char *input_dev = NULL;
+
+struct inputdev;
+static int inputdev_open(const char *name, struct inputdev **);
+static void inputdev_close(struct inputdev *);
+static int inputdev_run(struct inputdev *, struct byteq *);
+static void inputdev_update(struct inputdev *, const struct ezio *);
+static void inputdev_wait(struct inputdev *);
+
+static struct scanner *scanner_create(void);
+static void scanner_destroy(struct scanner *);
+static void scanner_run(struct scanner *, struct ezio *);
+static void scanner_wait(struct scanner *);
+static void scanner_left(struct scanner *, struct ezio *);
+static void scanner_right(struct scanner *, struct ezio *);
+
+static struct updater *updater_create(void);
+static void updater_destroy(struct updater *);
+static int updater_run(struct updater *, const struct ezio *shadow,
+ int ezio_fd);
+static void updater_wait(struct updater *, int ezio_fd);
+enum btn_status updater_get_buttons(struct updater *);
+bool updater_has_buttons(const struct updater *);
+
+static void handle_buttons(struct updater *, struct scanner *,
+ struct byteq *, struct ezio *);
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ struct terminal *terminal;
+ struct updater *updater;
+ struct scanner *scanner;
+ struct inputdev *inputdev;
+ struct byteq inputq;
+ struct ezio ezio;
+ int ezio_fd, pty_fd, dummy_fd;
+ int retval;
+ int i;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+
+ argc -= optind;
+ argv += optind;
+
+ /* Make sure that the ezio3 terminfo entry is available. */
+ dummy_fd = open("/dev/null", O_RDWR);
+ if (dummy_fd >= 0) {
+ if (setupterm("ezio3", dummy_fd, &retval) == ERR) {
+ if (retval == 0) {
+ ovs_fatal(0, "Missing terminfo entry for ezio3. "
+ "Did you run \"make install\"?");
+ } else {
+ ovs_fatal(0, "Missing terminfo database. Is ncurses "
+ "properly installed?");
+ }
+ }
+ del_curterm(cur_term);
+ close(dummy_fd);
+ } else {
+ ovs_error(errno, "failed to open /dev/null");
+ }
+
+ /* Lock serial port. */
+ retval = tty_lock(ezio_dev);
+ if (retval) {
+ ovs_fatal(retval, "%s: lock failed", ezio_dev);
+ }
+
+ /* Open EZIO and configure as 2400 bps, N-8-1, in raw mode. */
+ ezio_fd = open(ezio_dev, O_RDWR | O_NOCTTY);
+ if (ezio_fd < 0) {
+ ovs_fatal(errno, "%s: open", ezio_dev);
+ }
+ retval = tty_set_raw_mode(ezio_fd, B2400);
+ if (retval) {
+ ovs_fatal(retval, "%s: failed to configure tty parameters", ezio_dev);
+ }
+
+ /* Open keyboard device for input. */
+ if (input_dev) {
+ retval = inputdev_open(input_dev, &inputdev);
+ if (retval) {
+ ovs_fatal(retval, "%s: failed to open input device", input_dev);
+ }
+ } else {
+ inputdev = NULL;
+ }
+
+ /* Open pty master. */
+ pty_fd = tty_open_master_pty();
+ if (pty_fd < 0) {
+ ovs_fatal(-pty_fd, "failed to open master pty");
+ }
+ tty_set_window_size(pty_fd, 2, 40);
+
+ /* Start child process. */
+ if (argc < 1) {
+ char *child_argv[2];
+
+ child_argv[0] = getenv("SHELL");
+ if (!child_argv[0]) {
+ child_argv[0] = "/bin/sh";
+ }
+ child_argv[1] = NULL;
+ retval = tty_fork_child(pty_fd, child_argv);
+ } else {
+ retval = tty_fork_child(pty_fd, argv);
+ }
+ if (retval) {
+ ovs_fatal(retval, "failed to fork child process");
+ }
+
+ die_if_already_running();
+ daemonize();
+
+ terminal = terminal_create();
+ updater = updater_create();
+ scanner = scanner_create();
+ ezio_init(&ezio);
+ for (i = 0; i < 8; i++) {
+ ezio_set_default_icon(&ezio, i);
+ }
+ byteq_init(&inputq);
+ for (;;) {
+ /* Get button presses and keyboard input into inputq, then push the
+ * inputq to the pty. */
+ handle_buttons(updater, scanner, &inputq, &ezio);
+ if (inputdev) {
+ retval = inputdev_run(inputdev, &inputq);
+ if (retval) {
+ VLOG_ERR("error reading from input device: %s",
+ strerror(retval));
+ inputdev_close(inputdev);
+ inputdev = NULL;
+ }
+ }
+ retval = byteq_write(&inputq, pty_fd);
+ if (retval && retval != EAGAIN) {
+ VLOG_ERR("error passing through input: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ }
+
+ /* Process data from pty in terminal emulator. */
+ retval = terminal_run(terminal, &ezio, pty_fd);
+ if (retval) {
+ VLOG_ERR("error reading from terminal: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ break;
+ }
+
+ /* Scroll left and right through text. */
+ scanner_run(scanner, &ezio);
+
+ /* Update the display to match what should be shown. */
+ retval = updater_run(updater, &ezio, ezio_fd);
+ if (retval) {
+ VLOG_ERR("error writing to ezio: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ break;
+ }
+ if (inputdev) {
+ inputdev_update(inputdev, &ezio);
+ }
+
+ /* Wait for something to happen. */
+ terminal_wait(terminal, pty_fd);
+ scanner_wait(scanner);
+ if (updater_has_buttons(updater)) {
+ poll_immediate_wake();
+ }
+ updater_wait(updater, ezio_fd);
+ if (!byteq_is_empty(&inputq)) {
+ poll_fd_wait(pty_fd, POLLOUT);
+ }
+ if (inputdev) {
+ inputdev_wait(inputdev);
+ }
+ poll_block();
+ }
+ terminal_destroy(terminal);
+ updater_destroy(updater);
+ scanner_destroy(scanner);
+
+ return 0;
+}
+
+static void
+send_keys(struct byteq *q, const char *s)
+{
+ size_t n = strlen(s);
+ if (byteq_avail(q) >= n) {
+ byteq_putn(q, s, n);
+ }
+}
+
+static void
+handle_buttons(struct updater *up, struct scanner *s,
+ struct byteq *q, struct ezio *ezio)
+{
+ while (updater_has_buttons(up)) {
+ int btns = updater_get_buttons(up);
+ switch (btns) {
+ case BTN_UP:
+ send_keys(q, "\x1b\x5b\x41"); /* Up arrow. */
+ break;
+
+ case BTN_UP | BTN_ESC:
+ send_keys(q, "\x1b[5~"); /* Page up. */
+ break;
+
+ case BTN_DOWN:
+ send_keys(q, "\x1b\x5b\x42"); /* Down arrow. */
+ break;
+
+ case BTN_DOWN | BTN_ESC:
+ send_keys(q, "\x1b[6~"); /* Page down. */
+ break;
+
+ case BTN_ENTER:
+ send_keys(q, "\r");
+ break;
+
+ case BTN_ESC:
+ send_keys(q, "\x7f");
+ break;
+
+ case BTN_UP | BTN_DOWN:
+ scanner_left(s, ezio);
+ break;
+
+ case BTN_ESC | BTN_ENTER:
+ scanner_right(s, ezio);
+ break;
+
+ case BTN_UP | BTN_DOWN | BTN_ENTER | BTN_ESC:
+ send_keys(q, "\x04"); /* End of file. */
+ break;
+
+ case BTN_UP | BTN_ENTER | BTN_ESC:
+ send_keys(q, "y");
+ break;
+
+ case BTN_DOWN | BTN_ENTER | BTN_ESC:
+ send_keys(q, "n");
+ break;
+ }
+ }
+}
+
+/* EZIO screen updater. */
+
+/* EZIO command codes. */
+#define EZIO_CMD 0xfe /* Command prefix byte. */
+#define EZIO_CLEAR 0x01 /* Clear screen. */
+#define EZIO_HOME 0x02 /* Move to (0, 0). */
+#define EZIO_READ 0x06 /* Poll keyboard. */
+
+#define EZIO_ENTRY_MODE 0x04 /* Set entry mode: */
+#define EZIO_LTOR_MODE 0x02 /* ...left-to-right (vs. r-to-l). */
+#define EZIO_SHIFT_MODE 0x01 /* ...scroll with output (vs. don't). */
+
+#define EZIO_DISPLAY_MODE 0x08 /* Set display mode: */
+#define EZIO_ENABLE_DISPLAY 0x04 /* ...turn on display (vs. blank). */
+#define EZIO_SHOW_CURSOR 0x02 /* ...show cursor (vs. hide). */
+#define EZIO_BLOCK_CURSOR 0x01 /* ...block cursor (vs. underline). */
+
+#define EZIO_INIT 0x28 /* Initialize EZIO. */
+
+#define EZIO_MOVE_CURSOR 0x80 /* Set cursor position. */
+#define EZIO_COL_SHIFT 0 /* Shift count for column (0-based). */
+#define EZIO_ROW_SHIFT 6 /* Shift count for row (0-based). */
+
+#define EZIO_DEFINE_ICON 0x40 /* Define icon. */
+#define EZIO_ICON_SHIFT 3 /* Shift count for icon number (0-7). */
+
+#define EZIO_SCROLL_LEFT 0x18 /* Scroll display left 1 position. */
+#define EZIO_SCROLL_RIGHT 0x1c /* Scroll display right 1 position. */
+#define EZIO_CURSOR_LEFT 0x10 /* Move cursor left 1 position. */
+#define EZIO_CURSOR_RIGHT 0x14 /* Move cursor right 1 position. */
+
+/* Rate limiting: the EZIO runs at 2400 bps, which is 240 bytes per second.
+ * Kernel tty buffers, on the other hand, tend to be at least 4 kB. That
+ * means that, if we keep the kernel buffer filled, then the queued data will
+ * be 4,096 kB / 240 bytes/s ~= 17 seconds ahead of what is actually
+ * displayed. This is not a happy situation. So we rate-limit with a token
+ * bucket.
+ *
+ * The parameters below work out as: (6 tokens/ms * 1000 ms) / (25
+ * tokens/byte) = 240 bytes/s. */
+#define UP_TOKENS_PER_MS 6 /* Tokens acquired per millisecond. */
+#define UP_BUCKET_SIZE (6 * 100) /* Capacity of the token bukect. */
+#define UP_TOKENS_PER_BYTE 25 /* Tokens required to output a byte. */
+
+struct updater {
+ /* Current state of EZIO device. */
+ struct ezio visible;
+
+ /* Output state. */
+ struct byteq obuf; /* Output being sent to serial port. */
+ int tokens; /* Token bucket content. */
+ long long int last_fill; /* Last time we increased 'tokens'.*/
+ bool up_to_date; /* Does visible state match shadow state? */
+
+ /* Input state. */
+ struct byteq ibuf; /* Queued button pushes. */
+ long long int last_poll; /* Last time we sent a button poll request. */
+ enum btn_status last_status; /* Last received button status. */
+ long long int last_change; /* Time when status most recently changed. */
+ int repeat_count; /* Autorepeat count. */
+ bool releasing; /* Waiting for button release? */
+};
+
+static void send_command(struct updater *, uint8_t command);
+static void recv_button_state(struct updater *, enum btn_status status);
+static int range(int value, int min, int max);
+static void send_command(struct updater *, uint8_t command);
+static void set_cursor_position(struct updater *, int x, int y);
+static bool icons_differ(const struct ezio *, const struct ezio *, int *idx);
+static void update_char(struct updater *, const struct ezio *, int x, int y);
+static void update_cursor_status(struct updater *, const struct ezio *);
+
+/* Creates and returns a new updater. */
+static struct updater *
+updater_create(void)
+{
+ struct updater *up = xmalloc(sizeof *up);
+ ezio_init(&up->visible);
+ byteq_init(&up->obuf);
+ up->tokens = UP_BUCKET_SIZE;
+ up->last_fill = time_msec();
+ byteq_init(&up->ibuf);
+ up->last_poll = LLONG_MIN;
+ up->last_status = 0;
+ up->last_change = time_msec();
+ up->releasing = false;
+ send_command(up, EZIO_INIT);
+ send_command(up, EZIO_INIT);
+ send_command(up, EZIO_CLEAR);
+ send_command(up, EZIO_HOME);
+ return up;
+}
+
+/* Destroys updater 'up. */
+static void
+updater_destroy(struct updater *up)
+{
+ free(up);
+}
+
+/* Sends EZIO commands over file descriptor 'ezio_fd' to the EZIO represented
+ * by updater 'up', to make the EZIO display the contents of 'shadow'.
+ * Rate-limiting can cause the update to be only partial, but the next call to
+ * updater_run() will resume the update.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+updater_run(struct updater *up, const struct ezio *shadow, int ezio_fd)
+{
+ uint8_t c;
+ while (read(ezio_fd, &c, 1) > 0) {
+ if ((c & 0xf0) == 0xb0) {
+ recv_button_state(up, ~c & 0x0f);
+ }
+ }
+
+ up->up_to_date = false;
+ for (;;) {
+ struct ezio *visible = &up->visible;
+ int idx, x, y;
+ int retval;
+
+ /* Flush the buffer out to the EZIO device. */
+ retval = byteq_write(&up->obuf, ezio_fd);
+ if (retval == EAGAIN) {
+ return 0;
+ } else if (retval) {
+ VLOG_WARN("error writing ezio: %s", strerror(retval));
+ return retval;
+ }
+
+ /* Make sure we have some tokens before we write anything more. */
+ if (up->tokens <= 0) {
+ long long int now = time_msec();
+ if (now > up->last_fill) {
+ up->tokens += (now - up->last_fill) * UP_TOKENS_PER_MS;
+ up->last_fill = now;
+ if (up->tokens > UP_BUCKET_SIZE) {
+ up->tokens = UP_BUCKET_SIZE;
+ }
+ }
+ if (up->tokens <= 0) {
+ /* Still out of tokens. */
+ return 0;
+ }
+ }
+
+ /* Consider what else we might want to send. */
+ if (time_msec() >= up->last_poll + 100) {
+ /* Send a button-read command. */
+ send_command(up, EZIO_READ);
+ up->last_poll = time_msec();
+ } else if (visible->show_cursor && !shadow->show_cursor) {
+ /* Turn off the cursor. */
+ update_cursor_status(up, shadow);
+ } else if (icons_differ(shadow, visible, &idx)) {
+ /* Update the icons. */
+ send_command(up, EZIO_DEFINE_ICON + (idx << EZIO_ICON_SHIFT));
+ byteq_putn(&up->obuf, &shadow->icons[idx][0], 8);
+ set_cursor_position(up, shadow->x, shadow->y);
+ memcpy(visible->icons[idx], shadow->icons[idx], 8);
+ } else if (visible->x_ofs != shadow->x_ofs) {
+ /* Scroll to the correct horizontal position. */
+ if (visible->x_ofs < shadow->x_ofs) {
+ send_command(up, EZIO_SCROLL_LEFT);
+ visible->x_ofs++;
+ } else {
+ send_command(up, EZIO_SCROLL_RIGHT);
+ visible->x_ofs--;
+ }
+ } else if (ezio_chars_differ(shadow, visible, shadow->x_ofs,
+ shadow->x_ofs + 16, &x, &y)) {
+ /* Update the visible region. */
+ update_char(up, shadow, x, y);
+ } else if (ezio_chars_differ(shadow, visible, 0, 40, &x, &y)) {
+ /* Update the off-screen region. */
+ update_char(up, shadow, x, y);
+ } else if ((visible->x != shadow->x || visible->y != shadow->y)
+ && shadow->show_cursor) {
+ /* Update the cursor position. (This has to follow updating the
+ * display content, because updating display content changes the
+ * cursor position.) */
+ set_cursor_position(up, shadow->x, shadow->y);
+ } else if (visible->show_cursor != shadow->show_cursor
+ || visible->blink_cursor != shadow->blink_cursor) {
+ /* Update the cursor type. */
+ update_cursor_status(up, shadow);
+ } else {
+ /* We're fully up-to-date. */
+ up->up_to_date = true;
+ return 0;
+ }
+ up->tokens -= UP_TOKENS_PER_BYTE * byteq_used(&up->obuf);
+ }
+}
+
+/* Calls poll-loop functions that will cause poll_block() to wake up when
+ * updater_run() has work to do. */
+static void
+updater_wait(struct updater *up, int ezio_fd)
+{
+ if (!byteq_is_empty(&up->obuf)) {
+ poll_fd_wait(ezio_fd, POLLOUT);
+ } else if (up->tokens <= 0) {
+ poll_timer_wait((-up->tokens / UP_TOKENS_PER_MS) + 1);
+ } else if (!up->up_to_date) {
+ poll_immediate_wake();
+ }
+
+ if (!up->last_status && time_msec() - up->last_change > 100) {
+ /* No button presses in a while. Sleep longer. */
+ poll_timer_wait(100);
+ } else {
+ poll_timer_wait(50);
+ }
+}
+
+/* Returns a button or buttons that were pushed. Must not be called if
+ * updater_has_buttons() would return false. One or more BTN_* flags will be
+ * set in the return value. */
+enum btn_status
+updater_get_buttons(struct updater *up)
+{
+ return byteq_get(&up->ibuf);
+}
+
+/* Any buttons pushed? */
+bool
+updater_has_buttons(const struct updater *up)
+{
+ return !byteq_is_empty(&up->ibuf);
+}
+
+/* Adds 'btns' to the queue of pushed buttons */
+static void
+buttons_pushed(struct updater *up, enum btn_status btns)
+{
+ if (!byteq_is_full(&up->ibuf)) {
+ byteq_put(&up->ibuf, btns);
+ }
+}
+
+/* Updates the buttons-pushed queue based on the current button 'status'. */
+static void
+recv_button_state(struct updater *up, enum btn_status status)
+{
+ /* Calculate milliseconds since button status last changed. */
+ long long int stable_msec;
+ if (status != up->last_status) {
+ up->last_change = time_msec();
+ stable_msec = 0;
+ } else {
+ stable_msec = time_msec() - up->last_change;
+ }
+
+ if (up->releasing) {
+ if (!status) {
+ up->releasing = false;
+ }
+ } else if (up->last_status) {
+ if (!(status & up->last_status)) {
+ /* Button(s) were pushed and released. */
+ if (!up->repeat_count) {
+ buttons_pushed(up, up->last_status);
+ }
+ } else if (stable_msec >= 150 && !up->repeat_count) {
+ /* Buttons have been stable for a while, so push them once. */
+ buttons_pushed(up, status);
+ up->repeat_count++;
+ } else if (stable_msec >= 1000) {
+ /* Autorepeat 10/second after 1 second hold time. */
+ int n = (stable_msec - 1000) / 100 + 1;
+ while (up->repeat_count < n) {
+ buttons_pushed(up, status);
+ up->repeat_count++;
+ }
+ } else if ((status & up->last_status) == up->last_status) {
+ /* More buttons pushed than at last poll. */
+ } else {
+ /* Some, but not all, buttons were released. Ignore the buttons
+ * until all are released. */
+ up->releasing = true;
+ }
+ }
+ if (!status) {
+ up->repeat_count = 0;
+ }
+ up->last_status = status;
+}
+
+static int
+range(int value, int min, int max)
+{
+ return value < min ? min : value > max ? max : value;
+}
+
+static void
+send_command(struct updater *up, uint8_t command)
+{
+ byteq_put(&up->obuf, EZIO_CMD);
+ byteq_put(&up->obuf, command);
+}
+
+/* Moves the cursor to 0-based position (x, y). Updates 'up->visible' to
+ * reflect the change. */
+static void
+set_cursor_position(struct updater *up, int x, int y)
+{
+ int command = EZIO_MOVE_CURSOR;
+ command |= range(x, 0, 39) << EZIO_COL_SHIFT;
+ command |= range(y, 0, 1) << EZIO_ROW_SHIFT;
+ send_command(up, command);
+ up->visible.x = x;
+ up->visible.y = y;
+}
+
+/* If any of the icons differ from 'a' to 'b', returns true and sets '*idx' to
+ * the index of the first icon that differs. Otherwise, returns false. */
+static bool
+icons_differ(const struct ezio *a, const struct ezio *b, int *idx)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(a->icons); i++) {
+ if (memcmp(&a->icons[i], &b->icons[i], sizeof a->icons[i])) {
+ *idx = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Queues commands in 'up''s output buffer to update the character at 0-based
+ * position (x,y) to match the character that 'shadow' has there. Updates
+ * 'up->visible' to reflect the change. */
+static void
+update_char(struct updater *up, const struct ezio *shadow, int x, int y)
+{
+ if (x != up->visible.x || y != up->visible.y) {
+ set_cursor_position(up, x, y);
+ }
+ byteq_put(&up->obuf, shadow->chars[y][x]);
+ up->visible.chars[y][x] = shadow->chars[y][x];
+ up->visible.x++;
+}
+
+/* Queues commands in 'up''s output buffer to change the EZIO's cursor shape to
+ * match that in 'shadow'. Updates 'up->visible' to reflect the change. */
+static void
+update_cursor_status(struct updater *up, const struct ezio *shadow)
+{
+ uint8_t command = EZIO_DISPLAY_MODE | EZIO_ENABLE_DISPLAY;
+ if (shadow->show_cursor) {
+ command |= EZIO_SHOW_CURSOR;
+ if (shadow->blink_cursor) {
+ command |= EZIO_BLOCK_CURSOR;
+ }
+ }
+ send_command(up, command);
+ up->visible.show_cursor = shadow->show_cursor;
+ up->visible.blink_cursor = shadow->blink_cursor;
+}
+
+/* An input device, such as a tty. */
+
+struct inputdev {
+ /* Input. */
+ int fd; /* File descriptor. */
+
+ /* State for mirroring the EZIO display to the device. */
+ bool is_tty; /* We only attempt to mirror to ttys. */
+ struct byteq outq; /* Output queue. */
+ struct ezio visible; /* Data that we have displayed. */
+};
+
+/* Opens 'name' as a input device. If successful, returns 0 and stores a
+ * pointer to the input device in '*devp'. On failure, returns a positive
+ * errno value. */
+static int
+inputdev_open(const char *name, struct inputdev **devp)
+{
+ struct inputdev *dev;
+ int retval;
+ int fd;
+
+ *devp = NULL;
+ if (!strcmp(name, "vt")) {
+ fd = vt_open(O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ return -fd;
+ }
+ } else if (!strcmp(name, "-")) {
+ fd = dup(STDIN_FILENO);
+ if (fd < 0) {
+ return errno;
+ }
+ } else {
+ fd = open(name, O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ return errno;
+ }
+ }
+
+ retval = tty_set_raw_mode(fd, B0);
+ if (retval) {
+ close(fd);
+ VLOG_WARN("%s: failed to configure tty parameters: %s",
+ name, strerror(retval));
+ return retval;
+ }
+
+ dev = xmalloc(sizeof *dev);
+ dev->fd = fd;
+ dev->is_tty = isatty(fd);
+ byteq_init(&dev->outq);
+ ezio_init(&dev->visible);
+ *devp = dev;
+ return 0;
+}
+
+/* Closes and destroys input device 'dev'. */
+static void
+inputdev_close(struct inputdev *dev)
+{
+ if (dev) {
+ close(dev->fd);
+ free(dev);
+ }
+}
+
+/* Reads input from 'dev' into 'q'. Returns 0 if successful, otherwise a
+ * positive errno value. */
+static int
+inputdev_run(struct inputdev *dev, struct byteq *q)
+{
+ int retval = byteq_read(q, dev->fd);
+ return retval == EAGAIN ? 0 : retval;
+}
+
+/* Dumps data from 'dev''s output queue to the underlying file descriptor,
+ * updating the tty screen display. */
+static void
+flush_inputdev(struct inputdev *dev)
+{
+ int retval = byteq_write(&dev->outq, dev->fd);
+ if (retval && retval != EAGAIN) {
+ VLOG_WARN("error writing input device, "
+ "disabling further output");
+ dev->is_tty = false;
+ }
+}
+
+/* Updates the tty screen display on 'dev' to match 'e'. */
+static void
+inputdev_update(struct inputdev *dev, const struct ezio *e)
+{
+ struct byteq *q = &dev->outq;
+ int x, y;
+
+ if (!dev->is_tty) {
+ return;
+ }
+
+ flush_inputdev(dev);
+ if (!byteq_is_empty(q)) {
+ return;
+ }
+
+ if (!ezio_chars_differ(e, &dev->visible, 0, 40, &x, &y)
+ && e->x == dev->visible.x
+ && e->y == dev->visible.y
+ && e->x_ofs == dev->visible.x_ofs
+ && e->show_cursor == dev->visible.show_cursor) {
+ return;
+ }
+ dev->visible = *e;
+
+ byteq_put_string(q, "\033[H\033[2J"); /* Clear screen. */
+ for (y = 0; y < 4; y++) {
+ byteq_put(q, "+||+"[y]);
+ for (x = 0; x < 40; x++) {
+ int c;
+ if (x == e->x_ofs) {
+ byteq_put(q, '[');
+ }
+ c = y == 0 || y == 3 ? '-' : e->chars[y - 1][x];
+ if (c == 6) {
+ c = '\\';
+ } else if (c == 7) {
+ c = '~';
+ } else if (c < 0x20 || c > 0x7d) {
+ c = '?';
+ }
+ byteq_put(q, c);
+ if (x == e->x_ofs + 15) {
+ byteq_put(q, ']');
+ }
+ }
+ byteq_put(q, "+||+"[y]);
+ byteq_put(q, '\r');
+ byteq_put(q, '\n');
+ }
+ if (e->show_cursor) {
+ int x = range(e->x, 0, 39) + 2 + (e->x >= e->x_ofs) + (e->x > e->x_ofs + 15);
+ int y = range(e->y, 0, 1) + 2;
+ char cup[16];
+ sprintf(cup, "\033[%d;%dH", y, x); /* Position cursor. */
+ byteq_put_string(q, cup);
+ }
+ flush_inputdev(dev);
+}
+
+/* Calls poll-loop functions that will cause poll_block() to wake up when
+ * inputdev_run() has work to do. */
+static void
+inputdev_wait(struct inputdev *dev)
+{
+ int flags = POLLIN;
+ if (dev->is_tty && !byteq_is_empty(&dev->outq)) {
+ flags |= POLLOUT;
+ }
+ poll_fd_wait(dev->fd, flags);
+}
+
+/* Scrolls the display left and right automatically to display all the
+ * content. */
+
+enum scanner_state {
+ SCANNER_LEFT, /* Moving left. */
+ SCANNER_RIGHT /* Moving right. */
+};
+
+struct scanner {
+ enum scanner_state state; /* Current state. */
+ int wait; /* No. of cycles to pause before continuing. */
+ long long int last_move; /* Last time the state machine ran. */
+};
+
+static void find_min_max(struct ezio *, int *min, int *max);
+
+static struct scanner *
+scanner_create(void)
+{
+ struct scanner *s = xmalloc(sizeof *s);
+ s->state = SCANNER_RIGHT;
+ s->wait = 0;
+ s->last_move = LLONG_MIN;
+ return s;
+}
+
+static void
+scanner_destroy(struct scanner *s)
+{
+ free(s);
+}
+
+static void
+scanner_run(struct scanner *s, struct ezio *ezio)
+{
+ long long int now = time_msec();
+ if (now >= s->last_move + 750) {
+ s->last_move = now;
+ if (s->wait) {
+ s->wait--;
+ } else {
+ int min, max;
+
+ find_min_max(ezio, &min, &max);
+ if (max - min + 1 <= 16) {
+ ezio->x_ofs = min;
+ return;
+ }
+
+ switch (s->state) {
+ case SCANNER_RIGHT:
+ if (ezio->x_ofs + 15 < max) {
+ ezio->x_ofs++;
+ } else {
+ s->state = SCANNER_LEFT;
+ s->wait = 1;
+ }
+ break;
+
+ case SCANNER_LEFT:
+ if (ezio->x_ofs > min) {
+ ezio->x_ofs--;
+ } else {
+ s->state = SCANNER_RIGHT;
+ s->wait = 1;
+ }
+ break;
+ }
+ }
+ }
+}
+
+static void
+scanner_wait(struct scanner *s)
+{
+ long long int now = time_msec();
+ long long int expires = s->last_move + 750;
+ if (now >= expires) {
+ poll_immediate_wake();
+ } else {
+ poll_timer_wait(expires - now);
+ }
+
+}
+
+static void
+scanner_left(struct scanner *s, struct ezio *ezio)
+{
+ s->wait = 7;
+ if (ezio->x_ofs > 0) {
+ ezio->x_ofs--;
+ }
+}
+
+static void
+scanner_right(struct scanner *s, struct ezio *ezio)
+{
+ s->wait = 7;
+ if (ezio->x_ofs < 40 - 16) {
+ ezio->x_ofs++;
+ }
+}
+
+static void
+find_min_max(struct ezio *ezio, int *min, int *max)
+{
+ int x;
+
+ *min = 0;
+ for (x = 0; x < 40; x++) {
+ if (ezio->chars[0][x] != ' ' || ezio->chars[1][x] != ' ') {
+ *min = x;
+ break;
+ }
+ }
+
+ *max = 15;
+ for (x = 39; x >= 0; x--) {
+ if (ezio->chars[0][x] != ' ' || ezio->chars[1][x] != ' ') {
+ *max = x;
+ break;
+ }
+ }
+
+ if (ezio->show_cursor) {
+ if (ezio->x < *min) {
+ *min = ezio->x;
+ }
+ if (ezio->x > *max) {
+ *max = ezio->x;
+ }
+ }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ VLOG_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"ezio3", required_argument, 0, 'e'},
+ {"input", required_argument, 0, 'i'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'e':
+ ezio_dev = optarg;
+ break;
+
+ case 'i':
+ input_dev = optarg ? optarg : "-";
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ DAEMON_OPTION_HANDLERS
+ VLOG_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: EZIO3 terminal front-end\n"
+ "Provides a front-end to a 16x2 EZIO3 LCD display that makes\n"
+ "it look more like a conventional terminal\n"
+ "usage: %s [OPTIONS] [-- COMMAND [ARG...]]\n"
+ "where COMMAND is a command to run with stdin, stdout, and\n"
+ "stderr directed to the EZIO3 display.\n"
+ "\nSettings (defaults in parentheses):\n"
+ " -e, --ezio=TTY set EZIO3 serial device (/dev/ttyS1)\n"
+ " -i, --input=TERMINAL also read input from TERMINAL;\n"
+ " specify - for stdin, or vt to allocate\n"
+ " and switch to a free virtual terminal\n"
+ "\nOther options:\n"
+ " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n"
+ " -v, --verbose set maximum verbosity level\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ program_name, program_name);
+ exit(EXIT_SUCCESS);
+}