/*
- * Copyright (C) 1998-2007 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1998-2013 Andre Noll <maan@systemlinux.org>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
-/** \file gui.c ncurses-based interface for paraslash */
+/** \file gui.c Curses-based interface for paraslash. */
+#include <regex.h>
#include <signal.h>
#include <sys/types.h>
-#include <dirent.h>
+#include <curses.h>
+#include <locale.h>
+#include <sys/time.h>
+
#include "gui.cmdline.h"
#include "para.h"
#include "gui.h"
-#include <curses.h>
+#include "string.h"
#include "ringbuffer.h"
-#include "gui_common.h"
#include "fd.h"
#include "error.h"
#include "list.h"
#include "sched.h"
#include "signal.h"
+#include "ggo.h"
+#include "version.h"
/** define the array of error lists needed by para_gui */
INIT_GUI_ERRLISTS;
static int signal_pipe;
-static void finish(int sig);
-
static struct win_data {
WINDOW *win;
- NCURSES_SIZE_T begx;
- NCURSES_SIZE_T begy;
- NCURSES_SIZE_T cols;
- NCURSES_SIZE_T lines;
+ size_t begx;
+ size_t begy;
+ size_t cols;
+ size_t lines;
} top, bot, sb, in, sep;
#define RINGBUFFER_SIZE 512
size_t len;
int color;
};
-void *bot_win_rb;
+static struct ringbuffer *bot_win_rb;
#define NUM_LINES(len) (1 + (len) / bot.cols)
static unsigned scroll_position;
-static int cmd_died, curses_active;
+static int curses_active;
static pid_t cmd_pid;
-static int command_pipe = -1;
-static int audiod_pipe = -1;
+static int command_fds[2];
+static int stat_pipe = -1;
static struct gui_args_info conf;
enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE};
static struct gui_theme theme;
-int _argc;
-char **_argv;
+static int _argc;
+static 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);
+__noreturn 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_scroll_down(void);
static void com_page_up(void);
static void com_page_down(void);
+static void com_cancel_scrolling(void);
+static void com_scroll_top(void);
-struct gui_command command_list[] = {
+static struct gui_command command_list[] = {
{
.key = "?",
.name = "help",
.name = "page_down",
.description = "scroll down one page",
.handler = com_page_down
+ }, {
+ .key = "<home>",
+ .name = "scroll_top",
+ .description = "scroll to top of buffer",
+ .handler = com_scroll_top
+ }, {
+ .key = "<end>",
+ .name = "cancel_scroll",
+ .description = "deactivate scroll mode",
+ .handler = com_cancel_scrolling
}, {
.handler = NULL
}
sprintf(buf, "<ppage>");
return buf;
}
+ if (c == KEY_HOME) {
+ sprintf(buf, "<home>");
+ return buf;
+ }
+ if (c == KEY_END) {
+ sprintf(buf, "<end>");
+ return buf;
+ }
if (c < 256 && c > -128 && iscntrl((unsigned char) c)) {
if (c < 0)
c += 256;
return file_exists(tmp)? tmp: NULL;
}
-/*
- * print num spaces to curses window
- */
+/* Print given number of spaces to curses window. */
static void add_spaces(WINDOW* win, unsigned int num)
{
- while (num > 0) {
- num--;
- waddstr(win, " ");
+ char space[] = " ";
+ unsigned sz = sizeof(space) - 1; /* number of spaces */
+
+ while (num >= sz) {
+ waddstr(win, space);
+ num -= sz;
+ }
+ if (num > 0) {
+ assert(num < sz);
+ space[num] = '\0';
+ waddstr(win, space);
}
}
* 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,
+static int align_str(WINDOW* win, char *str, unsigned int len,
unsigned int align)
{
- int num; /* of spaces */
- char *str;
+ int ret, i, num; /* of spaces */
+ size_t width;
- if (!win || !string)
- return -1;
- num = len - strlen(string);
- str = para_strdup(string);
+ if (!win || !str)
+ return 0;
+ ret = strwidth(str, &width);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ width = 0;
+ str[0] = '\0';
+ }
+ num = len - width;
if (num < 0) {
str[len] = '\0';
num = 0;
}
+ /* replace control characters by spaces */
+ for (i = 0; i < len && str[i]; i++) {
+ if (str[i] == '\n' || str[i] == '\r' || str[i] == '\f')
+ str[i] = ' ';
+ }
if (align == LEFT) {
waddstr(win, str);
add_spaces(win, num);
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;
+ va_list ap;
if (!curses_active)
return;
wattron(in.win, COLOR_PAIR(color));
- PARA_VSPRINTF(fmt, msg);
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
wmove(in.win, 0, 0);
align_str(in.win, msg, sb.cols, LEFT);
free(msg);
*/
static void print_status_bar(void)
{
+ char *tmp;
+
if (!curses_active)
return;
+ tmp = para_strdup(STANDARD_STATUS_BAR);
wmove(sb.win, 0, 0);
- align_str(sb.win, STANDARD_STATUS_BAR, sb.cols, CENTER);
+ align_str(sb.win, tmp, sb.cols, CENTER);
+ free(tmp);
wrefresh(sb.win);
}
return RINGBUFFER_SIZE - 1;
}
+/*
+returns number of first visible rbe, *lines is the number of lines drawn.
+ */
static int draw_top_rbe(unsigned *lines)
{
- unsigned len;
- int offset, fvr = first_visible_rbe(lines);
+ int ret, fvr = first_visible_rbe(lines);
struct rb_entry *rbe;
+ size_t bytes_to_skip, cells_to_skip, width;
if (fvr < 0)
return -1;
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;
+ if (*lines > bot.lines) {
+ /* rbe is partially visible multi-line */
+ cells_to_skip = (*lines - bot.lines) * bot.cols;
+ ret = skip_cells(rbe->msg, cells_to_skip, &bytes_to_skip);
+ if (ret < 0)
+ return ret;
+ ret = strwidth(rbe->msg + bytes_to_skip, &width);
+ if (ret < 0)
+ return ret;
+ } else {
+ bytes_to_skip = 0;
+ width = rbe->len;
+ }
wattron(bot.win, COLOR_PAIR(rbe->color));
- waddstr(bot.win, rbe->msg + offset);
- *lines = NUM_LINES(len - offset);
+ waddstr(bot.win, rbe->msg + bytes_to_skip);
+ *lines = NUM_LINES(width);
return fvr;
}
static void rb_add_entry(int color, char *msg)
{
- struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
+ struct rb_entry *old, *new;
int x, y;
+ size_t len;
+
+ if (strwidth(msg, &len) < 0)
+ return;
+ new = para_malloc(sizeof(struct rb_entry));
new->color = color;
- new->len = strlen(msg);
+ new->len = len;
new->msg = msg;
old = ringbuffer_add(bot_win_rb, new);
// fprintf(stderr, "added: %s\n", new->msg);
__printf_2_3 static void outputf(int color, const char* fmt,...)
{
char *msg;
+ va_list ap;
if (!curses_active)
return;
- PARA_VSPRINTF(fmt, msg);
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
rb_add_entry(color, msg);
wrefresh(bot.win);
}
-static int add_output_line(char *line, __a_unused void *data)
+static int add_output_line(char *line, void *data)
{
+ int color = *(int *)data? COLOR_ERRMSG : COLOR_OUTPUT;
if (!curses_active)
return 1;
- rb_add_entry(COLOR_OUTPUT, para_strdup(line));
+ rb_add_entry(color, para_strdup(line));
return 1;
}
-void para_log(int ll, const char *fmt,...)
+static int loglevel;
+
+static __printf_2_3 void curses_log(int ll, const char *fmt,...)
{
int color;
char *msg;
+ va_list ap;
- if (ll < conf.loglevel_arg || !curses_active)
+ if (ll < loglevel || !curses_active)
return;
switch (ll) {
- case DEBUG:
- case INFO:
- case NOTICE:
+ case LL_DEBUG:
+ case LL_INFO:
+ case LL_NOTICE:
color = COLOR_MSG;
break;
default:
color = COLOR_ERRMSG;
}
- PARA_VSPRINTF(fmt, msg);
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
chop(msg);
rb_add_entry(color, msg);
wrefresh(bot.win);
}
+__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
static void setup_signal_handling(void)
{
para_install_sighandler(SIGCHLD);
para_install_sighandler(SIGWINCH);
para_install_sighandler(SIGUSR1);
-// signal(SIGPIPE, SIG_IGN);
- signal(SIGHUP, SIG_IGN);
+ para_sigaction(SIGHUP, SIG_IGN);
}
-static void do_exit(int ret)
+/* kill every process in the process group and exit */
+__noreturn static void kill_pg_and_die(int ret)
{
- signal(SIGTERM, SIG_IGN);
+ para_sigaction(SIGTERM, SIG_IGN);
kill(0, SIGTERM);
exit(ret);
}
endwin();
}
-static void finish(int ret)
+__noreturn static void finish(int ret)
{
shutdown_curses();
- do_exit(ret);
+ kill_pg_and_die(ret);
}
/*
* exit curses and print given message to stdout/stderr
*/
-__printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...)
+__noreturn __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...)
{
va_list argp;
FILE *outfd = ret? stderr: stdout;
va_start(argp, fmt);
vfprintf(outfd, fmt, argp);
va_end(argp);
- do_exit(ret);
+ kill_pg_and_die(ret);
}
static void print_welcome(void)
{
- int ll = conf.loglevel_arg;
- if (ll > NOTICE)
+ if (loglevel > LL_NOTICE)
return;
- outputf(COLOR_WELCOME, "Welcome to para_gui " PACKAGE_VERSION
- " \"" CODENAME "\". Theme: %s", theme.name);
+ outputf(COLOR_WELCOME, "Welcome to %s. Theme: %s",
+ version_single_line("gui"), theme.name);
wclrtoeol(bot.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
*/
wrefresh(top.win);
}
+static int update_item(int item_num, char *buf)
+{
+ char **c = stat_content + item_num;
+
+ free(*c);
+ if (buf && buf[0])
+ goto dup;
+ switch (item_num) {
+ case SI_ARTIST:
+ *c = para_strdup("(artist tag not set)");
+ goto print;
+ case SI_TITLE:
+ *c = para_strdup("(title tag not set)");
+ goto print;
+ case SI_YEAR:
+ *c = para_strdup("????");
+ goto print;
+ case SI_ALBUM:
+ *c = para_strdup("(album tag not set)");
+ goto print;
+ case SI_COMMENT:
+ *c = para_strdup("(comment tag not set)");
+ goto print;
+ }
+dup:
+ *c = para_strdup(buf);
+print:
+ print_stat_item(item_num);
+ return 1;
+}
+
+static int read_stat_pipe(fd_set *rfds)
+{
+ static char *buf;
+ static int bufsize, loaded;
+ int ret, ret2;
+ size_t sz;
+
+ if (stat_pipe < 0)
+ return 0;
+ if (loaded >= bufsize) {
+ if (bufsize > 1000 * 1000) {
+ loaded = 0;
+ return 0;
+ }
+ bufsize += bufsize + 1000;
+ buf = para_realloc(buf, bufsize);
+ }
+ assert(loaded < bufsize);
+ ret = read_nonblock(stat_pipe, buf + loaded, bufsize - loaded,
+ rfds, &sz);
+ loaded += sz;
+ ret2 = for_each_stat_item(buf, loaded, update_item);
+ if (ret < 0 || ret2 < 0) {
+ loaded = 0;
+ return ret2 < 0? ret2 : ret;
+ }
+ sz = ret2; /* what is left */
+ if (sz > 0 && sz < loaded)
+ memmove(buf, buf + loaded - sz, sz);
+ loaded = sz;
+ return 1;
+}
+
static void print_all_items(void)
{
int i;
}
}
-static void init_colors(void)
+static void init_pair_or_die(short pair, short f, short b)
+{
+ if (init_pair(pair, f, b) == ERR)
+ msg_n_exit(EXIT_FAILURE, "fatal: init_pair() failed\n");
+}
+
+static void init_colors_or_die(void)
{
int i;
if (!has_colors())
- msg_n_exit(EXIT_FAILURE, "Error: No color term\n");
- start_color();
+ msg_n_exit(EXIT_FAILURE, "fatal: No color term\n");
+ if (start_color() == ERR)
+ msg_n_exit(EXIT_FAILURE, "fatal: failed to start colors\n");
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);
+ init_pair_or_die(i + 1, theme.data[i].fg,
+ theme.data[i].bg);
+ init_pair_or_die(COLOR_STATUSBAR, theme.sb_fg, theme.sb_bg);
+ init_pair_or_die(COLOR_COMMAND, theme.cmd_fg, theme.cmd_bg);
+ init_pair_or_die(COLOR_OUTPUT, theme.output_fg, theme.output_bg);
+ init_pair_or_die(COLOR_MSG, theme.msg_fg, theme.msg_bg);
+ init_pair_or_die(COLOR_ERRMSG, theme.err_msg_fg, theme.err_msg_bg);
+ init_pair_or_die(COLOR_WELCOME, theme.welcome_fg, theme.welcome_bg);
+ init_pair_or_die(COLOR_SEPARATOR, theme.sep_fg, theme.sep_bg);
+ init_pair_or_die(COLOR_TOP, theme.default_fg, theme.default_bg);
+ init_pair_or_die(COLOR_BOT, theme.default_fg, theme.default_bg);
}
-/*
- * (re-)initialize the curses library FIXME: Error checking
- */
+/* (Re-)initialize the curses library. */
static void init_curses(void)
{
curses_active = 1;
- if (top.win && refresh() == ERR) { /* refesh is really needed */
+ if (top.win && refresh() == ERR) /* refesh is really needed */
msg_n_exit(EXIT_FAILURE, "refresh() failed\n");
- }
- check_geometry();
+ 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);
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();
+ nonl(); /* do not NL->CR/NL on output, always returns OK */
+ /* don't echo input */
+ if (noecho() == ERR)
+ msg_n_exit(EXIT_FAILURE, "fatal: noecho() failed\n");
+ /* take input chars one at a time, no wait for \n */
+ if (cbreak() == ERR)
+ msg_n_exit(EXIT_FAILURE, "fatal: cbreak() failed\n");
+ init_colors_or_die();
+ clear(); /* ignore non-fatal errors */
init_wins(theme.top_lines_default);
print_all_items();
- noecho(); /* don't echo input */
+ // noecho(); /* don't echo input */
}
-
static void check_sigchld(void)
{
int ret;
ret = para_reap_child(&pid);
if (ret <= 0)
return;
- if (pid == cmd_pid) {
+ 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.
}
return;
case SIGINT:
- PARA_WARNING_LOG("%s", "caught SIGINT, reset");
- /* Nothing to do. SIGINT killed our child, para_client stat.
- * This get noticed by do_select which resets everything
+ PARA_WARNING_LOG("caught SIGINT, reset");
+ /* Nothing to do. SIGINT killed our child which gets noticed
+ * by do_select and resets everything.
*/
return;
case SIGUSR1:
- PARA_NOTICE_LOG("%s", "got SIGUSR1, rereading configuration");
+ PARA_NOTICE_LOG("got SIGUSR1, rereading configuration");
com_reread_conf();
return;
case SIGCHLD:
}
}
-static int open_audiod_pipe(void)
+static void status_pre_select(fd_set *rfds, int *max_fileno, struct timeval *tv)
{
- static int init = 1;
+ static struct timeval next_exec, atm, diff;
+ int ret, fds[3] = {0, 1, 0};
+ pid_t pid;
- if (init)
- init = 0;
- else
- sleep(1);
- return para_open_audiod_pipe(conf.stat_cmd_arg);
+ if (stat_pipe >= 0)
+ goto success;
+ /* Avoid busy loop */
+ gettimeofday(&atm, NULL);
+ if (tv_diff(&next_exec, &atm, &diff) > 0) {
+ if (tv_diff(&diff, tv, NULL) < 0)
+ *tv = diff;
+ return;
+ }
+ next_exec.tv_sec = atm.tv_sec + 2;
+ ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
+ if (ret < 0)
+ return;
+ ret = mark_fd_nonblocking(fds[1]);
+ if (ret < 0) {
+ close(fds[1]);
+ return;
+ }
+ stat_pipe = fds[1];
+success:
+ para_fd_set(stat_pipe, rfds, max_fileno);
}
+#define COMMAND_BUF_SIZE 32768
+
/*
* 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.
+ * COMMAND_MODE: check command fds and stdin. Return when peer has closed both
+ * stdout and stderr or when any key is pressed.
*
* EXTERNAL_MODE: Check only signal pipe. Used when an external command
- * is running. During that thime curses is disabled. Returns when
+ * 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 */
+ int ret, i, max_fileno;
+ char command_buf[2][COMMAND_BUF_SIZE] = {"", ""};
+ int cbo[2] = {0, 0}; /* command buf offsets */
struct timeval tv;
+ unsigned flags[2] = {0, 0}; /* for for_each_line() */
+
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);
+ status_pre_select(&rfds, &max_fileno, &tv);
/* 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);
+ if (mode == COMMAND_MODE) {
+ if (command_fds[0] >= 0)
+ para_fd_set(command_fds[0], &rfds, &max_fileno);
+ if (command_fds[1] >= 0)
+ para_fd_set(command_fds[1], &rfds, &max_fileno);
+ }
+ if (mode == GETCH_MODE || mode == COMMAND_MODE)
+ para_fd_set(STDIN_FILENO, &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);
- }
+ ret = para_next_signal(&rfds);
+ if (ret > 0)
+ handle_signal(ret);
/* 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 (mode == COMMAND_MODE) {
+ for (i = 0; i < 2; i++) {
+ size_t sz;
+ if (command_fds[i] < 0)
+ continue;
+ ret = read_nonblock(command_fds[i],
+ command_buf[i] + cbo[i],
+ COMMAND_BUF_SIZE - 1 - cbo[i], &rfds, &sz);
+ cbo[i] += sz;
+ sz = cbo[i];
+ cbo[i] = for_each_line(flags[i], command_buf[i], cbo[i],
+ add_output_line, &i);
+ if (sz != cbo[i]) { /* at least one line found */
+ wrefresh(bot.win);
+ flags[i] = 0;
+ }
+ if (ret < 0) {
+ PARA_NOTICE_LOG("closing command fd %d: %s",
+ i, para_strerror(-ret));
+ close(command_fds[i]);
+ command_fds[i] = -1;
+ flags[i] = 0;
+ if (command_fds[!i] < 0) /* both fds closed */
+ return 0;
+ }
+ if (cbo[i] == COMMAND_BUF_SIZE - 1) {
+ PARA_NOTICE_LOG("discarding overlong line");
+ cbo[i] = 0;
+ flags[i] = FELF_DISCARD_FIRST;
+ }
}
}
- 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();
- }
+ ret = read_stat_pipe(&rfds);
+ if (ret < 0) {
+ PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret));
+ close(stat_pipe);
+ stat_pipe = -1;
+ clear_all_items();
+ free(stat_content[SI_BASENAME]);
+ stat_content[SI_BASENAME] =
+ para_strdup("stat command terminated!?");
+ 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 (command_fds[0] >= 0) {
+ close(command_fds[0]);
+ command_fds[0] = -1;
+ }
+ if (command_fds[1] >= 0) {
+ close(command_fds[1]);
+ command_fds[1] = -1;
}
if (cmd_pid)
kill(cmd_pid, SIGTERM);
return ret;
break;
case EXTERNAL_MODE:
- if (cmd_died) {
- cmd_died = 0;
+ if (cmd_pid == 0)
return 0;
- }
}
goto repeat;
}
/*
* read from command pipe and print data to bot window
*/
-static int send_output(void)
+static void send_output(void)
{
- if (command_pipe < 0)
- return 0;
+ int ret;
+
+ ret = mark_fd_nonblocking(command_fds[0]);
+ if (ret < 0)
+ goto fail;
+ ret = mark_fd_nonblocking(command_fds[1]);
+ if (ret < 0)
+ goto fail;
if (do_select(COMMAND_MODE) >= 0)
- PARA_INFO_LOG("%s", "command complete");
+ PARA_INFO_LOG("command complete");
else
- PARA_NOTICE_LOG("%s", "command aborted");
+ PARA_NOTICE_LOG("command aborted");
print_in_bar(COLOR_MSG, " ");
- return 1;
+ return;
+fail:
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ close(command_fds[0]);
+ close(command_fds[1]);
}
-static int client_cmd_cmdline(char *cmd)
+static void para_cmd(char *cmd)
{
- int ret, fds[3] = {0, 1, 0};
+ int ret, fds[3] = {0, 1, 1};
char *c = make_message(BINDIR "/para_client -- %s", cmd);
outputf(COLOR_COMMAND, "%s", c);
ret = para_exec_cmdline_pid(&cmd_pid, c, fds);
free(c);
if (ret < 0)
- return -1;
- command_pipe = fds[1];
- mark_fd_nonblocking(command_pipe);
- return send_output();
+ return;
+ command_fds[0] = fds[1];
+ command_fds[1] = fds[2];
+ send_output();
}
/*
* exec command and print output to bot win
*/
-static int display_cmd(char *cmd)
+static void display_cmd(char *cmd)
{
- int fds[3] = {0, 1, 0};
+ int fds[3] = {0, 1, 1};
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];
- mark_fd_nonblocking(command_pipe);
- return send_output();
+ return;
+ command_fds[0] = fds[1];
+ command_fds[1] = fds[2];
+ send_output();
}
/*
* shutdown curses and stat pipe before executing external commands
*/
-static int external_cmd(char *cmd)
+static void external_cmd(char *cmd)
{
int fds[3] = {-1, -1, -1};
if (cmd_pid)
- return -1;
+ return;
shutdown_curses();
- para_exec_cmdline_pid(&cmd_pid, cmd, fds);
- cmd_died = 0;
+ if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
+ return;
do_select(EXTERNAL_MODE);
init_curses();
- return 0;
}
static void print_scroll_msg(void)
filled - scroll_position, ringbuffer_filled(bot_win_rb));
}
+static void com_scroll_top(void)
+{
+ int i = RINGBUFFER_SIZE - 1;
+ unsigned lines = 0;
+
+ while (i > 0 && !ringbuffer_get(bot_win_rb, i))
+ i--;
+ /* i is oldest entry */
+ for (; lines < bot.lines && i >= 0; i--) {
+ struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
+ if (!rbe)
+ break;
+ lines += NUM_LINES(rbe->len);
+ }
+ i++;
+ if (lines > 0 && scroll_position != i) {
+ scroll_position = i;
+ redraw_bot_win();
+ print_scroll_msg();
+ return;
+ }
+ print_in_bar(COLOR_ERRMSG, "top of buffer is shown\n");
+}
+
+static void com_cancel_scrolling(void)
+{
+
+ if (scroll_position == 0) {
+ print_in_bar(COLOR_ERRMSG, "bottom of buffer is shown\n");
+ return;
+ }
+ scroll_position = 0;
+ redraw_bot_win();
+}
+
static void com_page_down(void)
{
unsigned lines = 0;
struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
if (!rbe)
break;
- lines += NUM_LINES(strlen(rbe->msg));
+ lines += NUM_LINES(rbe->len);
}
if (lines) {
scroll_position = i;
{
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--) {
- fvr = first_visible_rbe(&lines);
+ first_visible_rbe(&lines);
if (lines == bot.lines)
break;
}
static void com_ll_decr(void)
{
- if (conf.loglevel_arg <= DEBUG) {
+ if (loglevel <= LL_DEBUG) {
print_in_bar(COLOR_ERRMSG,
"loglevel already at maximal verbosity\n");
return;
}
- conf.loglevel_arg--;
- print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+ loglevel--;
+ print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
}
static void com_ll_incr(void)
{
- if (conf.loglevel_arg >= EMERG) {
+ if (loglevel >= LL_EMERG) {
print_in_bar(COLOR_ERRMSG,
- "loglevel already at miminal verbosity\n");
+ "loglevel already at minimal verbosity\n");
return;
}
- conf.loglevel_arg++;
- print_in_bar(COLOR_MSG, "loglevel set to %d\n", conf.loglevel_arg);
+ loglevel++;
+ print_in_bar(COLOR_MSG, "loglevel set to %d\n", loglevel);
}
/*
.override = 1,
.initialize = 1,
.check_required = 0,
- .check_ambiguity = 0
+ .check_ambiguity = 0,
+ .print_errors = 0,
};
if (!cf) {
- PARA_WARNING_LOG("%s", "there is no configuration to read");
+ PARA_WARNING_LOG("there is no configuration to read");
return;
}
- PARA_INFO_LOG("%s", "rereading command line options and config file");
- gui_cmdline_parser(_argc, _argv, &conf);
+ PARA_INFO_LOG("rereading command line options and config file");
+ gui_cmdline_parser_ext(_argc, _argv, &conf, ¶ms);
gui_cmdline_parser_config_file(cf, &conf, ¶ms);
- PARA_NOTICE_LOG("%s", "config file reloaded");
+ PARA_NOTICE_LOG("config file reloaded");
if (check_key_map_args() < 0)
finish(EXIT_FAILURE);
}
static void com_shrink_top_win(void)
{
if (top.lines <= theme.top_lines_min) {
- PARA_WARNING_LOG("%s", "can not decrease top window");
+ PARA_WARNING_LOG("can not decrease top window");
return;
}
init_wins(top.lines - 1);
static void com_enlarge_top_win(void)
{
if (bot.lines < 3) {
- PARA_WARNING_LOG("%s", "can not increase top window");
+ PARA_WARNING_LOG("can not increase top window");
return;
}
init_wins(top.lines + 1);
static void com_version(void)
{
- print_in_bar(COLOR_MSG, "para_gui " PACKAGE_VERSION " \""
- CODENAME "\"");
+ print_in_bar(COLOR_MSG, "%s", version_single_line("gui"));
}
-static void com_quit(void)
+__noreturn static void com_quit(void)
{
finish(0);
}
/* first check user's key bindings */
for (i = 0; i < conf.key_map_given; ++i) {
- char tmp[MAXLINE], *handler, *arg;
+ char *tmp, *handler, *arg;
- strcpy(tmp, conf.key_map_arg[i]);
- if (!split_key_map(tmp, &handler, &arg))
+ tmp = para_strdup(conf.key_map_arg[i]);
+ if (!split_key_map(tmp, &handler, &arg)) {
+ free(tmp);
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;
- }
}
+ if (strcmp(tmp, km_keyname(c))) {
+ free(tmp);
+ continue;
+ }
+ if (*handler == 'd')
+ display_cmd(arg);
+ else if (*handler == 'x')
+ external_cmd(arg);
+ else if (*handler == 'p')
+ para_cmd(arg);
+ else if (*handler == 'i') {
+ int num = find_cmd_byname(arg);
+ if (num >= 0)
+ command_list[num].handler();
+ }
+ free(tmp);
+ return;
}
/* not found, check internal key bindings */
for (i = 0; command_list[i].handler; i++) {
km_keyname(c));
}
+__noreturn static void print_help_and_die(void)
+{
+ struct ggo_help h = DEFINE_GGO_HELP(gui);
+ bool d = conf.detailed_help_given;
+
+ ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
+ exit(0);
+}
+
int main(int argc, char *argv[])
{
int ret;
_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);
- }
+ gui_cmdline_parser(argc, argv, &conf); /* exits on errors */
+ loglevel = get_loglevel_by_name(conf.loglevel_arg);
+ version_handle_flag("gui", conf.version_given);
+ if (conf.help_given || conf.detailed_help_given)
+ print_help_and_die();
cf = configfile_exists();
if (!cf && conf.config_file_given) {
fprintf(stderr, "can not read config file %s\n",
.override = 0,
.initialize = 0,
.check_required = 0,
- .check_ambiguity = 0
+ .check_ambiguity = 0,
+ .print_errors = 1,
};
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");
+ fprintf(stderr, "invalid key map\n");
exit(EXIT_FAILURE);
}
+ init_theme_or_die(conf.theme_arg, &theme);
+ top.lines = theme.top_lines_default;
setup_signal_handling();
bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
+ setlocale(LC_CTYPE, "");
initscr(); /* needed only once, always successful */
init_curses();
print_welcome();