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