Merge branch 'sched'
[paraslash.git] / gui.c
1 /*
2  * Copyright (C) 1998-2006 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 /** \file gui.c ncurses-based interface for paraslash */
20
21 #include "gui.cmdline.h"
22 #include "para.h"
23 #include <curses.h>
24 #include "ringbuffer.h"
25 #include "string.h"
26 #include "fd.h"
27 #include "error.h"
28
29 /** define the array of error lists needed by para_gui */
30 INIT_GUI_ERRLISTS;
31 extern const char *status_item_list[NUM_STAT_ITEMS];
32 static char *stat_content[NUM_STAT_ITEMS];
33
34 #define STANDARD_STATUS_BAR "para_gui " VERSION " (hit ? for help)"
35
36 static int signal_pipe;
37
38 static void finish(int sig);
39
40 static struct win_data {
41         WINDOW *win;
42         NCURSES_SIZE_T begx;
43         NCURSES_SIZE_T begy;
44         NCURSES_SIZE_T cols;
45         NCURSES_SIZE_T lines;
46 } top, bot, sb, in, sep;
47
48 #define RINGBUFFER_SIZE 512
49 struct rb_entry {
50         char *msg;
51         size_t len;
52         int color;
53 };
54 void *bot_win_rb;
55 #define NUM_LINES(len) (1 + (len) / bot.cols)
56
57 static unsigned scroll_position;
58
59 static int external_cmd_died, curses_active;
60 static pid_t external_cmd_pid;
61
62 static int command_pipe = -1;
63 static int audiod_pipe = -1;
64 static struct gengetopt_args_info conf;
65
66 enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
67
68
69 #define COLOR_STATUSBAR 32
70 #define COLOR_COMMAND 33
71 #define COLOR_OUTPUT 34
72 #define COLOR_MSG 35
73 #define COLOR_ERRMSG 36
74 #define COLOR_WELCOME 37
75 #define COLOR_SEPARATOR 38
76 #define COLOR_TOP 39
77 #define COLOR_BOT 40
78
79 struct gui_command {
80         const char *key;
81         const char *name;
82         const char *description;
83         void (*handler)(void);
84 };
85
86 struct stat_item {
87         char name[MAXLINE];
88         char prefix[MAXLINE];
89         char postfix[MAXLINE];
90         unsigned y;
91         unsigned x;
92         unsigned len;
93         int fg, bg;
94         int align;
95         char content[MAXLINE];
96 };
97
98 static struct gui_theme theme;
99
100 int _argc;
101 char **_argv;
102
103 static void com_help(void);
104 static void com_reread_conf(void);
105 static void com_enlarge_top_win(void);
106 static void com_shrink_top_win(void);
107 static void com_version(void);
108 static void com_quit(void);
109 static void com_refresh(void);
110 static void com_ll_incr(void);
111 static void com_ll_decr(void);
112 static void com_prev_theme(void);
113 static void com_next_theme(void);
114 static void com_scroll_up(void);
115 static void com_scroll_down(void);
116 static void com_page_up(void);
117 static void com_page_down(void);
118
119 struct gui_command command_list[] = {
120         {
121                 .key = "?",
122                 .name = "help",
123                 .description = "print help",
124                 .handler = com_help
125         }, {
126                 .key = "+",
127                 .name = "enlarge_win",
128                 .description = "enlarge the top window",
129                 .handler = com_enlarge_top_win
130         }, {
131                 .key = "-",
132                 .name = "shrink_win",
133                 .description = "shrink the top window",
134                 .handler = com_shrink_top_win
135         }, {
136                 .key = "r",
137                 .name = "reread_conf",
138                 .description = "reread configuration file",
139                 .handler = com_reread_conf
140         }, {
141                 .key = "q",
142                 .name = "quit",
143                 .description = "exit para_gui",
144                 .handler = com_quit
145         }, {
146                 .key = "^L",
147                 .name = "refresh",
148                 .description = "redraw the screen",
149                 .handler = com_refresh
150         }, {
151                 .key = ".",
152                 .name = "next_theme",
153                 .description = "switch to next theme",
154                 .handler = com_next_theme
155         }, {
156                 .key = ",",
157                 .name = "prev_theme",
158                 .description = "switch to previous stream",
159                 .handler = com_prev_theme
160         }, {
161                 .key = ">",
162                 .name = "ll_incr",
163                 .description = "increase loglevel (decreases verbosity)",
164                 .handler = com_ll_incr
165         }, {
166                 .key = "<",
167                 .name = "ll_decr",
168                 .description = "decrease loglevel (increases verbosity)",
169                 .handler = com_ll_decr
170         }, {
171                 .key = "V",
172                 .name = "version",
173                 .description = "show the para_gui version",
174                 .handler = com_version
175         }, {
176                 .key = "<up>",
177                 .name = "scroll_up",
178                 .description = "scroll up one line",
179                 .handler = com_scroll_up
180         }, {
181                 .key = "<down>",
182                 .name = "scroll_down",
183                 .description = "scroll down one line",
184                 .handler = com_scroll_down
185         }, {
186                 .key = "<ppage>",
187                 .name = "page_up",
188                 .description = "scroll up one page",
189                 .handler = com_page_up
190         }, {
191                 .key = "<npage>",
192                 .name = "page_down",
193                 .description = "scroll down one page",
194                 .handler = com_page_down
195         }, {
196                 .handler = NULL
197         }
198 };
199
200 static int find_cmd_byname(char *name)
201 {
202         int i;
203
204         for (i = 0; command_list[i].handler; i++)
205                 if (!strcmp(command_list[i].name, name))
206                         return i;
207         return -1;
208 }
209
210 /* taken from mutt */
211 static char *km_keyname(int c)
212 {
213         static char buf[10];
214
215         if (c == KEY_UP) {
216                 sprintf(buf, "<up>");
217                 return buf;
218         }
219         if (c == KEY_DOWN) {
220                 sprintf(buf, "<down>");
221                 return buf;
222         }
223         if (c == KEY_LEFT) {
224                 sprintf(buf, "<left>");
225                 return buf;
226         }
227         if (c == KEY_RIGHT) {
228                 sprintf(buf, "<right>");
229                 return buf;
230         }
231         if (c == KEY_NPAGE) {
232                 sprintf(buf, "<npage>");
233                 return buf;
234         }
235         if (c == KEY_PPAGE) {
236                 sprintf(buf, "<ppage>");
237                 return buf;
238         }
239         if (c < 256 && c > -128 && iscntrl((unsigned char) c)) {
240                 if (c < 0)
241                         c += 256;
242                 if (c < 128) {
243                         buf[0] = '^';
244                         buf[1] = (c + '@') & 0x7f;
245                         buf[2] = 0;
246                 } else
247                         snprintf(buf, sizeof(buf), "\\%d%d%d", c >> 6,
248                                 (c >> 3) & 7, c & 7);
249         } else if (c >= KEY_F0 && c < KEY_F(256))
250                 sprintf(buf, "<F%d>", c - KEY_F0);
251         else if (isprint(c))
252                 snprintf(buf, sizeof(buf), "%c", (unsigned char) c);
253         else
254                 snprintf(buf, sizeof(buf), "\\x%hx", (unsigned short) c);
255         return buf;
256 }
257
258 static char *configfile_exists(void)
259 {
260         static char *config_file;
261         char *tmp;
262
263         if (!conf.config_file_given) {
264                 if (!config_file) {
265                         char *home = para_homedir();
266                         config_file = make_message("%s/.paraslash/gui.conf",
267                                 home);
268                         free(home);
269                 }
270                 tmp = config_file;
271         } else
272                 tmp = conf.config_file_arg;
273         return file_exists(tmp)? tmp: NULL;
274 }
275
276 /*
277  * print num spaces to curses window
278  */
279 static void add_spaces(WINDOW* win, unsigned int num)
280 {
281         while (num > 0) {
282                 num--;
283                 waddstr(win, " ");
284         }
285 }
286
287 /*
288  * print aligned string to curses window. This function always prints
289  * exactly len chars.
290  */
291 static int align_str(WINDOW* win, const char *string, unsigned int len,
292                 unsigned int align)
293 {
294         int num; /* of spaces */
295         char *str;
296
297         if (!win || !string)
298                 return -1;
299         num = len - strlen(string);
300         str = para_strdup(string);
301         if (num < 0) {
302                 str[len] = '\0';
303                 num = 0;
304         }
305         if (align == LEFT) {
306                 waddstr(win, str);
307                 add_spaces(win, num);
308         } else if (align == RIGHT) {
309                 add_spaces(win, num);
310                 waddstr(win, str);
311         } else {
312                 add_spaces(win, num / 2);
313                 waddstr(win, str[0]? str: "");
314                 add_spaces(win, num - num / 2);
315         }
316         free(str);
317         return 1;
318 }
319
320 __printf_2_3 static void print_in_bar(int color, const char *fmt,...)
321 {
322         char *msg;
323
324         if (!curses_active)
325                 return;
326         wattron(in.win, COLOR_PAIR(color));
327         PARA_VSPRINTF(fmt, msg);
328         wmove(in.win, 0, 0);
329         align_str(in.win, msg, sb.cols, LEFT);
330         free(msg);
331         wrefresh(in.win);
332 }
333
334 /*
335  * update the status bar
336  */
337 static void print_status_bar(void)
338 {
339         if (!curses_active)
340                 return;
341         wmove(sb.win, 0, 0);
342         align_str(sb.win,STANDARD_STATUS_BAR, sb.cols, CENTER);
343         wrefresh(sb.win);
344 }
345
346 /*
347  * get the number of the oldest rbe that is (partially) visible. On return,
348  * lines contains the sum of the number of lines of all visable entries. If the
349  * first one is only partially visible, lines is greater than bot.lines.
350  */
351 static int first_visible_rbe(unsigned *lines)
352 {
353         int i;
354         *lines = 0;
355         for (i = scroll_position; i < RINGBUFFER_SIZE; i++) {
356                 struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
357                 int rbe_lines;
358                 if (!rbe)
359                         return i - 1;
360 //              fprintf(stderr, "found: %s\n", rbe->msg);
361                 rbe_lines = NUM_LINES(rbe->len);
362                 if (rbe_lines > bot.lines)
363                         return -1;
364                 *lines += rbe_lines;
365                 if (*lines >= bot.lines)
366                         return i;
367         }
368         return RINGBUFFER_SIZE - 1;
369 }
370
371 static int draw_top_rbe(unsigned *lines)
372 {
373         unsigned len;
374         int offset, fvr = first_visible_rbe(lines);
375         struct rb_entry *rbe;
376
377         if (fvr < 0)
378                 return -1;
379         wmove(bot.win, 0, 0);
380         rbe = ringbuffer_get(bot_win_rb, fvr);
381         if (!rbe)
382                 return -1;
383         /* first rbe might be only partially visible */
384         offset = (*lines - bot.lines) * bot.cols;
385         len = strlen(rbe->msg);
386         if (offset < 0 || len < offset)
387                 return -1;
388         wattron(bot.win, COLOR_PAIR(rbe->color));
389         waddstr(bot.win, rbe->msg + offset);
390         *lines = NUM_LINES(len - offset);
391         return fvr;
392 }
393
394 static void redraw_bot_win(void)
395 {
396         unsigned lines;
397         int i;
398
399         wmove(bot.win, 0, 0);
400         wclear(bot.win);
401         i = draw_top_rbe(&lines);
402         if (i <= 0)
403                 goto out;
404         while (i > 0 && lines < bot.lines) {
405                 struct rb_entry *rbe = ringbuffer_get(bot_win_rb, --i);
406                 if (!rbe) {
407                         lines++;
408                         waddstr(bot.win, "\n");
409                         continue;
410                 }
411                 lines += NUM_LINES(rbe->len);
412                 wattron(bot.win, COLOR_PAIR(rbe->color));
413                 waddstr(bot.win, "\n");
414                 waddstr(bot.win, rbe->msg);
415         }
416 out:
417         wrefresh(bot.win);
418 }
419
420 static void rb_add_entry(int color, char *msg)
421 {
422         struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
423         int x, y;
424         new->color = color;
425         new->len = strlen(msg);
426         new->msg = msg;
427         old = ringbuffer_add(bot_win_rb, new);
428 //      fprintf(stderr, "added: %s\n", new->msg);
429         if (old) {
430                 free(old->msg);
431                 free(old);
432         }
433         if (scroll_position) {
434                 /* discard current scrolling, like xterm does */
435                 scroll_position = 0;
436                 redraw_bot_win();
437                 return;
438         }
439         wattron(bot.win, COLOR_PAIR(color));
440         getyx(bot.win, y, x);
441         if (y || x)
442                 waddstr(bot.win, "\n");
443         waddstr(bot.win, msg);
444 }
445
446 /*
447  * print formated output to bot win and refresh
448  */
449 __printf_2_3 static void outputf(int color, const char* fmt,...)
450 {
451         char *msg;
452
453         if (!curses_active)
454                 return;
455         PARA_VSPRINTF(fmt, msg);
456         rb_add_entry(color, msg);
457         wrefresh(bot.win);
458 }
459
460 static void add_output_line(char *line)
461 {
462         if (!curses_active)
463                 return;
464         rb_add_entry(COLOR_OUTPUT, para_strdup(line));
465 }
466
467 void para_log(int ll, const char *fmt,...)
468 {
469         int color;
470         char *msg;
471
472         if (ll < conf.loglevel_arg || !curses_active)
473                 return;
474         switch (ll) {
475                 case DEBUG:
476                 case INFO:
477                 case NOTICE:
478                         color = COLOR_MSG;
479                         break;
480                 default:
481                         color = COLOR_ERRMSG;
482         }
483         PARA_VSPRINTF(fmt, msg);
484         chop(msg);
485         rb_add_entry(color, msg);
486         wrefresh(bot.win);
487 }
488
489 static void setup_signal_handling(void)
490 {
491         signal_pipe = para_signal_init();
492         para_install_sighandler(SIGINT);
493         para_install_sighandler(SIGTERM);
494         para_install_sighandler(SIGCHLD);
495         para_install_sighandler(SIGWINCH);
496         para_install_sighandler(SIGUSR1);
497         signal(SIGPIPE, SIG_IGN);
498         signal(SIGHUP, SIG_IGN);
499 }
500
501 static void do_exit(int ret)
502 {
503         signal(SIGTERM, SIG_IGN);
504         kill(0, SIGTERM);
505         exit(ret);
506 }
507
508 static void shutdown_curses(void)
509 {
510         if (!curses_active)
511                 return;
512         def_prog_mode();
513         curses_active = 0;
514         endwin();
515 }
516
517 static void finish(int ret)
518 {
519         shutdown_curses();
520         do_exit(ret);
521 }
522
523 /*
524  * exit curses and print given message to stdout/stderr
525  */
526 __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...)
527 {
528         va_list argp;
529         FILE *outfd = ret? stderr: stdout;
530
531         shutdown_curses();
532         va_start(argp, fmt);
533         vfprintf(outfd, fmt, argp);
534         va_end(argp);
535         do_exit(ret);
536 }
537
538 static void print_welcome(void)
539 {
540         int ll = conf.loglevel_arg;
541         if (ll > NOTICE)
542                 return;
543         outputf(COLOR_WELCOME, "Welcome to para_gui " VERSION
544                 " \"" CODENAME "\". Theme: %s", theme.name);
545         wclrtoeol(bot.win);
546 }
547
548 /*
549  * init all windows
550  */
551 static void init_wins(int top_lines)
552 {
553         int i;
554
555         top.lines = top_lines;
556         top.cols = COLS;
557         top.begy = 0;
558         top.begx = 0;
559
560         bot.lines = LINES - top.lines - 3;
561         bot.cols = COLS;
562         bot.begy = top.lines + 1;
563         bot.begx = 0;
564
565         sb.lines = 1;
566         sb.cols = COLS;
567         sb.begy = LINES - 2;
568         sb.begx = 0;
569
570         in.lines = 1;
571         in.cols = COLS;
572         in.begy = LINES - 1;
573         in.begx = 0;
574
575         sep.lines = 1;
576         sep.cols = COLS;
577         sep.begy = top.lines;
578         sep.begx = 0;
579
580         assume_default_colors(theme.default_fg, theme.default_bg);
581         if (top.win) {
582                 mvwin(top.win, top.begy, top.begx);
583                 wresize(top.win, top.lines, top.cols);
584
585                 mvwin(sb.win, sb.begy, sb.begx);
586                 wresize(sb.win, sb.lines, sb.cols);
587
588                 mvwin(sep.win, sep.begy, sep.begx);
589                 wresize(sep.win, sep.lines, sep.cols);
590
591                 mvwin(bot.win, bot.begy, bot.begx);
592                 wresize(bot.win, bot.lines, bot.cols);
593
594                 mvwin(in.win, in.begy, in.begx);
595                 wresize(in.win, in.lines, in.cols);
596         } else {
597                 sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx);
598                 top.win = newwin(top.lines, top.cols, top.begy, top.begx);
599                 bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx);
600                 sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx);
601                 in.win = newwin(in.lines, in.cols, in.begy, in.begx);
602                 if (!top.win || !bot.win || !sb.win || !in.win || !sep.win)
603                         msg_n_exit(1, "Error: Cannot create curses windows\n");
604                 wclear(bot.win);
605                 wclear(sb.win);
606                 wclear(in.win);
607                 scrollok(bot.win, 1);
608                 wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR));
609                 wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR));
610                 wattron(bot.win, COLOR_PAIR(COLOR_BOT));
611                 wattron(top.win, COLOR_PAIR(COLOR_TOP));
612                 nodelay(top.win, 1);
613                 nodelay(bot.win, 1);
614                 nodelay(sb.win, 1);
615                 nodelay(in.win, 0);
616
617                 keypad(top.win, 1);
618                 keypad(bot.win, 1);
619                 keypad(sb.win, 1);
620                 keypad(in.win, 1);
621                 print_status_bar();
622         }
623         wmove(sep.win, 0, 0);
624         for (i = 1; i <= COLS; i++)
625                 waddstr(sep.win, theme.sep_str);
626         wclear(top.win);
627         //wclear(bot.win);
628         wnoutrefresh(top.win);
629         wnoutrefresh(bot.win);
630         //wnoutrefresh(sb.win);
631         print_status_bar();
632         wnoutrefresh(in.win);
633         wnoutrefresh(sep.win);
634         doupdate();
635 }
636
637 static void check_geometry(void)
638 {
639         if (LINES < theme.lines_min || COLS < theme.cols_min)
640                 msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small"
641                         " (need at least %dx%d)\n", COLS, LINES,
642                         theme.cols_min, theme.lines_min);
643 }
644
645 /*
646  * Print stat item #i to curses window
647  */
648 static void print_stat_item(int i)
649 {
650         char *tmp;
651         struct stat_item_data d = theme.data[i];
652         char *c = stat_content[i];
653
654         if (!curses_active || !d.len || !c)
655                 return;
656         tmp = make_message("%s%s%s", d.prefix, c, d.postfix);
657 //      PARA_DEBUG_LOG("%s: read: %s\n", __func__, tmp);
658         wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100);
659         wrefresh(top.win);
660         wattron(top.win, COLOR_PAIR(i + 1));
661         align_str(top.win, tmp, d.len * COLS / 100, d.align);
662         free(tmp);
663         wrefresh(top.win);
664 }
665
666 static void print_all_items(void)
667 {
668         int i;
669
670         if (!curses_active)
671                 return;
672         for (i = 0; i < NUM_STAT_ITEMS; i++)
673                 print_stat_item(i);
674 }
675 static void clear_all_items(void)
676 {
677         int i;
678
679         for (i = 0; i < NUM_STAT_ITEMS; i++) {
680                 free(stat_content[i]);
681                 stat_content[i] = para_strdup("");
682         }
683 }
684
685 static void init_colors(void)
686 {
687         int i;
688         if (!has_colors())
689                 msg_n_exit(EXIT_FAILURE, "Error: No color term\n");
690         start_color();
691         for (i = 0; i < NUM_STAT_ITEMS; i++)
692                 if (theme.data[i].len)
693                         init_pair(i + 1, theme.data[i].fg, theme.data[i].bg);
694         init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
695         init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
696         init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
697         init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg);
698         init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
699         init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
700         init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
701         init_pair(COLOR_TOP, theme.default_fg, theme.default_bg);
702         init_pair(COLOR_BOT, theme.default_fg, theme.default_bg);
703 }
704
705 /*
706  * (re-)initialize the curses library  FIXME: Error checking
707  */
708 static void init_curses(void)
709 {
710         curses_active = 1;
711         if (top.win && refresh() == ERR) { /* refesh is really needed */
712                 msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
713         }
714         check_geometry();
715         curs_set(0); /* make cursor invisible, ignore errors */
716 //      if (noraw() == ERR);
717 //              msg_n_exit(EXIT_FAILURE, "can not place terminal out of "
718 //                      "raw mode\n");
719         nonl(); /* tell curses not to do NL->CR/NL on output */
720         noecho(); /* don't echo input */
721         cbreak(); /* take input chars one at a time, no wait for \n */
722         //reset_prog_mode();
723         init_colors();
724         clear();
725         init_wins(theme.top_lines_default);
726         print_all_items();
727         noecho(); /* don't echo input */
728 }
729
730
731 static void check_sigchld(void)
732 {
733         pid_t pid;
734 reap_next_child:
735         pid = para_reap_child();
736         if (pid <= 0)
737                 return;
738         if (pid == external_cmd_pid) {
739                 external_cmd_pid = 0;
740                 external_cmd_died = 1;
741         }
742         goto reap_next_child;
743 }
744
745 /*
746  * print status line if line starts with known command.
747  */
748 static void check_stat_line(char *line)
749 {
750         int i;
751
752 //      PARA_INFO_LOG("%s: checking: %s\n", __func__, line);
753         i = stat_line_valid(line);
754         if (i >= 0) {
755                 line += strlen(status_item_list[i]) + 1;
756                 free(stat_content[i]);
757                 stat_content[i] = para_strdup(line);
758                 print_stat_item(i);
759         }
760         return;
761 }
762
763 /*
764  * This sucker modifies its first argument. *handler and *arg are
765  * pointers to 0-terminated strings (inside line). Crap.
766  */
767 static int split_key_map(char *line, char **handler, char **arg)
768 {
769         if (!(*handler = strchr(line + 1, ':')))
770                 goto err_out;
771         **handler = '\0';
772         (*handler)++;
773         if (!(*arg = strchr(*handler, ':')))
774                 goto err_out;
775         **arg = '\0';
776         (*arg)++;
777         return 1;
778 err_out:
779         return 0;
780 }
781
782 static int check_key_map_args(void)
783 {
784         char *s;
785         int i, ret = -1;
786         char *tmp = NULL, *handler, *arg;
787
788         for (i = 0; i < conf.key_map_given; ++i) {
789                 s = conf.key_map_arg[i];
790                 if (!(*s))
791                         goto err_out;
792                 free(tmp);
793                 tmp = para_strdup(s);
794                 if (!split_key_map(tmp, &handler, &arg))
795                         goto err_out;
796                 if (strlen(handler) != 1)
797                         goto err_out;
798                 if (*handler != 'x'
799                         && *handler != 'd'
800                         && *handler != 'i'
801                         && *handler != 'p')
802                         goto err_out;
803                 if (*handler != 'i')
804                         continue;
805                 if (find_cmd_byname(arg) < 0)
806                         goto err_out;
807         }
808         ret = 0;
809 err_out:
810         free(tmp);
811         return ret;
812 }
813
814 /*
815  * React to various signal-related events
816  */
817 static void handle_signal(int sig)
818 {
819         switch (sig) {
820         case SIGTERM:
821                 msg_n_exit(EXIT_FAILURE,
822                         "only the good die young (caught SIGTERM))\n");
823                 return;
824         case SIGWINCH:
825                 if (curses_active) {
826                         shutdown_curses();
827                         init_curses();
828                         redraw_bot_win();
829                 }
830                 return;
831         case SIGINT:
832                 PARA_WARNING_LOG("%s", "caught SIGINT, reset");
833                 /* Nothing to do. SIGINT killed our child, para_client stat.
834                  * This get noticed by do_select which resets everything
835                  */
836                 return;
837         case SIGUSR1:
838                 PARA_NOTICE_LOG("%s", "got SIGUSR1, rereading configuration");
839                 com_reread_conf();
840                 return;
841         case SIGCHLD:
842                 check_sigchld();
843                 return;
844         }
845 }
846
847 static int open_audiod_pipe(void)
848 {
849         static int init = 1;
850
851         if (init)
852                 init = 0;
853         else
854                 sleep(1);
855         return para_open_audiod_pipe(conf.stat_cmd_arg);
856 }
857
858 /*
859  * This is the core select loop. Besides the (internal) signal
860  * pipe, the following other fds are checked according to the mode:
861  *
862  * GETCH_MODE: check stdin, return when key is pressed
863  *
864  * COMMAND_MODE: check command_pipe and stdin. Return when a screen full
865  * of output has been read or when other end has closed command pipe or
866  * when any key is pressed.
867  *
868  * EXTERNAL_MODE: Check only signal pipe. Used when an external command
869  * is running. During that thime curses is disabled.  Returns when
870  * external_cmd_pid == 0.
871  */
872 static int do_select(int mode)
873 {
874         fd_set rfds;
875         int ret;
876         int max_fileno, cp_numread = 1;
877         char command_buf[STRINGSIZE] = "";
878         int cbo = 0; /* command buf offset */
879         struct timeval tv;
880 repeat:
881         tv.tv_sec = conf.timeout_arg  / 1000;
882         tv.tv_usec = (conf.timeout_arg % 1000) * 1000;
883 //      ret = refresh_status();
884         FD_ZERO(&rfds);
885         max_fileno = 0;
886         /* audiod pipe */
887         if (audiod_pipe < 0)
888                 audiod_pipe = open_audiod_pipe();
889         if (audiod_pipe >= 0)
890                 para_fd_set(audiod_pipe, &rfds, &max_fileno);
891         /* signal pipe */
892         para_fd_set(signal_pipe, &rfds, &max_fileno);
893         /* command pipe only for COMMAND_MODE */
894         if (command_pipe >= 0 && mode == COMMAND_MODE)
895                 para_fd_set(command_pipe, &rfds, &max_fileno);
896         if (curses_active)
897                 para_fd_set(STDIN_FILENO, &rfds, &max_fileno);
898         ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
899 //      PARA_DEBUG_LOG("select returned %d\n", ret);
900         if (ret <= 0)
901                 goto check_return; /* skip fd checks */
902         /* signals */
903         if (FD_ISSET(signal_pipe, &rfds)) {
904                 int sig_nr = para_next_signal();
905                 if (sig_nr > 0)
906                         handle_signal(sig_nr);
907         }
908         /* read command pipe if ready */
909         if (command_pipe >= 0 && mode == COMMAND_MODE &&
910                         FD_ISSET(command_pipe, &rfds)) {
911                 cp_numread = read(command_pipe, command_buf + cbo,
912                         STRINGSIZE - 1 - cbo);
913                 if (cp_numread >= 0)
914                         cbo += cp_numread;
915                 else {
916                         if (cp_numread < 0)
917                                 PARA_ERROR_LOG("read error (%d)", cp_numread);
918                         close(command_pipe);
919                         command_pipe = -1;
920                 }
921         }
922         if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds))
923                 if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) {
924                         close(audiod_pipe);
925                         audiod_pipe = -1;
926                         clear_all_items();
927                         free(stat_content[SI_STATUS_BAR]);
928                         stat_content[SI_STATUS_BAR] =
929                                 para_strdup("audiod not running!?");
930                         print_all_items();
931                 }
932 check_return:
933         switch (mode) {
934         case COMMAND_MODE:
935                 if (cp_numread <= 0 && !cbo) /* command complete */
936                         return 0;
937                 if (cbo)
938                         cbo = for_each_line(command_buf, cbo, &add_output_line);
939                 if (cp_numread <= 0)
940                         cbo = 0;
941                 wrefresh(bot.win);
942                 break;
943         case GETCH_MODE:
944                 ret = wgetch(top.win);
945                 if (ret != ERR && ret != KEY_RESIZE)
946                         return ret;
947                 break;
948         case EXTERNAL_MODE:
949                 if (external_cmd_died) {
950                         external_cmd_died = 0;
951                         return 0;
952                 }
953         }
954         goto repeat;
955 }
956
957 /*
958  * read from command pipe and print data to bot window
959  */
960 static int send_output(void)
961 {
962         if (command_pipe < 0)
963                 return 0;
964         if (do_select(COMMAND_MODE) >= 0)
965                 PARA_INFO_LOG("%s", "command complete");
966         else
967                 PARA_NOTICE_LOG("%s", "command aborted");
968         print_in_bar(COLOR_MSG, " ");
969         return 1;
970 }
971
972 static int client_cmd_cmdline(char *cmd)
973 {
974         pid_t pid;
975         int ret, fds[3] = {0, 1, 0};
976         char *c = make_message(BINDIR "/para_client %s", cmd);
977
978         outputf(COLOR_COMMAND, "%s", c);
979         print_in_bar(COLOR_MSG, "executing client command, hit q to abort\n");
980         ret = para_exec_cmdline_pid(&pid, c, fds);
981         free(c);
982         if (ret < 0)
983                 return -1;
984         command_pipe = fds[1];
985         return send_output();
986 }
987
988 /*
989  * exec command and print output to bot win
990  */
991 static int display_cmd(char *cmd)
992 {
993         pid_t pid;
994         int fds[3] = {0, 1, 0};
995
996         print_in_bar(COLOR_MSG, "executing display command, hit q to abort");
997         outputf(COLOR_COMMAND, "%s", cmd);
998         if (para_exec_cmdline_pid(&pid, cmd, fds) < 0)
999                 return -1;
1000         command_pipe = fds[1];
1001         return send_output();
1002 }
1003
1004 /*
1005  * shutdown curses and stat pipe before executing external commands
1006  */
1007 static int external_cmd(char *cmd)
1008 {
1009         int fds[3] = {-1, -1, -1};
1010
1011         if (external_cmd_pid)
1012                 return -1;
1013         shutdown_curses();
1014         para_exec_cmdline_pid(&external_cmd_pid, cmd, fds);
1015         do_select(EXTERNAL_MODE);
1016         init_curses();
1017         return 0;
1018 }
1019
1020 static void print_scroll_msg(void)
1021 {
1022         unsigned lines_total, filled = ringbuffer_filled(bot_win_rb);
1023         int first_rbe = first_visible_rbe(&lines_total);
1024         print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe,
1025                 filled - scroll_position, ringbuffer_filled(bot_win_rb));
1026 }
1027
1028 static void com_page_down(void)
1029 {
1030         unsigned lines = 0;
1031         int i = scroll_position;
1032         while (lines < bot.lines && --i > 0) {
1033                 struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
1034                 if (!rbe)
1035                         break;
1036                 lines += NUM_LINES(strlen(rbe->msg));
1037         }
1038         if (lines) {
1039                 scroll_position = i;
1040                 redraw_bot_win();
1041                 print_scroll_msg();
1042                 return;
1043         }
1044         print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
1045 }
1046
1047 static void com_page_up(void)
1048 {
1049         unsigned lines;
1050         int fvr = first_visible_rbe(&lines);
1051         if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) {
1052                 print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
1053                 return;
1054         }
1055         scroll_position = fvr + 1;
1056         for (; scroll_position > 0; scroll_position--) {
1057                 fvr = first_visible_rbe(&lines);
1058                 if (lines == bot.lines)
1059                         break;
1060         }
1061         redraw_bot_win();
1062         print_scroll_msg();
1063 }
1064
1065 static void com_scroll_down(void)
1066 {
1067         struct rb_entry *rbe;
1068         int rbe_lines;
1069
1070         if (!scroll_position) {
1071                 print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
1072                 return;
1073         }
1074         scroll_position--;
1075         rbe = ringbuffer_get(bot_win_rb, scroll_position);
1076         rbe_lines = NUM_LINES(rbe->len);
1077         wscrl(bot.win, rbe_lines);
1078         wmove(bot.win, bot.lines - rbe_lines, 0);
1079         wattron(bot.win, COLOR_PAIR(rbe->color));
1080         waddstr(bot.win, rbe->msg);
1081         wrefresh(bot.win);
1082         print_scroll_msg();
1083 }
1084
1085 static void com_scroll_up(void)
1086 {
1087         struct rb_entry *rbe = NULL;
1088         unsigned lines;
1089         int i, first_rbe, scroll;
1090
1091         /* the entry that is going to vanish */
1092         rbe = ringbuffer_get(bot_win_rb, scroll_position);
1093         if (!rbe)
1094                 goto err_out;
1095         scroll = NUM_LINES(rbe->len);
1096         first_rbe = first_visible_rbe(&lines);
1097         if (first_rbe < 0 || (first_rbe == ringbuffer_filled(bot_win_rb) - 1))
1098                 goto err_out;
1099         scroll_position++;
1100         wscrl(bot.win, -scroll);
1101         i = draw_top_rbe(&lines);
1102         if (i < 0)
1103                 goto err_out;
1104         while (i > 0 && lines < scroll) {
1105                 int rbe_lines;
1106                 rbe = ringbuffer_get(bot_win_rb, --i);
1107                 if (!rbe)
1108                         break;
1109                 rbe_lines = NUM_LINES(rbe->len);
1110                 lines += rbe_lines;
1111 //              fprintf(stderr, "msg: %s\n", rbe->msg);
1112                 wattron(bot.win, COLOR_PAIR(rbe->color));
1113                 waddstr(bot.win, "\n");
1114                 waddstr(bot.win, rbe->msg);
1115                 if (!i)
1116                         break;
1117                 i--;
1118         }
1119         wrefresh(bot.win);
1120         print_scroll_msg();
1121         return;
1122 err_out:
1123         print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
1124 }
1125
1126 static void com_ll_decr(void)
1127 {
1128         if (conf.loglevel_arg <= DEBUG) {
1129                 print_in_bar(COLOR_ERRMSG,
1130                         "loglevel already at maximal verbosity\n");
1131                 return;
1132         }
1133         conf.loglevel_arg--;
1134         print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
1135 }
1136
1137 static void com_ll_incr(void)
1138 {
1139         if (conf.loglevel_arg >= EMERG) {
1140                 print_in_bar(COLOR_ERRMSG,
1141                         "loglevel already at miminal verbosity\n");
1142                 return;
1143         }
1144         conf.loglevel_arg++;
1145         print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
1146 }
1147
1148 /*
1149  * reread configuration, terminate on errors
1150  */
1151 static void com_reread_conf(void)
1152 {
1153         char *cf =configfile_exists();
1154
1155         if (!cf) {
1156                 PARA_WARNING_LOG("%s", "there is no configuration to read");
1157                 return;
1158         }
1159         PARA_INFO_LOG("%s", "rereading command line options and config file");
1160         cmdline_parser(_argc, _argv, &conf);
1161         cmdline_parser_configfile(cf, &conf, 1, 1, 0);
1162         PARA_NOTICE_LOG("%s", "configuration read");
1163         if (check_key_map_args() < 0)
1164                 finish(EXIT_FAILURE);
1165 }
1166
1167 static void com_help(void)
1168 {
1169         int i;
1170
1171         for (i = 0; i < conf.key_map_given; ++i) {
1172                 char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]);
1173                 const char *handler_text = "???", *desc = NULL;
1174
1175                 if (!split_key_map(tmp, &handler, &arg)) {
1176                         free(tmp);
1177                         return;
1178                 }
1179                 switch (*handler) {
1180                         case 'i':
1181                                 handler_text = "internal";
1182                                 desc = command_list[find_cmd_byname(arg)].description;
1183                                 break;
1184                         case 'x': handler_text = "external"; break;
1185                         case 'd': handler_text = "display "; break;
1186                         case 'p': handler_text = "para    "; break;
1187                 }
1188                 outputf(COLOR_MSG, "%s\t%s\t%s%s\t%s", tmp, handler_text, arg,
1189                         strlen(arg) < 8? "\t" : "",
1190                         desc? desc : "");
1191                 free(tmp);
1192         }
1193         for (i = 0; command_list[i].handler; i++) {
1194                 struct gui_command gc = command_list[i];
1195
1196                 outputf(COLOR_MSG, "%s\tinternal\t%s\t%s%s", gc.key, gc.name,
1197                         strlen(gc.name) < 8? "\t" : "",
1198                         gc.description);
1199         }
1200         print_in_bar(COLOR_MSG, "try \"para_gui -h\" or \"para_client help\" "
1201                 "for more info");
1202 }
1203
1204 static void com_shrink_top_win(void)
1205 {
1206         if (top.lines <= theme.top_lines_min) {
1207                 PARA_WARNING_LOG("%s", "can not decrease top window");
1208                 return;
1209         }
1210         init_wins(top.lines - 1);
1211         wclear(top.win);
1212         print_all_items();
1213         print_in_bar(COLOR_MSG, "%s", "decreased top window");
1214 }
1215
1216 static void com_enlarge_top_win(void)
1217 {
1218         if (bot.lines < 3) {
1219                 PARA_WARNING_LOG("%s", "can not increase top window");
1220                 return;
1221         }
1222         init_wins(top.lines + 1);
1223         wclear(top.win);
1224         print_all_items();
1225         print_in_bar(COLOR_MSG, "increased top window");
1226 }
1227
1228 static void com_version(void)
1229 {
1230         print_in_bar(COLOR_MSG, "para_gui " VERSION " \"" CODENAME "\"");
1231 }
1232
1233 static void com_quit(void)
1234 {
1235         finish(0);
1236 }
1237
1238 static void com_refresh(void)
1239 {
1240         shutdown_curses();
1241         init_curses();
1242 }
1243
1244 static void change_theme(int next)
1245 {
1246         if (next)
1247                 next_theme(&theme);
1248         else
1249                 prev_theme(&theme);
1250         /* This seems to be needed twice, why? */
1251         com_refresh();
1252         com_refresh();
1253         PARA_NOTICE_LOG("new theme: %s", theme.name);
1254 }
1255
1256 static void com_next_theme(void)
1257 {
1258         change_theme(1);
1259 }
1260
1261 static void com_prev_theme(void)
1262 {
1263         change_theme(0);
1264 }
1265
1266
1267 static void handle_command(int c)
1268 {
1269         int i;
1270
1271         /* first check user's key bindings */
1272         for (i = 0; i < conf.key_map_given; ++i) {
1273                 char tmp[MAXLINE], *handler, *arg;
1274
1275                 strcpy(tmp, conf.key_map_arg[i]);
1276                 if (!split_key_map(tmp, &handler, &arg))
1277                         return;
1278                 if (!strcmp(tmp, km_keyname(c))) {
1279                         if (*handler == 'd') {
1280                                 display_cmd(arg);
1281                                 return;
1282                         }
1283                         if (*handler == 'x') {
1284                                 external_cmd(arg);
1285                                 return;
1286                         }
1287                         if (*handler == 'p') {
1288                                 client_cmd_cmdline(arg);
1289                                 return;
1290                         }
1291                         if (*handler == 'i') {
1292                                 int num = find_cmd_byname(arg);
1293                                 if (num >= 0)
1294                                         command_list[num].handler();
1295                                 return;
1296                         }
1297                 }
1298         }
1299         /* not found, check internal key bindings */
1300         for (i = 0; command_list[i].handler; i++) {
1301                 if (!strcmp(km_keyname(c), command_list[i].key)) {
1302                         command_list[i].handler();
1303                         return;
1304                 }
1305         }
1306         print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
1307                 km_keyname(c));
1308 }
1309
1310 int main(int argc, char *argv[])
1311 {
1312         int ret;
1313         char *cf;
1314
1315         _argc = argc;
1316         _argv = argv;
1317
1318         if (cmdline_parser(argc, argv, &conf)) {
1319                 fprintf(stderr, "parse error while reading command line\n");
1320                 exit(EXIT_FAILURE);
1321         }
1322         init_theme(0, &theme);
1323         top.lines = theme.top_lines_default;
1324         if (check_key_map_args() < 0) {
1325                 fprintf(stderr, "invalid key map\n");
1326                 exit(EXIT_FAILURE);
1327         }
1328         cf = configfile_exists();
1329         if (!cf && conf.config_file_given) {
1330                 fprintf(stderr, "can not read config file %s\n",
1331                         conf.config_file_arg);
1332                 exit(EXIT_FAILURE);
1333         }
1334         if (cf)
1335                 cmdline_parser_configfile(cf, &conf, 0, 0, 0);
1336         if (check_key_map_args() < 0) {
1337                 fprintf(stderr, "invalid key map in config file\n");
1338                 exit(EXIT_FAILURE);
1339         }
1340         setup_signal_handling();
1341         bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
1342         initscr(); /* needed only once, always successful */
1343         init_curses();
1344         print_welcome();
1345         for (;;) {
1346                 print_status_bar();
1347                 ret = do_select(GETCH_MODE);
1348                 if (!ret)
1349                         continue;
1350                 print_in_bar(COLOR_MSG, " ");
1351                 handle_command(ret);
1352         }
1353 }