0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 20:03:10 +02:00
mpv/input/input.c
Uoti Urpala 82b8f89bae 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.
2011-07-17 07:36:09 +03:00

1992 lines
64 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 <ctype.h>
#include <assert.h>
#include "input.h"
#include "mp_fifo.h"
#include "keycodes.h"
#include "osdep/timer.h"
#include "libavutil/avstring.h"
#include "mp_msg.h"
#include "m_config.h"
#include "m_option.h"
#include "path.h"
#include "talloc.h"
#include "options.h"
#include "bstr.h"
#include "joystick.h"
#ifdef CONFIG_LIRC
#include "lirc.h"
#endif
#ifdef CONFIG_LIRCC
#include <lirc/lircc.h>
#endif
#include "ar.h"
#define MP_MAX_KEY_DOWN 32
struct cmd_bind {
int input[MP_MAX_KEY_DOWN + 1];
char *cmd;
};
struct key_name {
int key;
char *name;
};
/// This array defines all known commands.
/// The first field is an id used to recognize the command without too many strcmp.
/// The second is obviously the command name.
/// The third is the minimum number of arguments this command needs.
/// Then comes the definition of each argument, terminated with an arg of type -1.
/// A command can take a maximum of MP_CMD_MAX_ARGS-1 arguments (-1 because of
/// the last one) which is actually 9.
/// For the args, the first field is the type (actually int, float or string), the second
/// is the default value wich is used for optional arguments
static const mp_cmd_t mp_cmds[] = {
#ifdef CONFIG_RADIO
{ MP_CMD_RADIO_STEP_CHANNEL, "radio_step_channel", 1, { { MP_CMD_ARG_INT ,{0}}, {-1,{0}} }},
{ MP_CMD_RADIO_SET_CHANNEL, "radio_set_channel", 1, { { MP_CMD_ARG_STRING, {0}}, {-1,{0}} }},
{ MP_CMD_RADIO_SET_FREQ, "radio_set_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_RADIO_STEP_FREQ, "radio_step_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
#endif
{ MP_CMD_SEEK, "seek", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_EDL_MARK, "edl_mark", 0, { {-1,{0}} } },
{ MP_CMD_AUDIO_DELAY, "audio_delay", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SPEED_INCR, "speed_incr", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_SPEED_MULT, "speed_mult", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_SPEED_SET, "speed_set", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_QUIT, "quit", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_STOP, "stop", 0, { {-1,{0}} } },
{ MP_CMD_PAUSE, "pause", 0, { {-1,{0}} } },
{ MP_CMD_FRAME_STEP, "frame_step", 0, { {-1,{0}} } },
{ MP_CMD_PLAY_TREE_STEP, "pt_step",1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT ,{0}}, {-1,{0}} } },
{ MP_CMD_PLAY_TREE_UP_STEP, "pt_up_step",1, { { MP_CMD_ARG_INT,{0} }, { MP_CMD_ARG_INT ,{0}}, {-1,{0}} } },
{ MP_CMD_PLAY_ALT_SRC_STEP, "alt_src_step",1, { { MP_CMD_ARG_INT,{0} }, {-1,{0}} } },
{ MP_CMD_LOOP, "loop", 1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SUB_DELAY, "sub_delay",1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SUB_STEP, "sub_step",1, { { MP_CMD_ARG_INT,{0} }, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_OSD, "osd",0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_OSD_SHOW_TEXT, "osd_show_text", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{-1}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_OSD_SHOW_PROPERTY_TEXT, "osd_show_property_text",1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{-1}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_OSD_SHOW_PROGRESSION, "osd_show_progression", 0, { {-1,{0}} } },
{ MP_CMD_VOLUME, "volume", 1, { { MP_CMD_ARG_FLOAT,{0} }, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_BALANCE, "balance", 1, { { MP_CMD_ARG_FLOAT,{0} }, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_MIXER_USEMASTER, "use_master", 0, { {-1,{0}} } },
{ MP_CMD_MUTE, "mute", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_CONTRAST, "contrast",1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_GAMMA, "gamma", 1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_BRIGHTNESS, "brightness",1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_HUE, "hue",1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SATURATION, "saturation",1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_FRAMEDROPPING, "frame_drop",0, { { MP_CMD_ARG_INT,{-1} }, {-1,{0}} } },
{ MP_CMD_SUB_POS, "sub_pos", 1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SUB_ALIGNMENT, "sub_alignment",0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_SUB_VISIBILITY, "sub_visibility", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_SUB_LOAD, "sub_load", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
{ MP_CMD_SUB_REMOVE, "sub_remove", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_SUB_SELECT, "vobsub_lang", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } }, // for compatibility
{ MP_CMD_SUB_SELECT, "sub_select", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } },
{ MP_CMD_SUB_SOURCE, "sub_source", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } },
{ MP_CMD_SUB_VOB, "sub_vob", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } },
{ MP_CMD_SUB_DEMUX, "sub_demux", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } },
{ MP_CMD_SUB_FILE, "sub_file", 0, { { MP_CMD_ARG_INT,{-2} }, {-1,{0}} } },
{ MP_CMD_SUB_LOG, "sub_log", 0, { {-1,{0}} } },
{ MP_CMD_SUB_SCALE, "sub_scale",1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
#ifdef CONFIG_ASS
{ MP_CMD_ASS_USE_MARGINS, "ass_use_margins", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
#endif
{ MP_CMD_GET_PERCENT_POS, "get_percent_pos", 0, { {-1,{0}} } },
{ MP_CMD_GET_TIME_POS, "get_time_pos", 0, { {-1,{0}} } },
{ MP_CMD_GET_TIME_LENGTH, "get_time_length", 0, { {-1,{0}} } },
{ MP_CMD_GET_FILENAME, "get_file_name", 0, { {-1,{0}} } },
{ MP_CMD_GET_VIDEO_CODEC, "get_video_codec", 0, { {-1,{0}} } },
{ MP_CMD_GET_VIDEO_BITRATE, "get_video_bitrate", 0, { {-1,{0}} } },
{ MP_CMD_GET_VIDEO_RESOLUTION, "get_video_resolution", 0, { {-1,{0}} } },
{ MP_CMD_GET_AUDIO_CODEC, "get_audio_codec", 0, { {-1,{0}} } },
{ MP_CMD_GET_AUDIO_BITRATE, "get_audio_bitrate", 0, { {-1,{0}} } },
{ MP_CMD_GET_AUDIO_SAMPLES, "get_audio_samples", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_TITLE, "get_meta_title", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_ARTIST, "get_meta_artist", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_ALBUM, "get_meta_album", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_YEAR, "get_meta_year", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_COMMENT, "get_meta_comment", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_TRACK, "get_meta_track", 0, { {-1,{0}} } },
{ MP_CMD_GET_META_GENRE, "get_meta_genre", 0, { {-1,{0}} } },
{ MP_CMD_SWITCH_AUDIO, "switch_audio", 0, { { MP_CMD_ARG_INT,{-1} }, {-1,{0}} } },
{ MP_CMD_SWITCH_ANGLE, "switch_angle", 0, { { MP_CMD_ARG_INT,{-1} }, {-1,{0}} } },
{ MP_CMD_SWITCH_TITLE, "switch_title", 0, { { MP_CMD_ARG_INT,{-1} }, {-1,{0}} } },
#ifdef CONFIG_TV
{ MP_CMD_TV_START_SCAN, "tv_start_scan", 0, { {-1,{0}} }},
{ MP_CMD_TV_STEP_CHANNEL, "tv_step_channel", 1, { { MP_CMD_ARG_INT ,{0}}, {-1,{0}} }},
{ MP_CMD_TV_STEP_NORM, "tv_step_norm",0, { {-1,{0}} } },
{ MP_CMD_TV_STEP_CHANNEL_LIST, "tv_step_chanlist", 0, { {-1,{0}} } },
{ MP_CMD_TV_SET_CHANNEL, "tv_set_channel", 1, { { MP_CMD_ARG_STRING, {0}}, {-1,{0}} }},
{ MP_CMD_TV_LAST_CHANNEL, "tv_last_channel", 0, { {-1,{0}} } },
{ MP_CMD_TV_SET_FREQ, "tv_set_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_TV_STEP_FREQ, "tv_step_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_TV_SET_NORM, "tv_set_norm", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
{ MP_CMD_TV_SET_BRIGHTNESS, "tv_set_brightness", 1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT,{1} }, {-1,{0}} }},
{ MP_CMD_TV_SET_CONTRAST, "tv_set_contrast", 1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT,{1} }, {-1,{0}} }},
{ MP_CMD_TV_SET_HUE, "tv_set_hue", 1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT,{1} }, {-1,{0}} }},
{ MP_CMD_TV_SET_SATURATION, "tv_set_saturation", 1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT,{1} }, {-1,{0}} }},
#endif
{ MP_CMD_SUB_FORCED_ONLY, "forced_subs_only", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
#ifdef CONFIG_DVBIN
{ MP_CMD_DVB_SET_CHANNEL, "dvb_set_channel", 2, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}}}},
#endif
{ MP_CMD_SWITCH_RATIO, "switch_ratio", 0, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } },
{ MP_CMD_VO_FULLSCREEN, "vo_fullscreen", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_VO_ONTOP, "vo_ontop", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_FILE_FILTER, "file_filter", 1, { { MP_CMD_ARG_INT, {0}}, {-1,{0}}}},
{ MP_CMD_VO_ROOTWIN, "vo_rootwin", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_VO_BORDER, "vo_border", 0, { {MP_CMD_ARG_INT,{-1}}, {-1,{0}} } },
{ MP_CMD_SCREENSHOT, "screenshot", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_PANSCAN, "panscan",1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SWITCH_VSYNC, "switch_vsync", 0, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_LOADFILE, "loadfile", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_LOADLIST, "loadlist", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_RUN, "run", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
{ MP_CMD_CAPTURING, "capturing", 0, { {-1,{0}} } },
{ MP_CMD_VF_CHANGE_RECTANGLE, "change_rectangle", 2, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}}}},
{ MP_CMD_TV_TELETEXT_ADD_DEC, "teletext_add_dec", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
{ MP_CMD_TV_TELETEXT_GO_LINK, "teletext_go_link", 1, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
#ifdef CONFIG_DVDNAV
{ MP_CMD_DVDNAV, "dvdnav", 1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
#endif
#ifdef CONFIG_MENU
{ MP_CMD_MENU, "menu",1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_SET_MENU, "set_menu",1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_CHELP, "help", 0, { {-1,{0}} } },
{ MP_CMD_CEXIT, "exit", 0, { {-1,{0}} } },
{ MP_CMD_CHIDE, "hide", 0, { {MP_CMD_ARG_INT,{3000}}, {-1,{0}} } },
#endif
{ MP_CMD_GET_VO_FULLSCREEN, "get_vo_fullscreen", 0, { {-1,{0}} } },
{ MP_CMD_GET_SUB_VISIBILITY, "get_sub_visibility", 0, { {-1,{0}} } },
{ MP_CMD_KEYDOWN_EVENTS, "key_down_event", 1, { {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SET_PROPERTY, "set_property", 2, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_SET_PROPERTY_OSD, "set_property_osd", 2, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_GET_PROPERTY, "get_property", 1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_STEP_PROPERTY, "step_property", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_STEP_PROPERTY_OSD, "step_property_osd", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SEEK_CHAPTER, "seek_chapter", 1, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_SET_MOUSE_POS, "set_mouse_pos", 2, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
{ MP_CMD_AF_SWITCH, "af_switch", 1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_AF_ADD, "af_add", 1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_AF_DEL, "af_del", 1, { {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ MP_CMD_AF_CLR, "af_clr", 0, { {-1,{0}} } },
{ MP_CMD_AF_CMDLINE, "af_cmdline", 2, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_STRING, {0}}, {-1,{0}} } },
{ 0, NULL, 0, {} }
};
/// The names of the keys as used in input.conf
/// If you add some new keys, you also need to add them here
static const struct key_name key_names[] = {
{ ' ', "SPACE" },
{ '#', "SHARP" },
{ KEY_ENTER, "ENTER" },
{ KEY_TAB, "TAB" },
{ KEY_BACKSPACE, "BS" },
{ KEY_DELETE, "DEL" },
{ KEY_INSERT, "INS" },
{ KEY_HOME, "HOME" },
{ KEY_END, "END" },
{ KEY_PAGE_UP, "PGUP" },
{ KEY_PAGE_DOWN, "PGDWN" },
{ KEY_ESC, "ESC" },
{ KEY_RIGHT, "RIGHT" },
{ KEY_LEFT, "LEFT" },
{ KEY_DOWN, "DOWN" },
{ KEY_UP, "UP" },
{ KEY_F+1, "F1" },
{ KEY_F+2, "F2" },
{ KEY_F+3, "F3" },
{ KEY_F+4, "F4" },
{ KEY_F+5, "F5" },
{ KEY_F+6, "F6" },
{ KEY_F+7, "F7" },
{ KEY_F+8, "F8" },
{ KEY_F+9, "F9" },
{ KEY_F+10, "F10" },
{ KEY_F+11, "F11" },
{ KEY_F+12, "F12" },
{ KEY_KP0, "KP0" },
{ KEY_KP1, "KP1" },
{ KEY_KP2, "KP2" },
{ KEY_KP3, "KP3" },
{ KEY_KP4, "KP4" },
{ KEY_KP5, "KP5" },
{ KEY_KP6, "KP6" },
{ KEY_KP7, "KP7" },
{ KEY_KP8, "KP8" },
{ KEY_KP9, "KP9" },
{ KEY_KPDEL, "KP_DEL" },
{ KEY_KPDEC, "KP_DEC" },
{ KEY_KPINS, "KP_INS" },
{ KEY_KPENTER, "KP_ENTER" },
{ MOUSE_BTN0, "MOUSE_BTN0" },
{ MOUSE_BTN1, "MOUSE_BTN1" },
{ MOUSE_BTN2, "MOUSE_BTN2" },
{ MOUSE_BTN3, "MOUSE_BTN3" },
{ MOUSE_BTN4, "MOUSE_BTN4" },
{ MOUSE_BTN5, "MOUSE_BTN5" },
{ MOUSE_BTN6, "MOUSE_BTN6" },
{ MOUSE_BTN7, "MOUSE_BTN7" },
{ MOUSE_BTN8, "MOUSE_BTN8" },
{ MOUSE_BTN9, "MOUSE_BTN9" },
{ MOUSE_BTN10, "MOUSE_BTN10" },
{ MOUSE_BTN11, "MOUSE_BTN11" },
{ MOUSE_BTN12, "MOUSE_BTN12" },
{ MOUSE_BTN13, "MOUSE_BTN13" },
{ MOUSE_BTN14, "MOUSE_BTN14" },
{ MOUSE_BTN15, "MOUSE_BTN15" },
{ MOUSE_BTN16, "MOUSE_BTN16" },
{ MOUSE_BTN17, "MOUSE_BTN17" },
{ MOUSE_BTN18, "MOUSE_BTN18" },
{ MOUSE_BTN19, "MOUSE_BTN19" },
{ MOUSE_BTN0_DBL, "MOUSE_BTN0_DBL" },
{ MOUSE_BTN1_DBL, "MOUSE_BTN1_DBL" },
{ MOUSE_BTN2_DBL, "MOUSE_BTN2_DBL" },
{ MOUSE_BTN3_DBL, "MOUSE_BTN3_DBL" },
{ MOUSE_BTN4_DBL, "MOUSE_BTN4_DBL" },
{ MOUSE_BTN5_DBL, "MOUSE_BTN5_DBL" },
{ MOUSE_BTN6_DBL, "MOUSE_BTN6_DBL" },
{ MOUSE_BTN7_DBL, "MOUSE_BTN7_DBL" },
{ MOUSE_BTN8_DBL, "MOUSE_BTN8_DBL" },
{ MOUSE_BTN9_DBL, "MOUSE_BTN9_DBL" },
{ MOUSE_BTN10_DBL, "MOUSE_BTN10_DBL" },
{ MOUSE_BTN11_DBL, "MOUSE_BTN11_DBL" },
{ MOUSE_BTN12_DBL, "MOUSE_BTN12_DBL" },
{ MOUSE_BTN13_DBL, "MOUSE_BTN13_DBL" },
{ MOUSE_BTN14_DBL, "MOUSE_BTN14_DBL" },
{ MOUSE_BTN15_DBL, "MOUSE_BTN15_DBL" },
{ MOUSE_BTN16_DBL, "MOUSE_BTN16_DBL" },
{ MOUSE_BTN17_DBL, "MOUSE_BTN17_DBL" },
{ MOUSE_BTN18_DBL, "MOUSE_BTN18_DBL" },
{ MOUSE_BTN19_DBL, "MOUSE_BTN19_DBL" },
{ JOY_AXIS1_MINUS, "JOY_UP" },
{ JOY_AXIS1_PLUS, "JOY_DOWN" },
{ JOY_AXIS0_MINUS, "JOY_LEFT" },
{ JOY_AXIS0_PLUS, "JOY_RIGHT" },
{ JOY_AXIS0_PLUS, "JOY_AXIS0_PLUS" },
{ JOY_AXIS0_MINUS, "JOY_AXIS0_MINUS" },
{ JOY_AXIS1_PLUS, "JOY_AXIS1_PLUS" },
{ JOY_AXIS1_MINUS, "JOY_AXIS1_MINUS" },
{ JOY_AXIS2_PLUS, "JOY_AXIS2_PLUS" },
{ JOY_AXIS2_MINUS, "JOY_AXIS2_MINUS" },
{ JOY_AXIS3_PLUS, "JOY_AXIS3_PLUS" },
{ JOY_AXIS3_MINUS, "JOY_AXIS3_MINUS" },
{ JOY_AXIS4_PLUS, "JOY_AXIS4_PLUS" },
{ JOY_AXIS4_MINUS, "JOY_AXIS4_MINUS" },
{ JOY_AXIS5_PLUS, "JOY_AXIS5_PLUS" },
{ JOY_AXIS5_MINUS, "JOY_AXIS5_MINUS" },
{ JOY_AXIS6_PLUS, "JOY_AXIS6_PLUS" },
{ JOY_AXIS6_MINUS, "JOY_AXIS6_MINUS" },
{ JOY_AXIS7_PLUS, "JOY_AXIS7_PLUS" },
{ JOY_AXIS7_MINUS, "JOY_AXIS7_MINUS" },
{ JOY_AXIS8_PLUS, "JOY_AXIS8_PLUS" },
{ JOY_AXIS8_MINUS, "JOY_AXIS8_MINUS" },
{ JOY_AXIS9_PLUS, "JOY_AXIS9_PLUS" },
{ JOY_AXIS9_MINUS, "JOY_AXIS9_MINUS" },
{ JOY_BTN0, "JOY_BTN0" },
{ JOY_BTN1, "JOY_BTN1" },
{ JOY_BTN2, "JOY_BTN2" },
{ JOY_BTN3, "JOY_BTN3" },
{ JOY_BTN4, "JOY_BTN4" },
{ JOY_BTN5, "JOY_BTN5" },
{ JOY_BTN6, "JOY_BTN6" },
{ JOY_BTN7, "JOY_BTN7" },
{ JOY_BTN8, "JOY_BTN8" },
{ JOY_BTN9, "JOY_BTN9" },
{ AR_PLAY, "AR_PLAY" },
{ AR_PLAY_HOLD, "AR_PLAY_HOLD" },
{ AR_NEXT, "AR_NEXT" },
{ AR_NEXT_HOLD, "AR_NEXT_HOLD" },
{ AR_PREV, "AR_PREV" },
{ AR_PREV_HOLD, "AR_PREV_HOLD" },
{ AR_MENU, "AR_MENU" },
{ AR_MENU_HOLD, "AR_MENU_HOLD" },
{ AR_VUP, "AR_VUP" },
{ AR_VDOWN, "AR_VDOWN" },
{ KEY_POWER, "POWER" },
{ KEY_MENU, "MENU" },
{ KEY_PLAY, "PLAY" },
{ KEY_PAUSE, "PAUSE" },
{ KEY_PLAYPAUSE, "PLAYPAUSE" },
{ KEY_STOP, "STOP" },
{ KEY_FORWARD, "FORWARD" },
{ KEY_REWIND, "REWIND" },
{ KEY_NEXT, "NEXT" },
{ KEY_PREV, "PREV" },
{ KEY_VOLUME_UP, "VOLUME_UP" },
{ KEY_VOLUME_DOWN, "VOLUME_DOWN" },
{ KEY_MUTE, "MUTE" },
// These are kept for backward compatibility
{ KEY_PAUSE, "XF86_PAUSE" },
{ KEY_STOP, "XF86_STOP" },
{ KEY_PREV, "XF86_PREV" },
{ KEY_NEXT, "XF86_NEXT" },
{ KEY_CLOSE_WIN, "CLOSE_WIN" },
{ 0, NULL }
};
struct key_name modifier_names[] = {
{ KEY_MODIFIER_SHIFT, "Shift" },
{ KEY_MODIFIER_CTRL, "Ctrl" },
{ KEY_MODIFIER_ALT, "Alt" },
{ KEY_MODIFIER_META, "Meta" },
{ 0 }
};
#define KEY_MODIFIER_MASK (KEY_MODIFIER_SHIFT | KEY_MODIFIER_CTRL | KEY_MODIFIER_ALT | KEY_MODIFIER_META)
// This is the default binding. The content of input.conf overrides these.
// The first arg is a null terminated array of key codes.
// The second is the command
static const struct cmd_bind def_cmd_binds[] = {
{ { MOUSE_BTN0_DBL, 0 }, "vo_fullscreen" },
{ { MOUSE_BTN2, 0 }, "pause" },
{ { MOUSE_BTN3, 0 }, "seek 10" },
{ { MOUSE_BTN4, 0 }, "seek -10" },
{ { MOUSE_BTN5, 0 }, "volume 1" },
{ { MOUSE_BTN6, 0 }, "volume -1" },
#ifdef CONFIG_DVDNAV
{ { KEY_KP8, 0 }, "dvdnav up" }, // up
{ { KEY_KP2, 0 }, "dvdnav down" }, // down
{ { KEY_KP4, 0 }, "dvdnav left" }, // left
{ { KEY_KP6, 0 }, "dvdnav right" }, // right
{ { KEY_KP5, 0 }, "dvdnav menu" }, // menu
{ { KEY_KPENTER, 0 }, "dvdnav select" }, // select
{ { MOUSE_BTN0, 0 }, "dvdnav mouse" }, //select
{ { KEY_KP7, 0 }, "dvdnav prev" }, // previous menu
#endif
{ { KEY_RIGHT, 0 }, "seek 10" },
{ { KEY_LEFT, 0 }, "seek -10" },
{ { KEY_MODIFIER_SHIFT + KEY_RIGHT, 0 }, "seek 1 0 1" },
{ { KEY_MODIFIER_SHIFT + KEY_LEFT, 0 }, "seek -1 0 1" },
{ { KEY_UP, 0 }, "seek 60" },
{ { KEY_DOWN, 0 }, "seek -60" },
{ { KEY_MODIFIER_SHIFT + KEY_UP, 0 }, "seek 5 0 1" },
{ { KEY_MODIFIER_SHIFT + KEY_DOWN, 0 }, "seek -5 0 1" },
{ { KEY_PAGE_UP, 0 }, "seek 600" },
{ { KEY_PAGE_DOWN, 0 }, "seek -600" },
{ { '+', 0 }, "audio_delay 0.100" },
{ { '-', 0 }, "audio_delay -0.100" },
{ { '[', 0 }, "speed_mult 0.9091" },
{ { ']', 0 }, "speed_mult 1.1" },
{ { '{', 0 }, "speed_mult 0.5" },
{ { '}', 0 }, "speed_mult 2.0" },
{ { KEY_BACKSPACE, 0 }, "speed_set 1.0" },
{ { 'q', 0 }, "quit" },
{ { KEY_ESC, 0 }, "quit" },
{ { 'p', 0 }, "pause" },
{ { ' ', 0 }, "pause" },
{ { '.', 0 }, "frame_step" },
{ { KEY_HOME, 0 }, "pt_up_step 1" },
{ { KEY_END, 0 }, "pt_up_step -1" },
{ { '>', 0 }, "pt_step 1" },
{ { KEY_ENTER, 0 }, "pt_step 1 1" },
{ { '<', 0 }, "pt_step -1" },
{ { KEY_INS, 0 }, "alt_src_step 1" },
{ { KEY_DEL, 0 }, "alt_src_step -1" },
{ { 'o', 0 }, "osd" },
{ { 'I', 0 }, "osd_show_property_text \"${filename}\"" },
{ { 'P', 0 }, "osd_show_progression" },
{ { 'z', 0 }, "sub_delay -0.1" },
{ { 'x', 0 }, "sub_delay +0.1" },
{ { 'g', 0 }, "sub_step -1" },
{ { 'y', 0 }, "sub_step +1" },
{ { '9', 0 }, "volume -1" },
{ { '/', 0 }, "volume -1" },
{ { '0', 0 }, "volume 1" },
{ { '*', 0 }, "volume 1" },
{ { '(', 0 }, "balance -0.1" },
{ { ')', 0 }, "balance 0.1" },
{ { 'm', 0 }, "mute" },
{ { '1', 0 }, "contrast -1" },
{ { '2', 0 }, "contrast 1" },
{ { '3', 0 }, "brightness -1" },
{ { '4', 0 }, "brightness 1" },
{ { '5', 0 }, "hue -1" },
{ { '6', 0 }, "hue 1" },
{ { '7', 0 }, "saturation -1" },
{ { '8', 0 }, "saturation 1" },
{ { 'd', 0 }, "frame_drop" },
{ { 'D', 0 }, "step_property_osd deinterlace" },
{ { 'c', 0 }, "step_property_osd yuv_colorspace" },
{ { 'r', 0 }, "sub_pos -1" },
{ { 't', 0 }, "sub_pos +1" },
{ { 'a', 0 }, "sub_alignment" },
{ { 'v', 0 }, "sub_visibility" },
{ { 'j', 0 }, "sub_select" },
{ { 'J', 0 }, "sub_select -3" },
{ { 'F', 0 }, "forced_subs_only" },
{ { '#', 0 }, "switch_audio" },
{ { '_', 0 }, "step_property switch_video" },
{ { KEY_TAB, 0 }, "step_property switch_program" },
{ { 'i', 0 }, "edl_mark" },
#ifdef CONFIG_TV
{ { 'h', 0 }, "tv_step_channel 1" },
{ { 'k', 0 }, "tv_step_channel -1" },
{ { 'n', 0 }, "tv_step_norm" },
{ { 'u', 0 }, "tv_step_chanlist" },
#endif
{ { 'X', 0 }, "step_property teletext_mode 1" },
{ { 'W', 0 }, "step_property teletext_page 1" },
{ { 'Q', 0 }, "step_property teletext_page -1" },
#ifdef CONFIG_JOYSTICK
{ { JOY_AXIS0_PLUS, 0 }, "seek 10" },
{ { JOY_AXIS0_MINUS, 0 }, "seek -10" },
{ { JOY_AXIS1_MINUS, 0 }, "seek 60" },
{ { JOY_AXIS1_PLUS, 0 }, "seek -60" },
{ { JOY_BTN0, 0 }, "pause" },
{ { JOY_BTN1, 0 }, "osd" },
{ { JOY_BTN2, 0 }, "volume 1"},
{ { JOY_BTN3, 0 }, "volume -1"},
#endif
#ifdef CONFIG_APPLE_REMOTE
{ { AR_PLAY, 0}, "pause" },
{ { AR_PLAY_HOLD, 0}, "quit" },
{ { AR_NEXT, 0 }, "seek 30" },
{ { AR_NEXT_HOLD, 0 }, "seek 120" },
{ { AR_PREV, 0 }, "seek -10" },
{ { AR_PREV_HOLD, 0 }, "seek -120" },
{ { AR_MENU, 0 }, "osd" },
{ { AR_MENU_HOLD, 0 }, "mute" },
{ { AR_VUP, 0 }, "volume 1"},
{ { AR_VDOWN, 0 }, "volume -1"},
#endif
{ { 'T', 0 }, "vo_ontop" },
{ { 'f', 0 }, "vo_fullscreen" },
{ { 'C', 0 }, "step_property_osd capturing" },
{ { 's', 0 }, "screenshot 0" },
{ { 'S', 0 }, "screenshot 1" },
{ { 'w', 0 }, "panscan -0.1" },
{ { 'e', 0 }, "panscan +0.1" },
{ { KEY_POWER, 0 }, "quit" },
{ { KEY_MENU, 0 }, "osd" },
{ { KEY_PLAY, 0 }, "pause" },
{ { KEY_PAUSE, 0 }, "pause" },
{ { KEY_PLAYPAUSE, 0 }, "pause" },
{ { KEY_STOP, 0 }, "quit" },
{ { KEY_FORWARD, 0 }, "seek 60" },
{ { KEY_REWIND, 0 }, "seek -60" },
{ { KEY_NEXT, 0 }, "pt_step 1" },
{ { KEY_PREV, 0 }, "pt_step -1" },
{ { KEY_VOLUME_UP, 0 }, "volume 1" },
{ { KEY_VOLUME_DOWN, 0 }, "volume -1" },
{ { KEY_MUTE, 0 }, "mute" },
{ { KEY_CLOSE_WIN, 0 }, "quit" },
{ { '!', 0 }, "seek_chapter -1" },
{ { '@', 0 }, "seek_chapter 1" },
{ { 'A', 0 }, "switch_angle 1" },
{ { 'U', 0 }, "stop" },
{ { 0 }, NULL }
};
#ifndef MP_MAX_KEY_FD
#define MP_MAX_KEY_FD 10
#endif
#ifndef MP_MAX_CMD_FD
#define MP_MAX_CMD_FD 10
#endif
struct input_fd {
int fd;
union {
int (*key)(void *ctx, int fd);
int (*cmd)(int fd, char *dest, int size);
} read_func;
int (*close_func)(int fd);
void *ctx;
unsigned eof : 1;
unsigned drop : 1;
unsigned dead : 1;
unsigned got_cmd : 1;
unsigned no_select : 1;
// These fields are for the cmd fds.
char *buffer;
int pos, size;
};
struct cmd_filter {
mp_input_cmd_filter filter;
void *ctx;
struct cmd_filter *next;
};
struct cmd_bind_section {
struct cmd_bind *cmd_binds;
char *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;
mp_cmd_t *ar_cmd;
unsigned int last_ar;
// 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];
unsigned int num_key_down;
unsigned int last_key_down;
bool default_bindings;
// List of command binding sections
struct cmd_bind_section *cmd_bind_sections;
// Name of currently used command section
char *section;
// The command binds of current section
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;
struct cmd_queue key_cmd_queue;
struct cmd_queue control_cmd_queue;
};
static struct cmd_filter *cmd_filters = NULL;
// Callback to allow the menu filter to grab the incoming keys
int (*mp_input_key_cb)(int code) = NULL;
int async_quit_request;
static int print_key_list(m_option_t *cfg);
static int print_cmd_list(m_option_t *cfg);
// Our command line options
static const m_option_t input_conf[] = {
OPT_STRING("conf", input.config_file, CONF_GLOBAL),
OPT_INT("ar-delay", input.ar_delay, CONF_GLOBAL),
OPT_INT("ar-rate", input.ar_rate, CONF_GLOBAL),
{ "keylist", print_key_list, CONF_TYPE_FUNC, CONF_GLOBAL, 0, 0, NULL },
{ "cmdlist", print_cmd_list, CONF_TYPE_FUNC, CONF_GLOBAL, 0, 0, NULL },
OPT_STRING("js-dev", input.js_dev, CONF_GLOBAL),
OPT_STRING("ar-dev", input.ar_dev, CONF_GLOBAL),
OPT_STRING("file", input.in_file, CONF_GLOBAL),
OPT_MAKE_FLAGS("default-bindings", input.default_bindings, CONF_GLOBAL),
{ NULL, NULL, 0, 0, 0, 0, NULL}
};
static const m_option_t mp_input_opts[] = {
{ "input", (void *)&input_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
OPT_MAKE_FLAGS("joystick", input.use_joystick, CONF_GLOBAL),
OPT_MAKE_FLAGS("lirc", input.use_lirc, CONF_GLOBAL),
OPT_MAKE_FLAGS("lircc", input.use_lircc, CONF_GLOBAL),
OPT_MAKE_FLAGS("ar", input.use_ar, CONF_GLOBAL),
{ NULL, NULL, 0, 0, 0, 0, NULL}
};
static int default_cmd_func(int fd, char *buf, int l);
static char *get_key_name(int key, char *ret)
{
for (int i = 0; modifier_names[i].name; i++) {
if (modifier_names[i].key & key) {
ret = talloc_asprintf_append_buffer(ret, "%s+",
modifier_names[i].name);
key -= modifier_names[i].key;
}
}
for (int i = 0; key_names[i].name != NULL; i++) {
if (key_names[i].key == key)
return talloc_asprintf_append_buffer(ret, "%s", key_names[i].name);
}
if (isascii(key))
return talloc_asprintf_append_buffer(ret, "%c", key);
// Print the hex key code
return talloc_asprintf_append_buffer(ret, "%#-8x", key);
}
static char *get_key_combo_name(int *keys, int max)
{
char *ret = talloc_strdup(NULL, "");
while (1) {
ret = get_key_name(*keys, ret);
if (--max && *++keys)
talloc_asprintf_append_buffer(ret, "-");
else
break;
}
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))
{
if (ictx->num_cmd_fd == MP_MAX_CMD_FD) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many command file descriptors, "
"cannot register file descriptor %d.\n", fd);
return 0;
}
if (select && fd < 0) {
mp_msg(MSGT_INPUT, MSGL_ERR,
"Invalid fd %d in mp_input_add_cmd_fd", fd);
return 0;
}
ictx->cmd_fds[ictx->num_cmd_fd] = (struct input_fd){
.fd = fd,
.read_func.cmd = read_func ? read_func : default_cmd_func,
.close_func = close_func,
.no_select = !select
};
ictx->num_cmd_fd++;
return 1;
}
void mp_input_rm_cmd_fd(struct input_ctx *ictx, int fd)
{
struct input_fd *cmd_fds = ictx->cmd_fds;
unsigned int i;
for (i = 0; i < ictx->num_cmd_fd; i++) {
if (cmd_fds[i].fd == fd)
break;
}
if (i == ictx->num_cmd_fd)
return;
if (cmd_fds[i].close_func)
cmd_fds[i].close_func(cmd_fds[i].fd);
talloc_free(cmd_fds[i].buffer);
if (i + 1 < ictx->num_cmd_fd)
memmove(&cmd_fds[i], &cmd_fds[i + 1],
(ictx->num_cmd_fd - i - 1) * sizeof(struct input_fd));
ictx->num_cmd_fd--;
}
void mp_input_rm_key_fd(struct input_ctx *ictx, int fd)
{
struct input_fd *key_fds = ictx->key_fds;
unsigned int i;
for (i = 0; i < ictx->num_key_fd; i++) {
if (key_fds[i].fd == fd)
break;
}
if (i == ictx->num_key_fd)
return;
if (key_fds[i].close_func)
key_fds[i].close_func(key_fds[i].fd);
if (i + 1 < ictx->num_key_fd)
memmove(&key_fds[i], &key_fds[i + 1],
(ictx->num_key_fd - i - 1) * sizeof(struct input_fd));
ictx->num_key_fd--;
}
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)
{
if (ictx->num_key_fd == MP_MAX_KEY_FD) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many key file descriptors, "
"cannot register file descriptor %d.\n", fd);
return 0;
}
if (select && fd < 0) {
mp_msg(MSGT_INPUT, MSGL_ERR,
"Invalid fd %d in mp_input_add_key_fd", fd);
return 0;
}
ictx->key_fds[ictx->num_key_fd] = (struct input_fd){
.fd = fd,
.read_func.key = read_func,
.close_func = close_func,
.no_select = !select,
.ctx = ctx,
};
ictx->num_key_fd++;
return 1;
}
int mp_input_parse_and_queue_cmds(struct input_ctx *ictx, const char *str)
{
int cmd_num = 0;
while (*str == '\n' || *str == '\r' || *str == ' ')
++str;
while (*str) {
mp_cmd_t *cmd;
size_t len = strcspn(str, "\r\n");
char *cmdbuf = talloc_size(NULL, len + 1);
av_strlcpy(cmdbuf, str, len + 1);
cmd = mp_input_parse_cmd(cmdbuf);
if (cmd) {
mp_input_queue_cmd(ictx, cmd);
++cmd_num;
}
str += len;
while (*str == '\n' || *str == '\r' || *str == ' ')
++str;
talloc_free(cmdbuf);
}
return cmd_num;
}
mp_cmd_t *mp_input_parse_cmd(char *str)
{
int i, l;
int pausing = 0;
char *ptr, *e;
const mp_cmd_t *cmd_def;
// Ignore heading spaces.
while (str[0] == ' ' || str[0] == '\t')
++str;
if (strncmp(str, "pausing ", 8) == 0) {
pausing = 1;
str = &str[8];
} else if (strncmp(str, "pausing_keep ", 13) == 0) {
pausing = 2;
str = &str[13];
} else if (strncmp(str, "pausing_toggle ", 15) == 0) {
pausing = 3;
str = &str[15];
} else if (strncmp(str, "pausing_keep_force ", 19) == 0) {
pausing = 4;
str = &str[19];
}
ptr = str + strcspn(str, "\t ");
if (*ptr != 0)
l = ptr - str;
else
l = strlen(str);
if (l == 0)
return NULL;
for (i = 0; mp_cmds[i].name != NULL; i++) {
if (strncasecmp(mp_cmds[i].name, str, l) == 0)
break;
}
if (mp_cmds[i].name == NULL)
return NULL;
cmd_def = &mp_cmds[i];
mp_cmd_t *cmd = talloc_ptrtype(NULL, cmd);
*cmd = (mp_cmd_t){
.id = cmd_def->id,
.name = talloc_strdup(cmd, cmd_def->name),
.pausing = pausing,
};
ptr = str;
for (i = 0; ptr && i < MP_CMD_MAX_ARGS; i++) {
while (ptr[0] != ' ' && ptr[0] != '\t' && ptr[0] != '\0')
ptr++;
if (ptr[0] == '\0')
break;
while (ptr[0] == ' ' || ptr[0] == '\t')
ptr++;
if (ptr[0] == '\0' || ptr[0] == '#')
break;
cmd->args[i].type = cmd_def->args[i].type;
switch (cmd_def->args[i].type) {
case MP_CMD_ARG_INT:
errno = 0;
cmd->args[i].v.i = atoi(ptr);
if (errno != 0) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d "
"isn't an integer.\n", cmd_def->name, i + 1);
ptr = NULL;
}
break;
case MP_CMD_ARG_FLOAT:
errno = 0;
cmd->args[i].v.f = atof(ptr);
if (errno != 0) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d "
"isn't a float.\n", cmd_def->name, i + 1);
ptr = NULL;
}
break;
case MP_CMD_ARG_STRING: {
char term;
char *ptr2 = ptr, *start;
if (ptr[0] == '\'' || ptr[0] == '"') {
term = ptr[0];
ptr2++;
} else
term = ' ';
start = ptr2;
while (1) {
e = strchr(ptr2, term);
if (!e)
break;
if (e <= ptr2 || *(e - 1) != '\\')
break;
ptr2 = e + 1;
}
if (term != ' ' && (!e || e[0] == '\0')) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d is "
"unterminated.\n", cmd_def->name, i + 1);
ptr = NULL;
break;
} else if (!e)
e = ptr + strlen(ptr);
l = e - start;
ptr2 = start;
for (e = strchr(ptr2, '\\'); e && e < start + l; e = strchr(ptr2, '\\')) {
memmove(e, e + 1, strlen(e));
ptr2 = e + 1;
l--;
}
cmd->args[i].v.s = talloc_strndup(cmd, start, l);
if (term != ' ')
ptr += l + 2;
break;
}
case -1:
ptr = NULL;
break;
default:
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Unknown argument %d\n", i);
}
}
cmd->nargs = i;
if (cmd_def->nargs > cmd->nargs) {
/* mp_msg(MSGT_INPUT, MSGL_ERR, "Got command '%s' but\n", str); */
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s requires at least %d "
"arguments, we found only %d so far.\n", cmd_def->name,
cmd_def->nargs, cmd->nargs);
mp_cmd_free(cmd);
return NULL;
}
for (; i < MP_CMD_MAX_ARGS && cmd_def->args[i].type != -1; i++) {
memcpy(&cmd->args[i], &cmd_def->args[i], sizeof(struct mp_cmd_arg));
if (cmd_def->args[i].type == MP_CMD_ARG_STRING
&& cmd_def->args[i].v.s != NULL)
cmd->args[i].v.s = talloc_strdup(cmd, cmd_def->args[i].v.s);
}
if (i < MP_CMD_MAX_ARGS)
cmd->args[i].type = -1;
return cmd;
}
#define MP_CMD_MAX_SIZE 4096
static int read_cmd(struct input_fd *mp_fd, char **ret)
{
char *end;
*ret = NULL;
// Allocate the buffer if it doesn't exist
if (!mp_fd->buffer) {
mp_fd->buffer = talloc_size(NULL, MP_CMD_MAX_SIZE);
mp_fd->pos = 0;
mp_fd->size = MP_CMD_MAX_SIZE;
}
// Get some data if needed/possible
while (!mp_fd->got_cmd && !mp_fd->eof && (mp_fd->size - mp_fd->pos > 1)) {
int r = mp_fd->read_func.cmd(mp_fd->fd, mp_fd->buffer + mp_fd->pos,
mp_fd->size - 1 - mp_fd->pos);
// Error ?
if (r < 0) {
switch (r) {
case MP_INPUT_ERROR:
case MP_INPUT_DEAD:
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error while reading "
"command file descriptor %d: %s\n",
mp_fd->fd, strerror(errno));
case MP_INPUT_NOTHING:
return r;
case MP_INPUT_RETRY:
continue;
}
// EOF ?
} else if (r == 0) {
mp_fd->eof = 1;
break;
}
mp_fd->pos += r;
break;
}
mp_fd->got_cmd = 0;
while (1) {
int l = 0;
// Find the cmd end
mp_fd->buffer[mp_fd->pos] = '\0';
end = strchr(mp_fd->buffer, '\r');
if (end)
*end = '\n';
end = strchr(mp_fd->buffer, '\n');
// No cmd end ?
if (!end) {
// If buffer is full we must drop all until the next \n
if (mp_fd->size - mp_fd->pos <= 1) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command buffer of file "
"descriptor %d is full: dropping content.\n",
mp_fd->fd);
mp_fd->pos = 0;
mp_fd->drop = 1;
}
break;
}
// We already have a cmd : set the got_cmd flag
else if ((*ret)) {
mp_fd->got_cmd = 1;
break;
}
l = end - mp_fd->buffer;
// Not dropping : put the cmd in ret
if (!mp_fd->drop)
*ret = talloc_strndup(NULL, mp_fd->buffer, l);
else
mp_fd->drop = 0;
mp_fd->pos -= l + 1;
memmove(mp_fd->buffer, end + 1, mp_fd->pos);
}
if (*ret)
return 1;
else
return MP_INPUT_NOTHING;
}
static int default_cmd_func(int fd, char *buf, int l)
{
while (1) {
int r = read(fd, buf, l);
// Error ?
if (r < 0) {
if (errno == EINTR)
continue;
else if (errno == EAGAIN)
return MP_INPUT_NOTHING;
return MP_INPUT_ERROR;
// EOF ?
}
return r;
}
}
void mp_input_add_cmd_filter(mp_input_cmd_filter func, void *ctx)
{
struct cmd_filter *filter = talloc_ptrtype(NULL, filter);
filter->filter = func;
filter->ctx = ctx;
filter->next = cmd_filters;
cmd_filters = filter;
}
static char *find_bind_for_key(const struct cmd_bind *binds, int n, int *keys)
{
int j;
if (n <= 0)
return NULL;
for (j = 0; binds[j].cmd != NULL; j++) {
int found = 1, s;
for (s = 0; s < n && binds[j].input[s] != 0; s++) {
if (binds[j].input[s] != keys[s]) {
found = 0;
break;
}
}
if (found && binds[j].input[s] == 0 && s == n)
break;
}
return binds[j].cmd;
}
static struct cmd_bind_section *get_bind_section(struct input_ctx *ictx,
char *section)
{
struct cmd_bind_section *bind_section = ictx->cmd_bind_sections;
if (section == NULL)
section = "default";
while (bind_section) {
if (strcmp(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->cmd_binds = NULL;
bind_section->section = talloc_strdup(bind_section, section);
bind_section->next = NULL;
return bind_section;
}
static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, int n, int *keys)
{
char *cmd = NULL;
mp_cmd_t *ret;
if (ictx->cmd_binds)
cmd = find_bind_for_key(ictx->cmd_binds, n, keys);
if (ictx->cmd_binds_default && cmd == NULL)
cmd = find_bind_for_key(ictx->cmd_binds_default, n, keys);
if (ictx->default_bindings && cmd == NULL)
cmd = find_bind_for_key(def_cmd_binds, n, keys);
if (cmd == NULL) {
char *key_buf = get_key_combo_name(keys, n);
mp_tmsg(MSGT_INPUT, MSGL_WARN,
"No bind found for key '%s'.\n", key_buf);
talloc_free(key_buf);
return NULL;
}
if (strcmp(cmd, "ignore") == 0)
return NULL;
ret = mp_input_parse_cmd(cmd);
if (!ret) {
char *key_buf = get_key_combo_name(keys, n);
mp_tmsg(MSGT_INPUT, MSGL_ERR,
"Invalid command for bound key '%s': '%s'\n", key_buf, cmd);
talloc_free(key_buf);
}
return ret;
}
static mp_cmd_t *interpret_key(struct input_ctx *ictx, int code)
{
unsigned int j;
mp_cmd_t *ret;
/* On normal keyboards shift changes the character code of non-special
* keys, so don't count the modifier separately for those. In other words
* we want to have "a" and "A" instead of "a" and "Shift+A"; but a separate
* shift modifier is still kept for special keys like arrow keys.
*/
int unmod = code & ~KEY_MODIFIER_MASK;
if (unmod < 256 && unmod != KEY_ENTER && unmod != KEY_TAB)
code &= ~KEY_MODIFIER_SHIFT;
if (mp_input_key_cb) {
if (code & MP_KEY_DOWN)
return NULL;
code &= ~(MP_KEY_DOWN | MP_NO_REPEAT_KEY);
if (mp_input_key_cb(code))
return NULL;
}
if (code & MP_KEY_DOWN) {
if (ictx->num_key_down > MP_MAX_KEY_DOWN) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many key down events "
"at the same time\n");
return NULL;
}
code &= ~MP_KEY_DOWN;
// Check if we don't already have this key as pushed
for (j = 0; j < ictx->num_key_down; j++) {
if (ictx->key_down[j] == code)
break;
}
if (j != ictx->num_key_down)
return NULL;
ictx->key_down[ictx->num_key_down] = code;
ictx->num_key_down++;
ictx->last_key_down = GetTimer();
ictx->ar_state = 0;
return NULL;
}
// button released or press of key with no separate down/up events
for (j = 0; j < ictx->num_key_down; j++) {
if (ictx->key_down[j] == code)
break;
}
bool doubleclick = code >= MOUSE_BTN0_DBL && code < MOUSE_BTN_DBL_END;
if (doubleclick) {
int btn = code - MOUSE_BTN0_DBL + MOUSE_BTN0;
if (!ictx->num_key_down
|| ictx->key_down[ictx->num_key_down - 1] != btn)
return NULL;
j = ictx->num_key_down - 1;
ictx->key_down[j] = code;
}
if (j == ictx->num_key_down) { // was not already down; add temporarily
if (ictx->num_key_down > MP_MAX_KEY_DOWN) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many key down events "
"at the same time\n");
return NULL;
}
ictx->key_down[ictx->num_key_down] = code;
ictx->num_key_down++;
ictx->last_key_down = 1;
}
// Interpret only maximal point of multibutton event
ret = ictx->last_key_down ?
get_cmd_from_keys(ictx, ictx->num_key_down, ictx->key_down)
: NULL;
if (doubleclick) {
ictx->key_down[j] = code - MOUSE_BTN0_DBL + MOUSE_BTN0;
return ret;
}
// Remove the key
if (j + 1 < ictx->num_key_down)
memmove(&ictx->key_down[j], &ictx->key_down[j + 1],
(ictx->num_key_down - (j + 1)) * sizeof(int));
ictx->num_key_down--;
ictx->last_key_down = 0;
ictx->ar_state = -1;
mp_cmd_free(ictx->ar_cmd);
ictx->ar_cmd = NULL;
return ret;
}
static mp_cmd_t *check_autorepeat(struct input_ctx *ictx)
{
// No input : autorepeat ?
if (ictx->ar_rate > 0 && ictx->ar_state >= 0 && ictx->num_key_down > 0
&& !(ictx->key_down[ictx->num_key_down - 1] & MP_NO_REPEAT_KEY)) {
unsigned int t = GetTimer();
// First time : wait delay
if (ictx->ar_state == 0
&& (t - ictx->last_key_down) >= ictx->ar_delay * 1000) {
ictx->ar_cmd = get_cmd_from_keys(ictx, ictx->num_key_down,
ictx->key_down);
if (!ictx->ar_cmd) {
ictx->ar_state = -1;
return NULL;
}
ictx->ar_state = 1;
ictx->last_ar = t;
return mp_cmd_clone(ictx->ar_cmd);
// Then send rate / sec event
} else if (ictx->ar_state == 1
&& (t - ictx->last_ar) >= 1000000 / ictx->ar_rate) {
ictx->last_ar = t;
return mp_cmd_clone(ictx->ar_cmd);
}
}
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 void read_events(struct input_ctx *ictx, int time)
{
ictx->got_new_events = false;
struct input_fd *key_fds = ictx->key_fds;
struct input_fd *cmd_fds = ictx->cmd_fds;
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--;
} 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 (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);
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 (time)
usec_sleep(time * 1000);
#endif
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
read_key_fd(ictx, &key_fds[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))
continue;
#endif
read_cmd_fd(ictx, &cmd_fds[i]);
}
}
/* 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)
{
ictx->got_new_events = true;
if (!cmd)
return 0;
queue_add(&ictx->control_cmd_queue, cmd, true);
return 1;
}
/**
* \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)
{
if (async_quit_request)
return mp_input_parse_cmd("quit 1");
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 (!peek_only && queue)
queue_pop(queue);
return ret;
}
void mp_cmd_free(mp_cmd_t *cmd)
{
talloc_free(cmd);
}
mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
{
mp_cmd_t *ret;
int i;
ret = talloc_memdup(NULL, cmd, sizeof(mp_cmd_t));
ret->name = talloc_strdup(ret, cmd->name);
for (i = 0; i < MP_CMD_MAX_ARGS && cmd->args[i].type != -1; i++) {
if (cmd->args[i].type == MP_CMD_ARG_STRING && cmd->args[i].v.s != NULL)
ret->args[i].v.s = talloc_strdup(ret, cmd->args[i].v.s);
}
return ret;
}
int mp_input_get_key_from_name(const char *name)
{
int modifiers = 0;
const char *p;
while ((p = strchr(name, '+'))) {
for (struct key_name *m = modifier_names; m->name; m++)
if (!bstrcasecmp(BSTR(m->name), (struct bstr){(char *)name, p - name})) {
modifiers |= m->key;
goto found;
}
if (!strcmp(name, "+"))
return '+' + modifiers;
return -1;
found:
name = p + 1;
}
int len = strlen(name);
if (len == 1) // Direct key code
return (unsigned char)name[0] + modifiers;
else if (len > 2 && strncasecmp("0x", name, 2) == 0)
return strtol(name, NULL, 16) + modifiers;
for (int i = 0; key_names[i].name != NULL; i++) {
if (strcasecmp(key_names[i].name, name) == 0)
return key_names[i].key + modifiers;
}
return -1;
}
static int get_input_from_name(char *name, int *keys)
{
char *end, *ptr;
int n = 0;
ptr = name;
n = 0;
for (end = strchr(ptr, '-'); ptr != NULL; end = strchr(ptr, '-')) {
if (end && end[1] != '\0') {
if (end[1] == '-')
end = &end[1];
end[0] = '\0';
}
keys[n] = mp_input_get_key_from_name(ptr);
if (keys[n] < 0)
return 0;
n++;
if (end && end[1] != '\0' && n < MP_MAX_KEY_DOWN)
ptr = &end[1];
else
break;
}
keys[n] = 0;
return 1;
}
#define BS_MAX 256
#define SPACE_CHAR " \n\r\t"
static void bind_keys(struct input_ctx *ictx,
const int keys[MP_MAX_KEY_DOWN + 1], char *cmd)
{
int i = 0, j;
struct cmd_bind *bind = NULL;
struct cmd_bind_section *bind_section = NULL;
char *section = NULL, *p;
if (*cmd == '{' && (p = strchr(cmd, '}'))) {
*p = 0;
section = ++cmd;
cmd = ++p;
// Jump beginning space
cmd += strspn(cmd, SPACE_CHAR);
}
bind_section = get_bind_section(ictx, section);
if (bind_section->cmd_binds) {
for (i = 0; bind_section->cmd_binds[i].cmd != NULL; i++) {
for (j = 0; bind_section->cmd_binds[i].input[j] == keys[j] && keys[j] != 0; j++)
/* NOTHING */;
if (keys[j] == 0 && bind_section->cmd_binds[i].input[j] == 0 ) {
bind = &bind_section->cmd_binds[i];
break;
}
}
}
if (!bind) {
bind_section->cmd_binds = talloc_realloc(bind_section,
bind_section->cmd_binds,
struct cmd_bind, i + 2);
memset(&bind_section->cmd_binds[i], 0, 2 * sizeof(struct cmd_bind));
bind = &bind_section->cmd_binds[i];
}
talloc_free(bind->cmd);
bind->cmd = talloc_strdup(bind_section->cmd_binds, cmd);
memcpy(bind->input, keys, (MP_MAX_KEY_DOWN + 1) * sizeof(int));
}
static int parse_config(struct input_ctx *ictx, char *file)
{
int fd;
int bs = 0, r, eof = 0, comments = 0;
char *iter, *end;
char buffer[BS_MAX];
int n_binds = 0, keys[MP_MAX_KEY_DOWN + 1] = { 0 };
fd = open(file, O_RDONLY);
if (fd < 0) {
mp_msg(MSGT_INPUT, MSGL_V, "Can't open input config file %s: %s\n",
file, strerror(errno));
return 0;
}
mp_msg(MSGT_INPUT, MSGL_V, "Parsing input config file %s\n", file);
while (1) {
if (!eof && bs < BS_MAX - 1) {
if (bs > 0)
bs--;
r = read(fd, buffer + bs, BS_MAX - 1 - bs);
if (r < 0) {
if (errno == EINTR)
continue;
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error while reading "
"input config file %s: %s\n", file, strerror(errno));
close(fd);
return 0;
} else if (r == 0) {
eof = 1;
} else {
bs += r + 1;
buffer[bs - 1] = '\0';
}
}
// Empty buffer : return
if (bs <= 1) {
mp_msg(MSGT_INPUT, MSGL_V, "Input config file %s parsed: "
"%d binds\n", file, n_binds);
close(fd);
return 1;
}
iter = buffer;
if (comments) {
iter += strcspn(iter, "\n");
if (*iter == 0) { // Buffer was full of comment
bs = 0;
continue;
}
iter++;
r = strlen(iter);
memmove(buffer, iter, r + 1);
bs = r + 1;
comments = 0;
continue;
}
// Find the wanted key
if (keys[0] == 0) {
// Jump beginning space
iter += strspn(iter, SPACE_CHAR);
if (*iter == 0) { // Buffer was full of space char
bs = 0;
continue;
}
if (iter[0] == '#') { // Comments
comments = 1;
continue;
}
// Find the end of the key code name
end = iter + strcspn(iter, SPACE_CHAR);
if (*end == 0) { // Key name doesn't fit in the buffer
if (buffer == iter) {
if (eof && (buffer - iter) == bs)
mp_tmsg(MSGT_INPUT, MSGL_ERR,
"Unfinished binding %s\n", iter);
else
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Buffer is too small "
"for this key name: %s\n", iter);
return 0;
}
memmove(buffer, iter, end - iter);
bs = end - iter;
continue;
}
{
char name[end - iter + 1];
strncpy(name, iter, end - iter);
name[end - iter] = '\0';
if (!get_input_from_name(name, keys)) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Unknown key '%s'\n", name);
close(fd);
return 0;
}
}
if (bs > (end - buffer))
memmove(buffer, end, bs - (end - buffer));
bs -= end - buffer;
continue;
} else { // Get the command
while (iter[0] == ' ' || iter[0] == '\t')
iter++;
// Found new line
if (iter[0] == '\n' || iter[0] == '\r') {
char *key_buf = get_key_combo_name(keys, MP_MAX_KEY_DOWN);
mp_tmsg(MSGT_INPUT, MSGL_ERR,
"No command found for key '%s'.\n", key_buf);
talloc_free(key_buf);
keys[0] = 0;
if (iter > buffer) {
memmove(buffer, iter, bs - (iter - buffer));
bs -= (iter - buffer);
}
continue;
}
end = iter + strcspn(iter, "\n\r");
if (*end == 0 && !(eof && ((end + 1) - buffer) == bs)) {
if (iter == buffer) {
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Buffer is too small "
"for command %s\n", buffer);
close(fd);
return 0;
}
memmove(buffer, iter, end - iter);
bs = end - iter;
continue;
}
{
char cmd[end - iter + 1];
strncpy(cmd, iter, end - iter);
cmd[end - iter] = '\0';
//printf("Set bind %d => %s\n",keys[0],cmd);
bind_keys(ictx, keys, cmd);
n_binds++;
}
keys[0] = 0;
end++;
if (bs > (end - buffer))
memmove(buffer, end, bs - (end - buffer));
bs -= (end - buffer);
continue;
}
}
mp_tmsg(MSGT_INPUT, MSGL_ERR, "What are we doing here?\n");
close(fd);
mp_input_set_section(ictx, NULL);
return 0;
}
void mp_input_set_section(struct input_ctx *ictx, char *name)
{
struct cmd_bind_section *bind_section = NULL;
ictx->cmd_binds = NULL;
ictx->cmd_binds_default = NULL;
talloc_free(ictx->section);
if (name)
ictx->section = talloc_strdup(ictx, name);
else
ictx->section = talloc_strdup(ictx, "default");
if ((bind_section = get_bind_section(ictx, ictx->section)))
ictx->cmd_binds = bind_section->cmd_binds;
if (strcmp(ictx->section, "default") == 0)
return;
if ((bind_section = get_bind_section(ictx, NULL)))
ictx->cmd_binds_default = bind_section->cmd_binds;
}
char *mp_input_get_section(struct input_ctx *ictx)
{
return ictx->section;
}
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,
.default_bindings = input_conf->default_bindings,
};
char *file;
char *config_file = input_conf->config_file;
file = config_file[0] != '/' ? get_path(config_file) : config_file;
if (!file)
return ictx;
if (!parse_config(ictx, file)) {
// free file if it was allocated by get_path(),
// before it gets overwritten
if (file != config_file)
free(file);
// Try global conf dir
file = MPLAYER_CONFDIR "/input.conf";
if (!parse_config(ictx, file))
mp_msg(MSGT_INPUT, MSGL_V, "Falling back on default (hardcoded) "
"input config\n");
} else {
// free file if it was allocated by get_path()
if (file != config_file)
free(file);
}
#ifdef CONFIG_JOYSTICK
if (input_conf->use_joystick) {
int fd = mp_input_joystick_init(input_conf->js_dev);
if (fd < 0)
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't init input joystick\n");
else
mp_input_add_key_fd(ictx, fd, 1, mp_input_joystick_read,
close, NULL);
}
#endif
#ifdef CONFIG_LIRC
if (input_conf->use_lirc) {
int fd = mp_input_lirc_init();
if (fd > 0)
mp_input_add_cmd_fd(ictx, fd, 0, mp_input_lirc_read,
mp_input_lirc_close);
}
#endif
#ifdef CONFIG_LIRCC
if (input_conf->use_lircc) {
int fd = lircc_init("mplayer", NULL);
if (fd >= 0)
mp_input_add_cmd_fd(ictx, fd, 1, NULL, lircc_cleanup);
}
#endif
#ifdef CONFIG_APPLE_REMOTE
if (input_conf->use_ar) {
if (mp_input_ar_init() < 0)
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't init Apple Remote.\n");
else
mp_input_add_key_fd(ictx, -1, 0, mp_input_ar_read,
mp_input_ar_close, NULL);
}
#endif
#ifdef CONFIG_APPLE_IR
if (input_conf->use_ar) {
int fd = mp_input_appleir_init(input_conf->ar_dev);
if (fd < 0)
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't init Apple Remote.\n");
else
mp_input_add_key_fd(ictx, fd, 1, mp_input_appleir_read,
close, NULL);
}
#endif
if (input_conf->in_file) {
struct stat st;
int mode = O_RDONLY | O_NONBLOCK;
// Use RDWR for FIFOs to ensure they stay open over multiple accesses.
// Note that on Windows stat may fail for named pipes,
// but due to how the API works, using RDONLY should be ok.
if (stat(input_conf->in_file, &st) == 0 && S_ISFIFO(st.st_mode))
mode = O_RDWR | O_NONBLOCK;
int in_file_fd = open(input_conf->in_file, mode);
if (in_file_fd >= 0)
mp_input_add_cmd_fd(ictx, in_file_fd, 1, NULL, close);
else
mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't open %s: %s\n",
input_conf->in_file, strerror(errno));
}
return ictx;
}
void mp_input_uninit(struct input_ctx *ictx)
{
if (!ictx)
return;
unsigned int i;
for (i = 0; i < ictx->num_key_fd; i++) {
if (ictx->key_fds[i].close_func)
ictx->key_fds[i].close_func(ictx->key_fds[i].fd);
}
for (i = 0; i < ictx->num_cmd_fd; i++) {
if (ictx->cmd_fds[i].close_func)
ictx->cmd_fds[i].close_func(ictx->cmd_fds[i].fd);
}
talloc_free(ictx);
}
void mp_input_register_options(m_config_t *cfg)
{
m_config_register_options(cfg, mp_input_opts);
}
static int print_key_list(m_option_t *cfg)
{
int i;
printf("\n");
for (i = 0; key_names[i].name != NULL; i++)
printf("%s\n", key_names[i].name);
exit(0);
}
static int print_cmd_list(m_option_t *cfg)
{
const mp_cmd_t *cmd;
int i, j;
const char *type;
for (i = 0; (cmd = &mp_cmds[i])->name != NULL; i++) {
printf("%-20.20s", cmd->name);
for (j = 0; j < MP_CMD_MAX_ARGS && cmd->args[j].type != -1; j++) {
switch (cmd->args[j].type) {
case MP_CMD_ARG_INT:
type = "Integer";
break;
case MP_CMD_ARG_FLOAT:
type = "Float";
break;
case MP_CMD_ARG_STRING:
type = "String";
break;
default:
type = "??";
}
if (j + 1 > cmd->nargs)
printf(" [%s]", type);
else
printf(" %s", type);
}
printf("\n");
}
exit(0);
}
/**
* \param time time to wait for an interruption in milliseconds
*/
int mp_input_check_interrupt(struct input_ctx *ictx, int time)
{
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);
}
}