+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);
+ }
+ }
+ 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);