0eb5444fc42c60a3fd116f3bdf8fbc76cc698416
1 /* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
3 /** \file gui.c Curses-based interface for paraslash. */
18 #include "ringbuffer.h"
26 /** Array of error strings. */
29 static struct lls_parse_result
*cmdline_lpr
, *lpr
;
31 #define CMD_PTR (lls_cmd(0, gui_suite))
32 #define OPT_RESULT(_name) (lls_opt_result(LSG_GUI_PARA_GUI_OPT_ ## _name, lpr))
33 #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
34 #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
35 #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
36 #define FOR_EACH_KEY_MAP(_i) for (_i = 0; _i < OPT_GIVEN(KEY_MAP); _i++)
38 static char *stat_content
[NUM_STAT_ITEMS
];
40 static struct gui_window
{
43 } top
, bot
, sb
, in
, sep
;
45 /** How many lines of output to remember. */
46 #define RINGBUFFER_SIZE 512
53 static struct ringbuffer
*bot_win_rb
;
55 static unsigned scroll_position
;
56 static pid_t exec_pid
;
57 static int exec_fds
[2] = {-1, -1};
60 /** Type of the process currently being executed. */
62 EXEC_IDLE
, /**< No process running. */
63 EXEC_DCMD
, /**< para or display process running. */
64 EXEC_XCMD
, /**< External process running. */
68 * Codes for various colors.
70 * Each status item has its own color pair. The ones defined here start at a
71 * higher number so that they do not overlap with these.
74 COLOR_STATUSBAR
= NUM_STAT_ITEMS
+ 1,
87 const char *description
;
88 void (*handler
)(void);
91 static struct gui_theme theme
;
93 #define GUI_COMMANDS \
94 GUI_COMMAND(help, "?", "print help") \
95 GUI_COMMAND(enlarge_top_win, "+", "enlarge the top window") \
96 GUI_COMMAND(shrink_top_win, "-", "shrink the top window") \
97 GUI_COMMAND(reread_conf, "r", "reread configuration file") \
98 GUI_COMMAND(quit, "q", "exit para_gui") \
99 GUI_COMMAND(refresh, "^L", "redraw the screen") \
100 GUI_COMMAND(next_theme, ".", "switch to next theme") \
101 GUI_COMMAND(prev_theme, ",", "switch to previous theme") \
102 GUI_COMMAND(ll_incr, ">", "increase loglevel (decreases verbosity)") \
103 GUI_COMMAND(ll_decr, "<", "decrease loglevel (increases verbosity)") \
104 GUI_COMMAND(version, "V", "show the para_gui version") \
105 GUI_COMMAND(scroll_up, "<up>", "scroll up one line") \
106 GUI_COMMAND(scroll_down, "<down>", "scroll_down") \
107 GUI_COMMAND(page_up, "<ppage>", "scroll up one page") \
108 GUI_COMMAND(page_down, "<npage>", "scroll down one page") \
109 GUI_COMMAND(scroll_top, "<home>", "scroll to top of buffer") \
110 GUI_COMMAND(cancel_scroll, "<end>", "deactivate scroll mode") \
112 /* declare command handlers */
113 #define GUI_COMMAND(_c, _k, _d) \
114 static void com_ ## _c(void);
119 /* define command array */
120 #define GUI_COMMAND(_c, _k, _d) \
125 .handler = com_ ## _c \
128 static struct gui_command command_list
[] = {GUI_COMMANDS
{.name
= NULL
}};
139 struct timeval next_exec
;
143 /** Stdout/stderr of the executing process is read in chunks of this size. */
144 #define COMMAND_BUF_SIZE 32768
148 char command_buf
[2][COMMAND_BUF_SIZE
]; /* stdout/stderr of command */
149 int cbo
[2]; /* command buf offsets */
150 unsigned flags
[2]; /* passed to for_each_line() */
153 static int find_cmd_byname(const char *name
)
157 for (i
= 0; command_list
[i
].handler
; i
++)
158 if (!strcmp(command_list
[i
].name
, name
))
164 * Even though ncurses provides getmaxx and getmaxy, these functions/macros are
165 * not described in the XSI Curses standard.
167 static int get_num_lines(struct gui_window
*w
)
170 __a_unused
int cols
; /* avoid "set but not used" warnings */
172 getmaxyx(w
->win
, lines
, cols
);
176 static int get_num_cols(struct gui_window
*w
)
178 __a_unused
int lines
; /* avoid "set but not used" warnings */
181 getmaxyx(w
->win
, lines
, cols
);
185 /** Number of lines of the window are occupied by an output line. */
186 #define NUM_LINES(len) (1 + (len) / get_num_cols(&bot))
188 /* isendwin() returns false before initscr() was called */
189 static bool curses_active(void)
191 return top
.win
&& !isendwin();
194 /* taken from mutt */
195 static const char *km_keyname(int c
)
200 sprintf(buf
, "<up>");
204 sprintf(buf
, "<down>");
208 sprintf(buf
, "<left>");
211 if (c
== KEY_RIGHT
) {
212 sprintf(buf
, "<right>");
215 if (c
== KEY_NPAGE
) {
216 sprintf(buf
, "<npage>");
219 if (c
== KEY_PPAGE
) {
220 sprintf(buf
, "<ppage>");
224 sprintf(buf
, "<home>");
228 sprintf(buf
, "<end>");
231 if (c
< 256 && c
> -128 && iscntrl((unsigned char) c
)) {
236 buf
[1] = (c
+ '@') & 0x7f;
239 snprintf(buf
, sizeof(buf
), "\\%d%d%d", c
>> 6,
240 (c
>> 3) & 7, c
& 7);
241 } else if (c
>= KEY_F0
&& c
< KEY_F(256))
242 sprintf(buf
, "<F%d>", c
- KEY_F0
);
244 snprintf(buf
, sizeof(buf
), "%c", (unsigned char) c
);
246 snprintf(buf
, sizeof(buf
), "\\x%hx", (unsigned short) c
);
250 /* Print given number of spaces to curses window. */
251 static void add_spaces(WINDOW
*win
, unsigned int num
)
253 const char space
[] = " ";
254 const unsigned sz
= sizeof(space
) - 1; /* number of spaces */
262 waddstr(win
, space
+ sz
- num
);
267 * Print aligned string to curses window. This function always prints
270 static int align_str(WINDOW
*win
, const char *str
, unsigned int len
,
273 int ret
, num
; /* of spaces */
275 char *sstr
; /* sanitized string */
279 ret
= sanitize_str(str
, len
, &sstr
, &width
);
281 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
283 sstr
= para_strdup(NULL
);
285 assert(width
<= len
);
289 add_spaces(win
, num
);
290 } else if (align
== RIGHT
) {
291 add_spaces(win
, num
);
294 add_spaces(win
, num
/ 2);
296 add_spaces(win
, num
- num
/ 2);
302 static void refresh_window(struct gui_window
*gw
)
304 gw
->needs_update
= true;
307 static bool window_update_needed(void)
309 return top
.needs_update
|| bot
.needs_update
|| sb
.needs_update
||
310 in
.needs_update
|| sep
.needs_update
;
313 __printf_2_3
static void print_in_bar(int color
, const char *fmt
,...)
318 if (!curses_active())
320 wattron(in
.win
, COLOR_PAIR(color
));
322 xvasprintf(&msg
, fmt
, ap
);
325 align_str(in
.win
, msg
, get_num_cols(&in
), LEFT
);
330 static void print_status_bar(void)
334 tmp
= para_strdup("para_gui " PACKAGE_VERSION
" (hit ? for help)");
336 align_str(sb
.win
, tmp
, get_num_cols(&sb
), CENTER
);
341 * get the number of the oldest rbe that is (partially) visible. On return,
342 * lines contains the sum of the number of lines of all visible entries. If the
343 * first one is only partially visible, lines is greater than bot.lines.
345 static int first_visible_rbe(unsigned *lines
)
347 int i
, bot_lines
= get_num_lines(&bot
);
350 for (i
= scroll_position
; i
< RINGBUFFER_SIZE
; i
++) {
351 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
355 rbe_lines
= NUM_LINES(rbe
->len
);
356 if (rbe_lines
> bot_lines
)
359 if (*lines
>= bot_lines
)
362 return RINGBUFFER_SIZE
- 1;
366 returns number of first visible rbe, *lines is the number of lines drawn.
368 static int draw_top_rbe(unsigned *lines
)
370 int bot_cols
, bot_lines
, ret
, fvr
= first_visible_rbe(lines
);
371 struct rb_entry
*rbe
;
372 size_t bytes_to_skip
, cells_to_skip
, width
;
376 wmove(bot
.win
, 0, 0);
377 rbe
= ringbuffer_get(bot_win_rb
, fvr
);
380 getmaxyx(bot
.win
, bot_lines
, bot_cols
);
381 if (*lines
> bot_lines
) {
382 /* rbe is partially visible multi-line */
383 cells_to_skip
= (*lines
- bot_lines
) * bot_cols
;
384 ret
= skip_cells(rbe
->msg
, cells_to_skip
, &bytes_to_skip
);
387 ret
= strwidth(rbe
->msg
+ bytes_to_skip
, &width
);
394 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
395 waddstr(bot
.win
, rbe
->msg
+ bytes_to_skip
);
396 *lines
= NUM_LINES(width
);
400 static void redraw_bot_win(void)
403 int i
, bot_lines
= get_num_lines(&bot
);
405 wmove(bot
.win
, 0, 0);
407 i
= draw_top_rbe(&lines
);
410 while (i
> 0 && lines
< bot_lines
) {
411 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, --i
);
414 waddstr(bot
.win
, "\n");
417 lines
+= NUM_LINES(rbe
->len
);
418 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
419 waddstr(bot
.win
, "\n");
420 waddstr(bot
.win
, rbe
->msg
);
423 refresh_window(&bot
);
426 static void rb_add_entry(int color
, char *msg
)
428 struct rb_entry
*old
, *new;
432 if (strwidth(msg
, &len
) < 0)
434 new = para_malloc(sizeof(struct rb_entry
));
438 old
= ringbuffer_add(bot_win_rb
, new);
443 if (scroll_position
) {
444 /* discard current scrolling, like xterm does */
449 wattron(bot
.win
, COLOR_PAIR(color
));
450 getyx(bot
.win
, y
, x
);
452 waddstr(bot
.win
, "\n");
453 waddstr(bot
.win
, msg
);
456 /* Print formatted output to bot win and refresh. */
457 __printf_2_3
static void outputf(int color
, const char *fmt
,...)
462 if (!curses_active())
465 xvasprintf(&msg
, fmt
, ap
);
467 rb_add_entry(color
, msg
);
468 refresh_window(&bot
);
471 static int add_output_line(char *line
, void *data
)
473 int color
= *(int *)data
? COLOR_ERRMSG
: COLOR_OUTPUT
;
475 if (!curses_active())
477 rb_add_entry(color
, para_strdup(line
));
481 static __printf_2_3
void curses_log(int ll
, const char *fmt
,...)
488 if (curses_active()) {
489 int color
= ll
<= LL_NOTICE
? COLOR_MSG
: COLOR_ERRMSG
;
491 unsigned bytes
= xvasprintf(&msg
, fmt
, ap
);
492 if (bytes
> 0 && msg
[bytes
- 1] == '\n')
493 msg
[bytes
- 1] = '\0'; /* cut trailing newline */
494 rb_add_entry(color
, msg
);
495 refresh_window(&bot
);
496 } else if (exec_pid
<= 0) /* no external command running */
497 vfprintf(stderr
, fmt
, ap
);
501 /** The log function of para_gui, always set to curses_log(). */
502 __printf_2_3
void (*para_log
)(int, const char *, ...) = curses_log
;
504 /* Call endwin() to reset the terminal into non-visual mode. */
505 static void shutdown_curses(void)
508 * If para_gui received a terminating signal in external mode, the
509 * terminal can be in an unusable state at this point because the child
510 * process might not have caught the signal. In this case endwin() has
511 * already been called and must not be called again. So we first return
512 * to program mode, then call endwin().
514 if (!curses_active())
519 /* Disable curses, print a message, kill running processes and exit. */
520 __noreturn __printf_2_3
static void die(int exit_code
, const char *fmt
, ...)
524 /* Kill every process in our process group. */
525 para_sigaction(SIGTERM
, SIG_IGN
);
527 /* Wait up to two seconds for child processes to die. */
529 while (waitpid(0, NULL
, 0) >= 0)
532 /* mousemask() exists only in ncurses */
533 #ifdef NCURSES_MOUSE_VERSION
534 mousemask(~(mmask_t
)0, NULL
); /* Avoid bad terminal state with xterm. */
538 vfprintf(stderr
, fmt
, argp
);
543 /* Print stat item #i to curses window. */
544 static void print_stat_item(int i
)
547 struct stat_item_data d
= theme
.data
[i
];
548 char *c
= stat_content
[i
];
549 int top_lines
= get_num_lines(&top
);
551 if (!curses_active() || !d
.len
|| !c
)
553 tmp
= make_message("%s%s%s", d
.prefix
, c
, d
.postfix
);
554 wmove(top
.win
, d
.y
* top_lines
/ 100, d
.x
* COLS
/ 100);
555 wattron(top
.win
, COLOR_PAIR(i
+ 1));
556 align_str(top
.win
, tmp
, d
.len
* COLS
/ 100, d
.align
);
558 refresh_window(&top
);
561 static int update_item(int item_num
, char *buf
)
563 char **c
= stat_content
+ item_num
;
570 *c
= para_strdup("(artist tag not set)");
573 *c
= para_strdup("(title tag not set)");
576 *c
= para_strdup("????");
579 *c
= para_strdup("(album tag not set)");
582 *c
= para_strdup("(comment tag not set)");
586 *c
= para_strdup(buf
);
588 print_stat_item(item_num
);
592 static void print_all_items(void)
596 if (!curses_active())
598 FOR_EACH_STATUS_ITEM(i
)
602 static void clear_all_items(void)
606 FOR_EACH_STATUS_ITEM(i
) {
607 free(stat_content
[i
]);
608 stat_content
[i
] = para_strdup("");
612 static void status_pre_select(struct sched
*s
, void *context
)
614 struct status_task
*st
= context
;
617 para_fd_set(st
->fd
, &s
->rfds
, &s
->max_fileno
);
618 if (task_get_notification(st
->task
) < 0)
619 return sched_min_delay(s
);
621 sched_request_barrier_or_min_delay(&st
->next_exec
, s
);
624 static int status_post_select(struct sched
*s
, void *context
)
626 struct status_task
*st
= context
;
630 ret
= task_get_notification(st
->task
);
631 if (ret
== -E_GUI_SIGCHLD
&& st
->pid
> 0) {
633 if (waitpid(st
->pid
, &exit_status
, WNOHANG
) == st
->pid
) {
635 PARA_ERROR_LOG("stat command exit status: %d",
640 int fds
[3] = {0, 1, 0};
643 /* Avoid busy loop */
644 if (tv_diff(&st
->next_exec
, now
, NULL
) > 0)
646 st
->next_exec
.tv_sec
= now
->tv_sec
+ 2;
647 ret
= para_exec_cmdline_pid(&st
->pid
,
648 OPT_STRING_VAL(STAT_CMD
), fds
);
651 ret
= mark_fd_nonblocking(fds
[1]);
660 if (st
->loaded
>= st
->bufsize
) {
661 if (st
->bufsize
> 1000 * 1000) {
665 st
->bufsize
+= st
->bufsize
+ 1000;
666 st
->buf
= para_realloc(st
->buf
, st
->bufsize
);
668 assert(st
->loaded
< st
->bufsize
);
669 ret
= read_nonblock(st
->fd
, st
->buf
+ st
->loaded
,
670 st
->bufsize
- st
->loaded
, &s
->rfds
, &sz
);
672 ret2
= for_each_stat_item(st
->buf
, st
->loaded
, update_item
);
673 if (ret
< 0 || ret2
< 0) {
675 PARA_NOTICE_LOG("closing stat pipe: %s\n",
676 para_strerror(ret
< 0? -ret
: -ret2
));
680 free(stat_content
[SI_basename
]);
681 stat_content
[SI_basename
] =
682 para_strdup("stat command terminated!?");
686 sz
= ret2
; /* what is left */
687 if (sz
> 0 && sz
< st
->loaded
)
688 memmove(st
->buf
, st
->buf
+ st
->loaded
- sz
, sz
);
693 /* Initialize all windows. */
694 static void init_wins(int top_lines
)
696 int top_y
= 0, bot_y
= top_lines
+ 1, sb_y
= LINES
- 2,
697 in_y
= LINES
- 1, sep_y
= top_lines
;
698 int bot_lines
= LINES
- top_lines
- 3, sb_lines
= 1, in_lines
= 1,
701 assume_default_colors(theme
.dflt
.fg
, theme
.dflt
.bg
);
703 wresize(top
.win
, top_lines
, COLS
);
704 mvwin(top
.win
, top_y
, 0);
706 wresize(sb
.win
, sb_lines
, COLS
);
707 mvwin(sb
.win
, sb_y
, 0);
709 wresize(sep
.win
, sep_lines
, COLS
);
710 mvwin(sep
.win
, sep_y
, 0);
712 wresize(bot
.win
, bot_lines
, COLS
);
713 mvwin(bot
.win
, bot_y
, 0);
715 wresize(in
.win
, in_lines
, COLS
);
716 mvwin(in
.win
, in_y
, 0);
718 sep
.win
= newwin(sep_lines
, COLS
, sep_y
, 0);
719 top
.win
= newwin(top_lines
, COLS
, top_y
, 0);
720 bot
.win
= newwin(bot_lines
, COLS
, bot_y
, 0);
721 sb
.win
= newwin(sb_lines
, COLS
, sb_y
, 0);
722 in
.win
= newwin(in_lines
, COLS
, in_y
, 0);
723 if (!top
.win
|| !bot
.win
|| !sb
.win
|| !in
.win
|| !sep
.win
)
724 die(EXIT_FAILURE
, "Error: Cannot create curses windows\n");
728 scrollok(bot
.win
, 1);
729 wattron(sb
.win
, COLOR_PAIR(COLOR_STATUSBAR
));
730 wattron(sep
.win
, COLOR_PAIR(COLOR_SEPARATOR
));
731 wattron(bot
.win
, COLOR_PAIR(COLOR_BOT
));
732 wattron(top
.win
, COLOR_PAIR(COLOR_TOP
));
743 wmove(sep
.win
, 0, 0);
744 whline(sep
.win
, theme
.sep_char
, COLS
);
748 wnoutrefresh(top
.win
);
749 wnoutrefresh(bot
.win
);
751 wnoutrefresh(sb
.win
);
752 wnoutrefresh(in
.win
);
753 wnoutrefresh(sep
.win
);
757 static void init_pair_or_die(short pair
, short f
, short b
)
759 if (init_pair(pair
, f
, b
) == ERR
)
760 die(EXIT_FAILURE
, "fatal: init_pair() failed\n");
763 static void init_colors_or_die(void)
768 die(EXIT_FAILURE
, "fatal: No color term\n");
769 if (start_color() == ERR
)
770 die(EXIT_FAILURE
, "fatal: failed to start colors\n");
771 FOR_EACH_STATUS_ITEM(i
)
772 if (theme
.data
[i
].len
)
773 init_pair_or_die(i
+ 1, theme
.data
[i
].color
.fg
,
774 theme
.data
[i
].color
.bg
);
775 init_pair_or_die(COLOR_STATUSBAR
, theme
.sb
.fg
, theme
.sb
.bg
);
776 init_pair_or_die(COLOR_COMMAND
, theme
.cmd
.fg
, theme
.cmd
.bg
);
777 init_pair_or_die(COLOR_OUTPUT
, theme
.output
.fg
, theme
.output
.bg
);
778 init_pair_or_die(COLOR_MSG
, theme
.msg
.fg
, theme
.msg
.bg
);
779 init_pair_or_die(COLOR_ERRMSG
, theme
.err_msg
.fg
, theme
.err_msg
.bg
);
780 init_pair_or_die(COLOR_SEPARATOR
, theme
.sep
.fg
, theme
.sep
.bg
);
781 init_pair_or_die(COLOR_TOP
, theme
.dflt
.fg
, theme
.dflt
.bg
);
782 init_pair_or_die(COLOR_BOT
, theme
.dflt
.fg
, theme
.dflt
.bg
);
785 /* (Re-)initialize the curses library. */
786 static void init_curses(void)
790 if (refresh() == ERR
) /* refresh is really needed */
791 die(EXIT_FAILURE
, "refresh() failed\n");
792 if (LINES
< theme
.lines_min
|| COLS
< theme
.cols_min
)
793 die(EXIT_FAILURE
, "Terminal (%dx%d) too small"
794 " (need at least %dx%d)\n", COLS
, LINES
,
795 theme
.cols_min
, theme
.lines_min
);
796 curs_set(0); /* make cursor invisible, ignore errors */
797 nonl(); /* do not NL->CR/NL on output, always returns OK */
798 /* don't echo input */
800 die(EXIT_FAILURE
, "fatal: noecho() failed\n");
801 /* take input chars one at a time, no wait for \n */
803 die(EXIT_FAILURE
, "fatal: cbreak() failed\n");
804 init_colors_or_die();
805 clear(); /* ignore non-fatal errors */
806 init_wins(theme
.top_lines_default
);
807 // noecho(); /* don't echo input */
811 * This sucker modifies its first argument. *handler and *arg are
812 * pointers to 0-terminated strings (inside line). Crap.
814 static int split_key_map(char *line
, char **handler
, char **arg
)
816 if (!(*handler
= strchr(line
+ 1, ':')))
820 if (!(*arg
= strchr(*handler
, ':')))
829 static void check_key_map_args_or_die(void)
833 const struct lls_opt_result
*lor
= OPT_RESULT(KEY_MAP
);
835 FOR_EACH_KEY_MAP(i
) {
839 tmp
= para_strdup(lls_string_val(i
, lor
));
840 if (!split_key_map(tmp
, &handler
, &arg
))
842 if (strlen(handler
) != 1)
844 if (*handler
!= 'x' && *handler
!= 'd' && *handler
!= 'i'
849 if (find_cmd_byname(arg
) < 0)
852 if (i
!= OPT_GIVEN(KEY_MAP
))
853 die(EXIT_FAILURE
, "invalid key map: %s\n",
854 lls_string_val(i
, lor
));
858 static void parse_config_file_or_die(bool reload
)
861 unsigned flags
= MCF_DONT_FREE
;
863 if (lpr
!= cmdline_lpr
)
864 lls_free_parse_result(lpr
, CMD_PTR
);
867 flags
|= MCF_OVERRIDE
;
868 ret
= lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE
),
869 "gui.conf", &lpr
, CMD_PTR
, gui_suite
, flags
);
871 PARA_EMERG_LOG("failed to parse config file: %s\n",
872 para_strerror(-ret
));
875 loglevel
= OPT_UINT32_VAL(LOGLEVEL
);
876 check_key_map_args_or_die();
877 theme_init(OPT_STRING_VAL(THEME
), &theme
);
880 /* Reread configuration, terminate on errors. */
881 static void reread_conf(void)
884 * If the reload of the config file fails, we are about to exit. In
885 * this case we print the error message to stderr rather than to the
886 * curses window. So we have to shutdown curses first.
889 parse_config_file_or_die(true);
891 print_in_bar(COLOR_MSG
, "config file reloaded\n");
894 /* React to various signal-related events. */
895 static int signal_post_select(struct sched
*s
, __a_unused
void *context
)
897 int ret
= para_next_signal(&s
->rfds
);
903 die(EXIT_FAILURE
, "only the good die young (caught SIGTERM)\n");
905 PARA_NOTICE_LOG("got SIGWINCH\n");
906 if (curses_active()) {
915 PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
919 task_notify_all(s
, E_GUI_SIGCHLD
);
925 static enum exec_status
exec_status(void)
927 if (exec_fds
[0] >= 0 || exec_fds
[1] >= 0)
934 static void exec_pre_select(struct sched
*s
, void *context
)
936 struct exec_task
*et
= context
;
937 if (exec_fds
[0] >= 0)
938 para_fd_set(exec_fds
[0], &s
->rfds
, &s
->max_fileno
);
939 if (exec_fds
[1] >= 0)
940 para_fd_set(exec_fds
[1], &s
->rfds
, &s
->max_fileno
);
941 if (task_get_notification(et
->task
) < 0)
945 static int exec_post_select(struct sched
*s
, void *context
)
947 struct exec_task
*ct
= context
;
950 ret
= task_get_notification(ct
->task
);
951 if (ret
== -E_GUI_SIGCHLD
&& exec_pid
> 0) {
953 if (waitpid(exec_pid
, &exit_status
, WNOHANG
) == exec_pid
) {
956 PARA_INFO_LOG("command exit status: %d", exit_status
);
957 print_in_bar(COLOR_MSG
, " ");
960 for (i
= 0; i
< 2; i
++) {
964 ret
= read_nonblock(exec_fds
[i
],
965 ct
->command_buf
[i
] + ct
->cbo
[i
],
966 COMMAND_BUF_SIZE
- 1 - ct
->cbo
[i
], &s
->rfds
, &sz
);
969 ct
->cbo
[i
] = for_each_line(ct
->flags
[i
], ct
->command_buf
[i
],
970 ct
->cbo
[i
], add_output_line
, &i
);
971 if (sz
!= ct
->cbo
[i
]) { /* at least one line found */
972 refresh_window(&bot
);
975 if (ret
< 0 || exec_pid
== 0) {
977 PARA_NOTICE_LOG("closing command fd %d: %s",
978 i
, para_strerror(-ret
));
983 if (exec_fds
[!i
] < 0) /* both fds closed */
986 if (ct
->cbo
[i
] == COMMAND_BUF_SIZE
- 1) {
987 PARA_NOTICE_LOG("discarding overlong line");
989 ct
->flags
[i
] = FELF_DISCARD_FIRST
;
995 static void input_pre_select(struct sched
*s
, __a_unused
void *context
)
997 if (exec_status() != EXEC_XCMD
)
998 para_fd_set(STDIN_FILENO
, &s
->rfds
, &s
->max_fileno
);
999 if (window_update_needed())
1003 /* Read from command pipe and print data to bot window. */
1004 static void exec_and_display(const char *file_and_args
)
1006 int ret
, fds
[3] = {0, 1, 1};
1008 outputf(COLOR_COMMAND
, "%s", file_and_args
);
1009 ret
= para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1012 ret
= mark_fd_nonblocking(fds
[1]);
1015 ret
= mark_fd_nonblocking(fds
[2]);
1018 exec_fds
[0] = fds
[1];
1019 exec_fds
[1] = fds
[2];
1020 print_in_bar(COLOR_MSG
, "hit any key to abort\n");
1023 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
1028 static void exec_para(const char *args
)
1030 char *file_and_args
;
1032 file_and_args
= make_message(BINDIR
"/para_client -- %s", args
);
1033 exec_and_display(file_and_args
);
1034 free(file_and_args
);
1037 /* Shutdown curses and stat pipe before executing external commands. */
1038 static void exec_external(char *file_and_args
)
1040 int fds
[3] = {-1, -1, -1};
1045 para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1048 static void handle_command(int c
)
1051 const struct lls_opt_result
*lor
= OPT_RESULT(KEY_MAP
);
1053 /* first check user-defined key bindings */
1054 FOR_EACH_KEY_MAP(i
) {
1055 char *tmp
, *handler
, *arg
;
1057 tmp
= para_strdup(lls_string_val(i
, lor
));
1058 if (!split_key_map(tmp
, &handler
, &arg
)) {
1062 if (strcmp(tmp
, km_keyname(c
))) {
1066 if (*handler
== 'd')
1067 exec_and_display(arg
);
1068 else if (*handler
== 'x')
1070 else if (*handler
== 'p')
1072 else if (*handler
== 'i') {
1073 int num
= find_cmd_byname(arg
);
1075 command_list
[num
].handler();
1080 /* not found, check internal key bindings */
1081 for (i
= 0; command_list
[i
].handler
; i
++) {
1082 if (!strcmp(km_keyname(c
), command_list
[i
].key
)) {
1083 command_list
[i
].handler();
1087 print_in_bar(COLOR_ERRMSG
, "key '%s' is not bound, press ? for help",
1091 static int input_post_select(__a_unused
struct sched
*s
,
1092 __a_unused
void *context
)
1095 enum exec_status exs
= exec_status();
1097 if (exs
== EXEC_XCMD
)
1099 if (window_update_needed()) {
1100 if (top
.needs_update
)
1101 assert(wnoutrefresh(top
.win
) == OK
);
1102 if (bot
.needs_update
)
1103 assert(wnoutrefresh(bot
.win
) == OK
);
1104 if (sep
.needs_update
)
1105 assert(wnoutrefresh(sep
.win
) == OK
);
1106 if (sb
.needs_update
)
1107 assert(wnoutrefresh(sb
.win
) == OK
);
1108 if (in
.needs_update
)
1109 assert(wnoutrefresh(in
.win
) == OK
);
1111 top
.needs_update
= bot
.needs_update
= sb
.needs_update
=
1112 in
.needs_update
= sep
.needs_update
= false;
1114 ret
= wgetch(top
.win
);
1117 if (ret
== KEY_RESIZE
) /* already handled in signal_post_select() */
1119 if (exs
== EXEC_IDLE
)
1120 handle_command(ret
);
1121 else if (exec_pid
> 0)
1122 kill(exec_pid
, SIGTERM
);
1126 static void print_scroll_msg(void)
1128 unsigned lines_total
, filled
= ringbuffer_filled(bot_win_rb
);
1129 int first_rbe
= first_visible_rbe(&lines_total
);
1131 print_in_bar(COLOR_MSG
, "scrolled view: %u-%u/%u\n", filled
- first_rbe
,
1132 filled
- scroll_position
, ringbuffer_filled(bot_win_rb
));
1135 static void com_scroll_top(void)
1137 int i
= RINGBUFFER_SIZE
- 1, bot_lines
= get_num_lines(&bot
);
1140 while (i
> 0 && !ringbuffer_get(bot_win_rb
, i
))
1142 /* i is oldest entry */
1143 for (; lines
< bot_lines
&& i
>= 0; i
--) {
1144 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1147 lines
+= NUM_LINES(rbe
->len
);
1150 if (lines
> 0 && scroll_position
!= i
) {
1151 scroll_position
= i
;
1156 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1159 static void com_cancel_scroll(void)
1162 if (scroll_position
== 0) {
1163 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1166 scroll_position
= 0;
1170 static void com_page_down(void)
1173 int i
= scroll_position
, bot_lines
= get_num_lines(&bot
);
1175 while (lines
< bot_lines
&& --i
> 0) {
1176 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1179 lines
+= NUM_LINES(rbe
->len
);
1182 scroll_position
= i
;
1187 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1190 static void com_page_up(void)
1193 int fvr
= first_visible_rbe(&lines
), bot_lines
= get_num_lines(&bot
);
1195 if (fvr
< 0 || fvr
+ 1 >= ringbuffer_filled(bot_win_rb
)) {
1196 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1199 scroll_position
= fvr
+ 1;
1200 for (; scroll_position
> 0; scroll_position
--) {
1201 first_visible_rbe(&lines
);
1202 if (lines
== bot_lines
)
1209 static void com_scroll_down(void)
1211 struct rb_entry
*rbe
;
1212 int rbe_lines
, bot_lines
= get_num_lines(&bot
);
1214 if (!scroll_position
) {
1215 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1219 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1220 rbe_lines
= NUM_LINES(rbe
->len
);
1221 wscrl(bot
.win
, rbe_lines
);
1222 wmove(bot
.win
, bot_lines
- rbe_lines
, 0);
1223 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1224 waddstr(bot
.win
, rbe
->msg
);
1225 refresh_window(&bot
);
1229 static void com_scroll_up(void)
1231 struct rb_entry
*rbe
= NULL
;
1233 int i
, first_rbe
, num_scroll
;
1235 /* the entry that is going to vanish */
1236 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1239 num_scroll
= NUM_LINES(rbe
->len
);
1240 first_rbe
= first_visible_rbe(&lines
);
1241 if (first_rbe
< 0 || (first_rbe
== ringbuffer_filled(bot_win_rb
) - 1))
1244 wscrl(bot
.win
, -num_scroll
);
1245 i
= draw_top_rbe(&lines
);
1248 while (i
> 0 && lines
< num_scroll
) {
1250 rbe
= ringbuffer_get(bot_win_rb
, --i
);
1253 rbe_lines
= NUM_LINES(rbe
->len
);
1255 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1256 waddstr(bot
.win
, "\n");
1257 waddstr(bot
.win
, rbe
->msg
);
1262 refresh_window(&bot
);
1266 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1269 static void com_ll_decr(void)
1271 if (loglevel
<= LL_DEBUG
) {
1272 print_in_bar(COLOR_ERRMSG
,
1273 "loglevel already at maximal verbosity\n");
1277 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1280 static void com_ll_incr(void)
1282 if (loglevel
>= LL_EMERG
) {
1283 print_in_bar(COLOR_ERRMSG
,
1284 "loglevel already at minimal verbosity\n");
1288 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1291 static void com_reread_conf(void)
1296 static void com_help(void)
1299 const struct lls_opt_result
*lor
= OPT_RESULT(KEY_MAP
);
1301 FOR_EACH_KEY_MAP(i
) {
1302 char *handler
, *arg
, *tmp
= para_strdup(lls_string_val(i
, lor
));
1303 const char *handler_text
= "???", *desc
= NULL
;
1305 if (!split_key_map(tmp
, &handler
, &arg
)) {
1311 handler_text
= "internal";
1312 desc
= command_list
[find_cmd_byname(arg
)].description
;
1314 case 'x': handler_text
= "external"; break;
1315 case 'd': handler_text
= "display "; break;
1316 case 'p': handler_text
= "para "; break;
1318 outputf(COLOR_MSG
, "%s\t%s\t%s%s\t%s", tmp
, handler_text
, arg
,
1319 strlen(arg
) < 8? "\t" : "",
1323 for (i
= 0; command_list
[i
].handler
; i
++) {
1324 struct gui_command gc
= command_list
[i
];
1326 outputf(COLOR_MSG
, "%s\tinternal\t%s\t%s%s", gc
.key
, gc
.name
,
1327 strlen(gc
.name
) < 8? "\t" : "",
1330 print_in_bar(COLOR_MSG
, "try \"para_gui -h\" or \"para_client help\" "
1334 static void com_shrink_top_win(void)
1336 int top_lines
= get_num_lines(&top
);
1338 if (top_lines
<= theme
.top_lines_min
) {
1339 PARA_WARNING_LOG("can not decrease top window\n");
1342 init_wins(top_lines
- 1);
1343 print_in_bar(COLOR_MSG
, "%s", "decreased top window");
1346 static void com_enlarge_top_win(void)
1348 int top_lines
= get_num_lines(&top
), bot_lines
= get_num_lines(&bot
);
1350 if (bot_lines
< 3) {
1351 PARA_WARNING_LOG("can not increase top window\n");
1354 init_wins(top_lines
+ 1);
1355 print_in_bar(COLOR_MSG
, "increased top window");
1358 static void com_version(void)
1360 print_in_bar(COLOR_MSG
, "%s", version_single_line("gui"));
1363 __noreturn
static void com_quit(void)
1365 die(EXIT_SUCCESS
, "%s", "");
1368 static void com_refresh(void)
1374 static void com_next_theme(void)
1380 static void com_prev_theme(void)
1386 static int setup_tasks_and_schedule(void)
1389 struct exec_task exec_task
= {.task
= NULL
};
1390 struct status_task status_task
= {.fd
= -1};
1391 struct input_task input_task
= {.task
= NULL
};
1392 struct signal_task
*signal_task
;
1393 struct sched sched
= {.default_timeout
= {.tv_sec
= 1}};
1395 exec_task
.task
= task_register(&(struct task_info
) {
1397 .pre_select
= exec_pre_select
,
1398 .post_select
= exec_post_select
,
1399 .context
= &exec_task
,
1402 status_task
.task
= task_register(&(struct task_info
) {
1404 .pre_select
= status_pre_select
,
1405 .post_select
= status_post_select
,
1406 .context
= &status_task
,
1409 input_task
.task
= task_register(&(struct task_info
) {
1411 .pre_select
= input_pre_select
,
1412 .post_select
= input_post_select
,
1413 .context
= &input_task
,
1416 signal_task
= signal_init_or_die();
1417 para_install_sighandler(SIGINT
);
1418 para_install_sighandler(SIGTERM
);
1419 para_install_sighandler(SIGCHLD
);
1420 para_install_sighandler(SIGUSR1
);
1421 para_install_sighandler(SIGWINCH
);
1422 signal_task
->task
= task_register(&(struct task_info
) {
1424 .pre_select
= signal_pre_select
,
1425 .post_select
= signal_post_select
,
1426 .context
= signal_task
,
1428 ret
= schedule(&sched
);
1429 sched_shutdown(&sched
);
1430 signal_shutdown(signal_task
);
1434 static void handle_help_flags(void)
1438 if (OPT_GIVEN(DETAILED_HELP
))
1439 help
= lls_long_help(CMD_PTR
);
1440 else if (OPT_GIVEN(HELP
))
1441 help
= lls_short_help(CMD_PTR
);
1444 printf("%s\n", help
);
1450 * The main function of para_gui.
1452 * \param argc Usual argument count.
1453 * \param argv Usual argument vector.
1455 * After initialization para_gui registers the following tasks to the paraslash
1456 * scheduler: status, exec, signal, input.
1458 * The status task executes the para_audioc stat command to obtain the status
1459 * of para_server and para_audiod, and displays this information in the top
1460 * window of para_gui.
1462 * The exec task is responsible for printing the output of the currently
1463 * running executable to the bottom window.
1465 * The signal task performs various actions according to signals received. For
1466 * example, it reloads the configuration file on SIGUSR1, and it shuts down the
1467 * curses system on SIGTERM to restore the terminal settings before exit.
1469 * The input task reads single key strokes from stdin. For each key pressed, it
1470 * executes the command handler associated with this key.
1472 * \return \p EXIT_SUCCESS or \p EXIT_FAILURE.
1474 int main(int argc
, char *argv
[])
1479 ret
= lls(lls_parse(argc
, argv
, CMD_PTR
, &cmdline_lpr
, &errctx
));
1483 loglevel
= OPT_UINT32_VAL(LOGLEVEL
);
1484 version_handle_flag("gui", OPT_GIVEN(VERSION
));
1485 handle_help_flags();
1486 parse_config_file_or_die(false);
1487 bot_win_rb
= ringbuffer_new(RINGBUFFER_SIZE
);
1488 setlocale(LC_CTYPE
, "");
1489 initscr(); /* needed only once, always successful */
1491 ret
= setup_tasks_and_schedule();
1493 lls_free_parse_result(lpr
, CMD_PTR
);
1494 if (lpr
!= cmdline_lpr
)
1495 lls_free_parse_result(cmdline_lpr
, CMD_PTR
);
1498 PARA_ERROR_LOG("%s\n", errctx
);
1500 PARA_EMERG_LOG("%s\n", para_strerror(-ret
));
1502 return ret
< 0? EXIT_FAILURE
: EXIT_SUCCESS
;