mirror of
https://github.com/mpv-player/mpv
synced 2024-12-22 23:02:37 +00:00
9ba6641879
Especially with other components (libavcodec, OSX stuff), the thread list can get quite populated. Setting the thread name helps when debugging. Since this is not portable, we check the OS variants in waf configure. old-configure just gets a special-case for glibc, since doing a full check here would probably be a waste of effort.
1540 lines
46 KiB
C
1540 lines
46 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <assert.h>
|
|
|
|
#include <libavutil/avstring.h>
|
|
#include <libavutil/common.h>
|
|
|
|
#include "osdep/io.h"
|
|
#include "osdep/semaphore.h"
|
|
#include "misc/rendezvous.h"
|
|
|
|
#include "input.h"
|
|
#include "keycodes.h"
|
|
#include "cmd_list.h"
|
|
#include "cmd_parse.h"
|
|
#include "osdep/threads.h"
|
|
#include "osdep/timer.h"
|
|
#include "common/msg.h"
|
|
#include "common/global.h"
|
|
#include "options/m_config.h"
|
|
#include "options/m_option.h"
|
|
#include "options/path.h"
|
|
#include "talloc.h"
|
|
#include "options/options.h"
|
|
#include "misc/bstr.h"
|
|
#include "stream/stream.h"
|
|
#include "common/common.h"
|
|
|
|
#if HAVE_COCOA
|
|
#include "osdep/macosx_events.h"
|
|
#endif
|
|
|
|
#define input_lock(ictx) pthread_mutex_lock(&ictx->mutex)
|
|
#define input_unlock(ictx) pthread_mutex_unlock(&ictx->mutex)
|
|
|
|
#define MP_MAX_KEY_DOWN 4
|
|
|
|
struct cmd_bind {
|
|
int keys[MP_MAX_KEY_DOWN];
|
|
int num_keys;
|
|
char *cmd;
|
|
char *location; // filename/line number of definition
|
|
bool is_builtin;
|
|
struct cmd_bind_section *owner;
|
|
};
|
|
|
|
struct cmd_bind_section {
|
|
struct cmd_bind *binds;
|
|
int num_binds;
|
|
char *section;
|
|
struct mp_rect mouse_area; // set at runtime, if at all
|
|
bool mouse_area_set; // mouse_area is valid and should be tested
|
|
struct cmd_bind_section *next;
|
|
};
|
|
|
|
#define MP_MAX_SOURCES 10
|
|
|
|
#define MAX_ACTIVE_SECTIONS 50
|
|
|
|
struct active_section {
|
|
char *name;
|
|
int flags;
|
|
};
|
|
|
|
struct cmd_queue {
|
|
struct mp_cmd *first;
|
|
};
|
|
|
|
struct input_ctx {
|
|
pthread_mutex_t mutex;
|
|
sem_t wakeup;
|
|
struct mp_log *log;
|
|
struct mpv_global *global;
|
|
struct input_opts *opts;
|
|
|
|
bool using_alt_gr;
|
|
bool using_ar;
|
|
bool using_cocoa_media_keys;
|
|
bool win_drag;
|
|
|
|
// Autorepeat stuff
|
|
short ar_state;
|
|
int64_t last_ar;
|
|
|
|
// history of key downs - the newest is in position 0
|
|
int key_history[MP_MAX_KEY_DOWN];
|
|
// key code of the last key that triggered MP_KEY_STATE_DOWN
|
|
int last_key_down;
|
|
int64_t last_key_down_time;
|
|
bool current_down_cmd_need_release;
|
|
struct mp_cmd *current_down_cmd;
|
|
|
|
int last_doubleclick_key_down;
|
|
double last_doubleclick_time;
|
|
|
|
// Mouse position on the consumer side (as command.c sees it)
|
|
int mouse_x, mouse_y;
|
|
char *mouse_section; // last section to receive mouse event
|
|
|
|
// Mouse position on the producer side (as the VO sees it)
|
|
// Unlike mouse_x/y, this can be used to resolve mouse click bindings.
|
|
int mouse_vo_x, mouse_vo_y;
|
|
|
|
bool mouse_mangle, mouse_src_mangle;
|
|
struct mp_rect mouse_src, mouse_dst;
|
|
|
|
// List of command binding sections
|
|
struct cmd_bind_section *cmd_bind_sections;
|
|
|
|
// List currently active command sections
|
|
struct active_section active_sections[MAX_ACTIVE_SECTIONS];
|
|
int num_active_sections;
|
|
|
|
unsigned int mouse_event_counter;
|
|
|
|
struct mp_input_src *sources[MP_MAX_SOURCES];
|
|
int num_sources;
|
|
|
|
struct cmd_queue cmd_queue;
|
|
|
|
struct mp_cancel *cancel;
|
|
};
|
|
|
|
static int parse_config(struct input_ctx *ictx, bool builtin, bstr data,
|
|
const char *location, const char *restrict_section);
|
|
static void close_input_sources(struct input_ctx *ictx);
|
|
|
|
#define OPT_BASE_STRUCT struct input_opts
|
|
struct input_opts {
|
|
char *config_file;
|
|
int doubleclick_time;
|
|
// Maximum number of queued commands from keypresses (limit to avoid
|
|
// repeated slow commands piling up)
|
|
int key_fifo_size;
|
|
// Autorepeat config (be aware of mp_input_set_repeat_info())
|
|
int ar_delay;
|
|
int ar_rate;
|
|
char *js_dev;
|
|
int use_joystick;
|
|
int use_lirc;
|
|
char *lirc_configfile;
|
|
int use_lircc;
|
|
int use_alt_gr;
|
|
int use_appleremote;
|
|
int use_media_keys;
|
|
int use_app_events;
|
|
int default_bindings;
|
|
int enable_mouse_movements;
|
|
int vo_key_input;
|
|
int test;
|
|
};
|
|
|
|
const struct m_sub_options input_config = {
|
|
.opts = (const m_option_t[]) {
|
|
OPT_STRING("conf", config_file, CONF_GLOBAL),
|
|
OPT_INT("ar-delay", ar_delay, CONF_GLOBAL),
|
|
OPT_INT("ar-rate", ar_rate, CONF_GLOBAL),
|
|
OPT_PRINT("keylist", mp_print_key_list),
|
|
OPT_PRINT("cmdlist", mp_print_cmd_list),
|
|
OPT_STRING("js-dev", js_dev, CONF_GLOBAL),
|
|
OPT_FLAG("default-bindings", default_bindings, CONF_GLOBAL),
|
|
OPT_FLAG("test", test, CONF_GLOBAL),
|
|
OPT_INTRANGE("doubleclick-time", doubleclick_time, 0, 0, 1000),
|
|
OPT_FLAG("joystick", use_joystick, CONF_GLOBAL),
|
|
OPT_FLAG("lirc", use_lirc, CONF_GLOBAL),
|
|
OPT_FLAG("right-alt-gr", use_alt_gr, CONF_GLOBAL),
|
|
OPT_INTRANGE("key-fifo-size", key_fifo_size, CONF_GLOBAL, 2, 65000),
|
|
OPT_FLAG("cursor", enable_mouse_movements, CONF_GLOBAL),
|
|
OPT_FLAG("vo-keyboard", vo_key_input, CONF_GLOBAL),
|
|
OPT_FLAG("x11-keyboard", vo_key_input, CONF_GLOBAL), // old alias
|
|
#if HAVE_LIRC
|
|
OPT_STRING("lirc-conf", lirc_configfile, CONF_GLOBAL),
|
|
#endif
|
|
#if HAVE_COCOA
|
|
OPT_FLAG("appleremote", use_appleremote, CONF_GLOBAL),
|
|
OPT_FLAG("media-keys", use_media_keys, CONF_GLOBAL),
|
|
OPT_FLAG("app-events", use_app_events, CONF_GLOBAL),
|
|
#endif
|
|
{0}
|
|
},
|
|
.size = sizeof(struct input_opts),
|
|
.defaults = &(const struct input_opts){
|
|
.key_fifo_size = 7,
|
|
.doubleclick_time = 300,
|
|
.ar_delay = 200,
|
|
.ar_rate = 40,
|
|
.use_lirc = 1,
|
|
.use_alt_gr = 1,
|
|
.enable_mouse_movements = 1,
|
|
#if HAVE_COCOA
|
|
.use_appleremote = 1,
|
|
.use_media_keys = 1,
|
|
.use_app_events = 1,
|
|
#endif
|
|
.default_bindings = 1,
|
|
.vo_key_input = 1,
|
|
},
|
|
};
|
|
|
|
static const char builtin_input_conf[] =
|
|
#include "input/input_conf.h"
|
|
;
|
|
|
|
static bool test_rect(struct mp_rect *rc, int x, int y)
|
|
{
|
|
return x >= rc->x0 && y >= rc->y0 && x < rc->x1 && y < rc->y1;
|
|
}
|
|
|
|
static int queue_count_cmds(struct cmd_queue *queue)
|
|
{
|
|
int res = 0;
|
|
for (struct mp_cmd *cmd = queue->first; cmd; cmd = cmd->queue_next)
|
|
res++;
|
|
return res;
|
|
}
|
|
|
|
static void queue_remove(struct cmd_queue *queue, struct mp_cmd *cmd)
|
|
{
|
|
struct mp_cmd **p_prev = &queue->first;
|
|
while (*p_prev != cmd) {
|
|
p_prev = &(*p_prev)->queue_next;
|
|
}
|
|
// if this fails, cmd was not in the queue
|
|
assert(*p_prev == cmd);
|
|
*p_prev = cmd->queue_next;
|
|
}
|
|
|
|
static struct mp_cmd *queue_remove_head(struct cmd_queue *queue)
|
|
{
|
|
struct mp_cmd *ret = queue->first;
|
|
if (ret)
|
|
queue_remove(queue, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void queue_add_tail(struct cmd_queue *queue, struct mp_cmd *cmd)
|
|
{
|
|
struct mp_cmd **p_prev = &queue->first;
|
|
while (*p_prev)
|
|
p_prev = &(*p_prev)->queue_next;
|
|
*p_prev = cmd;
|
|
cmd->queue_next = NULL;
|
|
}
|
|
|
|
static struct mp_cmd *queue_peek_tail(struct cmd_queue *queue)
|
|
{
|
|
struct mp_cmd *cur = queue->first;
|
|
while (cur && cur->queue_next)
|
|
cur = cur->queue_next;
|
|
return cur;
|
|
}
|
|
|
|
static void append_bind_info(struct input_ctx *ictx, char **pmsg,
|
|
struct cmd_bind *bind)
|
|
{
|
|
char *msg = *pmsg;
|
|
struct mp_cmd *cmd = mp_input_parse_cmd(ictx, bstr0(bind->cmd),
|
|
bind->location);
|
|
bstr stripped = cmd ? cmd->original : bstr0(bind->cmd);
|
|
msg = talloc_asprintf_append(msg, " '%.*s'", BSTR_P(stripped));
|
|
if (!cmd)
|
|
msg = talloc_asprintf_append(msg, " (invalid)");
|
|
if (strcmp(bind->owner->section, "default") != 0)
|
|
msg = talloc_asprintf_append(msg, " in section {%s}",
|
|
bind->owner->section);
|
|
msg = talloc_asprintf_append(msg, " in %s", bind->location);
|
|
if (bind->is_builtin)
|
|
msg = talloc_asprintf_append(msg, " (default)");
|
|
talloc_free(cmd);
|
|
*pmsg = msg;
|
|
}
|
|
|
|
static mp_cmd_t *handle_test(struct input_ctx *ictx, int code)
|
|
{
|
|
if (code == MP_KEY_CLOSE_WIN) {
|
|
MP_WARN(ictx,
|
|
"CLOSE_WIN was received. This pseudo key can be remapped too,\n"
|
|
"but --input-test will always quit when receiving it.\n");
|
|
const char *args[] = {"quit", NULL};
|
|
mp_cmd_t *res = mp_input_parse_cmd_strv(ictx->log, args);
|
|
return res;
|
|
}
|
|
|
|
char *key_buf = mp_input_get_key_combo_name(&code, 1);
|
|
char *msg = talloc_asprintf(NULL, "Key %s is bound to:\n", key_buf);
|
|
talloc_free(key_buf);
|
|
|
|
int count = 0;
|
|
for (struct cmd_bind_section *bs = ictx->cmd_bind_sections;
|
|
bs; bs = bs->next)
|
|
{
|
|
for (int i = 0; i < bs->num_binds; i++) {
|
|
if (bs->binds[i].num_keys && bs->binds[i].keys[0] == code) {
|
|
count++;
|
|
if (count > 1)
|
|
msg = talloc_asprintf_append(msg, "\n");
|
|
msg = talloc_asprintf_append(msg, "%d. ", count);
|
|
append_bind_info(ictx, &msg, &bs->binds[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!count)
|
|
msg = talloc_asprintf_append(msg, "(nothing)");
|
|
|
|
MP_INFO(ictx, "%s\n", msg);
|
|
const char *args[] = {"show_text", msg, NULL};
|
|
mp_cmd_t *res = mp_input_parse_cmd_strv(ictx->log, args);
|
|
talloc_free(msg);
|
|
return res;
|
|
}
|
|
|
|
static struct cmd_bind_section *get_bind_section(struct input_ctx *ictx,
|
|
bstr section)
|
|
{
|
|
struct cmd_bind_section *bind_section = ictx->cmd_bind_sections;
|
|
|
|
if (section.len == 0)
|
|
section = bstr0("default");
|
|
while (bind_section) {
|
|
if (bstrcmp0(section, bind_section->section) == 0)
|
|
return bind_section;
|
|
if (bind_section->next == NULL)
|
|
break;
|
|
bind_section = bind_section->next;
|
|
}
|
|
if (bind_section) {
|
|
bind_section->next = talloc_ptrtype(ictx, bind_section->next);
|
|
bind_section = bind_section->next;
|
|
} else {
|
|
ictx->cmd_bind_sections = talloc_ptrtype(ictx, ictx->cmd_bind_sections);
|
|
bind_section = ictx->cmd_bind_sections;
|
|
}
|
|
*bind_section = (struct cmd_bind_section) {
|
|
.section = bstrdup0(bind_section, section),
|
|
};
|
|
return bind_section;
|
|
}
|
|
|
|
static void key_buf_add(int *buf, int code)
|
|
{
|
|
for (int n = MP_MAX_KEY_DOWN - 1; n > 0; n--)
|
|
buf[n] = buf[n - 1];
|
|
buf[0] = code;
|
|
}
|
|
|
|
static struct cmd_bind *find_bind_for_key_section(struct input_ctx *ictx,
|
|
char *section, int code)
|
|
{
|
|
struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(section));
|
|
|
|
if (!bs->num_binds)
|
|
return NULL;
|
|
|
|
int keys[MP_MAX_KEY_DOWN];
|
|
memcpy(keys, ictx->key_history, sizeof(keys));
|
|
key_buf_add(keys, code);
|
|
|
|
struct cmd_bind *best = NULL;
|
|
|
|
// Prefer user-defined keys over builtin bindings
|
|
for (int builtin = 0; builtin < 2; builtin++) {
|
|
if (builtin && !ictx->opts->default_bindings)
|
|
break;
|
|
if (best)
|
|
break;
|
|
for (int n = 0; n < bs->num_binds; n++) {
|
|
if (bs->binds[n].is_builtin == (bool)builtin) {
|
|
struct cmd_bind *b = &bs->binds[n];
|
|
// we have: keys=[key2 key1 keyX ...]
|
|
// and: b->keys=[key1 key2] (and may be just a prefix)
|
|
for (int i = 0; i < b->num_keys; i++) {
|
|
if (b->keys[i] != keys[b->num_keys - 1 - i])
|
|
goto skip;
|
|
}
|
|
if (!best || b->num_keys >= best->num_keys)
|
|
best = b;
|
|
skip: ;
|
|
}
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
static struct cmd_bind *find_any_bind_for_key(struct input_ctx *ictx,
|
|
char *force_section, int code)
|
|
{
|
|
if (force_section)
|
|
return find_bind_for_key_section(ictx, force_section, code);
|
|
|
|
bool use_mouse = MP_KEY_DEPENDS_ON_MOUSE_POS(code);
|
|
|
|
// First look whether a mouse section is capturing all mouse input
|
|
// exclusively (regardless of the active section stack order).
|
|
if (use_mouse && MP_KEY_IS_MOUSE_BTN_SINGLE(ictx->last_key_down)) {
|
|
struct cmd_bind *bind =
|
|
find_bind_for_key_section(ictx, ictx->mouse_section, code);
|
|
if (bind)
|
|
return bind;
|
|
}
|
|
|
|
struct cmd_bind *best_bind = NULL;
|
|
for (int i = ictx->num_active_sections - 1; i >= 0; i--) {
|
|
struct active_section *s = &ictx->active_sections[i];
|
|
struct cmd_bind *bind = find_bind_for_key_section(ictx, s->name, code);
|
|
if (bind) {
|
|
struct cmd_bind_section *bs = bind->owner;
|
|
if (!use_mouse || (bs->mouse_area_set && test_rect(&bs->mouse_area,
|
|
ictx->mouse_vo_x,
|
|
ictx->mouse_vo_y)))
|
|
{
|
|
if (!best_bind || (best_bind->is_builtin && !bind->is_builtin))
|
|
best_bind = bind;
|
|
}
|
|
}
|
|
if (s->flags & MP_INPUT_EXCLUSIVE)
|
|
break;
|
|
}
|
|
|
|
return best_bind;
|
|
}
|
|
|
|
static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
|
int code)
|
|
{
|
|
if (ictx->opts->test)
|
|
return handle_test(ictx, code);
|
|
|
|
struct cmd_bind *cmd = find_any_bind_for_key(ictx, force_section, code);
|
|
if (cmd == NULL) {
|
|
int msgl = MSGL_WARN;
|
|
if (code == MP_KEY_MOUSE_MOVE || code == MP_KEY_MOUSE_LEAVE)
|
|
msgl = MSGL_DEBUG;
|
|
char *key_buf = mp_input_get_key_combo_name(&code, 1);
|
|
MP_MSG(ictx, msgl, "No bind found for key '%s'.\n", key_buf);
|
|
talloc_free(key_buf);
|
|
return NULL;
|
|
}
|
|
mp_cmd_t *ret = mp_input_parse_cmd(ictx, bstr0(cmd->cmd), cmd->location);
|
|
if (ret) {
|
|
ret->input_section = cmd->owner->section;
|
|
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
|
|
char *keyname = mp_input_get_key_combo_name(&code, 1);
|
|
MP_DBG(ictx, "key '%s' -> '%s' in '%s'\n",
|
|
keyname, cmd->cmd, ret->input_section);
|
|
talloc_free(keyname);
|
|
}
|
|
} else {
|
|
char *key_buf = mp_input_get_key_combo_name(&code, 1);
|
|
MP_ERR(ictx, "Invalid command for bound key '%s': '%s'\n",
|
|
key_buf, cmd->cmd);
|
|
talloc_free(key_buf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void update_mouse_section(struct input_ctx *ictx)
|
|
{
|
|
struct cmd_bind *bind =
|
|
find_any_bind_for_key(ictx, NULL, MP_KEY_MOUSE_MOVE);
|
|
|
|
char *new_section = bind ? bind->owner->section : "default";
|
|
|
|
char *old = ictx->mouse_section;
|
|
ictx->mouse_section = new_section;
|
|
|
|
if (strcmp(old, ictx->mouse_section) != 0) {
|
|
MP_DBG(ictx, "input: switch section %s -> %s\n",
|
|
old, ictx->mouse_section);
|
|
mp_input_queue_cmd(ictx, get_cmd_from_keys(ictx, old, MP_KEY_MOUSE_LEAVE));
|
|
}
|
|
}
|
|
|
|
// Called when the currently held-down key is released. This (usually) sends
|
|
// the a key-up version of the command associated with the keys that were held
|
|
// down.
|
|
// If the drop_current parameter is set to true, then don't send the key-up
|
|
// command. Unless we've already sent a key-down event, in which case the
|
|
// input receiver (the player) must get a key-up event, or it would get stuck
|
|
// thinking a key is still held down.
|
|
static void release_down_cmd(struct input_ctx *ictx, bool drop_current)
|
|
{
|
|
if (ictx->current_down_cmd_need_release)
|
|
drop_current = false;
|
|
if (!drop_current && ictx->current_down_cmd &&
|
|
ictx->current_down_cmd->key_up_follows)
|
|
{
|
|
memset(ictx->key_history, 0, sizeof(ictx->key_history));
|
|
ictx->current_down_cmd->key_up_follows = false;
|
|
mp_input_queue_cmd(ictx, ictx->current_down_cmd);
|
|
} else {
|
|
talloc_free(ictx->current_down_cmd);
|
|
}
|
|
ictx->current_down_cmd = NULL;
|
|
ictx->current_down_cmd_need_release = false;
|
|
ictx->last_key_down = 0;
|
|
ictx->last_key_down_time = 0;
|
|
ictx->ar_state = -1;
|
|
update_mouse_section(ictx);
|
|
}
|
|
|
|
// Whether a command shall be sent on both key down and key up events.
|
|
static bool key_updown_ok(enum mp_command_type cmd)
|
|
{
|
|
switch (cmd) {
|
|
case MP_CMD_SCRIPT_DISPATCH:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We don't want the append to the command queue indefinitely, because that
|
|
// could lead to situations where recovery would take too long. On the other
|
|
// hand, don't drop commands that will abort playback.
|
|
static bool should_drop_cmd(struct input_ctx *ictx, struct mp_cmd *cmd)
|
|
{
|
|
struct cmd_queue *queue = &ictx->cmd_queue;
|
|
return queue_count_cmds(queue) >= ictx->opts->key_fifo_size &&
|
|
!mp_input_is_abort_cmd(cmd);
|
|
}
|
|
|
|
static struct mp_cmd *resolve_key(struct input_ctx *ictx, int code)
|
|
{
|
|
update_mouse_section(ictx);
|
|
struct mp_cmd *cmd = get_cmd_from_keys(ictx, NULL, code);
|
|
key_buf_add(ictx->key_history, code);
|
|
if (cmd && cmd->id != MP_CMD_IGNORE && !should_drop_cmd(ictx, cmd))
|
|
return cmd;
|
|
talloc_free(cmd);
|
|
return NULL;
|
|
}
|
|
|
|
static void interpret_key(struct input_ctx *ictx, int code, double scale)
|
|
{
|
|
int state = code & (MP_KEY_STATE_DOWN | MP_KEY_STATE_UP);
|
|
code = code & ~(unsigned)state;
|
|
|
|
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
|
|
char *key = mp_input_get_key_name(code);
|
|
MP_DBG(ictx, "key code=%#x '%s'%s%s\n",
|
|
code, key, (state & MP_KEY_STATE_DOWN) ? " down" : "",
|
|
(state & MP_KEY_STATE_UP) ? " up" : "");
|
|
talloc_free(key);
|
|
}
|
|
|
|
if (MP_KEY_DEPENDS_ON_MOUSE_POS(code & ~MP_KEY_MODIFIER_MASK)) {
|
|
ictx->mouse_event_counter++;
|
|
mp_input_wakeup(ictx);
|
|
}
|
|
|
|
struct mp_cmd *cmd = NULL;
|
|
|
|
if (state == MP_KEY_STATE_DOWN) {
|
|
// Protect against VOs which send STATE_DOWN with autorepeat
|
|
if (ictx->last_key_down == code)
|
|
return;
|
|
// Cancel current down-event (there can be only one)
|
|
release_down_cmd(ictx, true);
|
|
cmd = resolve_key(ictx, code);
|
|
if (cmd && (code & MP_KEY_EMIT_ON_UP))
|
|
cmd->key_up_follows = true;
|
|
ictx->last_key_down = code;
|
|
ictx->last_key_down_time = mp_time_us();
|
|
ictx->ar_state = 0;
|
|
ictx->current_down_cmd = mp_cmd_clone(cmd);
|
|
ictx->current_down_cmd_need_release = false;
|
|
mp_input_wakeup(ictx); // possibly start timer for autorepeat
|
|
} else if (state == MP_KEY_STATE_UP) {
|
|
// Most VOs send RELEASE_ALL anyway
|
|
release_down_cmd(ictx, false);
|
|
} else {
|
|
// Press of key with no separate down/up events
|
|
if (ictx->last_key_down == code) {
|
|
// Mixing press events and up/down with the same key is not allowed
|
|
MP_WARN(ictx, "Mixing key presses and up/down.\n");
|
|
}
|
|
cmd = resolve_key(ictx, code);
|
|
}
|
|
|
|
if (!cmd)
|
|
return;
|
|
|
|
// Don't emit a command on key-down if the key is designed to emit commands
|
|
// on key-up (like mouse buttons). Also, if the command specifically should
|
|
// be sent both on key down and key up, still emit the command.
|
|
if (cmd->key_up_follows && !key_updown_ok(cmd->id)) {
|
|
talloc_free(cmd);
|
|
return;
|
|
}
|
|
|
|
memset(ictx->key_history, 0, sizeof(ictx->key_history));
|
|
|
|
cmd->scale = scale;
|
|
|
|
if (cmd->key_up_follows)
|
|
ictx->current_down_cmd_need_release = true;
|
|
mp_input_queue_cmd(ictx, cmd);
|
|
}
|
|
|
|
static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale)
|
|
{
|
|
struct input_opts *opts = ictx->opts;
|
|
|
|
code = mp_normalize_keycode(code);
|
|
int unmod = code & ~MP_KEY_MODIFIER_MASK;
|
|
if (code == MP_INPUT_RELEASE_ALL) {
|
|
MP_DBG(ictx, "release all\n");
|
|
release_down_cmd(ictx, false);
|
|
return;
|
|
}
|
|
if (!opts->enable_mouse_movements && MP_KEY_IS_MOUSE(unmod))
|
|
return;
|
|
if (unmod == MP_KEY_MOUSE_LEAVE) {
|
|
update_mouse_section(ictx);
|
|
mp_input_queue_cmd(ictx, get_cmd_from_keys(ictx, NULL, code));
|
|
return;
|
|
}
|
|
double now = mp_time_sec();
|
|
// ignore system-doubleclick if we generate these events ourselves
|
|
if (opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
|
|
return;
|
|
interpret_key(ictx, code, scale);
|
|
if (code & MP_KEY_STATE_DOWN) {
|
|
code &= ~MP_KEY_STATE_DOWN;
|
|
if (ictx->last_doubleclick_key_down == code &&
|
|
now - ictx->last_doubleclick_time < opts->doubleclick_time / 1000.0)
|
|
{
|
|
if (code >= MP_MOUSE_BTN0 && code <= MP_MOUSE_BTN2)
|
|
interpret_key(ictx, code - MP_MOUSE_BTN0 + MP_MOUSE_BTN0_DBL, 1);
|
|
}
|
|
ictx->last_doubleclick_key_down = code;
|
|
ictx->last_doubleclick_time = now;
|
|
}
|
|
}
|
|
|
|
void mp_input_put_key(struct input_ctx *ictx, int code)
|
|
{
|
|
input_lock(ictx);
|
|
mp_input_feed_key(ictx, code, 1);
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
void mp_input_put_key_utf8(struct input_ctx *ictx, int mods, struct bstr t)
|
|
{
|
|
while (t.len) {
|
|
int code = bstr_decode_utf8(t, &t);
|
|
if (code < 0)
|
|
break;
|
|
mp_input_put_key(ictx, code | mods);
|
|
}
|
|
}
|
|
|
|
void mp_input_put_axis(struct input_ctx *ictx, int direction, double value)
|
|
{
|
|
if (value == 0.0)
|
|
return;
|
|
input_lock(ictx);
|
|
mp_input_feed_key(ictx, direction, value);
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
void mp_input_set_mouse_transform(struct input_ctx *ictx, struct mp_rect *dst,
|
|
struct mp_rect *src)
|
|
{
|
|
input_lock(ictx);
|
|
ictx->mouse_mangle = dst || src;
|
|
if (ictx->mouse_mangle) {
|
|
ictx->mouse_dst = *dst;
|
|
ictx->mouse_src_mangle = !!src;
|
|
if (ictx->mouse_src_mangle)
|
|
ictx->mouse_src = *src;
|
|
}
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
bool mp_input_mouse_enabled(struct input_ctx *ictx)
|
|
{
|
|
input_lock(ictx);
|
|
bool r = ictx->opts->enable_mouse_movements;
|
|
input_unlock(ictx);
|
|
return r;
|
|
}
|
|
|
|
bool mp_input_vo_keyboard_enabled(struct input_ctx *ictx)
|
|
{
|
|
input_lock(ictx);
|
|
bool r = ictx->opts->vo_key_input;
|
|
input_unlock(ictx);
|
|
return r;
|
|
}
|
|
|
|
void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y)
|
|
{
|
|
input_lock(ictx);
|
|
MP_DBG(ictx, "mouse move %d/%d\n", x, y);
|
|
|
|
if (!ictx->opts->enable_mouse_movements) {
|
|
input_unlock(ictx);
|
|
return;
|
|
}
|
|
|
|
if (ictx->mouse_mangle) {
|
|
struct mp_rect *src = &ictx->mouse_src;
|
|
struct mp_rect *dst = &ictx->mouse_dst;
|
|
x = MPCLAMP(x, dst->x0, dst->x1) - dst->x0;
|
|
y = MPCLAMP(y, dst->y0, dst->y1) - dst->y0;
|
|
if (ictx->mouse_src_mangle) {
|
|
x = x * 1.0 / (dst->x1 - dst->x0) * (src->x1 - src->x0) + src->x0;
|
|
y = y * 1.0 / (dst->y1 - dst->y0) * (src->y1 - src->y0) + src->y0;
|
|
}
|
|
MP_DBG(ictx, "-> %d/%d\n", x, y);
|
|
}
|
|
|
|
ictx->mouse_event_counter++;
|
|
ictx->mouse_vo_x = x;
|
|
ictx->mouse_vo_y = y;
|
|
|
|
update_mouse_section(ictx);
|
|
struct mp_cmd *cmd = get_cmd_from_keys(ictx, NULL, MP_KEY_MOUSE_MOVE);
|
|
if (!cmd)
|
|
cmd = mp_input_parse_cmd(ictx, bstr0("ignore"), "<internal>");
|
|
|
|
if (cmd) {
|
|
cmd->mouse_move = true;
|
|
cmd->mouse_x = x;
|
|
cmd->mouse_y = y;
|
|
if (should_drop_cmd(ictx, cmd)) {
|
|
talloc_free(cmd);
|
|
} else {
|
|
// Coalesce with previous mouse move events (i.e. replace it)
|
|
struct mp_cmd *tail = queue_peek_tail(&ictx->cmd_queue);
|
|
if (tail && tail->mouse_move) {
|
|
queue_remove(&ictx->cmd_queue, tail);
|
|
talloc_free(tail);
|
|
}
|
|
mp_input_queue_cmd(ictx, cmd);
|
|
}
|
|
}
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
unsigned int mp_input_get_mouse_event_counter(struct input_ctx *ictx)
|
|
{
|
|
// Make the frontend always display the mouse cursor (as long as it's not
|
|
// forced invisible) if mouse input is desired.
|
|
input_lock(ictx);
|
|
if (mp_input_test_mouse_active(ictx, ictx->mouse_x, ictx->mouse_y))
|
|
ictx->mouse_event_counter++;
|
|
int ret = ictx->mouse_event_counter;
|
|
input_unlock(ictx);
|
|
return ret;
|
|
}
|
|
|
|
// adjust min time to wait until next repeat event
|
|
static void adjust_max_wait_time(struct input_ctx *ictx, double *time)
|
|
{
|
|
struct input_opts *opts = ictx->opts;
|
|
if (ictx->last_key_down && opts->ar_rate > 0 && ictx->ar_state >= 0) {
|
|
*time = FFMIN(*time, 1.0 / opts->ar_rate);
|
|
*time = FFMIN(*time, opts->ar_delay / 1000.0);
|
|
}
|
|
}
|
|
|
|
static bool test_abort_cmd(struct input_ctx *ictx, struct mp_cmd *new)
|
|
{
|
|
if (!mp_input_is_maybe_abort_cmd(new))
|
|
return false;
|
|
if (mp_input_is_abort_cmd(new))
|
|
return true;
|
|
// Abort only if there are going to be at least 2 commands in the queue.
|
|
for (struct mp_cmd *cmd = ictx->cmd_queue.first; cmd; cmd = cmd->queue_next) {
|
|
if (mp_input_is_maybe_abort_cmd(cmd))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
|
|
{
|
|
input_lock(ictx);
|
|
if (cmd) {
|
|
if (ictx->cancel && test_abort_cmd(ictx, cmd))
|
|
mp_cancel_trigger(ictx->cancel);
|
|
queue_add_tail(&ictx->cmd_queue, cmd);
|
|
mp_input_wakeup(ictx);
|
|
}
|
|
input_unlock(ictx);
|
|
return 1;
|
|
}
|
|
|
|
static mp_cmd_t *check_autorepeat(struct input_ctx *ictx)
|
|
{
|
|
struct input_opts *opts = ictx->opts;
|
|
|
|
// No input : autorepeat ?
|
|
if (opts->ar_rate <= 0 || !ictx->current_down_cmd || !ictx->last_key_down ||
|
|
(ictx->last_key_down & MP_NO_REPEAT_KEY) ||
|
|
!mp_input_is_repeatable_cmd(ictx->current_down_cmd))
|
|
ictx->ar_state = -1; // disable
|
|
if (ictx->ar_state >= 0) {
|
|
int64_t t = mp_time_us();
|
|
if (ictx->last_ar + 2000000 < t)
|
|
ictx->last_ar = t;
|
|
// First time : wait delay
|
|
if (ictx->ar_state == 0
|
|
&& (t - ictx->last_key_down_time) >= opts->ar_delay * 1000)
|
|
{
|
|
ictx->ar_state = 1;
|
|
ictx->last_ar = ictx->last_key_down_time + opts->ar_delay * 1000;
|
|
return mp_cmd_clone(ictx->current_down_cmd);
|
|
// Then send rate / sec event
|
|
} else if (ictx->ar_state == 1
|
|
&& (t - ictx->last_ar) >= 1000000 / opts->ar_rate) {
|
|
ictx->last_ar += 1000000 / opts->ar_rate;
|
|
return mp_cmd_clone(ictx->current_down_cmd);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void mp_input_wait(struct input_ctx *ictx, double seconds)
|
|
{
|
|
input_lock(ictx);
|
|
adjust_max_wait_time(ictx, &seconds);
|
|
input_unlock(ictx);
|
|
while (sem_trywait(&ictx->wakeup) == 0)
|
|
seconds = -1;
|
|
if (seconds > 0) {
|
|
MP_STATS(ictx, "start sleep");
|
|
struct timespec ts =
|
|
mp_time_us_to_timespec(mp_add_timeout(mp_time_us(), seconds));
|
|
sem_timedwait(&ictx->wakeup, &ts);
|
|
MP_STATS(ictx, "end sleep");
|
|
}
|
|
}
|
|
|
|
void mp_input_wakeup_nolock(struct input_ctx *ictx)
|
|
{
|
|
// Some audio APIs discourage use of locking in their audio callback,
|
|
// and these audio callbacks happen to call mp_input_wakeup_nolock()
|
|
// when new data is needed. This is why we use semaphores here.
|
|
sem_post(&ictx->wakeup);
|
|
}
|
|
|
|
void mp_input_wakeup(struct input_ctx *ictx)
|
|
{
|
|
mp_input_wakeup_nolock(ictx);
|
|
}
|
|
|
|
mp_cmd_t *mp_input_read_cmd(struct input_ctx *ictx)
|
|
{
|
|
input_lock(ictx);
|
|
struct mp_cmd *ret = queue_remove_head(&ictx->cmd_queue);
|
|
if (!ret) {
|
|
ret = check_autorepeat(ictx);
|
|
if (ret)
|
|
ret->repeated = true;
|
|
}
|
|
if (ret && ret->mouse_move) {
|
|
ictx->mouse_x = ret->mouse_x;
|
|
ictx->mouse_y = ret->mouse_y;
|
|
}
|
|
input_unlock(ictx);
|
|
return ret;
|
|
}
|
|
|
|
void mp_input_get_mouse_pos(struct input_ctx *ictx, int *x, int *y)
|
|
{
|
|
input_lock(ictx);
|
|
*x = ictx->mouse_x;
|
|
*y = ictx->mouse_y;
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
// If name is NULL, return "default".
|
|
// Return a statically allocated name of the section (i.e. return value never
|
|
// gets deallocated).
|
|
static char *normalize_section(struct input_ctx *ictx, char *name)
|
|
{
|
|
return get_bind_section(ictx, bstr0(name))->section;
|
|
}
|
|
|
|
void mp_input_disable_section(struct input_ctx *ictx, char *name)
|
|
{
|
|
input_lock(ictx);
|
|
name = normalize_section(ictx, name);
|
|
|
|
// Remove old section, or make sure it's on top if re-enabled
|
|
for (int i = ictx->num_active_sections - 1; i >= 0; i--) {
|
|
struct active_section *as = &ictx->active_sections[i];
|
|
if (strcmp(as->name, name) == 0) {
|
|
MP_TARRAY_REMOVE_AT(ictx->active_sections,
|
|
ictx->num_active_sections, i);
|
|
}
|
|
}
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
void mp_input_enable_section(struct input_ctx *ictx, char *name, int flags)
|
|
{
|
|
input_lock(ictx);
|
|
name = normalize_section(ictx, name);
|
|
|
|
mp_input_disable_section(ictx, name);
|
|
|
|
MP_DBG(ictx, "enable section '%s'\n", name);
|
|
|
|
if (ictx->num_active_sections < MAX_ACTIVE_SECTIONS) {
|
|
int top = ictx->num_active_sections;
|
|
if (!(flags & MP_INPUT_ON_TOP)) {
|
|
// insert before the first top entry
|
|
for (top = 0; top < ictx->num_active_sections; top++) {
|
|
if (ictx->active_sections[top].flags & MP_INPUT_ON_TOP)
|
|
break;
|
|
}
|
|
for (int n = ictx->num_active_sections; n > top; n--)
|
|
ictx->active_sections[n] = ictx->active_sections[n - 1];
|
|
}
|
|
ictx->active_sections[top] = (struct active_section){name, flags};
|
|
ictx->num_active_sections++;
|
|
}
|
|
|
|
MP_DBG(ictx, "active section stack:\n");
|
|
for (int n = 0; n < ictx->num_active_sections; n++) {
|
|
MP_DBG(ictx, " %s %d\n", ictx->active_sections[n].name,
|
|
ictx->active_sections[n].flags);
|
|
}
|
|
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
void mp_input_disable_all_sections(struct input_ctx *ictx)
|
|
{
|
|
input_lock(ictx);
|
|
ictx->num_active_sections = 0;
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
void mp_input_set_section_mouse_area(struct input_ctx *ictx, char *name,
|
|
int x0, int y0, int x1, int y1)
|
|
{
|
|
input_lock(ictx);
|
|
struct cmd_bind_section *s = get_bind_section(ictx, bstr0(name));
|
|
s->mouse_area = (struct mp_rect){x0, y0, x1, y1};
|
|
s->mouse_area_set = x0 != x1 && y0 != y1;
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
static bool test_mouse(struct input_ctx *ictx, int x, int y, int rej_flags)
|
|
{
|
|
input_lock(ictx);
|
|
bool res = false;
|
|
for (int i = 0; i < ictx->num_active_sections; i++) {
|
|
struct active_section *as = &ictx->active_sections[i];
|
|
if (as->flags & rej_flags)
|
|
continue;
|
|
struct cmd_bind_section *s = get_bind_section(ictx, bstr0(as->name));
|
|
if (s->mouse_area_set && test_rect(&s->mouse_area, x, y)) {
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
input_unlock(ictx);
|
|
return res;
|
|
}
|
|
|
|
bool mp_input_test_mouse_active(struct input_ctx *ictx, int x, int y)
|
|
{
|
|
return test_mouse(ictx, x, y, MP_INPUT_ALLOW_HIDE_CURSOR);
|
|
}
|
|
|
|
bool mp_input_test_dragging(struct input_ctx *ictx, int x, int y)
|
|
{
|
|
input_lock(ictx);
|
|
bool r = !ictx->win_drag || test_mouse(ictx, x, y, MP_INPUT_ALLOW_VO_DRAGGING);
|
|
input_unlock(ictx);
|
|
return r;
|
|
}
|
|
|
|
static void bind_dealloc(struct cmd_bind *bind)
|
|
{
|
|
talloc_free(bind->cmd);
|
|
talloc_free(bind->location);
|
|
}
|
|
|
|
// builtin: if true, remove all builtin binds, else remove all user binds
|
|
static void remove_binds(struct cmd_bind_section *bs, bool builtin)
|
|
{
|
|
for (int n = bs->num_binds - 1; n >= 0; n--) {
|
|
if (bs->binds[n].is_builtin == builtin) {
|
|
bind_dealloc(&bs->binds[n]);
|
|
assert(bs->num_binds >= 1);
|
|
bs->binds[n] = bs->binds[bs->num_binds - 1];
|
|
bs->num_binds--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
|
|
char *contents, bool builtin)
|
|
{
|
|
if (!name || !name[0])
|
|
return; // parse_config() changes semantics with restrict_section==empty
|
|
input_lock(ictx);
|
|
if (contents) {
|
|
parse_config(ictx, builtin, bstr0(contents), location, name);
|
|
} else {
|
|
// Disable:
|
|
mp_input_disable_section(ictx, name);
|
|
// Delete:
|
|
struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(name));
|
|
remove_binds(bs, builtin);
|
|
}
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
static bool bind_matches_key(struct cmd_bind *bind, int num_keys, const int *keys)
|
|
{
|
|
if (bind->num_keys != num_keys)
|
|
return false;
|
|
for (int i = 0; i < num_keys; i++) {
|
|
if (bind->keys[i] != keys[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void bind_keys(struct input_ctx *ictx, bool builtin, bstr section,
|
|
const int *keys, int num_keys, bstr command,
|
|
const char *loc)
|
|
{
|
|
struct cmd_bind_section *bs = get_bind_section(ictx, section);
|
|
struct cmd_bind *bind = NULL;
|
|
|
|
assert(num_keys <= MP_MAX_KEY_DOWN);
|
|
|
|
for (int n = 0; n < bs->num_binds; n++) {
|
|
struct cmd_bind *b = &bs->binds[n];
|
|
if (bind_matches_key(b, num_keys, keys) && b->is_builtin == builtin) {
|
|
bind = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bind) {
|
|
struct cmd_bind empty = {{0}};
|
|
MP_TARRAY_APPEND(bs, bs->binds, bs->num_binds, empty);
|
|
bind = &bs->binds[bs->num_binds - 1];
|
|
}
|
|
|
|
bind_dealloc(bind);
|
|
|
|
*bind = (struct cmd_bind) {
|
|
.cmd = bstrdup0(bs->binds, command),
|
|
.location = talloc_strdup(bs->binds, loc),
|
|
.owner = bs,
|
|
.is_builtin = builtin,
|
|
.num_keys = num_keys,
|
|
};
|
|
memcpy(bind->keys, keys, num_keys * sizeof(bind->keys[0]));
|
|
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
|
|
char *s = mp_input_get_key_combo_name(keys, num_keys);
|
|
MP_DBG(ictx, "add: section='%s' key='%s'%s cmd='%s' location='%s'\n",
|
|
bind->owner->section, s, bind->is_builtin ? " builtin" : "",
|
|
bind->cmd, bind->location);
|
|
talloc_free(s);
|
|
}
|
|
}
|
|
|
|
// restrict_section: every entry is forced to this section name
|
|
// if NULL, load normally and allow any sections
|
|
static int parse_config(struct input_ctx *ictx, bool builtin, bstr data,
|
|
const char *location, const char *restrict_section)
|
|
{
|
|
int n_binds = 0;
|
|
int line_no = 0;
|
|
char *cur_loc = NULL;
|
|
|
|
while (data.len) {
|
|
line_no++;
|
|
if (cur_loc)
|
|
talloc_free(cur_loc);
|
|
cur_loc = talloc_asprintf(NULL, "%s:%d", location, line_no);
|
|
|
|
bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
|
|
line = bstr_lstrip(line);
|
|
if (line.len == 0 || bstr_startswith0(line, "#"))
|
|
continue;
|
|
if (bstr_eatstart0(&line, "default-bindings ")) {
|
|
bstr orig = line;
|
|
bstr_split_tok(line, "#", &line, &(bstr){0});
|
|
line = bstr_strip(line);
|
|
if (bstr_equals0(line, "start")) {
|
|
builtin = true;
|
|
} else {
|
|
MP_ERR(ictx, "Broken line: %.*s at %s\n", BSTR_P(orig), cur_loc);
|
|
}
|
|
continue;
|
|
}
|
|
struct bstr command;
|
|
// Find the key name starting a line
|
|
struct bstr keyname = bstr_split(line, WHITESPACE, &command);
|
|
command = bstr_strip(command);
|
|
if (command.len == 0) {
|
|
MP_ERR(ictx, "Unfinished key binding: %.*s at %s\n", BSTR_P(line),
|
|
cur_loc);
|
|
continue;
|
|
}
|
|
char *name = bstrdup0(NULL, keyname);
|
|
int keys[MP_MAX_KEY_DOWN];
|
|
int num_keys = 0;
|
|
if (!mp_input_get_keys_from_string(name, MP_MAX_KEY_DOWN, &num_keys, keys))
|
|
{
|
|
talloc_free(name);
|
|
MP_ERR(ictx, "Unknown key '%.*s' at %s\n", BSTR_P(keyname), cur_loc);
|
|
continue;
|
|
}
|
|
talloc_free(name);
|
|
|
|
bstr section = bstr0(restrict_section);
|
|
if (!section.len) {
|
|
if (bstr_startswith0(command, "{")) {
|
|
int p = bstrchr(command, '}');
|
|
if (p != -1) {
|
|
section = bstr_strip(bstr_splice(command, 1, p));
|
|
command = bstr_lstrip(bstr_cut(command, p + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
bind_keys(ictx, builtin, section, keys, num_keys, command, cur_loc);
|
|
n_binds++;
|
|
|
|
// Print warnings if invalid commands are encountered.
|
|
talloc_free(mp_input_parse_cmd(ictx, command, cur_loc));
|
|
}
|
|
|
|
talloc_free(cur_loc);
|
|
|
|
return n_binds;
|
|
}
|
|
|
|
static int parse_config_file(struct input_ctx *ictx, char *file, bool warn)
|
|
{
|
|
int r = 0;
|
|
void *tmp = talloc_new(NULL);
|
|
stream_t *s = NULL;
|
|
|
|
file = mp_get_user_path(tmp, ictx->global, file);
|
|
if (!mp_path_exists(file)) {
|
|
MP_MSG(ictx, warn ? MSGL_ERR : MSGL_V,
|
|
"Input config file %s not found.\n", file);
|
|
goto done;
|
|
}
|
|
s = stream_open(file, ictx->global);
|
|
if (!s) {
|
|
MP_ERR(ictx, "Can't open input config file %s.\n", file);
|
|
goto done;
|
|
}
|
|
stream_skip_bom(s);
|
|
bstr data = stream_read_complete(s, tmp, 1000000);
|
|
if (data.start) {
|
|
MP_VERBOSE(ictx, "Parsing input config file %s\n", file);
|
|
int num = parse_config(ictx, false, data, file, NULL);
|
|
MP_VERBOSE(ictx, "Input config file %s parsed: %d binds\n", file, num);
|
|
r = 1;
|
|
} else {
|
|
MP_ERR(ictx, "Error reading input config file %s\n", file);
|
|
}
|
|
|
|
done:
|
|
free_stream(s);
|
|
talloc_free(tmp);
|
|
return r;
|
|
}
|
|
|
|
struct input_ctx *mp_input_init(struct mpv_global *global)
|
|
{
|
|
|
|
struct input_ctx *ictx = talloc_ptrtype(NULL, ictx);
|
|
*ictx = (struct input_ctx){
|
|
.global = global,
|
|
.opts = talloc_zero(ictx, struct input_opts), // replaced later
|
|
.ar_state = -1,
|
|
.log = mp_log_new(ictx, global->log, "input"),
|
|
.mouse_section = "default",
|
|
};
|
|
|
|
if (sem_init(&ictx->wakeup, 0, 0)) {
|
|
MP_FATAL(ictx, "mpv doesn't work on systems without POSIX semaphores.\n");
|
|
abort();
|
|
}
|
|
|
|
mpthread_mutex_init_recursive(&ictx->mutex);
|
|
|
|
// Setup default section, so that it does nothing.
|
|
mp_input_enable_section(ictx, NULL, MP_INPUT_ALLOW_VO_DRAGGING |
|
|
MP_INPUT_ALLOW_HIDE_CURSOR);
|
|
mp_input_set_section_mouse_area(ictx, NULL, INT_MIN, INT_MIN, INT_MAX, INT_MAX);
|
|
|
|
return ictx;
|
|
}
|
|
|
|
void mp_input_load(struct input_ctx *ictx)
|
|
{
|
|
struct input_opts *input_conf =
|
|
m_sub_options_copy(ictx, &input_config, ictx->global->opts->input_opts);
|
|
|
|
talloc_free(ictx->opts);
|
|
ictx->opts = input_conf;
|
|
|
|
// "Uncomment" the default key bindings in etc/input.conf and add them.
|
|
// All lines that do not start with '# ' are parsed.
|
|
bstr builtin = bstr0(builtin_input_conf);
|
|
while (builtin.len) {
|
|
bstr line = bstr_getline(builtin, &builtin);
|
|
bstr_eatstart0(&line, "#");
|
|
if (!bstr_startswith0(line, " "))
|
|
parse_config(ictx, true, line, "<builtin>", NULL);
|
|
}
|
|
|
|
bool config_ok = false;
|
|
if (input_conf->config_file)
|
|
config_ok = parse_config_file(ictx, input_conf->config_file, true);
|
|
if (!config_ok && ictx->global->opts->load_config) {
|
|
// Try global conf dir
|
|
char *file = mp_find_config_file(NULL, ictx->global, "input.conf");
|
|
config_ok = file && parse_config_file(ictx, file, false);
|
|
talloc_free(file);
|
|
}
|
|
if (!config_ok) {
|
|
MP_VERBOSE(ictx, "Falling back on default (hardcoded) input config\n");
|
|
}
|
|
|
|
#if HAVE_JOYSTICK
|
|
if (input_conf->use_joystick)
|
|
mp_input_joystick_add(ictx, input_conf->js_dev);
|
|
#endif
|
|
|
|
#if HAVE_LIRC
|
|
if (input_conf->use_lirc)
|
|
mp_input_lirc_add(ictx, input_conf->lirc_configfile);
|
|
#endif
|
|
|
|
if (input_conf->use_alt_gr) {
|
|
ictx->using_alt_gr = true;
|
|
}
|
|
|
|
#if HAVE_COCOA
|
|
if (input_conf->use_app_events) {
|
|
cocoa_start_event_monitor();
|
|
}
|
|
|
|
if (input_conf->use_appleremote) {
|
|
cocoa_init_apple_remote();
|
|
ictx->using_ar = true;
|
|
}
|
|
|
|
if (input_conf->use_media_keys) {
|
|
cocoa_init_media_keys();
|
|
ictx->using_cocoa_media_keys = true;
|
|
}
|
|
#endif
|
|
|
|
ictx->win_drag = ictx->global->opts->allow_win_drag;
|
|
|
|
#if defined(__MINGW32__)
|
|
if (ictx->global->opts->input_file && *ictx->global->opts->input_file)
|
|
#if HAVE_WAIO
|
|
mp_input_pipe_add(ictx, ictx->global->opts->input_file);
|
|
#else
|
|
MP_ERR(ictx, "Pipes not available.\n");
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static void clear_queue(struct cmd_queue *queue)
|
|
{
|
|
while (queue->first) {
|
|
struct mp_cmd *item = queue->first;
|
|
queue_remove(queue, item);
|
|
talloc_free(item);
|
|
}
|
|
}
|
|
|
|
void mp_input_uninit(struct input_ctx *ictx)
|
|
{
|
|
if (!ictx)
|
|
return;
|
|
|
|
#if HAVE_COCOA
|
|
if (ictx->using_ar) {
|
|
cocoa_uninit_apple_remote();
|
|
}
|
|
|
|
if (ictx->using_cocoa_media_keys) {
|
|
cocoa_uninit_media_keys();
|
|
}
|
|
#endif
|
|
|
|
close_input_sources(ictx);
|
|
clear_queue(&ictx->cmd_queue);
|
|
talloc_free(ictx->current_down_cmd);
|
|
pthread_mutex_destroy(&ictx->mutex);
|
|
sem_destroy(&ictx->wakeup);
|
|
talloc_free(ictx);
|
|
}
|
|
|
|
void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel)
|
|
{
|
|
input_lock(ictx);
|
|
ictx->cancel = cancel;
|
|
input_unlock(ictx);
|
|
}
|
|
|
|
bool mp_input_use_alt_gr(struct input_ctx *ictx)
|
|
{
|
|
return ictx->using_alt_gr;
|
|
}
|
|
|
|
struct mp_cmd *mp_input_parse_cmd(struct input_ctx *ictx, bstr str,
|
|
const char *location)
|
|
{
|
|
return mp_input_parse_cmd_(ictx->log, str, location);
|
|
}
|
|
|
|
void mp_input_run_cmd(struct input_ctx *ictx, const char **cmd)
|
|
{
|
|
mp_input_queue_cmd(ictx, mp_input_parse_cmd_strv(ictx->log, cmd));
|
|
}
|
|
|
|
struct mp_input_src_internal {
|
|
pthread_t thread;
|
|
bool thread_running;
|
|
int wakeup[2];
|
|
bool init_done;
|
|
|
|
char *cmd_buffer;
|
|
size_t cmd_buffer_size;
|
|
bool drop;
|
|
};
|
|
|
|
struct mp_input_src *mp_input_add_src(struct input_ctx *ictx)
|
|
{
|
|
input_lock(ictx);
|
|
if (ictx->num_sources == MP_MAX_SOURCES) {
|
|
input_unlock(ictx);
|
|
return NULL;
|
|
}
|
|
|
|
char name[80];
|
|
snprintf(name, sizeof(name), "#%d", ictx->num_sources + 1);
|
|
struct mp_input_src *src = talloc_ptrtype(NULL, src);
|
|
*src = (struct mp_input_src){
|
|
.global = ictx->global,
|
|
.log = mp_log_new(src, ictx->log, name),
|
|
.input_ctx = ictx,
|
|
.in = talloc(src, struct mp_input_src_internal),
|
|
};
|
|
*src->in = (struct mp_input_src_internal){
|
|
.wakeup = {-1, -1},
|
|
};
|
|
|
|
ictx->sources[ictx->num_sources++] = src;
|
|
|
|
input_unlock(ictx);
|
|
return src;
|
|
}
|
|
|
|
static void close_input_sources(struct input_ctx *ictx)
|
|
{
|
|
// To avoid lock-order issues, we first remove each source from the context,
|
|
// and then destroy it.
|
|
while (1) {
|
|
input_lock(ictx);
|
|
struct mp_input_src *src = ictx->num_sources ? ictx->sources[0] : NULL;
|
|
input_unlock(ictx);
|
|
if (!src)
|
|
break;
|
|
mp_input_src_kill(src);
|
|
}
|
|
}
|
|
|
|
void mp_input_src_kill(struct mp_input_src *src)
|
|
{
|
|
if (!src)
|
|
return;
|
|
struct input_ctx *ictx = src->input_ctx;
|
|
input_lock(ictx);
|
|
for (int n = 0; n < ictx->num_sources; n++) {
|
|
if (ictx->sources[n] == src) {
|
|
MP_TARRAY_REMOVE_AT(ictx->sources, ictx->num_sources, n);
|
|
input_unlock(ictx);
|
|
write(src->in->wakeup[1], &(char){0}, 1);
|
|
if (src->cancel)
|
|
src->cancel(src);
|
|
if (src->in->thread_running)
|
|
pthread_join(src->in->thread, NULL);
|
|
if (src->uninit)
|
|
src->uninit(src);
|
|
talloc_free(src);
|
|
return;
|
|
}
|
|
}
|
|
abort();
|
|
}
|
|
|
|
void mp_input_src_init_done(struct mp_input_src *src)
|
|
{
|
|
assert(!src->in->init_done);
|
|
assert(src->in->thread_running);
|
|
assert(pthread_equal(src->in->thread, pthread_self()));
|
|
src->in->init_done = true;
|
|
mp_rendezvous(&src->in->init_done, 0);
|
|
}
|
|
|
|
static void *input_src_thread(void *ptr)
|
|
{
|
|
void **args = ptr;
|
|
struct mp_input_src *src = args[0];
|
|
void (*loop_fn)(struct mp_input_src *src, void *ctx) = args[1];
|
|
void *ctx = args[2];
|
|
|
|
mpthread_set_name("input source");
|
|
|
|
src->in->thread_running = true;
|
|
|
|
loop_fn(src, ctx);
|
|
|
|
if (!src->in->init_done)
|
|
mp_rendezvous(&src->in->init_done, -1);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
|
|
void (*loop_fn)(struct mp_input_src *src, void *ctx))
|
|
{
|
|
struct mp_input_src *src = mp_input_add_src(ictx);
|
|
if (!src)
|
|
return -1;
|
|
|
|
#ifndef __MINGW32__
|
|
// Always create for convenience.
|
|
if (mp_make_wakeup_pipe(src->in->wakeup) < 0) {
|
|
mp_input_src_kill(src);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
void *args[] = {src, loop_fn, ctx};
|
|
if (pthread_create(&src->in->thread, NULL, input_src_thread, args)) {
|
|
mp_input_src_kill(src);
|
|
return -1;
|
|
}
|
|
if (mp_rendezvous(&src->in->init_done, 0) < 0) {
|
|
mp_input_src_kill(src);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int mp_input_src_get_wakeup_fd(struct mp_input_src *src)
|
|
{
|
|
return src->in->wakeup[0];
|
|
}
|
|
|
|
#define CMD_BUFFER (4 * 4096)
|
|
|
|
void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len)
|
|
{
|
|
struct mp_input_src_internal *in = src->in;
|
|
if (!in->cmd_buffer)
|
|
in->cmd_buffer = talloc_size(in, CMD_BUFFER);
|
|
while (len) {
|
|
char *next = memchr(buf, '\n', len);
|
|
bool term = !!next;
|
|
next = next ? next + 1 : buf + len;
|
|
size_t copy = next - buf;
|
|
bool overflow = copy > CMD_BUFFER - in->cmd_buffer_size;
|
|
if (overflow || in->drop) {
|
|
in->cmd_buffer_size = 0;
|
|
in->drop = overflow || !term;
|
|
MP_WARN(src, "Dropping overlong line.\n");
|
|
} else {
|
|
memcpy(in->cmd_buffer + in->cmd_buffer_size, buf, copy);
|
|
in->cmd_buffer_size += copy;
|
|
buf += copy;
|
|
len -= copy;
|
|
if (term) {
|
|
bstr s = {in->cmd_buffer, in->cmd_buffer_size};
|
|
s = bstr_strip(s);
|
|
struct mp_cmd *cmd= mp_input_parse_cmd_(src->log, s, "<>");
|
|
if (cmd)
|
|
mp_input_queue_cmd(src->input_ctx, cmd);
|
|
in->cmd_buffer_size = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void mp_input_set_repeat_info(struct input_ctx *ictx, int rate, int delay)
|
|
{
|
|
input_lock(ictx);
|
|
ictx->opts->ar_rate = rate;
|
|
ictx->opts->ar_delay = delay;
|
|
input_unlock(ictx);
|
|
}
|
|
|