testcontroller: use SDL_MAIN_USE_CALLBACKS so updates happen during live resizing

This commit is contained in:
Sam Lantinga
2025-01-20 09:43:21 -08:00
parent f8040b2e01
commit 362f96a6cf

View File

@@ -12,6 +12,7 @@
/* Simple program to test the SDL controller routines */
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
@@ -64,6 +65,7 @@ typedef struct
int trigger_effect;
} Controller;
static SDLTest_CommonState *state;
static SDL_Window *window = NULL;
static SDL_Renderer *screen = NULL;
static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
@@ -194,47 +196,47 @@ typedef struct
static void CyclePS5AudioRoute(Controller *device)
{
DS5EffectsState_t state;
DS5EffectsState_t effects;
device->audio_route = (device->audio_route + 1) % 4;
SDL_zero(state);
SDL_zero(effects);
switch (device->audio_route) {
case 0:
/* Audio disabled */
state.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
state.ucSpeakerVolume = 0; /* Minimum volume */
state.ucHeadphoneVolume = 0; /* Minimum volume */
state.ucAudioEnableBits = 0x00; /* Output to headphones */
effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
effects.ucSpeakerVolume = 0; /* Minimum volume */
effects.ucHeadphoneVolume = 0; /* Minimum volume */
effects.ucAudioEnableBits = 0x00; /* Output to headphones */
break;
case 1:
/* Headphones */
state.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */
state.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
state.ucAudioEnableBits = 0x00; /* Output to headphones */
effects.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */
effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
effects.ucAudioEnableBits = 0x00; /* Output to headphones */
break;
case 2:
/* Speaker */
state.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */
state.ucSpeakerVolume = 100; /* Maximum volume */
state.ucAudioEnableBits = 0x30; /* Output to speaker */
effects.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */
effects.ucSpeakerVolume = 100; /* Maximum volume */
effects.ucAudioEnableBits = 0x30; /* Output to speaker */
break;
case 3:
/* Both */
state.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
state.ucSpeakerVolume = 100; /* Maximum volume */
state.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
state.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */
effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
effects.ucSpeakerVolume = 100; /* Maximum volume */
effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
effects.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */
break;
}
SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects));
}
static void CyclePS5TriggerEffect(Controller *device)
{
DS5EffectsState_t state;
DS5EffectsState_t effects;
Uint8 effects[3][11] = {
Uint8 trigger_effects[3][11] = {
/* Clear trigger effect */
{ 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* Constant resistance across entire trigger pull */
@@ -243,13 +245,13 @@ static void CyclePS5TriggerEffect(Controller *device)
{ 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
};
device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(effects);
device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects);
SDL_zero(state);
state.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
SDL_memcpy(state.rgucRightTriggerEffect, effects[device->trigger_effect], sizeof(effects[0]));
SDL_memcpy(state.rgucLeftTriggerEffect, effects[device->trigger_effect], sizeof(effects[0]));
SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
SDL_zero(effects);
effects.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
SDL_memcpy(effects.rgucRightTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0]));
SDL_memcpy(effects.rgucLeftTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0]));
SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects));
}
static void ClearButtonHighlights(void)
@@ -1573,360 +1575,361 @@ static void UpdateGamepadEffects(void)
}
}
static void loop(void *arg)
SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event)
{
SDL_Event event;
SDL_ConvertEventToRenderCoordinates(screen, event);
switch (event->type) {
case SDL_EVENT_JOYSTICK_ADDED:
AddController(event->jdevice.which, true);
break;
case SDL_EVENT_JOYSTICK_REMOVED:
DelController(event->jdevice.which);
break;
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
SetController(event->jaxis.which);
}
} else if (display_mode == CONTROLLER_MODE_BINDING &&
event->jaxis.which == controller->id &&
event->jaxis.axis < controller->num_axes &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */
AxisState *pAxisState = &controller->axis_state[event->jaxis.axis];
int nValue = event->jaxis.value;
int nCurrentDistance, nFarthestDistance;
if (!pAxisState->m_bMoving) {
Sint16 nInitialValue;
pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue);
pAxisState->m_nLastValue = nValue;
pAxisState->m_nStartingValue = nInitialValue;
pAxisState->m_nFarthestValue = nInitialValue;
} else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) {
break;
} else {
pAxisState->m_nLastValue = nValue;
}
nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue);
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
if (nCurrentDistance > nFarthestDistance) {
pAxisState->m_nFarthestValue = nValue;
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
}
#ifdef DEBUG_AXIS_MAPPING
SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d\n", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance);
#endif
/* If we've gone out far enough and started to come back, let's bind this axis */
if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) {
char binding[12];
int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue);
int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue);
if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) {
/* The negative half axis */
(void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis);
} else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) {
/* The positive half axis */
(void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis);
} else {
(void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis);
if (axis_min > axis_max) {
/* Invert the axis */
SDL_strlcat(binding, "~", SDL_arraysize(binding));
}
}
#ifdef DEBUG_AXIS_MAPPING
SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s\n", event->jaxis.axis, axis_min, axis_max, binding);
#endif
CommitBindingElement(binding, false);
}
}
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
if (display_mode == CONTROLLER_MODE_TESTING) {
SetController(event->jbutton.which);
}
break;
case SDL_EVENT_JOYSTICK_BUTTON_UP:
if (display_mode == CONTROLLER_MODE_BINDING &&
event->jbutton.which == controller->id &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
char binding[12];
SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button);
CommitBindingElement(binding, false);
}
break;
case SDL_EVENT_JOYSTICK_HAT_MOTION:
if (display_mode == CONTROLLER_MODE_BINDING &&
event->jhat.which == controller->id &&
event->jhat.value != SDL_HAT_CENTERED &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
char binding[12];
SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value);
CommitBindingElement(binding, false);
}
break;
case SDL_EVENT_GAMEPAD_ADDED:
HandleGamepadAdded(event->gdevice.which, true);
break;
case SDL_EVENT_GAMEPAD_REMOVED:
HandleGamepadRemoved(event->gdevice.which);
break;
case SDL_EVENT_GAMEPAD_REMAPPED:
HandleGamepadRemapped(event->gdevice.which);
break;
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
RefreshControllerName();
break;
#ifdef VERBOSE_TOUCHPAD
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f\n",
event->gtouchpad.which,
event->gtouchpad.touchpad,
event->gtouchpad.finger,
(event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")),
event->gtouchpad.x,
event->gtouchpad.y,
event->gtouchpad.pressure);
break;
#endif /* VERBOSE_TOUCHPAD */
#ifdef VERBOSE_SENSORS
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")\n",
event->gsensor.which,
GetSensorName((SDL_SensorType) event->gsensor.sensor),
event->gsensor.data[0],
event->gsensor.data[1],
event->gsensor.data[2],
event->gsensor.sensor_timestamp);
break;
#endif /* VERBOSE_SENSORS */
#ifdef VERBOSE_AXES
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
SetController(event->gaxis.which);
}
}
SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d\n",
event->gaxis.which,
SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis),
event->gaxis.value);
break;
#endif /* VERBOSE_AXES */
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
SetController(event->gbutton.which);
}
}
#ifdef VERBOSE_BUTTONS
SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s\n",
event->gbutton.which,
SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button),
event->gbutton.state ? "pressed" : "released");
#endif /* VERBOSE_BUTTONS */
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) {
/* Cycle PS5 audio routing when the microphone button is pressed */
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
CyclePS5AudioRoute(controller);
}
/* Cycle PS5 trigger effects when the triangle button is pressed */
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) {
CyclePS5TriggerEffect(controller);
}
}
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseDown(event->button.x, event->button.y);
}
UpdateButtonHighlights(event->button.x, event->button.y, event->button.down);
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseUp(event->button.x, event->button.y);
}
if (display_mode == CONTROLLER_MODE_TESTING) {
if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) {
SetDisplayMode(CONTROLLER_MODE_BINDING);
}
} else if (display_mode == CONTROLLER_MODE_BINDING) {
if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) {
if (controller->mapping) {
SDL_Log("Mapping complete:\n");
SDL_Log("%s\n", controller->mapping);
}
SetDisplayMode(CONTROLLER_MODE_TESTING);
} else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) {
CancelMapping();
} else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) {
ClearMapping();
} else if (controller->has_bindings &&
GamepadButtonContains(copy_button, event->button.x, event->button.y)) {
CopyMapping();
} else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) {
PasteMapping();
} else if (title_pressed) {
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false);
} else if (type_pressed) {
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false);
} else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y);
if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
CommitGamepadType((SDL_GamepadType)type);
StopBinding();
}
} else {
int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
char *joystick_element;
if (controller->joystick != virtual_joystick) {
gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y);
}
if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) {
gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y);
}
if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
/* Set this to false if you don't want to start the binding flow at this point */
const bool should_start_flow = true;
SetCurrentBindingElement(gamepad_element, should_start_flow);
}
joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y);
if (joystick_element) {
CommitBindingElement(joystick_element, true);
SDL_free(joystick_element);
}
}
}
UpdateButtonHighlights(event->button.x, event->button.y, event->button.down);
break;
case SDL_EVENT_MOUSE_MOTION:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseMotion(event->motion.x, event->motion.y);
}
UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false);
break;
case SDL_EVENT_KEY_DOWN:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) {
if (controller && controller->gamepad) {
int player_index = (event->key.key - SDLK_0);
SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
}
break;
} else if (event->key.key == SDLK_A) {
OpenVirtualGamepad();
} else if (event->key.key == SDLK_D) {
CloseVirtualGamepad();
} else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) {
SDL_ReloadGamepadMappings();
} else if (event->key.key == SDLK_ESCAPE) {
done = true;
}
} else if (display_mode == CONTROLLER_MODE_BINDING) {
if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
CopyControllerName();
} else {
CopyMapping();
}
} else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
ClearControllerName();
PasteControllerName();
} else {
PasteMapping();
}
} else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
CopyControllerName();
ClearControllerName();
} else {
CopyMapping();
ClearMapping();
}
} else if (event->key.key == SDLK_SPACE) {
if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
ClearBinding();
}
} else if (event->key.key == SDLK_BACKSPACE) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
BackspaceControllerName();
}
} else if (event->key.key == SDLK_RETURN) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
StopBinding();
}
} else if (event->key.key == SDLK_ESCAPE) {
if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
StopBinding();
} else {
CancelMapping();
}
}
}
break;
case SDL_EVENT_TEXT_INPUT:
if (display_mode == CONTROLLER_MODE_BINDING) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
AddControllerNameText(event->text.text);
}
}
break;
case SDL_EVENT_QUIT:
done = true;
break;
default:
break;
}
if (done) {
return SDL_APP_SUCCESS;
} else {
return SDL_APP_CONTINUE;
}
}
SDL_AppResult SDLCALL SDL_AppIterate(void *appstate)
{
/* If we have a virtual controller, send a virtual accelerometer sensor reading */
if (virtual_joystick) {
float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f };
SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data));
}
/* Update to get the current event state */
SDL_PumpEvents();
/* Process all currently pending events */
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) == 1) {
SDL_ConvertEventToRenderCoordinates(screen, &event);
switch (event.type) {
case SDL_EVENT_JOYSTICK_ADDED:
AddController(event.jdevice.which, true);
break;
case SDL_EVENT_JOYSTICK_REMOVED:
DelController(event.jdevice.which);
break;
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event.jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
SetController(event.jaxis.which);
}
} else if (display_mode == CONTROLLER_MODE_BINDING &&
event.jaxis.which == controller->id &&
event.jaxis.axis < controller->num_axes &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */
AxisState *pAxisState = &controller->axis_state[event.jaxis.axis];
int nValue = event.jaxis.value;
int nCurrentDistance, nFarthestDistance;
if (!pAxisState->m_bMoving) {
Sint16 nInitialValue;
pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event.jaxis.axis, &nInitialValue);
pAxisState->m_nLastValue = nValue;
pAxisState->m_nStartingValue = nInitialValue;
pAxisState->m_nFarthestValue = nInitialValue;
} else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) {
break;
} else {
pAxisState->m_nLastValue = nValue;
}
nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue);
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
if (nCurrentDistance > nFarthestDistance) {
pAxisState->m_nFarthestValue = nValue;
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
}
#ifdef DEBUG_AXIS_MAPPING
SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d\n", event.jaxis.axis, nValue, nCurrentDistance, nFarthestDistance);
#endif
/* If we've gone out far enough and started to come back, let's bind this axis */
if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) {
char binding[12];
int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue);
int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue);
if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) {
/* The negative half axis */
(void)SDL_snprintf(binding, sizeof(binding), "-a%d", event.jaxis.axis);
} else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) {
/* The positive half axis */
(void)SDL_snprintf(binding, sizeof(binding), "+a%d", event.jaxis.axis);
} else {
(void)SDL_snprintf(binding, sizeof(binding), "a%d", event.jaxis.axis);
if (axis_min > axis_max) {
/* Invert the axis */
SDL_strlcat(binding, "~", SDL_arraysize(binding));
}
}
#ifdef DEBUG_AXIS_MAPPING
SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s\n", event.jaxis.axis, axis_min, axis_max, binding);
#endif
CommitBindingElement(binding, false);
}
}
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
if (display_mode == CONTROLLER_MODE_TESTING) {
SetController(event.jbutton.which);
}
break;
case SDL_EVENT_JOYSTICK_BUTTON_UP:
if (display_mode == CONTROLLER_MODE_BINDING &&
event.jbutton.which == controller->id &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
char binding[12];
SDL_snprintf(binding, sizeof(binding), "b%d", event.jbutton.button);
CommitBindingElement(binding, false);
}
break;
case SDL_EVENT_JOYSTICK_HAT_MOTION:
if (display_mode == CONTROLLER_MODE_BINDING &&
event.jhat.which == controller->id &&
event.jhat.value != SDL_HAT_CENTERED &&
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
char binding[12];
SDL_snprintf(binding, sizeof(binding), "h%d.%d", event.jhat.hat, event.jhat.value);
CommitBindingElement(binding, false);
}
break;
case SDL_EVENT_GAMEPAD_ADDED:
HandleGamepadAdded(event.gdevice.which, true);
break;
case SDL_EVENT_GAMEPAD_REMOVED:
HandleGamepadRemoved(event.gdevice.which);
break;
case SDL_EVENT_GAMEPAD_REMAPPED:
HandleGamepadRemapped(event.gdevice.which);
break;
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
RefreshControllerName();
break;
#ifdef VERBOSE_TOUCHPAD
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f\n",
event.gtouchpad.which,
event.gtouchpad.touchpad,
event.gtouchpad.finger,
(event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")),
event.gtouchpad.x,
event.gtouchpad.y,
event.gtouchpad.pressure);
break;
#endif /* VERBOSE_TOUCHPAD */
#ifdef VERBOSE_SENSORS
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")\n",
event.gsensor.which,
GetSensorName((SDL_SensorType) event.gsensor.sensor),
event.gsensor.data[0],
event.gsensor.data[1],
event.gsensor.data[2],
event.gsensor.sensor_timestamp);
break;
#endif /* VERBOSE_SENSORS */
#ifdef VERBOSE_AXES
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event.gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
SetController(event.gaxis.which);
}
}
SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d\n",
event.gaxis.which,
SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event.gaxis.axis),
event.gaxis.value);
break;
#endif /* VERBOSE_AXES */
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
SetController(event.gbutton.which);
}
}
#ifdef VERBOSE_BUTTONS
SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s\n",
event.gbutton.which,
SDL_GetGamepadStringForButton((SDL_GamepadButton) event.gbutton.button),
event.gbutton.state ? "pressed" : "released");
#endif /* VERBOSE_BUTTONS */
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) {
/* Cycle PS5 audio routing when the microphone button is pressed */
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
CyclePS5AudioRoute(controller);
}
/* Cycle PS5 trigger effects when the triangle button is pressed */
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) {
CyclePS5TriggerEffect(controller);
}
}
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseDown(event.button.x, event.button.y);
}
UpdateButtonHighlights(event.button.x, event.button.y, event.button.down);
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseUp(event.button.x, event.button.y);
}
if (display_mode == CONTROLLER_MODE_TESTING) {
if (GamepadButtonContains(setup_mapping_button, event.button.x, event.button.y)) {
SetDisplayMode(CONTROLLER_MODE_BINDING);
}
} else if (display_mode == CONTROLLER_MODE_BINDING) {
if (GamepadButtonContains(done_mapping_button, event.button.x, event.button.y)) {
if (controller->mapping) {
SDL_Log("Mapping complete:\n");
SDL_Log("%s\n", controller->mapping);
}
SetDisplayMode(CONTROLLER_MODE_TESTING);
} else if (GamepadButtonContains(cancel_button, event.button.x, event.button.y)) {
CancelMapping();
} else if (GamepadButtonContains(clear_button, event.button.x, event.button.y)) {
ClearMapping();
} else if (controller->has_bindings &&
GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
CopyMapping();
} else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) {
PasteMapping();
} else if (title_pressed) {
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false);
} else if (type_pressed) {
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false);
} else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
int type = GetGamepadTypeDisplayAt(gamepad_type, event.button.x, event.button.y);
if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
CommitGamepadType((SDL_GamepadType)type);
StopBinding();
}
} else {
int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
char *joystick_element;
if (controller->joystick != virtual_joystick) {
gamepad_element = GetGamepadImageElementAt(image, event.button.x, event.button.y);
}
if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) {
gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event.button.x, event.button.y);
}
if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
/* Set this to false if you don't want to start the binding flow at this point */
const bool should_start_flow = true;
SetCurrentBindingElement(gamepad_element, should_start_flow);
}
joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event.button.x, event.button.y);
if (joystick_element) {
CommitBindingElement(joystick_element, true);
SDL_free(joystick_element);
}
}
}
UpdateButtonHighlights(event.button.x, event.button.y, event.button.down);
break;
case SDL_EVENT_MOUSE_MOTION:
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
VirtualGamepadMouseMotion(event.motion.x, event.motion.y);
}
UpdateButtonHighlights(event.motion.x, event.motion.y, event.motion.state ? true : false);
break;
case SDL_EVENT_KEY_DOWN:
if (display_mode == CONTROLLER_MODE_TESTING) {
if (event.key.key >= SDLK_0 && event.key.key <= SDLK_9) {
if (controller && controller->gamepad) {
int player_index = (event.key.key - SDLK_0);
SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
}
break;
} else if (event.key.key == SDLK_A) {
OpenVirtualGamepad();
} else if (event.key.key == SDLK_D) {
CloseVirtualGamepad();
} else if (event.key.key == SDLK_R && (event.key.mod & SDL_KMOD_CTRL)) {
SDL_ReloadGamepadMappings();
} else if (event.key.key == SDLK_ESCAPE) {
done = true;
}
} else if (display_mode == CONTROLLER_MODE_BINDING) {
if (event.key.key == SDLK_C && (event.key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
CopyControllerName();
} else {
CopyMapping();
}
} else if (event.key.key == SDLK_V && (event.key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
ClearControllerName();
PasteControllerName();
} else {
PasteMapping();
}
} else if (event.key.key == SDLK_X && (event.key.mod & SDL_KMOD_CTRL)) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
CopyControllerName();
ClearControllerName();
} else {
CopyMapping();
ClearMapping();
}
} else if (event.key.key == SDLK_SPACE) {
if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
ClearBinding();
}
} else if (event.key.key == SDLK_BACKSPACE) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
BackspaceControllerName();
}
} else if (event.key.key == SDLK_RETURN) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
StopBinding();
}
} else if (event.key.key == SDLK_ESCAPE) {
if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
StopBinding();
} else {
CancelMapping();
}
}
}
break;
case SDL_EVENT_TEXT_INPUT:
if (display_mode == CONTROLLER_MODE_BINDING) {
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
AddControllerNameText(event.text.text);
}
}
break;
case SDL_EVENT_QUIT:
done = true;
break;
default:
break;
}
}
/* Wait 30 ms for joystick events to stop coming in,
in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger)
*/
@@ -1982,14 +1985,10 @@ static void loop(void *arg)
SDL_Delay(16);
SDL_RenderPresent(screen);
#ifdef SDL_PLATFORM_EMSCRIPTEN
if (done) {
emscripten_cancel_main_loop();
}
#endif
return SDL_APP_CONTINUE;
}
int main(int argc, char *argv[])
SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[])
{
bool show_mappings = false;
int i;
@@ -1997,12 +1996,11 @@ int main(int argc, char *argv[])
int screen_width, screen_height;
SDL_FRect area;
int gamepad_index = -1;
SDLTest_CommonState *state;
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (!state) {
return 1;
return SDL_APP_FAILURE;
}
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
@@ -2038,7 +2036,7 @@ int main(int argc, char *argv[])
if (consumed <= 0) {
static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL };
SDLTest_CommonLogUsage(state, argv[0], options);
return 1;
return SDL_APP_FAILURE;
}
i += consumed;
@@ -2050,7 +2048,7 @@ int main(int argc, char *argv[])
/* Initialize SDL (Note: video is required to start event loop) */
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
return 1;
return SDL_APP_FAILURE;
}
SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
@@ -2077,14 +2075,14 @@ int main(int argc, char *argv[])
window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, 0);
if (!window) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
return 2;
return SDL_APP_FAILURE;
}
screen = SDL_CreateRenderer(window, NULL);
if (!screen) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
return 2;
return SDL_APP_FAILURE;
}
SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
@@ -2110,7 +2108,7 @@ int main(int argc, char *argv[])
if (!image) {
SDL_DestroyRenderer(screen);
SDL_DestroyWindow(window);
return 2;
return SDL_APP_FAILURE;
}
SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
@@ -2178,7 +2176,7 @@ int main(int argc, char *argv[])
SetGamepadButtonArea(done_mapping_button, &area);
/* Process the initial gamepad list */
loop(NULL);
SDL_AppIterate(NULL);
if (gamepad_index < num_controllers) {
SetController(controllers[gamepad_index].id);
@@ -2186,15 +2184,11 @@ int main(int argc, char *argv[])
SetController(controllers[0].id);
}
/* Loop, getting gamepad events! */
#ifdef SDL_PLATFORM_EMSCRIPTEN
emscripten_set_main_loop_arg(loop, NULL, 0, 1);
#else
while (!done) {
loop(NULL);
}
#endif
return SDL_APP_CONTINUE;
}
void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result)
{
CloseVirtualGamepad();
while (num_controllers > 0) {
HandleGamepadRemoved(controllers[0].id);
@@ -2217,5 +2211,4 @@ int main(int argc, char *argv[])
SDL_DestroyWindow(window);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 0;
}