diff --git a/cfg-mplayer.h b/cfg-mplayer.h index b3b0ab91c3..93fb58f480 100644 --- a/cfg-mplayer.h +++ b/cfg-mplayer.h @@ -988,7 +988,7 @@ const m_option_t mplayer_opts[]={ {"slave", &slave_mode, CONF_TYPE_FLAG,CONF_GLOBAL , 0, 1, NULL}, OPT_MAKE_FLAGS("idle", player_idle_mode, CONF_GLOBAL), {"use-stdin", "-use-stdin has been renamed to -noconsolecontrols, use that instead.", CONF_TYPE_PRINT, 0, 0, 0, NULL}, - OPT_INTRANGE("key-fifo-size", key_fifo_size, CONF_GLOBAL, 2, 65000), + OPT_INTRANGE("key-fifo-size", input.key_fifo_size, CONF_GLOBAL, 2, 65000), OPT_MAKE_FLAGS("consolecontrols", consolecontrols, CONF_GLOBAL), {"mouse-movements", &enable_mouse_movements, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL}, {"nomouse-movements", &enable_mouse_movements, CONF_TYPE_FLAG, CONF_GLOBAL, 1, 0, NULL}, diff --git a/defaultopts.c b/defaultopts.c index 8bb927bb54..b498fef026 100644 --- a/defaultopts.c +++ b/defaultopts.c @@ -32,7 +32,6 @@ void set_default_mplayer_options(struct MPOpts *opts) .initial_audio_sync = 1, .term_osd = 1, .term_osd_esc = "\x1b[A\r\x1b[K", - .key_fifo_size = 7, .consolecontrols = 1, .doubleclick_time = 300, .audio_id = -1, @@ -58,6 +57,7 @@ void set_default_mplayer_options(struct MPOpts *opts) }, .input = { .config_file = "input.conf", + .key_fifo_size = 7, .ar_delay = 100, .ar_rate = 8, .use_joystick = 1, diff --git a/input/input.c b/input/input.c index b8ff15a0c7..fe7acbb3e7 100644 --- a/input/input.c +++ b/input/input.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "input.h" #include "mp_fifo.h" @@ -550,8 +551,6 @@ static const struct cmd_bind def_cmd_binds[] = { #define MP_MAX_CMD_FD 10 #endif -#define CMD_QUEUE_SIZE 100 - struct input_fd { int fd; union { @@ -582,6 +581,13 @@ struct cmd_bind_section { struct cmd_bind_section *next; }; +struct cmd_queue { + struct mp_cmd *first; + struct mp_cmd *last; + int num_cmds; + int num_abort_cmds; +}; + struct input_ctx { // Autorepeat stuff short ar_state; @@ -590,6 +596,9 @@ struct input_ctx { // Autorepeat config unsigned int ar_delay; unsigned int ar_rate; + // Maximum number of queued commands from keypresses (limit to avoid + // repeated slow commands piling up) + int key_fifo_size; // these are the keys currently down int key_down[MP_MAX_KEY_DOWN]; @@ -605,14 +614,18 @@ struct input_ctx { struct cmd_bind *cmd_binds; struct cmd_bind *cmd_binds_default; + // Used to track whether we managed to read something while checking + // events sources. If yes, the sources may have more queued. + bool got_new_events; + struct input_fd key_fds[MP_MAX_KEY_FD]; unsigned int num_key_fd; struct input_fd cmd_fds[MP_MAX_CMD_FD]; unsigned int num_cmd_fd; - mp_cmd_t *cmd_queue[CMD_QUEUE_SIZE]; - unsigned int cmd_queue_length, cmd_queue_start, cmd_queue_end; + struct cmd_queue key_cmd_queue; + struct cmd_queue control_cmd_queue; }; @@ -685,6 +698,46 @@ static char *get_key_combo_name(int *keys, int max) return ret; } +static bool is_abort_cmd(int cmd_id) +{ + switch (cmd_id) { + case MP_CMD_QUIT: + case MP_CMD_PLAY_TREE_STEP: + case MP_CMD_PLAY_TREE_UP_STEP: + case MP_CMD_PLAY_ALT_SRC_STEP: + return true; + } + return false; +} + +static void queue_pop(struct cmd_queue *queue) +{ + assert(queue->num_cmds > 0); + struct mp_cmd *cmd = queue->first; + queue->first = cmd->queue_next; + queue->num_cmds--; + queue->num_abort_cmds -= is_abort_cmd(cmd->id); +} + +static void queue_add(struct cmd_queue *queue, struct mp_cmd *cmd, + bool at_head) +{ + if (!queue->num_cmds) { + queue->first = cmd; + queue->last = cmd; + } else if (at_head) { + queue->first->queue_prev = cmd; + cmd->queue_next = queue->first; + queue->first = cmd; + } else { + queue->last->queue_next = cmd; + cmd->queue_prev = queue->last; + queue->last = cmd; + } + queue->num_cmds++; + queue->num_abort_cmds += is_abort_cmd(cmd->id); +} + int mp_input_add_cmd_fd(struct input_ctx *ictx, int fd, int select, int read_func(int fd, char *dest, int size), int close_func(int fd)) @@ -1261,193 +1314,199 @@ static mp_cmd_t *check_autorepeat(struct input_ctx *ictx) return NULL; } +void mp_input_feed_key(struct input_ctx *ictx, int code) +{ + ictx->got_new_events = true; + if (code == MP_INPUT_RELEASE_ALL) { + memset(ictx->key_down, 0, sizeof(ictx->key_down)); + ictx->num_key_down = 0; + ictx->last_key_down = 0; + return; + } + struct mp_cmd *cmd = interpret_key(ictx, code); + if (!cmd) + return; + struct cmd_queue *queue = &ictx->key_cmd_queue; + if (queue->num_cmds >= ictx->key_fifo_size && + (!is_abort_cmd(cmd->id) || queue->num_abort_cmds)) + return; + queue_add(queue, cmd, false); +} + +static void read_cmd_fd(struct input_ctx *ictx, struct input_fd *cmd_fd) +{ + int r; + char *text; + while ((r = read_cmd(cmd_fd, &text)) >= 0) { + ictx->got_new_events = true; + struct mp_cmd *cmd = mp_input_parse_cmd(text); + talloc_free(text); + if (cmd) + queue_add(&ictx->control_cmd_queue, cmd, false); + if (!cmd_fd->got_cmd) + return; + } + if (r == MP_INPUT_ERROR) + mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error on command file descriptor %d\n", + cmd_fd->fd); + else if (r == MP_INPUT_DEAD) + cmd_fd->dead = true; +} + +static void read_key_fd(struct input_ctx *ictx, struct input_fd *key_fd) +{ + int code = key_fd->read_func.key(key_fd->ctx, key_fd->fd); + if (code >= 0 || code == MP_INPUT_RELEASE_ALL) { + mp_input_feed_key(ictx, code); + return; + } + + if (code == MP_INPUT_ERROR) + mp_tmsg(MSGT_INPUT, MSGL_ERR, + "Error on key input file descriptor %d\n", key_fd->fd); + else if (code == MP_INPUT_DEAD) { + mp_tmsg(MSGT_INPUT, MSGL_ERR, + "Dead key input on file descriptor %d\n", key_fd->fd); + key_fd->dead = true; + } +} /** * \param time time to wait at most for an event in milliseconds */ -static mp_cmd_t *read_events(struct input_ctx *ictx, int time) +static void read_events(struct input_ctx *ictx, int time) { - int i; - int got_cmd = 0; + ictx->got_new_events = false; struct input_fd *key_fds = ictx->key_fds; struct input_fd *cmd_fds = ictx->cmd_fds; - for (i = 0; i < ictx->num_key_fd; i++) + for (int i = 0; i < ictx->num_key_fd; i++) if (key_fds[i].dead) { mp_input_rm_key_fd(ictx, key_fds[i].fd); i--; - } - for (i = 0; i < ictx->num_cmd_fd; i++) + } else if (time && key_fds[i].no_select) + read_key_fd(ictx, &key_fds[i]); + for (int i = 0; i < ictx->num_cmd_fd; i++) if (cmd_fds[i].dead || cmd_fds[i].eof) { mp_input_rm_cmd_fd(ictx, cmd_fds[i].fd); i--; - } else if (cmd_fds[i].got_cmd) - got_cmd = 1; + } else if (time && cmd_fds[i].no_select) + read_cmd_fd(ictx, &cmd_fds[i]); + if (ictx->got_new_events) + time = 0; #ifdef HAVE_POSIX_SELECT fd_set fds; FD_ZERO(&fds); - if (!got_cmd) { - int max_fd = 0; - for (i = 0; i < ictx->num_key_fd; i++) { - if (key_fds[i].no_select) - continue; - if (key_fds[i].fd > max_fd) - max_fd = key_fds[i].fd; - FD_SET(key_fds[i].fd, &fds); - } - for (i = 0; i < ictx->num_cmd_fd; i++) { - if (cmd_fds[i].no_select) - continue; - if (cmd_fds[i].fd > max_fd) - max_fd = cmd_fds[i].fd; - FD_SET(cmd_fds[i].fd, &fds); - } - struct timeval tv, *time_val; - if (time >= 0) { - tv.tv_sec = time / 1000; - tv.tv_usec = (time % 1000) * 1000; - time_val = &tv; - } else - time_val = NULL; - if (select(max_fd + 1, &fds, NULL, NULL, time_val) < 0) { - if (errno != EINTR) - mp_tmsg(MSGT_INPUT, MSGL_ERR, "Select error: %s\n", - strerror(errno)); - FD_ZERO(&fds); - } + int max_fd = 0; + for (int i = 0; i < ictx->num_key_fd; i++) { + if (key_fds[i].no_select) + continue; + if (key_fds[i].fd > max_fd) + max_fd = key_fds[i].fd; + FD_SET(key_fds[i].fd, &fds); + } + for (int i = 0; i < ictx->num_cmd_fd; i++) { + if (cmd_fds[i].no_select) + continue; + if (cmd_fds[i].fd > max_fd) + max_fd = cmd_fds[i].fd; + FD_SET(cmd_fds[i].fd, &fds); + } + struct timeval tv, *time_val; + if (time >= 0) { + tv.tv_sec = time / 1000; + tv.tv_usec = (time % 1000) * 1000; + time_val = &tv; + } else + time_val = NULL; + if (select(max_fd + 1, &fds, NULL, NULL, time_val) < 0) { + if (errno != EINTR) + mp_tmsg(MSGT_INPUT, MSGL_ERR, "Select error: %s\n", + strerror(errno)); + FD_ZERO(&fds); } #else - if (!got_cmd && time) + if (time) usec_sleep(time * 1000); #endif - for (i = 0; i < ictx->num_key_fd; i++) { + for (int i = 0; i < ictx->num_key_fd; i++) { #ifdef HAVE_POSIX_SELECT if (!key_fds[i].no_select && !FD_ISSET(key_fds[i].fd, &fds)) continue; #endif - - int code; - while (1) { - code = key_fds[i].read_func.key(key_fds[i].ctx, key_fds[i].fd); - if (code < 0) { - if (code == MP_INPUT_RELEASE_ALL) { - memset(ictx->key_down, 0, sizeof(ictx->key_down)); - ictx->num_key_down = 0; - ictx->last_key_down = 0; - continue; - } - break; - } - mp_cmd_t *ret = interpret_key(ictx, code); - if (ret) - return ret; - } - if (code == MP_INPUT_ERROR) - mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error on key input " - "file descriptor %d\n", key_fds[i].fd); - else if (code == MP_INPUT_DEAD) { - mp_tmsg(MSGT_INPUT, MSGL_ERR, "Dead key input on " - "file descriptor %d\n", key_fds[i].fd); - key_fds[i].dead = 1; - } + read_key_fd(ictx, &key_fds[i]); } - mp_cmd_t *autorepeat_cmd = check_autorepeat(ictx); - if (autorepeat_cmd) - return autorepeat_cmd; - for (i = 0; i < ictx->num_cmd_fd; i++) { + for (int i = 0; i < ictx->num_cmd_fd; i++) { #ifdef HAVE_POSIX_SELECT - if (!cmd_fds[i].no_select && !FD_ISSET(cmd_fds[i].fd, &fds) && - !cmd_fds[i].got_cmd) + if (!cmd_fds[i].no_select && !FD_ISSET(cmd_fds[i].fd, &fds)) continue; #endif - char *cmd; - int r; - while ((r = read_cmd(&cmd_fds[i], &cmd)) >= 0) { - mp_cmd_t *ret = mp_input_parse_cmd(cmd); - talloc_free(cmd); - if (ret) - return ret; - } - if (r == MP_INPUT_ERROR) - mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error on command " - "file descriptor %d\n", cmd_fds[i].fd); - else if (r == MP_INPUT_DEAD) - cmd_fds[i].dead = 1; + read_cmd_fd(ictx, &cmd_fds[i]); } - - return NULL; } +/* To support blocking file descriptors we don't loop the read over + * every source until it's known to be empty. Instead we use this wrapper + * to run select() again. + */ +static void read_all_events(struct input_ctx *ictx, int time) +{ + while (1) { + read_events(ictx, time); + if (!ictx->got_new_events) + return; + time = 0; + } +} int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd) { - if (!cmd || ictx->cmd_queue_length >= CMD_QUEUE_SIZE) + ictx->got_new_events = true; + if (!cmd) return 0; - ictx->cmd_queue[ictx->cmd_queue_end] = cmd; - ictx->cmd_queue_end = (ictx->cmd_queue_end + 1) % CMD_QUEUE_SIZE; - ictx->cmd_queue_length++; + queue_add(&ictx->control_cmd_queue, cmd, true); return 1; } -static mp_cmd_t *get_queued_cmd(struct input_ctx *ictx, int peek_only) -{ - mp_cmd_t *ret; - - if (ictx->cmd_queue_length == 0) - return NULL; - - ret = ictx->cmd_queue[ictx->cmd_queue_start]; - - if (!peek_only) { - ictx->cmd_queue_length--; - ictx->cmd_queue_start = (ictx->cmd_queue_start + 1) % CMD_QUEUE_SIZE; - } - - return ret; -} - /** * \param peek_only when set, the returned command stays in the queue. * Do not free the returned cmd whe you set this! */ mp_cmd_t *mp_input_get_cmd(struct input_ctx *ictx, int time, int peek_only) { - mp_cmd_t *ret = NULL; - struct cmd_filter *cf; - int from_queue; - if (async_quit_request) return mp_input_parse_cmd("quit 1"); - while (1) { - from_queue = 1; - ret = get_queued_cmd(ictx, peek_only); - if (ret) - break; - from_queue = 0; - ret = read_events(ictx, time); - if (!ret) { - from_queue = 1; - ret = get_queued_cmd(ictx, peek_only); - } - break; - } - if (!ret) - return NULL; - for (cf = cmd_filters; cf; cf = cf->next) { - if (cf->filter(ret, cf->ctx)) { - if (peek_only && from_queue) - // The filter ate the cmd, so we remove it from queue - ret = get_queued_cmd(ictx, 0); - mp_cmd_free(ret); + if (ictx->control_cmd_queue.num_cmds || ictx->key_cmd_queue.num_cmds) + time = 0; + read_all_events(ictx, time); + struct mp_cmd *ret; + struct cmd_queue *queue = &ictx->control_cmd_queue; + if (!queue->num_cmds) + queue = &ictx->key_cmd_queue; + if (!queue->num_cmds) { + queue = NULL; + ret = check_autorepeat(ictx); + if (!ret) return NULL; + } else + ret = queue->first; + + for (struct cmd_filter *cf = cmd_filters; cf; cf = cf->next) { + if (cf->filter(ret, cf->ctx)) { + // The filter ate the cmd, so remove it from the queue + if (queue) + queue_pop(queue); + mp_cmd_free(ret); + // Retry with next command + return mp_input_get_cmd(ictx, 0, peek_only); } } - if (!from_queue && peek_only) - mp_input_queue_cmd(ictx, ret); + if (!peek_only && queue) + queue_pop(queue); return ret; } @@ -1752,6 +1811,7 @@ struct input_ctx *mp_input_init(struct input_conf *input_conf) { struct input_ctx *ictx = talloc_ptrtype(NULL, ictx); *ictx = (struct input_ctx){ + .key_fifo_size = input_conf->key_fifo_size, .ar_state = -1, .ar_delay = input_conf->ar_delay, .ar_rate = input_conf->ar_rate, @@ -1917,19 +1977,15 @@ static int print_cmd_list(m_option_t *cfg) */ int mp_input_check_interrupt(struct input_ctx *ictx, int time) { - mp_cmd_t *cmd; - if ((cmd = mp_input_get_cmd(ictx, time, 1)) == NULL) - return 0; - switch (cmd->id) { - case MP_CMD_QUIT: - case MP_CMD_PLAY_TREE_STEP: - case MP_CMD_PLAY_TREE_UP_STEP: - case MP_CMD_PLAY_ALT_SRC_STEP: - // The cmd will be executed when we are back in the main loop - return 1; + for (int i = 0; ; i++) { + if (async_quit_request || ictx->key_cmd_queue.num_abort_cmds || + ictx->control_cmd_queue.num_abort_cmds) { + mp_tmsg(MSGT_INPUT, MSGL_WARN, "Received command to move to " + "another file. Aborting current processing.\n"); + return true; + } + if (i) + return false; + read_all_events(ictx, time); } - // remove the cmd from the queue - cmd = mp_input_get_cmd(ictx, time, 0); - mp_cmd_free(cmd); - return 0; } diff --git a/input/input.h b/input/input.h index ebabc16491..aaacfbe164 100644 --- a/input/input.h +++ b/input/input.h @@ -202,6 +202,8 @@ typedef struct mp_cmd { int nargs; struct mp_cmd_arg args[MP_CMD_MAX_ARGS]; int pausing; + struct mp_cmd *queue_prev; + struct mp_cmd *queue_next; } mp_cmd_t; @@ -234,6 +236,9 @@ int mp_input_add_key_fd(struct input_ctx *ictx, int fd, int select, int read_func(void *ctx, int fd), int close_func(int fd), void *ctx); +// Feed a keypress (alternative to being returned from read_func above) +void mp_input_feed_key(struct input_ctx *ictx, int code); + // As for the cmd one you usually don't need this function. void mp_input_rm_key_fd(struct input_ctx *ictx, int fd); diff --git a/libvo/video_out.c b/libvo/video_out.c index 443ef87cde..8b6292978f 100644 --- a/libvo/video_out.c +++ b/libvo/video_out.c @@ -434,7 +434,7 @@ static int event_fd_callback(void *ctx, int fd) { struct vo *vo = ctx; vo_check_events(vo); - return mplayer_get_key(vo->key_fifo, 0); + return MP_INPUT_NOTHING; } int vo_config(struct vo *vo, uint32_t width, uint32_t height, diff --git a/mp_fifo.c b/mp_fifo.c index 4bda7209e0..ffb3608fbf 100644 --- a/mp_fifo.c +++ b/mp_fifo.c @@ -29,86 +29,23 @@ struct mp_fifo { struct MPOpts *opts; - int *data; - int readpos; - int size; - int num_entries; - int max_up; - int num_up; + struct input_ctx *input; int last_key_down; unsigned last_down_time; }; -struct mp_fifo *mp_fifo_create(struct MPOpts *opts) +struct mp_fifo *mp_fifo_create(struct input_ctx *input, struct MPOpts *opts) { struct mp_fifo *fifo = talloc_zero(NULL, struct mp_fifo); + fifo->input = input; fifo->opts = opts; - /* Typical mouse wheel use will generate a sequence repeating 3 events: - * down, doubleclick, up, down, doubleclick, up, ... - * Normally only one of those event types triggers a command, - * so allow opts->key_fifo_size such repeats. - */ - fifo->max_up = opts->key_fifo_size; - fifo->size = opts->key_fifo_size * 3; - fifo->data = talloc_array_ptrtype(fifo, fifo->data, fifo->size); return fifo; } -static bool is_up(int code) -{ - return code > 0 && !(code & MP_KEY_DOWN) - && !(code >= MOUSE_BTN0_DBL && code < MOUSE_BTN_DBL_END); -} - -static int fifo_peek(struct mp_fifo *fifo, int offset) -{ - return fifo->data[(fifo->readpos + offset) % fifo->size]; -} - -static int fifo_read(struct mp_fifo *fifo) -{ - int code = fifo_peek(fifo, 0); - fifo->readpos += 1; - fifo->readpos %= fifo->size; - fifo->num_entries--; - fifo->num_up -= is_up(code); - assert(fifo->num_entries >= 0); - assert(fifo->num_up >= 0); - return code; -} - -static void fifo_write(struct mp_fifo *fifo, int code) -{ - fifo->data[(fifo->readpos + fifo->num_entries) % fifo->size] = code; - fifo->num_entries++; - fifo->num_up += is_up(code); - assert(fifo->num_entries <= fifo->size); - assert(fifo->num_up <= fifo->max_up); -} - -static void mplayer_put_key_internal(struct mp_fifo *fifo, int code) -{ - // Clear key-down state if we're forced to drop entries - if (fifo->num_entries >= fifo->size - 1 - || fifo->num_up >= fifo->max_up) { - if (fifo_peek(fifo, fifo->num_entries - 1) != MP_INPUT_RELEASE_ALL) - fifo_write(fifo, MP_INPUT_RELEASE_ALL); - } else - fifo_write(fifo, code); -} - -int mplayer_get_key(void *ctx, int fd) -{ - struct mp_fifo *fifo = ctx; - if (!fifo->num_entries) - return MP_INPUT_NOTHING; - return fifo_read(fifo); -} - static void put_double(struct mp_fifo *fifo, int code) { if (code >= MOUSE_BTN0 && code < MOUSE_BTN_END) - mplayer_put_key_internal(fifo, code - MOUSE_BTN0 + MOUSE_BTN0_DBL); + mp_input_feed_key(fifo->input, code - MOUSE_BTN0 + MOUSE_BTN0_DBL); } void mplayer_put_key(struct mp_fifo *fifo, int code) @@ -120,7 +57,7 @@ void mplayer_put_key(struct mp_fifo *fifo, int code) && (code & ~MP_KEY_DOWN) >= MOUSE_BTN0_DBL && (code & ~MP_KEY_DOWN) < MOUSE_BTN_DBL_END) return; - mplayer_put_key_internal(fifo, code); + mp_input_feed_key(fifo->input, code); if (code & MP_KEY_DOWN) { code &= ~MP_KEY_DOWN; if (fifo->last_key_down == code diff --git a/mp_fifo.h b/mp_fifo.h index 125b67eb2c..01c1fb0c37 100644 --- a/mp_fifo.h +++ b/mp_fifo.h @@ -20,11 +20,11 @@ #define MPLAYER_MP_FIFO_H struct mp_fifo; -int mplayer_get_key(void *ctx, int fd); void mplayer_put_key(struct mp_fifo *fifo, int code); // Can be freed with talloc_free() +struct input_ctx; struct MPOpts; -struct mp_fifo *mp_fifo_create(struct MPOpts *opts); +struct mp_fifo *mp_fifo_create(struct input_ctx *input, struct MPOpts *opts); #ifdef IS_OLD_VO diff --git a/mplayer.c b/mplayer.c index c8216fae90..c10cb61223 100644 --- a/mplayer.c +++ b/mplayer.c @@ -3732,7 +3732,7 @@ static void run_playloop(struct MPContext *mpctx) static int read_keys(void *ctx, int fd) { getch2(ctx); - return mplayer_get_key(ctx, 0); + return MP_INPUT_NOTHING; } static bool attachment_is_font(struct demux_attachment *att) @@ -3887,7 +3887,6 @@ int i; } } } - mpctx->key_fifo = mp_fifo_create(opts); print_version("MPlayer2"); @@ -4079,7 +4078,7 @@ if(!codecs_file || !parse_codec_cfg(codecs_file)){ // Init input system current_module = "init_input"; mpctx->input = mp_input_init(&opts->input); - mp_input_add_key_fd(mpctx->input, -1,0,mplayer_get_key,NULL, mpctx->key_fifo); + mpctx->key_fifo = mp_fifo_create(mpctx->input, opts); if(slave_mode) { #if USE_FD0_CMD_SELECT int flags = fcntl(0, F_GETFL); diff --git a/options.h b/options.h index 521d3a9751..61f77053bc 100644 --- a/options.h +++ b/options.h @@ -59,7 +59,6 @@ typedef struct MPOpts { char *term_osd_esc; char *playing_msg; int player_idle_mode; - int key_fifo_size; int consolecontrols; int doubleclick_time; int list_properties; @@ -123,6 +122,7 @@ typedef struct MPOpts { struct input_conf { char *config_file; + int key_fifo_size; unsigned int ar_delay; unsigned int ar_rate; char *js_dev;