summaryrefslogtreecommitdiffstats
path: root/input
diff options
context:
space:
mode:
authorUoti Urpala <uau@mplayer2.org>2011-07-17 04:47:50 +0300
committerUoti Urpala <uau@mplayer2.org>2011-07-17 07:36:09 +0300
commit82b8f89baeafc2b2b39381f2ee14e4a2b2619b4c (patch)
treeb7bd847d8666233ef289224162e1e5a8668b685d /input
parent1916b95b8d3737ac783eb6351664f9892824e5c5 (diff)
downloadmpv-82b8f89baeafc2b2b39381f2ee14e4a2b2619b4c.tar.bz2
mpv-82b8f89baeafc2b2b39381f2ee14e4a2b2619b4c.tar.xz
input: rework event reading and command queuing
Rework much of the logic related to reading from event sources and queuing commands. The two biggest architecture changes are: - The code buffering keycodes in mp_fifo.c is gone. Instead key input is now immediately fed to input.c and interpreted as commands, and then the commands are buffered instead. - mp_input_get_cmd() now always tries to read every available event from every event source and convert them to (buffered) commands. Before it would only process new events until one new command became available. Some relevant behavior changes: - Before commands could be lost when stream code called mp_input_check_interrupt() which read commands (to see if they were of types that triggered aborts during slow IO tasks) and then threw them away. This was especially an issue if cache was enabled and slow to read. Fixed - now it's possible to check whether there are queued commands which will abort playback of the current file without throwing other commands away. - mp_input_check_interrupt() now prints a message if it returns true. This is especially useful because the failures caused by aborted stream reads can trigger error messages from other code that was doing the read; the new message makes it more obvious what the cause of the subsequent error messages is. - It's now possible to again avoid making stdin non-blocking (which caused some issues) without reintroducing extra latency. The change will be done in a subsequent commit. - Event sources that do not support select() should now have somewhat lower latency in certain situations as they will be checked both before and after select()/sleep in input reading; before the sleep always happened first even if such sources already had queued input. Before the key fifo was also handled in this manner (first key triggered select, but if multiple were read then rest could be delayed; however in most cases this didn't add latency in practice as after central code started doing command handling it queried for further commands with a max sleep time of 0). - Key fifo limiting is more accurate now: it now counts actual commands intead of keycodes, and all queued keys are read immediately from input devices so they can be counted correctly. - Since keypresses are now interpreted immediately, commands which change keybindings will no longer affect following keypresses that have already been read before the command is executed. This should not be an issue in practice with current keybinding behavior.
Diffstat (limited to 'input')
-rw-r--r--input/input.c356
-rw-r--r--input/input.h5
2 files changed, 211 insertions, 150 deletions
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 <sys/time.h>
#include <fcntl.h>
#include <ctype.h>
+#include <assert.h>
#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 (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)) {
- if (peek_only && from_queue)
- // The filter ate the cmd, so we remove it from queue
- ret = get_queued_cmd(ictx, 0);
+ // The filter ate the cmd, so remove it from the queue
+ if (queue)
+ queue_pop(queue);
mp_cmd_free(ret);
- return NULL;
+ // 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);