mirror of
				https://github.com/thunderbrewhq/thunderbrew
				synced 2025-10-28 14:56:06 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			2244 lines
		
	
	
		
			80 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2244 lines
		
	
	
		
			80 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|   Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
 | |
| 
 | |
|   This software is provided 'as-is', without any express or implied
 | |
|   warranty.  In no event will the authors be held liable for any damages
 | |
|   arising from the use of this software.
 | |
| 
 | |
|   Permission is granted to anyone to use this software for any purpose,
 | |
|   including commercial applications, and to alter it and redistribute it
 | |
|   freely.
 | |
| */
 | |
| 
 | |
| /* 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>
 | |
| #include <SDL3/SDL_test_font.h>
 | |
| 
 | |
| #ifdef SDL_PLATFORM_EMSCRIPTEN
 | |
| #include <emscripten/emscripten.h>
 | |
| #endif
 | |
| 
 | |
| #include "gamepadutils.h"
 | |
| #include "testutils.h"
 | |
| 
 | |
| #if 0
 | |
| #define DEBUG_AXIS_MAPPING
 | |
| #endif
 | |
| 
 | |
| #define TITLE_HEIGHT 48.0f
 | |
| #define PANEL_SPACING 25.0f
 | |
| #define PANEL_WIDTH 250.0f
 | |
| #define MINIMUM_BUTTON_WIDTH 96.0f
 | |
| #define BUTTON_MARGIN 16.0f
 | |
| #define BUTTON_PADDING 12.0f
 | |
| #define GAMEPAD_WIDTH 512.0f
 | |
| #define GAMEPAD_HEIGHT 560.0f
 | |
| 
 | |
| #define SCREEN_WIDTH  (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH)
 | |
| #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT)
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|     bool m_bMoving;
 | |
|     int m_nLastValue;
 | |
|     int m_nStartingValue;
 | |
|     int m_nFarthestValue;
 | |
| } AxisState;
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|     SDL_JoystickID id;
 | |
| 
 | |
|     SDL_Joystick *joystick;
 | |
|     int num_axes;
 | |
|     AxisState *axis_state;
 | |
| 
 | |
|     SDL_Gamepad *gamepad;
 | |
|     char *mapping;
 | |
|     bool has_bindings;
 | |
| 
 | |
|     int audio_route;
 | |
|     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;
 | |
| static GamepadImage *image = NULL;
 | |
| static GamepadDisplay *gamepad_elements = NULL;
 | |
| static GamepadTypeDisplay *gamepad_type = NULL;
 | |
| static JoystickDisplay *joystick_elements = NULL;
 | |
| static GamepadButton *setup_mapping_button = NULL;
 | |
| static GamepadButton *done_mapping_button = NULL;
 | |
| static GamepadButton *cancel_button = NULL;
 | |
| static GamepadButton *clear_button = NULL;
 | |
| static GamepadButton *copy_button = NULL;
 | |
| static GamepadButton *paste_button = NULL;
 | |
| static char *backup_mapping = NULL;
 | |
| static bool done = false;
 | |
| static bool set_LED = false;
 | |
| static int num_controllers = 0;
 | |
| static Controller *controllers;
 | |
| static Controller *controller;
 | |
| static SDL_JoystickID mapping_controller = 0;
 | |
| static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
 | |
| static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
 | |
| static bool binding_flow = false;
 | |
| static int binding_flow_direction = 0;
 | |
| static Uint64 binding_advance_time = 0;
 | |
| static SDL_FRect title_area;
 | |
| static bool title_highlighted;
 | |
| static bool title_pressed;
 | |
| static SDL_FRect type_area;
 | |
| static bool type_highlighted;
 | |
| static bool type_pressed;
 | |
| static char *controller_name;
 | |
| static SDL_Joystick *virtual_joystick = NULL;
 | |
| static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
 | |
| static float virtual_axis_start_x;
 | |
| static float virtual_axis_start_y;
 | |
| static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
 | |
| static bool virtual_touchpad_active = false;
 | |
| static float virtual_touchpad_x;
 | |
| static float virtual_touchpad_y;
 | |
| 
 | |
| static int s_arrBindingOrder[] = {
 | |
|     /* Standard sequence */
 | |
|     SDL_GAMEPAD_BUTTON_SOUTH,
 | |
|     SDL_GAMEPAD_BUTTON_EAST,
 | |
|     SDL_GAMEPAD_BUTTON_WEST,
 | |
|     SDL_GAMEPAD_BUTTON_NORTH,
 | |
|     SDL_GAMEPAD_BUTTON_DPAD_LEFT,
 | |
|     SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
 | |
|     SDL_GAMEPAD_BUTTON_DPAD_UP,
 | |
|     SDL_GAMEPAD_BUTTON_DPAD_DOWN,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE,
 | |
|     SDL_GAMEPAD_BUTTON_LEFT_STICK,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE,
 | |
|     SDL_GAMEPAD_BUTTON_RIGHT_STICK,
 | |
|     SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER,
 | |
|     SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
 | |
|     SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER,
 | |
|     SDL_GAMEPAD_BUTTON_BACK,
 | |
|     SDL_GAMEPAD_BUTTON_START,
 | |
|     SDL_GAMEPAD_BUTTON_GUIDE,
 | |
|     SDL_GAMEPAD_BUTTON_MISC1,
 | |
|     SDL_GAMEPAD_ELEMENT_INVALID,
 | |
| 
 | |
|     /* Paddle sequence */
 | |
|     SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
 | |
|     SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
 | |
|     SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
 | |
|     SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
 | |
|     SDL_GAMEPAD_ELEMENT_INVALID,
 | |
| };
 | |
| 
 | |
| 
 | |
| static const char *GetSensorName(SDL_SensorType sensor)
 | |
| {
 | |
|     switch (sensor) {
 | |
|     case SDL_SENSOR_ACCEL:
 | |
|         return "accelerometer";
 | |
|     case SDL_SENSOR_GYRO:
 | |
|         return "gyro";
 | |
|     case SDL_SENSOR_ACCEL_L:
 | |
|         return "accelerometer (L)";
 | |
|     case SDL_SENSOR_GYRO_L:
 | |
|         return "gyro (L)";
 | |
|     case SDL_SENSOR_ACCEL_R:
 | |
|         return "accelerometer (R)";
 | |
|     case SDL_SENSOR_GYRO_R:
 | |
|         return "gyro (R)";
 | |
|     default:
 | |
|         return "UNKNOWN";
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* PS5 trigger effect documentation:
 | |
|    https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
 | |
| */
 | |
| typedef struct
 | |
| {
 | |
|     Uint8 ucEnableBits1;              /* 0 */
 | |
|     Uint8 ucEnableBits2;              /* 1 */
 | |
|     Uint8 ucRumbleRight;              /* 2 */
 | |
|     Uint8 ucRumbleLeft;               /* 3 */
 | |
|     Uint8 ucHeadphoneVolume;          /* 4 */
 | |
|     Uint8 ucSpeakerVolume;            /* 5 */
 | |
|     Uint8 ucMicrophoneVolume;         /* 6 */
 | |
|     Uint8 ucAudioEnableBits;          /* 7 */
 | |
|     Uint8 ucMicLightMode;             /* 8 */
 | |
|     Uint8 ucAudioMuteBits;            /* 9 */
 | |
|     Uint8 rgucRightTriggerEffect[11]; /* 10 */
 | |
|     Uint8 rgucLeftTriggerEffect[11];  /* 21 */
 | |
|     Uint8 rgucUnknown1[6];            /* 32 */
 | |
|     Uint8 ucLedFlags;                 /* 38 */
 | |
|     Uint8 rgucUnknown2[2];            /* 39 */
 | |
|     Uint8 ucLedAnim;                  /* 41 */
 | |
|     Uint8 ucLedBrightness;            /* 42 */
 | |
|     Uint8 ucPadLights;                /* 43 */
 | |
|     Uint8 ucLedRed;                   /* 44 */
 | |
|     Uint8 ucLedGreen;                 /* 45 */
 | |
|     Uint8 ucLedBlue;                  /* 46 */
 | |
| } DS5EffectsState_t;
 | |
| 
 | |
| static void CyclePS5AudioRoute(Controller *device)
 | |
| {
 | |
|     DS5EffectsState_t effects;
 | |
| 
 | |
|     device->audio_route = (device->audio_route + 1) % 4;
 | |
| 
 | |
|     SDL_zero(effects);
 | |
|     switch (device->audio_route) {
 | |
|     case 0:
 | |
|         /* Audio disabled */
 | |
|         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 */
 | |
|         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 */
 | |
|         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 */
 | |
|         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, &effects, sizeof(effects));
 | |
| }
 | |
| 
 | |
| static void CyclePS5TriggerEffect(Controller *device)
 | |
| {
 | |
|     DS5EffectsState_t effects;
 | |
| 
 | |
|     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 */
 | |
|         { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
 | |
|         /* Resistance and vibration when trigger is pulled */
 | |
|         { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
 | |
|     };
 | |
| 
 | |
|     device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects);
 | |
| 
 | |
|     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)
 | |
| {
 | |
|     title_highlighted = false;
 | |
|     title_pressed = false;
 | |
| 
 | |
|     type_highlighted = false;
 | |
|     type_pressed = false;
 | |
| 
 | |
|     ClearGamepadImage(image);
 | |
|     SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
|     SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false);
 | |
|     SetGamepadButtonHighlight(setup_mapping_button, false, false);
 | |
|     SetGamepadButtonHighlight(done_mapping_button, false, false);
 | |
|     SetGamepadButtonHighlight(cancel_button, false, false);
 | |
|     SetGamepadButtonHighlight(clear_button, false, false);
 | |
|     SetGamepadButtonHighlight(copy_button, false, false);
 | |
|     SetGamepadButtonHighlight(paste_button, false, false);
 | |
| }
 | |
| 
 | |
| static void UpdateButtonHighlights(float x, float y, bool button_down)
 | |
| {
 | |
|     ClearButtonHighlights();
 | |
| 
 | |
|     if (display_mode == CONTROLLER_MODE_TESTING) {
 | |
|         SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down);
 | |
|     } else if (display_mode == CONTROLLER_MODE_BINDING) {
 | |
|         SDL_FPoint point;
 | |
|         int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID;
 | |
|         char *joystick_highlight_element;
 | |
| 
 | |
|         point.x = x;
 | |
|         point.y = y;
 | |
|         if (SDL_PointInRectFloat(&point, &title_area)) {
 | |
|             title_highlighted = true;
 | |
|             title_pressed = button_down;
 | |
|         } else {
 | |
|             title_highlighted = false;
 | |
|             title_pressed = false;
 | |
|         }
 | |
| 
 | |
|         if (SDL_PointInRectFloat(&point, &type_area)) {
 | |
|             type_highlighted = true;
 | |
|             type_pressed = button_down;
 | |
|         } else {
 | |
|             type_highlighted = false;
 | |
|             type_pressed = false;
 | |
|         }
 | |
| 
 | |
|         if (controller->joystick != virtual_joystick) {
 | |
|             gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
 | |
|         }
 | |
|         if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|             gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y);
 | |
|         }
 | |
|         SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down);
 | |
| 
 | |
|         if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
 | |
|             int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y);
 | |
|             SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down);
 | |
|         }
 | |
| 
 | |
|         joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y);
 | |
|         SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down);
 | |
|         SDL_free(joystick_highlight_element);
 | |
| 
 | |
|         SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down);
 | |
|         SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down);
 | |
|         SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down);
 | |
|         SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down);
 | |
|         SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int StandardizeAxisValue(int nValue)
 | |
| {
 | |
|     if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) {
 | |
|         return SDL_JOYSTICK_AXIS_MAX;
 | |
|     } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) {
 | |
|         return SDL_JOYSTICK_AXIS_MIN;
 | |
|     } else {
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void RefreshControllerName(void)
 | |
| {
 | |
|     const char *name = NULL;
 | |
| 
 | |
|     SDL_free(controller_name);
 | |
|     controller_name = NULL;
 | |
| 
 | |
|     if (controller) {
 | |
|         if (controller->gamepad) {
 | |
|             name = SDL_GetGamepadName(controller->gamepad);
 | |
|         } else {
 | |
|             name = SDL_GetJoystickName(controller->joystick);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (name) {
 | |
|         controller_name = SDL_strdup(name);
 | |
|     } else {
 | |
|         controller_name = SDL_strdup("");
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void SetAndFreeGamepadMapping(char *mapping)
 | |
| {
 | |
|     SDL_SetGamepadMapping(controller->id, mapping);
 | |
|     SDL_free(mapping);
 | |
| }
 | |
| 
 | |
| static void SetCurrentBindingElement(int element, bool flow)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
 | |
|         RefreshControllerName();
 | |
|     }
 | |
| 
 | |
|     if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         binding_flow_direction = 0;
 | |
|         last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
 | |
|     } else {
 | |
|         last_binding_element = binding_element;
 | |
|     }
 | |
|     binding_element = element;
 | |
|     binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH);
 | |
|     binding_advance_time = 0;
 | |
| 
 | |
|     for (i = 0; i < controller->num_axes; ++i) {
 | |
|         controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue;
 | |
|     }
 | |
| 
 | |
|     SetGamepadDisplaySelected(gamepad_elements, element);
 | |
| }
 | |
| 
 | |
| static void SetNextBindingElement(void)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) {
 | |
|         if (binding_element == s_arrBindingOrder[i]) {
 | |
|             binding_flow_direction = 1;
 | |
|             SetCurrentBindingElement(s_arrBindingOrder[i + 1], true);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
| }
 | |
| 
 | |
| static void SetPrevBindingElement(void)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) {
 | |
|         if (binding_element == s_arrBindingOrder[i]) {
 | |
|             binding_flow_direction = -1;
 | |
|             SetCurrentBindingElement(s_arrBindingOrder[i - 1], true);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
| }
 | |
| 
 | |
| static void StopBinding(void)
 | |
| {
 | |
|     SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
| }
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|     int axis;
 | |
|     int direction;
 | |
| } AxisInfo;
 | |
| 
 | |
| static bool ParseAxisInfo(const char *description, AxisInfo *info)
 | |
| {
 | |
|     if (!description) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if (*description == '-') {
 | |
|         info->direction = -1;
 | |
|         ++description;
 | |
|     } else if (*description == '+') {
 | |
|         info->direction = 1;
 | |
|         ++description;
 | |
|     } else {
 | |
|         info->direction = 0;
 | |
|     }
 | |
| 
 | |
|     if (description[0] == 'a' && SDL_isdigit(description[1])) {
 | |
|         ++description;
 | |
|         info->axis = SDL_atoi(description);
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static void CommitBindingElement(const char *binding, bool force)
 | |
| {
 | |
|     char *mapping;
 | |
|     int direction = 1;
 | |
|     bool ignore_binding = false;
 | |
| 
 | |
|     if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (controller->mapping) {
 | |
|         mapping = SDL_strdup(controller->mapping);
 | |
|     } else {
 | |
|         mapping = NULL;
 | |
|     }
 | |
| 
 | |
|     /* If the controller generates multiple events for a single element, pick the best one */
 | |
|     if (!force && binding_advance_time) {
 | |
|         char *current = GetElementBinding(mapping, binding_element);
 | |
|         bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_COUNT);
 | |
|         bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_COUNT &&
 | |
|                                 binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX);
 | |
|         bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
 | |
|                                    binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER);
 | |
|         bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP ||
 | |
|                                 binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN ||
 | |
|                                 binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT ||
 | |
|                                 binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
 | |
| 
 | |
|         if (native_button) {
 | |
|             bool current_button = (current && *current == 'b');
 | |
|             bool proposed_button = (binding && *binding == 'b');
 | |
|             if (current_button && !proposed_button) {
 | |
|                 ignore_binding = true;
 | |
|             }
 | |
|             /* Use the lower index button (we map from lower to higher button index) */
 | |
|             if (current_button && proposed_button && current[1] < binding[1]) {
 | |
|                 ignore_binding = true;
 | |
|             }
 | |
|         }
 | |
|         if (native_axis) {
 | |
|             AxisInfo current_axis_info  = { 0, 0 };
 | |
|             AxisInfo proposed_axis_info = { 0, 0 };
 | |
|             bool current_axis = ParseAxisInfo(current, ¤t_axis_info);
 | |
|             bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info);
 | |
| 
 | |
|             if (current_axis) {
 | |
|                 /* Ignore this unless the proposed binding extends the existing axis */
 | |
|                 ignore_binding = true;
 | |
| 
 | |
|                 if (native_trigger &&
 | |
|                     ((*current == '-' && *binding == '+' &&
 | |
|                       SDL_strcmp(current + 1, binding + 1) == 0) ||
 | |
|                      (*current == '+' && *binding == '-' &&
 | |
|                       SDL_strcmp(current + 1, binding + 1) == 0))) {
 | |
|                     /* Merge two half axes into a whole axis for a trigger */
 | |
|                     ++binding;
 | |
|                     ignore_binding = false;
 | |
|                 }
 | |
| 
 | |
|                 /* Use the lower index axis (we map from lower to higher axis index) */
 | |
|                 if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) {
 | |
|                     ignore_binding = false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if (native_dpad) {
 | |
|             bool current_hat = (current && *current == 'h');
 | |
|             bool proposed_hat = (binding && *binding == 'h');
 | |
|             if (current_hat && !proposed_hat) {
 | |
|                 ignore_binding = true;
 | |
|             }
 | |
|             /* Use the lower index hat (we map from lower to higher hat index) */
 | |
|             if (current_hat && proposed_hat && current[1] < binding[1]) {
 | |
|                 ignore_binding = true;
 | |
|             }
 | |
|         }
 | |
|         SDL_free(current);
 | |
|     }
 | |
| 
 | |
|     if (!ignore_binding && binding_flow && !force) {
 | |
|         int existing = GetElementForBinding(mapping, binding);
 | |
|         if (existing != SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|             SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
 | |
|             SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
 | |
|             SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
 | |
|             if (binding_element == action_forward) {
 | |
|                 /* Bind it! */
 | |
|             } else if (binding_element == action_backward) {
 | |
|                 if (existing == action_forward) {
 | |
|                     bool bound_backward = MappingHasElement(controller->mapping, action_backward);
 | |
|                     if (bound_backward) {
 | |
|                         /* Just move on to the next one */
 | |
|                         ignore_binding = true;
 | |
|                         SetNextBindingElement();
 | |
|                     } else {
 | |
|                         /* You can't skip the backward action, go back and start over */
 | |
|                         ignore_binding = true;
 | |
|                         SetPrevBindingElement();
 | |
|                     }
 | |
|                 } else if (existing == action_backward && binding_flow_direction == -1) {
 | |
|                     /* Keep going backwards */
 | |
|                     ignore_binding = true;
 | |
|                     SetPrevBindingElement();
 | |
|                 } else {
 | |
|                     /* Bind it! */
 | |
|                 }
 | |
|             } else if (existing == action_forward) {
 | |
|                 /* Just move on to the next one */
 | |
|                 ignore_binding = true;
 | |
|                 SetNextBindingElement();
 | |
|             } else if (existing == action_backward) {
 | |
|                 ignore_binding = true;
 | |
|                 SetPrevBindingElement();
 | |
|             } else if (existing == binding_element) {
 | |
|                 /* We're rebinding the same thing, just move to the next one */
 | |
|                 ignore_binding = true;
 | |
|                 SetNextBindingElement();
 | |
|             } else if (existing == action_delete) {
 | |
|                 /* Clear the current binding and move to the next one */
 | |
|                 binding = NULL;
 | |
|                 direction = 1;
 | |
|                 force = true;
 | |
|             } else if (binding_element != action_forward &&
 | |
|                        binding_element != action_backward) {
 | |
|                 /* Actually, we'll just clear the existing binding */
 | |
|                 /*ignore_binding = true;*/
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (ignore_binding) {
 | |
|         SDL_free(mapping);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     mapping = ClearMappingBinding(mapping, binding);
 | |
|     mapping = SetElementBinding(mapping, binding_element, binding);
 | |
|     SetAndFreeGamepadMapping(mapping);
 | |
| 
 | |
|     if (force) {
 | |
|         if (binding_flow) {
 | |
|             if (direction > 0) {
 | |
|                 SetNextBindingElement();
 | |
|             } else if (direction < 0) {
 | |
|                 SetPrevBindingElement();
 | |
|             }
 | |
|         } else {
 | |
|             StopBinding();
 | |
|         }
 | |
|     } else {
 | |
|         /* Wait to see if any more bindings come in */
 | |
|         binding_advance_time = SDL_GetTicks() + 30;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void ClearBinding(void)
 | |
| {
 | |
|     CommitBindingElement(NULL, true);
 | |
| }
 | |
| 
 | |
| static void SetDisplayMode(ControllerDisplayMode mode)
 | |
| {
 | |
|     float x, y;
 | |
|     SDL_MouseButtonFlags button_state;
 | |
| 
 | |
|     if (mode == CONTROLLER_MODE_BINDING) {
 | |
|         /* Make a backup of the current mapping */
 | |
|         if (controller->mapping) {
 | |
|             backup_mapping = SDL_strdup(controller->mapping);
 | |
|         }
 | |
|         mapping_controller = controller->id;
 | |
|         if (MappingHasBindings(backup_mapping)) {
 | |
|             SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
|         } else {
 | |
|             SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, true);
 | |
|         }
 | |
|     } else {
 | |
|         if (backup_mapping) {
 | |
|             SDL_free(backup_mapping);
 | |
|             backup_mapping = NULL;
 | |
|         }
 | |
|         mapping_controller = 0;
 | |
|         StopBinding();
 | |
|     }
 | |
| 
 | |
|     display_mode = mode;
 | |
|     SetGamepadImageDisplayMode(image, mode);
 | |
|     SetGamepadDisplayDisplayMode(gamepad_elements, mode);
 | |
| 
 | |
|     button_state = SDL_GetMouseState(&x, &y);
 | |
|     SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y);
 | |
|     UpdateButtonHighlights(x, y, button_state ? true : false);
 | |
| }
 | |
| 
 | |
| static void CancelMapping(void)
 | |
| {
 | |
|     SetAndFreeGamepadMapping(backup_mapping);
 | |
|     backup_mapping = NULL;
 | |
| 
 | |
|     SetDisplayMode(CONTROLLER_MODE_TESTING);
 | |
| }
 | |
| 
 | |
| static void ClearMapping(void)
 | |
| {
 | |
|     SetAndFreeGamepadMapping(NULL);
 | |
|     SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
 | |
| }
 | |
| 
 | |
| static void CopyMapping(void)
 | |
| {
 | |
|     if (controller && controller->mapping) {
 | |
|         SDL_SetClipboardText(controller->mapping);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void PasteMapping(void)
 | |
| {
 | |
|     if (controller) {
 | |
|         char *mapping = SDL_GetClipboardText();
 | |
|         if (MappingHasBindings(mapping)) {
 | |
|             StopBinding();
 | |
|             SDL_SetGamepadMapping(controller->id, mapping);
 | |
|             RefreshControllerName();
 | |
|         } else {
 | |
|             /* Not a valid mapping, ignore it */
 | |
|         }
 | |
|         SDL_free(mapping);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void CommitControllerName(void)
 | |
| {
 | |
|     char *mapping = NULL;
 | |
| 
 | |
|     if (controller->mapping) {
 | |
|         mapping = SDL_strdup(controller->mapping);
 | |
|     } else {
 | |
|         mapping = NULL;
 | |
|     }
 | |
|     mapping = SetMappingName(mapping, controller_name);
 | |
|     SetAndFreeGamepadMapping(mapping);
 | |
| }
 | |
| 
 | |
| static void AddControllerNameText(const char *text)
 | |
| {
 | |
|     size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0);
 | |
|     size_t text_length = SDL_strlen(text);
 | |
|     size_t size = current_length + text_length + 1;
 | |
|     char *name = (char *)SDL_realloc(controller_name, size);
 | |
|     if (name) {
 | |
|         SDL_memcpy(&name[current_length], text, text_length + 1);
 | |
|         controller_name = name;
 | |
|     }
 | |
|     CommitControllerName();
 | |
| }
 | |
| 
 | |
| static void BackspaceControllerName(void)
 | |
| {
 | |
|     size_t length = (controller_name ? SDL_strlen(controller_name) : 0);
 | |
|     if (length > 0) {
 | |
|         controller_name[length - 1] = '\0';
 | |
|     }
 | |
|     CommitControllerName();
 | |
| }
 | |
| 
 | |
| static void ClearControllerName(void)
 | |
| {
 | |
|     if (controller_name) {
 | |
|         *controller_name = '\0';
 | |
|     }
 | |
|     CommitControllerName();
 | |
| }
 | |
| 
 | |
| static void CopyControllerName(void)
 | |
| {
 | |
|     SDL_SetClipboardText(controller_name);
 | |
| }
 | |
| 
 | |
| static void PasteControllerName(void)
 | |
| {
 | |
|     SDL_free(controller_name);
 | |
|     controller_name = SDL_GetClipboardText();
 | |
|     CommitControllerName();
 | |
| }
 | |
| 
 | |
| static void CommitGamepadType(SDL_GamepadType type)
 | |
| {
 | |
|     char *mapping = NULL;
 | |
| 
 | |
|     if (controller->mapping) {
 | |
|         mapping = SDL_strdup(controller->mapping);
 | |
|     } else {
 | |
|         mapping = NULL;
 | |
|     }
 | |
|     mapping = SetMappingType(mapping, type);
 | |
|     SetAndFreeGamepadMapping(mapping);
 | |
| }
 | |
| 
 | |
| static const char *GetBindingInstruction(void)
 | |
| {
 | |
|     switch (binding_element) {
 | |
|     case SDL_GAMEPAD_ELEMENT_INVALID:
 | |
|         return "Select an element to bind from the list on the left";
 | |
|     case SDL_GAMEPAD_BUTTON_SOUTH:
 | |
|     case SDL_GAMEPAD_BUTTON_EAST:
 | |
|     case SDL_GAMEPAD_BUTTON_WEST:
 | |
|     case SDL_GAMEPAD_BUTTON_NORTH:
 | |
|         switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) {
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_A:
 | |
|             return "Press the A button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_B:
 | |
|             return "Press the B button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_X:
 | |
|             return "Press the X button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_Y:
 | |
|             return "Press the Y button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
 | |
|             return "Press the Cross (X) button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
 | |
|             return "Press the Circle button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
 | |
|             return "Press the Square button";
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
 | |
|             return "Press the Triangle button";
 | |
|         default:
 | |
|             return "";
 | |
|         }
 | |
|         break;
 | |
|     case SDL_GAMEPAD_BUTTON_BACK:
 | |
|         return "Press the left center button (Back/View/Share)";
 | |
|     case SDL_GAMEPAD_BUTTON_GUIDE:
 | |
|         return "Press the center button (Home/Guide)";
 | |
|     case SDL_GAMEPAD_BUTTON_START:
 | |
|         return "Press the right center button (Start/Menu/Options)";
 | |
|     case SDL_GAMEPAD_BUTTON_LEFT_STICK:
 | |
|         return "Press the left thumbstick button (LSB/L3)";
 | |
|     case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
 | |
|         return "Press the right thumbstick button (RSB/R3)";
 | |
|     case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
 | |
|         return "Press the left shoulder button (LB/L1)";
 | |
|     case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
 | |
|         return "Press the right shoulder button (RB/R1)";
 | |
|     case SDL_GAMEPAD_BUTTON_DPAD_UP:
 | |
|         return "Press the D-Pad up";
 | |
|     case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
 | |
|         return "Press the D-Pad down";
 | |
|     case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
 | |
|         return "Press the D-Pad left";
 | |
|     case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
 | |
|         return "Press the D-Pad right";
 | |
|     case SDL_GAMEPAD_BUTTON_MISC1:
 | |
|         return "Press the bottom center button (Share/Capture)";
 | |
|     case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
 | |
|         return "Press the upper paddle under your right hand";
 | |
|     case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
 | |
|         return "Press the upper paddle under your left hand";
 | |
|     case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
 | |
|         return "Press the lower paddle under your right hand";
 | |
|     case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
 | |
|         return "Press the lower paddle under your left hand";
 | |
|     case SDL_GAMEPAD_BUTTON_TOUCHPAD:
 | |
|         return "Press down on the touchpad";
 | |
|     case SDL_GAMEPAD_BUTTON_MISC2:
 | |
|     case SDL_GAMEPAD_BUTTON_MISC3:
 | |
|     case SDL_GAMEPAD_BUTTON_MISC4:
 | |
|     case SDL_GAMEPAD_BUTTON_MISC5:
 | |
|     case SDL_GAMEPAD_BUTTON_MISC6:
 | |
|         return "Press any additional button not already bound";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
 | |
|         return "Move the left thumbstick to the left";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
 | |
|         return "Move the left thumbstick to the right";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
 | |
|         return "Move the left thumbstick up";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
 | |
|         return "Move the left thumbstick down";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
 | |
|         return "Move the right thumbstick to the left";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
 | |
|         return "Move the right thumbstick to the right";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
 | |
|         return "Move the right thumbstick up";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
 | |
|         return "Move the right thumbstick down";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
 | |
|         return "Pull the left trigger (LT/L2)";
 | |
|     case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
 | |
|         return "Pull the right trigger (RT/R2)";
 | |
|     case SDL_GAMEPAD_ELEMENT_NAME:
 | |
|         return "Type the name of your controller";
 | |
|     case SDL_GAMEPAD_ELEMENT_TYPE:
 | |
|         return "Select the type of your controller";
 | |
|     default:
 | |
|         return "";
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int FindController(SDL_JoystickID id)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     for (i = 0; i < num_controllers; ++i) {
 | |
|         if (id == controllers[i].id) {
 | |
|             return i;
 | |
|         }
 | |
|     }
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| static void SetController(SDL_JoystickID id)
 | |
| {
 | |
|     int i = FindController(id);
 | |
| 
 | |
|     if (i < 0 && num_controllers > 0) {
 | |
|         i = 0;
 | |
|     }
 | |
| 
 | |
|     if (i >= 0) {
 | |
|         controller = &controllers[i];
 | |
|     } else {
 | |
|         controller = NULL;
 | |
|     }
 | |
| 
 | |
|     RefreshControllerName();
 | |
| }
 | |
| 
 | |
| static void AddController(SDL_JoystickID id, bool verbose)
 | |
| {
 | |
|     Controller *new_controllers;
 | |
|     Controller *new_controller;
 | |
|     SDL_Joystick *joystick;
 | |
| 
 | |
|     if (FindController(id) >= 0) {
 | |
|         /* We already have this controller */
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers));
 | |
|     if (!new_controllers) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     controller = NULL;
 | |
|     controllers = new_controllers;
 | |
|     new_controller = &new_controllers[num_controllers++];
 | |
|     SDL_zerop(new_controller);
 | |
|     new_controller->id = id;
 | |
| 
 | |
|     new_controller->joystick = SDL_OpenJoystick(id);
 | |
|     if (new_controller->joystick) {
 | |
|         new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick);
 | |
|         new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state));
 | |
|     }
 | |
| 
 | |
|     joystick = new_controller->joystick;
 | |
|     if (joystick) {
 | |
|         if (verbose && !SDL_IsGamepad(id)) {
 | |
|             const char *name = SDL_GetJoystickName(joystick);
 | |
|             const char *path = SDL_GetJoystickPath(joystick);
 | |
|             char guid[33];
 | |
|             SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : "");
 | |
|             SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid));
 | |
|             SDL_Log("No gamepad mapping for %s", guid);
 | |
|         }
 | |
|     } else {
 | |
|         SDL_Log("Couldn't open joystick: %s", SDL_GetError());
 | |
|     }
 | |
| 
 | |
|     if (mapping_controller) {
 | |
|         SetController(mapping_controller);
 | |
|     } else {
 | |
|         SetController(id);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void DelController(SDL_JoystickID id)
 | |
| {
 | |
|     int i = FindController(id);
 | |
| 
 | |
|     if (i < 0) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) {
 | |
|         SetDisplayMode(CONTROLLER_MODE_TESTING);
 | |
|     }
 | |
| 
 | |
|     /* Reset trigger state */
 | |
|     if (controllers[i].trigger_effect != 0) {
 | |
|         controllers[i].trigger_effect = -1;
 | |
|         CyclePS5TriggerEffect(&controllers[i]);
 | |
|     }
 | |
|     SDL_assert(controllers[i].gamepad == NULL);
 | |
|     if (controllers[i].axis_state) {
 | |
|         SDL_free(controllers[i].axis_state);
 | |
|     }
 | |
|     if (controllers[i].joystick) {
 | |
|         SDL_CloseJoystick(controllers[i].joystick);
 | |
|     }
 | |
| 
 | |
|     --num_controllers;
 | |
|     if (i < num_controllers) {
 | |
|         SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers));
 | |
|     }
 | |
| 
 | |
|     if (mapping_controller) {
 | |
|         SetController(mapping_controller);
 | |
|     } else {
 | |
|         SetController(id);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void HandleGamepadRemapped(SDL_JoystickID id)
 | |
| {
 | |
|     char *mapping;
 | |
|     int i = FindController(id);
 | |
| 
 | |
|     SDL_assert(i >= 0);
 | |
|     if (i < 0) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (!controllers[i].gamepad) {
 | |
|         /* Failed to open this controller */
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Get the current mapping */
 | |
|     mapping = SDL_GetGamepadMapping(controllers[i].gamepad);
 | |
| 
 | |
|     /* Make sure the mapping has a valid name */
 | |
|     if (mapping && !MappingHasName(mapping)) {
 | |
|         mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick));
 | |
|     }
 | |
| 
 | |
|     SDL_free(controllers[i].mapping);
 | |
|     controllers[i].mapping = mapping;
 | |
|     controllers[i].has_bindings = MappingHasBindings(mapping);
 | |
| }
 | |
| 
 | |
| static void HandleGamepadAdded(SDL_JoystickID id, bool verbose)
 | |
| {
 | |
|     SDL_Gamepad *gamepad;
 | |
|     Uint16 firmware_version;
 | |
|     SDL_SensorType sensors[] = {
 | |
|         SDL_SENSOR_ACCEL,
 | |
|         SDL_SENSOR_GYRO,
 | |
|         SDL_SENSOR_ACCEL_L,
 | |
|         SDL_SENSOR_GYRO_L,
 | |
|         SDL_SENSOR_ACCEL_R,
 | |
|         SDL_SENSOR_GYRO_R
 | |
|     };
 | |
|     int i;
 | |
| 
 | |
|     i = FindController(id);
 | |
|     if (i < 0) {
 | |
|         return;
 | |
|     }
 | |
|     SDL_Log("Gamepad %" SDL_PRIu32 " added", id);
 | |
| 
 | |
|     SDL_assert(!controllers[i].gamepad);
 | |
|     controllers[i].gamepad = SDL_OpenGamepad(id);
 | |
| 
 | |
|     gamepad = controllers[i].gamepad;
 | |
|     if (gamepad) {
 | |
|         if (verbose) {
 | |
|             SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad);
 | |
|             const char *name = SDL_GetGamepadName(gamepad);
 | |
|             const char *path = SDL_GetGamepadPath(gamepad);
 | |
|             SDL_GUID guid = SDL_GetGamepadGUIDForID(id);
 | |
|             char guid_string[33];
 | |
|             SDL_GUIDToString(guid, guid_string, sizeof(guid_string));
 | |
|             SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : "");
 | |
| 
 | |
|             firmware_version = SDL_GetGamepadFirmwareVersion(gamepad);
 | |
|             if (firmware_version) {
 | |
|                 SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version);
 | |
|             }
 | |
| 
 | |
|             if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) {
 | |
|                 SDL_Log("Has player LED");
 | |
|             }
 | |
| 
 | |
|             if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) {
 | |
|                 SDL_Log("Rumble supported");
 | |
|             }
 | |
| 
 | |
|             if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) {
 | |
|                 SDL_Log("Trigger rumble supported");
 | |
|             }
 | |
| 
 | |
|             if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) {
 | |
|                 SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad));
 | |
|             }
 | |
| 
 | |
|             switch (SDL_GetJoystickTypeForID(id)) {
 | |
|             case SDL_JOYSTICK_TYPE_WHEEL:
 | |
|                 SDL_Log("Controller is a wheel");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_ARCADE_STICK:
 | |
|                 SDL_Log("Controller is an arcade stick");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
 | |
|                 SDL_Log("Controller is a flight stick");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_DANCE_PAD:
 | |
|                 SDL_Log("Controller is a dance pad");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_GUITAR:
 | |
|                 SDL_Log("Controller is a guitar");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_DRUM_KIT:
 | |
|                 SDL_Log("Controller is a drum kit");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_ARCADE_PAD:
 | |
|                 SDL_Log("Controller is an arcade pad");
 | |
|                 break;
 | |
|             case SDL_JOYSTICK_TYPE_THROTTLE:
 | |
|                 SDL_Log("Controller is a throttle");
 | |
|                 break;
 | |
|             default:
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         for (i = 0; i < SDL_arraysize(sensors); ++i) {
 | |
|             SDL_SensorType sensor = sensors[i];
 | |
| 
 | |
|             if (SDL_GamepadHasSensor(gamepad, sensor)) {
 | |
|                 if (verbose) {
 | |
|                     SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor));
 | |
|                 }
 | |
|                 SDL_SetGamepadSensorEnabled(gamepad, sensor, true);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (verbose) {
 | |
|             char *mapping = SDL_GetGamepadMapping(gamepad);
 | |
|             if (mapping) {
 | |
|                 SDL_Log("Mapping: %s", mapping);
 | |
|                 SDL_free(mapping);
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         SDL_Log("Couldn't open gamepad: %s", SDL_GetError());
 | |
|     }
 | |
| 
 | |
|     HandleGamepadRemapped(id);
 | |
|     SetController(id);
 | |
| }
 | |
| 
 | |
| static void HandleGamepadRemoved(SDL_JoystickID id)
 | |
| {
 | |
|     int i = FindController(id);
 | |
| 
 | |
|     SDL_assert(i >= 0);
 | |
|     if (i < 0) {
 | |
|         return;
 | |
|     }
 | |
|     SDL_Log("Gamepad %" SDL_PRIu32 " removed", id);
 | |
| 
 | |
|     if (controllers[i].mapping) {
 | |
|         SDL_free(controllers[i].mapping);
 | |
|         controllers[i].mapping = NULL;
 | |
|     }
 | |
|     if (controllers[i].gamepad) {
 | |
|         SDL_CloseGamepad(controllers[i].gamepad);
 | |
|         controllers[i].gamepad = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static Uint16 ConvertAxisToRumble(Sint16 axisval)
 | |
| {
 | |
|     /* Only start rumbling if the axis is past the halfway point */
 | |
|     const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f);
 | |
|     if (axisval > half_axis) {
 | |
|         return (Uint16)(axisval - half_axis) * 4;
 | |
|     } else {
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static bool ShowingFront(void)
 | |
| {
 | |
|     bool showing_front = true;
 | |
|     int i;
 | |
| 
 | |
|     /* Show the back of the gamepad if the paddles are being held or bound */
 | |
|     for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) {
 | |
|         if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) ||
 | |
|             binding_element == i) {
 | |
|             showing_front = false;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
 | |
|         showing_front = false;
 | |
|     }
 | |
|     return showing_front;
 | |
| }
 | |
| 
 | |
| static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index)
 | |
| {
 | |
|     SDL_Log("Virtual Gamepad: player index set to %d", player_index);
 | |
| }
 | |
| 
 | |
| static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 | |
| {
 | |
|     SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
 | |
| {
 | |
|     SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue)
 | |
| {
 | |
|     SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static void OpenVirtualGamepad(void)
 | |
| {
 | |
|     SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } };
 | |
|     SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f };
 | |
|     SDL_VirtualJoystickDesc desc;
 | |
|     SDL_JoystickID virtual_id;
 | |
| 
 | |
|     if (virtual_joystick) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     SDL_INIT_INTERFACE(&desc);
 | |
|     desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
 | |
|     desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
 | |
|     desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
 | |
|     desc.ntouchpads = 1;
 | |
|     desc.touchpads = &virtual_touchpad;
 | |
|     desc.nsensors = 1;
 | |
|     desc.sensors = &virtual_sensor;
 | |
|     desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex;
 | |
|     desc.Rumble = VirtualGamepadRumble;
 | |
|     desc.RumbleTriggers = VirtualGamepadRumbleTriggers;
 | |
|     desc.SetLED = VirtualGamepadSetLED;
 | |
| 
 | |
|     virtual_id = SDL_AttachVirtualJoystick(&desc);
 | |
|     if (virtual_id == 0) {
 | |
|         SDL_Log("Couldn't attach virtual device: %s", SDL_GetError());
 | |
|     } else {
 | |
|         virtual_joystick = SDL_OpenJoystick(virtual_id);
 | |
|         if (!virtual_joystick) {
 | |
|             SDL_Log("Couldn't open virtual device: %s", SDL_GetError());
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void CloseVirtualGamepad(void)
 | |
| {
 | |
|     int i;
 | |
|     SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL);
 | |
|     if (joysticks) {
 | |
|         for (i = 0; joysticks[i]; ++i) {
 | |
|             SDL_JoystickID instance_id = joysticks[i];
 | |
|             if (SDL_IsJoystickVirtual(instance_id)) {
 | |
|                 SDL_DetachVirtualJoystick(instance_id);
 | |
|             }
 | |
|         }
 | |
|         SDL_free(joysticks);
 | |
|     }
 | |
| 
 | |
|     if (virtual_joystick) {
 | |
|         SDL_CloseJoystick(virtual_joystick);
 | |
|         virtual_joystick = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void VirtualGamepadMouseMotion(float x, float y)
 | |
| {
 | |
|     if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
 | |
|         if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
 | |
|             const float MOVING_DISTANCE = 2.0f;
 | |
|             if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE ||
 | |
|                 SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) {
 | |
|                 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false);
 | |
|                 virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
 | |
|         if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
 | |
|             virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
 | |
|             int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
 | |
|             float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f);
 | |
|             Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range));
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value);
 | |
|         } else {
 | |
|             float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f);
 | |
|             float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f);
 | |
|             Sint16 valueX, valueY;
 | |
| 
 | |
|             if (distanceX >= 0) {
 | |
|                 valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX);
 | |
|             } else {
 | |
|                 valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN);
 | |
|             }
 | |
|             if (distanceY >= 0) {
 | |
|                 valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX);
 | |
|             } else {
 | |
|                 valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN);
 | |
|             }
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX);
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (virtual_touchpad_active) {
 | |
|         SDL_FRect touchpad;
 | |
|         GetGamepadTouchpadArea(image, &touchpad);
 | |
|         virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
 | |
|         virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
 | |
|         SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void VirtualGamepadMouseDown(float x, float y)
 | |
| {
 | |
|     int element = GetGamepadImageElementAt(image, x, y);
 | |
| 
 | |
|     if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         SDL_FPoint point = { x, y };
 | |
|         SDL_FRect touchpad;
 | |
|         GetGamepadTouchpadArea(image, &touchpad);
 | |
|         if (SDL_PointInRectFloat(&point, &touchpad)) {
 | |
|             virtual_touchpad_active = true;
 | |
|             virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
 | |
|             virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
 | |
|             SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (element < SDL_GAMEPAD_BUTTON_COUNT) {
 | |
|         virtual_button_active = (SDL_GamepadButton)element;
 | |
|         SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true);
 | |
|     } else {
 | |
|         switch (element) {
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
 | |
|             virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX;
 | |
|             break;
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
 | |
|             virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX;
 | |
|             break;
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
 | |
|             virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
 | |
|             break;
 | |
|         case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
 | |
|             virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
 | |
|             break;
 | |
|         }
 | |
|         virtual_axis_start_x = x;
 | |
|         virtual_axis_start_y = y;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void VirtualGamepadMouseUp(float x, float y)
 | |
| {
 | |
|     if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
 | |
|         SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false);
 | |
|         virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
 | |
|     }
 | |
| 
 | |
|     if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
 | |
|         if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
 | |
|             virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN);
 | |
|         } else {
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0);
 | |
|             SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0);
 | |
|         }
 | |
|         virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
 | |
|     }
 | |
| 
 | |
|     if (virtual_touchpad_active) {
 | |
|         SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f);
 | |
|         virtual_touchpad_active = false;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void DrawGamepadWaiting(SDL_Renderer *renderer)
 | |
| {
 | |
|     const char *text = "Waiting for gamepad, press A to add a virtual controller";
 | |
|     float x, y;
 | |
| 
 | |
|     x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
 | |
|     y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
 | |
|     SDLTest_DrawString(renderer, x, y, text);
 | |
| }
 | |
| 
 | |
| static void DrawGamepadInfo(SDL_Renderer *renderer)
 | |
| {
 | |
|     const char *type;
 | |
|     const char *serial;
 | |
|     char text[128];
 | |
|     float x, y;
 | |
| 
 | |
|     if (title_highlighted) {
 | |
|         Uint8 r, g, b, a;
 | |
| 
 | |
|         SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
| 
 | |
|         if (title_pressed) {
 | |
|             SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
 | |
|         } else {
 | |
|             SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
 | |
|         }
 | |
|         SDL_RenderFillRect(renderer, &title_area);
 | |
| 
 | |
|         SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
|     }
 | |
| 
 | |
|     if (type_highlighted) {
 | |
|         Uint8 r, g, b, a;
 | |
| 
 | |
|         SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
| 
 | |
|         if (type_pressed) {
 | |
|             SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
 | |
|         } else {
 | |
|             SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
 | |
|         }
 | |
|         SDL_RenderFillRect(renderer, &type_area);
 | |
| 
 | |
|         SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
|     }
 | |
| 
 | |
|     if (controller->joystick) {
 | |
|         SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick));
 | |
|         x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f;
 | |
|         y = 8.0f;
 | |
|         SDLTest_DrawString(renderer, x, y, text);
 | |
|     }
 | |
| 
 | |
|     if (controller_name && *controller_name) {
 | |
|         x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
 | |
|         y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
 | |
|         SDLTest_DrawString(renderer, x, y, controller_name);
 | |
|     }
 | |
| 
 | |
|     if (SDL_IsJoystickVirtual(controller->id)) {
 | |
|         SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text));
 | |
|         x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
 | |
|         y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f;
 | |
|         SDLTest_DrawString(renderer, x, y, text);
 | |
|     }
 | |
| 
 | |
|     type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad));
 | |
|     x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2;
 | |
|     y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2;
 | |
|     SDLTest_DrawString(renderer, x, y, type);
 | |
| 
 | |
|     if (display_mode == CONTROLLER_MODE_TESTING) {
 | |
|         Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad);
 | |
|         if (steam_handle) {
 | |
|             SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle);
 | |
|             y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT);
 | |
|             x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
 | |
|             SDLTest_DrawString(renderer, x, y, text);
 | |
|         }
 | |
| 
 | |
|         SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
 | |
|                      SDL_GetJoystickVendor(controller->joystick),
 | |
|                      SDL_GetJoystickProduct(controller->joystick));
 | |
|         y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
 | |
|         x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
 | |
|         SDLTest_DrawString(renderer, x, y, text);
 | |
| 
 | |
|         serial = SDL_GetJoystickSerial(controller->joystick);
 | |
|         if (serial && *serial) {
 | |
|             SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
 | |
|             x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
 | |
|             y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
 | |
|             SDLTest_DrawString(renderer, x, y, text);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button)
 | |
| {
 | |
|     switch (SDL_GetGamepadButtonLabelForType(type, button)) {
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_A:
 | |
|         return "A";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_B:
 | |
|         return "B";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_X:
 | |
|         return "X";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_Y:
 | |
|         return "Y";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
 | |
|         return "Cross (X)";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
 | |
|         return "Circle";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
 | |
|         return "Square";
 | |
|     case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
 | |
|         return "Triangle";
 | |
|     default:
 | |
|         return "UNKNOWN";
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void DrawBindingTips(SDL_Renderer *renderer)
 | |
| {
 | |
|     const char *text;
 | |
|     SDL_FRect image_area, button_area;
 | |
|     float x, y;
 | |
| 
 | |
|     GetGamepadImageArea(image, &image_area);
 | |
|     GetGamepadButtonArea(done_mapping_button, &button_area);
 | |
|     x = image_area.x + image_area.w / 2;
 | |
|     y = image_area.y + image_area.h;
 | |
|     y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2;
 | |
| 
 | |
|     text = GetBindingInstruction();
 | |
| 
 | |
|     if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|         SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
 | |
|     } else {
 | |
|         Uint8 r, g, b, a;
 | |
|         SDL_FRect rect;
 | |
|         SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
 | |
|         bool bound_forward = MappingHasElement(controller->mapping, action_forward);
 | |
|         SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
 | |
|         bool bound_backward = MappingHasElement(controller->mapping, action_backward);
 | |
|         SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
 | |
|         bool bound_delete = MappingHasElement(controller->mapping, action_delete);
 | |
| 
 | |
|         y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2;
 | |
| 
 | |
|         rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f;
 | |
|         rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f;
 | |
|         rect.x = x - rect.w / 2;
 | |
|         rect.y = y - 2.0f;
 | |
| 
 | |
|         SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
|         SDL_SetRenderDrawColor(renderer, SELECTED_COLOR);
 | |
|         SDL_RenderFillRect(renderer, &rect);
 | |
|         SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
|         SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
 | |
| 
 | |
|         y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
 | |
| 
 | |
|         if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
 | |
|             text = "(press RETURN to complete)";
 | |
|         } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE ||
 | |
|                    binding_element == action_forward ||
 | |
|                    binding_element == action_backward) {
 | |
|             text = "(press ESC to cancel)";
 | |
|         } else {
 | |
|             static char dynamic_text[128];
 | |
|             SDL_GamepadType type = GetGamepadImageType(image);
 | |
|             if (binding_flow && bound_forward && bound_backward) {
 | |
|                 if (binding_element != action_delete && bound_delete) {
 | |
|                     SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, %s to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward), GetButtonLabel(type, action_delete));
 | |
|                 } else {
 | |
|                     SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, SPACE to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward));
 | |
|                 }
 | |
|                 text = dynamic_text;
 | |
|             } else {
 | |
|                 text = "(press SPACE to delete and ESC to cancel)";
 | |
|             }
 | |
|         }
 | |
|         SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void UpdateGamepadEffects(void)
 | |
| {
 | |
|     if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Update LED based on left thumbstick position */
 | |
|     {
 | |
|         Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
 | |
|         Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
 | |
| 
 | |
|         if (!set_LED) {
 | |
|             set_LED = (x < -8000 || x > 8000 || y > 8000);
 | |
|         }
 | |
|         if (set_LED) {
 | |
|             Uint8 r, g, b;
 | |
| 
 | |
|             if (x < 0) {
 | |
|                 r = (Uint8)(((~x) * 255) / 32767);
 | |
|                 b = 0;
 | |
|             } else {
 | |
|                 r = 0;
 | |
|                 b = (Uint8)(((int)(x)*255) / 32767);
 | |
|             }
 | |
|             if (y > 0) {
 | |
|                 g = (Uint8)(((int)(y)*255) / 32767);
 | |
|             } else {
 | |
|                 g = 0;
 | |
|             }
 | |
| 
 | |
|             SDL_SetGamepadLED(controller->gamepad, r, g, b);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (controller->trigger_effect == 0) {
 | |
|         /* Update rumble based on trigger state */
 | |
|         {
 | |
|             Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
 | |
|             Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
 | |
|             Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
 | |
|             Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
 | |
|             SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
 | |
|         }
 | |
| 
 | |
|         /* Update trigger rumble based on thumbstick state */
 | |
|         {
 | |
|             Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
 | |
|             Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
 | |
|             Uint16 left_rumble = ConvertAxisToRumble(~left);
 | |
|             Uint16 right_rumble = ConvertAxisToRumble(~right);
 | |
| 
 | |
|             SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, 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", 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", 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",
 | |
|                 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 ")",
 | |
|                 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",
 | |
|                 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",
 | |
|                 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:");
 | |
|                     SDL_Log("%s", 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));
 | |
|     }
 | |
| 
 | |
|     /* 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)
 | |
|     */
 | |
|     if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) {
 | |
|         if (binding_flow) {
 | |
|             SetNextBindingElement();
 | |
|         } else {
 | |
|             StopBinding();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* blank screen, set up for drawing this frame. */
 | |
|     SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
 | |
|     SDL_RenderClear(screen);
 | |
|     SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE);
 | |
| 
 | |
|     if (controller) {
 | |
|         SetGamepadImageShowingFront(image, ShowingFront());
 | |
|         UpdateGamepadImageFromGamepad(image, controller->gamepad);
 | |
|         if (display_mode == CONTROLLER_MODE_BINDING &&
 | |
|             binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
 | |
|             SetGamepadImageElement(image, binding_element, true);
 | |
|         }
 | |
|         RenderGamepadImage(image);
 | |
| 
 | |
|         if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
 | |
|             SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad));
 | |
|             RenderGamepadTypeDisplay(gamepad_type);
 | |
|         } else {
 | |
|             RenderGamepadDisplay(gamepad_elements, controller->gamepad);
 | |
|         }
 | |
|         RenderJoystickDisplay(joystick_elements, controller->joystick);
 | |
| 
 | |
|         if (display_mode == CONTROLLER_MODE_TESTING) {
 | |
|             RenderGamepadButton(setup_mapping_button);
 | |
|         } else if (display_mode == CONTROLLER_MODE_BINDING) {
 | |
|             DrawBindingTips(screen);
 | |
|             RenderGamepadButton(done_mapping_button);
 | |
|             RenderGamepadButton(cancel_button);
 | |
|             RenderGamepadButton(clear_button);
 | |
|             if (controller->has_bindings) {
 | |
|                 RenderGamepadButton(copy_button);
 | |
|             }
 | |
|             RenderGamepadButton(paste_button);
 | |
|         }
 | |
| 
 | |
|         DrawGamepadInfo(screen);
 | |
| 
 | |
|         UpdateGamepadEffects();
 | |
|     } else {
 | |
|         DrawGamepadWaiting(screen);
 | |
|     }
 | |
|     SDL_Delay(16);
 | |
|     SDL_RenderPresent(screen);
 | |
| 
 | |
|     return SDL_APP_CONTINUE;
 | |
| }
 | |
| 
 | |
| SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[])
 | |
| {
 | |
|     bool show_mappings = false;
 | |
|     int i;
 | |
|     float content_scale;
 | |
|     int screen_width, screen_height;
 | |
|     SDL_FRect area;
 | |
|     int gamepad_index = -1;
 | |
| 
 | |
|     /* Initialize test framework */
 | |
|     state = SDLTest_CommonCreateState(argv, 0);
 | |
|     if (!state) {
 | |
|         return SDL_APP_FAILURE;
 | |
|     }
 | |
| 
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto");
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1");
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
 | |
|     SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1");
 | |
| 
 | |
|     /* Enable input debug logging */
 | |
|     SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG);
 | |
| 
 | |
|     /* Parse commandline */
 | |
|     for (i = 1; i < argc;) {
 | |
|         int consumed;
 | |
| 
 | |
|         consumed = SDLTest_CommonArg(state, i);
 | |
|         if (!consumed) {
 | |
|             if (SDL_strcmp(argv[i], "--mappings") == 0) {
 | |
|                 show_mappings = true;
 | |
|                 consumed = 1;
 | |
|             } else if (SDL_strcmp(argv[i], "--virtual") == 0) {
 | |
|                 OpenVirtualGamepad();
 | |
|                 consumed = 1;
 | |
|             } else if (gamepad_index < 0) {
 | |
|                 char *endptr = NULL;
 | |
|                 gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0);
 | |
|                 if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) {
 | |
|                     consumed = 1;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if (consumed <= 0) {
 | |
|             static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL };
 | |
|             SDLTest_CommonLogUsage(state, argv[0], options);
 | |
|             return SDL_APP_FAILURE;
 | |
|         }
 | |
| 
 | |
|         i += consumed;
 | |
|     }
 | |
|     if (gamepad_index < 0) {
 | |
|         gamepad_index = 0;
 | |
|     }
 | |
| 
 | |
|     /* 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", SDL_GetError());
 | |
|         return SDL_APP_FAILURE;
 | |
|     }
 | |
| 
 | |
|     SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
 | |
| 
 | |
|     if (show_mappings) {
 | |
|         int count = 0;
 | |
|         char **mappings = SDL_GetGamepadMappings(&count);
 | |
|         int map_i;
 | |
|         SDL_Log("Supported mappings:");
 | |
|         for (map_i = 0; map_i < count; ++map_i) {
 | |
|             SDL_Log("\t%s", mappings[map_i]);
 | |
|         }
 | |
|         SDL_Log("%s", "");
 | |
|         SDL_free(mappings);
 | |
|     }
 | |
| 
 | |
|     /* Create a window to display gamepad state */
 | |
|     content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
 | |
|     if (content_scale == 0.0f) {
 | |
|         content_scale = 1.0f;
 | |
|     }
 | |
|     screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale);
 | |
|     screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale);
 | |
|     window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY);
 | |
|     if (!window) {
 | |
|         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError());
 | |
|         return SDL_APP_FAILURE;
 | |
|     }
 | |
| 
 | |
|     screen = SDL_CreateRenderer(window, NULL);
 | |
|     if (!screen) {
 | |
|         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError());
 | |
|         SDL_DestroyWindow(window);
 | |
|         return SDL_APP_FAILURE;
 | |
|     }
 | |
| 
 | |
|     SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
 | |
|     SDL_RenderClear(screen);
 | |
|     SDL_RenderPresent(screen);
 | |
| 
 | |
|     /* scale for platforms that don't give you the window size you asked for. */
 | |
|     SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT,
 | |
|                                      SDL_LOGICAL_PRESENTATION_LETTERBOX);
 | |
| 
 | |
| 
 | |
|     title_area.w = GAMEPAD_WIDTH;
 | |
|     title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
 | |
|     title_area.x = PANEL_WIDTH + PANEL_SPACING;
 | |
|     title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2;
 | |
| 
 | |
|     type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN;
 | |
|     type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
 | |
|     type_area.x = BUTTON_MARGIN;
 | |
|     type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2;
 | |
| 
 | |
|     image = CreateGamepadImage(screen);
 | |
|     if (!image) {
 | |
|         SDL_DestroyRenderer(screen);
 | |
|         SDL_DestroyWindow(window);
 | |
|         return SDL_APP_FAILURE;
 | |
|     }
 | |
|     SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
 | |
| 
 | |
|     gamepad_elements = CreateGamepadDisplay(screen);
 | |
|     area.x = 0;
 | |
|     area.y = TITLE_HEIGHT;
 | |
|     area.w = PANEL_WIDTH;
 | |
|     area.h = GAMEPAD_HEIGHT;
 | |
|     SetGamepadDisplayArea(gamepad_elements, &area);
 | |
| 
 | |
|     gamepad_type = CreateGamepadTypeDisplay(screen);
 | |
|     area.x = 0;
 | |
|     area.y = TITLE_HEIGHT;
 | |
|     area.w = PANEL_WIDTH;
 | |
|     area.h = GAMEPAD_HEIGHT;
 | |
|     SetGamepadTypeDisplayArea(gamepad_type, &area);
 | |
| 
 | |
|     joystick_elements = CreateJoystickDisplay(screen);
 | |
|     area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING;
 | |
|     area.y = TITLE_HEIGHT;
 | |
|     area.w = PANEL_WIDTH;
 | |
|     area.h = GAMEPAD_HEIGHT;
 | |
|     SetJoystickDisplayArea(joystick_elements, &area);
 | |
| 
 | |
|     setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping");
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING;
 | |
|     area.x = BUTTON_MARGIN;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(setup_mapping_button, &area);
 | |
| 
 | |
|     cancel_button = CreateGamepadButton(screen, "Cancel");
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING;
 | |
|     area.x = BUTTON_MARGIN;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(cancel_button, &area);
 | |
| 
 | |
|     clear_button = CreateGamepadButton(screen, "Clear");
 | |
|     area.x += area.w + BUTTON_PADDING;
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(clear_button, &area);
 | |
| 
 | |
|     copy_button = CreateGamepadButton(screen, "Copy");
 | |
|     area.x += area.w + BUTTON_PADDING;
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(copy_button, &area);
 | |
| 
 | |
|     paste_button = CreateGamepadButton(screen, "Paste");
 | |
|     area.x += area.w + BUTTON_PADDING;
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(paste_button, &area);
 | |
| 
 | |
|     done_mapping_button = CreateGamepadButton(screen, "Done");
 | |
|     area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING);
 | |
|     area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING;
 | |
|     area.x = SCREEN_WIDTH / 2 - area.w / 2;
 | |
|     area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
 | |
|     SetGamepadButtonArea(done_mapping_button, &area);
 | |
| 
 | |
|     /* Process the initial gamepad list */
 | |
|     SDL_AppIterate(NULL);
 | |
| 
 | |
|     if (gamepad_index < num_controllers) {
 | |
|         SetController(controllers[gamepad_index].id);
 | |
|     } else if (num_controllers > 0) {
 | |
|         SetController(controllers[0].id);
 | |
|     }
 | |
| 
 | |
|     return SDL_APP_CONTINUE;
 | |
| }
 | |
| 
 | |
| void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result)
 | |
| {
 | |
|     CloseVirtualGamepad();
 | |
|     while (num_controllers > 0) {
 | |
|         HandleGamepadRemoved(controllers[0].id);
 | |
|         DelController(controllers[0].id);
 | |
|     }
 | |
|     SDL_free(controllers);
 | |
|     SDL_free(controller_name);
 | |
|     DestroyGamepadImage(image);
 | |
|     DestroyGamepadDisplay(gamepad_elements);
 | |
|     DestroyGamepadTypeDisplay(gamepad_type);
 | |
|     DestroyJoystickDisplay(joystick_elements);
 | |
|     DestroyGamepadButton(setup_mapping_button);
 | |
|     DestroyGamepadButton(done_mapping_button);
 | |
|     DestroyGamepadButton(cancel_button);
 | |
|     DestroyGamepadButton(clear_button);
 | |
|     DestroyGamepadButton(copy_button);
 | |
|     DestroyGamepadButton(paste_button);
 | |
|     SDLTest_CleanupTextDrawing();
 | |
|     SDL_DestroyRenderer(screen);
 | |
|     SDL_DestroyWindow(window);
 | |
|     SDL_Quit();
 | |
|     SDLTest_CommonDestroyState(state);
 | |
| }
 | 
