/* * This file is part of MPlayer. * * MPlayer 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. * * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include "mp_msg.h" #include "m_option.h" #include "m_config.h" #include "playlist.h" #include "playlist_parser.h" #include "parser-mpcmd.h" #include "osdep/macosx_finder_args.h" #define GLOBAL 0 #define LOCAL 1 #define dvd_range(a) (a > 0 && a < 256) struct parse_state { struct m_config *config; int argc; char **argv; bool no_more_opts; bool error; const struct m_option *mp_opt; // NULL <=> it's a file arg struct bstr arg; struct bstr param; }; // Returns 0 if a valid option/file is available, <0 on error, 1 on end of args. static int split_opt_silent(struct parse_state *p) { assert(!p->error); if (p->argc < 1) return 1; p->mp_opt = NULL; p->arg = bstr0(p->argv[0]); p->param = bstr0(NULL); p->argc--; p->argv++; if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1) return 0; bool old_syntax = !bstr_startswith0(p->arg, "--"); if (old_syntax) { p->arg = bstr_cut(p->arg, 1); } else { p->arg = bstr_cut(p->arg, 2); int idx = bstrchr(p->arg, '='); if (idx > 0) { p->param = bstr_cut(p->arg, idx + 1); p->arg = bstr_splice(p->arg, 0, idx); } } p->mp_opt = m_config_get_option(p->config, p->arg); if (!p->mp_opt) { // Automagic "no-" arguments: "--no-bla" turns into "--bla=no". if (!bstr_startswith0(p->arg, "no-")) return -1; struct bstr s = bstr_cut(p->arg, 3); p->mp_opt = m_config_get_option(p->config, s); if (!p->mp_opt || p->mp_opt->type != &m_option_type_flag) return -1; // Avoid allowing "--no-no-bla". if (bstr_startswith(bstr0(p->mp_opt->name), bstr0("no-"))) return -1; // Flag options never have parameters. old_syntax = false; if (p->param.len) return -2; p->arg = s; p->param = bstr0("no"); } if (bstr_endswith0(p->arg, "-clr")) old_syntax = false; if (old_syntax && !(p->mp_opt->type->flags & M_OPT_TYPE_OLD_SYNTAX_NO_PARAM)) { if (p->argc < 1) return -3; p->param = bstr0(p->argv[0]); p->argc--; p->argv++; } return 0; } // Returns true if more args, false if all parsed or an error occurred. static bool split_opt(struct parse_state *p) { int r = split_opt_silent(p); if (r >= 0) return r == 0; p->error = true; if (r == -2) mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "A no-* option can't take parameters: --%.*s=%.*s\n", BSTR_P(p->arg), BSTR_P(p->param)); else if (r == -3) mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s needs a parameter.\n", BSTR_P(p->arg)); else mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Unknown option on the command line: %.*s\n", BSTR_P(p->arg)); return false; } static bool parse_flag(bstr name, bstr f) { struct m_option opt = {NULL, NULL, CONF_TYPE_FLAG, 0, 0, 1, NULL}; int val = 0; m_option_parse(&opt, name, f, &val); return !!val; } bool m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, int argc, char **argv) { int mode = 0; bool opt_exit = false; // exit immediately after parsing (help options) struct playlist_entry *local_start = NULL; bool shuffle = false; int local_params_count = 0; struct playlist_param *local_params = 0; assert(config != NULL); assert(!config->file_local_mode); config->mode = M_COMMAND_LINE; mode = GLOBAL; #ifdef CONFIG_MACOSX_FINDER if (macosx_finder_args(config, files, argc, argv)) return true; #endif struct parse_state p = {config, argc, argv}; while (split_opt(&p)) { if (p.mp_opt) { int r; if (mode == GLOBAL && !(p.mp_opt->flags & M_OPT_PRE_PARSE)) { r = m_config_set_option(config, p.arg, p.param); } else { r = m_config_check_option(config, p.arg, p.param); } if (r <= M_OPT_EXIT) { opt_exit = true; r = M_OPT_EXIT - r; } else if (r < 0) { char *msg = m_option_strerror(r); if (!msg) goto print_err; mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL, "Error parsing commandline option %.*s: %s\n", BSTR_P(p.arg), msg); goto err_out; } // Handle some special arguments outside option parser. if (!bstrcmp0(p.arg, "{")) { if (mode != GLOBAL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "'--{' can not be nested.\n"); goto err_out; } mode = LOCAL; // Needed for option checking. m_config_enter_file_local(config); assert(!local_start); local_start = files->last; continue; } if (!bstrcmp0(p.arg, "}")) { if (mode != LOCAL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Too many closing '--}'.\n"); goto err_out; } if (local_params_count) { // The files added between '{' and '}' are the entries from // the entry _after_ local_start, until the end of the list. // If local_start is NULL, the list was empty on '{', and we // want all files in the list. struct playlist_entry *cur = local_start ? local_start->next : files->first; if (!cur) mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Ignored options!\n"); while (cur) { playlist_entry_add_params(cur, local_params, local_params_count); cur = cur->next; } } local_params_count = 0; mode = GLOBAL; m_config_leave_file_local(config); local_start = NULL; shuffle = false; continue; } if (bstrcmp0(p.arg, "shuffle") == 0) { shuffle = parse_flag(p.arg, p.param); continue; } if (bstrcmp0(p.arg, "playlist") == 0) { // append the playlist to the local args char *param0 = bstrdup0(NULL, p.param); struct playlist *pl = playlist_parse_file(param0); talloc_free(param0); if (!pl) goto print_err; playlist_transfer_entries(files, pl); talloc_free(pl); continue; } if (bstrcmp0(p.arg, "v") == 0) { verbose++; continue; } if (mode == LOCAL) { MP_TARRAY_APPEND(NULL, local_params, local_params_count, (struct playlist_param) {p.arg, p.param}); } } else { // filename bstr file = p.arg; char *file0 = bstrdup0(NULL, p.arg); // expand DVD filename entries like dvd://1-3 into component titles if (bstr_startswith0(file, "dvd://")) { int offset = 6; char *splitpos = strstr(file0 + offset, "-"); if (splitpos != NULL) { int start_title = strtol(file0 + offset, NULL, 10); int end_title; //entries like dvd://-2 imply start at title 1 if (start_title < 0) { end_title = abs(start_title); start_title = 1; } else end_title = strtol(splitpos + 1, NULL, 10); if (dvd_range(start_title) && dvd_range(end_title) && (start_title < end_title)) { for (int j = start_title; j <= end_title; j++) { char entbuf[15]; snprintf(entbuf, sizeof(entbuf), "dvd://%d", j); playlist_add_file(files, entbuf); } } else mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Invalid play entry %s\n", file0); } else // dvd:// or dvd://x entry playlist_add_file(files, file0); } else playlist_add_file(files, file0); talloc_free(file0); // Lock stdin if it will be used as input if (bstrcmp0(file, "-") == 0) m_config_set_option0(config, "consolecontrols", "no"); } } if (p.error) goto err_out; if (mode != GLOBAL) { mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Missing closing --} on command line.\n"); goto err_out; } if (opt_exit) goto err_out; if (shuffle) playlist_shuffle(files); talloc_free(local_params); assert(!config->file_local_mode); return true; print_err: mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL, "Error parsing option on the command line: %.*s\n", BSTR_P(p.arg)); err_out: talloc_free(local_params); if (config->file_local_mode) m_config_leave_file_local(config); return false; } extern int mp_msg_levels[]; /* Parse some command line options early before main parsing. * --noconfig prevents reading configuration files (otherwise done before * command line parsing), and --really-quiet suppresses messages printed * during normal options parsing. */ void m_config_preparse_command_line(m_config_t *config, int argc, char **argv) { // Hack to shut up parser error messages int msg_lvl_backup = mp_msg_levels[MSGT_CFGPARSER]; mp_msg_levels[MSGT_CFGPARSER] = -11; struct parse_state p = {config, argc, argv}; while (split_opt_silent(&p) == 0) { if (p.mp_opt) { // Ignore non-pre-parse options. They will be set later. // Option parsing errors will be handled later as well. if (p.mp_opt->flags & M_OPT_PRE_PARSE) m_config_set_option(config, p.arg, p.param); } } mp_msg_levels[MSGT_CFGPARSER] = msg_lvl_backup; }