gui: Discard overlong input lines.
[paraslash.git] / gui.c
diff --git a/gui.c b/gui.c
index 5b44819c05afc074fadca3a5a7d5c1ece9fe8b8f..728fb9b72c815237ca5aa51c9d590afa312e0464 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1998-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1998-2013 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -9,7 +9,6 @@
 #include <regex.h>
 #include <signal.h>
 #include <sys/types.h>
-#include <dirent.h>
 #include <curses.h>
 
 #include "gui.cmdline.h"
@@ -22,6 +21,7 @@
 #include "list.h"
 #include "sched.h"
 #include "signal.h"
+#include "version.h"
 
 /** define the array of error lists needed by para_gui */
 INIT_GUI_ERRLISTS;
@@ -33,10 +33,10 @@ static int signal_pipe;
 
 static struct win_data {
        WINDOW *win;
-       NCURSES_SIZE_T begx;
-       NCURSES_SIZE_T begy;
-       NCURSES_SIZE_T cols;
-       NCURSES_SIZE_T lines;
+       size_t begx;
+       size_t begy;
+       size_t cols;
+       size_t lines;
 } top, bot, sb, in, sep;
 
 #define RINGBUFFER_SIZE 512
@@ -53,8 +53,8 @@ static unsigned scroll_position;
 static int cmd_died, curses_active;
 static pid_t cmd_pid;
 
-static int command_pipe = -1;
-static int audiod_pipe = -1;
+static int command_fds[2];
+static int stat_pipe = -1;
 static struct gui_args_info conf;
 
 enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
@@ -99,7 +99,7 @@ static void com_reread_conf(void);
 static void com_enlarge_top_win(void);
 static void com_shrink_top_win(void);
 static void com_version(void);
-static void com_quit(void);
+__noreturn static void com_quit(void);
 static void com_refresh(void);
 static void com_ll_incr(void);
 static void com_ll_decr(void);
@@ -109,6 +109,8 @@ static void com_scroll_up(void);
 static void com_scroll_down(void);
 static void com_page_up(void);
 static void com_page_down(void);
+static void com_cancel_scrolling(void);
+static void com_scroll_top(void);
 
 static struct gui_command command_list[] = {
        {
@@ -186,25 +188,21 @@ static struct gui_command command_list[] = {
                .name = "page_down",
                .description = "scroll down one page",
                .handler = com_page_down
+       }, {
+               .key = "<home>",
+               .name = "scroll_top",
+               .description = "scroll to top of buffer",
+               .handler = com_scroll_top
+       }, {
+               .key = "<end>",
+               .name = "cancel_scroll",
+               .description = "deactivate scroll mode",
+               .handler = com_cancel_scrolling
        }, {
                .handler = NULL
        }
 };
 
-static int para_open_audiod_pipe(char *cmd)
-{
-       int fds[3] = {0, 1, 0};
-       pid_t pid;
-       int ret = para_exec_cmdline_pid(&pid, cmd, fds);
-       if (ret < 0)
-               return ret;
-       ret = mark_fd_nonblocking(fds[1]);
-       if (ret > 0)
-               return fds[1];
-       close(fds[1]);
-       return ret;
-}
-
 static int find_cmd_byname(char *name)
 {
        int i;
@@ -244,6 +242,14 @@ static char *km_keyname(int c)
                sprintf(buf, "<ppage>");
                return buf;
        }
+       if (c == KEY_HOME) {
+               sprintf(buf, "<home>");
+               return buf;
+       }
+       if (c == KEY_END) {
+               sprintf(buf, "<end>");
+               return buf;
+       }
        if (c < 256 && c > -128 && iscntrl((unsigned char) c)) {
                if (c < 0)
                        c += 256;
@@ -308,9 +314,9 @@ static int align_str(WINDOW* win, char *str, unsigned int len,
                str[len] = '\0';
                num = 0;
        }
-       /* replace newlines by spaces */
+       /* replace control characters by spaces */
        for (i = 0; i < len && str[i]; i++) {
-               if (str[i] == '\n')
+               if (str[i] == '\n' || str[i] == '\r' || str[i] == '\f')
                        str[i] = ' ';
        }
        if (align == LEFT) {
@@ -330,11 +336,14 @@ 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)
                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);
        free(msg);
@@ -394,11 +403,13 @@ static int draw_top_rbe(unsigned *lines)
        rbe = ringbuffer_get(bot_win_rb, fvr);
        if (!rbe)
                return -1;
-       /* first rbe might be only partially visible */
-       offset = (*lines - bot.lines) * bot.cols;
        len = strlen(rbe->msg);
-       if (offset < 0 || len < offset)
-               return -1;
+       if (*lines > bot.lines) {
+               /* first rbe is only partially visible */
+               offset = (*lines - bot.lines) * bot.cols;
+               assert(offset <= len);
+       } else
+               offset = 0;
        wattron(bot.win, COLOR_PAIR(rbe->color));
        waddstr(bot.win, rbe->msg + offset);
        *lines = NUM_LINES(len - offset);
@@ -463,28 +474,33 @@ 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)
                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)
 {
+       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 para_log(int ll, const char *fmt,...)
+__printf_2_3 void curses_log(int ll, const char *fmt,...)
 {
        int color;
        char *msg;
+       va_list ap;
 
        if (ll < loglevel || !curses_active)
                return;
@@ -497,11 +513,14 @@ __printf_2_3 void para_log(int ll, const char *fmt,...)
                default:
                        color = COLOR_ERRMSG;
        }
-       PARA_VSPRINTF(fmt, msg);
+       va_start(ap, fmt);
+       xvasprintf(&msg, fmt, ap);
+       va_end(ap);
        chop(msg);
        rb_add_entry(color, msg);
        wrefresh(bot.win);
 }
+__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
 
 static void setup_signal_handling(void)
 {
@@ -649,14 +668,6 @@ static void init_wins(int top_lines)
        doupdate();
 }
 
-static void check_geometry(void)
-{
-       if (LINES < theme.lines_min || COLS < theme.cols_min)
-               msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
-                       " (need at least %dx%d)\n", COLS, LINES,
-                       theme.cols_min, theme.lines_min);
-}
-
 /*
  * Print stat item #i to curses window
  */
@@ -708,12 +719,15 @@ print:
        return 1;
 }
 
-static int read_audiod_pipe(int fd)
+static int read_stat_pipe(fd_set *rfds)
 {
        static char *buf;
        static int bufsize, loaded;
-       int ret;
+       int ret, ret2;
+       size_t sz;
 
+       if (stat_pipe < 0)
+               return 0;
        if (loaded >= bufsize) {
                if (bufsize > 1000 * 1000) {
                        loaded = 0;
@@ -723,14 +737,18 @@ static int read_audiod_pipe(int fd)
                buf = para_realloc(buf, bufsize);
        }
        assert(loaded < bufsize);
-       ret = read(fd, buf + loaded, bufsize - loaded);
-       if (ret <= 0)
-               return ret;
-       loaded += ret;
-       ret = for_each_stat_item(buf, loaded, update_item);
-       if (ret < 0)
-               return ret;
-       loaded = ret;
+       ret = read_nonblock(stat_pipe, buf + loaded, bufsize - loaded,
+               rfds, &sz);
+       loaded += sz;
+       ret2 = for_each_stat_item(buf, loaded, update_item);
+       if (ret < 0 || ret2 < 0) {
+               loaded = 0;
+               return ret2 < 0? ret2 : ret;
+       }
+       sz = ret2; /* what is left */
+       if (sz > 0 && sz < loaded)
+               memmove(buf, buf + loaded - sz, sz);
+       loaded = sz;
        return 1;
 }
 
@@ -754,53 +772,60 @@ static void clear_all_items(void)
        }
 }
 
-static void init_colors(void)
+static void init_pair_or_die(short pair, short f, short b)
+{
+       if (init_pair(pair, f, b) == ERR)
+               msg_n_exit(EXIT_FAILURE, "fatal: init_pair() failed\n");
+}
+
+static void init_colors_or_die(void)
 {
        int i;
 
        if (!has_colors())
-               msg_n_exit(EXIT_FAILURE, "Error: No color term\n");
-       start_color();
+               msg_n_exit(EXIT_FAILURE, "fatal: No color term\n");
+       if (start_color() == ERR)
+               msg_n_exit(EXIT_FAILURE, "fatal: failed to start colors\n");
        FOR_EACH_STATUS_ITEM(i)
                if (theme.data[i].len)
-                       init_pair(i + 1, theme.data[i].fg, theme.data[i].bg);
-       init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
-       init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
-       init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
-       init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg);
-       init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
-       init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
-       init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
-       init_pair(COLOR_TOP, theme.default_fg, theme.default_bg);
-       init_pair(COLOR_BOT, theme.default_fg, theme.default_bg);
+                       init_pair_or_die(i + 1, theme.data[i].fg,
+                               theme.data[i].bg);
+       init_pair_or_die(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
+       init_pair_or_die(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
+       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);
 }
 
-/*
- * (re-)initialize the curses library  FIXME: Error checking
- */
+/* (Re-)initialize the curses library. */
 static void init_curses(void)
 {
        curses_active = 1;
-       if (top.win && refresh() == ERR) /* refesh is really needed */
+       if (top.win && refresh() == ERR) /* refesh is really needed */
                msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
-       }
-       check_geometry();
+       if (LINES < theme.lines_min || COLS < theme.cols_min)
+               msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
+                       " (need at least %dx%d)\n", COLS, LINES,
+                       theme.cols_min, theme.lines_min);
        curs_set(0); /* make cursor invisible, ignore errors */
-//     if (noraw() == ERR);
-//             msg_n_exit(EXIT_FAILURE, "can not place terminal out of "
-//                     "raw mode\n");
-       nonl(); /* tell curses not to do NL->CR/NL on output */
-       noecho(); /* don't echo input */
-       cbreak(); /* take input chars one at a time, no wait for \n */
-       //reset_prog_mode();
-       init_colors();
-       clear();
+       nonl(); /* do not NL->CR/NL on output, always returns OK */
+       /* don't echo input */
+       if (noecho() == ERR)
+               msg_n_exit(EXIT_FAILURE, "fatal: noecho() failed\n");
+       /* take input chars one at a time, no wait for \n */
+       if (cbreak() == ERR)
+               msg_n_exit(EXIT_FAILURE, "fatal: cbreak() failed\n");
+       init_colors_or_die();
+       clear(); /* ignore non-fatal errors */
        init_wins(theme.top_lines_default);
        print_all_items();
-       noecho(); /* don't echo input */
+       // noecho(); /* don't echo input */
 }
 
-
 static void check_sigchld(void)
 {
        int ret;
@@ -900,26 +925,42 @@ static void handle_signal(int sig)
        }
 }
 
-static int open_audiod_pipe(void)
+static int open_stat_pipe(void)
 {
        static int init = 1;
+       int ret, fds[3] = {0, 1, 0};
+       pid_t pid;
 
        if (init)
                init = 0;
        else
-               sleep(1);
-       return para_open_audiod_pipe(conf.stat_cmd_arg);
+               /*
+                * 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 */
+       ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
+       if (ret < 0)
+               return ret;
+       ret = mark_fd_nonblocking(fds[1]);
+       if (ret >= 0)
+               return fds[1];
+       close(fds[1]);
+       return ret;
 }
 
+#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
@@ -928,76 +969,89 @@ static int open_audiod_pipe(void)
 static int do_select(int mode)
 {
        fd_set rfds;
-       int ret;
-       int max_fileno, cp_numread = 1;
-       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;
+
 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;
-       /* audiod pipe */
-       if (audiod_pipe < 0)
-               audiod_pipe = open_audiod_pipe();
-       if (audiod_pipe >= 0)
-               para_fd_set(audiod_pipe, &rfds, &max_fileno);
+       if (stat_pipe < 0)
+               stat_pipe = open_stat_pipe();
+       if (stat_pipe >= 0)
+               para_fd_set(stat_pipe, &rfds, &max_fileno);
        /* 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);
+       }
        ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
        if (ret <= 0)
                goto check_return; /* skip fd checks */
        /* signals */
-       if (FD_ISSET(signal_pipe, &rfds)) {
-               int sig_nr = para_next_signal();
-               if (sig_nr > 0)
-                       handle_signal(sig_nr);
-       }
+       ret = para_next_signal(&rfds);
+       if (ret > 0)
+               handle_signal(ret);
        /* read command pipe if ready */
-       if (command_pipe >= 0 && mode == COMMAND_MODE &&
-                       FD_ISSET(command_pipe, &rfds)) {
-               cp_numread = read(command_pipe, command_buf + cbo,
-                       sizeof(command_buf) - 1 - cbo);
-               if (cp_numread >= 0)
-                       cbo += cp_numread;
-               else {
-                       if (cp_numread < 0)
-                               PARA_ERROR_LOG("read error (%d)", cp_numread);
-                       close(command_pipe);
-                       command_pipe = -1;
+       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(command_buf[i], cbo[i],
+                               add_output_line, &i);
+                       if (sz != cbo[i])
+                               wrefresh(bot.win);
+                       if (ret < 0) {
+                               PARA_NOTICE_LOG("closing command fd %d: %s",
+                                       i, para_strerror(-ret));
+                               close(command_fds[i]);
+                               command_fds[i] = -1;
+                               if (command_fds[!i] < 0) /* both fds closed */
+                                       return 0;
+                       }
+                       if (cbo[i] == COMMAND_BUF_SIZE - 1) {
+                               PARA_NOTICE_LOG("discarding overlong line");
+                               cbo[i] = 0;
+                       }
                }
        }
-       if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds))
-               if (read_audiod_pipe(audiod_pipe) <= 0) {
-                       close(audiod_pipe);
-                       audiod_pipe = -1;
-                       clear_all_items();
-                       free(stat_content[SI_BASENAME]);
-                       stat_content[SI_BASENAME] =
-                               para_strdup("audiod not running!?");
-                       print_all_items();
-               }
+       ret = read_stat_pipe(&rfds);
+       if (ret < 0) {
+               PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret));
+               close(stat_pipe);
+               stat_pipe = -1;
+               clear_all_items();
+               free(stat_content[SI_BASENAME]);
+               stat_content[SI_BASENAME] =
+                       para_strdup("stat command terminated!?");
+               print_all_items();
+       }
 check_return:
        switch (mode) {
        case COMMAND_MODE:
-               if (cp_numread <= 0 && !cbo) /* command complete */
-                       return 0;
-               if (cbo)
-                       cbo = for_each_line(command_buf, cbo,
-                               &add_output_line, NULL);
-               if (cp_numread <= 0)
-                       cbo = 0;
-               wrefresh(bot.win);
                ret = wgetch(top.win);
                if (ret != ERR && ret != KEY_RESIZE) {
-                       if (command_pipe) {
-                               close(command_pipe);
-                               command_pipe = -1;
+                       if (command_fds[0] >= 0) {
+                               close(command_fds[0]);
+                               command_fds[0] = -1;
+                       }
+                       if (command_fds[1] >= 0) {
+                               close(command_fds[1]);
+                               command_fds[1] = -1;
                        }
                        if (cmd_pid)
                                kill(cmd_pid, SIGTERM);
@@ -1021,28 +1075,31 @@ check_return:
 /*
  * read from command pipe and print data to bot window
  */
-static int send_output(void)
+static void send_output(void)
 {
        int ret;
 
-       if (command_pipe < 0)
-               return 0;
-       ret = mark_fd_nonblocking(command_pipe);
-       if (ret < 0) {
-               close(command_pipe);
-               return ret;
-       }
+       ret = mark_fd_nonblocking(command_fds[0]);
+       if (ret < 0)
+               goto fail;
+       ret = mark_fd_nonblocking(command_fds[1]);
+       if (ret < 0)
+               goto fail;
        if (do_select(COMMAND_MODE) >= 0)
                PARA_INFO_LOG("command complete");
        else
                PARA_NOTICE_LOG("command aborted");
        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};
+       int ret, fds[3] = {0, 1, 1};
        char *c = make_message(BINDIR "/para_client -- %s", cmd);
 
        outputf(COLOR_COMMAND, "%s", c);
@@ -1050,41 +1107,43 @@ static int client_cmd_cmdline(char *cmd)
        ret = para_exec_cmdline_pid(&cmd_pid, c, fds);
        free(c);
        if (ret < 0)
-               return -1;
-       command_pipe = fds[1];
-       return send_output();
+               return;
+       command_fds[0] = fds[1];
+       command_fds[1] = fds[2];
+       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};
+       int fds[3] = {0, 1, 1};
 
        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();
+               return;
+       command_fds[0] = fds[1];
+       command_fds[1] = fds[2];
+       send_output();
 }
 
 /*
  * 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);
+       if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
+               return;
        cmd_died = 0;
        do_select(EXTERNAL_MODE);
        init_curses();
-       return 0;
 }
 
 static void print_scroll_msg(void)
@@ -1095,6 +1154,41 @@ static void print_scroll_msg(void)
                filled - scroll_position, ringbuffer_filled(bot_win_rb));
 }
 
+static void com_scroll_top(void)
+{
+       int i = RINGBUFFER_SIZE - 1;
+       unsigned lines = 0;
+
+       while (i > 0 && !ringbuffer_get(bot_win_rb, i))
+               i--;
+       /* i is oldest entry */
+       for (; lines < bot.lines && i >= 0; i--) {
+               struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+               if (!rbe)
+                       break;
+               lines += NUM_LINES(strlen(rbe->msg));
+       }
+       i++;
+       if (lines > 0 && scroll_position != i) {
+               scroll_position = i;
+               redraw_bot_win();
+               print_scroll_msg();
+               return;
+       }
+       print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+}
+
+static void com_cancel_scrolling(void)
+{
+
+       if (scroll_position == 0) {
+               print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+               return;
+       }
+       scroll_position = 0;
+       redraw_bot_win();
+}
+
 static void com_page_down(void)
 {
        unsigned lines = 0;
@@ -1226,7 +1320,8 @@ static void com_reread_conf(void)
                .override = 1,
                .initialize = 1,
                .check_required = 0,
-               .check_ambiguity = 0
+               .check_ambiguity = 0,
+               .print_errors = 0,
        };
 
        if (!cf) {
@@ -1234,8 +1329,11 @@ static void com_reread_conf(void)
                return;
        }
        PARA_INFO_LOG("rereading command line options and config file");
-       gui_cmdline_parser(_argc, _argv, &conf);
-       gui_cmdline_parser_config_file(cf, &conf, &params);
+       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");
        if (check_key_map_args() < 0)
                finish(EXIT_FAILURE);
@@ -1348,31 +1446,30 @@ static void handle_command(int c)
 
        /* first check user's key bindings */
        for (i = 0; i < conf.key_map_given; ++i) {
-               char tmp[MAXLINE], *handler, *arg;
+               char *tmp, *handler, *arg;
 
-               strcpy(tmp, conf.key_map_arg[i]);
-               if (!split_key_map(tmp, &handler, &arg))
+               tmp = para_strdup(conf.key_map_arg[i]);
+               if (!split_key_map(tmp, &handler, &arg)) {
+                       free(tmp);
                        return;
-               if (!strcmp(tmp, km_keyname(c))) {
-                       if (*handler == 'd') {
-                               display_cmd(arg);
-                               return;
-                       }
-                       if (*handler == 'x') {
-                               external_cmd(arg);
-                               return;
-                       }
-                       if (*handler == 'p') {
-                               client_cmd_cmdline(arg);
-                               return;
-                       }
-                       if (*handler == 'i') {
-                               int num = find_cmd_byname(arg);
-                               if (num >= 0)
-                                       command_list[num].handler();
-                               return;
-                       }
                }
+               if (strcmp(tmp, km_keyname(c))) {
+                       free(tmp);
+                       continue;
+               }
+               if (*handler == 'd')
+                       display_cmd(arg);
+               else if (*handler == 'x')
+                       external_cmd(arg);
+               else if (*handler == 'p')
+                       para_cmd(arg);
+               else if (*handler == 'i') {
+                       int num = find_cmd_byname(arg);
+                       if (num >= 0)
+                               command_list[num].handler();
+               }
+               free(tmp);
+               return;
        }
        /* not found, check internal key bindings */
        for (i = 0; command_list[i].handler; i++) {
@@ -1393,17 +1490,8 @@ int main(int argc, char *argv[])
        _argc = argc;
        _argv = argv;
 
-       if (gui_cmdline_parser(argc, argv, &conf)) {
-               fprintf(stderr, "parse error while reading command line\n");
-               exit(EXIT_FAILURE);
-       }
+       gui_cmdline_parser(argc, argv, &conf); /* exits on errors */
        HANDLE_VERSION_FLAG("gui", conf);
-       init_theme(0, &theme);
-       top.lines = theme.top_lines_default;
-       if (check_key_map_args() < 0) {
-               fprintf(stderr, "invalid key map\n");
-               exit(EXIT_FAILURE);
-       }
        cf = configfile_exists();
        if (!cf && conf.config_file_given) {
                fprintf(stderr, "can not read config file %s\n",
@@ -1415,15 +1503,19 @@ int main(int argc, char *argv[])
                        .override = 0,
                        .initialize = 0,
                        .check_required = 0,
-                       .check_ambiguity = 0
+                       .check_ambiguity = 0,
+                       .print_errors = 1,
                };
-               gui_cmdline_parser_config_file(cf, &conf, &params);
+               if (gui_cmdline_parser_config_file(cf, &conf, &params) != 0)
+                       exit(EXIT_FAILURE);
        }
        loglevel = get_loglevel_by_name(conf.loglevel_arg);
        if (check_key_map_args() < 0) {
-               fprintf(stderr, "invalid key map in config file\n");
+               fprintf(stderr, "invalid key map\n");
                exit(EXIT_FAILURE);
        }
+       init_theme_or_die(conf.theme_arg, &theme);
+       top.lines = theme.top_lines_default;
        setup_signal_handling();
        bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
        initscr(); /* needed only once, always successful */