Merge branch 't/testsuite'
[paraslash.git] / gui.c
diff --git a/gui.c b/gui.c
index f08976d2575d96afca710fb5d0b7b4bdd9246ab4..38addd9815cc5bf2775c80278679a1e9c64e872e 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -1,19 +1,21 @@
 /*
- * Copyright (C) 1998-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1998-2011 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file gui.c Curses-based interface for paraslash. */
 
+#include <regex.h>
 #include <signal.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <curses.h>
+
 #include "gui.cmdline.h"
 #include "para.h"
 #include "gui.h"
 #include "string.h"
-#include <curses.h>
 #include "ringbuffer.h"
 #include "fd.h"
 #include "error.h"
@@ -43,7 +45,7 @@ struct rb_entry {
        size_t len;
        int color;
 };
-struct ringbuffer *bot_win_rb;
+static struct ringbuffer *bot_win_rb;
 #define NUM_LINES(len) (1 + (len) / bot.cols)
 
 static unsigned scroll_position;
@@ -52,7 +54,7 @@ static int cmd_died, curses_active;
 static pid_t cmd_pid;
 
 static int command_pipe = -1;
-static int audiod_pipe = -1;
+static int stat_pipe = -1;
 static struct gui_args_info conf;
 
 enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
@@ -89,8 +91,8 @@ struct stat_item {
 
 static struct gui_theme theme;
 
-int _argc;
-char **_argv;
+static int _argc;
+static char **_argv;
 
 static void com_help(void);
 static void com_reread_conf(void);
@@ -107,8 +109,10 @@ 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);
 
-struct gui_command command_list[] = {
+static struct gui_command command_list[] = {
        {
                .key = "?",
                .name = "help",
@@ -184,43 +188,21 @@ 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 read_audiod_pipe(int fd, line_handler_t *line_handler)
-{
-       static char buf[4096];
-       const ssize_t bufsize = sizeof(buf) - 1;
-       static ssize_t loaded;
-       ssize_t ret;
-
-       if (loaded >= bufsize)
-               loaded = 0;
-       ret = read(fd, buf + loaded, bufsize - loaded);
-       if (ret > 0) {
-               loaded += ret;
-               buf[loaded] = '\0';
-               loaded = for_each_line(buf, loaded, line_handler, NULL);
-       }
-       return ret;
-}
-
 static int find_cmd_byname(char *name)
 {
        int i;
@@ -260,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;
@@ -312,20 +302,23 @@ static void add_spaces(WINDOW* win, unsigned int num)
  * print aligned string to curses window. This function always prints
  * exactly len chars.
  */
-static int align_str(WINDOW* win, const char *string, unsigned int len,
+static int align_str(WINDOW* win, char *str, unsigned int len,
                unsigned int align)
 {
-       int num; /* of spaces */
-       char *str;
+       int i, num; /* of spaces */
 
-       if (!win || !string)
+       if (!win || !str)
                return -1;
-       num = len - strlen(string);
-       str = para_strdup(string);
+       num = len - strlen(str);
        if (num < 0) {
                str[len] = '\0';
                num = 0;
        }
+       /* replace control characters by spaces */
+       for (i = 0; i < len && str[i]; i++) {
+               if (str[i] == '\n' || str[i] == '\r' || str[i] == '\f')
+                       str[i] = ' ';
+       }
        if (align == LEFT) {
                waddstr(win, str);
                add_spaces(win, num);
@@ -337,7 +330,6 @@ static int align_str(WINDOW* win, const char *string, unsigned int len,
                waddstr(win, str[0]? str: "");
                add_spaces(win, num - num / 2);
        }
-       free(str);
        return 1;
 }
 
@@ -360,10 +352,14 @@ __printf_2_3 static void print_in_bar(int color, const char *fmt,...)
  */
 static void print_status_bar(void)
 {
+       char *tmp;
+
        if (!curses_active)
                return;
+       tmp = para_strdup(STANDARD_STATUS_BAR);
        wmove(sb.win, 0, 0);
-       align_str(sb.win, STANDARD_STATUS_BAR, sb.cols, CENTER);
+       align_str(sb.win, tmp, sb.cols, CENTER);
+       free(tmp);
        wrefresh(sb.win);
 }
 
@@ -404,11 +400,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);
@@ -489,12 +487,14 @@ static int add_output_line(char *line, __a_unused void *data)
        return 1;
 }
 
+static int loglevel;
+
 __printf_2_3 void para_log(int ll, const char *fmt,...)
 {
        int color;
        char *msg;
 
-       if (ll < conf.loglevel_arg || !curses_active)
+       if (ll < loglevel || !curses_active)
                return;
        switch (ll) {
                case LL_DEBUG:
@@ -519,13 +519,12 @@ static void setup_signal_handling(void)
        para_install_sighandler(SIGCHLD);
        para_install_sighandler(SIGWINCH);
        para_install_sighandler(SIGUSR1);
-//     signal(SIGPIPE, SIG_IGN);
-       signal(SIGHUP, SIG_IGN);
+       para_sigaction(SIGHUP, SIG_IGN);
 }
 
 __noreturn static void do_exit(int ret)
 {
-       signal(SIGTERM, SIG_IGN);
+       para_sigaction(SIGTERM, SIG_IGN);
        kill(0, SIGTERM);
        exit(ret);
 }
@@ -562,8 +561,7 @@ __noreturn __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...)
 
 static void print_welcome(void)
 {
-       int ll = conf.loglevel_arg;
-       if (ll > LL_NOTICE)
+       if (loglevel > LL_NOTICE)
                return;
        outputf(COLOR_WELCOME, "Welcome to para_gui " PACKAGE_VERSION
                " \"" CODENAME "\". Theme: %s", theme.name);
@@ -659,14 +657,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
  */
@@ -687,6 +677,70 @@ static void print_stat_item(int i)
        wrefresh(top.win);
 }
 
+static int update_item(int item_num, char *buf)
+{
+       char **c = stat_content + item_num;
+
+       free(*c);
+       if (buf && buf[0])
+               goto dup;
+       switch (item_num) {
+       case SI_ARTIST:
+               *c = para_strdup("(artist tag not set)");
+               goto print;
+       case SI_TITLE:
+               *c = para_strdup("(title tag not set)");
+               goto print;
+       case SI_YEAR:
+               *c = para_strdup("????");
+               goto print;
+       case SI_ALBUM:
+               *c = para_strdup("(album tag not set)");
+               goto print;
+       case SI_COMMENT:
+               *c = para_strdup("(comment tag not set)");
+               goto print;
+       }
+dup:
+       *c = para_strdup(buf);
+print:
+       print_stat_item(item_num);
+       return 1;
+}
+
+static int read_stat_pipe(fd_set *rfds)
+{
+       static char *buf;
+       static int bufsize, loaded;
+       int ret, ret2;
+       size_t sz;
+
+       if (stat_pipe < 0)
+               return 0;
+       if (loaded >= bufsize) {
+               if (bufsize > 1000 * 1000) {
+                       loaded = 0;
+                       return 0;
+               }
+               bufsize += bufsize + 1000;
+               buf = para_realloc(buf, bufsize);
+       }
+       assert(loaded < bufsize);
+       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;
+}
+
 static void print_all_items(void)
 {
        int i;
@@ -707,53 +761,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;
@@ -769,26 +830,6 @@ reap_next_child:
        goto reap_next_child;
 }
 
-/*
- * print status line if line starts with known command.
- */
-static int check_stat_line(char *line, __a_unused void *data)
-{
-       int i;
-
-//     PARA_INFO_LOG("%s: checking: %s\n", __func__, line);
-       i = stat_line_valid(line);
-       if (i >= 0) {
-               line += strlen(status_item_list[i]) + 1;
-               if (*line == ' ')
-                       line++;
-               free(stat_content[i]);
-               stat_content[i] = para_strdup(line);
-               print_stat_item(i);
-       }
-       return 1;
-}
-
 /*
  * This sucker modifies its first argument. *handler and *arg are
  * pointers to 0-terminated strings (inside line). Crap.
@@ -859,8 +900,8 @@ static void handle_signal(int sig)
                return;
        case SIGINT:
                PARA_WARNING_LOG("caught SIGINT, reset");
-               /* Nothing to do. SIGINT killed our child, para_client stat.
-                * This get noticed by do_select which resets everything
+               /* Nothing to do. SIGINT killed our child which gets noticed
+                * by do_select and resets everything.
                 */
                return;
        case SIGUSR1:
@@ -873,15 +914,30 @@ 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;
 }
 
 /*
@@ -895,14 +951,14 @@ static int open_audiod_pipe(void)
  * when any key is pressed.
  *
  * EXTERNAL_MODE: Check only signal pipe. Used when an external command
- * is running. During that thime curses is disabled.  Returns when
+ * is running. During that time curses is disabled.  Returns when
  * cmd_pid == 0.
  */
 static int do_select(int mode)
 {
        fd_set rfds;
        int ret;
-       int max_fileno, cp_numread = 1;
+       int max_fileno;
        char command_buf[4096] = "";
        int cbo = 0; /* command buf offset */
        struct timeval tv;
@@ -912,11 +968,10 @@ repeat:
 //     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 */
@@ -926,46 +981,41 @@ repeat:
        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);
+       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 (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds))
-               if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 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) {
@@ -1068,6 +1118,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;
@@ -1091,13 +1176,14 @@ static void com_page_up(void)
 {
        unsigned lines;
        int fvr = first_visible_rbe(&lines);
+
        if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) {
                print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
                return;
        }
        scroll_position = fvr + 1;
        for (; scroll_position > 0; scroll_position--) {
-               fvr = first_visible_rbe(&lines);
+               first_visible_rbe(&lines);
                if (lines == bot.lines)
                        break;
        }
@@ -1168,24 +1254,24 @@ err_out:
 
 static void com_ll_decr(void)
 {
-       if (conf.loglevel_arg <= LL_DEBUG) {
+       if (loglevel <= LL_DEBUG) {
                print_in_bar(COLOR_ERRMSG,
                        "loglevel already at maximal verbosity\n");
                return;
        }
-       conf.loglevel_arg--;
-       print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+       loglevel--;
+       print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
 }
 
 static void com_ll_incr(void)
 {
-       if (conf.loglevel_arg >= LL_EMERG) {
+       if (loglevel >= LL_EMERG) {
                print_in_bar(COLOR_ERRMSG,
-                       "loglevel already at miminal verbosity\n");
+                       "loglevel already at minimal verbosity\n");
                return;
        }
-       conf.loglevel_arg++;
-       print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+       loglevel++;
+       print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
 }
 
 /*
@@ -1198,7 +1284,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) {
@@ -1206,8 +1293,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);
@@ -1320,31 +1410,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')
+                       client_cmd_cmdline(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++) {
@@ -1365,17 +1454,9 @@ 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");
+       if (gui_cmdline_parser(argc, argv, &conf) != 0)
                exit(EXIT_FAILURE);
-       }
        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",
@@ -1387,14 +1468,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 */