/*
* 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 .
*/
#include
#include
#include
#include "common/common.h"
#include "common/msg.h"
#include "input.h"
#include "input/keycodes.h"
struct gamepad_priv {
SDL_GameController *controller;
};
static Uint32 gamepad_cancel_wakeup;
static void initialize_events(void)
{
gamepad_cancel_wakeup = SDL_RegisterEvents(1);
}
static pthread_once_t events_initialized = PTHREAD_ONCE_INIT;
#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 analog_map[][5] = {
// 0 -> sdl enum
// 1 -> negative state
// 2 -> neutral-negative state
// 3 -> neutral-positive state
// 4 -> positive state
{ SDL_CONTROLLER_AXIS_LEFTX,
MP_KEY_GAMEPAD_LEFT_STICK_LEFT | MP_KEY_STATE_DOWN,
MP_KEY_GAMEPAD_LEFT_STICK_LEFT | MP_KEY_STATE_UP,
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_STATE_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_STATE_UP,
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_STATE_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,
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,
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_analog_mp_key(int sdl_key, int16_t value)
{
const int sdl_axis_max = 32767;
const int negative = 1;
const int negative_neutral = 2;
const int positive_neutral = 3;
const int positive = 4;
const float activation_threshold = sdl_axis_max * 0.33;
const float noise_threshold = sdl_axis_max * 0.06;
// sometimes SDL just keeps shitting out low values around 0 that mess
// with key repeating code
if (value < noise_threshold && value > -noise_threshold) {
return INVALID_KEY;
}
int state = value > 0 ? positive_neutral : negative_neutral;
if (value >= sdl_axis_max - activation_threshold) {
state = positive;
}
if (value <= activation_threshold - sdl_axis_max) {
state = negative;
}
for (int i = 0; i < MP_ARRAY_SIZE(analog_map); i++) {
if (analog_map[i][0] == sdl_key) {
return analog_map[i][state];
}
}
return INVALID_KEY;
}
static void request_cancel(struct mp_input_src *src)
{
MP_VERBOSE(src, "exiting...\n");
SDL_Event event = { .type = gamepad_cancel_wakeup };
SDL_PushEvent(&event);
}
static void uninit(struct mp_input_src *src)
{
MP_VERBOSE(src, "exited.\n");
}
#define GUID_LEN 33
static void add_gamepad(struct mp_input_src *src, int id)
{
struct gamepad_priv *p = src->priv;
if (p->controller) {
MP_WARN(src, "can't add more than one controller\n");
return;
}
if (SDL_IsGameController(id)) {
SDL_GameController *controller = SDL_GameControllerOpen(id);
if (controller) {
const char *name = SDL_GameControllerName(controller);
MP_INFO(src, "added controller: %s\n", name);
p->controller = controller;
return;
}
}
}
static void remove_gamepad(struct mp_input_src *src, int id)
{
struct gamepad_priv *p = src->priv;
SDL_GameController *controller = p->controller;
SDL_Joystick* j = SDL_GameControllerGetJoystick(controller);
SDL_JoystickID jid = SDL_JoystickInstanceID(j);
if (controller && jid == id) {
const char *name = SDL_GameControllerName(controller);
MP_INFO(src, "removed controller: %s\n", name);
SDL_GameControllerClose(controller);
p->controller = NULL;
}
}
static void read_gamepad_thread(struct mp_input_src *src, void *param)
{
if (SDL_WasInit(SDL_INIT_EVENTS)) {
MP_ERR(src, "Another component is using SDL already.\n");
mp_input_src_init_done(src);
return;
}
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) {
MP_ERR(src, "SDL_Init failed\n");
mp_input_src_init_done(src);
return;
}
pthread_once(&events_initialized, initialize_events);
if (gamepad_cancel_wakeup == (Uint32)-1) {
MP_ERR(src, "Can't register SDL custom events\n");
mp_input_src_init_done(src);
return;
}
struct gamepad_priv *p =src->priv = talloc_zero(src, struct gamepad_priv);
src->cancel = request_cancel;
src->uninit = uninit;
mp_input_src_init_done(src);
SDL_Event ev;
while (SDL_WaitEvent(&ev) != 0) {
if (ev.type == gamepad_cancel_wakeup) {
break;
}
switch (ev.type) {
case SDL_CONTROLLERDEVICEADDED: {
add_gamepad(src, ev.cdevice.which);
continue;
}
case SDL_CONTROLLERDEVICEREMOVED: {
remove_gamepad(src, ev.cdevice.which);
continue;
}
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_analog_mp_key(ev.caxis.axis, ev.caxis.value);
if (key != INVALID_KEY) {
mp_input_put_key(src->input_ctx, key);
}
continue;
}
}
}
if (p->controller) {
SDL_Joystick* j = SDL_GameControllerGetJoystick(p->controller);
SDL_JoystickID jid = SDL_JoystickInstanceID(j);
remove_gamepad(src, jid);
}
// 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_sdl_gamepad_add(struct input_ctx *ictx)
{
mp_input_add_thread_src(ictx, NULL, read_gamepad_thread);
}