+static inline int get_user_key_map_idx(int key)
+{
+ assert(!is_internal_key(key));
+ return key - NUM_INTERNALLY_MAPPED_KEYS;
+}
+
+static inline int get_key_map_idx(int key)
+{
+ return is_internal_key(key)?
+ get_internal_key_map_idx(key) : get_user_key_map_idx(key);
+}
+
+static inline char *get_user_key_map_arg(int key)
+{
+ return conf.key_map_arg[get_user_key_map_idx(key)];
+}
+
+static inline char *get_internal_key_map_seq(int key)
+{
+ return para_strdup(default_keyseqs[get_internal_key_map_idx(key)]);
+}
+
+static char *get_user_key_map_seq(int key)
+{
+ const char *kma = get_user_key_map_arg(key);
+ const char *p = strchr(kma + 1, ':');
+ char *result;
+ int len;
+
+ if (!p)
+ return NULL;
+ len = p - kma;
+ result = para_malloc(len + 1);
+ memcpy(result, kma, len);
+ result[len] = '\0';
+ return result;
+}
+
+static char *get_key_map_seq(int key)
+{
+ return is_internal_key(key)?
+ get_internal_key_map_seq(key) : get_user_key_map_seq(key);
+}
+
+static inline char *get_internal_key_map_cmd(int key)
+{
+ return para_strdup(default_commands[get_internal_key_map_idx(key)]);
+}
+
+static char *get_user_key_map_cmd(int key)
+{
+ const char *kma = get_user_key_map_arg(key);
+ const char *p = strchr(kma + 1, ':');
+
+ if (!p)
+ return NULL;
+ return para_strdup(p + 1);
+}
+
+static char *get_key_map_cmd(int key)
+{
+ return is_internal_key(key)?
+ get_internal_key_map_cmd(key) : get_user_key_map_cmd(key);
+}
+
+static char **get_mapped_keyseqs(void)
+{
+ char **result;
+ int i;
+
+ result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *));
+ FOR_EACH_MAPPED_KEY(i) {
+ int idx = get_key_map_idx(i);
+ char *seq = get_key_map_seq(i);
+ char *cmd = get_key_map_cmd(i);
+ bool internal = is_internal_key(i);
+ PARA_DEBUG_LOG("%s key sequence #%d: %s -> %s\n",
+ internal? "internal" : "user-defined",
+ idx, seq, cmd);
+ result[i] = seq;
+ free(cmd);
+ }
+ result[i] = NULL;
+ return result;
+}
+
+/* defines one command of para_play */
+struct pp_command {
+ const char *name;
+ int (*handler)(struct play_task *, int, char**);
+ const char *description;
+ const char *usage;
+ const char *help;
+};
+
+#include "play.command_list.h"
+static struct pp_command pp_cmds[] = {DEFINE_PLAY_CMD_ARRAY};
+#define FOR_EACH_COMMAND(c) for (c = 0; pp_cmds[c].name; c++)
+
+#include "play.completion.h"
+static struct i9e_completer pp_completers[];
+
+I9E_DUMMY_COMPLETER(jmp);
+I9E_DUMMY_COMPLETER(next);
+I9E_DUMMY_COMPLETER(prev);
+I9E_DUMMY_COMPLETER(fg);
+I9E_DUMMY_COMPLETER(bg);
+I9E_DUMMY_COMPLETER(ls);
+I9E_DUMMY_COMPLETER(info);
+I9E_DUMMY_COMPLETER(play);
+I9E_DUMMY_COMPLETER(pause);
+I9E_DUMMY_COMPLETER(stop);
+I9E_DUMMY_COMPLETER(tasks);
+I9E_DUMMY_COMPLETER(quit);
+I9E_DUMMY_COMPLETER(ff);
+
+static void help_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *result)
+{
+ result->matches = i9e_complete_commands(ci->word, pp_completers);
+}
+
+static struct i9e_completer pp_completers[] = {PLAY_COMPLETERS {.name = NULL}};
+
+static void attach_stdout(struct play_task *pt, const char *name)
+{
+ if (pt->btrn)
+ return;
+ pt->btrn = btr_new_node(&(struct btr_node_description)
+ EMBRACE(.name = name));
+ i9e_attach_to_stdout(pt->btrn);
+}
+
+static void detach_stdout(struct play_task *pt)
+{
+ btr_remove_node(&pt->btrn);
+}
+
+static int com_quit(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ pt->rq = CRT_TERM_RQ;
+ return 0;
+}
+
+static int com_help(struct play_task *pt, int argc, char **argv)
+{
+ int i;
+ char *buf;
+ size_t sz;
+
+ if (argc > 2)
+ return -E_PLAY_SYNTAX;
+ if (argc < 2) {
+ if (pt->background)
+ FOR_EACH_COMMAND(i) {
+ sz = xasprintf(&buf, "%s\t%s\n", pp_cmds[i].name,
+ pp_cmds[i].description);
+ btr_add_output(buf, sz, pt->btrn);
+ }
+ else {
+ FOR_EACH_MAPPED_KEY(i) {
+ bool internal = is_internal_key(i);
+ int idx = get_key_map_idx(i);
+ char *seq = get_key_map_seq(i);
+ char *cmd = get_key_map_cmd(i);
+ sz = xasprintf(&buf,
+ "%s key #%d: %s -> %s\n",
+ internal? "internal" : "user-defined",
+ idx, seq, cmd);
+ btr_add_output(buf, sz, pt->btrn);
+ free(seq);
+ free(cmd);
+ }
+ }
+ return 0;
+ }
+ FOR_EACH_COMMAND(i) {
+ if (strcmp(pp_cmds[i].name, argv[1]))
+ continue;
+ sz = xasprintf(&buf,
+ "NAME\n\t%s -- %s\n"
+ "SYNOPSIS\n\t%s\n"
+ "DESCRIPTION\n%s\n",
+ argv[1],
+ pp_cmds[i].description,
+ pp_cmds[i].usage,
+ pp_cmds[i].help
+ );
+ btr_add_output(buf, sz, pt->btrn);
+ return 0;
+ }
+ return -E_BAD_PLAY_CMD;
+}
+
+static int com_info(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ char *buf;
+ size_t sz;
+ static char dflt[] = "[no information available]";
+
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n",
+ pt->current_file, conf.inputs[pt->current_file]);
+ btr_add_output(buf, sz, pt->btrn);
+ buf = pt->afhi_txt? pt->afhi_txt : dflt;
+ btr_add_output_dont_free(buf, strlen(buf), pt->btrn);
+ return 0;
+}
+
+static void list_file(struct play_task *pt, int num)
+{
+ char *buf;
+ size_t sz;
+
+ sz = xasprintf(&buf, "%s %4u %s\n", num == pt->current_file?
+ "*" : " ", num, conf.inputs[num]);
+ btr_add_output(buf, sz, pt->btrn);
+}
+
+static int com_tasks(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ static char state;
+ char *buf;
+ size_t sz;
+
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+
+ buf = get_task_list(&sched);
+ btr_add_output(buf, strlen(buf), pt->btrn);
+ state = get_playback_state(pt);
+ sz = xasprintf(&buf, "state: %c\n", state);
+ btr_add_output(buf, sz, pt->btrn);
+ return 0;
+}
+
+static int com_ls(struct play_task *pt, int argc, char **argv)
+{
+ int i, j, ret;
+
+ if (argc == 1) {
+ FOR_EACH_PLAYLIST_FILE(i)
+ list_file(pt, i);
+ return 0;
+ }
+ for (j = 1; j < argc; j++) {
+ FOR_EACH_PLAYLIST_FILE(i) {
+ ret = fnmatch(argv[j], conf.inputs[i], 0);
+ if (ret == 0) /* match */
+ list_file(pt, i);