3193 lines
66 KiB
C
3193 lines
66 KiB
C
/*
|
|
* Copyright 2008-2013 Various Authors
|
|
* Copyright 2004-2005 Timo Hirvonen
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "command_mode.h"
|
|
#include "search_mode.h"
|
|
#include "cmdline.h"
|
|
#include "options.h"
|
|
#include "ui_curses.h"
|
|
#include "history.h"
|
|
#include "tabexp.h"
|
|
#include "tabexp_file.h"
|
|
#include "browser.h"
|
|
#include "filters.h"
|
|
#include "player.h"
|
|
#include "output.h"
|
|
#include "editable.h"
|
|
#include "lib.h"
|
|
#include "pl.h"
|
|
#include "play_queue.h"
|
|
#include "cmus.h"
|
|
#include "worker.h"
|
|
#include "keys.h"
|
|
#include "xmalloc.h"
|
|
#include "xstrjoin.h"
|
|
#include "misc.h"
|
|
#include "path.h"
|
|
#include "spawn.h"
|
|
#include "utils.h"
|
|
#include "list.h"
|
|
#include "debug.h"
|
|
#include "load_dir.h"
|
|
#include "help.h"
|
|
#include "op.h"
|
|
#include "mpris.h"
|
|
#include "job.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <dirent.h>
|
|
|
|
static struct history cmd_history;
|
|
static char *cmd_history_filename;
|
|
static char *history_search_text = NULL;
|
|
static int arg_expand_cmd = -1;
|
|
static int mute_vol_l = 0, mute_vol_r = 0;
|
|
|
|
/* view {{{ */
|
|
|
|
void view_clear(int view)
|
|
{
|
|
switch (view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
worker_remove_jobs_by_type(JOB_TYPE_LIB);
|
|
editable_clear(&lib_editable);
|
|
|
|
/* FIXME: make this optional? */
|
|
lib_clear_store();
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_clear();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
worker_remove_jobs_by_type(JOB_TYPE_QUEUE);
|
|
editable_clear(&pq_editable);
|
|
break;
|
|
default:
|
|
info_msg(":clear only works in views 1-4");
|
|
}
|
|
}
|
|
|
|
void view_add(int view, char *arg, int prepend)
|
|
{
|
|
char *tmp, *name;
|
|
enum file_type ft;
|
|
|
|
tmp = expand_filename(arg);
|
|
ft = cmus_detect_ft(tmp, &name);
|
|
if (ft == FILE_TYPE_INVALID) {
|
|
error_msg("adding '%s': %s", tmp, strerror(errno));
|
|
free(tmp);
|
|
return;
|
|
}
|
|
free(tmp);
|
|
|
|
switch (view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB, 0, NULL);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_add_file_to_marked_pl(name);
|
|
break;
|
|
case QUEUE_VIEW:
|
|
if (prepend) {
|
|
cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE,
|
|
0, NULL);
|
|
} else {
|
|
cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE, 0,
|
|
NULL);
|
|
}
|
|
break;
|
|
default:
|
|
info_msg(":add only works in views 1-4");
|
|
}
|
|
free(name);
|
|
}
|
|
|
|
static char *view_load_prepare(char *arg)
|
|
{
|
|
char *name, *tmp = expand_filename(arg);
|
|
enum file_type ft = cmus_detect_ft(tmp, &name);
|
|
if (ft == FILE_TYPE_INVALID) {
|
|
error_msg("loading '%s': %s", tmp, strerror(errno));
|
|
free(tmp);
|
|
return NULL;
|
|
}
|
|
free(tmp);
|
|
|
|
if (ft == FILE_TYPE_FILE)
|
|
ft = FILE_TYPE_PL;
|
|
if (ft != FILE_TYPE_PL) {
|
|
error_msg("loading '%s': not a playlist file", name);
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
void view_load(int view, char *arg)
|
|
{
|
|
char *name = view_load_prepare(arg);
|
|
if (!name)
|
|
return;
|
|
|
|
switch (view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
worker_remove_jobs_by_type(JOB_TYPE_LIB);
|
|
editable_clear(&lib_editable);
|
|
cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB, 0,
|
|
NULL);
|
|
free(lib_filename);
|
|
lib_filename = name;
|
|
break;
|
|
default:
|
|
info_msg(":load only works in views 1-2");
|
|
free(name);
|
|
}
|
|
}
|
|
|
|
static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep,
|
|
save_ti_cb save_ti)
|
|
{
|
|
char *filename = *filenamep;
|
|
|
|
if (arg) {
|
|
if (strcmp(arg, "-") == 0) {
|
|
filename = (char *) arg;
|
|
} else {
|
|
free(filename);
|
|
filename = xstrdup(arg);
|
|
*filenamep = filename;
|
|
}
|
|
} else if (!filename) {
|
|
error_msg("need a file as argument, no default stored yet");
|
|
return;
|
|
}
|
|
|
|
if (save_ti(for_each_ti, filename, NULL) == -1)
|
|
error_msg("saving '%s': %s", filename, strerror(errno));
|
|
}
|
|
|
|
void view_save(int view, char *arg, int to_stdout, int filtered, int extended)
|
|
{
|
|
char **dest;
|
|
save_ti_cb save_ti = extended ? cmus_save_ext : cmus_save;
|
|
for_each_ti_cb lib_for_each_ti = filtered ? lib_for_each_filtered : lib_for_each;
|
|
|
|
if (arg) {
|
|
if (to_stdout) {
|
|
arg = xstrdup(arg);
|
|
} else {
|
|
char *tmp = expand_filename(arg);
|
|
arg = path_absolute(tmp);
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
switch (view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
if (worker_has_job_by_type(JOB_TYPE_LIB))
|
|
goto worker_running;
|
|
dest = extended ? &lib_ext_filename : &lib_filename;
|
|
do_save(lib_for_each_ti, arg, dest, save_ti);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
if (arg)
|
|
pl_export_selected_pl(arg);
|
|
else
|
|
pl_save();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
if (worker_has_job_by_type(JOB_TYPE_QUEUE))
|
|
goto worker_running;
|
|
dest = extended ? &play_queue_ext_filename : &play_queue_filename;
|
|
do_save(play_queue_for_each, arg, dest, save_ti);
|
|
break;
|
|
default:
|
|
info_msg(":save only works in views 1 - 4");
|
|
}
|
|
free(arg);
|
|
return;
|
|
worker_running:
|
|
error_msg("can't save when tracks are being added");
|
|
free(arg);
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* if only_last != 0, only return the last flag */
|
|
static int do_parse_flags(const char **strp, const char *flags, int only_last)
|
|
{
|
|
const char *str = *strp;
|
|
int flag = 0;
|
|
|
|
if (str == NULL)
|
|
return flag;
|
|
|
|
while (*str && (only_last || !flag)) {
|
|
if (*str != '-')
|
|
break;
|
|
|
|
// "-"
|
|
if (str[1] == 0)
|
|
break;
|
|
|
|
// "--" or "-- "
|
|
if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
|
|
str += 2;
|
|
break;
|
|
}
|
|
|
|
// not "-?" or "-? "
|
|
if (str[2] && str[2] != ' ')
|
|
break;
|
|
|
|
flag = str[1];
|
|
if (!strchr(flags, flag)) {
|
|
error_msg("invalid option -%c", flag);
|
|
return -1;
|
|
}
|
|
|
|
str += 2;
|
|
|
|
while (*str == ' ')
|
|
str++;
|
|
}
|
|
while (*str == ' ')
|
|
str++;
|
|
if (*str == 0)
|
|
str = NULL;
|
|
*strp = str;
|
|
return flag;
|
|
}
|
|
|
|
static int parse_flags(const char **strp, const char *flags)
|
|
{
|
|
return do_parse_flags(strp, flags, 1);
|
|
}
|
|
|
|
static int parse_one_flag(const char **strp, const char *flags)
|
|
{
|
|
return do_parse_flags(strp, flags, 0);
|
|
}
|
|
|
|
/* is str == "...-", but not "...-- -" ? copied from do_parse_flags() */
|
|
static int is_stdout_filename(const char *str)
|
|
{
|
|
if (!str)
|
|
return 0;
|
|
|
|
while (*str) {
|
|
if (*str != '-')
|
|
return 0;
|
|
// "-"
|
|
if (str[1] == 0)
|
|
return 1;
|
|
// "--" or "-- "
|
|
if (str[1] == '-' && (str[2] == 0 || str[2] == ' '))
|
|
return 0;
|
|
// not "-?" or "-? "
|
|
if (str[2] && str[2] != ' ')
|
|
return 0;
|
|
str += 2;
|
|
while (*str == ' ')
|
|
str++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flag_to_view(int flag)
|
|
{
|
|
switch (flag) {
|
|
case 'l':
|
|
case 'L':
|
|
return TREE_VIEW;
|
|
case 'p':
|
|
return PLAYLIST_VIEW;
|
|
case 'q':
|
|
case 'Q':
|
|
return QUEUE_VIEW;
|
|
default:
|
|
return cur_view;
|
|
}
|
|
}
|
|
|
|
struct window *current_win(void)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
return lib_cur_win;
|
|
case SORTED_VIEW:
|
|
return lib_editable.shared->win;
|
|
case PLAYLIST_VIEW:
|
|
return pl_cursor_win();
|
|
case QUEUE_VIEW:
|
|
return pq_editable.shared->win;
|
|
case BROWSER_VIEW:
|
|
return browser_win;
|
|
case HELP_VIEW:
|
|
return help_win;
|
|
case FILTERS_VIEW:
|
|
default:
|
|
return filters_win;
|
|
}
|
|
}
|
|
|
|
static void cmd_add(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "lpqQ");
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (arg == NULL) {
|
|
error_msg("not enough arguments\n");
|
|
return;
|
|
}
|
|
view_add(flag_to_view(flag), arg, flag == 'Q');
|
|
}
|
|
|
|
static void cmd_clear(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "lpq");
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (arg) {
|
|
error_msg("too many arguments\n");
|
|
return;
|
|
}
|
|
view_clear(flag_to_view(flag));
|
|
}
|
|
|
|
static void cmd_load(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "l");
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (arg == NULL) {
|
|
error_msg("not enough arguments\n");
|
|
return;
|
|
}
|
|
view_load(flag_to_view(flag), arg);
|
|
}
|
|
|
|
static void cmd_save(char *arg)
|
|
{
|
|
int to_stdout = is_stdout_filename(arg);
|
|
int flag = 0, f, extended = 0;
|
|
|
|
do {
|
|
f = parse_one_flag((const char **)&arg, "eLlpq");
|
|
if (f == 'e')
|
|
extended = 1;
|
|
else if (f)
|
|
flag = f;
|
|
} while (f > 0);
|
|
|
|
if (flag == -1)
|
|
return;
|
|
view_save(flag_to_view(flag), arg, to_stdout, flag == 'L', extended);
|
|
}
|
|
|
|
static void cmd_set(char *arg)
|
|
{
|
|
char *value = NULL;
|
|
int i;
|
|
|
|
for (i = 0; arg[i]; i++) {
|
|
if (arg[i] == '=') {
|
|
arg[i] = 0;
|
|
value = &arg[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
if (value) {
|
|
option_set(arg, value);
|
|
help_win->changed = 1;
|
|
if (cur_view == TREE_VIEW) {
|
|
lib_track_win->changed = 1;
|
|
lib_tree_win->changed = 1;
|
|
} else if (cur_view == PLAYLIST_VIEW) {
|
|
pl_mark_for_redraw();
|
|
} else {
|
|
current_win()->changed = 1;
|
|
}
|
|
update_titleline();
|
|
update_statusline();
|
|
} else {
|
|
struct cmus_opt *opt;
|
|
char buf[OPTION_MAX_SIZE];
|
|
|
|
/* support "set <option>?" */
|
|
i--;
|
|
if (arg[i] == '?')
|
|
arg[i] = 0;
|
|
|
|
opt = option_find(arg);
|
|
if (opt) {
|
|
opt->get(opt->data, buf, OPTION_MAX_SIZE);
|
|
info_msg("setting: '%s=%s'", arg, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmd_toggle(char *arg)
|
|
{
|
|
struct cmus_opt *opt = option_find(arg);
|
|
|
|
if (opt == NULL)
|
|
return;
|
|
|
|
if (opt->toggle == NULL) {
|
|
error_msg("%s is not toggle option", opt->name);
|
|
return;
|
|
}
|
|
opt->toggle(opt->data);
|
|
help_win->changed = 1;
|
|
if (cur_view == TREE_VIEW) {
|
|
lib_track_win->changed = 1;
|
|
lib_tree_win->changed = 1;
|
|
} else if (cur_view == PLAYLIST_VIEW) {
|
|
pl_mark_for_redraw();
|
|
} else {
|
|
current_win()->changed = 1;
|
|
}
|
|
update_titleline();
|
|
update_statusline();
|
|
}
|
|
|
|
static int get_number(char *str, char **end)
|
|
{
|
|
int val = 0;
|
|
|
|
while (*str >= '0' && *str <= '9') {
|
|
val *= 10;
|
|
val += *str++ - '0';
|
|
}
|
|
*end = str;
|
|
return val;
|
|
}
|
|
|
|
static void cmd_seek(char *arg)
|
|
{
|
|
int relative = 0;
|
|
int seek = 0, sign = 1, count;
|
|
|
|
switch (*arg) {
|
|
case '-':
|
|
sign = -1;
|
|
/* fallthrough */
|
|
case '+':
|
|
relative = 1;
|
|
arg++;
|
|
break;
|
|
}
|
|
|
|
count = 0;
|
|
goto inside;
|
|
|
|
do {
|
|
int num;
|
|
char *end;
|
|
|
|
if (*arg != ':')
|
|
break;
|
|
arg++;
|
|
inside:
|
|
num = get_number(arg, &end);
|
|
if (arg == end)
|
|
break;
|
|
arg = end;
|
|
seek = seek * 60 + num;
|
|
} while (++count < 3);
|
|
|
|
seek *= sign;
|
|
if (!count)
|
|
goto err;
|
|
|
|
if (count == 1) {
|
|
switch (tolower((unsigned char)*arg)) {
|
|
case 'h':
|
|
seek *= 60;
|
|
/* fallthrough */
|
|
case 'm':
|
|
seek *= 60;
|
|
/* fallthrough */
|
|
case 's':
|
|
arg++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!*arg) {
|
|
player_seek(seek, relative, 0);
|
|
return;
|
|
}
|
|
err:
|
|
error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
|
|
}
|
|
|
|
static void cmd_factivate(char *arg)
|
|
{
|
|
filters_activate_names(arg);
|
|
}
|
|
|
|
static void cmd_live_filter(char *arg)
|
|
{
|
|
filters_set_live(arg);
|
|
}
|
|
|
|
static void cmd_filter(char *arg)
|
|
{
|
|
filters_set_anonymous(arg);
|
|
}
|
|
|
|
static void cmd_fset(char *arg)
|
|
{
|
|
filters_set_filter(arg);
|
|
}
|
|
|
|
static void cmd_help(char *arg)
|
|
{
|
|
info_msg("To get started with cmus, read cmus-tutorial(7) and cmus(1) man pages");
|
|
}
|
|
|
|
static void cmd_invert(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case SORTED_VIEW:
|
|
editable_invert_marks(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_invert_marks();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_invert_marks(&pq_editable);
|
|
break;
|
|
default:
|
|
info_msg(":invert only works in views 2-4");
|
|
}
|
|
}
|
|
|
|
static void cmd_mark(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case SORTED_VIEW:
|
|
editable_mark(&lib_editable, arg);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_mark(arg);
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_mark(&pq_editable, arg);
|
|
break;
|
|
default:
|
|
info_msg(":mark only works in views 2-4");
|
|
}
|
|
}
|
|
|
|
static void cmd_unmark(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case SORTED_VIEW:
|
|
editable_unmark(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_unmark();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_unmark(&pq_editable);
|
|
break;
|
|
default:
|
|
info_msg(":unmark only works in views 2-4");
|
|
}
|
|
}
|
|
|
|
static void cmd_update_cache(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "f");
|
|
cmus_update_cache(flag == 'f');
|
|
}
|
|
|
|
static void cmd_cd(char *arg)
|
|
{
|
|
if (arg) {
|
|
char *dir, *absolute;
|
|
|
|
dir = expand_filename(arg);
|
|
absolute = path_absolute(dir);
|
|
if (chdir(dir) == -1) {
|
|
error_msg("could not cd to '%s': %s", dir, strerror(errno));
|
|
} else {
|
|
browser_chdir(absolute);
|
|
}
|
|
free(absolute);
|
|
free(dir);
|
|
} else {
|
|
if (chdir(home_dir) == -1) {
|
|
error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
|
|
} else {
|
|
browser_chdir(home_dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmd_bind(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "f");
|
|
char *key, *func;
|
|
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (arg == NULL)
|
|
goto err;
|
|
|
|
key = strchr(arg, ' ');
|
|
if (key == NULL)
|
|
goto err;
|
|
*key++ = 0;
|
|
while (*key == ' ')
|
|
key++;
|
|
|
|
func = strchr(key, ' ');
|
|
if (func == NULL)
|
|
goto err;
|
|
*func++ = 0;
|
|
while (*func == ' ')
|
|
func++;
|
|
if (*func == 0)
|
|
goto err;
|
|
|
|
key_bind(arg, key, func, flag == 'f');
|
|
if (cur_view == HELP_VIEW)
|
|
window_changed(help_win);
|
|
return;
|
|
err:
|
|
error_msg("expecting 3 arguments (context, key and function)\n");
|
|
}
|
|
|
|
static void cmd_unbind(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "f");
|
|
char *key;
|
|
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (arg == NULL)
|
|
goto err;
|
|
|
|
key = strchr(arg, ' ');
|
|
if (key == NULL)
|
|
goto err;
|
|
*key++ = 0;
|
|
while (*key == ' ')
|
|
key++;
|
|
if (*key == 0)
|
|
goto err;
|
|
|
|
strip_trailing_spaces(key);
|
|
|
|
key_unbind(arg, key, flag == 'f');
|
|
return;
|
|
err:
|
|
error_msg("expecting 2 arguments (context and key)\n");
|
|
}
|
|
|
|
static void cmd_showbind(char *arg)
|
|
{
|
|
char *key;
|
|
|
|
key = strchr(arg, ' ');
|
|
if (key == NULL)
|
|
goto err;
|
|
*key++ = 0;
|
|
while (*key == ' ')
|
|
key++;
|
|
if (*key == 0)
|
|
goto err;
|
|
|
|
strip_trailing_spaces(key);
|
|
|
|
show_binding(arg, key);
|
|
return;
|
|
err:
|
|
error_msg("expecting 2 arguments (context and key)\n");
|
|
}
|
|
|
|
static void cmd_quit(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "i");
|
|
enum ui_query_answer answer;
|
|
if (!worker_has_job_by_type(JOB_TYPE_ANY)) {
|
|
if (flag != 'i' || yes_no_query("Quit cmus? [y/N]") != UI_QUERY_ANSWER_NO)
|
|
cmus_running = 0;
|
|
} else {
|
|
answer = yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]");
|
|
if (answer != UI_QUERY_ANSWER_NO)
|
|
cmus_running = 0;
|
|
}
|
|
}
|
|
|
|
static void cmd_reshuffle(char *arg)
|
|
{
|
|
lib_reshuffle();
|
|
pl_reshuffle();
|
|
}
|
|
|
|
static void cmd_source(char *arg)
|
|
{
|
|
char *filename = expand_filename(arg);
|
|
|
|
if (source_file(filename) == -1)
|
|
error_msg("sourcing %s: %s", filename, strerror(errno));
|
|
free(filename);
|
|
}
|
|
|
|
static void cmd_colorscheme(char *arg)
|
|
{
|
|
char filename[512];
|
|
|
|
snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
|
|
if (source_file(filename) == -1) {
|
|
snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_data_dir, arg);
|
|
if (source_file(filename) == -1)
|
|
error_msg("sourcing %s: %s", filename, strerror(errno));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* \" inside double-quotes becomes "
|
|
* \\ inside double-quotes becomes \
|
|
*/
|
|
static char *parse_quoted(const char **strp)
|
|
{
|
|
const char *str = *strp;
|
|
const char *start;
|
|
char *ret, *dst;
|
|
|
|
str++;
|
|
start = str;
|
|
while (1) {
|
|
int c = *str++;
|
|
|
|
if (c == 0)
|
|
goto error;
|
|
if (c == '"')
|
|
break;
|
|
if (c == '\\') {
|
|
if (*str++ == 0)
|
|
goto error;
|
|
}
|
|
}
|
|
*strp = str;
|
|
ret = xnew(char, str - start);
|
|
str = start;
|
|
dst = ret;
|
|
while (1) {
|
|
int c = *str++;
|
|
|
|
if (c == '"')
|
|
break;
|
|
if (c == '\\') {
|
|
c = *str++;
|
|
if (c != '"' && c != '\\')
|
|
*dst++ = '\\';
|
|
}
|
|
*dst++ = c;
|
|
}
|
|
*dst = 0;
|
|
return ret;
|
|
error:
|
|
error_msg("`\"' expected");
|
|
return NULL;
|
|
}
|
|
|
|
static char *parse_escaped(const char **strp)
|
|
{
|
|
const char *str = *strp;
|
|
const char *start;
|
|
char *ret, *dst;
|
|
|
|
start = str;
|
|
while (1) {
|
|
int c = *str;
|
|
|
|
if (c == 0 || c == ' ' || c == '\'' || c == '"')
|
|
break;
|
|
|
|
str++;
|
|
if (c == '\\') {
|
|
c = *str;
|
|
if (c == 0)
|
|
break;
|
|
str++;
|
|
}
|
|
}
|
|
*strp = str;
|
|
ret = xnew(char, str - start + 1);
|
|
str = start;
|
|
dst = ret;
|
|
while (1) {
|
|
int c = *str;
|
|
|
|
if (c == 0 || c == ' ' || c == '\'' || c == '"')
|
|
break;
|
|
|
|
str++;
|
|
if (c == '\\') {
|
|
c = *str;
|
|
if (c == 0) {
|
|
*dst++ = '\\';
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
*dst++ = c;
|
|
}
|
|
*dst = 0;
|
|
return ret;
|
|
}
|
|
|
|
static char *parse_one(const char **strp)
|
|
{
|
|
const char *str = *strp;
|
|
char *ret = NULL;
|
|
|
|
while (1) {
|
|
char *part = NULL;
|
|
int c = *str;
|
|
|
|
if (!c || c == ' ')
|
|
break;
|
|
if (c == '"') {
|
|
part = parse_quoted(&str);
|
|
if (part == NULL)
|
|
goto error;
|
|
} else if (c == '\'') {
|
|
/* backslashes are normal chars inside single-quotes */
|
|
const char *end;
|
|
|
|
str++;
|
|
end = strchr(str, '\'');
|
|
if (end == NULL)
|
|
goto sq_missing;
|
|
part = xstrndup(str, end - str);
|
|
str = end + 1;
|
|
} else {
|
|
part = parse_escaped(&str);
|
|
}
|
|
|
|
if (ret == NULL) {
|
|
ret = xstrdup(part);
|
|
} else {
|
|
char *tmp = xstrjoin(ret, part);
|
|
free(ret);
|
|
ret = tmp;
|
|
}
|
|
free(part);
|
|
}
|
|
*strp = str;
|
|
return ret;
|
|
sq_missing:
|
|
error_msg("`'' expected");
|
|
error:
|
|
free(ret);
|
|
return NULL;
|
|
}
|
|
|
|
char **parse_cmd(const char *cmd, int *args_idx, int *ac)
|
|
{
|
|
char **av = NULL;
|
|
int nr = 0;
|
|
int alloc = 0;
|
|
|
|
while (*cmd) {
|
|
char *arg;
|
|
|
|
/* there can't be spaces at start of command
|
|
* and there is at least one argument */
|
|
if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
|
|
/* {} is replaced with file arguments */
|
|
if (*args_idx != -1)
|
|
goto only_once_please;
|
|
*args_idx = nr;
|
|
cmd += 2;
|
|
goto skip_spaces;
|
|
} else {
|
|
arg = parse_one(&cmd);
|
|
if (arg == NULL)
|
|
goto error;
|
|
}
|
|
|
|
if (nr == alloc) {
|
|
alloc = alloc ? alloc * 2 : 4;
|
|
av = xrenew(char *, av, alloc + 1);
|
|
}
|
|
av[nr++] = arg;
|
|
skip_spaces:
|
|
while (*cmd == ' ')
|
|
cmd++;
|
|
}
|
|
av[nr] = NULL;
|
|
*ac = nr;
|
|
return av;
|
|
only_once_please:
|
|
error_msg("{} can be used only once");
|
|
error:
|
|
while (nr > 0)
|
|
free(av[--nr]);
|
|
free(av);
|
|
return NULL;
|
|
}
|
|
|
|
struct track_info_selection {
|
|
struct track_info **tis;
|
|
int tis_alloc;
|
|
int tis_nr;
|
|
};
|
|
|
|
static int add_ti(void *data, struct track_info *ti)
|
|
{
|
|
struct track_info_selection *sel = data;
|
|
if (sel->tis_nr == sel->tis_alloc) {
|
|
sel->tis_alloc = sel->tis_alloc ? sel->tis_alloc * 2 : 8;
|
|
sel->tis = xrenew(struct track_info *, sel->tis, sel->tis_alloc);
|
|
}
|
|
track_info_ref(ti);
|
|
sel->tis[sel->tis_nr++] = ti;
|
|
return 0;
|
|
}
|
|
|
|
static void cmd_run(char *arg)
|
|
{
|
|
char **av, **argv;
|
|
int ac, argc, i, run, files_idx = -1;
|
|
struct track_info_selection sel = { .tis = NULL };
|
|
|
|
if (cur_view > QUEUE_VIEW) {
|
|
info_msg("Command execution is supported only in views 1-4");
|
|
return;
|
|
}
|
|
|
|
av = parse_cmd(arg, &files_idx, &ac);
|
|
if (av == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* collect selected files (struct track_info) */
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
_tree_for_each_sel(add_ti, &sel, 0);
|
|
break;
|
|
case SORTED_VIEW:
|
|
_editable_for_each_sel(&lib_editable, add_ti, &sel, 0);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
_pl_for_each_sel(add_ti, &sel, 0);
|
|
break;
|
|
case QUEUE_VIEW:
|
|
_editable_for_each_sel(&pq_editable, add_ti, &sel, 0);
|
|
break;
|
|
}
|
|
|
|
if (sel.tis_nr == 0) {
|
|
/* no files selected, do nothing */
|
|
free_str_array(av);
|
|
return;
|
|
}
|
|
sel.tis[sel.tis_nr] = NULL;
|
|
|
|
/* build argv */
|
|
argv = xnew(char *, ac + sel.tis_nr + 1);
|
|
argc = 0;
|
|
if (files_idx == -1) {
|
|
/* add selected files after rest of the args */
|
|
for (i = 0; i < ac; i++)
|
|
argv[argc++] = av[i];
|
|
for (i = 0; i < sel.tis_nr; i++)
|
|
argv[argc++] = sel.tis[i]->filename;
|
|
} else {
|
|
for (i = 0; i < files_idx; i++)
|
|
argv[argc++] = av[i];
|
|
for (i = 0; i < sel.tis_nr; i++)
|
|
argv[argc++] = sel.tis[i]->filename;
|
|
for (i = files_idx; i < ac; i++)
|
|
argv[argc++] = av[i];
|
|
}
|
|
argv[argc] = NULL;
|
|
|
|
for (i = 0; argv[i]; i++)
|
|
d_print("ARG: '%s'\n", argv[i]);
|
|
|
|
run = 1;
|
|
if (confirm_run && (sel.tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
|
|
if (yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel.tis_nr) != UI_QUERY_ANSWER_YES) {
|
|
info_msg("Aborted");
|
|
run = 0;
|
|
}
|
|
}
|
|
if (run) {
|
|
int status;
|
|
|
|
if (spawn(argv, &status, 1)) {
|
|
error_msg("executing %s: %s", argv[0], strerror(errno));
|
|
} else {
|
|
if (WIFEXITED(status)) {
|
|
int rc = WEXITSTATUS(status);
|
|
|
|
if (rc)
|
|
error_msg("%s returned %d", argv[0], rc);
|
|
}
|
|
if (WIFSIGNALED(status))
|
|
error_msg("%s received signal %d", argv[0], WTERMSIG(status));
|
|
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
/* this must be done before sel.tis are unreffed */
|
|
free_str_array(av);
|
|
free(argv);
|
|
|
|
/* remove non-existed files, update tags for changed files */
|
|
cmus_update_tis(sel.tis, sel.tis_nr, 0);
|
|
|
|
/* we don't own sel.tis anymore! */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
free_str_array(av);
|
|
free(argv);
|
|
for (i = 0; sel.tis[i]; i++)
|
|
track_info_unref(sel.tis[i]);
|
|
free(sel.tis);
|
|
}
|
|
|
|
static void cmd_shell(char *arg)
|
|
{
|
|
const char * const argv[] = { "sh", "-c", arg, NULL };
|
|
|
|
if (spawn((char **) argv, NULL, 0))
|
|
error_msg("executing '%s': %s", arg, strerror(errno));
|
|
}
|
|
|
|
static int get_one_ti(void *data, struct track_info *ti)
|
|
{
|
|
struct track_info **sel_ti = data;
|
|
|
|
track_info_ref(ti);
|
|
*sel_ti = ti;
|
|
/* stop the for each loop, we need only the first selected track */
|
|
return 1;
|
|
}
|
|
|
|
static void cmd_echo(char *arg)
|
|
{
|
|
struct track_info *sel_ti;
|
|
char *ptr = arg;
|
|
|
|
while (1) {
|
|
ptr = strchr(ptr, '{');
|
|
if (ptr == NULL)
|
|
break;
|
|
if (ptr[1] == '}')
|
|
break;
|
|
ptr++;
|
|
}
|
|
|
|
if (ptr == NULL) {
|
|
info_msg("%s", arg);
|
|
return;
|
|
}
|
|
|
|
if (cur_view > QUEUE_VIEW) {
|
|
info_msg("echo with {} in its arguments is supported only in views 1-4");
|
|
return;
|
|
}
|
|
|
|
*ptr = 0;
|
|
ptr += 2;
|
|
|
|
/* get only the first selected track */
|
|
sel_ti = NULL;
|
|
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
_tree_for_each_sel(get_one_ti, &sel_ti, 0);
|
|
break;
|
|
case SORTED_VIEW:
|
|
_editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
_pl_for_each_sel(get_one_ti, &sel_ti, 0);
|
|
break;
|
|
case QUEUE_VIEW:
|
|
_editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
|
|
break;
|
|
}
|
|
|
|
if (sel_ti == NULL)
|
|
return;
|
|
|
|
info_msg("%s%s%s", arg, sel_ti->filename, ptr);
|
|
track_info_unref(sel_ti);
|
|
}
|
|
|
|
static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
|
|
{
|
|
unsigned int f = 0;
|
|
int ch, val = 0, digits = 0, sign = 1;
|
|
|
|
if (*arg == '-') {
|
|
arg++;
|
|
f |= VF_RELATIVE;
|
|
sign = -1;
|
|
} else if (*arg == '+') {
|
|
arg++;
|
|
f |= VF_RELATIVE;
|
|
}
|
|
|
|
while (1) {
|
|
ch = *arg++;
|
|
if (ch < '0' || ch > '9')
|
|
break;
|
|
val *= 10;
|
|
val += ch - '0';
|
|
digits++;
|
|
}
|
|
if (digits == 0)
|
|
goto err;
|
|
|
|
if (ch == '%') {
|
|
f |= VF_PERCENTAGE;
|
|
ch = *arg;
|
|
}
|
|
if (ch)
|
|
goto err;
|
|
|
|
*value = sign * val;
|
|
*flags = f;
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static void cmd_mute(char *arg)
|
|
{
|
|
int l = 0, r = 0;
|
|
int *vl, *vr;
|
|
|
|
if (soft_vol) {
|
|
vl = &soft_vol_l;
|
|
vr = &soft_vol_r;
|
|
} else {
|
|
vl = &volume_l;
|
|
vr = &volume_r;
|
|
}
|
|
|
|
if (*vl == 0 && *vr == 0) {
|
|
// unmute
|
|
l = mute_vol_l;
|
|
r = mute_vol_r;
|
|
} else {
|
|
mute_vol_l = *vl;
|
|
mute_vol_r = *vr;
|
|
}
|
|
|
|
int rc = player_set_vol(l, 0, r, 0);
|
|
if (rc != OP_ERROR_SUCCESS) {
|
|
char *msg = op_get_error_msg(rc, "can't change volume");
|
|
error_msg("%s", msg);
|
|
free(msg);
|
|
} else {
|
|
mpris_volume_changed();
|
|
}
|
|
update_statusline();
|
|
}
|
|
|
|
|
|
/*
|
|
* :vol value [value]
|
|
*
|
|
* where value is [-+]?[0-9]+%?
|
|
*/
|
|
static void cmd_vol(char *arg)
|
|
{
|
|
char **values = get_words(arg);
|
|
unsigned int lf, rf;
|
|
int l, r;
|
|
|
|
if (values[1] && values[2])
|
|
goto err;
|
|
|
|
if (parse_vol_arg(values[0], &l, &lf))
|
|
goto err;
|
|
|
|
r = l;
|
|
rf = lf;
|
|
if (values[1] && parse_vol_arg(values[1], &r, &rf))
|
|
goto err;
|
|
|
|
free_str_array(values);
|
|
|
|
int rc = player_set_vol(l, lf, r, rf);
|
|
if (rc != OP_ERROR_SUCCESS) {
|
|
char *msg = op_get_error_msg(rc, "can't change volume");
|
|
error_msg("%s", msg);
|
|
free(msg);
|
|
} else {
|
|
mpris_volume_changed();
|
|
}
|
|
update_statusline();
|
|
return;
|
|
err:
|
|
free_str_array(values);
|
|
error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
|
|
}
|
|
|
|
static void cmd_prev_view(char *arg)
|
|
{
|
|
if (prev_view >= 0) {
|
|
set_view(prev_view);
|
|
}
|
|
}
|
|
|
|
static void cmd_left_view(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
|
|
if (cur_view == TREE_VIEW) {
|
|
if (flag != 'n') {
|
|
set_view(HELP_VIEW);
|
|
}
|
|
} else {
|
|
set_view(cur_view - 1);
|
|
}
|
|
}
|
|
|
|
static void cmd_right_view(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
|
|
if (cur_view == HELP_VIEW) {
|
|
if (flag != 'n') {
|
|
set_view(TREE_VIEW);
|
|
}
|
|
} else {
|
|
set_view(cur_view + 1);
|
|
}
|
|
}
|
|
|
|
static void cmd_pl_create(char *arg)
|
|
{
|
|
pl_create(arg);
|
|
}
|
|
|
|
static void cmd_pl_export(char *arg)
|
|
{
|
|
if (cur_view == PLAYLIST_VIEW)
|
|
pl_export_selected_pl(arg);
|
|
else
|
|
info_msg(":pl-export only works in view 3");
|
|
}
|
|
|
|
static char *get_browser_add_file(void)
|
|
{
|
|
char *sel = browser_get_sel();
|
|
|
|
if (sel && (ends_with(sel, "/../") || ends_with(sel, "/.."))) {
|
|
info_msg("For convenience, you can not add \"..\" directory from the browser view");
|
|
free(sel);
|
|
sel = NULL;
|
|
}
|
|
|
|
return sel;
|
|
}
|
|
|
|
static void cmd_pl_import(char *arg)
|
|
{
|
|
char *name = NULL;
|
|
|
|
if (arg)
|
|
name = view_load_prepare(arg);
|
|
else if (cur_view == BROWSER_VIEW)
|
|
name = get_browser_add_file();
|
|
else
|
|
error_msg("not enough arguments");
|
|
|
|
if (name) {
|
|
pl_import(name);
|
|
free(name);
|
|
}
|
|
}
|
|
|
|
static void cmd_pl_rename(char *arg)
|
|
{
|
|
if (cur_view == PLAYLIST_VIEW)
|
|
pl_rename_selected_pl(arg);
|
|
else
|
|
info_msg(":pl-rename only works in view 3");
|
|
}
|
|
|
|
static void cmd_pl_delete(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "a");
|
|
if (flag == 'a')
|
|
pl_delete_all();
|
|
else
|
|
pl_delete_by_name(arg);
|
|
}
|
|
|
|
static void cmd_version(char *arg)
|
|
{
|
|
info_msg(VERSION);
|
|
}
|
|
|
|
static void cmd_view(char *arg)
|
|
{
|
|
int view;
|
|
|
|
if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
|
|
set_view(view - 1);
|
|
}
|
|
}
|
|
|
|
static void cmd_push(char *arg)
|
|
{
|
|
if (arg)
|
|
cmdline_set_text(arg);
|
|
enter_command_mode();
|
|
}
|
|
|
|
static void cmd_p_next(char *arg)
|
|
{
|
|
cmus_next();
|
|
}
|
|
|
|
static void cmd_p_pause(char *arg)
|
|
{
|
|
player_pause();
|
|
}
|
|
|
|
static void cmd_p_pause_playback(char *arg)
|
|
{
|
|
player_pause_playback();
|
|
}
|
|
|
|
static void cmd_p_play(char *arg)
|
|
{
|
|
if (arg) {
|
|
char *tmp = expand_filename(arg);
|
|
cmus_play_file(tmp);
|
|
free(tmp);
|
|
} else {
|
|
player_play();
|
|
}
|
|
}
|
|
|
|
static void cmd_p_prev(char *arg)
|
|
{
|
|
if (rewind_offset < 0 || player_info.pos < rewind_offset) {
|
|
cmus_prev();
|
|
} else {
|
|
player_play();
|
|
}
|
|
}
|
|
|
|
static void cmd_p_next_album(char *arg)
|
|
{
|
|
cmus_next_album();
|
|
}
|
|
|
|
static void cmd_p_prev_album(char *arg)
|
|
{
|
|
cmus_prev_album();
|
|
}
|
|
|
|
static void cmd_p_stop(char *arg)
|
|
{
|
|
player_stop();
|
|
}
|
|
|
|
static void cmd_pwd(char *arg)
|
|
{
|
|
char buf[4096];
|
|
if (getcwd(buf, sizeof buf)) {
|
|
info_msg("%s", buf);
|
|
}
|
|
}
|
|
|
|
static void cmd_raise_vte(char *arg)
|
|
{
|
|
cmus_raise_vte();
|
|
}
|
|
|
|
static void cmd_rand(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
break;
|
|
case SORTED_VIEW:
|
|
editable_rand(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_rand();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_rand(&pq_editable);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_search_next(char *arg)
|
|
{
|
|
if (search_str) {
|
|
if (!search_next(searchable, search_str, search_direction))
|
|
search_not_found();
|
|
}
|
|
}
|
|
|
|
static void cmd_search_prev(char *arg)
|
|
{
|
|
if (search_str) {
|
|
if (!search_next(searchable, search_str, !search_direction))
|
|
search_not_found();
|
|
}
|
|
}
|
|
|
|
static void cmd_search_start(char *arg)
|
|
{
|
|
enter_search_mode();
|
|
}
|
|
|
|
static void cmd_search_b_start(char *arg)
|
|
{
|
|
enter_search_backward_mode();
|
|
}
|
|
|
|
static int sorted_for_each_sel(track_info_cb cb, void *data, int reverse, int advance)
|
|
{
|
|
return editable_for_each_sel(&lib_editable, cb, data, reverse, advance);
|
|
}
|
|
|
|
static int pq_for_each_sel(track_info_cb cb, void *data, int reverse, int advance)
|
|
{
|
|
return editable_for_each_sel(&pq_editable, cb, data, reverse, advance);
|
|
}
|
|
|
|
static for_each_sel_ti_cb view_for_each_sel[4] = {
|
|
tree_for_each_sel,
|
|
sorted_for_each_sel,
|
|
pl_for_each_sel,
|
|
pq_for_each_sel
|
|
};
|
|
|
|
/* wrapper for add_ti_cb, (void *) can't store function pointers */
|
|
struct wrapper_cb_data {
|
|
add_ti_cb cb;
|
|
};
|
|
|
|
/* wrapper for void lib_add_track(struct track_info *) etc. */
|
|
static int wrapper_cb(void *data, struct track_info *ti)
|
|
{
|
|
struct wrapper_cb_data *add = data;
|
|
|
|
add->cb(ti, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static void add_from_browser(add_ti_cb add, int job_type, int advance)
|
|
{
|
|
char *sel = get_browser_add_file();
|
|
|
|
if (sel) {
|
|
enum file_type ft;
|
|
char *ret;
|
|
|
|
ft = cmus_detect_ft(sel, &ret);
|
|
if (ft != FILE_TYPE_INVALID) {
|
|
cmus_add(add, ret, ft, job_type, 0, NULL);
|
|
if (advance)
|
|
window_down(browser_win, 1);
|
|
}
|
|
free(ret);
|
|
free(sel);
|
|
}
|
|
}
|
|
|
|
static void cmd_win_add_l(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
|
|
return;
|
|
|
|
if (cur_view <= QUEUE_VIEW) {
|
|
struct wrapper_cb_data add = { lib_add_track };
|
|
view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
|
|
} else if (cur_view == BROWSER_VIEW) {
|
|
add_from_browser(lib_add_track, JOB_TYPE_LIB, flag != 'n');
|
|
}
|
|
}
|
|
|
|
static void cmd_win_add_p(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (cur_view == PLAYLIST_VIEW && pl_visible_is_marked())
|
|
return;
|
|
|
|
if (cur_view <= QUEUE_VIEW) {
|
|
struct wrapper_cb_data add = { pl_add_track_to_marked_pl2 };
|
|
view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
|
|
} else if (cur_view == BROWSER_VIEW) {
|
|
char *sel = get_browser_add_file();
|
|
if (sel) {
|
|
if (pl_add_file_to_marked_pl(sel) && flag != 'n')
|
|
window_down(browser_win, 1);
|
|
free(sel);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmd_win_add_Q(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (cur_view == QUEUE_VIEW)
|
|
return;
|
|
|
|
if (cur_view <= QUEUE_VIEW) {
|
|
struct wrapper_cb_data add = { play_queue_prepend };
|
|
view_for_each_sel[cur_view](wrapper_cb, &add, 1, flag != 'n');
|
|
} else if (cur_view == BROWSER_VIEW) {
|
|
add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE, flag != 'n');
|
|
}
|
|
}
|
|
|
|
static void cmd_win_add_q(char *arg)
|
|
{
|
|
int flag = parse_flags((const char **)&arg, "n");
|
|
if (flag == -1)
|
|
return;
|
|
|
|
if (cur_view == QUEUE_VIEW)
|
|
return;
|
|
|
|
if (cur_view <= QUEUE_VIEW) {
|
|
struct wrapper_cb_data add = { play_queue_append };
|
|
view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
|
|
} else if (cur_view == BROWSER_VIEW) {
|
|
add_from_browser(play_queue_append, JOB_TYPE_QUEUE, flag != 'n');
|
|
}
|
|
}
|
|
|
|
static void cmd_win_activate(char *arg)
|
|
{
|
|
struct track_info *info = NULL;
|
|
struct shuffle_info *previous = NULL, *next = NULL;
|
|
struct rb_root *shuffle_root = NULL;
|
|
|
|
if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW) {
|
|
if (shuffle == SHUFFLE_TRACKS) {
|
|
if (lib_cur_track)
|
|
previous = &lib_cur_track->simple_track.shuffle_info;
|
|
shuffle_root = &lib_shuffle_root;
|
|
} else if (shuffle == SHUFFLE_ALBUMS) {
|
|
if (lib_cur_track)
|
|
previous = &lib_cur_track->album->shuffle_info;
|
|
shuffle_root = &lib_album_shuffle_root;
|
|
}
|
|
}
|
|
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
info = tree_activate_selected();
|
|
if (shuffle == SHUFFLE_TRACKS)
|
|
next = &lib_cur_track->simple_track.shuffle_info;
|
|
else if (shuffle == SHUFFLE_ALBUMS)
|
|
next = &lib_cur_track->album->shuffle_info;
|
|
break;
|
|
case SORTED_VIEW:
|
|
info = sorted_activate_selected();
|
|
if (shuffle == SHUFFLE_TRACKS)
|
|
next = &lib_cur_track->simple_track.shuffle_info;
|
|
else if (shuffle == SHUFFLE_ALBUMS)
|
|
next = &lib_cur_track->album->shuffle_info;
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
info = pl_play_selected_row();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
break;
|
|
case BROWSER_VIEW:
|
|
browser_enter();
|
|
break;
|
|
case FILTERS_VIEW:
|
|
filters_activate(1);
|
|
break;
|
|
case HELP_VIEW:
|
|
help_select();
|
|
break;
|
|
}
|
|
|
|
if (info) {
|
|
if (shuffle && next)
|
|
shuffle_insert(shuffle_root, previous, next);
|
|
/* update lib/pl mode */
|
|
if (cur_view < 2)
|
|
play_library = 1;
|
|
if (cur_view == 2)
|
|
play_library = 0;
|
|
|
|
player_play_file(info);
|
|
}
|
|
}
|
|
|
|
static void cmd_win_mv_after(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case SORTED_VIEW:
|
|
editable_move_after(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_win_mv_after();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_move_after(&pq_editable);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_win_mv_before(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case SORTED_VIEW:
|
|
editable_move_before(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_win_mv_before();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_move_before(&pq_editable);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_win_remove(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
tree_remove_sel();
|
|
break;
|
|
case SORTED_VIEW:
|
|
editable_remove_sel(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_win_remove();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_remove_sel(&pq_editable);
|
|
break;
|
|
case BROWSER_VIEW:
|
|
browser_delete();
|
|
break;
|
|
case FILTERS_VIEW:
|
|
filters_delete_filter();
|
|
break;
|
|
case HELP_VIEW:
|
|
help_remove();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_win_sel_cur(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
tree_sel_current(auto_expand_albums_selcur);
|
|
break;
|
|
case SORTED_VIEW:
|
|
sorted_sel_current();
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_select_playing_track();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_win_toggle(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
tree_toggle_expand_artist();
|
|
break;
|
|
case SORTED_VIEW:
|
|
editable_toggle_mark(&lib_editable);
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_win_toggle();
|
|
break;
|
|
case QUEUE_VIEW:
|
|
editable_toggle_mark(&pq_editable);
|
|
break;
|
|
case FILTERS_VIEW:
|
|
filters_toggle_filter();
|
|
break;
|
|
case HELP_VIEW:
|
|
help_toggle();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_win_scroll_down(char *arg)
|
|
{
|
|
window_scroll_down(current_win());
|
|
}
|
|
|
|
static void cmd_win_scroll_up(char *arg)
|
|
{
|
|
window_scroll_up(current_win());
|
|
}
|
|
|
|
static void cmd_win_bottom(char *arg)
|
|
{
|
|
window_goto_bottom(current_win());
|
|
}
|
|
|
|
static void cmd_win_down(char *arg)
|
|
{
|
|
unsigned num_rows = 1;
|
|
char *end;
|
|
|
|
if (arg) {
|
|
if ((num_rows = get_number(arg, &end)) == 0 || *end) {
|
|
error_msg("invalid argument\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
window_down(current_win(), num_rows);
|
|
}
|
|
|
|
static void cmd_win_next(char *arg)
|
|
{
|
|
if (cur_view == TREE_VIEW)
|
|
tree_toggle_active_window();
|
|
else if (cur_view == PLAYLIST_VIEW)
|
|
pl_win_next();
|
|
}
|
|
|
|
static void cmd_win_pg_down(char *arg)
|
|
{
|
|
window_page_down(current_win());
|
|
}
|
|
|
|
static void cmd_win_pg_up(char *arg)
|
|
{
|
|
window_page_up(current_win());
|
|
}
|
|
|
|
static void cmd_win_hf_pg_down(char *arg)
|
|
{
|
|
window_half_page_down(current_win());
|
|
}
|
|
|
|
static void cmd_win_hf_pg_up(char *arg)
|
|
{
|
|
window_half_page_up(current_win());
|
|
}
|
|
|
|
static void cmd_win_pg_top(char *arg)
|
|
{
|
|
window_page_top(current_win());
|
|
}
|
|
|
|
static void cmd_win_pg_bottom(char *arg)
|
|
{
|
|
window_page_bottom(current_win());
|
|
}
|
|
|
|
static void cmd_win_pg_middle(char *arg)
|
|
{
|
|
window_page_middle(current_win());
|
|
}
|
|
|
|
static void cmd_win_update_cache(char *arg)
|
|
{
|
|
struct track_info_selection sel = { .tis = NULL };
|
|
int flag = parse_flags((const char **)&arg, "f");
|
|
|
|
if (cur_view != TREE_VIEW && cur_view != SORTED_VIEW)
|
|
return;
|
|
|
|
view_for_each_sel[cur_view](add_ti, &sel, 0, 1);
|
|
if (sel.tis_nr == 0)
|
|
return;
|
|
sel.tis[sel.tis_nr] = NULL;
|
|
cmus_update_tis(sel.tis, sel.tis_nr, flag == 'f');
|
|
}
|
|
|
|
static void cmd_win_top(char *arg)
|
|
{
|
|
window_goto_top(current_win());
|
|
}
|
|
|
|
static void cmd_win_up(char *arg)
|
|
{
|
|
unsigned num_rows = 1;
|
|
char *end;
|
|
|
|
if (arg) {
|
|
if ((num_rows = get_number(arg, &end)) == 0 || *end) {
|
|
error_msg("invalid argument\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
window_up(current_win(), num_rows);
|
|
}
|
|
|
|
static void cmd_win_update(char *arg)
|
|
{
|
|
switch (cur_view) {
|
|
case TREE_VIEW:
|
|
case SORTED_VIEW:
|
|
cmus_update_lib();
|
|
break;
|
|
case PLAYLIST_VIEW:
|
|
pl_win_update();
|
|
break;
|
|
case BROWSER_VIEW:
|
|
browser_reload();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cmd_browser_up(char *arg)
|
|
{
|
|
browser_up();
|
|
}
|
|
|
|
static void cmd_refresh(char *arg)
|
|
{
|
|
clearok(curscr, TRUE);
|
|
refresh();
|
|
}
|
|
|
|
static int cmp_intp(const void *ap, const void *bp)
|
|
{
|
|
int a = *(int *)ap;
|
|
int b = *(int *)bp;
|
|
return a - b;
|
|
}
|
|
|
|
static int *rand_array(int size, int nmax)
|
|
{
|
|
int *r = xnew(int, size + 1);
|
|
int i, offset = 0;
|
|
int count = size;
|
|
|
|
if (count > nmax / 2) {
|
|
/*
|
|
* Imagine that there are 1000 tracks in library and we want to
|
|
* add 998 random tracks to queue. After we have added 997
|
|
* random numbers to the array it would be quite hard to find a
|
|
* random number that isn't already in the array (3/1000
|
|
* probability).
|
|
*
|
|
* So we invert the logic:
|
|
*
|
|
* Find two (1000 - 998) random numbers in 0..999 range and put
|
|
* them at end of the array. Sort the numbers and then fill
|
|
* the array starting at index 0 with incrementing values that
|
|
* are not in the set of random numbers.
|
|
*/
|
|
count = nmax - count;
|
|
offset = size - count;
|
|
}
|
|
|
|
for (i = 0; i < count; ) {
|
|
int v, j;
|
|
found:
|
|
v = rand() % nmax;
|
|
for (j = 0; j < i; j++) {
|
|
if (r[offset + j] == v)
|
|
goto found;
|
|
}
|
|
r[offset + i++] = v;
|
|
}
|
|
qsort(r + offset, count, sizeof(*r), cmp_intp);
|
|
|
|
if (offset) {
|
|
int j, n;
|
|
|
|
/* simplifies next loop */
|
|
r[size] = nmax;
|
|
|
|
/* convert the indexes we don't want to those we want */
|
|
i = 0;
|
|
j = offset;
|
|
n = 0;
|
|
do {
|
|
while (n < r[j])
|
|
r[i++] = n++;
|
|
j++;
|
|
n++;
|
|
} while (i < size);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int count_albums(void)
|
|
{
|
|
struct artist *artist;
|
|
struct rb_node *tmp1, *tmp2;
|
|
int count = 0;
|
|
|
|
rb_for_each_entry(artist, tmp1, &lib_artist_root, tree_node) {
|
|
rb_for_each(tmp2, &artist->album_root)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
struct album_list {
|
|
struct list_head node;
|
|
const struct album *album;
|
|
};
|
|
|
|
static void cmd_lqueue(char *arg)
|
|
{
|
|
LIST_HEAD(head);
|
|
const struct list_head *item;
|
|
const struct album *album;
|
|
int count = 1, nmax, i, pos;
|
|
int *r;
|
|
|
|
if (arg) {
|
|
long int val;
|
|
|
|
if (str_to_int(arg, &val) || val <= 0) {
|
|
error_msg("argument must be positive integer");
|
|
return;
|
|
}
|
|
count = val;
|
|
}
|
|
nmax = count_albums();
|
|
if (count > nmax)
|
|
count = nmax;
|
|
if (!count)
|
|
return;
|
|
|
|
r = rand_array(count, nmax);
|
|
album = to_album(rb_first(&to_artist(rb_first(&lib_artist_root))->album_root));
|
|
pos = 0;
|
|
for (i = 0; i < count; i++) {
|
|
struct album_list *a;
|
|
|
|
while (pos < r[i]) {
|
|
struct artist *artist = album->artist;
|
|
if (!rb_next(&album->tree_node)) {
|
|
artist = to_artist(rb_next(&artist->tree_node));
|
|
album = to_album(rb_first(&artist->album_root));
|
|
} else {
|
|
album = to_album(rb_next(&album->tree_node));
|
|
}
|
|
pos++;
|
|
}
|
|
a = xnew(struct album_list, 1);
|
|
a->album = album;
|
|
list_add_rand(&head, &a->node, i);
|
|
}
|
|
free(r);
|
|
|
|
item = head.next;
|
|
do {
|
|
struct list_head *next = item->next;
|
|
struct album_list *a = container_of(item, struct album_list, node);
|
|
struct tree_track *t;
|
|
struct rb_node *tmp;
|
|
|
|
rb_for_each_entry(t, tmp, &a->album->track_root, tree_node)
|
|
play_queue_append(tree_track_info(t), NULL);
|
|
free(a);
|
|
item = next;
|
|
} while (item != &head);
|
|
}
|
|
|
|
struct track_list {
|
|
struct list_head node;
|
|
const struct simple_track *track;
|
|
};
|
|
|
|
static void cmd_tqueue(char *arg)
|
|
{
|
|
LIST_HEAD(head);
|
|
struct list_head *item;
|
|
int count = 1, i, pos;
|
|
int *r;
|
|
|
|
if (arg) {
|
|
long int val;
|
|
|
|
if (str_to_int(arg, &val) || val <= 0) {
|
|
error_msg("argument must be positive integer");
|
|
return;
|
|
}
|
|
count = val;
|
|
}
|
|
if (count > lib_editable.nr_tracks)
|
|
count = lib_editable.nr_tracks;
|
|
if (!count)
|
|
return;
|
|
|
|
r = rand_array(count, lib_editable.nr_tracks);
|
|
item = lib_editable.head.next;
|
|
pos = 0;
|
|
for (i = 0; i < count; i++) {
|
|
struct track_list *t;
|
|
|
|
while (pos < r[i]) {
|
|
item = item->next;
|
|
pos++;
|
|
}
|
|
t = xnew(struct track_list, 1);
|
|
t->track = to_simple_track(item);
|
|
list_add_rand(&head, &t->node, i);
|
|
}
|
|
free(r);
|
|
|
|
item = head.next;
|
|
do {
|
|
struct list_head *next = item->next;
|
|
struct track_list *t = container_of(item, struct track_list, node);
|
|
play_queue_append(t->track->info, NULL);
|
|
free(t);
|
|
item = next;
|
|
} while (item != &head);
|
|
}
|
|
|
|
/* tab exp {{{
|
|
*
|
|
* these functions fill tabexp struct, which is resetted beforehand
|
|
*/
|
|
|
|
/* buffer used for tab expansion */
|
|
static char expbuf[512];
|
|
|
|
static int filter_directories(const char *name, const struct stat *s)
|
|
{
|
|
return S_ISDIR(s->st_mode);
|
|
}
|
|
|
|
static int filter_executable_files(const char *name, const struct stat *s)
|
|
{
|
|
return S_ISREG(s->st_mode) && (s->st_mode & 0111);
|
|
}
|
|
|
|
static int filter_any(const char *name, const struct stat *s)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static int filter_playable(const char *name, const struct stat *s)
|
|
{
|
|
return S_ISDIR(s->st_mode) || cmus_is_playable(name);
|
|
}
|
|
|
|
static int filter_playlist(const char *name, const struct stat *s)
|
|
{
|
|
return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
|
|
}
|
|
|
|
static int filter_supported(const char *name, const struct stat *s)
|
|
{
|
|
return S_ISDIR(s->st_mode) || cmus_is_supported(name);
|
|
}
|
|
|
|
static void expand_files(const char *str)
|
|
{
|
|
expand_files_and_dirs(str, filter_any);
|
|
}
|
|
|
|
static void expand_directories(const char *str)
|
|
{
|
|
expand_files_and_dirs(str, filter_directories);
|
|
}
|
|
|
|
static void expand_playable(const char *str)
|
|
{
|
|
expand_files_and_dirs(str, filter_playable);
|
|
}
|
|
|
|
static void expand_playlist(const char *str)
|
|
{
|
|
expand_files_and_dirs(str, filter_playlist);
|
|
}
|
|
|
|
static void expand_supported(const char *str)
|
|
{
|
|
expand_files_and_dirs(str, filter_supported);
|
|
}
|
|
|
|
static void expand_add(const char *str)
|
|
{
|
|
int flag = parse_flags(&str, "lpqQ");
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (str == NULL)
|
|
str = "";
|
|
expand_supported(str);
|
|
|
|
if (tabexp.head && flag) {
|
|
snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
|
|
free(tabexp.head);
|
|
tabexp.head = xstrdup(expbuf);
|
|
}
|
|
}
|
|
|
|
static void expand_program_paths(const char *str)
|
|
{
|
|
if (str == NULL)
|
|
str = "";
|
|
if (str[0] == '~' || strchr(str, '/'))
|
|
expand_files(str);
|
|
else
|
|
expand_env_path(str, filter_executable_files);
|
|
}
|
|
|
|
static void expand_program_paths_option(const char *str, const char *opt)
|
|
{
|
|
expand_program_paths(str);
|
|
|
|
if (tabexp.head && opt) {
|
|
snprintf(expbuf, sizeof(expbuf), "%s=%s", opt, tabexp.head);
|
|
free(tabexp.head);
|
|
tabexp.head = xstrdup(expbuf);
|
|
}
|
|
}
|
|
|
|
static void expand_load_save(const char *str)
|
|
{
|
|
int flag = parse_flags(&str, "lp");
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (str == NULL)
|
|
str = "";
|
|
expand_playlist(str);
|
|
|
|
if (tabexp.head && flag) {
|
|
snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
|
|
free(tabexp.head);
|
|
tabexp.head = xstrdup(expbuf);
|
|
}
|
|
}
|
|
|
|
static void expand_key_context(const char *str, const char *force)
|
|
{
|
|
int pos, i, len = strlen(str);
|
|
char **tails;
|
|
|
|
tails = xnew(char *, NR_CTXS);
|
|
pos = 0;
|
|
for (i = 0; key_context_names[i]; i++) {
|
|
int cmp = strncmp(str, key_context_names[i], len);
|
|
if (cmp > 0)
|
|
continue;
|
|
if (cmp < 0)
|
|
break;
|
|
tails[pos++] = xstrdup(key_context_names[i] + len);
|
|
}
|
|
|
|
if (pos == 0) {
|
|
free(tails);
|
|
return;
|
|
}
|
|
if (pos == 1) {
|
|
char *tmp = xstrjoin(tails[0], " ");
|
|
free(tails[0]);
|
|
tails[0] = tmp;
|
|
}
|
|
snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
|
|
tabexp.head = xstrdup(expbuf);
|
|
tabexp.tails = tails;
|
|
tabexp.count = pos;
|
|
}
|
|
|
|
static int get_context(const char *str, int len)
|
|
{
|
|
int i, c = -1, count = 0;
|
|
|
|
for (i = 0; key_context_names[i]; i++) {
|
|
if (strncmp(str, key_context_names[i], len) == 0) {
|
|
if (key_context_names[i][len] == 0) {
|
|
/* exact */
|
|
return i;
|
|
}
|
|
c = i;
|
|
count++;
|
|
}
|
|
}
|
|
if (count == 1)
|
|
return c;
|
|
return -1;
|
|
}
|
|
|
|
static void expand_command_line(const char *str);
|
|
|
|
static void expand_bind_args(const char *str)
|
|
{
|
|
/* :bind context key function
|
|
*
|
|
* possible values for str:
|
|
* c
|
|
* context k
|
|
* context key f
|
|
*
|
|
* you need to know context before you can expand function
|
|
*/
|
|
/* start and end pointers for context, key and function */
|
|
const char *cs, *ce, *ks, *ke, *fs;
|
|
int i, c, k, count;
|
|
int flag = parse_flags((const char **)&str, "f");
|
|
const char *force = "";
|
|
|
|
if (flag == -1)
|
|
return;
|
|
if (str == NULL)
|
|
str = "";
|
|
|
|
if (flag == 'f')
|
|
force = "-f ";
|
|
|
|
cs = str;
|
|
ce = strchr(cs, ' ');
|
|
if (ce == NULL) {
|
|
expand_key_context(cs, force);
|
|
return;
|
|
}
|
|
|
|
/* context must be expandable */
|
|
c = get_context(cs, ce - cs);
|
|
if (c == -1) {
|
|
/* context is ambiguous or invalid */
|
|
return;
|
|
}
|
|
|
|
ks = ce;
|
|
while (*ks == ' ')
|
|
ks++;
|
|
ke = strchr(ks, ' ');
|
|
if (ke == NULL) {
|
|
/* expand key */
|
|
int len = strlen(ks);
|
|
PTR_ARRAY(array);
|
|
|
|
for (i = 0; key_table[i].name; i++) {
|
|
int cmp = strncmp(ks, key_table[i].name, len);
|
|
if (cmp > 0)
|
|
continue;
|
|
if (cmp < 0)
|
|
break;
|
|
ptr_array_add(&array, xstrdup(key_table[i].name + len));
|
|
}
|
|
|
|
if (!array.count)
|
|
return;
|
|
|
|
if (array.count == 1) {
|
|
char **ptrs = array.ptrs;
|
|
char *tmp = xstrjoin(ptrs[0], " ");
|
|
free(ptrs[0]);
|
|
ptrs[0] = tmp;
|
|
}
|
|
|
|
snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
|
|
|
|
tabexp.head = xstrdup(expbuf);
|
|
tabexp.tails = array.ptrs;
|
|
tabexp.count = array.count;
|
|
return;
|
|
}
|
|
|
|
/* key must be expandable */
|
|
k = -1;
|
|
count = 0;
|
|
for (i = 0; key_table[i].name; i++) {
|
|
if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
|
|
if (key_table[i].name[ke - ks] == 0) {
|
|
/* exact */
|
|
k = i;
|
|
count = 1;
|
|
break;
|
|
}
|
|
k = i;
|
|
count++;
|
|
}
|
|
}
|
|
if (count != 1) {
|
|
/* key is ambiguous or invalid */
|
|
return;
|
|
}
|
|
|
|
fs = ke;
|
|
while (*fs == ' ')
|
|
fs++;
|
|
|
|
if (*fs == ':')
|
|
fs++;
|
|
|
|
/* expand com [arg...] */
|
|
expand_command_line(fs);
|
|
if (tabexp.head == NULL) {
|
|
/* command expand failed */
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* tabexp.head is now "com"
|
|
* tabexp.tails is [ mand1 mand2 ... ]
|
|
*
|
|
* need to change tabexp.head to "context key com"
|
|
*/
|
|
|
|
snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
|
|
key_table[k].name, tabexp.head);
|
|
free(tabexp.head);
|
|
tabexp.head = xstrdup(expbuf);
|
|
}
|
|
|
|
static void expand_unbind_args(const char *str)
|
|
{
|
|
/* :unbind context key */
|
|
/* start and end pointers for context and key */
|
|
const char *cs, *ce, *ks;
|
|
const struct binding *b;
|
|
PTR_ARRAY(array);
|
|
int c, len;
|
|
|
|
cs = str;
|
|
ce = strchr(cs, ' ');
|
|
if (ce == NULL) {
|
|
expand_key_context(cs, "");
|
|
return;
|
|
}
|
|
|
|
/* context must be expandable */
|
|
c = get_context(cs, ce - cs);
|
|
if (c == -1) {
|
|
/* context is ambiguous or invalid */
|
|
return;
|
|
}
|
|
|
|
ks = ce;
|
|
while (*ks == ' ')
|
|
ks++;
|
|
|
|
/* expand key */
|
|
len = strlen(ks);
|
|
b = key_bindings[c];
|
|
while (b) {
|
|
if (!strncmp(ks, b->key->name, len))
|
|
ptr_array_add(&array, xstrdup(b->key->name + len));
|
|
b = b->next;
|
|
}
|
|
if (!array.count)
|
|
return;
|
|
|
|
snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
|
|
|
|
tabexp.head = xstrdup(expbuf);
|
|
tabexp.tails = array.ptrs;
|
|
tabexp.count = array.count;
|
|
}
|
|
|
|
static void expand_factivate(const char *str)
|
|
{
|
|
/* "name1 name2 name3", expand only name3 */
|
|
struct filter_entry *e;
|
|
const char *name;
|
|
PTR_ARRAY(array);
|
|
int str_len, len, i;
|
|
|
|
str_len = strlen(str);
|
|
i = str_len;
|
|
while (i > 0) {
|
|
if (str[i - 1] == ' ')
|
|
break;
|
|
i--;
|
|
}
|
|
len = str_len - i;
|
|
name = str + i;
|
|
|
|
list_for_each_entry(e, &filters_head, node) {
|
|
if (!strncmp(name, e->name, len))
|
|
ptr_array_add(&array, xstrdup(e->name + len));
|
|
}
|
|
if (!array.count)
|
|
return;
|
|
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = array.ptrs;
|
|
tabexp.count = array.count;
|
|
}
|
|
|
|
static void expand_fset(const char *str)
|
|
{
|
|
struct filter_entry *e;
|
|
PTR_ARRAY(array);
|
|
|
|
list_for_each_entry(e, &filters_head, node) {
|
|
char *line = xnew(char, strlen(e->name) + strlen(e->filter) + 2);
|
|
sprintf(line, "%s=%s", e->name, e->filter);
|
|
if (!strncmp(str, line, strlen(str)))
|
|
ptr_array_add(&array, xstrdup(line + strlen(str)));
|
|
free(line);
|
|
}
|
|
if (!array.count)
|
|
return;
|
|
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = array.ptrs;
|
|
tabexp.count = array.count;
|
|
}
|
|
|
|
static void expand_options(const char *str)
|
|
{
|
|
struct cmus_opt *opt;
|
|
int len;
|
|
char **tails, *sep;
|
|
|
|
/* tabexp is resetted */
|
|
len = strlen(str);
|
|
sep = strchr(str, '=');
|
|
if (len > 1 && sep) {
|
|
/* expand value */
|
|
char *var = xstrndup(str, sep - str);
|
|
|
|
list_for_each_entry(opt, &option_head, node) {
|
|
if (strcmp(var, opt->name) == 0) {
|
|
if (str[len - 1] == '=') {
|
|
char buf[OPTION_MAX_SIZE];
|
|
|
|
tails = xnew(char *, 1);
|
|
|
|
buf[0] = 0;
|
|
opt->get(opt->data, buf, OPTION_MAX_SIZE);
|
|
tails[0] = xstrdup(buf);
|
|
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = tails;
|
|
tabexp.count = 1;
|
|
} else if (opt->flags & OPT_PROGRAM_PATH) {
|
|
expand_program_paths_option(sep + 1, var);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free(var);
|
|
} else {
|
|
/* expand variable */
|
|
int pos;
|
|
|
|
tails = xnew(char *, nr_options);
|
|
pos = 0;
|
|
list_for_each_entry(opt, &option_head, node) {
|
|
if (strncmp(str, opt->name, len) == 0)
|
|
tails[pos++] = xstrdup(opt->name + len);
|
|
}
|
|
if (pos > 0) {
|
|
if (pos == 1) {
|
|
/* only one variable matches, add '=' */
|
|
char *tmp = xstrjoin(tails[0], "=");
|
|
|
|
free(tails[0]);
|
|
tails[0] = tmp;
|
|
}
|
|
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = tails;
|
|
tabexp.count = pos;
|
|
} else {
|
|
free(tails);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void expand_toptions(const char *str)
|
|
{
|
|
struct cmus_opt *opt;
|
|
int len, pos;
|
|
char **tails;
|
|
|
|
tails = xnew(char *, nr_options);
|
|
len = strlen(str);
|
|
pos = 0;
|
|
list_for_each_entry(opt, &option_head, node) {
|
|
if (opt->toggle == NULL)
|
|
continue;
|
|
if (strncmp(str, opt->name, len) == 0)
|
|
tails[pos++] = xstrdup(opt->name + len);
|
|
}
|
|
if (pos > 0) {
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = tails;
|
|
tabexp.count = pos;
|
|
} else {
|
|
free(tails);
|
|
}
|
|
}
|
|
|
|
static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
|
|
{
|
|
struct directory dir;
|
|
const char *name, *dot;
|
|
int len = strlen(str);
|
|
|
|
if (dir_open(&dir, dirname))
|
|
return;
|
|
|
|
while ((name = dir_read(&dir))) {
|
|
if (!S_ISREG(dir.st.st_mode))
|
|
continue;
|
|
if (strncmp(name, str, len))
|
|
continue;
|
|
dot = strrchr(name, '.');
|
|
if (dot == NULL || strcmp(dot, ".theme"))
|
|
continue;
|
|
if (dot - name < len)
|
|
/* str is "foo.th"
|
|
* matches "foo.theme"
|
|
* which also ends with ".theme"
|
|
*/
|
|
continue;
|
|
ptr_array_add(array, xstrndup(name + len, dot - name - len));
|
|
}
|
|
dir_close(&dir);
|
|
}
|
|
|
|
static void expand_colorscheme(const char *str)
|
|
{
|
|
PTR_ARRAY(array);
|
|
|
|
load_themes(cmus_config_dir, str, &array);
|
|
load_themes(cmus_data_dir, str, &array);
|
|
|
|
if (array.count) {
|
|
ptr_array_sort(&array, strptrcmp);
|
|
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = array.ptrs;
|
|
tabexp.count = array.count;
|
|
}
|
|
}
|
|
|
|
static void expand_commands(const char *str);
|
|
|
|
/* tab exp }}} */
|
|
|
|
/* sort by name */
|
|
struct command commands[] = {
|
|
{ "add", cmd_add, 1, 1, expand_add, 0, 0 },
|
|
{ "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
|
|
{ "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
|
|
{ "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
|
|
{ "clear", cmd_clear, 0, 1, NULL, 0, 0 },
|
|
{ "colorscheme", cmd_colorscheme, 1, 1, expand_colorscheme, 0, 0 },
|
|
{ "echo", cmd_echo, 1, -1, NULL, 0, 0 },
|
|
{ "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
|
|
{ "filter", cmd_filter, 0, 1, NULL, 0, 0 },
|
|
{ "fset", cmd_fset, 1, 1, expand_fset, 0, 0 },
|
|
{ "help", cmd_help, 0, 0, NULL, 0, 0 },
|
|
{ "invert", cmd_invert, 0, 0, NULL, 0, 0 },
|
|
{ "live-filter", cmd_live_filter, 0, 1, NULL, 0, CMD_LIVE },
|
|
{ "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
|
|
{ "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
|
|
{ "mark", cmd_mark, 0, 1, NULL, 0, 0 },
|
|
{ "mute", cmd_mute, 0, 0, NULL, 0, 0 },
|
|
{ "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
|
|
{ "player-next-album", cmd_p_next_album, 0, 0, NULL, 0, 0 },
|
|
{ "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
|
|
{ "player-pause-playback", cmd_p_pause_playback, 0, 0, NULL, 0, 0 },
|
|
{ "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
|
|
{ "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
|
|
{ "player-prev-album", cmd_p_prev_album, 0, 0, NULL, 0, 0 },
|
|
{ "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
|
|
{ "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
|
|
{ "left-view", cmd_left_view, 0, 1, NULL, 0, 0 },
|
|
{ "right-view", cmd_right_view, 0, 1, NULL, 0, 0 },
|
|
{ "pl-create", cmd_pl_create, 1, -1, NULL, 0, 0 },
|
|
{ "pl-export", cmd_pl_export, 1, -1, NULL, 0, 0 },
|
|
{ "pl-import", cmd_pl_import, 0, -1, NULL, 0, 0 },
|
|
{ "pl-rename", cmd_pl_rename, 1, -1, NULL, 0, 0 },
|
|
{ "pl-delete", cmd_pl_delete, 1, 1, NULL, 0, 0 },
|
|
{ "push", cmd_push, 0, -1, expand_commands, 0, 0 },
|
|
{ "pwd", cmd_pwd, 0, 0, NULL, 0, 0 },
|
|
{ "raise-vte", cmd_raise_vte, 0, 0, NULL, 0, 0 },
|
|
{ "rand", cmd_rand, 0, 0, NULL, 0, 0 },
|
|
{ "quit", cmd_quit, 0, 1, NULL, 0, 0 },
|
|
{ "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
|
|
{ "reshuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
|
|
{ "run", cmd_run, 1, -1, expand_program_paths, 0, CMD_UNSAFE },
|
|
{ "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
|
|
{ "search-b-start", cmd_search_b_start, 0, 0, NULL, 0, 0 },
|
|
{ "search-next", cmd_search_next, 0, 0, NULL, 0, 0 },
|
|
{ "search-prev", cmd_search_prev, 0, 0, NULL, 0, 0 },
|
|
{ "search-start", cmd_search_start, 0, 0, NULL, 0, 0 },
|
|
{ "seek", cmd_seek, 1, 1, NULL, 0, 0 },
|
|
{ "set", cmd_set, 1, 1, expand_options, 0, 0 },
|
|
{ "shell", cmd_shell, 1, -1, expand_program_paths, 0, CMD_UNSAFE },
|
|
{ "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
|
|
{ "shuffle", cmd_reshuffle, 0, 0, NULL, 0, CMD_HIDDEN },
|
|
{ "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
|
|
{ "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
|
|
{ "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
|
|
{ "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
|
|
{ "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
|
|
{ "update-cache", cmd_update_cache, 0, 1, NULL, 0, 0 },
|
|
{ "version", cmd_version, 0, 0, NULL, 0, 0 },
|
|
{ "view", cmd_view, 1, 1, NULL, 0, 0 },
|
|
{ "vol", cmd_vol, 1, 2, NULL, 0, 0 },
|
|
{ "w", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
|
|
{ "win-activate", cmd_win_activate, 0, 0, NULL, 0, 0 },
|
|
{ "win-add-l", cmd_win_add_l, 0, 1, NULL, 0, 0 },
|
|
{ "win-add-p", cmd_win_add_p, 0, 1, NULL, 0, 0 },
|
|
{ "win-add-Q", cmd_win_add_Q, 0, 1, NULL, 0, 0 },
|
|
{ "win-add-q", cmd_win_add_q, 0, 1, NULL, 0, 0 },
|
|
{ "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
|
|
{ "win-down", cmd_win_down, 0, 1, NULL, 0, 0 },
|
|
{ "win-half-page-down", cmd_win_hf_pg_down, 0, 0, NULL, 0, 0 },
|
|
{ "win-half-page-up", cmd_win_hf_pg_up, 0, 0, NULL, 0, 0 },
|
|
{ "win-mv-after", cmd_win_mv_after, 0, 0, NULL, 0, 0 },
|
|
{ "win-mv-before", cmd_win_mv_before, 0, 0, NULL, 0, 0 },
|
|
{ "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
|
|
{ "win-page-bottom", cmd_win_pg_bottom, 0, 0, NULL, 0, 0 },
|
|
{ "win-page-down", cmd_win_pg_down, 0, 0, NULL, 0, 0 },
|
|
{ "win-page-middle", cmd_win_pg_middle, 0, 0, NULL, 0, 0 },
|
|
{ "win-page-top", cmd_win_pg_top, 0, 0, NULL, 0, 0 },
|
|
{ "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
|
|
{ "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
|
|
{ "win-scroll-down", cmd_win_scroll_down, 0, 0, NULL, 0, 0 },
|
|
{ "win-scroll-up", cmd_win_scroll_up, 0, 0, NULL, 0, 0 },
|
|
{ "win-sel-cur", cmd_win_sel_cur, 0, 0, NULL, 0, 0 },
|
|
{ "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
|
|
{ "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
|
|
{ "win-up", cmd_win_up, 0, 1, NULL, 0, 0 },
|
|
{ "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
|
|
{ "win-update-cache", cmd_win_update_cache, 0, 1, NULL, 0, 0 },
|
|
{ "wq", cmd_quit, 0, 1, NULL, 0, 0 },
|
|
{ NULL, NULL, 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
/* fills tabexp struct */
|
|
static void expand_commands(const char *str)
|
|
{
|
|
int i, len, pos;
|
|
char **tails;
|
|
|
|
/* tabexp is resetted */
|
|
tails = xnew(char *, N_ELEMENTS(commands) - 1);
|
|
len = strlen(str);
|
|
pos = 0;
|
|
for (i = 0; commands[i].name; i++) {
|
|
if (strncmp(str, commands[i].name, len) == 0 && !(commands[i].flags & CMD_HIDDEN))
|
|
tails[pos++] = xstrdup(commands[i].name + len);
|
|
}
|
|
if (pos > 0) {
|
|
if (pos == 1) {
|
|
/* only one command matches, add ' ' */
|
|
char *tmp = xstrjoin(tails[0], " ");
|
|
|
|
free(tails[0]);
|
|
tails[0] = tmp;
|
|
}
|
|
tabexp.head = xstrdup(str);
|
|
tabexp.tails = tails;
|
|
tabexp.count = pos;
|
|
} else {
|
|
free(tails);
|
|
}
|
|
}
|
|
|
|
struct command *get_command(const char *str)
|
|
{
|
|
int i, len;
|
|
|
|
while (*str == ' ')
|
|
str++;
|
|
for (len = 0; str[len] && str[len] != ' '; len++)
|
|
;
|
|
|
|
for (i = 0; commands[i].name; i++) {
|
|
if (strncmp(str, commands[i].name, len))
|
|
continue;
|
|
|
|
if (commands[i].name[len] == 0) {
|
|
/* exact */
|
|
return &commands[i];
|
|
}
|
|
|
|
if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
|
|
/* ambiguous */
|
|
return NULL;
|
|
}
|
|
return &commands[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* fills tabexp struct */
|
|
static void expand_command_line(const char *str)
|
|
{
|
|
/* :command [arg]...
|
|
*
|
|
* examples:
|
|
*
|
|
* str expanded value (tabexp.head)
|
|
* -------------------------------------
|
|
* fs fset
|
|
* b c bind common
|
|
* se se (tabexp.tails = [ ek t ])
|
|
*/
|
|
/* command start/end, argument start */
|
|
const char *cs, *ce, *as;
|
|
const struct command *cmd;
|
|
|
|
cs = str;
|
|
ce = strchr(cs, ' ');
|
|
if (ce == NULL) {
|
|
/* expand command */
|
|
expand_commands(cs);
|
|
return;
|
|
}
|
|
|
|
/* command must be expandable */
|
|
cmd = get_command(cs);
|
|
if (cmd == NULL) {
|
|
/* command ambiguous or invalid */
|
|
return;
|
|
}
|
|
|
|
if (cmd->expand == NULL) {
|
|
/* can't expand argument */
|
|
return;
|
|
}
|
|
|
|
as = ce;
|
|
while (*as == ' ')
|
|
as++;
|
|
|
|
/* expand argument */
|
|
cmd->expand(as);
|
|
if (tabexp.head == NULL) {
|
|
/* argument expansion failed */
|
|
return;
|
|
}
|
|
|
|
/* tabexp.head is now start of the argument string */
|
|
snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
|
|
free(tabexp.head);
|
|
tabexp.head = xstrdup(expbuf);
|
|
}
|
|
|
|
static void tab_expand(int direction)
|
|
{
|
|
char *s1, *s2, *tmp;
|
|
int pos;
|
|
|
|
/* strip white space */
|
|
pos = 0;
|
|
while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
|
|
pos++;
|
|
|
|
/* string to expand */
|
|
s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
|
|
|
|
/* tail */
|
|
s2 = xstrdup(cmdline.line + cmdline.bpos);
|
|
|
|
tmp = tabexp_expand(s1, expand_command_line, direction);
|
|
if (tmp) {
|
|
/* tmp.s2 */
|
|
int l1, l2;
|
|
|
|
l1 = strlen(tmp);
|
|
l2 = strlen(s2);
|
|
cmdline.blen = l1 + l2;
|
|
if (cmdline.blen >= cmdline.size) {
|
|
while (cmdline.blen >= cmdline.size)
|
|
cmdline.size *= 2;
|
|
cmdline.line = xrenew(char, cmdline.line, cmdline.size);
|
|
}
|
|
sprintf(cmdline.line, "%s%s", tmp, s2);
|
|
cmdline.bpos = l1;
|
|
cmdline.cpos = u_strlen_safe(tmp);
|
|
cmdline.clen = u_strlen_safe(cmdline.line);
|
|
free(tmp);
|
|
}
|
|
free(s1);
|
|
free(s2);
|
|
}
|
|
|
|
static void reset_tab_expansion(void)
|
|
{
|
|
tabexp_reset();
|
|
arg_expand_cmd = -1;
|
|
}
|
|
|
|
static void cmdline_modified(void)
|
|
{
|
|
char *cmd, *arg;
|
|
struct command *c;
|
|
|
|
if (!parse_command(cmdline.line, &cmd, &arg))
|
|
return;
|
|
|
|
c = get_command(cmd);
|
|
if (!c)
|
|
goto end;
|
|
|
|
if (c->flags & CMD_LIVE)
|
|
run_parsed_command(cmd, arg);
|
|
|
|
end:
|
|
free(cmd);
|
|
free(arg);
|
|
}
|
|
|
|
int parse_command(const char *buf, char **cmdp, char **argp)
|
|
{
|
|
int cmd_start, cmd_end, cmd_len;
|
|
int arg_start, arg_end;
|
|
int i;
|
|
|
|
i = 0;
|
|
while (buf[i] && buf[i] == ' ')
|
|
i++;
|
|
|
|
if (buf[i] == '#')
|
|
return 0;
|
|
|
|
cmd_start = i;
|
|
while (buf[i] && buf[i] != ' ')
|
|
i++;
|
|
cmd_end = i;
|
|
while (buf[i] && buf[i] == ' ')
|
|
i++;
|
|
arg_start = i;
|
|
while (buf[i])
|
|
i++;
|
|
arg_end = i;
|
|
|
|
cmd_len = cmd_end - cmd_start;
|
|
if (cmd_len == 0)
|
|
return 0;
|
|
|
|
*cmdp = xstrndup(buf + cmd_start, cmd_len);
|
|
if (arg_start == arg_end) {
|
|
*argp = NULL;
|
|
} else {
|
|
*argp = xstrndup(buf + arg_start, arg_end - arg_start);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int run_only_safe_commands;
|
|
|
|
void run_parsed_command(char *cmd, char *arg)
|
|
{
|
|
int cmd_len = strlen(cmd);
|
|
int i = 0;
|
|
|
|
while (1) {
|
|
const struct command *c = &commands[i];
|
|
|
|
if (c->name == NULL) {
|
|
error_msg("unknown command\n");
|
|
break;
|
|
}
|
|
if (strncmp(cmd, c->name, cmd_len) == 0) {
|
|
const char *next = commands[i + 1].name;
|
|
int exact = c->name[cmd_len] == 0;
|
|
|
|
if (!exact && next && strncmp(cmd, next, cmd_len) == 0) {
|
|
error_msg("ambiguous command\n");
|
|
break;
|
|
}
|
|
if (c->min_args > 0 && arg == NULL) {
|
|
error_msg("not enough arguments\n");
|
|
break;
|
|
}
|
|
if (c->max_args == 0 && arg) {
|
|
error_msg("too many arguments\n");
|
|
break;
|
|
}
|
|
if (run_only_safe_commands && (c->flags & CMD_UNSAFE)) {
|
|
if (c->func != cmd_save || !is_stdout_filename(arg)) {
|
|
d_print("trying to execute unsafe command over net\n");
|
|
break;
|
|
}
|
|
}
|
|
c->func(arg);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void run_command(const char *buf)
|
|
{
|
|
char *cmd, *arg;
|
|
|
|
if (!parse_command(buf, &cmd, &arg))
|
|
return;
|
|
|
|
run_parsed_command(cmd, arg);
|
|
free(arg);
|
|
free(cmd);
|
|
}
|
|
|
|
static void reset_history_search(void)
|
|
{
|
|
history_reset_search(&cmd_history);
|
|
free(history_search_text);
|
|
history_search_text = NULL;
|
|
}
|
|
|
|
static void backspace(void)
|
|
{
|
|
if (cmdline.clen > 0) {
|
|
cmdline_backspace();
|
|
} else {
|
|
input_mode = NORMAL_MODE;
|
|
}
|
|
}
|
|
|
|
void command_mode_ch(uchar ch)
|
|
{
|
|
switch (ch) {
|
|
case 0x01: // ^A
|
|
cmdline_move_home();
|
|
break;
|
|
case 0x02: // ^B
|
|
cmdline_move_left();
|
|
break;
|
|
case 0x04: // ^D
|
|
cmdline_delete_ch();
|
|
cmdline_modified();
|
|
break;
|
|
case 0x05: // ^E
|
|
cmdline_move_end();
|
|
break;
|
|
case 0x06: // ^F
|
|
cmdline_move_right();
|
|
break;
|
|
case 0x03: // ^C
|
|
case 0x07: // ^G
|
|
case 0x1B: // ESC
|
|
if (cmdline.blen) {
|
|
history_add_line(&cmd_history, cmdline.line);
|
|
cmdline_clear();
|
|
}
|
|
input_mode = NORMAL_MODE;
|
|
break;
|
|
case 0x10: // ^P
|
|
command_mode_key(KEY_UP);
|
|
return;
|
|
case 0xE: // ^N
|
|
command_mode_key(KEY_DOWN);
|
|
return;
|
|
case 0x0A:
|
|
if (cmdline.blen) {
|
|
run_command(cmdline.line);
|
|
history_add_line(&cmd_history, cmdline.line);
|
|
cmdline_clear();
|
|
}
|
|
input_mode = NORMAL_MODE;
|
|
break;
|
|
case 0x0B:
|
|
cmdline_clear_end();
|
|
cmdline_modified();
|
|
break;
|
|
case 0x09:
|
|
tab_expand(1);
|
|
break;
|
|
case 0x15:
|
|
cmdline_backspace_to_bol();
|
|
cmdline_modified();
|
|
break;
|
|
case 0x17: // ^W
|
|
cmdline_backward_delete_word(cmdline_word_delimiters);
|
|
cmdline_modified();
|
|
break;
|
|
case 0x08: // ^H
|
|
case 127:
|
|
backspace();
|
|
cmdline_modified();
|
|
break;
|
|
default:
|
|
cmdline_insert_ch(ch);
|
|
cmdline_modified();
|
|
}
|
|
reset_history_search();
|
|
if (ch != 0x09)
|
|
reset_tab_expansion();
|
|
}
|
|
|
|
void command_mode_escape(int c)
|
|
{
|
|
switch (c) {
|
|
case 98:
|
|
cmdline_backward_word(cmdline_filename_delimiters);
|
|
break;
|
|
case 100:
|
|
cmdline_delete_word(cmdline_filename_delimiters);
|
|
cmdline_modified();
|
|
break;
|
|
case 102:
|
|
cmdline_forward_word(cmdline_filename_delimiters);
|
|
break;
|
|
case 127:
|
|
case KEY_BACKSPACE:
|
|
cmdline_backward_delete_word(cmdline_filename_delimiters);
|
|
cmdline_modified();
|
|
break;
|
|
}
|
|
reset_history_search();
|
|
}
|
|
|
|
void command_mode_key(int key)
|
|
{
|
|
if (key != KEY_BTAB)
|
|
reset_tab_expansion();
|
|
switch (key) {
|
|
case KEY_DC:
|
|
cmdline_delete_ch();
|
|
cmdline_modified();
|
|
break;
|
|
case KEY_BACKSPACE:
|
|
backspace();
|
|
cmdline_modified();
|
|
break;
|
|
case KEY_LEFT:
|
|
cmdline_move_left();
|
|
return;
|
|
case KEY_RIGHT:
|
|
cmdline_move_right();
|
|
return;
|
|
case KEY_HOME:
|
|
cmdline_move_home();
|
|
return;
|
|
case KEY_END:
|
|
cmdline_move_end();
|
|
return;
|
|
case KEY_UP:
|
|
{
|
|
const char *s;
|
|
|
|
if (history_search_text == NULL)
|
|
history_search_text = xstrdup(cmdline.line);
|
|
s = history_search_forward(&cmd_history, history_search_text);
|
|
if (s)
|
|
cmdline_set_text(s);
|
|
}
|
|
return;
|
|
case KEY_DOWN:
|
|
if (history_search_text) {
|
|
const char *s;
|
|
|
|
s = history_search_backward(&cmd_history, history_search_text);
|
|
if (s) {
|
|
cmdline_set_text(s);
|
|
} else {
|
|
cmdline_set_text(history_search_text);
|
|
}
|
|
}
|
|
return;
|
|
case KEY_BTAB:
|
|
tab_expand(-1);
|
|
break;
|
|
default:
|
|
d_print("key = %c (%d)\n", key, key);
|
|
}
|
|
reset_history_search();
|
|
}
|
|
|
|
void command_mode_mouse(MEVENT *event)
|
|
{
|
|
if ((event->bstate & BUTTON1_PRESSED) || (event->bstate & BUTTON3_PRESSED)) {
|
|
if (event->y <= window_get_nr_rows(current_win()) + 2) {
|
|
if (cmdline.blen) {
|
|
history_add_line(&cmd_history, cmdline.line);
|
|
cmdline_clear();
|
|
}
|
|
input_mode = NORMAL_MODE;
|
|
normal_mode_mouse(event);
|
|
return;
|
|
}
|
|
if (event->x == 0)
|
|
return;
|
|
int i = event->x > cmdline.clen ? cmdline.clen : event->x - 1;
|
|
while (i < cmdline.cpos)
|
|
cmdline_move_left();
|
|
while (i > cmdline.cpos)
|
|
cmdline_move_right();
|
|
} else if (event->bstate & BUTTON4_PRESSED) {
|
|
command_mode_key(KEY_UP);
|
|
} else if (event->bstate & BUTTON5_PRESSED) {
|
|
command_mode_key(KEY_DOWN);
|
|
}
|
|
}
|
|
|
|
void commands_init(void)
|
|
{
|
|
cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
|
|
history_load(&cmd_history, cmd_history_filename, 2000);
|
|
}
|
|
|
|
void commands_exit(void)
|
|
{
|
|
view_clear(TREE_VIEW);
|
|
view_clear(SORTED_VIEW);
|
|
view_clear(PLAYLIST_VIEW);
|
|
view_clear(QUEUE_VIEW);
|
|
history_save(&cmd_history);
|
|
history_free(&cmd_history);
|
|
free(cmd_history_filename);
|
|
tabexp_reset();
|
|
}
|