gui: Clear top window only once on resize.
[paraslash.git] / gui.c
diff --git a/gui.c b/gui.c
index 3fc81965233ca2983cb7ae3c33238a6544ef87c9..96043da05b5f9b602e229f131b5248c7f43d6190 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1998-2011 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1998-2014 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -10,6 +10,8 @@
 #include <signal.h>
 #include <sys/types.h>
 #include <curses.h>
+#include <locale.h>
+#include <sys/time.h>
 
 #include "gui.cmdline.h"
 #include "para.h"
 #include "list.h"
 #include "sched.h"
 #include "signal.h"
+#include "ggo.h"
 #include "version.h"
 
 /** define the array of error lists needed by para_gui */
 INIT_GUI_ERRLISTS;
 static char *stat_content[NUM_STAT_ITEMS];
 
-#define STANDARD_STATUS_BAR "para_gui " PACKAGE_VERSION " (hit ? for help)"
-
 static int signal_pipe;
 
-static struct win_data {
+static struct gui_window {
        WINDOW *win;
-       size_t begx;
-       size_t begy;
        size_t cols;
        size_t lines;
 } top, bot, sb, in, sep;
@@ -50,25 +49,31 @@ static struct ringbuffer *bot_win_rb;
 
 static unsigned scroll_position;
 
-static int cmd_died, curses_active;
 static pid_t cmd_pid;
 
-static int command_pipe = -1;
+static int command_fds[2] = {-1, -1};
 static int stat_pipe = -1;
 static struct gui_args_info conf;
 
 enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
 
 
-#define COLOR_STATUSBAR 52
-#define COLOR_COMMAND 53
-#define COLOR_OUTPUT 54
-#define COLOR_MSG 55
-#define COLOR_ERRMSG 56
-#define COLOR_WELCOME 57
-#define COLOR_SEPARATOR 58
-#define COLOR_TOP 59
-#define COLOR_BOT 60
+/**
+ * Codes for various colors.
+ *
+ * Each status item has its own color pair. The ones defined here start at a
+ * higher number so that they do not overlap with these.
+ */
+enum gui_color_pair {
+       COLOR_STATUSBAR = NUM_STAT_ITEMS + 1,
+       COLOR_COMMAND,
+       COLOR_OUTPUT,
+       COLOR_MSG,
+       COLOR_ERRMSG,
+       COLOR_SEPARATOR,
+       COLOR_TOP,
+       COLOR_BOT,
+};
 
 struct gui_command {
        const char *key;
@@ -77,18 +82,6 @@ struct gui_command {
        void (*handler)(void);
 };
 
-struct stat_item {
-       char name[MAXLINE];
-       char prefix[MAXLINE];
-       char postfix[MAXLINE];
-       unsigned y;
-       unsigned x;
-       unsigned len;
-       int fg, bg;
-       int align;
-       char content[MAXLINE];
-};
-
 static struct gui_theme theme;
 
 static int _argc;
@@ -213,6 +206,12 @@ static int find_cmd_byname(char *name)
        return -1;
 }
 
+/* isendwin() returns false before initscr() was called */
+static bool curses_active(void)
+{
+       return top.win && !isendwin();
+}
+
 /* taken from mutt */
 static char *km_keyname(int c)
 {
@@ -287,14 +286,20 @@ static char *configfile_exists(void)
        return file_exists(tmp)? tmp: NULL;
 }
 
-/*
- * print num spaces to curses window
- */
+/* Print given number of spaces to curses window. */
 static void add_spaces(WINDOW* win, unsigned int num)
 {
-       while (num > 0) {
-               num--;
-               waddstr(win, " ");
+       char space[] = "                                ";
+       unsigned sz = sizeof(space) - 1; /* number of spaces */
+
+       while (num >= sz)  {
+               waddstr(win, space);
+               num -= sz;
+       }
+       if (num > 0) {
+               assert(num < sz);
+               space[num] = '\0';
+               waddstr(win, space);
        }
 }
 
@@ -305,11 +310,18 @@ static void add_spaces(WINDOW* win, unsigned int num)
 static int align_str(WINDOW* win, char *str, unsigned int len,
                unsigned int align)
 {
-       int i, num; /* of spaces */
+       int ret, i, num; /* of spaces */
+       size_t width;
 
        if (!win || !str)
-               return -1;
-       num = len - strlen(str);
+               return 0;
+       ret = strwidth(str, &width);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               width = 0;
+               str[0] = '\0';
+       }
+       num = len - width;
        if (num < 0) {
                str[len] = '\0';
                num = 0;
@@ -336,36 +348,33 @@ static int align_str(WINDOW* win, char *str, unsigned int len,
 __printf_2_3 static void print_in_bar(int color, const char *fmt,...)
 {
        char *msg;
+       va_list ap;
 
-       if (!curses_active)
+       if (!curses_active())
                return;
        wattron(in.win, COLOR_PAIR(color));
-       PARA_VSPRINTF(fmt, msg);
+       va_start(ap, fmt);
+       xvasprintf(&msg, fmt, ap);
+       va_end(ap);
        wmove(in.win, 0, 0);
-       align_str(in.win, msg, sb.cols, LEFT);
+       align_str(in.win, msg, in.cols, LEFT);
        free(msg);
        wrefresh(in.win);
 }
 
-/*
- * update the status bar
- */
 static void print_status_bar(void)
 {
        char *tmp;
 
-       if (!curses_active)
-               return;
-       tmp = para_strdup(STANDARD_STATUS_BAR);
+       tmp = para_strdup("para_gui " PACKAGE_VERSION " (hit ? for help)");
        wmove(sb.win, 0, 0);
        align_str(sb.win, tmp, sb.cols, CENTER);
        free(tmp);
-       wrefresh(sb.win);
 }
 
 /*
  * get the number of the oldest rbe that is (partially) visible. On return,
- * lines contains the sum of the number of lines of all visable entries. If the
+ * lines contains the sum of the number of lines of all visible entries. If the
  * first one is only partially visible, lines is greater than bot.lines.
  */
 static int first_visible_rbe(unsigned *lines)
@@ -388,11 +397,14 @@ static int first_visible_rbe(unsigned *lines)
        return RINGBUFFER_SIZE - 1;
 }
 
+/*
+returns number of first visible rbe, *lines is the number of lines drawn.
+ */
 static int draw_top_rbe(unsigned *lines)
 {
-       unsigned len;
-       int offset, fvr = first_visible_rbe(lines);
+       int ret, fvr = first_visible_rbe(lines);
        struct rb_entry *rbe;
+       size_t bytes_to_skip, cells_to_skip, width;
 
        if (fvr < 0)
                return -1;
@@ -400,16 +412,22 @@ static int draw_top_rbe(unsigned *lines)
        rbe = ringbuffer_get(bot_win_rb, fvr);
        if (!rbe)
                return -1;
-       len = strlen(rbe->msg);
        if (*lines > bot.lines) {
-               /* first rbe is only partially visible */
-               offset = (*lines - bot.lines) * bot.cols;
-               assert(offset <= len);
-       } else
-               offset = 0;
+               /* rbe is partially visible multi-line */
+               cells_to_skip = (*lines - bot.lines) * bot.cols;
+               ret = skip_cells(rbe->msg, cells_to_skip, &bytes_to_skip);
+               if (ret < 0)
+                       return ret;
+               ret = strwidth(rbe->msg + bytes_to_skip, &width);
+               if (ret < 0)
+                       return ret;
+       } else {
+               bytes_to_skip = 0;
+               width = rbe->len;
+       }
        wattron(bot.win, COLOR_PAIR(rbe->color));
-       waddstr(bot.win, rbe->msg + offset);
-       *lines = NUM_LINES(len - offset);
+       waddstr(bot.win, rbe->msg + bytes_to_skip);
+       *lines = NUM_LINES(width);
        return fvr;
 }
 
@@ -441,10 +459,15 @@ out:
 
 static void rb_add_entry(int color, char *msg)
 {
-       struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
+       struct rb_entry *old, *new;
        int x, y;
+       size_t len;
+
+       if (strwidth(msg, &len) < 0)
+               return;
+       new = para_malloc(sizeof(struct rb_entry));
        new->color = color;
-       new->len = strlen(msg);
+       new->len = len;
        new->msg = msg;
        old = ringbuffer_add(bot_win_rb, new);
 //     fprintf(stderr, "added: %s\n", new->msg);
@@ -471,30 +494,37 @@ static void rb_add_entry(int color, char *msg)
 __printf_2_3 static void outputf(int color, const char* fmt,...)
 {
        char *msg;
+       va_list ap;
 
-       if (!curses_active)
+       if (!curses_active())
                return;
-       PARA_VSPRINTF(fmt, msg);
+       va_start(ap, fmt);
+       xvasprintf(&msg, fmt, ap);
+       va_end(ap);
        rb_add_entry(color, msg);
        wrefresh(bot.win);
 }
 
-static int add_output_line(char *line, __a_unused void *data)
+static int add_output_line(char *line, void *data)
 {
-       if (!curses_active)
+       int color = *(int *)data? COLOR_ERRMSG : COLOR_OUTPUT;
+
+       if (!curses_active())
                return 1;
-       rb_add_entry(COLOR_OUTPUT, para_strdup(line));
+       rb_add_entry(color, para_strdup(line));
        return 1;
 }
 
 static int loglevel;
 
-__printf_2_3 void curses_log(int ll, const char *fmt,...)
+static __printf_2_3 void curses_log(int ll, const char *fmt,...)
 {
        int color;
        char *msg;
+       va_list ap;
+       unsigned bytes;
 
-       if (ll < loglevel || !curses_active)
+       if (ll < loglevel || !curses_active())
                return;
        switch (ll) {
                case LL_DEBUG:
@@ -505,8 +535,11 @@ __printf_2_3 void curses_log(int ll, const char *fmt,...)
                default:
                        color = COLOR_ERRMSG;
        }
-       PARA_VSPRINTF(fmt, msg);
-       chop(msg);
+       va_start(ap, fmt);
+       bytes = xvasprintf(&msg, fmt, ap);
+       va_end(ap);
+       if (bytes > 0 && msg[bytes - 1] == '\n')
+               msg[bytes - 1] = '\0'; /* cut trailing newline */
        rb_add_entry(color, msg);
        wrefresh(bot.win);
 }
@@ -520,10 +553,10 @@ static void setup_signal_handling(void)
        para_install_sighandler(SIGCHLD);
        para_install_sighandler(SIGWINCH);
        para_install_sighandler(SIGUSR1);
-       para_sigaction(SIGHUP, SIG_IGN);
 }
 
-__noreturn static void do_exit(int ret)
+/* kill every process in the process group and exit */
+__noreturn static void kill_pg_and_die(int ret)
 {
        para_sigaction(SIGTERM, SIG_IGN);
        kill(0, SIGTERM);
@@ -532,17 +565,14 @@ __noreturn static void do_exit(int ret)
 
 static void shutdown_curses(void)
 {
-       if (!curses_active)
-               return;
        def_prog_mode();
-       curses_active = 0;
        endwin();
 }
 
 __noreturn static void finish(int ret)
 {
        shutdown_curses();
-       do_exit(ret);
+       kill_pg_and_die(ret);
 }
 
 /*
@@ -557,16 +587,7 @@ __noreturn __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...)
        va_start(argp, fmt);
        vfprintf(outfd, fmt, argp);
        va_end(argp);
-       do_exit(ret);
-}
-
-static void print_welcome(void)
-{
-       if (loglevel > LL_NOTICE)
-               return;
-       outputf(COLOR_WELCOME, "Welcome to para_gui " PACKAGE_VERSION
-               " \"" CODENAME "\". Theme: %s", theme.name);
-       wclrtoeol(bot.win);
+       kill_pg_and_die(ret);
 }
 
 /*
@@ -574,55 +595,37 @@ static void print_welcome(void)
  */
 static void init_wins(int top_lines)
 {
-       int i;
+       int top_y = 0, bot_y = top_lines + 1, sb_y = LINES - 2,
+               in_y = LINES - 1, sep_y = top_lines;
 
        top.lines = top_lines;
-       top.cols = COLS;
-       top.begy = 0;
-       top.begx = 0;
-
        bot.lines = LINES - top.lines - 3;
-       bot.cols = COLS;
-       bot.begy = top.lines + 1;
-       bot.begx = 0;
-
-       sb.lines = 1;
-       sb.cols = COLS;
-       sb.begy = LINES - 2;
-       sb.begx = 0;
+       sb.lines = in.lines = sep.lines = 1;
 
-       in.lines = 1;
-       in.cols = COLS;
-       in.begy = LINES - 1;
-       in.begx = 0;
-
-       sep.lines = 1;
-       sep.cols = COLS;
-       sep.begy = top.lines;
-       sep.begx = 0;
+       top.cols = bot.cols = sb.cols = in.cols = sep.cols = COLS;
 
        assume_default_colors(theme.default_fg, theme.default_bg);
        if (top.win) {
-               mvwin(top.win, top.begy, top.begx);
                wresize(top.win, top.lines, top.cols);
+               mvwin(top.win, top_y, 0);
 
-               mvwin(sb.win, sb.begy, sb.begx);
                wresize(sb.win, sb.lines, sb.cols);
+               mvwin(sb.win, sb_y, 0);
 
-               mvwin(sep.win, sep.begy, sep.begx);
                wresize(sep.win, sep.lines, sep.cols);
+               mvwin(sep.win, sep_y, 0);
 
-               mvwin(bot.win, bot.begy, bot.begx);
                wresize(bot.win, bot.lines, bot.cols);
+               mvwin(bot.win, bot_y, 0);
 
-               mvwin(in.win, in.begy, in.begx);
                wresize(in.win, in.lines, in.cols);
+               mvwin(in.win, in_y, 0);
        } else {
-               sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx);
-               top.win = newwin(top.lines, top.cols, top.begy, top.begx);
-               bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx);
-               sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx);
-               in.win = newwin(in.lines, in.cols, in.begy, in.begx);
+               sep.win = newwin(sep.lines, sep.cols, sep_y, 0);
+               top.win = newwin(top.lines, top.cols, top_y, 0);
+               bot.win = newwin(bot.lines, bot.cols, bot_y, 0);
+               sb.win = newwin(sb.lines, sb.cols, sb_y, 0);
+               in.win = newwin(in.lines, in.cols, in_y, 0);
                if (!top.win || !bot.win || !sb.win || !in.win || !sep.win)
                        msg_n_exit(1, "Error: Cannot create curses windows\n");
                wclear(bot.win);
@@ -642,17 +645,15 @@ static void init_wins(int top_lines)
                keypad(bot.win, 1);
                keypad(sb.win, 1);
                keypad(in.win, 1);
-               print_status_bar();
        }
        wmove(sep.win, 0, 0);
-       for (i = 1; i <= COLS; i++)
-               waddstr(sep.win, theme.sep_str);
+       whline(sep.win, theme.sep_char, COLS);
        wclear(top.win);
        //wclear(bot.win);
        wnoutrefresh(top.win);
        wnoutrefresh(bot.win);
-       //wnoutrefresh(sb.win);
        print_status_bar();
+       wnoutrefresh(sb.win);
        wnoutrefresh(in.win);
        wnoutrefresh(sep.win);
        doupdate();
@@ -667,7 +668,7 @@ static void print_stat_item(int i)
        struct stat_item_data d = theme.data[i];
        char *c = stat_content[i];
 
-       if (!curses_active || !d.len || !c)
+       if (!curses_active() || !d.len || !c)
                return;
        tmp = make_message("%s%s%s", d.prefix, c, d.postfix);
        wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100);
@@ -746,7 +747,7 @@ static void print_all_items(void)
 {
        int i;
 
-       if (!curses_active)
+       if (!curses_active())
                return;
        FOR_EACH_STATUS_ITEM(i)
                print_stat_item(i);
@@ -785,7 +786,6 @@ static void init_colors_or_die(void)
        init_pair_or_die(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
        init_pair_or_die(COLOR_MSG, theme.msg_fg, theme.msg_bg);
        init_pair_or_die(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
-       init_pair_or_die(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
        init_pair_or_die(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
        init_pair_or_die(COLOR_TOP, theme.default_fg, theme.default_bg);
        init_pair_or_die(COLOR_BOT, theme.default_fg, theme.default_bg);
@@ -794,8 +794,9 @@ static void init_colors_or_die(void)
 /* (Re-)initialize the curses library. */
 static void init_curses(void)
 {
-       curses_active = 1;
-       if (top.win && refresh() == ERR) /* refesh is really needed */
+       if (curses_active())
+               return;
+       if (top.win && refresh() == ERR) /* refresh is really needed */
                msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
        if (LINES < theme.lines_min || COLS < theme.cols_min)
                msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
@@ -820,14 +821,13 @@ static void check_sigchld(void)
 {
        int ret;
        pid_t pid;
+
 reap_next_child:
        ret = para_reap_child(&pid);
        if (ret <= 0)
                return;
-       if (pid == cmd_pid) {
+       if (pid == cmd_pid)
                cmd_pid = 0;
-               cmd_died = 1;
-       }
        goto reap_next_child;
 }
 
@@ -859,25 +859,25 @@ static int check_key_map_args(void)
        for (i = 0; i < conf.key_map_given; ++i) {
                s = conf.key_map_arg[i];
                if (!(*s))
-                       goto err_out;
+                       goto out;
                free(tmp);
                tmp = para_strdup(s);
                if (!split_key_map(tmp, &handler, &arg))
-                       goto err_out;
+                       goto out;
                if (strlen(handler) != 1)
-                       goto err_out;
+                       goto out;
                if (*handler != 'x'
                        && *handler != 'd'
                        && *handler != 'i'
                        && *handler != 'p')
-                       goto err_out;
+                       goto out;
                if (*handler != 'i')
                        continue;
                if (find_cmd_byname(arg) < 0)
-                       goto err_out;
+                       goto out;
        }
        ret = 0;
-err_out:
+out:
        free(tmp);
        return ret;
 }
@@ -893,20 +893,20 @@ static void handle_signal(int sig)
                        "only the good die young (caught SIGTERM))\n");
                return;
        case SIGWINCH:
-               if (curses_active) {
+               if (curses_active()) {
                        shutdown_curses();
                        init_curses();
                        redraw_bot_win();
                }
                return;
        case SIGINT:
-               PARA_WARNING_LOG("caught SIGINT, reset");
+               PARA_WARNING_LOG("caught SIGINT, reset\n");
                /* Nothing to do. SIGINT killed our child which gets noticed
                 * by do_select and resets everything.
                 */
                return;
        case SIGUSR1:
-               PARA_NOTICE_LOG("got SIGUSR1, rereading configuration");
+               PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
                com_reread_conf();
                return;
        case SIGCHLD:
@@ -915,41 +915,45 @@ static void handle_signal(int sig)
        }
 }
 
-static int open_stat_pipe(void)
+static void status_pre_select(fd_set *rfds, int *max_fileno, struct timeval *tv)
 {
-       static int init = 1;
+       static struct timeval next_exec, atm, diff;
        int ret, fds[3] = {0, 1, 0};
        pid_t pid;
 
-       if (init)
-               init = 0;
-       else
-               /*
-                * Sleep a bit to avoid a busy loop. As the call to sleep() may
-                * be interrupted by SIGCHLD, we simply wait until the call
-                * succeeds.
-                */
-               while (sleep(2))
-                       ; /* nothing */
+       if (stat_pipe >= 0)
+               goto success;
+       /* Avoid busy loop */
+       gettimeofday(&atm, NULL);
+       if (tv_diff(&next_exec, &atm, &diff) > 0) {
+               if (tv_diff(&diff, tv, NULL) < 0)
+                       *tv = diff;
+               return;
+       }
+       next_exec.tv_sec = atm.tv_sec + 2;
        ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
        if (ret < 0)
-               return ret;
+               return;
        ret = mark_fd_nonblocking(fds[1]);
-       if (ret >= 0)
-               return fds[1];
-       close(fds[1]);
-       return ret;
+       if (ret < 0) {
+               close(fds[1]);
+               return;
+       }
+       stat_pipe = fds[1];
+success:
+       para_fd_set(stat_pipe, rfds, max_fileno);
 }
 
+#define COMMAND_BUF_SIZE 32768
+
 /*
  * This is the core select loop. Besides the (internal) signal
  * pipe, the following other fds are checked according to the mode:
  *
  * GETCH_MODE: check stdin, return when key is pressed
  *
- * COMMAND_MODE: check command_pipe and stdin. Return when a screen full
- * of output has been read or when other end has closed command pipe or
- * when any key is pressed.
+ * COMMAND_MODE: check command fds and stdin. Return when peer has closed both
+ * stdout and stderr or when any key is pressed.
  *
  * EXTERNAL_MODE: Check only signal pipe. Used when an external command
  * is running. During that time curses is disabled.  Returns when
@@ -958,26 +962,30 @@ static int open_stat_pipe(void)
 static int do_select(int mode)
 {
        fd_set rfds;
-       int ret;
-       int max_fileno;
-       char command_buf[4096] = "";
-       int cbo = 0; /* command buf offset */
+       int ret, i, max_fileno;
+       char command_buf[2][COMMAND_BUF_SIZE] = {"", ""};
+       int cbo[2] = {0, 0}; /* command buf offsets */
        struct timeval tv;
+       unsigned flags[2] = {0, 0}; /* for for_each_line() */
+
 repeat:
        tv.tv_sec = conf.timeout_arg  / 1000;
        tv.tv_usec = (conf.timeout_arg % 1000) * 1000;
 //     ret = refresh_status();
        FD_ZERO(&rfds);
        max_fileno = 0;
-       if (stat_pipe < 0)
-               stat_pipe = open_stat_pipe();
-       if (stat_pipe >= 0)
-               para_fd_set(stat_pipe, &rfds, &max_fileno);
+       status_pre_select(&rfds, &max_fileno, &tv);
        /* signal pipe */
        para_fd_set(signal_pipe, &rfds, &max_fileno);
        /* command pipe only for COMMAND_MODE */
-       if (command_pipe >= 0 && mode == COMMAND_MODE)
-               para_fd_set(command_pipe, &rfds, &max_fileno);
+       if (mode == COMMAND_MODE) {
+               if (command_fds[0] >= 0)
+                       para_fd_set(command_fds[0], &rfds, &max_fileno);
+               if (command_fds[1] >= 0)
+                       para_fd_set(command_fds[1], &rfds, &max_fileno);
+       }
+       if (mode == GETCH_MODE || mode == COMMAND_MODE)
+               para_fd_set(STDIN_FILENO, &rfds, &max_fileno);
        ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
        if (ret <= 0)
                goto check_return; /* skip fd checks */
@@ -986,21 +994,37 @@ repeat:
        if (ret > 0)
                handle_signal(ret);
        /* read command pipe if ready */
-       if (command_pipe >= 0 && mode == COMMAND_MODE) {
-               size_t sz;
-               ret = read_nonblock(command_pipe, command_buf + cbo,
-                       sizeof(command_buf) - 1 - cbo, &rfds, &sz);
-               cbo += sz;
-               sz = cbo;
-               cbo = for_each_line(command_buf, cbo, &add_output_line, NULL);
-               if (sz != cbo)
-                       wrefresh(bot.win);
-               if (ret < 0) {
-                       PARA_NOTICE_LOG("closing command pipe: %s",
-                               para_strerror(-ret));
-                       close(command_pipe);
-                       command_pipe = -1;
-                       return 0;
+       if (mode == COMMAND_MODE) {
+               for (i = 0; i < 2; i++) {
+                       size_t sz;
+                       if (command_fds[i] < 0)
+                               continue;
+                       ret = read_nonblock(command_fds[i],
+                               command_buf[i] + cbo[i],
+                               COMMAND_BUF_SIZE - 1 - cbo[i], &rfds, &sz);
+                       cbo[i] += sz;
+                       sz = cbo[i];
+                       cbo[i] = for_each_line(flags[i], command_buf[i], cbo[i],
+                               add_output_line, &i);
+                       if (sz != cbo[i]) { /* at least one line found */
+                               wrefresh(bot.win);
+                               flags[i] = 0;
+                       }
+                       if (ret < 0) {
+                               PARA_NOTICE_LOG("closing command fd %d: %s\n",
+                                       i, para_strerror(-ret));
+                               close(command_fds[i]);
+                               command_fds[i] = -1;
+                               flags[i] = 0;
+                               cbo[i] = 0;
+                               if (command_fds[!i] < 0) /* both fds closed */
+                                       return 0;
+                       }
+                       if (cbo[i] == COMMAND_BUF_SIZE - 1) {
+                               PARA_NOTICE_LOG("discarding overlong line\n");
+                               cbo[i] = 0;
+                               flags[i] = FELF_DISCARD_FIRST;
+                       }
                }
        }
        ret = read_stat_pipe(&rfds);
@@ -1019,10 +1043,6 @@ check_return:
        case COMMAND_MODE:
                ret = wgetch(top.win);
                if (ret != ERR && ret != KEY_RESIZE) {
-                       if (command_pipe) {
-                               close(command_pipe);
-                               command_pipe = -1;
-                       }
                        if (cmd_pid)
                                kill(cmd_pid, SIGTERM);
                        return -1;
@@ -1034,87 +1054,78 @@ check_return:
                        return ret;
                break;
        case EXTERNAL_MODE:
-               if (cmd_died) {
-                       cmd_died = 0;
+               if (cmd_pid == 0)
                        return 0;
-               }
        }
        goto repeat;
 }
 
-/*
- * read from command pipe and print data to bot window
- */
-static int send_output(void)
+/* read from command pipe and print data to bot window */
+static void exec_and_display_cmd(const char *cmd)
 {
-       int ret;
+       int ret, fds[3] = {0, 1, 1};
 
-       if (command_pipe < 0)
-               return 0;
-       ret = mark_fd_nonblocking(command_pipe);
-       if (ret < 0) {
-               close(command_pipe);
-               return ret;
-       }
+       outputf(COLOR_COMMAND, "%s", cmd);
+       ret = para_exec_cmdline_pid(&cmd_pid, cmd, fds);
+       if (ret < 0)
+               return;
+       ret = mark_fd_nonblocking(fds[1]);
+       if (ret < 0)
+               goto fail;
+       ret = mark_fd_nonblocking(fds[2]);
+       if (ret < 0)
+               goto fail;
+       command_fds[0] = fds[1];
+       command_fds[1] = fds[2];
        if (do_select(COMMAND_MODE) >= 0)
-               PARA_INFO_LOG("command complete");
+               PARA_INFO_LOG("command complete\n");
        else
-               PARA_NOTICE_LOG("command aborted");
+               PARA_NOTICE_LOG("command aborted\n");
        print_in_bar(COLOR_MSG, " ");
-       return 1;
+       return;
+fail:
+       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       close(command_fds[0]);
+       close(command_fds[1]);
 }
 
-static int client_cmd_cmdline(char *cmd)
+static void para_cmd(char *cmd)
 {
-       int ret, fds[3] = {0, 1, 0};
-       char *c = make_message(BINDIR "/para_client -- %s", cmd);
+       char *c;
 
-       outputf(COLOR_COMMAND, "%s", c);
        print_in_bar(COLOR_MSG, "executing client command, hit any key to abort\n");
-       ret = para_exec_cmdline_pid(&cmd_pid, c, fds);
+       c = make_message(BINDIR "/para_client -- %s", cmd);
+       exec_and_display_cmd(c);
        free(c);
-       if (ret < 0)
-               return -1;
-       command_pipe = fds[1];
-       return send_output();
 }
 
-/*
- * exec command and print output to bot win
- */
-static int display_cmd(char *cmd)
+static void display_cmd(char *cmd)
 {
-       int fds[3] = {0, 1, 0};
-
        print_in_bar(COLOR_MSG, "executing display command, hit any key to abort");
-       outputf(COLOR_COMMAND, "%s", cmd);
-       if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
-               return -1;
-       command_pipe = fds[1];
-       return send_output();
+       exec_and_display_cmd(cmd);
 }
 
 /*
  * shutdown curses and stat pipe before executing external commands
  */
-static int external_cmd(char *cmd)
+static void external_cmd(char *cmd)
 {
        int fds[3] = {-1, -1, -1};
 
        if (cmd_pid)
-               return -1;
+               return;
        shutdown_curses();
-       para_exec_cmdline_pid(&cmd_pid, cmd, fds);
-       cmd_died = 0;
+       if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
+               return;
        do_select(EXTERNAL_MODE);
        init_curses();
-       return 0;
 }
 
 static void print_scroll_msg(void)
 {
        unsigned lines_total, filled = ringbuffer_filled(bot_win_rb);
        int first_rbe = first_visible_rbe(&lines_total);
+
        print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe,
                filled - scroll_position, ringbuffer_filled(bot_win_rb));
 }
@@ -1131,7 +1142,7 @@ static void com_scroll_top(void)
                struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
                if (!rbe)
                        break;
-               lines += NUM_LINES(strlen(rbe->msg));
+               lines += NUM_LINES(rbe->len);
        }
        i++;
        if (lines > 0 && scroll_position != i) {
@@ -1158,11 +1169,12 @@ static void com_page_down(void)
 {
        unsigned lines = 0;
        int i = scroll_position;
+
        while (lines < bot.lines && --i > 0) {
                struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
                if (!rbe)
                        break;
-               lines += NUM_LINES(strlen(rbe->msg));
+               lines += NUM_LINES(rbe->len);
        }
        if (lines) {
                scroll_position = i;
@@ -1280,7 +1292,7 @@ static void com_ll_incr(void)
  */
 static void com_reread_conf(void)
 {
-       char *cf =configfile_exists();
+       char *cf = configfile_exists();
        struct gui_cmdline_parser_params params = {
                .override = 1,
                .initialize = 1,
@@ -1290,16 +1302,19 @@ static void com_reread_conf(void)
        };
 
        if (!cf) {
-               PARA_WARNING_LOG("there is no configuration to read");
+               PARA_WARNING_LOG("there is no configuration to read\n");
                return;
        }
-       PARA_INFO_LOG("rereading command line options and config file");
+       PARA_INFO_LOG("rereading command line options and config file\n");
        gui_cmdline_parser_ext(_argc, _argv, &conf, &params);
-       if (gui_cmdline_parser_config_file(cf, &conf, &params) != 0) {
-               PARA_EMERG_LOG("errors in config file");
-               finish(EXIT_FAILURE);
-       }
-       PARA_NOTICE_LOG("config file reloaded");
+       /*
+        * Despite .print_errors is set to 0, gengetopt will print to stderr
+        * anyway, and exit on errors. So we have to shutdown curses first.
+        */
+       shutdown_curses();
+       gui_cmdline_parser_config_file(cf, &conf, &params);
+       init_curses();
+       PARA_NOTICE_LOG("config file reloaded\n");
        if (check_key_map_args() < 0)
                finish(EXIT_FAILURE);
 }
@@ -1344,11 +1359,10 @@ static void com_help(void)
 static void com_shrink_top_win(void)
 {
        if (top.lines <= theme.top_lines_min) {
-               PARA_WARNING_LOG("can not decrease top window");
+               PARA_WARNING_LOG("can not decrease top window\n");
                return;
        }
        init_wins(top.lines - 1);
-       wclear(top.win);
        print_all_items();
        print_in_bar(COLOR_MSG, "%s", "decreased top window");
 }
@@ -1356,19 +1370,17 @@ static void com_shrink_top_win(void)
 static void com_enlarge_top_win(void)
 {
        if (bot.lines < 3) {
-               PARA_WARNING_LOG("can not increase top window");
+               PARA_WARNING_LOG("can not increase top window\n");
                return;
        }
        init_wins(top.lines + 1);
-       wclear(top.win);
        print_all_items();
        print_in_bar(COLOR_MSG, "increased top window");
 }
 
 static void com_version(void)
 {
-       print_in_bar(COLOR_MSG, "para_gui " PACKAGE_VERSION " \""
-               CODENAME "\"");
+       print_in_bar(COLOR_MSG, "%s", version_single_line("gui"));
 }
 
 __noreturn static void com_quit(void)
@@ -1382,34 +1394,23 @@ static void com_refresh(void)
        init_curses();
 }
 
-static void change_theme(int next)
-{
-       if (next)
-               next_theme(&theme);
-       else
-               prev_theme(&theme);
-       /* This seems to be needed twice, why? */
-       com_refresh();
-       com_refresh();
-       PARA_NOTICE_LOG("new theme: %s", theme.name);
-}
-
 static void com_next_theme(void)
 {
-       change_theme(1);
+       theme_next(&theme);
+       com_refresh();
 }
 
 static void com_prev_theme(void)
 {
-       change_theme(0);
+       theme_prev(&theme);
+       com_refresh();
 }
 
-
 static void handle_command(int c)
 {
        int i;
 
-       /* first check user's key bindings */
+       /* first check user-defined key bindings */
        for (i = 0; i < conf.key_map_given; ++i) {
                char *tmp, *handler, *arg;
 
@@ -1427,7 +1428,7 @@ static void handle_command(int c)
                else if (*handler == 'x')
                        external_cmd(arg);
                else if (*handler == 'p')
-                       client_cmd_cmdline(arg);
+                       para_cmd(arg);
                else if (*handler == 'i') {
                        int num = find_cmd_byname(arg);
                        if (num >= 0)
@@ -1447,6 +1448,15 @@ static void handle_command(int c)
                km_keyname(c));
 }
 
+__noreturn static void print_help_and_die(void)
+{
+       struct ggo_help h = DEFINE_GGO_HELP(gui);
+       bool d = conf.detailed_help_given;
+
+       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
+       exit(0);
+}
+
 int main(int argc, char *argv[])
 {
        int ret;
@@ -1455,9 +1465,11 @@ int main(int argc, char *argv[])
        _argc = argc;
        _argv = argv;
 
-       if (gui_cmdline_parser(argc, argv, &conf) != 0)
-               exit(EXIT_FAILURE);
-       HANDLE_VERSION_FLAG("gui", conf);
+       gui_cmdline_parser(argc, argv, &conf); /* exits on errors */
+       loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       version_handle_flag("gui", conf.version_given);
+       if (conf.help_given || conf.detailed_help_given)
+               print_help_and_die();
        cf = configfile_exists();
        if (!cf && conf.config_file_given) {
                fprintf(stderr, "can not read config file %s\n",
@@ -1472,21 +1484,19 @@ int main(int argc, char *argv[])
                        .check_ambiguity = 0,
                        .print_errors = 1,
                };
-               if (gui_cmdline_parser_config_file(cf, &conf, &params) != 0)
-                       exit(EXIT_FAILURE);
+               gui_cmdline_parser_config_file(cf, &conf, &params);
+               loglevel = get_loglevel_by_name(conf.loglevel_arg);
        }
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
        if (check_key_map_args() < 0) {
                fprintf(stderr, "invalid key map\n");
                exit(EXIT_FAILURE);
        }
-       init_theme_or_die(conf.theme_arg, &theme);
-       top.lines = theme.top_lines_default;
+       theme_init(conf.theme_arg, &theme);
        setup_signal_handling();
        bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
+       setlocale(LC_CTYPE, "");
        initscr(); /* needed only once, always successful */
        init_curses();
-       print_welcome();
        for (;;) {
                print_status_bar();
                ret = do_select(GETCH_MODE);