summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefano Pigozzi <stefano.pigozzi@gmail.com>2018-08-07 08:43:14 +0200
committerStefano Pigozzi <stefano.pigozzi@gmail.com>2018-08-07 08:43:14 +0200
commitf278cd68d86d663c5c9e07cbbc1638acf9095f98 (patch)
tree121a42b0b38ea4cb6f3a6ac18d2337a0bf7c2053
parent5532a3da1ee058443d3162582b601f19b59377c2 (diff)
downloadmpv-controller.tar.bz2
mpv-controller.tar.xz
add gamepad support through SDL2controller
The code is very basic: - only handles gamepads, could be extended for generic joysticks in the future. - only has button mappings for controllers natively supported by SDL2. I heard more can be added through env vars, there's also ways to load mappings from text files, but I'd rather not go there yet. Common ones like Dualshock are supported natively. - analog buttons like TRIGGERs and AXIS' are mapped to discrete buttons using a threshold.
-rw-r--r--DOCS/man/options.rst3
-rw-r--r--input/gamepad.c237
-rw-r--r--input/input.c13
-rw-r--r--input/input.h2
-rw-r--r--input/keycodes.c26
-rw-r--r--input/keycodes.h28
-rw-r--r--wscript4
-rw-r--r--wscript_build.py1
8 files changed, 314 insertions, 0 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index e03f783b90..456b1ade96 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -3070,6 +3070,9 @@ Input
(OS X only)
Enable/disable Apple Remote support. Enabled by default (except for libmpv).
+``--input-gamepad=<yes|no>``
+ Enable/disable SDL2 Gamepad support. Enabled by default (except for libmpv).
+
``--input-cursor``, ``--no-input-cursor``
Permit mpv to receive pointer events reported by the video output
driver. Necessary to use the OSC, or to select the buttons in DVD menus.
diff --git a/input/gamepad.c b/input/gamepad.c
new file mode 100644
index 0000000000..795ff51dbd
--- /dev/null
+++ b/input/gamepad.c
@@ -0,0 +1,237 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <SDL.h>
+#include <stdbool.h>
+#include "common/common.h"
+#include "osdep/atomic.h"
+#include "common/msg.h"
+#include "input.h"
+#include "input/keycodes.h"
+
+struct priv {
+ atomic_bool cancel_requested;
+};
+
+#define INVALID_KEY -1
+
+static const int button_map[][2] = {
+ { SDL_CONTROLLER_BUTTON_A, MP_KEY_GAMEPAD_ACTION_DOWN },
+ { SDL_CONTROLLER_BUTTON_B, MP_KEY_GAMEPAD_ACTION_RIGHT },
+ { SDL_CONTROLLER_BUTTON_X, MP_KEY_GAMEPAD_ACTION_LEFT },
+ { SDL_CONTROLLER_BUTTON_Y, MP_KEY_GAMEPAD_ACTION_UP },
+ { SDL_CONTROLLER_BUTTON_BACK, MP_KEY_GAMEPAD_BACK },
+ { SDL_CONTROLLER_BUTTON_GUIDE, MP_KEY_GAMEPAD_MENU },
+ { SDL_CONTROLLER_BUTTON_START, MP_KEY_GAMEPAD_START },
+ { SDL_CONTROLLER_BUTTON_LEFTSTICK, MP_KEY_GAMEPAD_LEFT_STICK },
+ { SDL_CONTROLLER_BUTTON_RIGHTSTICK, MP_KEY_GAMEPAD_RIGHT_STICK },
+ { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, MP_KEY_GAMEPAD_LEFT_SHOULDER },
+ { SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, MP_KEY_GAMEPAD_RIGHT_SHOULDER },
+ { SDL_CONTROLLER_BUTTON_DPAD_UP, MP_KEY_GAMEPAD_DPAD_UP },
+ { SDL_CONTROLLER_BUTTON_DPAD_DOWN, MP_KEY_GAMEPAD_DPAD_DOWN },
+ { SDL_CONTROLLER_BUTTON_DPAD_LEFT, MP_KEY_GAMEPAD_DPAD_LEFT },
+ { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, MP_KEY_GAMEPAD_DPAD_RIGHT },
+};
+
+static const int axis_map[][4] = {
+ // 0 -> sdl enum
+ // 1 -> negative state
+ // 2 -> neutral state
+ // 3 -> positive state
+ { SDL_CONTROLLER_AXIS_LEFTX,
+ MP_KEY_GAMEPAD_LEFT_STICK_LEFT | MP_KEY_STATE_DOWN,
+ MP_KEY_GAMEPAD_LEFT_STICK_LEFT |
+ MP_KEY_GAMEPAD_LEFT_STICK_RIGHT |
+ MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_LEFT_STICK_RIGHT | MP_KEY_STATE_DOWN },
+
+ { SDL_CONTROLLER_AXIS_LEFTY,
+ MP_KEY_GAMEPAD_LEFT_STICK_UP | MP_KEY_STATE_DOWN,
+ MP_KEY_GAMEPAD_LEFT_STICK_UP |
+ MP_KEY_GAMEPAD_LEFT_STICK_DOWN |
+ MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_LEFT_STICK_DOWN | MP_KEY_STATE_DOWN },
+
+ { SDL_CONTROLLER_AXIS_RIGHTX,
+ MP_KEY_GAMEPAD_RIGHT_STICK_LEFT | MP_KEY_STATE_DOWN,
+ MP_KEY_GAMEPAD_RIGHT_STICK_LEFT |
+ MP_KEY_GAMEPAD_RIGHT_STICK_RIGHT |
+ MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_RIGHT_STICK_RIGHT | MP_KEY_STATE_DOWN },
+
+ { SDL_CONTROLLER_AXIS_RIGHTY,
+ MP_KEY_GAMEPAD_RIGHT_STICK_UP | MP_KEY_STATE_DOWN,
+ MP_KEY_GAMEPAD_RIGHT_STICK_UP |
+ MP_KEY_GAMEPAD_RIGHT_STICK_DOWN |
+ MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_RIGHT_STICK_DOWN | MP_KEY_STATE_DOWN },
+
+ { SDL_CONTROLLER_AXIS_TRIGGERLEFT,
+ INVALID_KEY,
+ MP_KEY_GAMEPAD_LEFT_TRIGGER | MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_LEFT_TRIGGER | MP_KEY_STATE_DOWN },
+
+ { SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
+ INVALID_KEY,
+ MP_KEY_GAMEPAD_RIGHT_TRIGGER | MP_KEY_STATE_UP,
+ MP_KEY_GAMEPAD_RIGHT_TRIGGER | MP_KEY_STATE_DOWN },
+};
+
+static int lookup_button_mp_key(int sdl_key) {
+ for (int i = 0; i < MP_ARRAY_SIZE(button_map); i++) {
+ if (button_map[i][0] == sdl_key) {
+ return button_map[i][1];
+ }
+ }
+ return INVALID_KEY;
+}
+
+static int lookup_axis_mp_key(int sdl_key, int16_t value) {
+ const int sdl_axis_max = 32767;
+ const int negative = 1;
+ const int neutral = 2;
+ const int positive = 3;
+
+ const float threshold = (sdl_axis_max * 0.01);
+
+ int state = neutral;
+
+ if (value >= sdl_axis_max - threshold) {
+ state = positive;
+ }
+
+ if (value <= threshold - sdl_axis_max) {
+ state = negative;
+ }
+
+ for (int i = 0; i < MP_ARRAY_SIZE(axis_map); i++) {
+ if (axis_map[i][0] == sdl_key) {
+ return axis_map[i][state];
+ }
+ }
+
+ return INVALID_KEY;
+}
+
+
+static void request_cancel(struct mp_input_src *src)
+{
+ struct priv *p = src->priv;
+
+ MP_VERBOSE(src, "exiting...\n");
+ atomic_store(&p->cancel_requested, true);
+}
+
+static void uninit(struct mp_input_src *src)
+{
+ MP_VERBOSE(src, "exited.\n");
+}
+
+#define GUID_LEN 33
+
+static void read_gamepad_thread(struct mp_input_src *src, void *param)
+{
+ if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) {
+ MP_ERR(src, "SDL_Init failed\n");
+ mp_input_src_init_done(src);
+ return;
+ };
+
+ SDL_GameController *controller;
+ struct priv *p = talloc_zero(src, struct priv);
+ char guid[GUID_LEN];
+
+ if (SDL_NumJoysticks() <= 0) {
+ MP_VERBOSE(src, "no joysticks found");
+ mp_input_src_init_done(src);
+ return;
+ }
+
+ MP_VERBOSE(src, "connected controllers: %i\n", SDL_NumJoysticks());
+
+ for (int i = 0; i < SDL_NumJoysticks(); ++i) {
+ if (SDL_IsGameController(i)) {
+ controller = SDL_GameControllerOpen(i);
+ SDL_JoystickID id = SDL_JoystickInstanceID(
+ SDL_GameControllerGetJoystick(controller));
+
+ SDL_JoystickGetGUIDString(
+ SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(controller)),
+ guid, GUID_LEN);
+
+ if (controller) {
+ MP_VERBOSE(
+ src, "detected controller #%i: %s, guid: %s\n",
+ id, SDL_GameControllerName(controller), guid);
+
+ // stop at first controller apparently SDL can't open more
+ // than one controller anyway?
+ break;
+ }
+ }
+ }
+
+ atomic_store(&p->cancel_requested, false);
+ src->priv = p;
+ src->cancel = request_cancel;
+ src->uninit = uninit;
+ mp_input_src_init_done(src);
+
+ SDL_Event ev;
+
+ while (!atomic_load(&p->cancel_requested)) {
+ while (SDL_PollEvent(&ev) != 0) {
+ switch (ev.type) {
+ case SDL_CONTROLLERBUTTONDOWN: {
+ const int key = lookup_button_mp_key(ev.cbutton.button);
+ if (key != INVALID_KEY) {
+ mp_input_put_key(src->input_ctx, key | MP_KEY_STATE_DOWN);
+ }
+ continue;
+ }
+ case SDL_CONTROLLERBUTTONUP: {
+ const int key = lookup_button_mp_key(ev.cbutton.button);
+ if (key != INVALID_KEY) {
+ mp_input_put_key(src->input_ctx, key | MP_KEY_STATE_UP);
+ }
+ continue;
+ }
+ case SDL_CONTROLLERAXISMOTION: {
+ const int key =
+ lookup_axis_mp_key(ev.caxis.axis, ev.caxis.value);
+ if (key != INVALID_KEY) {
+ mp_input_put_key(src->input_ctx, key);
+ }
+ continue;
+ }
+ }
+ }
+ }
+
+ if (controller != NULL) {
+ SDL_GameControllerClose(controller);
+ }
+
+ // must be called on the same thread of SDL_InitSubSystem, so uninit
+ // callback can't be used for this
+ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
+}
+
+void mp_input_gamepad_add(struct input_ctx *ictx)
+{
+ mp_input_add_thread_src(ictx, NULL, read_gamepad_thread);
+}
diff --git a/input/input.c b/input/input.c
index 20c39dd4ac..bb07640b4c 100644
--- a/input/input.c
+++ b/input/input.c
@@ -176,6 +176,7 @@ struct input_opts {
int ar_rate;
int use_alt_gr;
int use_appleremote;
+ int use_gamepad;
int use_media_keys;
int default_bindings;
int enable_mouse_movements;
@@ -202,6 +203,9 @@ const struct m_sub_options input_config = {
#if HAVE_COCOA
OPT_FLAG("input-appleremote", use_appleremote, 0),
#endif
+#if HAVE_GAMEPAD
+ OPT_FLAG("input-gamepad", use_gamepad, 0),
+#endif
OPT_FLAG("window-dragging", allow_win_drag, 0),
OPT_REPLACED("input-x11-keyboard", "input-vo-keyboard"),
{0}
@@ -218,6 +222,9 @@ const struct m_sub_options input_config = {
#if HAVE_COCOA
.use_appleremote = 1,
#endif
+#if HAVE_GAMEPAD
+ .use_gamepad = 1,
+#endif
.default_bindings = 1,
.vo_key_input = 1,
.allow_win_drag = 1,
@@ -1395,6 +1402,12 @@ void mp_input_load_config(struct input_ctx *ictx)
mp_input_pipe_add(ictx, ictx->global->opts->input_file);
#endif
+#if HAVE_GAMEPAD
+ if (ictx->opts->use_gamepad) {
+ mp_input_gamepad_add(ictx);
+ }
+#endif
+
input_unlock(ictx);
}
diff --git a/input/input.h b/input/input.h
index 1641f9fad6..338ae25289 100644
--- a/input/input.h
+++ b/input/input.h
@@ -211,6 +211,8 @@ void mp_input_set_repeat_info(struct input_ctx *ictx, int rate, int delay);
void mp_input_pipe_add(struct input_ctx *ictx, const char *filename);
+void mp_input_gamepad_add(struct input_ctx *ictx);
+
struct mp_ipc_ctx;
struct mp_client_api;
diff --git a/input/keycodes.c b/input/keycodes.c
index 81371e20bf..a03f07e59a 100644
--- a/input/keycodes.c
+++ b/input/keycodes.c
@@ -117,6 +117,32 @@ static const struct key_name key_names[] = {
{ MP_AR_VDOWN, "AR_VDOWN" },
{ MP_AR_VDOWN_HOLD, "AR_VDOWN_HOLD" },
+ { MP_KEY_GAMEPAD_ACTION_DOWN, "GAMEPAD_ACTION_DOWN" },
+ { MP_KEY_GAMEPAD_ACTION_RIGHT, "GAMEPAD_ACTION_RIGHT" },
+ { MP_KEY_GAMEPAD_ACTION_LEFT, "GAMEPAD_ACTION_LEFT" },
+ { MP_KEY_GAMEPAD_ACTION_UP, "GAMEPAD_ACTION_UP" },
+ { MP_KEY_GAMEPAD_BACK, "GAMEPAD_BACK" },
+ { MP_KEY_GAMEPAD_MENU, "GAMEPAD_MENU" },
+ { MP_KEY_GAMEPAD_START, "GAMEPAD_START" },
+ { MP_KEY_GAMEPAD_LEFT_SHOULDER, "GAMEPAD_LEFT_SHOULDER" },
+ { MP_KEY_GAMEPAD_RIGHT_SHOULDER, "GAMEPAD_RIGHT_SHOULDER" },
+ { MP_KEY_GAMEPAD_LEFT_TRIGGER, "GAMEPAD_LEFT_TRIGGER" },
+ { MP_KEY_GAMEPAD_RIGHT_TRIGGER, "GAMEPAD_RIGHT_TRIGGER" },
+ { MP_KEY_GAMEPAD_LEFT_STICK, "GAMEPAD_LEFT_STICK" },
+ { MP_KEY_GAMEPAD_RIGHT_STICK, "GAMEPAD_RIGHT_STICK" },
+ { MP_KEY_GAMEPAD_DPAD_UP, "GAMEPAD_DPAD_UP" },
+ { MP_KEY_GAMEPAD_DPAD_DOWN, "GAMEPAD_DPAD_DOWN" },
+ { MP_KEY_GAMEPAD_DPAD_LEFT, "GAMEPAD_DPAD_LEFT" },
+ { MP_KEY_GAMEPAD_DPAD_RIGHT, "GAMEPAD_DPAD_RIGHT" },
+ { MP_KEY_GAMEPAD_LEFT_STICK_UP, "GAMEPAD_LEFT_STICK_UP" },
+ { MP_KEY_GAMEPAD_LEFT_STICK_DOWN, "GAMEPAD_LEFT_STICK_DOWN" },
+ { MP_KEY_GAMEPAD_LEFT_STICK_LEFT, "GAMEPAD_LEFT_STICK_LEFT" },
+ { MP_KEY_GAMEPAD_LEFT_STICK_RIGHT, "GAMEPAD_LEFT_STICK_RIGHT" },
+ { MP_KEY_GAMEPAD_RIGHT_STICK_UP, "GAMEPAD_RIGHT_STICK_UP" },
+ { MP_KEY_GAMEPAD_RIGHT_STICK_DOWN, "GAMEPAD_RIGHT_STICK_DOWN" },
+ { MP_KEY_GAMEPAD_RIGHT_STICK_LEFT, "GAMEPAD_RIGHT_STICK_LEFT" },
+ { MP_KEY_GAMEPAD_RIGHT_STICK_RIGHT, "GAMEPAD_RIGHT_STICK_RIGHT" },
+
{ MP_KEY_POWER, "POWER" },
{ MP_KEY_MENU, "MENU" },
{ MP_KEY_PLAY, "PLAY" },
diff --git a/input/keycodes.h b/input/keycodes.h
index 604fee53dc..12b45e528a 100644
--- a/input/keycodes.h
+++ b/input/keycodes.h
@@ -154,6 +154,34 @@
#define MP_AR_VDOWN (MP_AR_BASE + 12)
#define MP_AR_VDOWN_HOLD (MP_AR_BASE + 13)
+/* game controller keys */
+#define MP_KEY_GAMEPAD (MP_KEY_BASE+0xF0)
+#define MP_KEY_GAMEPAD_ACTION_DOWN (MP_KEY_GAMEPAD+0)
+#define MP_KEY_GAMEPAD_ACTION_RIGHT (MP_KEY_GAMEPAD+1)
+#define MP_KEY_GAMEPAD_ACTION_LEFT (MP_KEY_GAMEPAD+2)
+#define MP_KEY_GAMEPAD_ACTION_UP (MP_KEY_GAMEPAD+3)
+#define MP_KEY_GAMEPAD_BACK (MP_KEY_GAMEPAD+4)
+#define MP_KEY_GAMEPAD_MENU (MP_KEY_GAMEPAD+5)
+#define MP_KEY_GAMEPAD_START (MP_KEY_GAMEPAD+6)
+#define MP_KEY_GAMEPAD_LEFT_SHOULDER (MP_KEY_GAMEPAD+7)
+#define MP_KEY_GAMEPAD_RIGHT_SHOULDER (MP_KEY_GAMEPAD+8)
+#define MP_KEY_GAMEPAD_LEFT_TRIGGER (MP_KEY_GAMEPAD+9)
+#define MP_KEY_GAMEPAD_RIGHT_TRIGGER (MP_KEY_GAMEPAD+10)
+#define MP_KEY_GAMEPAD_LEFT_STICK (MP_KEY_GAMEPAD+11)
+#define MP_KEY_GAMEPAD_RIGHT_STICK (MP_KEY_GAMEPAD+12)
+#define MP_KEY_GAMEPAD_DPAD_UP (MP_KEY_GAMEPAD+13)
+#define MP_KEY_GAMEPAD_DPAD_DOWN (MP_KEY_GAMEPAD+14)
+#define MP_KEY_GAMEPAD_DPAD_LEFT (MP_KEY_GAMEPAD+15)
+#define MP_KEY_GAMEPAD_DPAD_RIGHT (MP_KEY_GAMEPAD+16)
+#define MP_KEY_GAMEPAD_LEFT_STICK_UP (MP_KEY_GAMEPAD+17)
+#define MP_KEY_GAMEPAD_LEFT_STICK_DOWN (MP_KEY_GAMEPAD+18)
+#define MP_KEY_GAMEPAD_LEFT_STICK_LEFT (MP_KEY_GAMEPAD+19)
+#define MP_KEY_GAMEPAD_LEFT_STICK_RIGHT (MP_KEY_GAMEPAD+20)
+#define MP_KEY_GAMEPAD_RIGHT_STICK_UP (MP_KEY_GAMEPAD+21)
+#define MP_KEY_GAMEPAD_RIGHT_STICK_DOWN (MP_KEY_GAMEPAD+22)
+#define MP_KEY_GAMEPAD_RIGHT_STICK_LEFT (MP_KEY_GAMEPAD+23)
+#define MP_KEY_GAMEPAD_RIGHT_STICK_RIGHT (MP_KEY_GAMEPAD+24)
+
// Reserved area. Can be used for keys that have no explicit names assigned,
// but should be mappable by the user anyway.
#define MP_KEY_UNKNOWN_RESERVED_START (MP_KEY_BASE+0x10000)
diff --git a/wscript b/wscript
index e0dbeab322..70137db138 100644
--- a/wscript
+++ b/wscript
@@ -406,6 +406,10 @@ iconv support use --disable-iconv.",
'name': '--libarchive',
'desc': 'libarchive wrapper for reading zip files and more',
'func': check_pkg_config('libarchive >= 3.0.0'),
+ }, {
+ 'name': '--gamepad',
+ 'desc': 'gamepad (with SDL2)',
+ 'func': check_pkg_config('sdl2'),
}
]
diff --git a/wscript_build.py b/wscript_build.py
index 4d974fd9c3..3870200b4b 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -306,6 +306,7 @@ def build(ctx):
( ipc_c ),
( "input/keycodes.c" ),
( "input/pipe-win32.c", "win32-pipes" ),
+ ( "input/gamepad.c", "gamepad" ),
## Misc
( "misc/bstr.c" ),