2 * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
4 * Licensed under the GPL v2. For licencing details see COPYING.
7 /** \file gui.c Curses-based interface for paraslash. */
11 #include <sys/types.h>
16 #include "gui.cmdline.h"
20 #include "ringbuffer.h"
29 /** define the array of error lists needed by para_gui */
31 static char *stat_content
[NUM_STAT_ITEMS
];
33 static struct gui_window
{
36 } top
, bot
, sb
, in
, sep
;
38 /** How many lines of output to remember. */
39 #define RINGBUFFER_SIZE 512
46 static struct ringbuffer
*bot_win_rb
;
48 static unsigned scroll_position
;
50 static pid_t exec_pid
;
52 static int exec_fds
[2] = {-1, -1};
53 static struct gui_args_info conf
;
56 /** Type of the process currently being executed. */
58 EXEC_IDLE
, /**< No process running. */
59 EXEC_DCMD
, /**< para or display process running. */
60 EXEC_XCMD
, /**< External process running. */
64 * Codes for various colors.
66 * Each status item has its own color pair. The ones defined here start at a
67 * higher number so that they do not overlap with these.
70 COLOR_STATUSBAR
= NUM_STAT_ITEMS
+ 1,
83 const char *description
;
84 void (*handler
)(void);
87 static struct gui_theme theme
;
89 #define GUI_COMMANDS \
90 GUI_COMMAND(help, "?", "print help") \
91 GUI_COMMAND(enlarge_top_win, "+", "enlarge the top window") \
92 GUI_COMMAND(shrink_top_win, "-", "shrink the top window") \
93 GUI_COMMAND(reread_conf, "r", "reread configuration file") \
94 GUI_COMMAND(quit, "q", "exit para_gui") \
95 GUI_COMMAND(refresh, "^L", "redraw the screen") \
96 GUI_COMMAND(next_theme, ".", "switch to next theme") \
97 GUI_COMMAND(prev_theme, ",", "switch to previous theme") \
98 GUI_COMMAND(ll_incr, ">", "increase loglevel (decreases verbosity)") \
99 GUI_COMMAND(ll_decr, "<", "decrease loglevel (increases verbosity)") \
100 GUI_COMMAND(version, "V", "show the para_gui version") \
101 GUI_COMMAND(scroll_up, "<up>", "scroll up one line") \
102 GUI_COMMAND(scroll_down, "<down>", "scroll_down") \
103 GUI_COMMAND(page_up, "<ppage>", "scroll up one page") \
104 GUI_COMMAND(page_down, "<npage>", "scroll down one page") \
105 GUI_COMMAND(scroll_top, "<home>", "scroll to top of buffer") \
106 GUI_COMMAND(cancel_scroll, "<end>", "deactivate scroll mode") \
108 /* declare command handlers */
109 #define GUI_COMMAND(_c, _k, _d) \
110 static void com_ ## _c(void);
115 /* define command array */
116 #define GUI_COMMAND(_c, _k, _d) \
121 .handler = com_ ## _c \
124 static struct gui_command command_list
[] = {GUI_COMMANDS
{.name
= NULL
}};
135 struct timeval next_exec
;
139 /** Stdout/stderr of the executing process is read in chunks of this size. */
140 #define COMMAND_BUF_SIZE 32768
144 char command_buf
[2][COMMAND_BUF_SIZE
]; /* stdout/stderr of command */
145 int cbo
[2]; /* command buf offsets */
146 unsigned flags
[2]; /* passed to for_each_line() */
149 static int find_cmd_byname(char *name
)
153 for (i
= 0; command_list
[i
].handler
; i
++)
154 if (!strcmp(command_list
[i
].name
, name
))
160 * Even though ncurses provides getmaxx and getmaxy, these functions/macros are
161 * not described in the XSI Curses standard.
163 static int get_num_lines(struct gui_window
*w
)
166 __a_unused
int cols
; /* avoid "set but not used" warnings */
168 getmaxyx(w
->win
, lines
, cols
);
172 static int get_num_cols(struct gui_window
*w
)
174 __a_unused
int lines
; /* avoid "set but not used" warnings */
177 getmaxyx(w
->win
, lines
, cols
);
181 /** Number of lines of the window are occupied by an output line. */
182 #define NUM_LINES(len) (1 + (len) / get_num_cols(&bot))
184 /* isendwin() returns false before initscr() was called */
185 static bool curses_active(void)
187 return top
.win
&& !isendwin();
190 /* taken from mutt */
191 static char *km_keyname(int c
)
196 sprintf(buf
, "<up>");
200 sprintf(buf
, "<down>");
204 sprintf(buf
, "<left>");
207 if (c
== KEY_RIGHT
) {
208 sprintf(buf
, "<right>");
211 if (c
== KEY_NPAGE
) {
212 sprintf(buf
, "<npage>");
215 if (c
== KEY_PPAGE
) {
216 sprintf(buf
, "<ppage>");
220 sprintf(buf
, "<home>");
224 sprintf(buf
, "<end>");
227 if (c
< 256 && c
> -128 && iscntrl((unsigned char) c
)) {
232 buf
[1] = (c
+ '@') & 0x7f;
235 snprintf(buf
, sizeof(buf
), "\\%d%d%d", c
>> 6,
236 (c
>> 3) & 7, c
& 7);
237 } else if (c
>= KEY_F0
&& c
< KEY_F(256))
238 sprintf(buf
, "<F%d>", c
- KEY_F0
);
240 snprintf(buf
, sizeof(buf
), "%c", (unsigned char) c
);
242 snprintf(buf
, sizeof(buf
), "\\x%hx", (unsigned short) c
);
246 /* Print given number of spaces to curses window. */
247 static void add_spaces(WINDOW
* win
, unsigned int num
)
250 unsigned sz
= sizeof(space
) - 1; /* number of spaces */
264 * print aligned string to curses window. This function always prints
267 static int align_str(WINDOW
* win
, char *str
, unsigned int len
,
270 int ret
, i
, num
; /* of spaces */
275 ret
= strwidth(str
, &width
);
277 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
286 /* replace control characters by spaces */
287 for (i
= 0; i
< len
&& str
[i
]; i
++) {
288 if (str
[i
] == '\n' || str
[i
] == '\r' || str
[i
] == '\f')
293 add_spaces(win
, num
);
294 } else if (align
== RIGHT
) {
295 add_spaces(win
, num
);
298 add_spaces(win
, num
/ 2);
299 waddstr(win
, str
[0]? str
: "");
300 add_spaces(win
, num
- num
/ 2);
305 static void refresh_window(struct gui_window
*gw
)
307 gw
->needs_update
= true;
310 static bool window_update_needed(void)
312 return top
.needs_update
|| bot
.needs_update
|| sb
.needs_update
||
313 in
.needs_update
|| sep
.needs_update
;
316 __printf_2_3
static void print_in_bar(int color
, const char *fmt
,...)
321 if (!curses_active())
323 wattron(in
.win
, COLOR_PAIR(color
));
325 xvasprintf(&msg
, fmt
, ap
);
328 align_str(in
.win
, msg
, get_num_cols(&in
), LEFT
);
333 static void print_status_bar(void)
337 tmp
= para_strdup("para_gui " PACKAGE_VERSION
" (hit ? for help)");
339 align_str(sb
.win
, tmp
, get_num_cols(&sb
), CENTER
);
344 * get the number of the oldest rbe that is (partially) visible. On return,
345 * lines contains the sum of the number of lines of all visible entries. If the
346 * first one is only partially visible, lines is greater than bot.lines.
348 static int first_visible_rbe(unsigned *lines
)
350 int i
, bot_lines
= get_num_lines(&bot
);
353 for (i
= scroll_position
; i
< RINGBUFFER_SIZE
; i
++) {
354 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
358 rbe_lines
= NUM_LINES(rbe
->len
);
359 if (rbe_lines
> bot_lines
)
362 if (*lines
>= bot_lines
)
365 return RINGBUFFER_SIZE
- 1;
369 returns number of first visible rbe, *lines is the number of lines drawn.
371 static int draw_top_rbe(unsigned *lines
)
373 int bot_cols
, bot_lines
, ret
, fvr
= first_visible_rbe(lines
);
374 struct rb_entry
*rbe
;
375 size_t bytes_to_skip
, cells_to_skip
, width
;
379 wmove(bot
.win
, 0, 0);
380 rbe
= ringbuffer_get(bot_win_rb
, fvr
);
383 getmaxyx(bot
.win
, bot_lines
, bot_cols
);
384 if (*lines
> bot_lines
) {
385 /* rbe is partially visible multi-line */
386 cells_to_skip
= (*lines
- bot_lines
) * bot_cols
;
387 ret
= skip_cells(rbe
->msg
, cells_to_skip
, &bytes_to_skip
);
390 ret
= strwidth(rbe
->msg
+ bytes_to_skip
, &width
);
397 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
398 waddstr(bot
.win
, rbe
->msg
+ bytes_to_skip
);
399 *lines
= NUM_LINES(width
);
403 static void redraw_bot_win(void)
406 int i
, bot_lines
= get_num_lines(&bot
);
408 wmove(bot
.win
, 0, 0);
410 i
= draw_top_rbe(&lines
);
413 while (i
> 0 && lines
< bot_lines
) {
414 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, --i
);
417 waddstr(bot
.win
, "\n");
420 lines
+= NUM_LINES(rbe
->len
);
421 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
422 waddstr(bot
.win
, "\n");
423 waddstr(bot
.win
, rbe
->msg
);
426 refresh_window(&bot
);
429 static void rb_add_entry(int color
, char *msg
)
431 struct rb_entry
*old
, *new;
435 if (strwidth(msg
, &len
) < 0)
437 new = para_malloc(sizeof(struct rb_entry
));
441 old
= ringbuffer_add(bot_win_rb
, new);
446 if (scroll_position
) {
447 /* discard current scrolling, like xterm does */
452 wattron(bot
.win
, COLOR_PAIR(color
));
453 getyx(bot
.win
, y
, x
);
455 waddstr(bot
.win
, "\n");
456 waddstr(bot
.win
, msg
);
460 * print formated output to bot win and refresh
462 __printf_2_3
static void outputf(int color
, const char* fmt
,...)
467 if (!curses_active())
470 xvasprintf(&msg
, fmt
, ap
);
472 rb_add_entry(color
, msg
);
473 refresh_window(&bot
);
476 static int add_output_line(char *line
, void *data
)
478 int color
= *(int *)data
? COLOR_ERRMSG
: COLOR_OUTPUT
;
480 if (!curses_active())
482 rb_add_entry(color
, para_strdup(line
));
486 static __printf_2_3
void curses_log(int ll
, const char *fmt
,...)
493 if (curses_active()) {
494 int color
= ll
<= LL_NOTICE
? COLOR_MSG
: COLOR_ERRMSG
;
496 unsigned bytes
= xvasprintf(&msg
, fmt
, ap
);
497 if (bytes
> 0 && msg
[bytes
- 1] == '\n')
498 msg
[bytes
- 1] = '\0'; /* cut trailing newline */
499 rb_add_entry(color
, msg
);
500 refresh_window(&bot
);
501 } else if (exec_pid
<= 0) /* no external command running */
502 vfprintf(stderr
, fmt
, ap
);
505 /** The log function of para_gui, always set to curses_log(). */
506 __printf_2_3
void (*para_log
)(int, const char*, ...) = curses_log
;
508 static void shutdown_curses(void)
514 /* disable curses, print a message, kill running processes and exit */
515 __noreturn __printf_2_3
static void die(int exit_code
, const char* fmt
, ...)
521 vfprintf(stderr
, fmt
, argp
);
523 /* kill every process in the process group and exit */
524 para_sigaction(SIGTERM
, SIG_IGN
);
530 * Print stat item #i to curses window
532 static void print_stat_item(int i
)
535 struct stat_item_data d
= theme
.data
[i
];
536 char *c
= stat_content
[i
];
537 int top_lines
= get_num_lines(&top
);
539 if (!curses_active() || !d
.len
|| !c
)
541 tmp
= make_message("%s%s%s", d
.prefix
, c
, d
.postfix
);
542 wmove(top
.win
, d
.y
* top_lines
/ 100, d
.x
* COLS
/ 100);
543 wattron(top
.win
, COLOR_PAIR(i
+ 1));
544 align_str(top
.win
, tmp
, d
.len
* COLS
/ 100, d
.align
);
546 refresh_window(&top
);
549 static int update_item(int item_num
, char *buf
)
551 char **c
= stat_content
+ item_num
;
558 *c
= para_strdup("(artist tag not set)");
561 *c
= para_strdup("(title tag not set)");
564 *c
= para_strdup("????");
567 *c
= para_strdup("(album tag not set)");
570 *c
= para_strdup("(comment tag not set)");
574 *c
= para_strdup(buf
);
576 print_stat_item(item_num
);
580 static void print_all_items(void)
584 if (!curses_active())
586 FOR_EACH_STATUS_ITEM(i
)
590 static void clear_all_items(void)
594 FOR_EACH_STATUS_ITEM(i
) {
595 free(stat_content
[i
]);
596 stat_content
[i
] = para_strdup("");
600 static void status_pre_select(struct sched
*s
, void *context
)
602 struct status_task
*st
= context
;
605 para_fd_set(st
->fd
, &s
->rfds
, &s
->max_fileno
);
606 if (task_get_notification(st
->task
) < 0)
607 return sched_min_delay(s
);
609 sched_request_barrier_or_min_delay(&st
->next_exec
, s
);
612 static int status_post_select(struct sched
*s
, void *context
)
614 struct status_task
*st
= context
;
618 ret
= task_get_notification(st
->task
);
619 if (ret
== -E_GUI_SIGCHLD
&& st
->pid
> 0) {
621 if (waitpid(st
->pid
, &exit_status
, WNOHANG
) == st
->pid
) {
623 PARA_ERROR_LOG("stat command exit status: %d",
628 int fds
[3] = {0, 1, 0};
631 /* Avoid busy loop */
632 if (tv_diff(&st
->next_exec
, now
, NULL
) > 0)
634 st
->next_exec
.tv_sec
= now
->tv_sec
+ 2;
635 ret
= para_exec_cmdline_pid(&st
->pid
, conf
.stat_cmd_arg
, fds
);
638 ret
= mark_fd_nonblocking(fds
[1]);
647 if (st
->loaded
>= st
->bufsize
) {
648 if (st
->bufsize
> 1000 * 1000) {
652 st
->bufsize
+= st
->bufsize
+ 1000;
653 st
->buf
= para_realloc(st
->buf
, st
->bufsize
);
655 assert(st
->loaded
< st
->bufsize
);
656 ret
= read_nonblock(st
->fd
, st
->buf
+ st
->loaded
,
657 st
->bufsize
- st
->loaded
, &s
->rfds
, &sz
);
659 ret2
= for_each_stat_item(st
->buf
, st
->loaded
, update_item
);
660 if (ret
< 0 || ret2
< 0) {
662 PARA_NOTICE_LOG("closing stat pipe: %s\n",
663 para_strerror(ret
< 0? -ret
: -ret2
));
667 free(stat_content
[SI_BASENAME
]);
668 stat_content
[SI_BASENAME
] =
669 para_strdup("stat command terminated!?");
673 sz
= ret2
; /* what is left */
674 if (sz
> 0 && sz
< st
->loaded
)
675 memmove(st
->buf
, st
->buf
+ st
->loaded
- sz
, sz
);
683 static void init_wins(int top_lines
)
685 int top_y
= 0, bot_y
= top_lines
+ 1, sb_y
= LINES
- 2,
686 in_y
= LINES
- 1, sep_y
= top_lines
;
687 int bot_lines
= LINES
- top_lines
- 3, sb_lines
= 1, in_lines
= 1,
690 assume_default_colors(theme
.dflt
.fg
, theme
.dflt
.bg
);
692 wresize(top
.win
, top_lines
, COLS
);
693 mvwin(top
.win
, top_y
, 0);
695 wresize(sb
.win
, sb_lines
, COLS
);
696 mvwin(sb
.win
, sb_y
, 0);
698 wresize(sep
.win
, sep_lines
, COLS
);
699 mvwin(sep
.win
, sep_y
, 0);
701 wresize(bot
.win
, bot_lines
, COLS
);
702 mvwin(bot
.win
, bot_y
, 0);
704 wresize(in
.win
, in_lines
, COLS
);
705 mvwin(in
.win
, in_y
, 0);
707 sep
.win
= newwin(sep_lines
, COLS
, sep_y
, 0);
708 top
.win
= newwin(top_lines
, COLS
, top_y
, 0);
709 bot
.win
= newwin(bot_lines
, COLS
, bot_y
, 0);
710 sb
.win
= newwin(sb_lines
, COLS
, sb_y
, 0);
711 in
.win
= newwin(in_lines
, COLS
, in_y
, 0);
712 if (!top
.win
|| !bot
.win
|| !sb
.win
|| !in
.win
|| !sep
.win
)
713 die(EXIT_FAILURE
, "Error: Cannot create curses windows\n");
717 scrollok(bot
.win
, 1);
718 wattron(sb
.win
, COLOR_PAIR(COLOR_STATUSBAR
));
719 wattron(sep
.win
, COLOR_PAIR(COLOR_SEPARATOR
));
720 wattron(bot
.win
, COLOR_PAIR(COLOR_BOT
));
721 wattron(top
.win
, COLOR_PAIR(COLOR_TOP
));
732 wmove(sep
.win
, 0, 0);
733 whline(sep
.win
, theme
.sep_char
, COLS
);
737 wnoutrefresh(top
.win
);
738 wnoutrefresh(bot
.win
);
740 wnoutrefresh(sb
.win
);
741 wnoutrefresh(in
.win
);
742 wnoutrefresh(sep
.win
);
746 static void init_pair_or_die(short pair
, short f
, short b
)
748 if (init_pair(pair
, f
, b
) == ERR
)
749 die(EXIT_FAILURE
, "fatal: init_pair() failed\n");
752 static void init_colors_or_die(void)
757 die(EXIT_FAILURE
, "fatal: No color term\n");
758 if (start_color() == ERR
)
759 die(EXIT_FAILURE
, "fatal: failed to start colors\n");
760 FOR_EACH_STATUS_ITEM(i
)
761 if (theme
.data
[i
].len
)
762 init_pair_or_die(i
+ 1, theme
.data
[i
].color
.fg
,
763 theme
.data
[i
].color
.bg
);
764 init_pair_or_die(COLOR_STATUSBAR
, theme
.sb
.fg
, theme
.sb
.bg
);
765 init_pair_or_die(COLOR_COMMAND
, theme
.cmd
.fg
, theme
.cmd
.bg
);
766 init_pair_or_die(COLOR_OUTPUT
, theme
.output
.fg
, theme
.output
.bg
);
767 init_pair_or_die(COLOR_MSG
, theme
.msg
.fg
, theme
.msg
.bg
);
768 init_pair_or_die(COLOR_ERRMSG
, theme
.err_msg
.fg
, theme
.err_msg
.bg
);
769 init_pair_or_die(COLOR_SEPARATOR
, theme
.sep
.fg
, theme
.sep
.bg
);
770 init_pair_or_die(COLOR_TOP
, theme
.dflt
.fg
, theme
.dflt
.bg
);
771 init_pair_or_die(COLOR_BOT
, theme
.dflt
.fg
, theme
.dflt
.bg
);
774 /* (Re-)initialize the curses library. */
775 static void init_curses(void)
779 if (top
.win
&& refresh() == ERR
) /* refresh is really needed */
780 die(EXIT_FAILURE
, "refresh() failed\n");
781 if (LINES
< theme
.lines_min
|| COLS
< theme
.cols_min
)
782 die(EXIT_FAILURE
, "Terminal (%dx%d) too small"
783 " (need at least %dx%d)\n", COLS
, LINES
,
784 theme
.cols_min
, theme
.lines_min
);
785 curs_set(0); /* make cursor invisible, ignore errors */
786 nonl(); /* do not NL->CR/NL on output, always returns OK */
787 /* don't echo input */
789 die(EXIT_FAILURE
, "fatal: noecho() failed\n");
790 /* take input chars one at a time, no wait for \n */
792 die(EXIT_FAILURE
, "fatal: cbreak() failed\n");
793 init_colors_or_die();
794 clear(); /* ignore non-fatal errors */
795 init_wins(theme
.top_lines_default
);
796 // noecho(); /* don't echo input */
800 * This sucker modifies its first argument. *handler and *arg are
801 * pointers to 0-terminated strings (inside line). Crap.
803 static int split_key_map(char *line
, char **handler
, char **arg
)
805 if (!(*handler
= strchr(line
+ 1, ':')))
809 if (!(*arg
= strchr(*handler
, ':')))
818 static void check_key_map_args_or_die(void)
823 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
827 tmp
= para_strdup(conf
.key_map_arg
[i
]);
828 if (!split_key_map(tmp
, &handler
, &arg
))
830 if (strlen(handler
) != 1)
832 if (*handler
!= 'x' && *handler
!= 'd' && *handler
!= 'i'
837 if (find_cmd_byname(arg
) < 0)
840 if (i
!= conf
.key_map_given
)
841 die(EXIT_FAILURE
, "invalid key map: %s\n", conf
.key_map_arg
[i
]);
845 static void parse_config_file_or_die(bool override
)
849 struct gui_cmdline_parser_params params
= {
850 .override
= override
,
852 .check_required
= !override
,
853 .check_ambiguity
= 0,
857 if (conf
.config_file_given
)
858 config_file
= para_strdup(conf
.config_file_arg
);
860 char *home
= para_homedir();
861 config_file
= make_message("%s/.paraslash/gui.conf", home
);
864 if (!file_exists(config_file
)) {
865 if (!conf
.config_file_given
)
868 PARA_EMERG_LOG("config file %s does not exist\n",
875 * When the gengetopt config file parser is called more than once, any
876 * key map arguments found in the config file are _appended_ to the old
877 * values, even though we turn on ->override. We want the new arguments
878 * to replace the old ones, so we must empty the key_map_arg array
879 * first. Unfortunately, this also clears any key map arguments given
880 * at the command line.
884 for (i
= 0; i
< conf
.key_map_given
; i
++) {
885 free(conf
.key_map_arg
[i
]);
886 conf
.key_map_arg
[i
] = NULL
;
888 conf
.key_map_given
= 0;
891 gui_cmdline_parser_config_file(config_file
, &conf
, ¶ms
);
892 loglevel
= get_loglevel_by_name(conf
.loglevel_arg
);
893 check_key_map_args_or_die();
899 theme_init(conf
.theme_arg
, &theme
);
902 /* reread configuration, terminate on errors */
903 static void reread_conf(void)
906 * gengetopt might print to stderr and exit on errors. So we have to
907 * shutdown curses first.
910 parse_config_file_or_die(true /* override */);
912 print_in_bar(COLOR_MSG
, "config file reloaded\n");
916 * React to various signal-related events
918 static int signal_post_select(struct sched
*s
, __a_unused
void *context
)
920 int ret
= para_next_signal(&s
->rfds
);
926 die(EXIT_FAILURE
, "only the good die young (caught SIGTERM)\n");
929 PARA_WARNING_LOG("caught SIGINT, reset\n");
930 /* Nothing to do. SIGINT killed our child which gets noticed
931 * by do_select and resets everything.
935 PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
939 task_notify_all(s
, E_GUI_SIGCHLD
);
945 static enum exec_status
exec_status(void)
947 if (exec_fds
[0] >= 0 || exec_fds
[1] >= 0)
954 static void exec_pre_select(struct sched
*s
, void *context
)
956 struct exec_task
*et
= context
;
957 if (exec_fds
[0] >= 0)
958 para_fd_set(exec_fds
[0], &s
->rfds
, &s
->max_fileno
);
959 if (exec_fds
[1] >= 0)
960 para_fd_set(exec_fds
[1], &s
->rfds
, &s
->max_fileno
);
961 if (task_get_notification(et
->task
) < 0)
965 static int exec_post_select(struct sched
*s
, void *context
)
967 struct exec_task
*ct
= context
;
970 ret
= task_get_notification(ct
->task
);
971 if (ret
== -E_GUI_SIGCHLD
&& exec_pid
> 0) {
973 if (waitpid(exec_pid
, &exit_status
, WNOHANG
) == exec_pid
) {
976 PARA_INFO_LOG("command exit status: %d", exit_status
);
977 print_in_bar(COLOR_MSG
, " ");
980 for (i
= 0; i
< 2; i
++) {
984 ret
= read_nonblock(exec_fds
[i
],
985 ct
->command_buf
[i
] + ct
->cbo
[i
],
986 COMMAND_BUF_SIZE
- 1 - ct
->cbo
[i
], &s
->rfds
, &sz
);
989 ct
->cbo
[i
] = for_each_line(ct
->flags
[i
], ct
->command_buf
[i
],
990 ct
->cbo
[i
], add_output_line
, &i
);
991 if (sz
!= ct
->cbo
[i
]) { /* at least one line found */
992 refresh_window(&bot
);
995 if (ret
< 0 || exec_pid
== 0) {
997 PARA_NOTICE_LOG("closing command fd %d: %s",
998 i
, para_strerror(-ret
));
1003 if (exec_fds
[!i
] < 0) /* both fds closed */
1006 if (ct
->cbo
[i
] == COMMAND_BUF_SIZE
- 1) {
1007 PARA_NOTICE_LOG("discarding overlong line");
1009 ct
->flags
[i
] = FELF_DISCARD_FIRST
;
1015 static void input_pre_select(struct sched
*s
, __a_unused
void *context
)
1017 if (exec_status() != EXEC_XCMD
)
1018 para_fd_set(STDIN_FILENO
, &s
->rfds
, &s
->max_fileno
);
1019 if (window_update_needed())
1023 /* read from command pipe and print data to bot window */
1024 static void exec_and_display(const char *file_and_args
)
1026 int ret
, fds
[3] = {0, 1, 1};
1028 outputf(COLOR_COMMAND
, "%s", file_and_args
);
1029 ret
= para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1032 ret
= mark_fd_nonblocking(fds
[1]);
1035 ret
= mark_fd_nonblocking(fds
[2]);
1038 exec_fds
[0] = fds
[1];
1039 exec_fds
[1] = fds
[2];
1040 print_in_bar(COLOR_MSG
, "hit any key to abort\n");
1043 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
1048 static void exec_para(const char *args
)
1050 char *file_and_args
;
1052 file_and_args
= make_message(BINDIR
"/para_client -- %s", args
);
1053 exec_and_display(file_and_args
);
1054 free(file_and_args
);
1058 * shutdown curses and stat pipe before executing external commands
1060 static void exec_external(char *file_and_args
)
1062 int fds
[3] = {-1, -1, -1};
1067 para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1070 static void handle_command(int c
)
1074 /* first check user-defined key bindings */
1075 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
1076 char *tmp
, *handler
, *arg
;
1078 tmp
= para_strdup(conf
.key_map_arg
[i
]);
1079 if (!split_key_map(tmp
, &handler
, &arg
)) {
1083 if (strcmp(tmp
, km_keyname(c
))) {
1087 if (*handler
== 'd')
1088 exec_and_display(arg
);
1089 else if (*handler
== 'x')
1091 else if (*handler
== 'p')
1093 else if (*handler
== 'i') {
1094 int num
= find_cmd_byname(arg
);
1096 command_list
[num
].handler();
1101 /* not found, check internal key bindings */
1102 for (i
= 0; command_list
[i
].handler
; i
++) {
1103 if (!strcmp(km_keyname(c
), command_list
[i
].key
)) {
1104 command_list
[i
].handler();
1108 print_in_bar(COLOR_ERRMSG
, "key '%s' is not bound, press ? for help",
1112 static int input_post_select(__a_unused
struct sched
*s
, __a_unused
void *context
)
1115 enum exec_status exs
= exec_status();
1117 if (exs
== EXEC_XCMD
)
1119 if (window_update_needed()) {
1120 if (top
.needs_update
)
1121 assert(wnoutrefresh(top
.win
) == OK
);
1122 if (bot
.needs_update
)
1123 assert(wnoutrefresh(bot
.win
) == OK
);
1124 if (sep
.needs_update
)
1125 assert(wnoutrefresh(sep
.win
) == OK
);
1126 if (sb
.needs_update
)
1127 assert(wnoutrefresh(sb
.win
) == OK
);
1128 if (in
.needs_update
)
1129 assert(wnoutrefresh(in
.win
) == OK
);
1131 top
.needs_update
= bot
.needs_update
= sb
.needs_update
=
1132 in
.needs_update
= sep
.needs_update
= false;
1134 ret
= wgetch(top
.win
);
1137 if (ret
== KEY_RESIZE
) {
1138 if (curses_active()) {
1145 if (exs
== EXEC_IDLE
)
1146 handle_command(ret
);
1147 else if (exec_pid
> 0)
1148 kill(exec_pid
, SIGTERM
);
1152 static void print_scroll_msg(void)
1154 unsigned lines_total
, filled
= ringbuffer_filled(bot_win_rb
);
1155 int first_rbe
= first_visible_rbe(&lines_total
);
1157 print_in_bar(COLOR_MSG
, "scrolled view: %d-%d/%d\n", filled
- first_rbe
,
1158 filled
- scroll_position
, ringbuffer_filled(bot_win_rb
));
1161 static void com_scroll_top(void)
1163 int i
= RINGBUFFER_SIZE
- 1, bot_lines
= get_num_lines(&bot
);
1166 while (i
> 0 && !ringbuffer_get(bot_win_rb
, i
))
1168 /* i is oldest entry */
1169 for (; lines
< bot_lines
&& i
>= 0; i
--) {
1170 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1173 lines
+= NUM_LINES(rbe
->len
);
1176 if (lines
> 0 && scroll_position
!= i
) {
1177 scroll_position
= i
;
1182 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1185 static void com_cancel_scroll(void)
1188 if (scroll_position
== 0) {
1189 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1192 scroll_position
= 0;
1196 static void com_page_down(void)
1199 int i
= scroll_position
, bot_lines
= get_num_lines(&bot
);
1201 while (lines
< bot_lines
&& --i
> 0) {
1202 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1205 lines
+= NUM_LINES(rbe
->len
);
1208 scroll_position
= i
;
1213 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1216 static void com_page_up(void)
1219 int fvr
= first_visible_rbe(&lines
), bot_lines
= get_num_lines(&bot
);
1221 if (fvr
< 0 || fvr
+ 1 >= ringbuffer_filled(bot_win_rb
)) {
1222 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1225 scroll_position
= fvr
+ 1;
1226 for (; scroll_position
> 0; scroll_position
--) {
1227 first_visible_rbe(&lines
);
1228 if (lines
== bot_lines
)
1235 static void com_scroll_down(void)
1237 struct rb_entry
*rbe
;
1238 int rbe_lines
, bot_lines
= get_num_lines(&bot
);
1240 if (!scroll_position
) {
1241 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1245 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1246 rbe_lines
= NUM_LINES(rbe
->len
);
1247 wscrl(bot
.win
, rbe_lines
);
1248 wmove(bot
.win
, bot_lines
- rbe_lines
, 0);
1249 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1250 waddstr(bot
.win
, rbe
->msg
);
1251 refresh_window(&bot
);
1255 static void com_scroll_up(void)
1257 struct rb_entry
*rbe
= NULL
;
1259 int i
, first_rbe
, num_scroll
;
1261 /* the entry that is going to vanish */
1262 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1265 num_scroll
= NUM_LINES(rbe
->len
);
1266 first_rbe
= first_visible_rbe(&lines
);
1267 if (first_rbe
< 0 || (first_rbe
== ringbuffer_filled(bot_win_rb
) - 1))
1270 wscrl(bot
.win
, -num_scroll
);
1271 i
= draw_top_rbe(&lines
);
1274 while (i
> 0 && lines
< num_scroll
) {
1276 rbe
= ringbuffer_get(bot_win_rb
, --i
);
1279 rbe_lines
= NUM_LINES(rbe
->len
);
1281 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1282 waddstr(bot
.win
, "\n");
1283 waddstr(bot
.win
, rbe
->msg
);
1288 refresh_window(&bot
);
1292 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1295 static void com_ll_decr(void)
1297 if (loglevel
<= LL_DEBUG
) {
1298 print_in_bar(COLOR_ERRMSG
,
1299 "loglevel already at maximal verbosity\n");
1303 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1306 static void com_ll_incr(void)
1308 if (loglevel
>= LL_EMERG
) {
1309 print_in_bar(COLOR_ERRMSG
,
1310 "loglevel already at minimal verbosity\n");
1314 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1317 static void com_reread_conf(void)
1322 static void com_help(void)
1326 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
1327 char *handler
, *arg
, *tmp
= para_strdup(conf
.key_map_arg
[i
]);
1328 const char *handler_text
= "???", *desc
= NULL
;
1330 if (!split_key_map(tmp
, &handler
, &arg
)) {
1336 handler_text
= "internal";
1337 desc
= command_list
[find_cmd_byname(arg
)].description
;
1339 case 'x': handler_text
= "external"; break;
1340 case 'd': handler_text
= "display "; break;
1341 case 'p': handler_text
= "para "; break;
1343 outputf(COLOR_MSG
, "%s\t%s\t%s%s\t%s", tmp
, handler_text
, arg
,
1344 strlen(arg
) < 8? "\t" : "",
1348 for (i
= 0; command_list
[i
].handler
; i
++) {
1349 struct gui_command gc
= command_list
[i
];
1351 outputf(COLOR_MSG
, "%s\tinternal\t%s\t%s%s", gc
.key
, gc
.name
,
1352 strlen(gc
.name
) < 8? "\t" : "",
1355 print_in_bar(COLOR_MSG
, "try \"para_gui -h\" or \"para_client help\" "
1359 static void com_shrink_top_win(void)
1361 int top_lines
= get_num_lines(&top
);
1363 if (top_lines
<= theme
.top_lines_min
) {
1364 PARA_WARNING_LOG("can not decrease top window\n");
1367 init_wins(top_lines
- 1);
1368 print_in_bar(COLOR_MSG
, "%s", "decreased top window");
1371 static void com_enlarge_top_win(void)
1373 int top_lines
= get_num_lines(&top
), bot_lines
= get_num_lines(&bot
);
1375 if (bot_lines
< 3) {
1376 PARA_WARNING_LOG("can not increase top window\n");
1379 init_wins(top_lines
+ 1);
1380 print_in_bar(COLOR_MSG
, "increased top window");
1383 static void com_version(void)
1385 print_in_bar(COLOR_MSG
, "%s", version_single_line("gui"));
1388 __noreturn
static void com_quit(void)
1390 die(EXIT_SUCCESS
, "%s", "");
1393 static void com_refresh(void)
1399 static void com_next_theme(void)
1405 static void com_prev_theme(void)
1411 __noreturn
static void print_help_and_die(void)
1413 struct ggo_help h
= DEFINE_GGO_HELP(gui
);
1414 bool d
= conf
.detailed_help_given
;
1416 ggo_print_help(&h
, d
? GPH_STANDARD_FLAGS_DETAILED
: GPH_STANDARD_FLAGS
);
1420 static int setup_tasks_and_schedule(void)
1423 struct exec_task exec_task
= {.task
= NULL
};
1424 struct status_task status_task
= {.fd
= -1};
1425 struct input_task input_task
= {.task
= NULL
};
1426 struct signal_task
*signal_task
;
1427 struct sched sched
= {
1428 .default_timeout
= {
1429 .tv_sec
= conf
.timeout_arg
/ 1000,
1430 .tv_usec
= (conf
.timeout_arg
% 1000) * 1000,
1434 exec_task
.task
= task_register(&(struct task_info
) {
1436 .pre_select
= exec_pre_select
,
1437 .post_select
= exec_post_select
,
1438 .context
= &exec_task
,
1441 status_task
.task
= task_register(&(struct task_info
) {
1443 .pre_select
= status_pre_select
,
1444 .post_select
= status_post_select
,
1445 .context
= &status_task
,
1448 input_task
.task
= task_register(&(struct task_info
) {
1450 .pre_select
= input_pre_select
,
1451 .post_select
= input_post_select
,
1452 .context
= &input_task
,
1455 signal_task
= signal_init_or_die();
1456 para_install_sighandler(SIGINT
);
1457 para_install_sighandler(SIGTERM
);
1458 para_install_sighandler(SIGCHLD
);
1459 para_install_sighandler(SIGUSR1
);
1460 signal_task
->task
= task_register(&(struct task_info
) {
1462 .pre_select
= signal_pre_select
,
1463 .post_select
= signal_post_select
,
1464 .context
= signal_task
,
1466 ret
= schedule(&sched
);
1467 sched_shutdown(&sched
);
1468 signal_shutdown(signal_task
);
1473 * The main function of para_gui.
1475 * \param argc Usual argument count.
1476 * \param argv Usual argument vector.
1478 * After initialization para_gui registers the following tasks to the paraslash
1479 * scheduler: status, exec, signal, input.
1481 * The status task executes the para_audioc stat command to obtain the status
1482 * of para_server and para_audiod, and displays this information in the top
1483 * window of para_gui.
1485 * The exec task is responsible for printing the output of the currently
1486 * running executable to the bottom window.
1488 * The signal task performs suitable actions according to any signals received.
1489 * For example it refreshes all windows on terminal size changes and resets the
1490 * terminal on \p SIGTERM.
1492 * The input task reads single key strokes from stdin. For each key pressed, it
1493 * executes the command handler associated with this key.
1495 * \return \p EXIT_SUCCESS or \p EXIT_FAILURE.
1497 int main(int argc
, char *argv
[])
1499 gui_cmdline_parser(argc
, argv
, &conf
); /* exits on errors */
1500 loglevel
= get_loglevel_by_name(conf
.loglevel_arg
);
1501 version_handle_flag("gui", conf
.version_given
);
1502 if (conf
.help_given
|| conf
.detailed_help_given
)
1503 print_help_and_die();
1504 parse_config_file_or_die(false /* override */);
1505 bot_win_rb
= ringbuffer_new(RINGBUFFER_SIZE
);
1506 setlocale(LC_CTYPE
, "");
1507 initscr(); /* needed only once, always successful */
1509 return setup_tasks_and_schedule() < 0? EXIT_FAILURE
: EXIT_SUCCESS
;