+#include "play.command_list.h"
+
+typedef int play_command_handler_t(struct play_task *, int, char**);
+static play_command_handler_t PLAY_COMMAND_HANDLERS;
+
+/* defines one command of para_play */
+struct pp_command {
+ const char *name;
+ play_command_handler_t *handler;
+ const char *description;
+ const char *usage;
+ const char *help;
+};
+
+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_safe(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 %4d %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);
+ }
+ }
+ return 0;
+}
+
+static int com_play(struct play_task *pt, int argc, char **argv)
+{
+ int32_t x;
+ int ret;
+ char state;
+
+ if (argc > 2)
+ return -E_PLAY_SYNTAX;
+ state = get_playback_state(pt);
+ if (argc == 1) {
+ if (state == 'P')
+ return 0;
+ pt->next_file = pt->current_file;
+ pt->rq = CRT_REPOS;
+ pt->playing = true;
+ return 0;
+ }
+ ret = para_atoi32(argv[1], &x);
+ if (ret < 0)
+ return ret;
+ if (x < 0 || x >= conf.inputs_num)
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+ kill_stream(pt);
+ pt->next_file = x;
+ pt->rq = CRT_FILE_CHANGE;
+ return 0;
+}
+
+static int com_pause(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ char state;
+ long unsigned seconds, ss;
+
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ state = get_playback_state(pt);
+ pt->playing = false;
+ if (state != 'P')
+ return 0;
+ seconds = get_play_time(pt);
+ pt->playing = false;
+ ss = 0;
+ if (pt->seconds > 0)
+ ss = seconds * pt->num_chunks / pt->seconds + 1;
+ ss = PARA_MAX(ss, 0UL);
+ ss = PARA_MIN(ss, pt->num_chunks);
+ pt->start_chunk = ss;
+ kill_stream(pt);
+ return 0;
+}
+
+static int com_prev(struct play_task *pt, int argc, __a_unused char **argv)
+
+{
+ int ret;
+
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ ret = previous_valid_file(pt);
+ if (ret < 0)
+ return ret;
+ kill_stream(pt);
+ pt->next_file = ret;
+ pt->rq = CRT_FILE_CHANGE;
+ pt->start_chunk = 0;
+ return 0;
+}
+
+static int com_next(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ int ret;
+
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ ret = next_valid_file(pt);
+ if (ret < 0)
+ return ret;
+ kill_stream(pt);
+ pt->next_file = ret;
+ pt->rq = CRT_FILE_CHANGE;
+ pt->start_chunk = 0;
+ return 0;
+}
+
+static int com_fg(struct play_task *pt, int argc, __a_unused char **argv)
+{
+ if (argc != 1)
+ return -E_PLAY_SYNTAX;
+ pt->background = false;
+ return 0;
+}
+
+static int com_bg(struct play_task *pt, int argc, __a_unused char **argv)