2019-10-22 14:41:19 +00:00
|
|
|
/*
|
|
|
|
* 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 <stdbool.h>
|
2023-10-21 02:55:41 +00:00
|
|
|
|
|
|
|
#include <SDL.h>
|
|
|
|
|
2019-10-22 14:41:19 +00:00
|
|
|
#include "common/common.h"
|
|
|
|
#include "common/msg.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "input/keycodes.h"
|
2023-10-21 02:55:41 +00:00
|
|
|
#include "osdep/threads.h"
|
2019-10-22 14:41:19 +00:00
|
|
|
|
|
|
|
struct gamepad_priv {
|
|
|
|
SDL_GameController *controller;
|
|
|
|
};
|
|
|
|
|
|
|
|
static Uint32 gamepad_cancel_wakeup;
|
|
|
|
|
2019-10-25 13:33:23 +00:00
|
|
|
static void initialize_events(void)
|
2019-10-22 14:41:19 +00:00
|
|
|
{
|
|
|
|
gamepad_cancel_wakeup = SDL_RegisterEvents(1);
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
static mp_once events_initialized = MP_STATIC_ONCE_INITIALIZER;
|
2019-10-22 14:41:19 +00:00
|
|
|
|
|
|
|
#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)
|
|
|
|
{
|
2023-09-20 19:01:53 +00:00
|
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
|
|
|
|
|
2019-10-25 20:17:54 +00:00
|
|
|
if (SDL_WasInit(SDL_INIT_EVENTS)) {
|
|
|
|
MP_ERR(src, "Another component is using SDL already.\n");
|
|
|
|
mp_input_src_init_done(src);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-22 14:41:19 +00:00
|
|
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) {
|
|
|
|
MP_ERR(src, "SDL_Init failed\n");
|
|
|
|
mp_input_src_init_done(src);
|
|
|
|
return;
|
2019-10-25 20:17:54 +00:00
|
|
|
}
|
2019-10-22 14:41:19 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_exec_once(&events_initialized, initialize_events);
|
2019-10-22 14:41:19 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|