/*
* 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 .
*/
#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
#include
#include
#include
#include
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, "lp");
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