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(const 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
)
249 const char space
[] = " ";
250 const unsigned sz
= sizeof(space
) - 1; /* number of spaces */
258 waddstr(win
, space
+ sz
- num
);
263 * print aligned string to curses window. This function always prints
266 static int align_str(WINDOW
* win
, const char *str
, unsigned int len
,
269 int ret
, num
; /* of spaces */
271 char *sstr
; /* sanitized string */
275 ret
= sanitize_str(str
, len
, &sstr
, &width
);
277 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
279 sstr
= para_strdup(NULL
);
281 assert(width
<= len
);
285 add_spaces(win
, num
);
286 } else if (align
== RIGHT
) {
287 add_spaces(win
, num
);
290 add_spaces(win
, num
/ 2);
292 add_spaces(win
, num
- num
/ 2);
298 static void refresh_window(struct gui_window
*gw
)
300 gw
->needs_update
= true;
303 static bool window_update_needed(void)
305 return top
.needs_update
|| bot
.needs_update
|| sb
.needs_update
||
306 in
.needs_update
|| sep
.needs_update
;
309 __printf_2_3
static void print_in_bar(int color
, const char *fmt
,...)
314 if (!curses_active())
316 wattron(in
.win
, COLOR_PAIR(color
));
318 xvasprintf(&msg
, fmt
, ap
);
321 align_str(in
.win
, msg
, get_num_cols(&in
), LEFT
);
326 static void print_status_bar(void)
330 tmp
= para_strdup("para_gui " PACKAGE_VERSION
" (hit ? for help)");
332 align_str(sb
.win
, tmp
, get_num_cols(&sb
), CENTER
);
337 * get the number of the oldest rbe that is (partially) visible. On return,
338 * lines contains the sum of the number of lines of all visible entries. If the
339 * first one is only partially visible, lines is greater than bot.lines.
341 static int first_visible_rbe(unsigned *lines
)
343 int i
, bot_lines
= get_num_lines(&bot
);
346 for (i
= scroll_position
; i
< RINGBUFFER_SIZE
; i
++) {
347 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
351 rbe_lines
= NUM_LINES(rbe
->len
);
352 if (rbe_lines
> bot_lines
)
355 if (*lines
>= bot_lines
)
358 return RINGBUFFER_SIZE
- 1;
362 returns number of first visible rbe, *lines is the number of lines drawn.
364 static int draw_top_rbe(unsigned *lines
)
366 int bot_cols
, bot_lines
, ret
, fvr
= first_visible_rbe(lines
);
367 struct rb_entry
*rbe
;
368 size_t bytes_to_skip
, cells_to_skip
, width
;
372 wmove(bot
.win
, 0, 0);
373 rbe
= ringbuffer_get(bot_win_rb
, fvr
);
376 getmaxyx(bot
.win
, bot_lines
, bot_cols
);
377 if (*lines
> bot_lines
) {
378 /* rbe is partially visible multi-line */
379 cells_to_skip
= (*lines
- bot_lines
) * bot_cols
;
380 ret
= skip_cells(rbe
->msg
, cells_to_skip
, &bytes_to_skip
);
383 ret
= strwidth(rbe
->msg
+ bytes_to_skip
, &width
);
390 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
391 waddstr(bot
.win
, rbe
->msg
+ bytes_to_skip
);
392 *lines
= NUM_LINES(width
);
396 static void redraw_bot_win(void)
399 int i
, bot_lines
= get_num_lines(&bot
);
401 wmove(bot
.win
, 0, 0);
403 i
= draw_top_rbe(&lines
);
406 while (i
> 0 && lines
< bot_lines
) {
407 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, --i
);
410 waddstr(bot
.win
, "\n");
413 lines
+= NUM_LINES(rbe
->len
);
414 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
415 waddstr(bot
.win
, "\n");
416 waddstr(bot
.win
, rbe
->msg
);
419 refresh_window(&bot
);
422 static void rb_add_entry(int color
, char *msg
)
424 struct rb_entry
*old
, *new;
428 if (strwidth(msg
, &len
) < 0)
430 new = para_malloc(sizeof(struct rb_entry
));
434 old
= ringbuffer_add(bot_win_rb
, new);
439 if (scroll_position
) {
440 /* discard current scrolling, like xterm does */
445 wattron(bot
.win
, COLOR_PAIR(color
));
446 getyx(bot
.win
, y
, x
);
448 waddstr(bot
.win
, "\n");
449 waddstr(bot
.win
, msg
);
453 * print formated output to bot win and refresh
455 __printf_2_3
static void outputf(int color
, const char* fmt
,...)
460 if (!curses_active())
463 xvasprintf(&msg
, fmt
, ap
);
465 rb_add_entry(color
, msg
);
466 refresh_window(&bot
);
469 static int add_output_line(char *line
, void *data
)
471 int color
= *(int *)data
? COLOR_ERRMSG
: COLOR_OUTPUT
;
473 if (!curses_active())
475 rb_add_entry(color
, para_strdup(line
));
479 static __printf_2_3
void curses_log(int ll
, const char *fmt
,...)
486 if (curses_active()) {
487 int color
= ll
<= LL_NOTICE
? COLOR_MSG
: COLOR_ERRMSG
;
489 unsigned bytes
= xvasprintf(&msg
, fmt
, ap
);
490 if (bytes
> 0 && msg
[bytes
- 1] == '\n')
491 msg
[bytes
- 1] = '\0'; /* cut trailing newline */
492 rb_add_entry(color
, msg
);
493 refresh_window(&bot
);
494 } else if (exec_pid
<= 0) /* no external command running */
495 vfprintf(stderr
, fmt
, ap
);
498 /** The log function of para_gui, always set to curses_log(). */
499 __printf_2_3
void (*para_log
)(int, const char*, ...) = curses_log
;
501 /* Call endwin() to reset the terminal into non-visual mode. */
502 static void shutdown_curses(void)
505 * If para_gui received a terminating signal in external mode, the
506 * terminal can be in an unusable state at this point because the child
507 * process might not have caught the signal. In this case endwin() has
508 * already been called and must not be called again. So we first return
509 * to program mode, then call endwin().
511 if (!curses_active())
516 /* disable curses, print a message, kill running processes and exit */
517 __noreturn __printf_2_3
static void die(int exit_code
, const char* fmt
, ...)
521 /* Kill every process in our process group. */
522 para_sigaction(SIGTERM
, SIG_IGN
);
524 /* Wait up to two seconds for child processes to die. */
526 while (waitpid(0, NULL
, 0) >= 0)
529 /* mousemask() exists only in ncurses */
530 #ifdef NCURSES_MOUSE_VERSION
531 mousemask(~(mmask_t
)0, NULL
); /* Avoid bad terminal state with xterm. */
535 vfprintf(stderr
, fmt
, argp
);
541 * Print stat item #i to curses window
543 static void print_stat_item(int i
)
546 struct stat_item_data d
= theme
.data
[i
];
547 char *c
= stat_content
[i
];
548 int top_lines
= get_num_lines(&top
);
550 if (!curses_active() || !d
.len
|| !c
)
552 tmp
= make_message("%s%s%s", d
.prefix
, c
, d
.postfix
);
553 wmove(top
.win
, d
.y
* top_lines
/ 100, d
.x
* COLS
/ 100);
554 wattron(top
.win
, COLOR_PAIR(i
+ 1));
555 align_str(top
.win
, tmp
, d
.len
* COLS
/ 100, d
.align
);
557 refresh_window(&top
);
560 static int update_item(int item_num
, char *buf
)
562 char **c
= stat_content
+ item_num
;
569 *c
= para_strdup("(artist tag not set)");
572 *c
= para_strdup("(title tag not set)");
575 *c
= para_strdup("????");
578 *c
= para_strdup("(album tag not set)");
581 *c
= para_strdup("(comment tag not set)");
585 *c
= para_strdup(buf
);
587 print_stat_item(item_num
);
591 static void print_all_items(void)
595 if (!curses_active())
597 FOR_EACH_STATUS_ITEM(i
)
601 static void clear_all_items(void)
605 FOR_EACH_STATUS_ITEM(i
) {
606 free(stat_content
[i
]);
607 stat_content
[i
] = para_strdup("");
611 static void status_pre_select(struct sched
*s
, void *context
)
613 struct status_task
*st
= context
;
616 para_fd_set(st
->fd
, &s
->rfds
, &s
->max_fileno
);
617 if (task_get_notification(st
->task
) < 0)
618 return sched_min_delay(s
);
620 sched_request_barrier_or_min_delay(&st
->next_exec
, s
);
623 static int status_post_select(struct sched
*s
, void *context
)
625 struct status_task
*st
= context
;
629 ret
= task_get_notification(st
->task
);
630 if (ret
== -E_GUI_SIGCHLD
&& st
->pid
> 0) {
632 if (waitpid(st
->pid
, &exit_status
, WNOHANG
) == st
->pid
) {
634 PARA_ERROR_LOG("stat command exit status: %d",
639 int fds
[3] = {0, 1, 0};
642 /* Avoid busy loop */
643 if (tv_diff(&st
->next_exec
, now
, NULL
) > 0)
645 st
->next_exec
.tv_sec
= now
->tv_sec
+ 2;
646 ret
= para_exec_cmdline_pid(&st
->pid
, conf
.stat_cmd_arg
, fds
);
649 ret
= mark_fd_nonblocking(fds
[1]);
658 if (st
->loaded
>= st
->bufsize
) {
659 if (st
->bufsize
> 1000 * 1000) {
663 st
->bufsize
+= st
->bufsize
+ 1000;
664 st
->buf
= para_realloc(st
->buf
, st
->bufsize
);
666 assert(st
->loaded
< st
->bufsize
);
667 ret
= read_nonblock(st
->fd
, st
->buf
+ st
->loaded
,
668 st
->bufsize
- st
->loaded
, &s
->rfds
, &sz
);
670 ret2
= for_each_stat_item(st
->buf
, st
->loaded
, update_item
);
671 if (ret
< 0 || ret2
< 0) {
673 PARA_NOTICE_LOG("closing stat pipe: %s\n",
674 para_strerror(ret
< 0? -ret
: -ret2
));
678 free(stat_content
[SI_BASENAME
]);
679 stat_content
[SI_BASENAME
] =
680 para_strdup("stat command terminated!?");
684 sz
= ret2
; /* what is left */
685 if (sz
> 0 && sz
< st
->loaded
)
686 memmove(st
->buf
, st
->buf
+ st
->loaded
- sz
, sz
);
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)
834 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
838 tmp
= para_strdup(conf
.key_map_arg
[i
]);
839 if (!split_key_map(tmp
, &handler
, &arg
))
841 if (strlen(handler
) != 1)
843 if (*handler
!= 'x' && *handler
!= 'd' && *handler
!= 'i'
848 if (find_cmd_byname(arg
) < 0)
851 if (i
!= conf
.key_map_given
)
852 die(EXIT_FAILURE
, "invalid key map: %s\n", conf
.key_map_arg
[i
]);
856 static void parse_config_file_or_die(bool override
)
860 struct gui_cmdline_parser_params params
= {
861 .override
= override
,
863 .check_required
= !override
,
864 .check_ambiguity
= 0,
868 if (conf
.config_file_given
)
869 config_file
= para_strdup(conf
.config_file_arg
);
871 char *home
= para_homedir();
872 config_file
= make_message("%s/.paraslash/gui.conf", home
);
875 if (!file_exists(config_file
)) {
876 if (!conf
.config_file_given
)
879 PARA_EMERG_LOG("config file %s does not exist\n",
886 * When the gengetopt config file parser is called more than once, any
887 * key map arguments found in the config file are _appended_ to the old
888 * values, even though we turn on ->override. We want the new arguments
889 * to replace the old ones, so we must empty the key_map_arg array
890 * first. Unfortunately, this also clears any key map arguments given
891 * at the command line.
895 for (i
= 0; i
< conf
.key_map_given
; i
++) {
896 free(conf
.key_map_arg
[i
]);
897 conf
.key_map_arg
[i
] = NULL
;
899 conf
.key_map_given
= 0;
902 gui_cmdline_parser_config_file(config_file
, &conf
, ¶ms
);
903 loglevel
= get_loglevel_by_name(conf
.loglevel_arg
);
904 check_key_map_args_or_die();
910 theme_init(conf
.theme_arg
, &theme
);
913 /* reread configuration, terminate on errors */
914 static void reread_conf(void)
917 * gengetopt might print to stderr and exit on errors. So we have to
918 * shutdown curses first.
921 parse_config_file_or_die(true /* override */);
923 print_in_bar(COLOR_MSG
, "config file reloaded\n");
927 * React to various signal-related events
929 static int signal_post_select(struct sched
*s
, __a_unused
void *context
)
931 int ret
= para_next_signal(&s
->rfds
);
937 die(EXIT_FAILURE
, "only the good die young (caught SIGTERM)\n");
941 PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
945 task_notify_all(s
, E_GUI_SIGCHLD
);
951 static enum exec_status
exec_status(void)
953 if (exec_fds
[0] >= 0 || exec_fds
[1] >= 0)
960 static void exec_pre_select(struct sched
*s
, void *context
)
962 struct exec_task
*et
= context
;
963 if (exec_fds
[0] >= 0)
964 para_fd_set(exec_fds
[0], &s
->rfds
, &s
->max_fileno
);
965 if (exec_fds
[1] >= 0)
966 para_fd_set(exec_fds
[1], &s
->rfds
, &s
->max_fileno
);
967 if (task_get_notification(et
->task
) < 0)
971 static int exec_post_select(struct sched
*s
, void *context
)
973 struct exec_task
*ct
= context
;
976 ret
= task_get_notification(ct
->task
);
977 if (ret
== -E_GUI_SIGCHLD
&& exec_pid
> 0) {
979 if (waitpid(exec_pid
, &exit_status
, WNOHANG
) == exec_pid
) {
982 PARA_INFO_LOG("command exit status: %d", exit_status
);
983 print_in_bar(COLOR_MSG
, " ");
986 for (i
= 0; i
< 2; i
++) {
990 ret
= read_nonblock(exec_fds
[i
],
991 ct
->command_buf
[i
] + ct
->cbo
[i
],
992 COMMAND_BUF_SIZE
- 1 - ct
->cbo
[i
], &s
->rfds
, &sz
);
995 ct
->cbo
[i
] = for_each_line(ct
->flags
[i
], ct
->command_buf
[i
],
996 ct
->cbo
[i
], add_output_line
, &i
);
997 if (sz
!= ct
->cbo
[i
]) { /* at least one line found */
998 refresh_window(&bot
);
1001 if (ret
< 0 || exec_pid
== 0) {
1003 PARA_NOTICE_LOG("closing command fd %d: %s",
1004 i
, para_strerror(-ret
));
1009 if (exec_fds
[!i
] < 0) /* both fds closed */
1012 if (ct
->cbo
[i
] == COMMAND_BUF_SIZE
- 1) {
1013 PARA_NOTICE_LOG("discarding overlong line");
1015 ct
->flags
[i
] = FELF_DISCARD_FIRST
;
1021 static void input_pre_select(struct sched
*s
, __a_unused
void *context
)
1023 if (exec_status() != EXEC_XCMD
)
1024 para_fd_set(STDIN_FILENO
, &s
->rfds
, &s
->max_fileno
);
1025 if (window_update_needed())
1029 /* read from command pipe and print data to bot window */
1030 static void exec_and_display(const char *file_and_args
)
1032 int ret
, fds
[3] = {0, 1, 1};
1034 outputf(COLOR_COMMAND
, "%s", file_and_args
);
1035 ret
= para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1038 ret
= mark_fd_nonblocking(fds
[1]);
1041 ret
= mark_fd_nonblocking(fds
[2]);
1044 exec_fds
[0] = fds
[1];
1045 exec_fds
[1] = fds
[2];
1046 print_in_bar(COLOR_MSG
, "hit any key to abort\n");
1049 PARA_ERROR_LOG("%s\n", para_strerror(-ret
));
1054 static void exec_para(const char *args
)
1056 char *file_and_args
;
1058 file_and_args
= make_message(BINDIR
"/para_client -- %s", args
);
1059 exec_and_display(file_and_args
);
1060 free(file_and_args
);
1064 * shutdown curses and stat pipe before executing external commands
1066 static void exec_external(char *file_and_args
)
1068 int fds
[3] = {-1, -1, -1};
1073 para_exec_cmdline_pid(&exec_pid
, file_and_args
, fds
);
1076 static void handle_command(int c
)
1080 /* first check user-defined key bindings */
1081 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
1082 char *tmp
, *handler
, *arg
;
1084 tmp
= para_strdup(conf
.key_map_arg
[i
]);
1085 if (!split_key_map(tmp
, &handler
, &arg
)) {
1089 if (strcmp(tmp
, km_keyname(c
))) {
1093 if (*handler
== 'd')
1094 exec_and_display(arg
);
1095 else if (*handler
== 'x')
1097 else if (*handler
== 'p')
1099 else if (*handler
== 'i') {
1100 int num
= find_cmd_byname(arg
);
1102 command_list
[num
].handler();
1107 /* not found, check internal key bindings */
1108 for (i
= 0; command_list
[i
].handler
; i
++) {
1109 if (!strcmp(km_keyname(c
), command_list
[i
].key
)) {
1110 command_list
[i
].handler();
1114 print_in_bar(COLOR_ERRMSG
, "key '%s' is not bound, press ? for help",
1118 static int input_post_select(__a_unused
struct sched
*s
, __a_unused
void *context
)
1121 enum exec_status exs
= exec_status();
1123 if (exs
== EXEC_XCMD
)
1125 if (window_update_needed()) {
1126 if (top
.needs_update
)
1127 assert(wnoutrefresh(top
.win
) == OK
);
1128 if (bot
.needs_update
)
1129 assert(wnoutrefresh(bot
.win
) == OK
);
1130 if (sep
.needs_update
)
1131 assert(wnoutrefresh(sep
.win
) == OK
);
1132 if (sb
.needs_update
)
1133 assert(wnoutrefresh(sb
.win
) == OK
);
1134 if (in
.needs_update
)
1135 assert(wnoutrefresh(in
.win
) == OK
);
1137 top
.needs_update
= bot
.needs_update
= sb
.needs_update
=
1138 in
.needs_update
= sep
.needs_update
= false;
1140 ret
= wgetch(top
.win
);
1143 if (ret
== KEY_RESIZE
) {
1144 if (curses_active()) {
1151 if (exs
== EXEC_IDLE
)
1152 handle_command(ret
);
1153 else if (exec_pid
> 0)
1154 kill(exec_pid
, SIGTERM
);
1158 static void print_scroll_msg(void)
1160 unsigned lines_total
, filled
= ringbuffer_filled(bot_win_rb
);
1161 int first_rbe
= first_visible_rbe(&lines_total
);
1163 print_in_bar(COLOR_MSG
, "scrolled view: %d-%d/%d\n", filled
- first_rbe
,
1164 filled
- scroll_position
, ringbuffer_filled(bot_win_rb
));
1167 static void com_scroll_top(void)
1169 int i
= RINGBUFFER_SIZE
- 1, bot_lines
= get_num_lines(&bot
);
1172 while (i
> 0 && !ringbuffer_get(bot_win_rb
, i
))
1174 /* i is oldest entry */
1175 for (; lines
< bot_lines
&& i
>= 0; i
--) {
1176 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1179 lines
+= NUM_LINES(rbe
->len
);
1182 if (lines
> 0 && scroll_position
!= i
) {
1183 scroll_position
= i
;
1188 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1191 static void com_cancel_scroll(void)
1194 if (scroll_position
== 0) {
1195 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1198 scroll_position
= 0;
1202 static void com_page_down(void)
1205 int i
= scroll_position
, bot_lines
= get_num_lines(&bot
);
1207 while (lines
< bot_lines
&& --i
> 0) {
1208 struct rb_entry
*rbe
= ringbuffer_get(bot_win_rb
, i
);
1211 lines
+= NUM_LINES(rbe
->len
);
1214 scroll_position
= i
;
1219 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1222 static void com_page_up(void)
1225 int fvr
= first_visible_rbe(&lines
), bot_lines
= get_num_lines(&bot
);
1227 if (fvr
< 0 || fvr
+ 1 >= ringbuffer_filled(bot_win_rb
)) {
1228 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1231 scroll_position
= fvr
+ 1;
1232 for (; scroll_position
> 0; scroll_position
--) {
1233 first_visible_rbe(&lines
);
1234 if (lines
== bot_lines
)
1241 static void com_scroll_down(void)
1243 struct rb_entry
*rbe
;
1244 int rbe_lines
, bot_lines
= get_num_lines(&bot
);
1246 if (!scroll_position
) {
1247 print_in_bar(COLOR_ERRMSG
, "bottom of buffer is shown\n");
1251 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1252 rbe_lines
= NUM_LINES(rbe
->len
);
1253 wscrl(bot
.win
, rbe_lines
);
1254 wmove(bot
.win
, bot_lines
- rbe_lines
, 0);
1255 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1256 waddstr(bot
.win
, rbe
->msg
);
1257 refresh_window(&bot
);
1261 static void com_scroll_up(void)
1263 struct rb_entry
*rbe
= NULL
;
1265 int i
, first_rbe
, num_scroll
;
1267 /* the entry that is going to vanish */
1268 rbe
= ringbuffer_get(bot_win_rb
, scroll_position
);
1271 num_scroll
= NUM_LINES(rbe
->len
);
1272 first_rbe
= first_visible_rbe(&lines
);
1273 if (first_rbe
< 0 || (first_rbe
== ringbuffer_filled(bot_win_rb
) - 1))
1276 wscrl(bot
.win
, -num_scroll
);
1277 i
= draw_top_rbe(&lines
);
1280 while (i
> 0 && lines
< num_scroll
) {
1282 rbe
= ringbuffer_get(bot_win_rb
, --i
);
1285 rbe_lines
= NUM_LINES(rbe
->len
);
1287 wattron(bot
.win
, COLOR_PAIR(rbe
->color
));
1288 waddstr(bot
.win
, "\n");
1289 waddstr(bot
.win
, rbe
->msg
);
1294 refresh_window(&bot
);
1298 print_in_bar(COLOR_ERRMSG
, "top of buffer is shown\n");
1301 static void com_ll_decr(void)
1303 if (loglevel
<= LL_DEBUG
) {
1304 print_in_bar(COLOR_ERRMSG
,
1305 "loglevel already at maximal verbosity\n");
1309 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1312 static void com_ll_incr(void)
1314 if (loglevel
>= LL_EMERG
) {
1315 print_in_bar(COLOR_ERRMSG
,
1316 "loglevel already at minimal verbosity\n");
1320 print_in_bar(COLOR_MSG
, "loglevel set to %d\n", loglevel
);
1323 static void com_reread_conf(void)
1328 static void com_help(void)
1332 for (i
= 0; i
< conf
.key_map_given
; ++i
) {
1333 char *handler
, *arg
, *tmp
= para_strdup(conf
.key_map_arg
[i
]);
1334 const char *handler_text
= "???", *desc
= NULL
;
1336 if (!split_key_map(tmp
, &handler
, &arg
)) {
1342 handler_text
= "internal";
1343 desc
= command_list
[find_cmd_byname(arg
)].description
;
1345 case 'x': handler_text
= "external"; break;
1346 case 'd': handler_text
= "display "; break;
1347 case 'p': handler_text
= "para "; break;
1349 outputf(COLOR_MSG
, "%s\t%s\t%s%s\t%s", tmp
, handler_text
, arg
,
1350 strlen(arg
) < 8? "\t" : "",
1354 for (i
= 0; command_list
[i
].handler
; i
++) {
1355 struct gui_command gc
= command_list
[i
];
1357 outputf(COLOR_MSG
, "%s\tinternal\t%s\t%s%s", gc
.key
, gc
.name
,
1358 strlen(gc
.name
) < 8? "\t" : "",
1361 print_in_bar(COLOR_MSG
, "try \"para_gui -h\" or \"para_client help\" "
1365 static void com_shrink_top_win(void)
1367 int top_lines
= get_num_lines(&top
);
1369 if (top_lines
<= theme
.top_lines_min
) {
1370 PARA_WARNING_LOG("can not decrease top window\n");
1373 init_wins(top_lines
- 1);
1374 print_in_bar(COLOR_MSG
, "%s", "decreased top window");
1377 static void com_enlarge_top_win(void)
1379 int top_lines
= get_num_lines(&top
), bot_lines
= get_num_lines(&bot
);
1381 if (bot_lines
< 3) {
1382 PARA_WARNING_LOG("can not increase top window\n");
1385 init_wins(top_lines
+ 1);
1386 print_in_bar(COLOR_MSG
, "increased top window");
1389 static void com_version(void)
1391 print_in_bar(COLOR_MSG
, "%s", version_single_line("gui"));
1394 __noreturn
static void com_quit(void)
1396 die(EXIT_SUCCESS
, "%s", "");
1399 static void com_refresh(void)
1405 static void com_next_theme(void)
1411 static void com_prev_theme(void)
1417 __noreturn
static void print_help_and_die(void)
1419 struct ggo_help h
= DEFINE_GGO_HELP(gui
);
1420 bool d
= conf
.detailed_help_given
;
1422 ggo_print_help(&h
, d
? GPH_STANDARD_FLAGS_DETAILED
: GPH_STANDARD_FLAGS
);
1426 static int setup_tasks_and_schedule(void)
1429 struct exec_task exec_task
= {.task
= NULL
};
1430 struct status_task status_task
= {.fd
= -1};
1431 struct input_task input_task
= {.task
= NULL
};
1432 struct signal_task
*signal_task
;
1433 struct sched sched
= {
1434 .default_timeout
= {
1435 .tv_sec
= conf
.timeout_arg
/ 1000,
1436 .tv_usec
= (conf
.timeout_arg
% 1000) * 1000,
1440 exec_task
.task
= task_register(&(struct task_info
) {
1442 .pre_select
= exec_pre_select
,
1443 .post_select
= exec_post_select
,
1444 .context
= &exec_task
,
1447 status_task
.task
= task_register(&(struct task_info
) {
1449 .pre_select
= status_pre_select
,
1450 .post_select
= status_post_select
,
1451 .context
= &status_task
,
1454 input_task
.task
= task_register(&(struct task_info
) {
1456 .pre_select
= input_pre_select
,
1457 .post_select
= input_post_select
,
1458 .context
= &input_task
,
1461 signal_task
= signal_init_or_die();
1462 para_install_sighandler(SIGINT
);
1463 para_install_sighandler(SIGTERM
);
1464 para_install_sighandler(SIGCHLD
);
1465 para_install_sighandler(SIGUSR1
);
1466 signal_task
->task
= task_register(&(struct task_info
) {
1468 .pre_select
= signal_pre_select
,
1469 .post_select
= signal_post_select
,
1470 .context
= signal_task
,
1472 ret
= schedule(&sched
);
1473 sched_shutdown(&sched
);
1474 signal_shutdown(signal_task
);
1479 * The main function of para_gui.
1481 * \param argc Usual argument count.
1482 * \param argv Usual argument vector.
1484 * After initialization para_gui registers the following tasks to the paraslash
1485 * scheduler: status, exec, signal, input.
1487 * The status task executes the para_audioc stat command to obtain the status
1488 * of para_server and para_audiod, and displays this information in the top
1489 * window of para_gui.
1491 * The exec task is responsible for printing the output of the currently
1492 * running executable to the bottom window.
1494 * The signal task performs various actions according to signals received. For
1495 * example, it reloads the configuration file on SIGUSR1, and it shuts down the
1496 * curses system on SIGTERM to restore the terminal settings before exit.
1498 * The input task reads single key strokes from stdin. For each key pressed, it
1499 * executes the command handler associated with this key.
1501 * \return \p EXIT_SUCCESS or \p EXIT_FAILURE.
1503 int main(int argc
, char *argv
[])
1505 gui_cmdline_parser(argc
, argv
, &conf
); /* exits on errors */
1506 loglevel
= get_loglevel_by_name(conf
.loglevel_arg
);
1507 version_handle_flag("gui", conf
.version_given
);
1508 if (conf
.help_given
|| conf
.detailed_help_given
)
1509 print_help_and_die();
1510 parse_config_file_or_die(false /* override */);
1511 bot_win_rb
= ringbuffer_new(RINGBUFFER_SIZE
);
1512 setlocale(LC_CTYPE
, "");
1513 initscr(); /* needed only once, always successful */
1515 return setup_tasks_and_schedule() < 0? EXIT_FAILURE
: EXIT_SUCCESS
;