//
// Copyright(C) 2005-2014 Simon Howard
//
// This program 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.
//
// This program 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.
//
// DESCRIPTION:
// SDL Joystick code.
//
#include "SDL.h"
#include "SDL_joystick.h"
#include "SDL_gamecontroller.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "doomtype.h"
#include "d_event.h"
#include "i_joystick.h"
#include "i_system.h"
#include "m_config.h"
#include "m_fixed.h"
#include "m_misc.h"
static SDL_GameController *gamepad = NULL;
static SDL_Joystick *joystick = NULL;
// Configuration variables:
// Standard default.cfg Joystick enable/disable
static int usejoystick = 0;
// Use SDL_gamecontroller interface for the selected device
static int use_gamepad = 0;
// SDL_GameControllerType of gamepad
static int gamepad_type = 0;
// SDL GUID and index of the joystick to use.
static char *joystick_guid = "";
static int joystick_index = -1;
// Which joystick axis to use for horizontal movement, and whether to
// invert the direction:
static int joystick_x_axis = 0;
static int joystick_x_invert = 0;
// Which joystick axis to use for vertical movement, and whether to
// invert the direction:
static int joystick_y_axis = 1;
static int joystick_y_invert = 0;
// Which joystick axis to use for strafing?
static int joystick_strafe_axis = -1;
static int joystick_strafe_invert = 0;
// Which joystick axis to use for looking?
static int joystick_look_axis = -1;
static int joystick_look_invert = 0;
// Configurable dead zone for each axis, specified as a percentage of the axis
// max value.
static int joystick_x_dead_zone = 33;
static int joystick_y_dead_zone = 33;
static int joystick_strafe_dead_zone = 33;
static int joystick_look_dead_zone = 33;
int use_analog = 0;
int joystick_turn_sensitivity = 10;
int joystick_move_sensitivity = 10;
int joystick_look_sensitivity = 10;
// Virtual to physical button joystick button mapping. By default this
// is a straight mapping.
static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
};
void I_ShutdownGamepad(void)
{
if (gamepad != NULL)
{
SDL_GameControllerClose(gamepad);
gamepad = NULL;
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
}
}
static int FindFirstGamepad(void)
{
int i;
int gamepadindex = -1;
for (i = 0; i < SDL_NumJoysticks(); ++i)
{
if (SDL_IsGameController(i))
{
gamepadindex = i;
break;
}
}
return gamepadindex;
}
static int FindSpecificGamepad(SDL_JoystickGUID guid)
{
SDL_JoystickGUID dev_guid;
int i;
int gamepadindex = -1;
for (i = 0; i < SDL_NumJoysticks(); ++i)
{
dev_guid = SDL_JoystickGetDeviceGUID(i);
if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
{
gamepadindex = i;
break;
}
}
return gamepadindex;
}
static int DeviceIndexGamepad(void)
{
SDL_JoystickGUID guid, dev_guid;
int index = -1;
if (strcmp(joystick_guid, ""))
{
guid = SDL_JoystickGetGUIDFromString(joystick_guid);
// First, look for the gamepad at the previously-used index.
if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks())
{
dev_guid = SDL_JoystickGetDeviceGUID(joystick_index);
if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
{
return joystick_index;
}
}
// Maybe the index has moved?
index = FindSpecificGamepad(guid);
}
// If the previous gamepad isn't present, see if a different one is
// available.
if (index < 0)
{
index = FindFirstGamepad();
}
return index;
}
void I_InitGamepad(void)
{
SDL_JoystickGUID guid;
int index;
if (!use_gamepad)
{
return;
}
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
{
return;
}
index = DeviceIndexGamepad();
if (index < 0)
{
printf("I_InitGamepad: No gamepad found.\n");
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
return;
}
gamepad = SDL_GameControllerOpen(index);
if (gamepad == NULL)
{
printf("I_InitGamepad: Failed to open gamepad: %s\n", SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
return;
}
joystick_index = index;
gamepad_type = SDL_GameControllerTypeForIndex(index);
if (strcmp(joystick_guid, ""))
{
joystick_guid = malloc(GUID_STRING_BUF_SIZE);
}
guid = SDL_JoystickGetDeviceGUID(joystick_index);
SDL_JoystickGetGUIDString(guid, joystick_guid, GUID_STRING_BUF_SIZE);
// GameController events do not fire if Joystick events are disabled.
SDL_JoystickEventState(SDL_ENABLE);
SDL_GameControllerEventState(SDL_ENABLE);
printf("I_InitGamepad: %s\n", SDL_GameControllerName(gamepad));
I_AtExit(I_ShutdownGamepad, true);
}
static int GetTriggerStateGamepad(SDL_GameControllerAxis trigger)
{
return (SDL_GameControllerGetAxis(gamepad, trigger) > TRIGGER_THRESHOLD);
}
// Get the state of the given virtual button.
static int ReadButtonStateGamepad(int vbutton)
{
int physbutton, state;
// Map from virtual button to physical (SDL) button.
if (vbutton < NUM_VIRTUAL_BUTTONS)
{
physbutton = joystick_physical_buttons[vbutton];
}
else
{
physbutton = vbutton;
}
switch (physbutton)
{
case GAMEPAD_BUTTON_TRIGGERLEFT:
state = GetTriggerStateGamepad(SDL_CONTROLLER_AXIS_TRIGGERLEFT);
break;
case GAMEPAD_BUTTON_TRIGGERRIGHT:
state = GetTriggerStateGamepad(SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
break;
default:
state = SDL_GameControllerGetButton(gamepad, physbutton);
break;
}
return state;
}
// Get a bitmask of all currently-pressed buttons
static int GetButtonsStateGamepad(void)
{
int i;
int result = 0;
for (i = 0; i < MAX_VIRTUAL_BUTTONS; ++i)
{
if (ReadButtonStateGamepad(i))
{
result |= 1u << i;
}
}
return result;
}
// Read the state of an axis, inverting if necessary.
static int GetAxisStateGamepad(const int *axis_values, int axis, int invert,
int dead_zone)
{
int result;
// Axis -1 means disabled.
if (axis < 0)
{
return 0;
}
// Dead zone is expressed as percentage of axis max value
dead_zone = 32768 * dead_zone / 100;
result = axis_values[axis];
if (result < dead_zone && result > -dead_zone)
{
return 0;
}
if (invert)
{
result = -result;
}
result *= FRACUNIT / 32768; // Want FXP number between -1 and 1
return result;
}
#define DIRECTION_DEADZONE (50 * 32768 / 100)
static int GetDirectionalInputGamepad(const int *axis_values)
{
int value;
int dpad = JOY_DIR_NONE;
int leftstick = JOY_DIR_NONE;
int rightstick = JOY_DIR_NONE;
// Dpad.
if (SDL_GameControllerGetButton(gamepad, SDL_CONTROLLER_BUTTON_DPAD_UP))
{
dpad |= JOY_DIR_UP;
}
if (SDL_GameControllerGetButton(gamepad, SDL_CONTROLLER_BUTTON_DPAD_DOWN))
{
dpad |= JOY_DIR_DOWN;
}
if (SDL_GameControllerGetButton(gamepad, SDL_CONTROLLER_BUTTON_DPAD_LEFT))
{
dpad |= JOY_DIR_LEFT;
}
if (SDL_GameControllerGetButton(gamepad, SDL_CONTROLLER_BUTTON_DPAD_RIGHT))
{
dpad |= JOY_DIR_RIGHT;
}
// Left stick.
value = axis_values[SDL_CONTROLLER_AXIS_LEFTY];
if (value > DIRECTION_DEADZONE || value < -DIRECTION_DEADZONE)
{
if (value > 0)
{
leftstick |= JOY_DIR_DOWN;
}
else
{
leftstick |= JOY_DIR_UP;
}
}
value = axis_values[SDL_CONTROLLER_AXIS_LEFTX];
if (value > DIRECTION_DEADZONE || value < -DIRECTION_DEADZONE)
{
if (value > 0)
{
leftstick |= JOY_DIR_RIGHT;
}
else
{
leftstick |= JOY_DIR_LEFT;
}
}
// Right stick.
value = axis_values[SDL_CONTROLLER_AXIS_RIGHTX];
if (value > DIRECTION_DEADZONE || value < -DIRECTION_DEADZONE)
{
if (value > 0)
{
rightstick |= JOY_DIR_RIGHT;
}
else
{
rightstick |= JOY_DIR_LEFT;
}
}
value = axis_values[SDL_CONTROLLER_AXIS_RIGHTY];
if (value > DIRECTION_DEADZONE || value < -DIRECTION_DEADZONE)
{
if (value > 0)
{
rightstick |= JOY_DIR_DOWN;
}
else
{
rightstick |= JOY_DIR_UP;
}
}
return ((dpad << DPAD_SHIFT) | (leftstick << LSTICK_SHIFT) |
(rightstick << RSTICK_SHIFT));
}
void I_UpdateGamepad(void)
{
if (gamepad != NULL)
{
event_t ev;
int axis_values[SDL_CONTROLLER_AXIS_MAX];
axis_values[SDL_CONTROLLER_AXIS_LEFTX] =
SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_LEFTX);
axis_values[SDL_CONTROLLER_AXIS_LEFTY] =
SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_LEFTY);
axis_values[SDL_CONTROLLER_AXIS_RIGHTX] =
SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_RIGHTX);
axis_values[SDL_CONTROLLER_AXIS_RIGHTY] =
SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_RIGHTY);
ev.type = ev_joystick;
ev.data1 = GetButtonsStateGamepad();
ev.data2 = GetAxisStateGamepad(axis_values, joystick_x_axis,
joystick_x_invert, joystick_x_dead_zone);
ev.data3 = GetAxisStateGamepad(axis_values, joystick_y_axis,
joystick_y_invert, joystick_y_dead_zone);
ev.data4 = GetAxisStateGamepad(axis_values, joystick_strafe_axis,
joystick_strafe_invert,
joystick_strafe_dead_zone);
ev.data5 = GetAxisStateGamepad(axis_values, joystick_look_axis,
joystick_look_invert,
joystick_look_dead_zone);
ev.data6 = GetDirectionalInputGamepad(axis_values);
D_PostEvent(&ev);
}
}
void I_ShutdownJoystick(void)
{
if (joystick != NULL)
{
SDL_JoystickClose(joystick);
joystick = NULL;
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
}
static boolean IsValidAxis(int axis)
{
int num_axes;
if (axis < 0)
{
return true;
}
if (IS_BUTTON_AXIS(axis))
{
return true;
}
if (IS_HAT_AXIS(axis))
{
return HAT_AXIS_HAT(axis) < SDL_JoystickNumHats(joystick);
}
num_axes = SDL_JoystickNumAxes(joystick);
return axis < num_axes;
}
static int DeviceIndex(void)
{
SDL_JoystickGUID guid, dev_guid;
int i;
guid = SDL_JoystickGetGUIDFromString(joystick_guid);
// GUID identifies a class of device rather than a specific device.
// Check if joystick_index has the expected GUID, as this can act
// as a tie-breaker in case there are multiple identical devices.
if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks())
{
dev_guid = SDL_JoystickGetDeviceGUID(joystick_index);
if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
{
return joystick_index;
}
}
// Check all devices to look for one with the expected GUID.
for (i = 0; i < SDL_NumJoysticks(); ++i)
{
dev_guid = SDL_JoystickGetDeviceGUID(i);
if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
{
printf("I_InitJoystick: Joystick moved to index %d.\n", i);
return i;
}
}
// No joystick found with the expected GUID.
return -1;
}
void I_InitJoystick(void)
{
int index;
if (!usejoystick || !strcmp(joystick_guid, ""))
{
return;
}
if (use_gamepad)
{
I_InitGamepad();
return;
}
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
{
return;
}
index = DeviceIndex();
if (index < 0)
{
printf("I_InitJoystick: Couldn't find joystick with GUID \"%s\": "
"device not found or not connected?\n",
joystick_guid);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
return;
}
// Open the joystick
joystick = SDL_JoystickOpen(index);
if (joystick == NULL)
{
printf("I_InitJoystick: Failed to open joystick #%i: %s\n", index,
SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
return;
}
if (!IsValidAxis(joystick_x_axis)
|| !IsValidAxis(joystick_y_axis)
|| !IsValidAxis(joystick_strafe_axis)
|| !IsValidAxis(joystick_look_axis))
{
printf("I_InitJoystick: Invalid joystick axis for configured joystick "
"(run joystick setup again)\n");
SDL_JoystickClose(joystick);
joystick = NULL;
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
SDL_JoystickEventState(SDL_ENABLE);
// Initialized okay!
printf("I_InitJoystick: %s\n", SDL_JoystickName(joystick));
I_AtExit(I_ShutdownJoystick, true);
}
static boolean IsAxisButton(int physbutton)
{
if (IS_BUTTON_AXIS(joystick_x_axis))
{
if (physbutton == BUTTON_AXIS_NEG(joystick_x_axis)
|| physbutton == BUTTON_AXIS_POS(joystick_x_axis))
{
return true;
}
}
if (IS_BUTTON_AXIS(joystick_y_axis))
{
if (physbutton == BUTTON_AXIS_NEG(joystick_y_axis)
|| physbutton == BUTTON_AXIS_POS(joystick_y_axis))
{
return true;
}
}
if (IS_BUTTON_AXIS(joystick_strafe_axis))
{
if (physbutton == BUTTON_AXIS_NEG(joystick_strafe_axis)
|| physbutton == BUTTON_AXIS_POS(joystick_strafe_axis))
{
return true;
}
}
if (IS_BUTTON_AXIS(joystick_look_axis))
{
if (physbutton == BUTTON_AXIS_NEG(joystick_look_axis)
|| physbutton == BUTTON_AXIS_POS(joystick_look_axis))
{
return true;
}
}
return false;
}
// Get the state of the given virtual button.
static int ReadButtonState(int vbutton)
{
int physbutton;
// Map from virtual button to physical (SDL) button.
if (vbutton < NUM_VIRTUAL_BUTTONS)
{
physbutton = joystick_physical_buttons[vbutton];
}
else
{
physbutton = vbutton;
}
// Never read axis buttons as buttons.
if (IsAxisButton(physbutton))
{
return 0;
}
return SDL_JoystickGetButton(joystick, physbutton);
}
// Get a bitmask of all currently-pressed buttons
static int GetButtonsState(void)
{
int i;
int result = 0;
for (i = 0; i < MAX_VIRTUAL_BUTTONS; ++i)
{
if (ReadButtonState(i))
{
result |= 1 << i;
}
}
return result;
}
// Read the state of an axis, inverting if necessary.
static int GetAxisState(int axis, int invert, int dead_zone)
{
int result;
// Axis -1 means disabled.
if (axis < 0)
{
return 0;
}
// Is this a button axis, or a hat axis?
// If so, we need to handle it specially.
result = 0;
if (IS_BUTTON_AXIS(axis))
{
if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_NEG(axis)))
{
result -= 32767;
}
if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_POS(axis)))
{
result += 32767;
}
}
else if (IS_HAT_AXIS(axis))
{
int direction = HAT_AXIS_DIRECTION(axis);
int hatval = SDL_JoystickGetHat(joystick, HAT_AXIS_HAT(axis));
if (direction == HAT_AXIS_HORIZONTAL)
{
if ((hatval & SDL_HAT_LEFT) != 0)
{
result -= 32767;
}
else if ((hatval & SDL_HAT_RIGHT) != 0)
{
result += 32767;
}
}
else if (direction == HAT_AXIS_VERTICAL)
{
if ((hatval & SDL_HAT_UP) != 0)
{
result -= 32767;
}
else if ((hatval & SDL_HAT_DOWN) != 0)
{
result += 32767;
}
}
}
else
{
result = SDL_JoystickGetAxis(joystick, axis);
// Dead zone is expressed as percentage of axis max value
dead_zone = 32768 * dead_zone / 100;
if (result < dead_zone && result > -dead_zone)
{
result = 0;
}
}
if (invert)
{
result = -result;
}
result *= FRACUNIT / 32768; // Want FXP number between -1 and 1
return result;
}
static int GetDirectionalInput(int x, int y)
{
int retval = JOY_DIR_NONE;
if (x > 0)
{
retval |= JOY_DIR_RIGHT;
}
else if (x < 0)
{
retval |= JOY_DIR_LEFT;
}
if (y > 0)
{
retval |= JOY_DIR_DOWN;
}
else if (y < 0)
{
retval |= JOY_DIR_UP;
}
return (retval << DPAD_SHIFT);
}
void I_UpdateJoystick(void)
{
if (use_gamepad)
{
I_UpdateGamepad();
return;
}
if (joystick != NULL)
{
event_t ev;
ev.type = ev_joystick;
ev.data1 = GetButtonsState();
ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert,
joystick_x_dead_zone);
ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert,
joystick_y_dead_zone);
ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert,
joystick_strafe_dead_zone);
ev.data5 = GetAxisState(joystick_look_axis, joystick_look_invert,
joystick_look_dead_zone);
ev.data6 = GetDirectionalInput(ev.data2, ev.data3);
D_PostEvent(&ev);
}
}
void I_BindJoystickVariables(void)
{
int i;
M_BindIntVariable("use_joystick", &usejoystick);
M_BindIntVariable("use_gamepad", &use_gamepad);
M_BindIntVariable("gamepad_type", &gamepad_type);
M_BindStringVariable("joystick_guid", &joystick_guid);
M_BindIntVariable("joystick_index", &joystick_index);
M_BindIntVariable("joystick_x_axis", &joystick_x_axis);
M_BindIntVariable("joystick_y_axis", &joystick_y_axis);
M_BindIntVariable("joystick_strafe_axis", &joystick_strafe_axis);
M_BindIntVariable("joystick_x_invert", &joystick_x_invert);
M_BindIntVariable("joystick_y_invert", &joystick_y_invert);
M_BindIntVariable("joystick_strafe_invert",&joystick_strafe_invert);
M_BindIntVariable("joystick_look_axis", &joystick_look_axis);
M_BindIntVariable("joystick_look_invert", &joystick_look_invert);
M_BindIntVariable("joystick_x_dead_zone", &joystick_x_dead_zone);
M_BindIntVariable("joystick_y_dead_zone", &joystick_y_dead_zone);
M_BindIntVariable("joystick_strafe_dead_zone", &joystick_strafe_dead_zone);
M_BindIntVariable("joystick_look_dead_zone", &joystick_look_dead_zone);
M_BindIntVariable("use_analog", &use_analog);
M_BindIntVariable("joystick_turn_sensitivity", &joystick_turn_sensitivity);
M_BindIntVariable("joystick_move_sensitivity", &joystick_move_sensitivity);
M_BindIntVariable("joystick_look_sensitivity", &joystick_look_sensitivity);
for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i)
{
char name[32];
M_snprintf(name, sizeof(name), "joystick_physical_button%i", i);
M_BindIntVariable(name, &joystick_physical_buttons[i]);
}
}