-/*
- * Copyright (C) 1998-2014 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
/** \file gui.c Curses-based interface for paraslash. */
#include <curses.h>
#include <locale.h>
#include <sys/time.h>
+#include <lopsub.h>
-#include "gui.cmdline.h"
+#include "gui.lsg.h"
#include "para.h"
#include "gui.h"
#include "string.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;
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+static struct lls_parse_result *cmdline_lpr, *lpr;
+
+#define CMD_PTR (lls_cmd(0, gui_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_GUI_PARA_GUI_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+#define FOR_EACH_KEY_MAP(_i) for (_i = 0; _i < OPT_GIVEN(KEY_MAP); _i++)
+
static char *stat_content[NUM_STAT_ITEMS];
static struct gui_window {
static struct ringbuffer *bot_win_rb;
static unsigned scroll_position;
-
static pid_t exec_pid;
-
static int exec_fds[2] = {-1, -1};
-static struct gui_args_info conf;
static int loglevel;
/** Type of the process currently being executed. */
unsigned flags[2]; /* passed to for_each_line() */
};
-static int find_cmd_byname(char *name)
+static int find_cmd_byname(const char *name)
{
int i;
}
/* Print given number of spaces to curses window. */
-static void add_spaces(WINDOW* win, unsigned int num)
+static void add_spaces(WINDOW *win, unsigned int num)
{
- char space[] = " ";
- unsigned sz = sizeof(space) - 1; /* number of spaces */
+ const char space[] = " ";
+ const unsigned sz = sizeof(space) - 1; /* number of spaces */
while (num >= sz) {
waddstr(win, space);
}
if (num > 0) {
assert(num < sz);
- space[num] = '\0';
- waddstr(win, space);
+ waddstr(win, space + sz - num);
}
}
/*
- * print aligned string to curses window. This function always prints
+ * Print aligned string to curses window. This function always prints
* exactly len chars.
*/
-static int align_str(WINDOW* win, char *str, unsigned int len,
+static int align_str(WINDOW *win, const char *str, unsigned int len,
unsigned int align)
{
- int ret, i, num; /* of spaces */
+ int ret, num; /* of spaces */
size_t width;
+ char *sstr; /* sanitized string */
if (!win || !str)
return 0;
- ret = strwidth(str, &width);
+ ret = sanitize_str(str, len, &sstr, &width);
if (ret < 0) {
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
width = 0;
- str[0] = '\0';
+ sstr = para_strdup(NULL);
}
+ assert(width <= len);
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);
+ waddstr(win, sstr);
add_spaces(win, num);
} else if (align == RIGHT) {
add_spaces(win, num);
- waddstr(win, str);
+ waddstr(win, sstr);
} else {
add_spaces(win, num / 2);
- waddstr(win, str[0]? str: "");
+ waddstr(win, sstr);
add_spaces(win, num - num / 2);
}
+ free(sstr);
return 1;
}
waddstr(bot.win, msg);
}
-/*
- * print formated output to bot win and refresh
- */
-__printf_2_3 static void outputf(int color, const char* fmt,...)
+/* Print formatted output to bot win and refresh. */
+__printf_2_3 static void outputf(int color, const char *fmt,...)
{
char *msg;
va_list ap;
vfprintf(stderr, fmt, ap);
va_end(ap);
}
+
/** The log function of para_gui, always set to curses_log(). */
-__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
+__printf_2_3 void (*para_log)(int, const char *, ...) = curses_log;
+/* Call endwin() to reset the terminal into non-visual mode. */
static void shutdown_curses(void)
{
- def_prog_mode();
+ /*
+ * If para_gui received a terminating signal in external mode, the
+ * terminal can be in an unusable state at this point because the child
+ * process might not have caught the signal. In this case endwin() has
+ * already been called and must not be called again. So we first return
+ * to program mode, then call endwin().
+ */
+ if (!curses_active())
+ reset_prog_mode();
endwin();
}
-/* disable curses, print a message, kill running processes and exit */
-__noreturn __printf_2_3 static void die(int exit_code, const char* fmt, ...)
+/* Disable curses, print a message, kill running processes and exit. */
+__noreturn __printf_2_3 static void die(int exit_code, const char *fmt, ...)
{
va_list argp;
+ /* Kill every process in our process group. */
+ para_sigaction(SIGTERM, SIG_IGN);
+ kill(0, SIGTERM);
+ /* Wait up to two seconds for child processes to die. */
+ alarm(2);
+ while (waitpid(0, NULL, 0) >= 0)
+ ; /* nothing */
+ alarm(0);
+ /* mousemask() exists only in ncurses */
+#ifdef NCURSES_MOUSE_VERSION
+ mousemask(~(mmask_t)0, NULL); /* Avoid bad terminal state with xterm. */
+#endif
shutdown_curses();
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
- /* kill every process in the process group and exit */
- para_sigaction(SIGTERM, SIG_IGN);
- kill(0, SIGTERM);
exit(exit_code);
}
-/*
- * Print stat item #i to curses window
- */
+/* Print stat item #i to curses window. */
static void print_stat_item(int i)
{
char *tmp;
if (buf && buf[0])
goto dup;
switch (item_num) {
- case SI_ARTIST:
+ case SI_artist:
*c = para_strdup("(artist tag not set)");
goto print;
- case SI_TITLE:
+ case SI_title:
*c = para_strdup("(title tag not set)");
goto print;
- case SI_YEAR:
+ case SI_year:
*c = para_strdup("????");
goto print;
- case SI_ALBUM:
+ case SI_album:
*c = para_strdup("(album tag not set)");
goto print;
- case SI_COMMENT:
+ case SI_comment:
*c = para_strdup("(comment tag not set)");
goto print;
}
if (tv_diff(&st->next_exec, now, NULL) > 0)
return 0;
st->next_exec.tv_sec = now->tv_sec + 2;
- ret = para_exec_cmdline_pid(&st->pid, conf.stat_cmd_arg, fds);
+ ret = para_exec_cmdline_pid(&st->pid,
+ OPT_STRING_VAL(STAT_CMD), fds);
if (ret < 0)
return 0;
ret = mark_fd_nonblocking(fds[1]);
ret2 = for_each_stat_item(st->buf, st->loaded, update_item);
if (ret < 0 || ret2 < 0) {
st->loaded = 0;
- PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret));
+ PARA_NOTICE_LOG("closing stat pipe: %s\n",
+ para_strerror(ret < 0? -ret : -ret2));
close(st->fd);
st->fd = -1;
clear_all_items();
- free(stat_content[SI_BASENAME]);
- stat_content[SI_BASENAME] =
+ free(stat_content[SI_basename]);
+ stat_content[SI_basename] =
para_strdup("stat command terminated!?");
print_all_items();
return 0;
return 0;
}
-/*
- * init all windows
- */
+/* Initialize all windows. */
static void init_wins(int top_lines)
{
int top_y = 0, bot_y = top_lines + 1, sb_y = LINES - 2,
{
if (curses_active())
return;
- if (top.win && refresh() == ERR) /* refresh is really needed */
+ if (refresh() == ERR) /* refresh is really needed */
die(EXIT_FAILURE, "refresh() failed\n");
if (LINES < theme.lines_min || COLS < theme.cols_min)
die(EXIT_FAILURE, "Terminal (%dx%d) too small"
{
int i;
char *tmp = NULL;
+ const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
- for (i = 0; i < conf.key_map_given; ++i) {
+ FOR_EACH_KEY_MAP(i) {
char *handler, *arg;
free(tmp);
- tmp = para_strdup(conf.key_map_arg[i]);
+ tmp = para_strdup(lls_string_val(i, lor));
if (!split_key_map(tmp, &handler, &arg))
break;
if (strlen(handler) != 1)
if (find_cmd_byname(arg) < 0)
break;
}
- if (i != conf.key_map_given)
- die(EXIT_FAILURE, "invalid key map: %s\n", conf.key_map_arg[i]);
+ if (i != OPT_GIVEN(KEY_MAP))
+ die(EXIT_FAILURE, "invalid key map: %s\n",
+ lls_string_val(i, lor));
free(tmp);
}
-static void parse_config_file_or_die(bool override)
+static void parse_config_file_or_die(bool reload)
{
- bool err;
- char *config_file;
- struct gui_cmdline_parser_params params = {
- .override = override,
- .initialize = 0,
- .check_required = !override,
- .check_ambiguity = 0,
- .print_errors = 1,
- };
+ int ret;
+ char *cf = NULL, *errctx = NULL;
+ void *map;
+ size_t sz;
+ int cf_argc;
+ char **cf_argv;
+ struct lls_parse_result *cf_lpr, *merged_lpr;
- if (conf.config_file_given)
- config_file = para_strdup(conf.config_file_arg);
+ if (OPT_GIVEN(CONFIG_FILE))
+ cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
else {
char *home = para_homedir();
- config_file = make_message("%s/.paraslash/gui.conf", home);
+ cf = make_message("%s/.paraslash/gui.conf", home);
free(home);
}
- if (!file_exists(config_file)) {
- if (!conf.config_file_given)
- err = false;
- else {
- PARA_EMERG_LOG("config file %s does not exist\n",
- config_file);
- err = true;
- }
- goto out;
+ ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+ if (ret < 0) {
+ if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+ goto free_cf;
+ if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
+ goto free_cf;
+ ret = 0;
+ lpr = cmdline_lpr;
+ goto success;
}
- gui_cmdline_parser_config_file(config_file, &conf, ¶ms);
- loglevel = get_loglevel_by_name(conf.loglevel_arg);
+ ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+ para_munmap(map, sz);
+ if (ret < 0)
+ goto free_cf;
+ cf_argc = ret;
+ ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
+ lls_free_argv(cf_argv);
+ if (ret < 0)
+ goto free_cf;
+ if (reload) /* config file overrides command line */
+ ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
+ &errctx));
+ else /* command line options override config file options */
+ ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
+ &errctx));
+ lls_free_parse_result(cf_lpr, CMD_PTR);
+ if (ret < 0)
+ goto free_cf;
+ if (lpr != cmdline_lpr)
+ lls_free_parse_result(lpr, CMD_PTR);
+ lpr = merged_lpr;
+success:
+ loglevel = OPT_UINT32_VAL(LOGLEVEL);
check_key_map_args_or_die();
- err = false;
-out:
- free(config_file);
- if (err)
+ theme_init(OPT_STRING_VAL(THEME), &theme);
+free_cf:
+ free(cf);
+ if (ret < 0) {
+ if (errctx)
+ PARA_ERROR_LOG("%s\n", errctx);
+ free(errctx);
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
exit(EXIT_FAILURE);
- theme_init(conf.theme_arg, &theme);
+ }
}
-/* reread configuration, terminate on errors */
+/* Reread configuration, terminate on errors. */
static void reread_conf(void)
{
/*
- * gengetopt might print to stderr and exit on errors. So we have to
- * shutdown curses first.
+ * If the reload of the config file fails, we are about to exit. In
+ * this case we print the error message to stderr rather than to the
+ * curses window. So we have to shutdown curses first.
*/
shutdown_curses();
- parse_config_file_or_die(true /* override */);
+ parse_config_file_or_die(true);
init_curses();
print_in_bar(COLOR_MSG, "config file reloaded\n");
}
-/*
- * React to various signal-related events
- */
+/* React to various signal-related events. */
static int signal_post_select(struct sched *s, __a_unused void *context)
{
int ret = para_next_signal(&s->rfds);
switch (ret) {
case SIGTERM:
die(EXIT_FAILURE, "only the good die young (caught SIGTERM)\n");
- return 1;
case SIGINT:
- PARA_WARNING_LOG("caught SIGINT, reset\n");
- /* Nothing to do. SIGINT killed our child which gets noticed
- * by do_select and resets everything.
- */
return 1;
case SIGUSR1:
PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
sched_min_delay(s);
}
-/* read from command pipe and print data to bot window */
+/* Read from command pipe and print data to bot window. */
static void exec_and_display(const char *file_and_args)
{
int ret, fds[3] = {0, 1, 1};
free(file_and_args);
}
-/*
- * shutdown curses and stat pipe before executing external commands
- */
+/* Shutdown curses and stat pipe before executing external commands. */
static void exec_external(char *file_and_args)
{
int fds[3] = {-1, -1, -1};
static void handle_command(int c)
{
int i;
+ const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
/* first check user-defined key bindings */
- for (i = 0; i < conf.key_map_given; ++i) {
+ FOR_EACH_KEY_MAP(i) {
char *tmp, *handler, *arg;
- tmp = para_strdup(conf.key_map_arg[i]);
+ tmp = para_strdup(lls_string_val(i, lor));
if (!split_key_map(tmp, &handler, &arg)) {
free(tmp);
return;
km_keyname(c));
}
-static int input_post_select(__a_unused struct sched *s, __a_unused void *context)
+static int input_post_select(__a_unused struct sched *s,
+ __a_unused void *context)
{
int ret;
enum exec_status exs = exec_status();
return 0;
}
-static void signal_pre_select(struct sched *s, void *context)
-{
- struct signal_task *st = context;
- para_fd_set(st->fd, &s->rfds, &s->max_fileno);
-}
-
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,
+ print_in_bar(COLOR_MSG, "scrolled view: %u-%u/%u\n", filled - first_rbe,
filled - scroll_position, ringbuffer_filled(bot_win_rb));
}
static void com_help(void)
{
int i;
+ const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
- for (i = 0; i < conf.key_map_given; ++i) {
- char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]);
+ FOR_EACH_KEY_MAP(i) {
+ char *handler, *arg, *tmp = para_strdup(lls_string_val(i, lor));
const char *handler_text = "???", *desc = NULL;
if (!split_key_map(tmp, &handler, &arg)) {
com_refresh();
}
-__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);
-}
-
static int setup_tasks_and_schedule(void)
{
+ int ret;
struct exec_task exec_task = {.task = NULL};
struct status_task status_task = {.fd = -1};
struct input_task input_task = {.task = NULL};
- struct signal_task signal_task = {.task = NULL};
- struct sched sched = {
- .default_timeout = {
- .tv_sec = conf.timeout_arg / 1000,
- .tv_usec = (conf.timeout_arg % 1000) * 1000,
- },
- };
+ struct signal_task *signal_task;
+ struct sched sched = {.default_timeout = {.tv_sec = 1}};
exec_task.task = task_register(&(struct task_info) {
.name = "exec",
.context = &input_task,
}, &sched);
- signal_task.fd = para_signal_init();
+ signal_task = signal_init_or_die();
para_install_sighandler(SIGINT);
para_install_sighandler(SIGTERM);
para_install_sighandler(SIGCHLD);
para_install_sighandler(SIGUSR1);
- signal_task.task = task_register(&(struct task_info) {
+ signal_task->task = task_register(&(struct task_info) {
.name = "signal",
.pre_select = signal_pre_select,
.post_select = signal_post_select,
- .context = &signal_task,
+ .context = signal_task,
}, &sched);
- return schedule(&sched);
+ ret = schedule(&sched);
+ sched_shutdown(&sched);
+ signal_shutdown(signal_task);
+ return ret;
+}
+
+static void handle_help_flags(void)
+{
+ char *help;
+
+ if (OPT_GIVEN(DETAILED_HELP))
+ help = lls_long_help(CMD_PTR);
+ else if (OPT_GIVEN(HELP))
+ help = lls_short_help(CMD_PTR);
+ else
+ return;
+ printf("%s\n", help);
+ free(help);
+ exit(EXIT_SUCCESS);
}
/**
* The exec task is responsible for printing the output of the currently
* running executable to the bottom window.
*
- * The signal task performs suitable actions according to any signals received.
- * For example it refreshes all windows on terminal size changes and resets the
- * terminal on \p SIGTERM.
+ * The signal task performs various actions according to signals received. For
+ * example, it reloads the configuration file on SIGUSR1, and it shuts down the
+ * curses system on SIGTERM to restore the terminal settings before exit.
*
* The input task reads single key strokes from stdin. For each key pressed, it
* executes the command handler associated with this key.
*/
int main(int argc, char *argv[])
{
- 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();
- parse_config_file_or_die(false /* override */);
+ int ret;
+ char *errctx;
+
+ ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx));
+ if (ret < 0)
+ goto out;
+ lpr = cmdline_lpr;
+ loglevel = OPT_UINT32_VAL(LOGLEVEL);
+ version_handle_flag("gui", OPT_GIVEN(VERSION));
+ handle_help_flags();
+ parse_config_file_or_die(false);
bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
setlocale(LC_CTYPE, "");
initscr(); /* needed only once, always successful */
init_curses();
- return setup_tasks_and_schedule() < 0? EXIT_FAILURE : EXIT_SUCCESS;
+ ret = setup_tasks_and_schedule();
+out:
+ lls_free_parse_result(lpr, CMD_PTR);
+ if (lpr != cmdline_lpr)
+ lls_free_parse_result(cmdline_lpr, CMD_PTR);
+ if (ret < 0) {
+ if (errctx)
+ PARA_ERROR_LOG("%s\n", errctx);
+ free(errctx);
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ }
+ return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
}