/* * Copyright (C) 1998-2009 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file gui.c Curses-based interface for paraslash. */ #include #include #include #include "gui.cmdline.h" #include "para.h" #include "gui.h" #include "string.h" #include #include "ringbuffer.h" #include "fd.h" #include "error.h" #include "list.h" #include "sched.h" #include "signal.h" /** define the array of error lists needed by para_gui */ INIT_GUI_ERRLISTS; static char *stat_content[NUM_STAT_ITEMS]; #define STANDARD_STATUS_BAR "para_gui " PACKAGE_VERSION " (hit ? for help)" static int signal_pipe; static struct win_data { WINDOW *win; NCURSES_SIZE_T begx; NCURSES_SIZE_T begy; NCURSES_SIZE_T cols; NCURSES_SIZE_T lines; } top, bot, sb, in, sep; #define RINGBUFFER_SIZE 512 struct rb_entry { char *msg; size_t len; int color; }; struct ringbuffer *bot_win_rb; #define NUM_LINES(len) (1 + (len) / bot.cols) static unsigned scroll_position; static int cmd_died, curses_active; static pid_t cmd_pid; static int command_pipe = -1; static int audiod_pipe = -1; static struct gui_args_info conf; enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE}; #define COLOR_STATUSBAR 52 #define COLOR_COMMAND 53 #define COLOR_OUTPUT 54 #define COLOR_MSG 55 #define COLOR_ERRMSG 56 #define COLOR_WELCOME 57 #define COLOR_SEPARATOR 58 #define COLOR_TOP 59 #define COLOR_BOT 60 struct gui_command { const char *key; const char *name; const char *description; void (*handler)(void); }; struct stat_item { char name[MAXLINE]; char prefix[MAXLINE]; char postfix[MAXLINE]; unsigned y; unsigned x; unsigned len; int fg, bg; int align; char content[MAXLINE]; }; static struct gui_theme theme; int _argc; char **_argv; static void com_help(void); static void com_reread_conf(void); static void com_enlarge_top_win(void); static void com_shrink_top_win(void); static void com_version(void); static void com_quit(void); static void com_refresh(void); static void com_ll_incr(void); static void com_ll_decr(void); static void com_prev_theme(void); static void com_next_theme(void); static void com_scroll_up(void); static void com_scroll_down(void); static void com_page_up(void); static void com_page_down(void); struct gui_command command_list[] = { { .key = "?", .name = "help", .description = "print help", .handler = com_help }, { .key = "+", .name = "enlarge_win", .description = "enlarge the top window", .handler = com_enlarge_top_win }, { .key = "-", .name = "shrink_win", .description = "shrink the top window", .handler = com_shrink_top_win }, { .key = "r", .name = "reread_conf", .description = "reread configuration file", .handler = com_reread_conf }, { .key = "q", .name = "quit", .description = "exit para_gui", .handler = com_quit }, { .key = "^L", .name = "refresh", .description = "redraw the screen", .handler = com_refresh }, { .key = ".", .name = "next_theme", .description = "switch to next theme", .handler = com_next_theme }, { .key = ",", .name = "prev_theme", .description = "switch to previous stream", .handler = com_prev_theme }, { .key = ">", .name = "ll_incr", .description = "increase loglevel (decreases verbosity)", .handler = com_ll_incr }, { .key = "<", .name = "ll_decr", .description = "decrease loglevel (increases verbosity)", .handler = com_ll_decr }, { .key = "V", .name = "version", .description = "show the para_gui version", .handler = com_version }, { .key = "", .name = "scroll_up", .description = "scroll up one line", .handler = com_scroll_up }, { .key = "", .name = "scroll_down", .description = "scroll down one line", .handler = com_scroll_down }, { .key = "", .name = "page_up", .description = "scroll up one page", .handler = com_page_up }, { .key = "", .name = "page_down", .description = "scroll down one page", .handler = com_page_down }, { .handler = NULL } }; static int para_open_audiod_pipe(char *cmd) { int fds[3] = {0, 1, 0}; pid_t pid; int ret = para_exec_cmdline_pid(&pid, cmd, fds); if (ret < 0) return ret; ret = mark_fd_nonblocking(fds[1]); if (ret > 0) return fds[1]; close(fds[1]); return ret; } static int read_audiod_pipe(int fd, line_handler_t *line_handler) { static char buf[4096]; const ssize_t bufsize = sizeof(buf) - 1; static ssize_t loaded; ssize_t ret; if (loaded >= bufsize) loaded = 0; ret = read(fd, buf + loaded, bufsize - loaded); if (ret > 0) { loaded += ret; buf[loaded] = '\0'; loaded = for_each_line(buf, loaded, line_handler, NULL); } return ret; } static int find_cmd_byname(char *name) { int i; for (i = 0; command_list[i].handler; i++) if (!strcmp(command_list[i].name, name)) return i; return -1; } /* taken from mutt */ static char *km_keyname(int c) { static char buf[10]; if (c == KEY_UP) { sprintf(buf, ""); return buf; } if (c == KEY_DOWN) { sprintf(buf, ""); return buf; } if (c == KEY_LEFT) { sprintf(buf, ""); return buf; } if (c == KEY_RIGHT) { sprintf(buf, ""); return buf; } if (c == KEY_NPAGE) { sprintf(buf, ""); return buf; } if (c == KEY_PPAGE) { sprintf(buf, ""); return buf; } if (c < 256 && c > -128 && iscntrl((unsigned char) c)) { if (c < 0) c += 256; if (c < 128) { buf[0] = '^'; buf[1] = (c + '@') & 0x7f; buf[2] = 0; } else snprintf(buf, sizeof(buf), "\\%d%d%d", c >> 6, (c >> 3) & 7, c & 7); } else if (c >= KEY_F0 && c < KEY_F(256)) sprintf(buf, "", c - KEY_F0); else if (isprint(c)) snprintf(buf, sizeof(buf), "%c", (unsigned char) c); else snprintf(buf, sizeof(buf), "\\x%hx", (unsigned short) c); return buf; } static char *configfile_exists(void) { static char *config_file; char *tmp; if (!conf.config_file_given) { if (!config_file) { char *home = para_homedir(); config_file = make_message("%s/.paraslash/gui.conf", home); free(home); } tmp = config_file; } else tmp = conf.config_file_arg; return file_exists(tmp)? tmp: NULL; } /* * print num spaces to curses window */ static void add_spaces(WINDOW* win, unsigned int num) { while (num > 0) { num--; waddstr(win, " "); } } /* * print aligned string to curses window. This function always prints * exactly len chars. */ static int align_str(WINDOW* win, const char *string, unsigned int len, unsigned int align) { int num; /* of spaces */ char *str; if (!win || !string) return -1; num = len - strlen(string); str = para_strdup(string); if (num < 0) { str[len] = '\0'; num = 0; } if (align == LEFT) { waddstr(win, str); add_spaces(win, num); } else if (align == RIGHT) { add_spaces(win, num); waddstr(win, str); } else { add_spaces(win, num / 2); waddstr(win, str[0]? str: ""); add_spaces(win, num - num / 2); } free(str); return 1; } __printf_2_3 static void print_in_bar(int color, const char *fmt,...) { char *msg; if (!curses_active) return; wattron(in.win, COLOR_PAIR(color)); PARA_VSPRINTF(fmt, msg); wmove(in.win, 0, 0); align_str(in.win, msg, sb.cols, LEFT); free(msg); wrefresh(in.win); } /* * update the status bar */ static void print_status_bar(void) { if (!curses_active) return; wmove(sb.win, 0, 0); align_str(sb.win, STANDARD_STATUS_BAR, sb.cols, CENTER); wrefresh(sb.win); } /* * get the number of the oldest rbe that is (partially) visible. On return, * lines contains the sum of the number of lines of all visable entries. If the * first one is only partially visible, lines is greater than bot.lines. */ static int first_visible_rbe(unsigned *lines) { int i; *lines = 0; for (i = scroll_position; i < RINGBUFFER_SIZE; i++) { struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i); int rbe_lines; if (!rbe) return i - 1; // fprintf(stderr, "found: %s\n", rbe->msg); rbe_lines = NUM_LINES(rbe->len); if (rbe_lines > bot.lines) return -1; *lines += rbe_lines; if (*lines >= bot.lines) return i; } return RINGBUFFER_SIZE - 1; } static int draw_top_rbe(unsigned *lines) { unsigned len; int offset, fvr = first_visible_rbe(lines); struct rb_entry *rbe; if (fvr < 0) return -1; wmove(bot.win, 0, 0); rbe = ringbuffer_get(bot_win_rb, fvr); if (!rbe) return -1; /* first rbe might be only partially visible */ offset = (*lines - bot.lines) * bot.cols; len = strlen(rbe->msg); if (offset < 0 || len < offset) return -1; wattron(bot.win, COLOR_PAIR(rbe->color)); waddstr(bot.win, rbe->msg + offset); *lines = NUM_LINES(len - offset); return fvr; } static void redraw_bot_win(void) { unsigned lines; int i; wmove(bot.win, 0, 0); wclear(bot.win); i = draw_top_rbe(&lines); if (i <= 0) goto out; while (i > 0 && lines < bot.lines) { struct rb_entry *rbe = ringbuffer_get(bot_win_rb, --i); if (!rbe) { lines++; waddstr(bot.win, "\n"); continue; } lines += NUM_LINES(rbe->len); wattron(bot.win, COLOR_PAIR(rbe->color)); waddstr(bot.win, "\n"); waddstr(bot.win, rbe->msg); } out: wrefresh(bot.win); } static void rb_add_entry(int color, char *msg) { struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry)); int x, y; new->color = color; new->len = strlen(msg); new->msg = msg; old = ringbuffer_add(bot_win_rb, new); // fprintf(stderr, "added: %s\n", new->msg); if (old) { free(old->msg); free(old); } if (scroll_position) { /* discard current scrolling, like xterm does */ scroll_position = 0; redraw_bot_win(); return; } wattron(bot.win, COLOR_PAIR(color)); getyx(bot.win, y, x); if (y || x) waddstr(bot.win, "\n"); waddstr(bot.win, msg); } /* * print formated output to bot win and refresh */ __printf_2_3 static void outputf(int color, const char* fmt,...) { char *msg; if (!curses_active) return; PARA_VSPRINTF(fmt, msg); rb_add_entry(color, msg); wrefresh(bot.win); } static int add_output_line(char *line, __a_unused void *data) { if (!curses_active) return 1; rb_add_entry(COLOR_OUTPUT, para_strdup(line)); return 1; } static int loglevel; __printf_2_3 void para_log(int ll, const char *fmt,...) { int color; char *msg; if (ll < loglevel || !curses_active) return; switch (ll) { case LL_DEBUG: case LL_INFO: case LL_NOTICE: color = COLOR_MSG; break; default: color = COLOR_ERRMSG; } PARA_VSPRINTF(fmt, msg); chop(msg); rb_add_entry(color, msg); wrefresh(bot.win); } static void setup_signal_handling(void) { signal_pipe = para_signal_init(); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGCHLD); para_install_sighandler(SIGWINCH); para_install_sighandler(SIGUSR1); para_sigaction(SIGHUP, SIG_IGN); } __noreturn static void do_exit(int ret) { para_sigaction(SIGTERM, SIG_IGN); kill(0, SIGTERM); exit(ret); } static void shutdown_curses(void) { if (!curses_active) return; def_prog_mode(); curses_active = 0; endwin(); } __noreturn static void finish(int ret) { shutdown_curses(); do_exit(ret); } /* * exit curses and print given message to stdout/stderr */ __noreturn __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...) { va_list argp; FILE *outfd = ret? stderr: stdout; shutdown_curses(); va_start(argp, fmt); vfprintf(outfd, fmt, argp); va_end(argp); do_exit(ret); } static void print_welcome(void) { if (loglevel > LL_NOTICE) return; outputf(COLOR_WELCOME, "Welcome to para_gui " PACKAGE_VERSION " \"" CODENAME "\". Theme: %s", theme.name); wclrtoeol(bot.win); } /* * init all windows */ static void init_wins(int top_lines) { int i; top.lines = top_lines; top.cols = COLS; top.begy = 0; top.begx = 0; bot.lines = LINES - top.lines - 3; bot.cols = COLS; bot.begy = top.lines + 1; bot.begx = 0; sb.lines = 1; sb.cols = COLS; sb.begy = LINES - 2; sb.begx = 0; in.lines = 1; in.cols = COLS; in.begy = LINES - 1; in.begx = 0; sep.lines = 1; sep.cols = COLS; sep.begy = top.lines; sep.begx = 0; assume_default_colors(theme.default_fg, theme.default_bg); if (top.win) { mvwin(top.win, top.begy, top.begx); wresize(top.win, top.lines, top.cols); mvwin(sb.win, sb.begy, sb.begx); wresize(sb.win, sb.lines, sb.cols); mvwin(sep.win, sep.begy, sep.begx); wresize(sep.win, sep.lines, sep.cols); mvwin(bot.win, bot.begy, bot.begx); wresize(bot.win, bot.lines, bot.cols); mvwin(in.win, in.begy, in.begx); wresize(in.win, in.lines, in.cols); } else { sep.win = newwin(sep.lines, sep.cols, sep.begy, sep.begx); top.win = newwin(top.lines, top.cols, top.begy, top.begx); bot.win = newwin(bot.lines, bot.cols, bot.begy, bot.begx); sb.win = newwin(sb.lines, sb.cols, sb.begy, sb.begx); in.win = newwin(in.lines, in.cols, in.begy, in.begx); if (!top.win || !bot.win || !sb.win || !in.win || !sep.win) msg_n_exit(1, "Error: Cannot create curses windows\n"); wclear(bot.win); wclear(sb.win); wclear(in.win); scrollok(bot.win, 1); wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR)); wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR)); wattron(bot.win, COLOR_PAIR(COLOR_BOT)); wattron(top.win, COLOR_PAIR(COLOR_TOP)); nodelay(top.win, 1); nodelay(bot.win, 1); nodelay(sb.win, 1); nodelay(in.win, 0); keypad(top.win, 1); keypad(bot.win, 1); keypad(sb.win, 1); keypad(in.win, 1); print_status_bar(); } wmove(sep.win, 0, 0); for (i = 1; i <= COLS; i++) waddstr(sep.win, theme.sep_str); wclear(top.win); //wclear(bot.win); wnoutrefresh(top.win); wnoutrefresh(bot.win); //wnoutrefresh(sb.win); print_status_bar(); wnoutrefresh(in.win); wnoutrefresh(sep.win); doupdate(); } static void check_geometry(void) { if (LINES < theme.lines_min || COLS < theme.cols_min) msg_n_exit(EXIT_FAILURE, "Error: Terminal (%dx%d) too small" " (need at least %dx%d)\n", COLS, LINES, theme.cols_min, theme.lines_min); } /* * Print stat item #i to curses window */ static void print_stat_item(int i) { char *tmp; struct stat_item_data d = theme.data[i]; char *c = stat_content[i]; if (!curses_active || !d.len || !c) return; tmp = make_message("%s%s%s", d.prefix, c, d.postfix); wmove(top.win, d.y * top.lines / 100, d.x * COLS / 100); wrefresh(top.win); wattron(top.win, COLOR_PAIR(i + 1)); align_str(top.win, tmp, d.len * COLS / 100, d.align); free(tmp); wrefresh(top.win); } static void print_all_items(void) { int i; if (!curses_active) return; FOR_EACH_STATUS_ITEM(i) print_stat_item(i); } static void clear_all_items(void) { int i; FOR_EACH_STATUS_ITEM(i) { free(stat_content[i]); stat_content[i] = para_strdup(""); } } static void init_colors(void) { int i; if (!has_colors()) msg_n_exit(EXIT_FAILURE, "Error: No color term\n"); start_color(); FOR_EACH_STATUS_ITEM(i) if (theme.data[i].len) init_pair(i + 1, theme.data[i].fg, theme.data[i].bg); init_pair(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg); init_pair(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg); init_pair(COLOR_OUTPUT, theme.output_fg, theme.output_bg); init_pair(COLOR_MSG, theme.msg_fg, theme.msg_bg); init_pair(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg); init_pair(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg); init_pair(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg); init_pair(COLOR_TOP, theme.default_fg, theme.default_bg); init_pair(COLOR_BOT, theme.default_fg, theme.default_bg); } /* * (re-)initialize the curses library FIXME: Error checking */ static void init_curses(void) { curses_active = 1; if (top.win && refresh() == ERR) { /* refesh is really needed */ msg_n_exit(EXIT_FAILURE, "refresh() failed\n"); } check_geometry(); curs_set(0); /* make cursor invisible, ignore errors */ // if (noraw() == ERR); // msg_n_exit(EXIT_FAILURE, "can not place terminal out of " // "raw mode\n"); nonl(); /* tell curses not to do NL->CR/NL on output */ noecho(); /* don't echo input */ cbreak(); /* take input chars one at a time, no wait for \n */ //reset_prog_mode(); init_colors(); clear(); init_wins(theme.top_lines_default); print_all_items(); noecho(); /* don't echo input */ } static void check_sigchld(void) { int ret; pid_t pid; reap_next_child: ret = para_reap_child(&pid); if (ret <= 0) return; if (pid == cmd_pid) { cmd_pid = 0; cmd_died = 1; } goto reap_next_child; } /* * print status line if line starts with known command. */ static int check_stat_line(char *line, __a_unused void *data) { int i; // PARA_INFO_LOG("%s: checking: %s\n", __func__, line); i = stat_line_valid(line); if (i >= 0) { line += strlen(status_item_list[i]) + 1; if (*line == ' ') line++; free(stat_content[i]); stat_content[i] = para_strdup(line); print_stat_item(i); } return 1; } /* * This sucker modifies its first argument. *handler and *arg are * pointers to 0-terminated strings (inside line). Crap. */ static int split_key_map(char *line, char **handler, char **arg) { if (!(*handler = strchr(line + 1, ':'))) goto err_out; **handler = '\0'; (*handler)++; if (!(*arg = strchr(*handler, ':'))) goto err_out; **arg = '\0'; (*arg)++; return 1; err_out: return 0; } static int check_key_map_args(void) { char *s; int i, ret = -1; char *tmp = NULL, *handler, *arg; for (i = 0; i < conf.key_map_given; ++i) { s = conf.key_map_arg[i]; if (!(*s)) goto err_out; free(tmp); tmp = para_strdup(s); if (!split_key_map(tmp, &handler, &arg)) goto err_out; if (strlen(handler) != 1) goto err_out; if (*handler != 'x' && *handler != 'd' && *handler != 'i' && *handler != 'p') goto err_out; if (*handler != 'i') continue; if (find_cmd_byname(arg) < 0) goto err_out; } ret = 0; err_out: free(tmp); return ret; } /* * React to various signal-related events */ static void handle_signal(int sig) { switch (sig) { case SIGTERM: msg_n_exit(EXIT_FAILURE, "only the good die young (caught SIGTERM))\n"); return; case SIGWINCH: if (curses_active) { shutdown_curses(); init_curses(); redraw_bot_win(); } return; case SIGINT: PARA_WARNING_LOG("caught SIGINT, reset"); /* Nothing to do. SIGINT killed our child, para_client stat. * This get noticed by do_select which resets everything */ return; case SIGUSR1: PARA_NOTICE_LOG("got SIGUSR1, rereading configuration"); com_reread_conf(); return; case SIGCHLD: check_sigchld(); return; } } static int open_audiod_pipe(void) { static int init = 1; if (init) init = 0; else /* * Sleep a bit to avoid a busy loop. As the call to sleep() may * be interrupted by SIGCHLD, we simply wait until the call * succeeds. */ while (sleep(2)) ; /* nothing */ return para_open_audiod_pipe(conf.stat_cmd_arg); } /* * This is the core select loop. Besides the (internal) signal * pipe, the following other fds are checked according to the mode: * * GETCH_MODE: check stdin, return when key is pressed * * COMMAND_MODE: check command_pipe and stdin. Return when a screen full * of output has been read or when other end has closed command pipe or * when any key is pressed. * * EXTERNAL_MODE: Check only signal pipe. Used when an external command * is running. During that time curses is disabled. Returns when * cmd_pid == 0. */ static int do_select(int mode) { fd_set rfds; int ret; int max_fileno, cp_numread = 1; char command_buf[4096] = ""; int cbo = 0; /* command buf offset */ struct timeval tv; repeat: tv.tv_sec = conf.timeout_arg / 1000; tv.tv_usec = (conf.timeout_arg % 1000) * 1000; // ret = refresh_status(); FD_ZERO(&rfds); max_fileno = 0; /* audiod pipe */ if (audiod_pipe < 0) audiod_pipe = open_audiod_pipe(); if (audiod_pipe >= 0) para_fd_set(audiod_pipe, &rfds, &max_fileno); /* signal pipe */ para_fd_set(signal_pipe, &rfds, &max_fileno); /* command pipe only for COMMAND_MODE */ if (command_pipe >= 0 && mode == COMMAND_MODE) para_fd_set(command_pipe, &rfds, &max_fileno); ret = para_select(max_fileno + 1, &rfds, NULL, &tv); if (ret <= 0) goto check_return; /* skip fd checks */ /* signals */ if (FD_ISSET(signal_pipe, &rfds)) { int sig_nr = para_next_signal(); if (sig_nr > 0) handle_signal(sig_nr); } /* read command pipe if ready */ if (command_pipe >= 0 && mode == COMMAND_MODE && FD_ISSET(command_pipe, &rfds)) { cp_numread = read(command_pipe, command_buf + cbo, sizeof(command_buf) - 1 - cbo); if (cp_numread >= 0) cbo += cp_numread; else { if (cp_numread < 0) PARA_ERROR_LOG("read error (%d)", cp_numread); close(command_pipe); command_pipe = -1; } } if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds)) if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) { close(audiod_pipe); audiod_pipe = -1; clear_all_items(); free(stat_content[SI_BASENAME]); stat_content[SI_BASENAME] = para_strdup("audiod not running!?"); print_all_items(); } check_return: switch (mode) { case COMMAND_MODE: if (cp_numread <= 0 && !cbo) /* command complete */ return 0; if (cbo) cbo = for_each_line(command_buf, cbo, &add_output_line, NULL); if (cp_numread <= 0) cbo = 0; wrefresh(bot.win); ret = wgetch(top.win); if (ret != ERR && ret != KEY_RESIZE) { if (command_pipe) { close(command_pipe); command_pipe = -1; } if (cmd_pid) kill(cmd_pid, SIGTERM); return -1; } break; case GETCH_MODE: ret = wgetch(top.win); if (ret != ERR && ret != KEY_RESIZE) return ret; break; case EXTERNAL_MODE: if (cmd_died) { cmd_died = 0; return 0; } } goto repeat; } /* * read from command pipe and print data to bot window */ static int send_output(void) { int ret; if (command_pipe < 0) return 0; ret = mark_fd_nonblocking(command_pipe); if (ret < 0) { close(command_pipe); return ret; } if (do_select(COMMAND_MODE) >= 0) PARA_INFO_LOG("command complete"); else PARA_NOTICE_LOG("command aborted"); print_in_bar(COLOR_MSG, " "); return 1; } static int client_cmd_cmdline(char *cmd) { int ret, fds[3] = {0, 1, 0}; char *c = make_message(BINDIR "/para_client -- %s", cmd); outputf(COLOR_COMMAND, "%s", c); print_in_bar(COLOR_MSG, "executing client command, hit any key to abort\n"); ret = para_exec_cmdline_pid(&cmd_pid, c, fds); free(c); if (ret < 0) return -1; command_pipe = fds[1]; return send_output(); } /* * exec command and print output to bot win */ static int display_cmd(char *cmd) { int fds[3] = {0, 1, 0}; print_in_bar(COLOR_MSG, "executing display command, hit any key to abort"); outputf(COLOR_COMMAND, "%s", cmd); if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0) return -1; command_pipe = fds[1]; return send_output(); } /* * shutdown curses and stat pipe before executing external commands */ static int external_cmd(char *cmd) { int fds[3] = {-1, -1, -1}; if (cmd_pid) return -1; shutdown_curses(); para_exec_cmdline_pid(&cmd_pid, cmd, fds); cmd_died = 0; do_select(EXTERNAL_MODE); init_curses(); return 0; } static void print_scroll_msg(void) { unsigned lines_total, filled = ringbuffer_filled(bot_win_rb); int first_rbe = first_visible_rbe(&lines_total); print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe, filled - scroll_position, ringbuffer_filled(bot_win_rb)); } static void com_page_down(void) { unsigned lines = 0; int i = scroll_position; while (lines < bot.lines && --i > 0) { struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i); if (!rbe) break; lines += NUM_LINES(strlen(rbe->msg)); } if (lines) { scroll_position = i; redraw_bot_win(); print_scroll_msg(); return; } print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n"); } static void com_page_up(void) { unsigned lines; int fvr = first_visible_rbe(&lines); if (fvr < 0 || fvr + 1 >= ringbuffer_filled(bot_win_rb)) { print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n"); return; } scroll_position = fvr + 1; for (; scroll_position > 0; scroll_position--) { first_visible_rbe(&lines); if (lines == bot.lines) break; } redraw_bot_win(); print_scroll_msg(); } static void com_scroll_down(void) { struct rb_entry *rbe; int rbe_lines; if (!scroll_position) { print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n"); return; } scroll_position--; rbe = ringbuffer_get(bot_win_rb, scroll_position); rbe_lines = NUM_LINES(rbe->len); wscrl(bot.win, rbe_lines); wmove(bot.win, bot.lines - rbe_lines, 0); wattron(bot.win, COLOR_PAIR(rbe->color)); waddstr(bot.win, rbe->msg); wrefresh(bot.win); print_scroll_msg(); } static void com_scroll_up(void) { struct rb_entry *rbe = NULL; unsigned lines; int i, first_rbe, num_scroll; /* the entry that is going to vanish */ rbe = ringbuffer_get(bot_win_rb, scroll_position); if (!rbe) goto err_out; num_scroll = NUM_LINES(rbe->len); first_rbe = first_visible_rbe(&lines); if (first_rbe < 0 || (first_rbe == ringbuffer_filled(bot_win_rb) - 1)) goto err_out; scroll_position++; wscrl(bot.win, -num_scroll); i = draw_top_rbe(&lines); if (i < 0) goto err_out; while (i > 0 && lines < num_scroll) { int rbe_lines; rbe = ringbuffer_get(bot_win_rb, --i); if (!rbe) break; rbe_lines = NUM_LINES(rbe->len); lines += rbe_lines; // fprintf(stderr, "msg: %s\n", rbe->msg); wattron(bot.win, COLOR_PAIR(rbe->color)); waddstr(bot.win, "\n"); waddstr(bot.win, rbe->msg); if (!i) break; i--; } wrefresh(bot.win); print_scroll_msg(); return; err_out: print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n"); } static void com_ll_decr(void) { if (loglevel <= LL_DEBUG) { print_in_bar(COLOR_ERRMSG, "loglevel already at maximal verbosity\n"); return; } loglevel--; print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel); } static void com_ll_incr(void) { if (loglevel >= LL_EMERG) { print_in_bar(COLOR_ERRMSG, "loglevel already at minimal verbosity\n"); return; } loglevel++; print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel); } /* * reread configuration, terminate on errors */ static void com_reread_conf(void) { char *cf =configfile_exists(); struct gui_cmdline_parser_params params = { .override = 1, .initialize = 1, .check_required = 0, .check_ambiguity = 0 }; if (!cf) { PARA_WARNING_LOG("there is no configuration to read"); return; } PARA_INFO_LOG("rereading command line options and config file"); gui_cmdline_parser(_argc, _argv, &conf); gui_cmdline_parser_config_file(cf, &conf, ¶ms); PARA_NOTICE_LOG("config file reloaded"); if (check_key_map_args() < 0) finish(EXIT_FAILURE); } static void com_help(void) { int i; for (i = 0; i < conf.key_map_given; ++i) { char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]); const char *handler_text = "???", *desc = NULL; if (!split_key_map(tmp, &handler, &arg)) { free(tmp); return; } switch (*handler) { case 'i': handler_text = "internal"; desc = command_list[find_cmd_byname(arg)].description; break; case 'x': handler_text = "external"; break; case 'd': handler_text = "display "; break; case 'p': handler_text = "para "; break; } outputf(COLOR_MSG, "%s\t%s\t%s%s\t%s", tmp, handler_text, arg, strlen(arg) < 8? "\t" : "", desc? desc : ""); free(tmp); } for (i = 0; command_list[i].handler; i++) { struct gui_command gc = command_list[i]; outputf(COLOR_MSG, "%s\tinternal\t%s\t%s%s", gc.key, gc.name, strlen(gc.name) < 8? "\t" : "", gc.description); } print_in_bar(COLOR_MSG, "try \"para_gui -h\" or \"para_client help\" " "for more info"); } static void com_shrink_top_win(void) { if (top.lines <= theme.top_lines_min) { PARA_WARNING_LOG("can not decrease top window"); return; } init_wins(top.lines - 1); wclear(top.win); print_all_items(); print_in_bar(COLOR_MSG, "%s", "decreased top window"); } static void com_enlarge_top_win(void) { if (bot.lines < 3) { PARA_WARNING_LOG("can not increase top window"); return; } init_wins(top.lines + 1); wclear(top.win); print_all_items(); print_in_bar(COLOR_MSG, "increased top window"); } static void com_version(void) { print_in_bar(COLOR_MSG, "para_gui " PACKAGE_VERSION " \"" CODENAME "\""); } __noreturn static void com_quit(void) { finish(0); } static void com_refresh(void) { shutdown_curses(); init_curses(); } static void change_theme(int next) { if (next) next_theme(&theme); else prev_theme(&theme); /* This seems to be needed twice, why? */ com_refresh(); com_refresh(); PARA_NOTICE_LOG("new theme: %s", theme.name); } static void com_next_theme(void) { change_theme(1); } static void com_prev_theme(void) { change_theme(0); } static void handle_command(int c) { int i; /* first check user's key bindings */ for (i = 0; i < conf.key_map_given; ++i) { char tmp[MAXLINE], *handler, *arg; strcpy(tmp, conf.key_map_arg[i]); if (!split_key_map(tmp, &handler, &arg)) return; if (!strcmp(tmp, km_keyname(c))) { if (*handler == 'd') { display_cmd(arg); return; } if (*handler == 'x') { external_cmd(arg); return; } if (*handler == 'p') { client_cmd_cmdline(arg); return; } if (*handler == 'i') { int num = find_cmd_byname(arg); if (num >= 0) command_list[num].handler(); return; } } } /* not found, check internal key bindings */ for (i = 0; command_list[i].handler; i++) { if (!strcmp(km_keyname(c), command_list[i].key)) { command_list[i].handler(); return; } } print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help", km_keyname(c)); } int main(int argc, char *argv[]) { int ret; char *cf; _argc = argc; _argv = argv; if (gui_cmdline_parser(argc, argv, &conf)) { fprintf(stderr, "parse error while reading command line\n"); exit(EXIT_FAILURE); } HANDLE_VERSION_FLAG("gui", conf); init_theme(0, &theme); top.lines = theme.top_lines_default; if (check_key_map_args() < 0) { fprintf(stderr, "invalid key map\n"); exit(EXIT_FAILURE); } cf = configfile_exists(); if (!cf && conf.config_file_given) { fprintf(stderr, "can not read config file %s\n", conf.config_file_arg); exit(EXIT_FAILURE); } if (cf) { struct gui_cmdline_parser_params params = { .override = 0, .initialize = 0, .check_required = 0, .check_ambiguity = 0 }; gui_cmdline_parser_config_file(cf, &conf, ¶ms); } loglevel = get_loglevel_by_name(conf.loglevel_arg); if (check_key_map_args() < 0) { fprintf(stderr, "invalid key map in config file\n"); exit(EXIT_FAILURE); } setup_signal_handling(); bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE); initscr(); /* needed only once, always successful */ init_curses(); print_welcome(); for (;;) { print_status_bar(); ret = do_select(GETCH_MODE); if (!ret) continue; print_in_bar(COLOR_MSG, " "); handle_command(ret); } }